@ornncompute/cli 1.0.67 → 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 (39) hide show
  1. package/README.md +35 -27
  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/catalog-shell.mjs +4 -18
  12. package/src/external-bucket-commands.mjs +5 -73
  13. package/src/interaction-runtime.mjs +2 -37
  14. package/src/kubernetes-commands.mjs +11 -102
  15. package/src/launch-mode-handlers.mjs +0 -1
  16. package/src/mount-commands.mjs +25 -131
  17. package/src/storage-commands.mjs +2 -87
  18. package/src/vm-commands.mjs +11 -44
  19. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +12 -5
  20. package/vendor/capabilities/capabilities/admin-enrollment.js +18 -17
  21. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +3 -101
  22. package/vendor/capabilities/capabilities/admin.js +3 -3
  23. package/vendor/capabilities/capabilities/clusters.js +3 -3
  24. package/vendor/capabilities/capabilities/commerce-market.js +9 -9
  25. package/vendor/capabilities/capabilities/compute.js +7 -7
  26. package/vendor/capabilities/capabilities/credits.js +7 -2
  27. package/vendor/capabilities/capabilities/external-buckets.js +4 -4
  28. package/vendor/capabilities/capabilities/fleet.js +109 -29
  29. package/vendor/capabilities/capabilities/listings.js +44 -16
  30. package/vendor/capabilities/capabilities/locality.js +42 -0
  31. package/vendor/capabilities/capabilities/market-shared.js +1 -1
  32. package/vendor/capabilities/capabilities/markets.js +14 -14
  33. package/vendor/capabilities/capabilities/mounts.js +4 -4
  34. package/vendor/capabilities/capabilities/native-listing.js +51 -15
  35. package/vendor/capabilities/capabilities/networks.js +5 -5
  36. package/vendor/capabilities/capabilities/observability.js +49 -14
  37. package/vendor/capabilities/capabilities/tokens.js +5 -9
  38. package/vendor/capabilities/capabilities/users.js +8 -10
  39. package/vendor/capabilities/capabilities/volumes.js +5 -5
@@ -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
- http: { method: "POST", path: "/commerce/reservations/{fillId}/stop" },
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) => {
@@ -308,7 +308,7 @@ export const marketEndCapability = defineCapability({
308
308
  ], { fillId: parsed.fillId });
309
309
  if (gate)
310
310
  return gate;
311
- return ctx.fetch(`/commerce/reservations/${seg(parsed.fillId)}/stop`, { method: "POST" });
311
+ return ctx.fetch(`/commerce/reservations/${seg(parsed.fillId)}`, { method: "DELETE" });
312
312
  },
313
313
  });
