@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.
- package/README.md +35 -27
- package/package.json +1 -1
- package/src/admin-capacity-reservations.mjs +2 -12
- package/src/admin-command-handlers.mjs +0 -1
- package/src/admin-credits.mjs +1 -2
- package/src/admin-enrollment.mjs +22 -30
- package/src/admin-mutations.mjs +6 -56
- package/src/admin-node-diagnostics.mjs +2 -36
- package/src/admin-node-lifecycle.mjs +25 -43
- package/src/admin-shell.mjs +39 -32
- package/src/catalog-shell.mjs +4 -18
- package/src/external-bucket-commands.mjs +5 -73
- package/src/interaction-runtime.mjs +2 -37
- package/src/kubernetes-commands.mjs +11 -102
- package/src/launch-mode-handlers.mjs +0 -1
- package/src/mount-commands.mjs +25 -131
- package/src/storage-commands.mjs +2 -87
- package/src/vm-commands.mjs +11 -44
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +12 -5
- package/vendor/capabilities/capabilities/admin-enrollment.js +18 -17
- package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +3 -101
- package/vendor/capabilities/capabilities/admin.js +3 -3
- package/vendor/capabilities/capabilities/clusters.js +3 -3
- package/vendor/capabilities/capabilities/commerce-market.js +9 -9
- package/vendor/capabilities/capabilities/compute.js +7 -7
- package/vendor/capabilities/capabilities/credits.js +7 -2
- package/vendor/capabilities/capabilities/external-buckets.js +4 -4
- package/vendor/capabilities/capabilities/fleet.js +109 -29
- package/vendor/capabilities/capabilities/listings.js +44 -16
- package/vendor/capabilities/capabilities/locality.js +42 -0
- package/vendor/capabilities/capabilities/market-shared.js +1 -1
- package/vendor/capabilities/capabilities/markets.js +14 -14
- package/vendor/capabilities/capabilities/mounts.js +4 -4
- package/vendor/capabilities/capabilities/native-listing.js +51 -15
- package/vendor/capabilities/capabilities/networks.js +5 -5
- package/vendor/capabilities/capabilities/observability.js +49 -14
- package/vendor/capabilities/capabilities/tokens.js +5 -9
- package/vendor/capabilities/capabilities/users.js +8 -10
- 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
|
|
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
|
|
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
|
|
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>
|
|
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
|
|
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: "
|
|
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>
|
|
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)}
|
|
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
|
|
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
|
|
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
|
|
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>
|
|
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
|
|
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>
|
|
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
|
|
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>
|
|
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>
|
|
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>
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
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
|
|
33
|
-
|
|
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
|
|
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
|
|
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>]
|
|
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>
|
|
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>
|
|
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>
|
|
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>
|
|
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: {
|
|
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 =
|
|
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: {
|
|
108
|
-
|
|
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 =
|
|
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({
|
|
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 =
|
|
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 =
|
|
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.
|
|
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 =
|
|
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() ||
|
|
103
|
+
const privateKey = parsed.private_key?.trim() || "";
|
|
108
104
|
const payload = {
|
|
109
105
|
organization_id: organizationId,
|
|
110
|
-
facility_id: parsed.facility_id
|
|
111
|
-
expires_in_seconds: parsed.expires_in_seconds
|
|
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 }), {
|
|
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) ||
|
|
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>] [
|
|
82
|
-
automation: "ornn volumes create --name <name> [--source <volume-id>]
|
|
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>
|
|
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>] [
|
|
139
|
-
automation: "ornn volumes files upload <volume-id> <local-file> --path <remote-path>
|
|
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
|
}),
|