@ornncompute/cli 1.0.35 → 1.0.48
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 +39 -12
- package/package.json +1 -1
- package/src/admin-capacity-reservations.mjs +69 -139
- package/src/admin-node-lifecycle.mjs +48 -7
- package/src/admin-shell.mjs +101 -25
- package/src/catalog-shell.mjs +15 -20
- package/src/evidence-commands.mjs +30 -12
- package/src/kubernetes-commands.mjs +58 -16
- package/src/launch-mode-handlers.mjs +2 -2
- package/src/storage-commands.mjs +9 -1
- package/src/vm-commands.mjs +3 -3
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +36 -55
- package/vendor/capabilities/capabilities/admin.js +8 -6
- package/vendor/capabilities/capabilities/commerce-market.js +76 -101
- package/vendor/capabilities/capabilities/compute.js +4 -2
- package/vendor/capabilities/capabilities/listings.js +58 -38
- package/vendor/capabilities/capabilities/markets.js +7 -7
- package/vendor/capabilities/capabilities/native-listing.js +63 -0
- package/vendor/capabilities/capabilities/organization-invites.js +53 -0
- package/vendor/capabilities/capabilities/users.js +141 -31
- package/vendor/capabilities/cli-catalog.js +4 -2
- package/vendor/capabilities/cli-migrations.js +1 -2
- package/vendor/capabilities/software-catalog.js +52 -6
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { defineCapability } from "../capability.js";
|
|
3
|
-
import {
|
|
2
|
+
import { defineCapability, preview } from "../capability.js";
|
|
3
|
+
import { nativeListingCreateInput } from "./native-listing.js";
|
|
4
|
+
import { idempotencyHeaders } from "./market-shared.js";
|
|
4
5
|
function adminHelp(command, outcome, human, automation, review) {
|
|
5
6
|
return {
|
|
6
7
|
audience: "admin",
|
|
@@ -13,78 +14,58 @@ function adminHelp(command, outcome, human, automation, review) {
|
|
|
13
14
|
...(review ? { review } : {}),
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
down_payment_cents: z.number().int().nonnegative().optional(),
|
|
26
|
-
access_mode: z.enum(["bare-metal", "virtual-machine"]).optional(),
|
|
27
|
-
});
|
|
28
|
-
export const adminCapacityOffersCreateCapability = {
|
|
29
|
-
...createListingCapability,
|
|
17
|
+
export const adminCapacityOffersCreateCapability = defineCapability({
|
|
18
|
+
id: "listing.create",
|
|
19
|
+
domain: "listings",
|
|
20
|
+
roles: ["admin"],
|
|
21
|
+
description: "Create native capacity with explicit organization/facility IDs, hardware, availability and advertised terms. Returns {listing, terms}. Requires an idempotencyKey reused on retries and confirm: true.",
|
|
22
|
+
mutation: "preview-confirm",
|
|
23
|
+
http: { method: "POST", path: "/commerce/v2/listings" },
|
|
24
|
+
input: nativeListingCreateInput,
|
|
25
|
+
slack: false,
|
|
30
26
|
cli: {
|
|
31
|
-
|
|
32
|
-
vnext: adminHelp(["admin", "capacity-offers", "create"], "Create
|
|
27
|
+
command: ["admin", "capacity-offers", "create"],
|
|
28
|
+
vnext: adminHelp(["admin", "capacity-offers", "create"], "Create native capacity and its advertised financial terms.", "ornn admin capacity-offers create --gpu-slug GPU --capacity-node-count COUNT --gpus-per-node COUNT --facility-id UUID --provisioning-type bare-metal|virtual-machine --state draft|available --terms JSON --idempotency-key KEY [--organization-id UUID] [--available-at TIME --available-until TIME] [--days-to-rfs DAYS]", "Requires an eligible human terminal; reuse --idempotency-key on retries.", "Review the native parent fields and each financial term before default-no consent."),
|
|
33
29
|
},
|
|
34
|
-
input: createListingCapability.input,
|
|
35
30
|
execute: async (ctx, input) => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
const { confirm: _confirm, idempotencyKey, ...body } = nativeListingCreateInput.parse(input);
|
|
32
|
+
if (!ctx.confirmed)
|
|
33
|
+
return preview("listing.create", body);
|
|
34
|
+
return ctx.fetch("/commerce/v2/listings", {
|
|
35
|
+
method: "POST",
|
|
36
|
+
body,
|
|
37
|
+
headers: idempotencyHeaders(ctx, idempotencyKey),
|
|
38
|
+
});
|
|
42
39
|
},
|
|
43
|
-
};
|
|
44
|
-
const MICRO_USD = 1_000_000;
|
|
40
|
+
});
|
|
45
41
|
export const reservationCreateInput = z.object({
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
customerId: z.string().uuid(),
|
|
43
|
+
listingTermId: z.string().uuid(),
|
|
44
|
+
reservedNodeCount: z.number().int().positive().max(2_147_483_647),
|
|
45
|
+
reservedGpuCount: z.number().int().positive().max(2_147_483_647),
|
|
46
|
+
startsAt: z.iso.datetime({ offset: true }),
|
|
47
|
+
endsAt: z.iso.datetime({ offset: true }).nullable().optional(),
|
|
48
|
+
transactionId: z
|
|
51
49
|
.string()
|
|
52
|
-
.
|
|
53
|
-
.describe("
|
|
54
|
-
|
|
55
|
-
notes: z.string().max(2000).optional(),
|
|
50
|
+
.uuid()
|
|
51
|
+
.describe("Reuse this transaction ID when retrying the same reservation."),
|
|
52
|
+
vmDiskId: z.string().uuid().nullable().optional(),
|
|
56
53
|
});
|
|
57
|
-
function object(value) {
|
|
58
|
-
return value && typeof value === "object" && !Array.isArray(value)
|
|
59
|
-
? value
|
|
60
|
-
: {};
|
|
61
|
-
}
|
|
62
54
|
export const adminReservationsCreateCapability = defineCapability({
|
|
63
55
|
id: "reservations.create",
|
|
64
56
|
domain: "reservations",
|
|
65
57
|
roles: ["admin"],
|
|
66
|
-
description: "Create one
|
|
58
|
+
description: "Create one reservation from a listing term, customer, quantities, dates, and a stable transaction ID (admin).",
|
|
67
59
|
mutation: "preview-confirm",
|
|
68
60
|
http: { method: "POST", path: "/commerce/reservations" },
|
|
69
61
|
cli: {
|
|
70
62
|
command: ["admin", "reservations", "create"],
|
|
71
|
-
vnext: adminHelp(["admin", "reservations", "create"], "Create one
|
|
63
|
+
vnext: adminHelp(["admin", "reservations", "create"], "Create one reservation using the selected listing term and quantities.", "ornn admin reservations create --customer ID --listing-term ID --nodes COUNT --gpus COUNT --starts-at TIME --transaction ID [--ends-at TIME] [--vm-disk ID]", "Requires an eligible human terminal. Reuse --transaction when retrying the same reservation.", "Customer, listing term, reserved quantities, dates, VM disk, and transaction ID are reviewed before default-no consent."),
|
|
72
64
|
},
|
|
73
65
|
slack: false,
|
|
74
66
|
input: reservationCreateInput,
|
|
75
67
|
execute: async (ctx, input) => {
|
|
76
|
-
const
|
|
77
|
-
const body = {
|
|
78
|
-
listingId: parsed.listing_id,
|
|
79
|
-
organizationId: parsed.organization_id,
|
|
80
|
-
startsAt: parsed.start_at,
|
|
81
|
-
endsAt: parsed.end_at,
|
|
82
|
-
pricePerGpuHourMicroUsd: Math.round(Number(parsed.price_per_gpu_hour) * MICRO_USD),
|
|
83
|
-
...(parsed.down_payment_cents !== undefined
|
|
84
|
-
? { downPaymentCents: parsed.down_payment_cents }
|
|
85
|
-
: {}),
|
|
86
|
-
...(parsed.notes ? { notes: parsed.notes } : {}),
|
|
87
|
-
};
|
|
68
|
+
const body = reservationCreateInput.parse(input);
|
|
88
69
|
if (!ctx.confirmed)
|
|
89
70
|
return { preview: body };
|
|
90
71
|
return { reservation: await ctx.fetch("/commerce/reservations", { method: "POST", body }) };
|
|
@@ -3,10 +3,10 @@ import { defineCapability } from "../capability.js";
|
|
|
3
3
|
import { listFacilitiesCapability, listNodesCapability, listSuppliersCapability } from "./fleet.js";
|
|
4
4
|
import { logsLatestCapability, telemetryLatestCapability } from "./observability.js";
|
|
5
5
|
import { tokensListCapability } from "./tokens.js";
|
|
6
|
-
import {
|
|
7
|
-
import { adminCapacityOffersCreateCapability } from "./admin-capacity-reservations.js";
|
|
6
|
+
import { organizationListCapability, usersListCapability } from "./users.js";
|
|
8
7
|
export const ADMIN_CLI_ROOTS = [
|
|
9
8
|
{ name: "users" },
|
|
9
|
+
{ name: "organizations" },
|
|
10
10
|
{ name: "suppliers" },
|
|
11
11
|
{ name: "facilities" },
|
|
12
12
|
{ name: "nodes" },
|
|
@@ -36,7 +36,8 @@ function withAdminBinding(capability, help) {
|
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
export const
|
|
39
|
+
export const adminUsersListCapability = withAdminBinding(usersListCapability, adminHelp(["admin", "users", "list"], "List users belonging to organizations in the Authorization directory.", "ornn admin users list [--query QUERY] [--organization UUID] [--limit 25] [--offset OFFSET] [--cursor CURSOR]", "ornn admin users list --query QUERY --limit 25 [--cursor CURSOR] --format json"));
|
|
40
|
+
export const adminOrganizationsListCapability = withAdminBinding(organizationListCapability, adminHelp(["admin", "organizations", "list"], "List organizations in the Authorization directory.", "ornn admin organizations list [--query QUERY] [--role consumer|supplier] [--approved true|false] [--ids UUID,...] [--limit 25] [--offset OFFSET] [--cursor CURSOR]", "ornn admin organizations list --query QUERY --limit 25 [--cursor CURSOR] --format json"));
|
|
40
41
|
export const adminSuppliersListCapability = withAdminBinding(listSuppliersCapability, adminHelp(["admin", "suppliers", "list"], "List supplier organizations visible to staff.", "ornn admin suppliers list [--limit 50] [--cursor CURSOR]", "ornn admin suppliers list --limit 50 --format json"));
|
|
41
42
|
export const adminFacilitiesListCapability = withAdminBinding(listFacilitiesCapability, adminHelp(["admin", "facilities", "list"], "List facilities visible to staff.", "ornn admin facilities list [--supplier UUID] [--limit 50] [--cursor CURSOR]", "ornn admin facilities list --limit 50 --format json"));
|
|
42
43
|
export const adminNodesListCapability = withAdminBinding(listNodesCapability, adminHelp(["admin", "nodes", "list"], "List enrolled, ghost, and off-grid nodes.", "ornn admin nodes list [--supplier UUID] [--facility FACILITY] [--status STATUS] [--email EMAIL] [--limit 50] [--cursor CURSOR]", "ornn admin nodes list --limit 50 [--cursor CURSOR] --format json"));
|
|
@@ -64,7 +65,8 @@ export const adminNodeMetricsCapability = withAdminBinding(telemetryLatestCapabi
|
|
|
64
65
|
export const adminNodeLogsCapability = withAdminBinding(logsLatestCapability, adminHelp(["admin", "nodes", "logs"], "Read one bounded node log page.", "ornn admin nodes logs <node-id> [--stream kernel|serial] [--count 200] [--before TIME] [--cursor CURSOR]", "ornn admin nodes logs <node-id> --stream kernel --count 200 [--before TIME] [--cursor CURSOR] --format json", ["canonical node ID"]));
|
|
65
66
|
export const adminEnrollmentTokensListCapability = withAdminBinding(tokensListCapability, adminHelp(["admin", "enrollment-tokens", "list"], "List enrollment-token metadata without token bytes.", "ornn admin enrollment-tokens list [--limit 50] [--cursor CURSOR]", "ornn admin enrollment-tokens list --limit 50 --format json"));
|
|
66
67
|
export const adminCapabilities = [
|
|
67
|
-
|
|
68
|
+
adminUsersListCapability,
|
|
69
|
+
adminOrganizationsListCapability,
|
|
68
70
|
adminSuppliersListCapability,
|
|
69
71
|
adminFacilitiesListCapability,
|
|
70
72
|
adminNodesListCapability,
|
|
@@ -74,14 +76,14 @@ export const adminCapabilities = [
|
|
|
74
76
|
adminEnrollmentTokensListCapability,
|
|
75
77
|
];
|
|
76
78
|
const replacements = {
|
|
77
|
-
"listings.create": adminCapacityOffersCreateCapability,
|
|
78
79
|
"facilities.list": adminFacilitiesListCapability,
|
|
79
80
|
"logs.latest": adminNodeLogsCapability,
|
|
80
81
|
"nodes.list": adminNodesListCapability,
|
|
81
82
|
"supplier.list": adminSuppliersListCapability,
|
|
82
83
|
"telemetry.latest": adminNodeMetricsCapability,
|
|
83
84
|
"tokens.list": adminEnrollmentTokensListCapability,
|
|
84
|
-
"users.
|
|
85
|
+
"users.list": adminUsersListCapability,
|
|
86
|
+
"organization.list": adminOrganizationsListCapability,
|
|
85
87
|
};
|
|
86
88
|
/** Applies canonical admin CLI bindings without changing MCP or Slack identities. */
|
|
87
89
|
export function bindAdminCapabilities(capabilities) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { nativeTermsInput, nativeTermFields, listingWriteControls } from "./native-listing.js";
|
|
3
|
+
import { defineCapability, preview } from "../capability.js";
|
|
4
|
+
import { consent, idempotencyHeaders, positionals, query, seg } from "./market-shared.js";
|
|
4
5
|
// Commerce market verbs: one capability per role-aware `/commerce` route.
|
|
5
6
|
// Commerce decides from the caller's session what each listing or
|
|
6
7
|
// reservation reveals; these verbs return its JSON unchanged.
|
|
@@ -18,7 +19,6 @@ function help(command, outcome, human, automation, related, order, acceptedRefer
|
|
|
18
19
|
order,
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
|
-
const microUsd = z.coerce.number().int().positive();
|
|
22
22
|
const pageLimit = z.coerce.number().int().min(1).max(200).optional();
|
|
23
23
|
const nodeIdList = z.preprocess((value) => typeof value === "string"
|
|
24
24
|
? value
|
|
@@ -28,29 +28,29 @@ const nodeIdList = z.preprocess((value) => typeof value === "string"
|
|
|
28
28
|
: value, z.array(z.string().uuid()).min(1));
|
|
29
29
|
export const listingsListInput = z.object({
|
|
30
30
|
$positionals: positionals,
|
|
31
|
-
kind: z.enum(["primary", "secondary"]).optional(),
|
|
32
|
-
state: z.enum(["available", "
|
|
31
|
+
kind: z.enum(["primary", "secondary", "on_demand"]).optional(),
|
|
32
|
+
state: z.enum(["draft", "available", "held", "sold", "delisted"]).optional(),
|
|
33
33
|
sourceReservationId: z.string().uuid().optional(),
|
|
34
34
|
sourceListingId: z.string().uuid().optional(),
|
|
35
|
-
limit:
|
|
35
|
+
limit: z.coerce.number().int().min(1).max(100).optional(),
|
|
36
36
|
cursor: z.string().min(1).optional(),
|
|
37
37
|
});
|
|
38
38
|
export const listingsListCapability = defineCapability({
|
|
39
39
|
id: "listings.list",
|
|
40
40
|
domain: "listings",
|
|
41
41
|
roles: ["user"],
|
|
42
|
-
description: "List
|
|
42
|
+
description: "List native capacity listings. Primary listings require organization approval. Returns capacityListingId, hardware, capacity and availability; read listings.terms separately for prices and durations.",
|
|
43
43
|
mutation: "read",
|
|
44
|
-
http: { method: "GET", path: "/commerce/listings" },
|
|
44
|
+
http: { method: "GET", path: "/commerce/v2/listings" },
|
|
45
45
|
input: listingsListInput,
|
|
46
46
|
cli: {
|
|
47
47
|
command: ["capacity-offers", "list"],
|
|
48
|
-
vnext: help(["capacity-offers", "list"], "List
|
|
48
|
+
vnext: help(["capacity-offers", "list"], "List capacity parents with bounded pagination; read their financial terms separately.", "ornn capacity-offers list [--kind primary|secondary|on_demand] [--source-reservation-id <id>] [--limit <1-100>] [--cursor <opaque>]", "ornn capacity-offers list --limit 50 --format json", ["capacity-offers show", "capacity-offers terms", "schedules list"], 20, ["opaque page cursor"]),
|
|
49
49
|
},
|
|
50
50
|
slack: false,
|
|
51
51
|
execute: async (ctx, input) => {
|
|
52
52
|
const parsed = listingsListInput.parse(input ?? {});
|
|
53
|
-
return ctx.fetch(`/commerce/listings${query({
|
|
53
|
+
return ctx.fetch(`/commerce/v2/listings${query({
|
|
54
54
|
kind: parsed.kind,
|
|
55
55
|
state: parsed.state,
|
|
56
56
|
sourceReservationId: parsed.sourceReservationId,
|
|
@@ -68,122 +68,113 @@ export const listingShowCapability = defineCapability({
|
|
|
68
68
|
id: "listings.show",
|
|
69
69
|
domain: "listings",
|
|
70
70
|
roles: ["user"],
|
|
71
|
-
description: "Show one
|
|
71
|
+
description: "Show one native capacity listing. Returns capacityListingId, hardware, capacity and availability; use listings.terms for financial choices.",
|
|
72
72
|
mutation: "read",
|
|
73
|
-
http: { method: "GET", path: "/commerce/listings/{listingId}" },
|
|
73
|
+
http: { method: "GET", path: "/commerce/v2/listings/{listingId}" },
|
|
74
74
|
input: listingShowInput,
|
|
75
75
|
cli: {
|
|
76
76
|
command: ["capacity-offers", "show"],
|
|
77
|
-
vnext: help(["capacity-offers", "show"], "Inspect one listing's hardware,
|
|
77
|
+
vnext: help(["capacity-offers", "show"], "Inspect one listing's hardware, capacity and availability.", "ornn capacity-offers show --listing-id <id>", "ornn capacity-offers show --listing-id <id> --format json", ["capacity-offers list", "capacity-offers terms", "schedules show"], 21, ["listing ID"]),
|
|
78
78
|
},
|
|
79
79
|
slack: false,
|
|
80
80
|
execute: async (ctx, input) => {
|
|
81
81
|
const parsed = listingShowInput.parse(input);
|
|
82
|
-
return ctx.fetch(`/commerce/listings/${seg(parsed.listingId)}`, { method: "GET" });
|
|
82
|
+
return ctx.fetch(`/commerce/v2/listings/${seg(parsed.listingId)}`, { method: "GET" });
|
|
83
83
|
},
|
|
84
84
|
});
|
|
85
|
-
export const
|
|
86
|
-
.
|
|
85
|
+
export const listingTermsCapability = defineCapability({
|
|
86
|
+
id: "listings.terms",
|
|
87
|
+
domain: "listings",
|
|
88
|
+
roles: ["user"],
|
|
89
|
+
description: "Read a capacity listing's advertised financial terms. Each term returns listingTermsId, capacityListingId, pricePerGpuHourMicroUsd, duration bounds, downPaymentCents and renewable. Use the parent's capacityListingId as listingId. Prices are integer micro-USD per GPU-hour.",
|
|
90
|
+
mutation: "read",
|
|
91
|
+
http: { method: "GET", path: "/commerce/v2/listings/{listingId}/terms" },
|
|
92
|
+
input: listingShowInput,
|
|
93
|
+
cli: {
|
|
94
|
+
command: ["capacity-offers", "terms"],
|
|
95
|
+
vnext: help(["capacity-offers", "terms"], "Read the advertised financial terms for a capacity listing.", "ornn capacity-offers terms --listing-id <id>", "ornn capacity-offers terms --listing-id <id> --format json", ["capacity-offers show", "offers post"], 22, ["capacityListingId from capacity-offers list or show"]),
|
|
96
|
+
},
|
|
97
|
+
slack: false,
|
|
98
|
+
execute: async (ctx, input) => {
|
|
99
|
+
const parsed = listingShowInput.parse(input);
|
|
100
|
+
return ctx.fetch(`/commerce/v2/listings/${seg(parsed.listingId)}/terms`, { method: "GET" });
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
export const listingRelistInput = z.object({
|
|
87
104
|
$positionals: positionals,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
.uuid()
|
|
96
|
-
.optional()
|
|
97
|
-
.describe("A primary listing you own (suppliers relist their own listings)."),
|
|
98
|
-
nodeIds: nodeIdList.describe("The nodes to relist: a subset of the source's nodes (comma-separated on the CLI)."),
|
|
99
|
-
startsAt: z.string().min(1).describe("RFC 3339 start, inside the source's window."),
|
|
100
|
-
endsAt: z.string().min(1).describe("RFC 3339 end, inside the source's window."),
|
|
101
|
-
askPricePerGpuHourMicroUsd: microUsd.describe("Asking price in micro-USD per GPU-hour, the listing's only price: buyers buy any open window at it, and idle time sells as spot at it."),
|
|
102
|
-
sellAtMarketFirst: flag
|
|
103
|
-
.default(false)
|
|
104
|
-
.describe("Sell each node's GPUs into resting spot bids before asking at the asking price."),
|
|
105
|
+
reservationId: z.string().uuid(),
|
|
106
|
+
startsAt: z.iso.datetime({ offset: true }),
|
|
107
|
+
endsAt: z.iso.datetime({ offset: true }),
|
|
108
|
+
nodeCount: z.coerce.number().int().positive(),
|
|
109
|
+
nodeIds: nodeIdList.optional(),
|
|
110
|
+
reservedNodeIds: nodeIdList.optional(),
|
|
111
|
+
terms: nativeTermsInput,
|
|
105
112
|
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
106
113
|
confirm: z.boolean().optional(),
|
|
107
|
-
})
|
|
108
|
-
.refine((relist) => (relist.sourceReservationId === undefined) !== (relist.sourceListingId === undefined), {
|
|
109
|
-
message: "Give exactly one of sourceReservationId or sourceListingId.",
|
|
110
|
-
path: ["sourceReservationId"],
|
|
111
114
|
});
|
|
112
115
|
export const listingRelistCapability = defineCapability({
|
|
113
116
|
id: "listings.relist",
|
|
114
117
|
domain: "listings",
|
|
115
118
|
roles: ["user"],
|
|
116
|
-
description: "Relist
|
|
117
|
-
"Buyers buy any open window of it at the asking price, and its unsold time goes on the spot book. Fails with node_window_sold if those nodes are already sold for part of the window, or relist_overlaps if another live relist of those nodes overlaps it. Requires confirm: true.",
|
|
119
|
+
description: "Relist delivered capacity from a reservation using a native availability window, nodeCount and financial terms. Supply nodeIds or reservedNodeIds only when selecting specific nodes. Requires confirm: true.",
|
|
118
120
|
mutation: "preview-confirm",
|
|
119
|
-
http: { method: "POST", path: "/commerce/
|
|
121
|
+
http: { method: "POST", path: "/commerce/reservations/{reservationId}/relist" },
|
|
120
122
|
input: listingRelistInput,
|
|
121
123
|
cli: {
|
|
122
124
|
command: ["capacity-offers", "relist"],
|
|
123
|
-
vnext: help(["capacity-offers", "relist"], "Relist
|
|
125
|
+
vnext: help(["capacity-offers", "relist"], "Relist reserved capacity with explicit financial terms.", "ornn capacity-offers relist --reservation-id UUID --node-count COUNT --starts-at TIME --ends-at TIME --terms JSON [--node-ids UUID,...] [--reserved-node-ids UUID,...]", "ornn capacity-offers relist --reservation-id UUID --node-count 1 --starts-at TIME --ends-at TIME --terms JSON --idempotency-key KEY --yes --format json", ["capacity-offers terms", "capacity-offers delist"], 23, ["reservation ID"]),
|
|
124
126
|
},
|
|
125
127
|
slack: false,
|
|
126
128
|
execute: async (ctx, input) => {
|
|
127
|
-
const
|
|
128
|
-
const body = {
|
|
129
|
-
...(parsed.sourceReservationId ? { sourceReservationId: parsed.sourceReservationId } : {}),
|
|
130
|
-
...(parsed.sourceListingId ? { sourceListingId: parsed.sourceListingId } : {}),
|
|
131
|
-
nodeIds: parsed.nodeIds,
|
|
132
|
-
startsAt: parsed.startsAt,
|
|
133
|
-
endsAt: parsed.endsAt,
|
|
134
|
-
askPricePerGpuHourMicroUsd: parsed.askPricePerGpuHourMicroUsd,
|
|
135
|
-
sellAtMarketFirst: parsed.sellAtMarketFirst,
|
|
136
|
-
};
|
|
129
|
+
const { $positionals: _, reservationId, confirm: _confirm, idempotencyKey, ...body } = listingRelistInput.parse(input);
|
|
137
130
|
const gate = await consent(ctx, "listings.relist", "Relist capacity", [
|
|
138
|
-
{
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
? `reservation ${parsed.sourceReservationId}`
|
|
142
|
-
: `listing ${parsed.sourceListingId}`,
|
|
143
|
-
},
|
|
144
|
-
{ label: "Nodes", value: parsed.nodeIds.join(", ") },
|
|
145
|
-
{ label: "Window", value: `${parsed.startsAt} to ${parsed.endsAt}` },
|
|
146
|
-
{ label: "Asking price", value: microUsdLabel(parsed.askPricePerGpuHourMicroUsd) },
|
|
147
|
-
{ label: "Sell at market first", value: parsed.sellAtMarketFirst ? "yes" : "no" },
|
|
148
|
-
], body);
|
|
131
|
+
{ label: "Reservation", value: reservationId },
|
|
132
|
+
{ label: "Capacity and financial terms", value: JSON.stringify(body) },
|
|
133
|
+
], { reservationId, ...body });
|
|
149
134
|
if (gate)
|
|
150
135
|
return gate;
|
|
151
|
-
return ctx.fetch(
|
|
136
|
+
return ctx.fetch(`/commerce/reservations/${seg(reservationId)}/relist`, {
|
|
152
137
|
method: "POST",
|
|
153
138
|
body,
|
|
154
|
-
headers: idempotencyHeaders(ctx,
|
|
139
|
+
headers: idempotencyHeaders(ctx, idempotencyKey),
|
|
155
140
|
});
|
|
156
141
|
},
|
|
157
142
|
});
|
|
158
|
-
export const listingUpdateInput = z
|
|
143
|
+
export const listingUpdateInput = z
|
|
144
|
+
.object({
|
|
159
145
|
$positionals: positionals,
|
|
160
146
|
listingId: z.string().uuid(),
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
147
|
+
listingTermsId: z.string().uuid(),
|
|
148
|
+
...nativeTermFields,
|
|
149
|
+
...listingWriteControls,
|
|
150
|
+
})
|
|
151
|
+
.strict();
|
|
164
152
|
export const listingUpdateCapability = defineCapability({
|
|
165
153
|
id: "listings.update",
|
|
166
154
|
domain: "listings",
|
|
167
|
-
roles: ["
|
|
168
|
-
description: "
|
|
155
|
+
roles: ["admin"],
|
|
156
|
+
description: "Staff only: edit one advertised native financial term with its complete financial fields. Reuse idempotencyKey on retries. Requires confirm: true.",
|
|
169
157
|
mutation: "preview-confirm",
|
|
170
|
-
http: { method: "PATCH", path: "/commerce/listings/{listingId}" },
|
|
158
|
+
http: { method: "PATCH", path: "/commerce/v2/listings/{listingId}/terms/{listingTermsId}" },
|
|
171
159
|
input: listingUpdateInput,
|
|
172
160
|
cli: {
|
|
173
|
-
command: ["capacity-offers", "update"],
|
|
174
|
-
vnext:
|
|
161
|
+
command: ["admin", "capacity-offers", "update"],
|
|
162
|
+
vnext: {
|
|
163
|
+
...help(["admin", "capacity-offers", "update"], "Edit one advertised financial term as staff.", "ornn admin capacity-offers update --listing-id UUID --listing-terms-id UUID --min-duration-hours HOURS --max-duration-hours HOURS --price-per-gpu-hour-micro-usd RATE --renewable true|false --idempotency-key KEY [--type reserved|spot|on-demand] [--down-payment-cents CENTS]", "Requires an eligible human terminal; reuse --idempotency-key on retries.", ["capacity-offers terms"], 24),
|
|
164
|
+
audience: "admin",
|
|
165
|
+
},
|
|
175
166
|
},
|
|
176
167
|
slack: false,
|
|
168
|
+
mcp: false,
|
|
177
169
|
execute: async (ctx, input) => {
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return ctx.fetch(`/commerce/listings/${seg(parsed.listingId)}`, { method: "PATCH", body });
|
|
170
|
+
const { $positionals: _, listingId, listingTermsId, confirm: _confirm, idempotencyKey, ...body } = listingUpdateInput.parse(input);
|
|
171
|
+
if (!ctx.confirmed)
|
|
172
|
+
return preview("listings.update", { listingId, listingTermsId, ...body });
|
|
173
|
+
return ctx.fetch(`/commerce/v2/listings/${seg(listingId)}/terms/${seg(listingTermsId)}`, {
|
|
174
|
+
method: "PATCH",
|
|
175
|
+
body,
|
|
176
|
+
headers: idempotencyHeaders(ctx, idempotencyKey),
|
|
177
|
+
});
|
|
187
178
|
},
|
|
188
179
|
});
|
|
189
180
|
export const listingDelistInput = z.object({
|
|
@@ -197,7 +188,7 @@ export const listingDelistCapability = defineCapability({
|
|
|
197
188
|
roles: ["user"],
|
|
198
189
|
description: "Delist a listing you own. Its unsold time leaves the market, its spot asks are cancelled and running spot reservations on it end. Windows already bought stay sold. Requires confirm: true.",
|
|
199
190
|
mutation: "preview-confirm",
|
|
200
|
-
http: { method: "
|
|
191
|
+
http: { method: "POST", path: "/commerce/v2/listings/{listingId}/delist" },
|
|
201
192
|
input: listingDelistInput,
|
|
202
193
|
cli: {
|
|
203
194
|
command: ["capacity-offers", "delist"],
|
|
@@ -215,7 +206,7 @@ export const listingDelistCapability = defineCapability({
|
|
|
215
206
|
], { listingId: parsed.listingId });
|
|
216
207
|
if (gate)
|
|
217
208
|
return gate;
|
|
218
|
-
return ctx.fetch(`/commerce/listings/${seg(parsed.listingId)}`, { method: "
|
|
209
|
+
return ctx.fetch(`/commerce/v2/listings/${seg(parsed.listingId)}/delist`, { method: "POST" });
|
|
219
210
|
},
|
|
220
211
|
});
|
|
221
212
|
export const reservationsListInput = z.object({
|
|
@@ -301,30 +292,14 @@ export const reservationInvoicesCapability = defineCapability({
|
|
|
301
292
|
});
|
|
302
293
|
},
|
|
303
294
|
});
|
|
304
|
-
export const feesShowInput = z.object({ $positionals: positionals });
|
|
305
|
-
export const feesShowCapability = defineCapability({
|
|
306
|
-
id: "fees.show",
|
|
307
|
-
domain: "billing",
|
|
308
|
-
roles: ["user"],
|
|
309
|
-
description: "Show the transaction fee on spot and secondary trades, in parts per million (1000000 = 100%). The side that takes a resting order pays it; primary schedules carry no transaction fee.",
|
|
310
|
-
mutation: "read",
|
|
311
|
-
http: { method: "GET", path: "/commerce/fees" },
|
|
312
|
-
input: feesShowInput,
|
|
313
|
-
cli: {
|
|
314
|
-
command: ["fees", "show"],
|
|
315
|
-
vnext: help(["fees", "show"], "Show the transaction fee on spot and secondary trades.", "ornn fees show", "ornn fees show --format json", ["spot bid", "offers post"], 29),
|
|
316
|
-
},
|
|
317
|
-
slack: false,
|
|
318
|
-
execute: async (ctx) => ctx.fetch("/commerce/fees", { method: "GET" }),
|
|
319
|
-
});
|
|
320
295
|
export const commerceMarketCapabilities = [
|
|
321
296
|
listingsListCapability,
|
|
322
297
|
listingShowCapability,
|
|
298
|
+
listingTermsCapability,
|
|
323
299
|
listingRelistCapability,
|
|
324
300
|
listingUpdateCapability,
|
|
325
301
|
listingDelistCapability,
|
|
326
302
|
reservationsListCapability,
|
|
327
303
|
reservationsShowCapability,
|
|
328
304
|
reservationInvoicesCapability,
|
|
329
|
-
feesShowCapability,
|
|
330
305
|
];
|
|
@@ -23,6 +23,7 @@ export const launchInput = z
|
|
|
23
23
|
resume: z.union([z.string().min(1), z.literal(true)]).optional(),
|
|
24
24
|
timeout: z.string().optional(),
|
|
25
25
|
username: z.string().optional(),
|
|
26
|
+
vms: z.string().optional(),
|
|
26
27
|
wait: z.boolean().optional(),
|
|
27
28
|
yes: z.boolean().optional(),
|
|
28
29
|
volume: z.string().optional(),
|
|
@@ -139,11 +140,12 @@ export const launchCapability = cliOnly({
|
|
|
139
140
|
category: "journey",
|
|
140
141
|
order: 2,
|
|
141
142
|
outcome: "Launch one reviewed VM, Bare Metal machine, Kubernetes cluster, or Slurm cluster from active reserved capacity, including a declared durable Bare Metal workspace.",
|
|
142
|
-
human: "ornn launch [<capacity-reservation>] --mode vm|bare-metal|kubernetes|slurm [--key <ssh-key>]; Bare Metal may add --network shared --volume <managed-volume> --mount-access read-only|read-write; Kubernetes/Slurm prompt for missing --network public|private and --nodes <membership-count>",
|
|
143
|
-
automation: "ornn launch <canonical-reservation-id> --mode vm|bare-metal|kubernetes|slurm --organization <id> --yes --idempotency-key <key> --format json; VM/Bare Metal/Slurm require --key <canonical-key-id>; Bare Metal may add --network shared --volume <canonical-volume-id> --mount-access read-only|read-write; Kubernetes/Slurm require --network public|private --nodes <membership-count> --machines <canonical-machine-ids>",
|
|
143
|
+
human: "ornn launch [<capacity-reservation>] --mode vm|bare-metal|kubernetes|slurm [--key <ssh-key>]; Bare Metal may add --network shared --volume <managed-volume> --mount-access read-only|read-write; Kubernetes/Slurm prompt for missing --network public|private and --nodes <membership-count>, and may add running VMs with --vms <vm-ids>",
|
|
144
|
+
automation: "ornn launch <canonical-reservation-id> --mode vm|bare-metal|kubernetes|slurm --organization <id> --yes --idempotency-key <key> --format json; VM/Bare Metal/Slurm require --key <canonical-key-id>; Bare Metal may add --network shared --volume <canonical-volume-id> --mount-access read-only|read-write; Kubernetes/Slurm require --network public|private --nodes <membership-count> --machines <canonical-machine-ids>, and may add --vms <canonical-vm-ids> (with --nodes 0 for a cluster of VMs only)",
|
|
144
145
|
acceptedReferences: [
|
|
145
146
|
"canonical capacity-reservation ID",
|
|
146
147
|
"canonical eligible machine IDs",
|
|
148
|
+
"canonical running VM IDs for cluster membership",
|
|
147
149
|
"invocation-local eligible reservation and machine choices",
|
|
148
150
|
"exact SSH-key and managed-volume references; organization network shared",
|
|
149
151
|
],
|