314
314
  export const offersListInput = z.object({
@@ -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"],
@@ -7,35 +7,62 @@ export const nativeTermFields = {
7
7
  pricePerGpuHourMicroUsd: z.coerce.number().int().positive(),
8
8
  downPaymentCents: z.coerce.number().int().nonnegative().nullable().optional(),
9
9
  renewable: flag,
10
+ preemptNoticeSeconds: z.coerce.number().int().nonnegative().optional(),
10
11
  };
11
12
  export const nativeTermInput = z.object(nativeTermFields).strict();
12
- export const nativeTermsInput = z.preprocess((value) => {
13
- if (typeof value !== "string")
14
- return value;
15
- try {
16
- return JSON.parse(value);
17
- }
18
- catch {
19
- return value;
20
- }
21
- }, z.array(nativeTermInput));
13
+ function jsonArray(item) {
14
+ return z.preprocess((value) => {
15
+ if (typeof value !== "string")
16
+ return value;
17
+ try {
18
+ return JSON.parse(value);
19
+ }
20
+ catch {
21
+ return value;
22
+ }
23
+ }, z.array(item));
24
+ }
25
+ export const nativeTermsInput = jsonArray(nativeTermInput);
22
26
  export const listingWriteControls = {
23
27
  confirm: z.boolean().optional(),
24
28
  idempotencyKey: z.string().min(1).max(200),
25
29
  };
30
+ const listingLocation = z
31
+ .string()
32
+ .trim()
33
+ .min(1)
34
+ .max(256)
35
+ .optional()
36
+ .describe("Instead of facilityId: a state, city, address, or catalog region such as us-west-3. Ornn places it in a catalog region and uses the supplier's facility there.");
26
37
  export const nativeListingCreateInput = z
27
38
  .object({
28
39
  organizationId: z.string().uuid().nullable().optional(),
29
40
  kind: z.enum(["primary", "secondary"]).default("primary"),
30
41
  gpuSlug: z.string().min(1),
31
42
  gpusPerNode: z.coerce.number().int().positive(),
32
- capacityNodeCount: z.coerce.number().int().positive(),
33
- facilityId: z.string().uuid(),
43
+ capacityNodeCount: z.coerce
44
+ .number()
45
+ .int()
46
+ .positive()
47
+ .optional()
48
+ .describe("Defaults to the number of nodeIds."),
49
+ nodeIds: jsonArray(z.string().uuid()).optional(),
50
+ facilityId: z.string().uuid().optional(),
51
+ location: listingLocation,
34
52
  provisioningType: z.enum(["bare-metal", "virtual-machine"]),
35
53
  state: z.enum(["draft", "available"]),
36
- availableAt: z.iso.datetime({ offset: true }).nullable().optional(),
54
+ availableAt: z.iso
55
+ .datetime({ offset: true })
56
+ .nullable()
57
+ .optional()
58
+ .describe("Primary capacity takes availableAt and availableUntil, or daysToRfs, not both."),
37
59
  availableUntil: z.iso.datetime({ offset: true }).nullable().optional(),
38
- daysToRfs: z.coerce.number().int().nonnegative().optional(),
60
+ daysToRfs: z.coerce
61
+ .number()
62
+ .int()
63
+ .nonnegative()
64
+ .optional()
65
+ .describe("Days until ready for service; 0 when ready now. Required unless both dates are set."),
39
66
  cpu: z.string().optional(),
40
67
  ram: z.string().optional(),
41
68
  storage: z.string().optional(),
@@ -44,13 +71,22 @@ export const nativeListingCreateInput = z
44
71
  terms: nativeTermsInput,
45
72
  ...listingWriteControls,
46
73
  })
47
- .strict();
74
+ .strict()
75
+ .refine((value) => Boolean(value.facilityId) !== Boolean(value.location), {
76
+ message: "Give exactly one of facilityId or location.",
77
+ path: ["facilityId"],
78
+ })
79
+ .refine((value) => !value.location || Boolean(value.organizationId), {
80
+ message: "location needs organizationId, the supplier whose facility it is.",
81
+ path: ["organizationId"],
82
+ });
48
83
  export const nativeListingUpdateFields = {
49
84
  state: z.enum(["draft", "available", "delisted"]).optional(),
50
85
  nodeCount: z.coerce.number().int().positive().optional(),
51
86
  gpuSlug: z.string().min(1).optional(),
52
87
  gpusPerNode: z.coerce.number().int().positive().optional(),
53
88
  facilityId: z.string().uuid().optional(),
89
+ location: listingLocation,
54
90
  provisioningType: z.enum(["bare-metal", "virtual-machine"]).optional(),
55
91
  availableAt: z.iso.datetime({ offset: true }).optional(),
56
92
  availableUntil: z.iso.datetime({ offset: true }).nullable().optional(),
@@ -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,
@@ -3,11 +3,8 @@ import { defineCapability, preview } from "../capability.js";
3
3
  const optionalText = z.string().nullish();
4
4
  const tokenCreateInput = z.object({
5
5
  organization_id: z.string().min(1),
6
- organizationId: optionalText,
7
6
  facility_id: optionalText,
8
- facilityId: optionalText,
9
7
  expires_in_seconds: z.number().int().positive().nullish(),
10
- expiresInSeconds: z.number().int().positive().nullish(),
11
8
  filter: z.string().nullish(),
12
9
  ip: optionalText,
13
10
  installer_base: optionalText,
@@ -16,7 +13,6 @@ const tokenCreateInput = z.object({
16
13
  username: optionalText,
17
14
  port: z.number().int().min(1).max(65535).nullish(),
18
15
  private_key: optionalText,
19
- privateKey: optionalText,
20
16
  yes: z.boolean().optional(),
21
17
  });
22
18
  const emptyInput = z.object({});
@@ -62,7 +58,7 @@ export const tokensCreateCapability = defineCapability({
62
58
  id: "tokens.create",
63
59
  domain: "fleet",
64
60
  roles: ["admin"],
65
- description: "Admin-only. Mint an enrollment token. Without a host this returns the curl installer. With host + private_key, orchestrator SSHs and runs that curl (same as Bulk Enroll). Never invent a command. CLI asks y/N. Slack waits for a human confirm. MCP requires confirm: true.",
61
+ description: "Admin-only. Mint an enrollment token. Without a host this returns the curl installer. With host + private_key, orchestrator SSHs and runs that curl (same as Bulk Enroll). Never invent a command. Slack waits for a human confirm. ",
66
62
  mutation: "preview-confirm",
67
63
  http: { method: "POST", path: "/enrollment-tokens" },
68
64
  slack: {
@@ -99,16 +95,16 @@ export const tokensCreateCapability = defineCapability({
99
95
  input: tokenCreateInput,
100
96
  execute: async (ctx, input) => {
101
97
  const parsed = tokenCreateInput.parse(input);
102
- const organizationId = (parsed.organization_id || parsed.organizationId || "").trim();
98
+ const organizationId = parsed.organization_id.trim();
103
99
  const hosts = [
104
100
  ...(parsed.host?.trim() ? [parsed.host.trim()] : []),
105
101
  ...(parsed.hosts ?? []).map((host) => host.trim()).filter(Boolean),
106
102
  ];
107
- const privateKey = parsed.private_key?.trim() || parsed.privateKey?.trim() || "";
103
+ const privateKey = parsed.private_key?.trim() || "";
108
104
  const payload = {
109
105
  organization_id: organizationId,
110
- facility_id: parsed.facility_id ?? parsed.facilityId,
111
- expires_in_seconds: parsed.expires_in_seconds ?? parsed.expiresInSeconds,
106
+ facility_id: parsed.facility_id,
107
+ expires_in_seconds: parsed.expires_in_seconds,
112
108
  filter: parsed.filter,
113
109
  ip: parsed.ip,
114
110
  hosts,
@@ -17,12 +17,7 @@ export const usersListInput = z.object({
17
17
  offset: directoryOffset,
18
18
  });
19
19
  function directoryPath(input) {
20
- const params = [
21
- `limit=${input.limit}`,
22
- `offset=${input.offset}`,
23
- "sort=name",
24
- "direction=asc",
25
- ];
20
+ const params = [`limit=${input.limit}`, `offset=${input.offset}`, "sort=name", "direction=asc"];
26
21
  if (input.q)
27
22
  params.push(`q=${encodeURIComponent(input.q)}`);
28
23
  if (input.role)
@@ -51,7 +46,7 @@ export const organizationListCapability = defineCapability({
51
46
  id: "organization.list",
52
47
  domain: "users",
53
48
  roles: ["admin"],
54
- description: "List organizations from the Authorization directory. Search by company name, contact/member name, or email; filter by role, approval, or UUIDs. Use the organization id as organizationId when recording downtime, and ask the user to choose if matches are ambiguous.",
49
+ description: "List organizations from the Authorization directory. Search by company name, contact/member name, or email; filter by role, approval, or UUIDs. When next_offset is present, pass it back as offset for the next page. Use the organization id as organizationId when recording downtime, and ask the user to choose if matches are ambiguous.",
55
50
  mutation: "read",
56
51
  http: { method: "GET", path: "/v1/organizations" },
57
52
  slack: {
@@ -80,7 +75,9 @@ export const organizationListCapability = defineCapability({
80
75
  const parsed = organizationListInput.parse(input);
81
76
  const limit = parsed.limit;
82
77
  const offset = parsed.offset ?? 0;
83
- const rows = (await ctx.fetch(directoryPath({ ...parsed, limit, offset }), { method: "GET" }));
78
+ const rows = (await ctx.fetch(directoryPath({ ...parsed, limit, offset }), {
79
+ method: "GET",
80
+ }));
84
81
  return {
85
82
  organizations: rows.map((organization) => ({
86
83
  id: organization.id,
@@ -101,7 +98,7 @@ export const usersListCapability = defineCapability({
101
98
  id: "users.list",
102
99
  domain: "users",
103
100
  roles: ["admin"],
104
- description: "List users who belong to an organization, from the Authorization directory. Search by person name, email, or company name; use organization_id to restrict results to one organization.",
101
+ description: "List users who belong to an organization, from the Authorization directory. Search by person name, email, or company name; use organization_id to restrict results to one organization. When next_offset is present, pass it back as offset for the next page.",
105
102
  mutation: "read",
106
103
  http: { method: "GET", path: "/v1/organizations" },
107
104
  slack: {
@@ -139,7 +136,8 @@ export const usersListCapability = defineCapability({
139
136
  return members
140
137
  .filter((member) => !q ||
141
138
  matchesOrganization ||
142
- (member.name ?? "").toLowerCase().includes(q) || member.email.toLowerCase().includes(q))
139
+ (member.name ?? "").toLowerCase().includes(q) ||
140
+ member.email.toLowerCase().includes(q))
143
141
  .map((member) => ({
144
142
  user_id: member.user_id,
145
143
  email: member.email,
@@ -78,8 +78,8 @@ export const volumeCapabilities = [
78
78
  description: "Create or rediscover a uniquely named managed volume.",
79
79
  mutation: "preview-confirm",
80
80
  outcome: "Create one durable volume or safely replay its normalized intent.",
81
- human: "ornn volumes create --name <name> [--source <volume>] [--yes]",
82
- automation: "ornn volumes create --name <name> [--source <volume-id>] --yes --idempotency-key <key> --format json",
81
+ human: "ornn volumes create --name <name> [--source <volume>] []",
82
+ automation: "ornn volumes create --name <name> [--source <volume-id>] --idempotency-key <key> --format json",
83
83
  acceptedReferences: ["canonical source volume ID", "exact unique source volume name"],
84
84
  related: ["volumes show", "volumes files upload"],
85
85
  }),
@@ -113,7 +113,7 @@ export const volumeCapabilities = [
113
113
  mutation: "preview-confirm",
114
114
  outcome: "Delete one detached volume after explicit consent.",
115
115
  human: "ornn volumes delete <volume>",
116
- automation: "ornn volumes delete <volume-id> --yes --idempotency-key <key> --format json",
116
+ automation: "ornn volumes delete <volume-id> --idempotency-key <key> --format json",
117
117
  acceptedReferences: ["canonical volume ID", "exact unique volume name"],
118
118
  review: "The volume and all of its data are permanently deleted.",
119
119
  related: ["volume-mounts detach", "volumes show"],
@@ -135,8 +135,8 @@ export const volumeCapabilities = [
135
135
  description: "Stage, upload, and finalize one local file at a nested volume path.",
136
136
  mutation: "preview-confirm",
137
137
  outcome: "Upload one file through a reconciled staged transfer.",
138
- human: "ornn volumes files upload <volume> <local-file> [--path <remote-path>] [--content-type <type>] [--yes]",
139
- automation: "ornn volumes files upload <volume-id> <local-file> --path <remote-path> --yes --idempotency-key <key> --format json",
138
+ human: "ornn volumes files upload <volume> <local-file> [--path <remote-path>] [--content-type <type>] []",
139
+ automation: "ornn volumes files upload <volume-id> <local-file> --path <remote-path> --idempotency-key <key> --format json",
140
140
  acceptedReferences: ["canonical volume ID", "exact unique volume name"],
141
141
  related: ["volumes files list", "volumes refresh"],
142
142
  }),