@ornncompute/cli 1.0.72 → 1.0.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +24 -20
  2. package/package.json +1 -1
  3. package/src/admin-capacity-reservations.mjs +2 -12
  4. package/src/admin-command-handlers.mjs +0 -1
  5. package/src/admin-credits.mjs +1 -2
  6. package/src/admin-enrollment.mjs +22 -30
  7. package/src/admin-mutations.mjs +6 -56
  8. package/src/admin-node-diagnostics.mjs +2 -36
  9. package/src/admin-node-lifecycle.mjs +25 -43
  10. package/src/admin-shell.mjs +39 -32
  11. package/src/external-bucket-commands.mjs +5 -73
  12. package/src/interaction-runtime.mjs +2 -37
  13. package/src/kubernetes-commands.mjs +9 -100
  14. package/src/launch-mode-handlers.mjs +0 -1
  15. package/src/mount-commands.mjs +25 -131
  16. package/src/storage-commands.mjs +2 -87
  17. package/src/vm-commands.mjs +9 -42
  18. package/vendor/capabilities/capabilities/admin-enrollment.js +18 -17
  19. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +3 -101
  20. package/vendor/capabilities/capabilities/admin.js +3 -3
  21. package/vendor/capabilities/capabilities/clusters.js +3 -3
  22. package/vendor/capabilities/capabilities/commerce-market.js +9 -9
  23. package/vendor/capabilities/capabilities/compute.js +7 -7
  24. package/vendor/capabilities/capabilities/credits.js +7 -2
  25. package/vendor/capabilities/capabilities/external-buckets.js +4 -4
  26. package/vendor/capabilities/capabilities/fleet.js +109 -29
  27. package/vendor/capabilities/capabilities/market-shared.js +1 -1
  28. package/vendor/capabilities/capabilities/markets.js +12 -12
  29. package/vendor/capabilities/capabilities/mounts.js +4 -4
  30. package/vendor/capabilities/capabilities/networks.js +5 -5
  31. package/vendor/capabilities/capabilities/observability.js +49 -14
  32. package/vendor/capabilities/capabilities/tokens.js +5 -9
  33. package/vendor/capabilities/capabilities/users.js +8 -10
  34. package/vendor/capabilities/capabilities/volumes.js +5 -5
@@ -17,44 +17,41 @@ const suppliersInput = z.object({
17
17
  });
18
18
  const facilitiesInput = z.object({
19
19
  organization_id: z.string().optional(),
20
- organizationId: z.string().optional(),
21
20
  cursor: z.string().optional(),
22
- limit: z.number().int().min(1).max(500).optional(),
21
+ limit: z.number().int().min(1).max(100).optional(),
23
22
  });
24
23
  const nodesInput = z.object({
25
24
  organization_id: z.string().optional(),
26
- organizationId: z.string().optional(),
27
25
  facility_id: z.string().optional(),
28
- facilityId: z.string().optional(),
29
26
  status: z.string().optional(),
30
27
  reservation_id: z.string().optional(),
31
- reservationId: z.string().optional(),
32
28
  cursor: z.string().optional(),
33
29
  max: z.number().int().positive().max(5000).optional(),
34
30
  limit: z.number().int().positive().max(5000).optional(),
35
31
  });
36
32
  const nodeLifecycleInput = z.object({
37
33
  node_id: z.string().min(1),
38
- nodeId: z.string().optional(),
34
+ node_ref: z.string().optional(),
39
35
  reason: z.string().optional(),
40
36
  force: z.boolean().optional(),
37
+ skip_cleanup: z.boolean().optional(),
38
+ delete_node_record: z.literal(true).optional(),
39
+ idempotency_key: z.string().optional(),
40
+ yes: z.boolean().optional(),
41
+ wait: z.boolean().optional(),
42
+ timeout_seconds: z.number().positive().optional(),
41
43
  });
42
44
  const nodeExecInput = z.object({
43
45
  node_id: z.string().min(1),
44
- nodeId: z.string().optional(),
45
46
  command: z.string().min(1),
46
47
  timeout_seconds: z.number().int().positive(),
47
- timeoutSeconds: z.number().int().positive().optional(),
48
48
  yes: z.boolean().optional(),
49
49
  });
50
- function nodeIdOf(input) {
51
- return (input.node_id || input.nodeId || "").trim();
52
- }
53
50
  export const listSuppliersCapability = defineCapability({
54
51
  id: "supplier.list",
55
52
  domain: "fleet",
56
53
  roles: ["admin"],
57
- description: "List supplier organizations (id, name). Resolve supplier names to organization_id with this before nodes.list or facilities.list.",
54
+ description: "List supplier organizations (id, name). Resolve supplier names to organization_id with this before nodes.list or facilities.list. When next_offset is present, pass it back as offset for the next page.",
58
55
  mutation: "read",
59
56
  http: { method: "GET", path: "/v1/organizations" },
60
57
  slack: {
@@ -86,7 +83,7 @@ export const listFacilitiesCapability = defineCapability({
86
83
  id: "facilities.list",
87
84
  domain: "fleet",
88
85
  roles: ["admin"],
89
- description: "List facilities (id, display name, region, supplier organization id). Optional organization_id filter. Resolve facility names with this before nodes.list.",
86
+ description: "List facilities (id, display name, region, supplier organization id). Optional organization_id filter. When nextCursor is present, pass it back as cursor for the next page. Use facilities.show to resolve one facility id.",
90
87
  mutation: "read",
91
88
  http: { method: "GET", path: "/provisioning/facilities" },
92
89
  slack: {
@@ -97,6 +94,16 @@ export const listFacilitiesCapability = defineCapability({
97
94
  type: "string",
98
95
  description: "Optional supplier organization UUID from supplier.list.",
99
96
  },
97
+ cursor: {
98
+ type: "string",
99
+ description: "Pass the previous response's nextCursor to fetch the next page.",
100
+ },
101
+ limit: {
102
+ type: "integer",
103
+ minimum: 1,
104
+ maximum: 100,
105
+ description: "Optional page size (maximum 100).",
106
+ },
100
107
  },
101
108
  },
102
109
  },
@@ -104,12 +111,38 @@ export const listFacilitiesCapability = defineCapability({
104
111
  execute: async (ctx, input) => {
105
112
  const parsed = facilitiesInput.parse(input);
106
113
  return ctx.fetch(queryPath("/provisioning/facilities", {
107
- organizationId: parsed.organization_id ?? parsed.organizationId,
114
+ organizationId: parsed.organization_id,
108
115
  cursor: parsed.cursor,
109
116
  limit: parsed.limit === undefined ? undefined : String(parsed.limit),
110
117
  }), { method: "GET" });
111
118
  },
112
119
  });
120
+ export const facilityShowInput = z.object({ facilityId: z.uuid() });
121
+ export const facilityShowCapability = defineCapability({
122
+ id: "facilities.show",
123
+ domain: "fleet",
124
+ roles: ["admin"],
125
+ description: "Resolve one facility id (e.g. a listing's facilityId) to its display name, region and status.",
126
+ mutation: "read",
127
+ http: { method: "GET", path: "/provisioning/facilities/{facilityId}" },
128
+ cli: false,
129
+ slack: {
130
+ parameters: {
131
+ type: "object",
132
+ properties: {
133
+ facilityId: { type: "string", format: "uuid" },
134
+ },
135
+ required: ["facilityId"],
136
+ },
137
+ },
138
+ input: facilityShowInput,
139
+ execute: async (ctx, input) => {
140
+ const parsed = facilityShowInput.parse(input);
141
+ return ctx.fetch(`/provisioning/facilities/${encodeURIComponent(parsed.facilityId)}`, {
142
+ method: "GET",
143
+ });
144
+ },
145
+ });
113
146
  export const listNodesCapability = defineCapability({
114
147
  id: "nodes.list",
115
148
  domain: "fleet",
@@ -160,10 +193,10 @@ export const listNodesCapability = defineCapability({
160
193
  return ctx.fetch(queryPath("/nodes", pagination), { method: "GET" });
161
194
  }
162
195
  return ctx.fetch(queryPath("/nodes", {
163
- organization_id: parsed.organization_id ?? parsed.organizationId,
164
- facility_id: parsed.facility_id ?? parsed.facilityId,
196
+ organization_id: parsed.organization_id,
197
+ facility_id: parsed.facility_id,
165
198
  status: parsed.status,
166
- reservation_id: parsed.reservation_id ?? parsed.reservationId,
199
+ reservation_id: parsed.reservation_id,
167
200
  ...pagination,
168
201
  }), { method: "GET" });
169
202
  },
@@ -172,9 +205,21 @@ export const nodeOffGridCapability = defineCapability({
172
205
  id: "node.off-grid",
173
206
  domain: "fleet",
174
207
  roles: ["admin"],
175
- description: "Admin-only. Take a node off-grid (remove Ornn agent management, leave the host powered on). CLI asks y/N. Slack waits for a human confirm. MCP requires confirm: true.",
208
+ description: "Admin-only. Take a node off-grid (remove Ornn agent management, leave the host powered on). Slack waits for a human confirm. ",
176
209
  mutation: "preview-confirm",
177
210
  http: { method: "POST", path: "/nodes/{id}/off-grid" },
211
+ cli: {
212
+ command: ["admin", "nodes", "suspend-management"],
213
+ vnext: {
214
+ audience: "admin",
215
+ category: "resource",
216
+ command: ["admin", "nodes", "suspend-management"],
217
+ outcome: "Remove Ornn management and managed access from one powered-on node.",
218
+ human: "ornn admin nodes suspend-management <node-ref> [--reason TEXT] [] [--no-wait]",
219
+ automation: "ornn admin nodes suspend-management <node-id> --idempotency-key KEY --format json",
220
+ review: "Node, supplier, facility, capacity assignment, management removal, and managed-access loss.",
221
+ },
222
+ },
178
223
  slack: {
179
224
  parameters: {
180
225
  type: "object",
@@ -188,14 +233,18 @@ export const nodeOffGridCapability = defineCapability({
188
233
  input: nodeLifecycleInput,
189
234
  execute: async (ctx, input) => {
190
235
  const parsed = nodeLifecycleInput.parse(input);
191
- const id = nodeIdOf(parsed);
192
- const body = { ...(parsed.reason ? { reason: parsed.reason } : {}) };
236
+ const id = parsed.node_id.trim();
237
+ const body = {
238
+ ...(parsed.idempotency_key ? { request_id: parsed.idempotency_key } : {}),
239
+ ...(parsed.reason ? { reason: parsed.reason } : {}),
240
+ };
193
241
  if (!ctx.confirmed) {
194
242
  return preview("node.off-grid", { node_id: id, ...body });
195
243
  }
196
244
  return ctx.fetch(`/nodes/${encodeURIComponent(id)}/off-grid`, {
197
245
  method: "POST",
198
246
  body,
247
+ ...(parsed.idempotency_key ? { headers: { "Idempotency-Key": parsed.idempotency_key } } : {}),
199
248
  });
200
249
  },
201
250
  });
@@ -203,9 +252,21 @@ export const nodeOnGridCapability = defineCapability({
203
252
  id: "node.on-grid",
204
253
  domain: "fleet",
205
254
  roles: ["admin"],
206
- description: "Admin-only. Re-enroll an off-grid node: orchestrator SSHs with the stored admin key and runs the installer with restore_node_id. No caller key. CLI asks y/N. Slack waits for a human confirm. MCP requires confirm: true.",
255
+ description: "Admin-only. Re-enroll an off-grid node: orchestrator SSHs with the stored admin key and runs the installer with restore_node_id. No caller key. Slack waits for a human confirm. ",
207
256
  mutation: "preview-confirm",
208
257
  http: { method: "POST", path: "/nodes/{id}/on-grid" },
258
+ cli: {
259
+ command: ["admin", "nodes", "resume-management"],
260
+ vnext: {
261
+ audience: "admin",
262
+ category: "resource",
263
+ command: ["admin", "nodes", "resume-management"],
264
+ outcome: "Restore Ornn management and managed access on one off-grid node.",
265
+ human: "ornn admin nodes resume-management <node-ref> [] [--no-wait]",
266
+ automation: "ornn admin nodes resume-management <node-id> --idempotency-key KEY --format json",
267
+ review: "Node, supplier, facility, capacity assignment, remote re-enrollment, and restored managed access.",
268
+ },
269
+ },
209
270
  slack: {
210
271
  parameters: {
211
272
  type: "object",
@@ -218,13 +279,16 @@ export const nodeOnGridCapability = defineCapability({
218
279
  input: nodeLifecycleInput,
219
280
  execute: async (ctx, input) => {
220
281
  const parsed = nodeLifecycleInput.parse(input);
221
- const id = nodeIdOf(parsed);
282
+ const id = parsed.node_id.trim();
222
283
  if (!ctx.confirmed) {
223
284
  return preview("node.on-grid", { node_id: id });
224
285
  }
225
286
  return ctx.fetch(`/nodes/${encodeURIComponent(id)}/on-grid`, {
226
287
  method: "POST",
227
- body: {},
288
+ body: {
289
+ ...(parsed.idempotency_key ? { request_id: parsed.idempotency_key } : {}),
290
+ },
291
+ ...(parsed.idempotency_key ? { headers: { "Idempotency-Key": parsed.idempotency_key } } : {}),
228
292
  });
229
293
  },
230
294
  });
@@ -233,7 +297,7 @@ export const nodeExecCapability = defineCapability({
233
297
  domain: "fleet",
234
298
  roles: ["admin"],
235
299
  description: "Admin-only. Run one unrestricted command on an enrolled node over orchestrator SSH with the stored admin key. Always pass timeout_seconds. Not an interactive shell. On NVIDIA hosts use nvidia-smi; on AMD Instinct use amd-smi. Runs on the tool call — no confirm, no y/N.",
236
- mutation: "read",
300
+ mutation: "immediate",
237
301
  http: { method: "POST", path: "/remote" },
238
302
  slack: {
239
303
  parameters: {
@@ -252,8 +316,8 @@ export const nodeExecCapability = defineCapability({
252
316
  input: nodeExecInput,
253
317
  execute: async (ctx, input) => {
254
318
  const parsed = nodeExecInput.parse(input);
255
- const id = nodeIdOf(parsed);
256
- const timeoutSeconds = parsed.timeout_seconds ?? parsed.timeoutSeconds;
319
+ const id = parsed.node_id.trim();
320
+ const timeoutSeconds = parsed.timeout_seconds;
257
321
  return ctx.fetch("/remote", {
258
322
  method: "POST",
259
323
  body: { node_id: id, command: parsed.command, timeout_seconds: timeoutSeconds },
@@ -264,9 +328,21 @@ export const nodeTerminateCapability = defineCapability({
264
328
  id: "node.terminate",
265
329
  domain: "fleet",
266
330
  roles: ["admin"],
267
- description: "Admin-only. Terminate a node record (best-effort clean + delete). Off-grid nodes need force: true. There is no deenroll verb. CLI asks y/N. Slack waits for a human confirm. MCP requires confirm: true.",
331
+ description: "Admin-only. Terminate a node record (best-effort clean + delete). Off-grid nodes need force: true. There is no deenroll verb. Slack waits for a human confirm. ",
268
332
  mutation: "preview-confirm",
269
333
  http: { method: "POST", path: "/nodes/{id}/terminate" },
334
+ cli: {
335
+ command: ["admin", "nodes", "terminate"],
336
+ vnext: {
337
+ audience: "admin",
338
+ category: "resource",
339
+ command: ["admin", "nodes", "terminate"],
340
+ outcome: "End managed access and permanently delete one exact node record.",
341
+ human: "ornn admin nodes terminate <node-ref> --delete-node-record [--skip-cleanup] [--reason TEXT] []",
342
+ automation: "ornn admin nodes terminate <node-id> --delete-node-record --idempotency-key KEY --format json",
343
+ review: "Node, supplier, facility, reservation/capacity, host cleanup, node-record deletion, and access loss.",
344
+ },
345
+ },
270
346
  slack: {
271
347
  parameters: {
272
348
  type: "object",
@@ -281,10 +357,12 @@ export const nodeTerminateCapability = defineCapability({
281
357
  input: nodeLifecycleInput,
282
358
  execute: async (ctx, input) => {
283
359
  const parsed = nodeLifecycleInput.parse(input);
284
- const id = nodeIdOf(parsed);
360
+ const id = parsed.node_id.trim();
361
+ const force = parsed.force ?? parsed.skip_cleanup;
285
362
  const body = {
286
363
  ...(parsed.reason ? { reason: parsed.reason } : {}),
287
- ...(parsed.force !== undefined ? { force: parsed.force } : {}),
364
+ ...(force !== undefined ? { force } : {}),
365
+ ...(parsed.idempotency_key ? { request_id: parsed.idempotency_key } : {}),
288
366
  };
289
367
  if (!ctx.confirmed) {
290
368
  return preview("node.terminate", { node_id: id, ...body });
@@ -292,12 +370,14 @@ export const nodeTerminateCapability = defineCapability({
292
370
  return ctx.fetch(`/nodes/${encodeURIComponent(id)}/terminate`, {
293
371
  method: "POST",
294
372
  body,
373
+ ...(parsed.idempotency_key ? { headers: { "Idempotency-Key": parsed.idempotency_key } } : {}),
295
374
  });
296
375
  },
297
376
  });
298
377
  export const fleetCapabilities = [
299
378
  listSuppliersCapability,
300
379
  listFacilitiesCapability,
380
+ facilityShowCapability,
301
381
  listNodesCapability,
302
382
  nodeOffGridCapability,
303
383
  nodeOnGridCapability,
@@ -21,7 +21,7 @@ export function idempotencyHeaders(ctx, supplied) {
21
21
  }
22
22
  /**
23
23
  * One consent gate for every surface: the CLI reviews the terms in the
24
- * terminal (or takes `--yes`), MCP and Slack return a preview until the
24
+ * terminal (or takes ``), MCP and Slack return a preview until the
25
25
  * caller confirms. `null` means go ahead.
26
26
  */
27
27
  export async function consent(ctx, action, title, terms, payload) {
@@ -173,13 +173,13 @@ export const marketBidCapability = defineCapability({
173
173
  roles: ["user"],
174
174
  description: "Buy nodes at market, each the GPUs of one whole machine, as Spot (the default; a scheduled booking that needs the GPUs can stop it) or On-demand (runs until stopped or the listing ends), " +
175
175
  "in one region or any. The bid takes the cheapest nodes for sale now and cancels the rest. What one listing sells becomes a running reservation (a fill), prepaid hourly from Ornn credits first and then the saved card. " +
176
- "Book prices are what you pay per GPU-hour. Requires confirm: true.",
176
+ "Book prices are what you pay per GPU-hour..",
177
177
  mutation: "preview-confirm",
178
178
  http: { method: "POST", path: `${BASE}/orders` },
179
179
  input: marketBidInput,
180
180
  cli: {
181
181
  command: ["market", "bid"],
182
- vnext: help(["market", "bid"], "Buy nodes at market.", "ornn market bid --gpu-slug <slug> --nodes <n> [--sale-type spot|on-demand] [--region <slug>] [--disk-id <id>]", "ornn market bid --gpu-slug <slug> --nodes 2 --yes --format json", ["market quote", "market orders", "market fills", "market cancel"], 35, ["catalog GPU slug"]),
182
+ vnext: help(["market", "bid"], "Buy nodes at market.", "ornn market bid --gpu-slug <slug> --nodes <n> [--sale-type spot|on-demand] [--region <slug>] [--disk-id <id>]", "ornn market bid --gpu-slug <slug> --nodes 2 --format json", ["market quote", "market orders", "market fills", "market cancel"], 35, ["catalog GPU slug"]),
183
183
  },
184
184
  slack: false,
185
185
  execute: async (ctx, input) => {
@@ -237,13 +237,13 @@ export const marketCancelCapability = defineCapability({
237
237
  id: "market.cancel",
238
238
  domain: "markets",
239
239
  roles: ["user"],
240
- description: "Cancel what a resting bid has not bought yet. Its fills keep running; end them with market end. Requires confirm: true.",
240
+ description: "Cancel what a resting bid has not bought yet. Its fills keep running; end them with market end..",
241
241
  mutation: "preview-confirm",
242
242
  http: { method: "DELETE", path: `${BASE}/orders/{orderId}` },
243
243
  input: marketCancelInput,
244
244
  cli: {
245
245
  command: ["market", "cancel"],
246
- vnext: help(["market", "cancel"], "Cancel the rest of a resting bid.", "ornn market cancel --order-id <id>", "ornn market cancel --order-id <id> --yes --format json", ["market orders", "market end"], 37, ["order ID"]),
246
+ vnext: help(["market", "cancel"], "Cancel the rest of a resting bid.", "ornn market cancel --order-id <id>", "ornn market cancel --order-id <id> --format json", ["market orders", "market end"], 37, ["order ID"]),
247
247
  },
248
248
  slack: false,
249
249
  execute: async (ctx, input) => {
@@ -291,13 +291,13 @@ export const marketEndCapability = defineCapability({
291
291
  id: "market.end",
292
292
  domain: "markets",
293
293
  roles: ["user"],
294
- description: "End a running fill: its reservation stops, its VMs stop, and its GPUs go back on the book. Requires confirm: true.",
294
+ description: "End a running fill: its reservation stops, its VMs stop, and its GPUs go back on the book..",
295
295
  mutation: "preview-confirm",
296
296
  http: { method: "DELETE", path: "/commerce/reservations/{fillId}" },
297
297
  input: marketEndInput,
298
298
  cli: {
299
299
  command: ["market", "end"],
300
- vnext: help(["market", "end"], "End a running fill and stop its VMs.", "ornn market end --fill-id <id>", "ornn market end --fill-id <id> --yes --format json", ["market fills"], 39, ["fill ID", "reservation ID"]),
300
+ vnext: help(["market", "end"], "End a running fill and stop its VMs.", "ornn market end --fill-id <id>", "ornn market end --fill-id <id> --format json", ["market fills"], 39, ["fill ID", "reservation ID"]),
301
301
  },
302
302
  slack: false,
303
303
  execute: async (ctx, input) => {
@@ -366,13 +366,13 @@ export const offerPostCapability = defineCapability({
366
366
  id: "offers.post",
367
367
  domain: "markets",
368
368
  roles: ["user"],
369
- description: "Offer a price for a whole primary listing on one of its terms: every node, from the later of the listing's start and now, for durationSeconds. The listing owner accepts it to create your reservation. Relisted (secondary) capacity takes no offers: buy its windows with reservations.purchase. Requires confirm: true.",
369
+ description: "Offer a price for a whole primary listing on one of its terms: every node, from the later of the listing's start and now, for durationSeconds. The listing owner accepts it to create your reservation. Relisted (secondary) capacity takes no offers: buy its windows with reservations.purchase..",
370
370
  mutation: "preview-confirm",
371
371
  http: { method: "POST", path: `${BASE}/offers` },
372
372
  input: offerPostInput,
373
373
  cli: {
374
374
  command: ["offers", "post"],
375
- vnext: help(["offers", "post"], "Offer a price for a whole primary listing on one of its terms.", "ornn offers post --listing-id <id> --listing-term-id <id> [--duration-seconds <n>] --price-per-gpu-hour-micro-usd <n> [--down-payment-cents <n>]", "ornn offers post --listing-id <id> --listing-term-id <id> --duration-seconds 2592000 --price-per-gpu-hour-micro-usd 2500000 --yes --format json", ["capacity-offers terms", "offers list", "offers cancel"], 43, ["listing ID", "listingTermsId from capacity-offers terms"]),
375
+ vnext: help(["offers", "post"], "Offer a price for a whole primary listing on one of its terms.", "ornn offers post --listing-id <id> --listing-term-id <id> [--duration-seconds <n>] --price-per-gpu-hour-micro-usd <n> [--down-payment-cents <n>]", "ornn offers post --listing-id <id> --listing-term-id <id> --duration-seconds 2592000 --price-per-gpu-hour-micro-usd 2500000 --format json", ["capacity-offers terms", "offers list", "offers cancel"], 43, ["listing ID", "listingTermsId from capacity-offers terms"]),
376
376
  },
377
377
  slack: false,
378
378
  execute: async (ctx, input) => {
@@ -418,13 +418,13 @@ export const offerAcceptCapability = defineCapability({
418
418
  id: "offers.accept",
419
419
  domain: "markets",
420
420
  roles: ["user"],
421
- description: "Accept an offer on a primary listing you own. The buyer's reservation is booked and the listing's overlapping open offers are cancelled. Requires confirm: true.",
421
+ description: "Accept an offer on a primary listing you own. The buyer's reservation is booked and the listing's overlapping open offers are cancelled..",
422
422
  mutation: "preview-confirm",
423
423
  http: { method: "POST", path: `${BASE}/offers/{offerId}/accept` },
424
424
  input: offerDecisionInput,
425
425
  cli: {
426
426
  command: ["offers", "accept"],
427
- vnext: help(["offers", "accept"], "Accept one offer on a listing you own.", "ornn offers accept --offer-id <id>", "ornn offers accept --offer-id <id> --yes --format json", ["offers list"], 44, ["offer ID"]),
427
+ vnext: help(["offers", "accept"], "Accept one offer on a listing you own.", "ornn offers accept --offer-id <id>", "ornn offers accept --offer-id <id> --format json", ["offers list"], 44, ["offer ID"]),
428
428
  },
429
429
  slack: false,
430
430
  execute: async (ctx, input) => {
@@ -448,13 +448,13 @@ export const offerCancelCapability = defineCapability({
448
448
  id: "offers.cancel",
449
449
  domain: "markets",
450
450
  roles: ["user"],
451
- description: "Cancel an open offer you made, or one on a listing you own. Requires confirm: true.",
451
+ description: "Cancel an open offer you made, or one on a listing you own..",
452
452
  mutation: "preview-confirm",
453
453
  http: { method: "DELETE", path: `${BASE}/offers/{offerId}` },
454
454
  input: offerDecisionInput,
455
455
  cli: {
456
456
  command: ["offers", "cancel"],
457
- vnext: help(["offers", "cancel"], "Cancel one open offer.", "ornn offers cancel --offer-id <id>", "ornn offers cancel --offer-id <id> --yes --format json", ["offers list"], 45, ["offer ID"]),
457
+ vnext: help(["offers", "cancel"], "Cancel one open offer.", "ornn offers cancel --offer-id <id>", "ornn offers cancel --offer-id <id> --format json", ["offers list"], 45, ["offer ID"]),
458
458
  },
459
459
  slack: false,
460
460
  execute: async (ctx, input) => {
@@ -84,7 +84,7 @@ export const mountCapabilities = [
84
84
  order: 542,
85
85
  outcome: "Reconcile one exact volume-mount goal and preserve per-machine readiness.",
86
86
  human: "ornn volume-mounts attach <canonical-reservation-id> --volume <exact-name-or-id> --access read-only|read-write [--no-wait]",
87
- automation: "ornn volume-mounts attach <canonical-reservation-id> --volume <canonical-volume-id> --access read-only|read-write --yes --idempotency-key <key> --format json",
87
+ automation: "ornn volume-mounts attach <canonical-reservation-id> --volume <canonical-volume-id> --access read-only|read-write --idempotency-key <key> --format json",
88
88
  acceptedReferences: [
89
89
  "canonical capacity-reservation ID",
90
90
  "canonical or exact unique managed-volume reference",
@@ -114,7 +114,7 @@ export const mountCapabilities = [
114
114
  order: 544,
115
115
  outcome: "Reconcile an exact detached goal and preserve the underlying volume.",
116
116
  human: "ornn volume-mounts detach <canonical-reservation-id>",
117
- automation: "ornn volume-mounts detach <canonical-reservation-id> --yes --idempotency-key <key> --format json",
117
+ automation: "ornn volume-mounts detach <canonical-reservation-id> --idempotency-key <key> --format json",
118
118
  acceptedReferences: ["canonical capacity-reservation ID"],
119
119
  review: "Review the exact relationship and access loss; the managed volume remains.",
120
120
  resumableStates: ["detaching", "ended"],
@@ -128,7 +128,7 @@ export const mountCapabilities = [
128
128
  order: 550,
129
129
  outcome: "Create or rediscover one filesystem deployment and expose every machine mount.",
130
130
  human: "ornn filesystems deploy <canonical-reservation-id> --performance-tier <tier> --capacity-gib <gib> [--no-wait]",
131
- automation: "ornn filesystems deploy <canonical-reservation-id> --performance-tier <tier> --capacity-gib <gib> --yes --idempotency-key <key> --format json",
131
+ automation: "ornn filesystems deploy <canonical-reservation-id> --performance-tier <tier> --capacity-gib <gib> --idempotency-key <key> --format json",
132
132
  acceptedReferences: ["canonical capacity-reservation ID"],
133
133
  review: "Review the exact reservation, performance tier, capacity, and managed filesystem consequence.",
134
134
  resumableStates: ["accepted", "partial", "ready"],
@@ -167,7 +167,7 @@ export const mountCapabilities = [
167
167
  order: 553,
168
168
  outcome: "Detach every filesystem member while preserving the underlying managed volume.",
169
169
  human: "ornn filesystems delete <canonical-reservation-id>",
170
- automation: "ornn filesystems delete <canonical-reservation-id> --yes --idempotency-key <key> --format json",
170
+ automation: "ornn filesystems delete <canonical-reservation-id> --idempotency-key <key> --format json",
171
171
  acceptedReferences: ["canonical capacity-reservation ID"],
172
172
  review: "Review the exact deployment and access loss; its managed volume remains.",
173
173
  resumableStates: ["detaching", "ended"],
@@ -1089,7 +1089,7 @@ export const privateNetworkCreateCapability = customerResource({
1089
1089
  order: 502,
1090
1090
  outcome: "Create one reviewed private network with name-based durable replay.",
1091
1091
  human: "ornn private-networks create --name <name> [--cidr <cidr>]",
1092
- automation: "ornn private-networks create --name <name> [--cidr <cidr>] --yes --idempotency-key <key> --format json",
1092
+ automation: "ornn private-networks create --name <name> [--cidr <cidr>] --idempotency-key <key> --format json",
1093
1093
  review: "Review the normalized name and CIDR before creation.",
1094
1094
  resumableStates: ["ready"],
1095
1095
  related: ["private-networks list"],
@@ -1113,7 +1113,7 @@ export const privateNetworkUpdateCapability = customerResource({
1113
1113
  order: 503,
1114
1114
  outcome: "Apply one reviewed private-network name goal.",
1115
1115
  human: "ornn private-networks update <exact-name-or-id> --name <new-name>",
1116
- automation: "ornn private-networks update <exact-name-or-id> --name <new-name> --yes --idempotency-key <key> --format json",
1116
+ automation: "ornn private-networks update <exact-name-or-id> --name <new-name> --idempotency-key <key> --format json",
1117
1117
  acceptedReferences: ["canonical private-network ID", "exact unique private-network name"],
1118
1118
  review: "Review the exact private network and normalized new name.",
1119
1119
  related: ["private-networks show"],
@@ -1137,7 +1137,7 @@ export const privateNetworkDeleteCapability = customerResource({
1137
1137
  order: 504,
1138
1138
  outcome: "Delete a private network when the owner permits deletion.",
1139
1139
  human: "ornn private-networks delete <exact-name-or-id>",
1140
- automation: "ornn private-networks delete <exact-name-or-id> --yes --idempotency-key <key> --format json",
1140
+ automation: "ornn private-networks delete <exact-name-or-id> --idempotency-key <key> --format json",
1141
1141
  acceptedReferences: ["canonical private-network ID", "exact unique private-network name"],
1142
1142
  review: "Review the exact organization network before permanent deletion.",
1143
1143
  related: ["private-networks list"],
@@ -1184,7 +1184,7 @@ export const vpnConnectionConfigureCapability = customerResource({
1184
1184
  order: 521,
1185
1185
  outcome: "Configure or replay one VPN connection and explicitly export its secret configuration.",
1186
1186
  human: "ornn vpn-connections configure <canonical-reservation-id> --name <name> --private-key <path> (--print | --output <path>)",
1187
- automation: "ornn vpn-connections configure <canonical-reservation-id> --name <name> --private-key <path> --output <path> --yes --idempotency-key <key> --format json",
1187
+ automation: "ornn vpn-connections configure <canonical-reservation-id> --name <name> --private-key <path> --output <path> --idempotency-key <key> --format json",
1188
1188
  acceptedReferences: ["canonical capacity-reservation ID"],
1189
1189
  review: "Review the exact private target, locally derived WireGuard public key, connection name, and explicit secret destination.",
1190
1190
  resumableStates: ["ready"],
@@ -1209,7 +1209,7 @@ export const vpnConnectionDisconnectCapability = customerResource({
1209
1209
  order: 522,
1210
1210
  outcome: "Disconnect one VPN connection without deleting its private network.",
1211
1211
  human: "ornn vpn-connections disconnect <canonical-reservation-id> <exact-name-or-id>",
1212
- automation: "ornn vpn-connections disconnect <canonical-reservation-id> <exact-name-or-id> --yes --idempotency-key <key> --format json",
1212
+ automation: "ornn vpn-connections disconnect <canonical-reservation-id> <exact-name-or-id> --idempotency-key <key> --format json",
1213
1213
  acceptedReferences: [
1214
1214
  "canonical capacity-reservation ID",
1215
1215
  "canonical or exact unique VPN-connection reference",
@@ -16,7 +16,6 @@ function nodePrefix(role, id) {
16
16
  }
17
17
  const latestInput = z.object({
18
18
  node_id: z.string().min(1),
19
- nodeId: z.string().optional(),
20
19
  start: z.string().optional(),
21
20
  end: z.string().optional(),
22
21
  span: z.string().optional(),
@@ -25,7 +24,6 @@ const latestInput = z.object({
25
24
  });
26
25
  const logsLatestInput = z.object({
27
26
  node_id: z.string().min(1),
28
- nodeId: z.string().optional(),
29
27
  stream: z.enum(["serial", "kernel"]).default("kernel"),
30
28
  count: z.number().int().min(1).max(1000).optional(),
31
29
  before: z.string().optional(),
@@ -34,7 +32,6 @@ const logsLatestInput = z.object({
34
32
  });
35
33
  const tailInput = z.object({
36
34
  node_id: z.string().min(1),
37
- nodeId: z.string().optional(),
38
35
  duration: z.number().int().min(1).max(120).optional(),
39
36
  max_events: z.number().int().min(1).max(200).optional(),
40
37
  fields: z.string().optional(),
@@ -42,9 +39,6 @@ const tailInput = z.object({
42
39
  const logsTailInput = tailInput.extend({
43
40
  stream: z.enum(["serial", "kernel"]).default("kernel"),
44
41
  });
45
- function nodeId(input) {
46
- return (input.node_id || input.nodeId || "").trim();
47
- }
48
42
  function expandSpan(span, start, end) {
49
43
  if (start?.trim() && end?.trim()) {
50
44
  return { start: start.trim(), end: end.trim() };
@@ -72,7 +66,10 @@ export const telemetryLatestCapability = defineCapability({
72
66
  start: { type: "string" },
73
67
  end: { type: "string" },
74
68
  max_points: { type: "number", description: "Default 240, max 500." },
75
- metrics: { type: "string", description: "Optional comma-separated metric names; omit for all stored metrics." },
69
+ metrics: {
70
+ type: "string",
71
+ description: "Optional comma-separated metric names; omit for all stored metrics.",
72
+ },
76
73
  },
77
74
  required: ["node_id"],
78
75
  },
@@ -80,7 +77,7 @@ export const telemetryLatestCapability = defineCapability({
80
77
  input: latestInput,
81
78
  execute: async (ctx, input) => {
82
79
  const parsed = latestInput.parse(input);
83
- const id = nodeId(parsed);
80
+ const id = parsed.node_id.trim();
84
81
  const range = expandSpan(parsed.span, parsed.start, parsed.end);
85
82
  return ctx.fetch(queryPath(`${nodePrefix(ctx.role, id)}/telemetry/query`, {
86
83
  start: range.start,
@@ -90,6 +87,34 @@ export const telemetryLatestCapability = defineCapability({
90
87
  }), { method: "GET" });
91
88
  },
92
89
  });
90
+ export const telemetryLatestBatchCapability = defineCapability({
91
+ id: "telemetry.latest_batch",
92
+ domain: "observability",
93
+ roles: ["user", "admin"],
94
+ description: "Latest telemetry tip (last heartbeat, public_ip, GPU/CPU/memory utilization) for up to 100 nodes in one call. Prefer this over telemetry_latest per node when checking several nodes.",
95
+ mutation: "read",
96
+ http: { method: "POST", path: "/v1/observability/nodes/telemetry/latest" },
97
+ cli: false,
98
+ slack: {
99
+ parameters: {
100
+ type: "object",
101
+ properties: {
102
+ node_ids: {
103
+ type: "array",
104
+ items: { type: "string", format: "uuid" },
105
+ minItems: 1,
106
+ maxItems: 100,
107
+ },
108
+ },
109
+ required: ["node_ids"],
110
+ },
111
+ },
112
+ input: z.object({ node_ids: z.array(z.uuid()).min(1).max(100) }),
113
+ execute: async (ctx, input) => ctx.fetch("/v1/observability/nodes/telemetry/latest", {
114
+ method: "POST",
115
+ body: { node_ids: input.node_ids },
116
+ }),
117
+ });
93
118
  export const logsLatestCapability = defineCapability({
94
119
  id: "logs.latest",
95
120
  domain: "observability",
@@ -104,8 +129,14 @@ export const logsLatestCapability = defineCapability({
104
129
  node_id: { type: "string", description: "Node UUID from nodes.list." },
105
130
  stream: { type: "string", enum: ["serial", "kernel"] },
106
131
  count: { type: "number", description: "Page size. Default 200, max 1000." },
107
- before: { type: "string", description: "RFC3339 upper bound. Default now. Keep fixed when paging." },
108
- start: { type: "string", description: "RFC3339 lower bound. Default seven days before the upper bound." },
132
+ before: {
133
+ type: "string",
134
+ description: "RFC3339 upper bound. Default now. Keep fixed when paging.",
135
+ },
136
+ start: {
137
+ type: "string",
138
+ description: "RFC3339 lower bound. Default seven days before the upper bound.",
139
+ },
109
140
  cursor: { type: "string", description: "next_cursor from the previous page." },
110
141
  },
111
142
  required: ["node_id"],
@@ -114,7 +145,7 @@ export const logsLatestCapability = defineCapability({
114
145
  input: logsLatestInput,
115
146
  execute: async (ctx, input) => {
116
147
  const parsed = logsLatestInput.parse(input);
117
- const id = nodeId(parsed);
148
+ const id = parsed.node_id.trim();
118
149
  const end = parsed.before?.trim() || new Date().toISOString();
119
150
  const start = parsed.start?.trim() || new Date(Date.parse(end) - 7 * 86_400_000).toISOString();
120
151
  return ctx.fetch(queryPath(`${nodePrefix(ctx.role, id)}/logs/${parsed.stream}/query`, {
@@ -145,7 +176,10 @@ export const telemetrySnapshotCapability = defineCapability({
145
176
  required: ["node_id"],
146
177
  },
147
178
  },
148
- input: z.object({ node_id: z.string().min(1), limit: z.number().int().min(1).max(1000).optional() }),
179
+ input: z.object({
180
+ node_id: z.string().min(1),
181
+ limit: z.number().int().min(1).max(1000).optional(),
182
+ }),
149
183
  execute: async (ctx, input) => ctx.fetch(queryPath(`/v1/observability/nodes/${encodeURIComponent(input.node_id)}/telemetry/latest`, {
150
184
  limit: String(input.limit ?? 1),
151
185
  }), { method: "GET" }),
@@ -172,7 +206,7 @@ export const telemetryTailCapability = defineCapability({
172
206
  input: tailInput,
173
207
  execute: async (ctx, input) => {
174
208
  const parsed = tailInput.parse(input);
175
- const id = nodeId(parsed);
209
+ const id = parsed.node_id.trim();
176
210
  return ctx.fetch(queryPath(`${nodePrefix(ctx.role, id)}/telemetry/subscribe`, {
177
211
  fields: parsed.fields,
178
212
  duration: String(parsed.duration ?? 20),
@@ -202,7 +236,7 @@ export const logsTailCapability = defineCapability({
202
236
  input: logsTailInput,
203
237
  execute: async (ctx, input) => {
204
238
  const parsed = logsTailInput.parse(input);
205
- const id = nodeId(parsed);
239
+ const id = parsed.node_id.trim();
206
240
  return ctx.fetch(queryPath(`${nodePrefix(ctx.role, id)}/logs/${parsed.stream}/subscribe`, {
207
241
  duration: String(parsed.duration ?? 20),
208
242
  max_events: String(parsed.max_events ?? 40),
@@ -212,6 +246,7 @@ export const logsTailCapability = defineCapability({
212
246
  export const observabilityCapabilities = [
213
247
  telemetrySnapshotCapability,
214
248
  telemetryLatestCapability,
249
+ telemetryLatestBatchCapability,
215
250
  telemetryTailCapability,
216
251
  logsLatestCapability,
217
252
  logsTailCapability,