@ornncompute/cli 1.0.72 → 1.0.97
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 +55 -24
- package/package.json +4 -2
- package/src/admin-capacity-reservations.mjs +6 -13
- package/src/admin-command-handlers.mjs +2 -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 +3 -41
- package/src/admin-node-lifecycle.mjs +40 -160
- package/src/admin-node-sanitize.mjs +243 -0
- package/src/admin-shell.mjs +39 -32
- package/src/catalog-shell.mjs +6 -6
- package/src/evidence-commands.mjs +4 -6
- package/src/external-bucket-commands.mjs +76 -96
- package/src/interaction-runtime.mjs +5 -37
- package/src/kubernetes-commands.mjs +25 -183
- package/src/launch-mode-handlers.mjs +0 -1
- package/src/machine-reference.mjs +33 -56
- package/src/mount-commands.mjs +25 -131
- package/src/slurm-commands.mjs +13 -2
- package/src/storage-commands.mjs +2 -87
- package/src/vm-commands.mjs +48 -101
- package/vendor/api-client/commerce.js +743 -0
- package/vendor/capabilities/cli.js +7688 -7
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +0 -101
- package/vendor/capabilities/capabilities/admin-enrollment.js +0 -321
- package/vendor/capabilities/capabilities/admin-node-diagnostics.js +0 -88
- package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +0 -235
- package/vendor/capabilities/capabilities/admin.js +0 -91
- package/vendor/capabilities/capabilities/cli.js +0 -156
- package/vendor/capabilities/capabilities/clusters.js +0 -248
- package/vendor/capabilities/capabilities/commerce-market.js +0 -405
- package/vendor/capabilities/capabilities/compute.js +0 -523
- package/vendor/capabilities/capabilities/credits.js +0 -78
- package/vendor/capabilities/capabilities/evidence.js +0 -145
- package/vendor/capabilities/capabilities/external-buckets.js +0 -191
- package/vendor/capabilities/capabilities/fleet.js +0 -306
- package/vendor/capabilities/capabilities/identity.js +0 -129
- package/vendor/capabilities/capabilities/listings.js +0 -398
- package/vendor/capabilities/capabilities/locality.js +0 -42
- package/vendor/capabilities/capabilities/market-shared.js +0 -46
- package/vendor/capabilities/capabilities/markets.js +0 -482
- package/vendor/capabilities/capabilities/mounts.js +0 -176
- package/vendor/capabilities/capabilities/native-listing.js +0 -99
- package/vendor/capabilities/capabilities/networks.js +0 -1264
- package/vendor/capabilities/capabilities/observability.js +0 -218
- package/vendor/capabilities/capabilities/organization-invites.js +0 -53
- package/vendor/capabilities/capabilities/reservation-list.js +0 -68
- package/vendor/capabilities/capabilities/staff-shared.js +0 -64
- package/vendor/capabilities/capabilities/tokens.js +0 -165
- package/vendor/capabilities/capabilities/users.js +0 -159
- package/vendor/capabilities/capabilities/volumes.js +0 -178
- package/vendor/capabilities/capability.js +0 -99
- package/vendor/capabilities/cli-catalog.js +0 -73
- package/vendor/capabilities/cli-migrations.js +0 -264
- package/vendor/capabilities/names.js +0 -6
- package/vendor/capabilities/software-catalog.js +0 -82
- package/vendor/capabilities/spec-catalog.js +0 -151
- package/vendor/capabilities/supplier-directory.js +0 -20
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { defineCapability } from "../capability.js";
|
|
3
|
-
const emptyInput = z.object({});
|
|
4
|
-
const loginInput = z.object({
|
|
5
|
-
authBase: z.string().min(1).optional(),
|
|
6
|
-
noBrowser: z.boolean().optional(),
|
|
7
|
-
pollInterval: z.number().positive().optional(),
|
|
8
|
-
timeout: z.number().positive().optional(),
|
|
9
|
-
});
|
|
10
|
-
function executeSession(context, command, input) {
|
|
11
|
-
if (!context.cli?.session) {
|
|
12
|
-
throw new Error("This capability must execute through the Ornn CLI session runtime.");
|
|
13
|
-
}
|
|
14
|
-
return context.cli.session(command, input);
|
|
15
|
-
}
|
|
16
|
-
export const loginCapability = defineCapability({
|
|
17
|
-
id: "identity.login",
|
|
18
|
-
domain: "identity",
|
|
19
|
-
roles: ["user"],
|
|
20
|
-
description: "Authenticate the CLI with Ornn device authorization.",
|
|
21
|
-
mutation: "local",
|
|
22
|
-
http: { method: "POST", path: "/v1/cli/device/start" },
|
|
23
|
-
input: loginInput,
|
|
24
|
-
cli: {
|
|
25
|
-
command: ["login"],
|
|
26
|
-
vnext: {
|
|
27
|
-
audience: "customer",
|
|
28
|
-
category: "session",
|
|
29
|
-
arguments: "semantic",
|
|
30
|
-
outcome: "Authenticate in a browser and return to the terminal.",
|
|
31
|
-
human: "ornn login [--no-browser] [--timeout <seconds>]",
|
|
32
|
-
automation: "ornn login --format json",
|
|
33
|
-
resumableStates: ["browser authorization"],
|
|
34
|
-
related: ["context", "logout"],
|
|
35
|
-
order: 1,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
mcp: false,
|
|
39
|
-
slack: false,
|
|
40
|
-
execute: async (context, input) => executeSession(context, "login", input),
|
|
41
|
-
});
|
|
42
|
-
export const contextCapability = defineCapability({
|
|
43
|
-
id: "identity.context",
|
|
44
|
-
domain: "identity",
|
|
45
|
-
roles: ["user"],
|
|
46
|
-
description: "Show the current server-verified organization context.",
|
|
47
|
-
mutation: "read",
|
|
48
|
-
http: { method: "GET", path: "/v1/cli/session" },
|
|
49
|
-
input: emptyInput,
|
|
50
|
-
cli: {
|
|
51
|
-
command: ["context"],
|
|
52
|
-
vnext: {
|
|
53
|
-
audience: "customer",
|
|
54
|
-
category: "session",
|
|
55
|
-
arguments: "semantic",
|
|
56
|
-
outcome: "Show the current user, selected organization, approval, and available organizations.",
|
|
57
|
-
human: "ornn context",
|
|
58
|
-
automation: "ornn context --format json",
|
|
59
|
-
related: ["login", "logout"],
|
|
60
|
-
order: 2,
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
mcp: false,
|
|
64
|
-
slack: false,
|
|
65
|
-
execute: async (context, input) => executeSession(context, "context", input),
|
|
66
|
-
});
|
|
67
|
-
export const logoutCapability = defineCapability({
|
|
68
|
-
id: "identity.logout",
|
|
69
|
-
domain: "identity",
|
|
70
|
-
roles: ["user"],
|
|
71
|
-
description: "Revoke the remote CLI session and clear its local bearer.",
|
|
72
|
-
mutation: "local",
|
|
73
|
-
http: { method: "DELETE", path: "/v1/cli/session" },
|
|
74
|
-
input: emptyInput,
|
|
75
|
-
cli: {
|
|
76
|
-
command: ["logout"],
|
|
77
|
-
vnext: {
|
|
78
|
-
audience: "customer",
|
|
79
|
-
category: "session",
|
|
80
|
-
outcome: "End the remote session and always remove the local bearer.",
|
|
81
|
-
human: "ornn logout",
|
|
82
|
-
automation: "ornn logout --format json",
|
|
83
|
-
related: ["login", "context"],
|
|
84
|
-
order: 3,
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
mcp: false,
|
|
88
|
-
slack: false,
|
|
89
|
-
execute: async (context, input) => executeSession(context, "logout", input),
|
|
90
|
-
});
|
|
91
|
-
export const whoamiCapability = defineCapability({
|
|
92
|
-
id: "identity.whoami",
|
|
93
|
-
domain: "identity",
|
|
94
|
-
roles: ["user", "admin"],
|
|
95
|
-
description: "Show the signed-in Ornn user and organization.",
|
|
96
|
-
mutation: "read",
|
|
97
|
-
http: { method: "GET", path: "/v1/cli/session" },
|
|
98
|
-
input: emptyInput,
|
|
99
|
-
execute: async (ctx) => ctx.fetch("/v1/cli/session", { method: "GET" }),
|
|
100
|
-
});
|
|
101
|
-
export const statusCapability = defineCapability({
|
|
102
|
-
id: "identity.status",
|
|
103
|
-
domain: "identity",
|
|
104
|
-
roles: ["user", "admin"],
|
|
105
|
-
description: "Show Ornn platform status.",
|
|
106
|
-
mutation: "read",
|
|
107
|
-
http: { method: "GET", path: "/v1/cli/status" },
|
|
108
|
-
input: emptyInput,
|
|
109
|
-
execute: async (ctx) => ctx.fetch("/v1/cli/status", { method: "GET" }),
|
|
110
|
-
cli: {
|
|
111
|
-
command: ["status"],
|
|
112
|
-
vnext: {
|
|
113
|
-
audience: "customer",
|
|
114
|
-
category: "platform",
|
|
115
|
-
outcome: "Check current Ornn platform availability.",
|
|
116
|
-
human: "ornn status",
|
|
117
|
-
automation: "ornn status --format json",
|
|
118
|
-
related: ["version", "resources"],
|
|
119
|
-
order: 10,
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
export const identityCapabilities = [
|
|
124
|
-
whoamiCapability,
|
|
125
|
-
loginCapability,
|
|
126
|
-
contextCapability,
|
|
127
|
-
logoutCapability,
|
|
128
|
-
statusCapability,
|
|
129
|
-
];
|
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
import { listSupplierOrganizations } from "../supplier-directory.js";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { defineCapability, preview } from "../capability.js";
|
|
4
|
-
import { ensureFacility, LOCATION_LOOKUP_UNAVAILABLE, locateRegion, unplacedLocation, } from "./locality.js";
|
|
5
|
-
import { bindHardwareFields, HARDWARE_LISTING_FIELDS, HARDWARE_LISTING_FILTERS, loadHardwareCatalog, projectCatalogTerms, resolveCatalogTerm, } from "../spec-catalog.js";
|
|
6
|
-
function queryPath(path, params) {
|
|
7
|
-
const parts = [];
|
|
8
|
-
for (const [key, value] of Object.entries(params)) {
|
|
9
|
-
if (value?.trim())
|
|
10
|
-
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.trim())}`);
|
|
11
|
-
}
|
|
12
|
-
return parts.length ? `${path}?${parts.join("&")}` : path;
|
|
13
|
-
}
|
|
14
|
-
export const listingCreateInput = z.object({
|
|
15
|
-
organizationId: z.string().optional(),
|
|
16
|
-
organization_id: z.string().optional(),
|
|
17
|
-
facilityId: z.string().optional(),
|
|
18
|
-
facility_id: z.string().optional(),
|
|
19
|
-
kind: z.enum(["gpu", "cpu", "node", "link"]).optional(),
|
|
20
|
-
gpu: z.string().optional(),
|
|
21
|
-
cpu: z.string().optional(),
|
|
22
|
-
site: z.string().optional(),
|
|
23
|
-
fabric: z.string().optional(),
|
|
24
|
-
min_nodes: z.number().int().optional(),
|
|
25
|
-
starts_after: z.string().optional(),
|
|
26
|
-
starts_before: z.string().optional(),
|
|
27
|
-
ends_after: z.string().optional(),
|
|
28
|
-
ends_before: z.string().optional(),
|
|
29
|
-
gpu_type: z.string().optional(),
|
|
30
|
-
node_model: z.string().optional(),
|
|
31
|
-
fabric_type: z.string().optional(),
|
|
32
|
-
node_count: z.number().int().optional(),
|
|
33
|
-
gpus_per_node: z.number().int().optional(),
|
|
34
|
-
site_nickname: z
|
|
35
|
-
.string()
|
|
36
|
-
.optional()
|
|
37
|
-
.describe("A state, city, address, or catalog region such as us-west-3."),
|
|
38
|
-
site_supplier: z.string().optional(),
|
|
39
|
-
ram: z.string().optional(),
|
|
40
|
-
storage: z.string().optional(),
|
|
41
|
-
internet: z.string().optional(),
|
|
42
|
-
network_hardware: z.string().optional(),
|
|
43
|
-
available_start_at: z
|
|
44
|
-
.string()
|
|
45
|
-
.optional()
|
|
46
|
-
.describe("Start of the availability window. YYYY-MM-DD or RFC3339."),
|
|
47
|
-
available_end_at: z
|
|
48
|
-
.string()
|
|
49
|
-
.optional()
|
|
50
|
-
.describe("End of the availability window. YYYY-MM-DD or RFC3339."),
|
|
51
|
-
available_from: z.string().optional().describe("Alias for available_start_at."),
|
|
52
|
-
available_to: z.string().optional().describe("Alias for available_end_at."),
|
|
53
|
-
buy_now_price_per_gpu_hour: z.number().optional().describe("Price buyers pay, USD per GPU-hour."),
|
|
54
|
-
deposit_percent: z.number().optional(),
|
|
55
|
-
down_payment_cents: z.number().int().nonnegative().optional(),
|
|
56
|
-
access_mode: z.enum(["bare-metal", "virtual-machine"]).optional(),
|
|
57
|
-
state: z.enum(["available", "draft"]).optional(),
|
|
58
|
-
price_omitted: z.boolean().optional(),
|
|
59
|
-
announce: z.boolean().optional(),
|
|
60
|
-
text: z.string().optional(),
|
|
61
|
-
confirm: z.boolean().optional(),
|
|
62
|
-
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
63
|
-
});
|
|
64
|
-
const catalogTermsInput = z.object({ kind: z.enum(["gpu", "cpu", "node", "link"]).optional() });
|
|
65
|
-
const slackListingCreateParams = {
|
|
66
|
-
type: "object",
|
|
67
|
-
properties: {
|
|
68
|
-
gpu_type: { type: "string" },
|
|
69
|
-
cpu: { type: "string" },
|
|
70
|
-
node_model: { type: "string" },
|
|
71
|
-
fabric_type: { type: "string" },
|
|
72
|
-
node_count: { type: "integer" },
|
|
73
|
-
gpus_per_node: { type: "integer" },
|
|
74
|
-
site_nickname: {
|
|
75
|
-
type: "string",
|
|
76
|
-
description: "Where the capacity is, exactly as the user said it: a state, city, address, or catalog region such as us-west-3. Ornn places it in a catalog region.",
|
|
77
|
-
},
|
|
78
|
-
site_supplier: { type: "string" },
|
|
79
|
-
ram: { type: "string" },
|
|
80
|
-
storage: { type: "string" },
|
|
81
|
-
available_start_at: { type: "string" },
|
|
82
|
-
available_end_at: { type: "string" },
|
|
83
|
-
buy_now_price_per_gpu_hour: { type: "number" },
|
|
84
|
-
deposit_percent: { type: "number" },
|
|
85
|
-
price_omitted: { type: "boolean" },
|
|
86
|
-
announce: { type: "boolean" },
|
|
87
|
-
text: { type: "string" },
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
export const listCatalogTermsCapability = defineCapability({
|
|
91
|
-
id: "listings.catalog_terms",
|
|
92
|
-
domain: "listings",
|
|
93
|
-
roles: ["admin"],
|
|
94
|
-
description: "Dump the orchestrator hardware dictionary (display name + aliases). Call once and pick from it. Hardware fields on listings must match a term. Optional kind filter: gpu, cpu, node, or link.",
|
|
95
|
-
mutation: "read",
|
|
96
|
-
http: { method: "GET", path: "/v1/orchestrator/catalog/hardware" },
|
|
97
|
-
slack: {
|
|
98
|
-
parameters: {
|
|
99
|
-
type: "object",
|
|
100
|
-
properties: {
|
|
101
|
-
kind: { type: "string", enum: ["gpu", "cpu", "node", "link"] },
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
input: catalogTermsInput,
|
|
106
|
-
execute: async (ctx, input) => {
|
|
107
|
-
const parsed = catalogTermsInput.parse(input);
|
|
108
|
-
const hardware = await loadHardwareCatalog(ctx.fetch);
|
|
109
|
-
return projectCatalogTerms(hardware.terms, parsed.kind);
|
|
110
|
-
},
|
|
111
|
-
});
|
|
112
|
-
export const listListingsCapability = defineCapability({
|
|
113
|
-
id: "listings.available",
|
|
114
|
-
domain: "listings",
|
|
115
|
-
roles: ["user", "admin"],
|
|
116
|
-
description: "List currently available marketplace listings. GPU, CPU, and fabric filters must be orchestrator catalog display names, canonicals, or aliases — not raw slang or spec-sheet text. Site may be a facility nickname. Returns spec-shaped JSON plus public reserve/OG/PDF URLs. Filters combine as AND. Date filters are inclusive YYYY-MM-DD.",
|
|
117
|
-
mutation: "read",
|
|
118
|
-
slack: {
|
|
119
|
-
parameters: {
|
|
120
|
-
type: "object",
|
|
121
|
-
properties: {
|
|
122
|
-
gpu: { type: "string", description: "Catalog GPU term such as H100 or NVIDIA H100." },
|
|
123
|
-
cpu: { type: "string", description: "Catalog CPU term such as Xeon or EPYC." },
|
|
124
|
-
site: { type: "string", description: "Location / facility filter such as Dallas." },
|
|
125
|
-
fabric: { type: "string", description: "Catalog link term such as XDR or NDR." },
|
|
126
|
-
min_nodes: { type: "integer", description: "Minimum total nodes." },
|
|
127
|
-
starts_after: {
|
|
128
|
-
type: "string",
|
|
129
|
-
description: "YYYY-MM-DD. Keep listings that start on or after this date.",
|
|
130
|
-
},
|
|
131
|
-
starts_before: {
|
|
132
|
-
type: "string",
|
|
133
|
-
description: "YYYY-MM-DD. Keep listings that start on or before this date.",
|
|
134
|
-
},
|
|
135
|
-
ends_after: {
|
|
136
|
-
type: "string",
|
|
137
|
-
description: "YYYY-MM-DD. Keep listings that end on or after this date.",
|
|
138
|
-
},
|
|
139
|
-
ends_before: {
|
|
140
|
-
type: "string",
|
|
141
|
-
description: "YYYY-MM-DD. Keep listings that end on or before this date.",
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
input: listingCreateInput,
|
|
147
|
-
execute: async (ctx, input) => {
|
|
148
|
-
const parsed = listingCreateInput.parse(input);
|
|
149
|
-
const hardware = await loadHardwareCatalog(ctx.fetch);
|
|
150
|
-
const bound = bindHardwareFields(hardware.terms, parsed, HARDWARE_LISTING_FILTERS);
|
|
151
|
-
if (!bound.ok)
|
|
152
|
-
return bound;
|
|
153
|
-
return ctx.fetch("/listings/available", { method: "GET", body: bound.payload });
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
const MICRO_USD = 1_000_000;
|
|
157
|
-
function microUsd(dollars) {
|
|
158
|
-
return Math.round(dollars * MICRO_USD);
|
|
159
|
-
}
|
|
160
|
-
/**
|
|
161
|
-
* A primary listing's down payment in cents, from a percentage of the buyer
|
|
162
|
-
* total over the window: price (micro-USD per GPU-hour) x GPUs x hours.
|
|
163
|
-
*/
|
|
164
|
-
export function primaryDownPaymentCents(terms) {
|
|
165
|
-
const hours = (Date.parse(terms.endsAt) - Date.parse(terms.startsAt)) / 3_600_000;
|
|
166
|
-
const totalCents = (terms.pricePerGpuHourMicroUsd * terms.nodeCount * terms.gpusPerNode * hours) / 10_000;
|
|
167
|
-
return Math.max(0, Math.round((totalCents * terms.percent) / 100));
|
|
168
|
-
}
|
|
169
|
-
/** Shared native creation body for Slack's preview and human-confirmed write. */
|
|
170
|
-
export function primaryListingBody(input, pricePerGpuHourMicroUsd, downPaymentCents) {
|
|
171
|
-
return {
|
|
172
|
-
...input,
|
|
173
|
-
kind: "primary",
|
|
174
|
-
terms: [
|
|
175
|
-
{
|
|
176
|
-
type: "reserved",
|
|
177
|
-
minDurationHours: 1 / 3600,
|
|
178
|
-
maxDurationHours: (Date.parse(input.availableUntil) - Date.parse(input.availableAt)) / 3600000,
|
|
179
|
-
renewable: false,
|
|
180
|
-
pricePerGpuHourMicroUsd,
|
|
181
|
-
downPaymentCents,
|
|
182
|
-
},
|
|
183
|
-
],
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
/** Commerce parses listing windows as RFC3339 timestamps, not bare dates. */
|
|
187
|
-
function rfc3339(value) {
|
|
188
|
-
const trimmed = value.trim();
|
|
189
|
-
if (!trimmed.includes("T"))
|
|
190
|
-
return `${trimmed}T00:00:00Z`;
|
|
191
|
-
return trimmed.endsWith("Z") || /[+-]\d{2}:\d{2}$/.test(trimmed) ? trimmed : `${trimmed}Z`;
|
|
192
|
-
}
|
|
193
|
-
// Keys the schema accepts for the caller's convenience but that are never part
|
|
194
|
-
// of the create body: window aliases (resolved into startAt/endAt below), the
|
|
195
|
-
// browse-side filters this input shares with listings.available, and the
|
|
196
|
-
// confirm flag. They used to ride along into the POST verbatim, which is why
|
|
197
|
-
// naming a window with available_from/available_to looked like a dead
|
|
198
|
-
// parameter — the alias was passed through unresolved and unnormalized instead
|
|
199
|
-
// of becoming the startAt commerce actually reads.
|
|
200
|
-
const LISTING_CREATE_INPUT_ONLY_KEYS = [
|
|
201
|
-
"available_start_at",
|
|
202
|
-
"available_end_at",
|
|
203
|
-
"available_from",
|
|
204
|
-
"available_to",
|
|
205
|
-
"starts_after",
|
|
206
|
-
"starts_before",
|
|
207
|
-
"ends_after",
|
|
208
|
-
"ends_before",
|
|
209
|
-
"min_nodes",
|
|
210
|
-
"site",
|
|
211
|
-
"kind",
|
|
212
|
-
"confirm",
|
|
213
|
-
"idempotencyKey",
|
|
214
|
-
"organizationId",
|
|
215
|
-
"organization_id",
|
|
216
|
-
"site_supplier",
|
|
217
|
-
"facilityId",
|
|
218
|
-
"facility_id",
|
|
219
|
-
"buy_now_price_per_gpu_hour",
|
|
220
|
-
"deposit_percent",
|
|
221
|
-
"down_payment_cents",
|
|
222
|
-
"access_mode",
|
|
223
|
-
"state",
|
|
224
|
-
"price_omitted",
|
|
225
|
-
];
|
|
226
|
-
export const createListingCapability = defineCapability({
|
|
227
|
-
id: "listings.create",
|
|
228
|
-
mcp: false,
|
|
229
|
-
domain: "listings",
|
|
230
|
-
roles: ["admin"],
|
|
231
|
-
description: "Create a primary marketplace listing (admin). gpu_type, cpu, node_model, and fabric_type must exact-match an orchestrator catalog display name, canonical, or alias; gpu_type becomes the listing's GPU slug. " +
|
|
232
|
-
"buy_now_price_per_gpu_hour is the customer price; deposit_percent is the upfront payment as a percentage of the full availability window. Nodes are attached afterwards. Slack never POSTs until a human replies confirm. MCP and CLI use native listing.create instead.",
|
|
233
|
-
mutation: "preview-confirm",
|
|
234
|
-
http: { method: "POST", path: "/commerce/v2/listings" },
|
|
235
|
-
slack: { parameters: slackListingCreateParams },
|
|
236
|
-
input: listingCreateInput,
|
|
237
|
-
execute: async (ctx, input) => {
|
|
238
|
-
const parsed = listingCreateInput.parse(input);
|
|
239
|
-
const hardware = await loadHardwareCatalog(ctx.fetch);
|
|
240
|
-
const { gpu, fabric, ...rest } = parsed;
|
|
241
|
-
const normalized = {
|
|
242
|
-
...rest,
|
|
243
|
-
gpu_type: parsed.gpu_type ?? gpu,
|
|
244
|
-
fabric_type: parsed.fabric_type ?? fabric,
|
|
245
|
-
};
|
|
246
|
-
const bound = bindHardwareFields(hardware.terms, normalized, HARDWARE_LISTING_FIELDS);
|
|
247
|
-
if (!bound.ok)
|
|
248
|
-
return bound;
|
|
249
|
-
let organizationId = parsed.organizationId ?? parsed.organization_id;
|
|
250
|
-
let facilityId = parsed.facilityId ?? parsed.facility_id;
|
|
251
|
-
const supplierQuery = (parsed.site_supplier ?? "").trim().toLowerCase();
|
|
252
|
-
const place = (parsed.site_nickname ?? parsed.site ?? "").trim();
|
|
253
|
-
if (!organizationId && supplierQuery) {
|
|
254
|
-
const matches = (await listSupplierOrganizations(ctx.fetch)).filter((org) => org.id.toLowerCase() === supplierQuery || org.name.toLowerCase() === supplierQuery);
|
|
255
|
-
if (matches.length !== 1) {
|
|
256
|
-
return { ok: false, error: matches.length ? "ambiguous_supplier" : "supplier_not_found" };
|
|
257
|
-
}
|
|
258
|
-
organizationId = matches[0].id;
|
|
259
|
-
}
|
|
260
|
-
let located = null;
|
|
261
|
-
let lookupUnavailable = false;
|
|
262
|
-
if (!facilityId && place) {
|
|
263
|
-
try {
|
|
264
|
-
located = await locateRegion(ctx.fetch, place);
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
if (ctx.confirmed)
|
|
268
|
-
throw error;
|
|
269
|
-
lookupUnavailable = true;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
if ((facilityId && !organizationId) || (!facilityId && !place && organizationId)) {
|
|
273
|
-
const matches = [];
|
|
274
|
-
const seenCursors = new Set();
|
|
275
|
-
let cursor;
|
|
276
|
-
do {
|
|
277
|
-
const response = (await ctx.fetch(queryPath("/provisioning/facilities", { cursor }), {
|
|
278
|
-
method: "GET",
|
|
279
|
-
}));
|
|
280
|
-
if (!Array.isArray(response.facilities))
|
|
281
|
-
throw new Error("invalid facility catalog");
|
|
282
|
-
matches.push(...response.facilities.filter((facility) => (!organizationId ||
|
|
283
|
-
(facility.organizationId ?? facility.organization_id) === organizationId) &&
|
|
284
|
-
(!facilityId || (facility.facilityId ?? facility.id) === facilityId)));
|
|
285
|
-
cursor = response.nextCursor ?? undefined;
|
|
286
|
-
if (cursor) {
|
|
287
|
-
if (seenCursors.has(cursor) || seenCursors.size >= 49) {
|
|
288
|
-
throw new Error("facility catalog pagination did not complete");
|
|
289
|
-
}
|
|
290
|
-
seenCursors.add(cursor);
|
|
291
|
-
}
|
|
292
|
-
} while (cursor);
|
|
293
|
-
if (matches.length !== 1) {
|
|
294
|
-
return { ok: false, error: matches.length ? "ambiguous_facility" : "facility_not_found" };
|
|
295
|
-
}
|
|
296
|
-
facilityId = matches[0].facilityId ?? matches[0].id;
|
|
297
|
-
organizationId ??= matches[0].organizationId ?? matches[0].organization_id;
|
|
298
|
-
if (!facilityId || !organizationId)
|
|
299
|
-
return { ok: false, error: "invalid_facility_owner" };
|
|
300
|
-
}
|
|
301
|
-
// Every window alias resolves to the same pair, and both are normalized:
|
|
302
|
-
// commerce decodes startsAt/endsAt as time.Time, so a bare "2026-09-01"
|
|
303
|
-
// fails the whole request body rather than the one field.
|
|
304
|
-
const startsAt = rfc3339(parsed.available_start_at ??
|
|
305
|
-
parsed.available_from ??
|
|
306
|
-
parsed.starts_after ??
|
|
307
|
-
new Date(Math.floor(Date.now() / 1000) * 1000).toISOString());
|
|
308
|
-
const endsAt = rfc3339(parsed.available_end_at ??
|
|
309
|
-
parsed.available_to ??
|
|
310
|
-
parsed.ends_before ??
|
|
311
|
-
parsed.ends_after ??
|
|
312
|
-
new Date(Math.floor(Date.now() / 1000) * 1000 + 30 * 24 * 60 * 60 * 1000).toISOString());
|
|
313
|
-
const nodeCount = parsed.node_count ?? parsed.min_nodes ?? 1;
|
|
314
|
-
const gpusPerNode = parsed.gpus_per_node ?? 8;
|
|
315
|
-
const boundPayload = bound.payload;
|
|
316
|
-
const draft = Object.fromEntries(Object.entries(boundPayload).filter(([key]) => !LISTING_CREATE_INPUT_ONLY_KEYS.includes(key)));
|
|
317
|
-
if (located)
|
|
318
|
-
draft.site_nickname = located.name;
|
|
319
|
-
else if (place && !facilityId && !lookupUnavailable)
|
|
320
|
-
delete draft.site_nickname;
|
|
321
|
-
const gpuSlug = normalized.gpu_type
|
|
322
|
-
? resolveCatalogTerm(hardware.terms, "gpu", normalized.gpu_type).canonical
|
|
323
|
-
: null;
|
|
324
|
-
const buyerPrice = parsed.buy_now_price_per_gpu_hour;
|
|
325
|
-
const buyerMicro = buyerPrice != null ? microUsd(buyerPrice) : null;
|
|
326
|
-
const downPaymentCents = parsed.down_payment_cents ??
|
|
327
|
-
(parsed.deposit_percent != null && buyerMicro != null
|
|
328
|
-
? primaryDownPaymentCents({
|
|
329
|
-
pricePerGpuHourMicroUsd: buyerMicro,
|
|
330
|
-
nodeCount,
|
|
331
|
-
gpusPerNode,
|
|
332
|
-
startsAt,
|
|
333
|
-
endsAt,
|
|
334
|
-
percent: parsed.deposit_percent,
|
|
335
|
-
})
|
|
336
|
-
: 0);
|
|
337
|
-
const priced = Boolean(organizationId && gpuSlug && buyerMicro != null);
|
|
338
|
-
const listingAt = (facility) => priced && facility
|
|
339
|
-
? primaryListingBody({
|
|
340
|
-
organizationId: organizationId,
|
|
341
|
-
facilityId: facility,
|
|
342
|
-
gpuSlug: gpuSlug,
|
|
343
|
-
gpusPerNode,
|
|
344
|
-
provisioningType: parsed.access_mode ?? "virtual-machine",
|
|
345
|
-
availableAt: startsAt,
|
|
346
|
-
availableUntil: endsAt,
|
|
347
|
-
...(typeof draft.cpu === "string" ? { cpu: draft.cpu } : {}),
|
|
348
|
-
...(typeof draft.ram === "string" ? { ram: draft.ram } : {}),
|
|
349
|
-
...(typeof draft.storage === "string" ? { storage: draft.storage } : {}),
|
|
350
|
-
...(typeof draft.fabric_type === "string" ? { fabricType: draft.fabric_type } : {}),
|
|
351
|
-
...(typeof draft.internet === "string" ? { internet: draft.internet } : {}),
|
|
352
|
-
capacityNodeCount: nodeCount,
|
|
353
|
-
state: parsed.state ?? "available",
|
|
354
|
-
}, buyerMicro, downPaymentCents)
|
|
355
|
-
: null;
|
|
356
|
-
if (!ctx.confirmed) {
|
|
357
|
-
const listing = listingAt(facilityId);
|
|
358
|
-
return preview("create_listing", {
|
|
359
|
-
...draft,
|
|
360
|
-
node_count: nodeCount,
|
|
361
|
-
gpus_per_node: gpusPerNode,
|
|
362
|
-
...(located ? { facility_region: located.region } : {}),
|
|
363
|
-
...(lookupUnavailable
|
|
364
|
-
? { location_unavailable: place, location_hint: LOCATION_LOOKUP_UNAVAILABLE }
|
|
365
|
-
: place && !facilityId && !located
|
|
366
|
-
? { location_unresolved: place, location_hint: unplacedLocation(place) }
|
|
367
|
-
: {}),
|
|
368
|
-
...(listing ? { listing } : {}),
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
if (located && !facilityId && priced) {
|
|
372
|
-
facilityId = (await ensureFacility(ctx.fetch, organizationId, located.region)).facilityId;
|
|
373
|
-
}
|
|
374
|
-
const listing = listingAt(facilityId);
|
|
375
|
-
if (!listing) {
|
|
376
|
-
return {
|
|
377
|
-
ok: false,
|
|
378
|
-
error: !organizationId || !facilityId
|
|
379
|
-
? "facility_required"
|
|
380
|
-
: gpuSlug
|
|
381
|
-
? "price_required"
|
|
382
|
-
: "gpu_required",
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
if (!parsed.idempotencyKey)
|
|
386
|
-
return { ok: false, error: "idempotency_key_required" };
|
|
387
|
-
return ctx.fetch("/commerce/v2/listings", {
|
|
388
|
-
method: "POST",
|
|
389
|
-
body: listing,
|
|
390
|
-
headers: { "Idempotency-Key": parsed.idempotencyKey },
|
|
391
|
-
});
|
|
392
|
-
},
|
|
393
|
-
});
|
|
394
|
-
export const listingCapabilities = [
|
|
395
|
-
listCatalogTermsCapability,
|
|
396
|
-
listListingsCapability,
|
|
397
|
-
createListingCapability,
|
|
398
|
-
];
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { statusOf } from "./staff-shared.js";
|
|
2
|
-
export const LOCALITY_GEOLOCATE_PATH = "/v1/orchestrator/locality/geolocate";
|
|
3
|
-
export const FACILITY_ENSURE_PATH = "/provisioning/facilities/ensure";
|
|
4
|
-
/**
|
|
5
|
-
* `GET /v1/orchestrator/locality/geolocate?address=`: the catalog region a typed
|
|
6
|
-
* place stands in. Orchestrator answers a region code or name as itself and
|
|
7
|
-
* geocodes anything else (state, city, address) to its nearest region.
|
|
8
|
-
* Null means Orchestrator could not place the text (404); an unavailable
|
|
9
|
-
* geocoder or gateway throws, so callers ask for a retry rather than new text.
|
|
10
|
-
*/
|
|
11
|
-
export async function locateRegion(fetch, place) {
|
|
12
|
-
const address = place.trim();
|
|
13
|
-
if (!address)
|
|
14
|
-
return null;
|
|
15
|
-
let located;
|
|
16
|
-
try {
|
|
17
|
-
located = (await fetch(`${LOCALITY_GEOLOCATE_PATH}?address=${encodeURIComponent(address)}`, {
|
|
18
|
-
method: "GET",
|
|
19
|
-
}));
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
if (statusOf(error) === 404)
|
|
23
|
-
return null;
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
const region = typeof located?.facility_slug === "string" ? located.facility_slug.trim() : "";
|
|
27
|
-
if (!region)
|
|
28
|
-
return null;
|
|
29
|
-
const name = typeof located?.region === "string" && located.region.trim() ? located.region.trim() : region;
|
|
30
|
-
return { region, name };
|
|
31
|
-
}
|
|
32
|
-
export function unplacedLocation(place) {
|
|
33
|
-
return `Ornn could not place "${place}". Give a state, city, address, or catalog region such as us-west-3.`;
|
|
34
|
-
}
|
|
35
|
-
export const LOCATION_LOOKUP_UNAVAILABLE = "Ornn's location lookup is unavailable right now. Keep this location and try again in a moment.";
|
|
36
|
-
/** `POST /internal/v1/facilities/ensure`: the organization's facility at a catalog region. */
|
|
37
|
-
export async function ensureFacility(fetch, organizationId, region) {
|
|
38
|
-
return (await fetch(FACILITY_ENSURE_PATH, {
|
|
39
|
-
method: "POST",
|
|
40
|
-
body: { organizationId, region },
|
|
41
|
-
}));
|
|
42
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { preview, } from "../capability.js";
|
|
3
|
-
/** CLI semantic arguments arrive as strings; MCP and Slack send JSON values. */
|
|
4
|
-
export const positionals = z.array(z.string()).max(0).default([]);
|
|
5
|
-
/** Integer micro-USD per GPU-hour (1000000 = $1.00). */
|
|
6
|
-
export const microUsd = z.coerce.number().int().positive();
|
|
7
|
-
export const flag = z.preprocess((value) => (value === "true" ? true : value === "false" ? false : value), z.boolean());
|
|
8
|
-
export function seg(value) {
|
|
9
|
-
return encodeURIComponent(value);
|
|
10
|
-
}
|
|
11
|
-
export function query(params) {
|
|
12
|
-
const parts = Object.entries(params)
|
|
13
|
-
.filter(([, value]) => value !== undefined && value !== null && String(value).trim() !== "")
|
|
14
|
-
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
15
|
-
return parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
16
|
-
}
|
|
17
|
-
/** Without a CLI runtime or a supplied key, the transport mints one per write. */
|
|
18
|
-
export function idempotencyHeaders(ctx, supplied) {
|
|
19
|
-
const key = ctx.cli?.interaction?.idempotencyKey(supplied) ?? supplied?.trim();
|
|
20
|
-
return key ? { "Idempotency-Key": key } : undefined;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* One consent gate for every surface: the CLI reviews the terms in the
|
|
24
|
-
* terminal (or takes `--yes`), MCP and Slack return a preview until the
|
|
25
|
-
* caller confirms. `null` means go ahead.
|
|
26
|
-
*/
|
|
27
|
-
export async function consent(ctx, action, title, terms, payload) {
|
|
28
|
-
const interaction = ctx.cli?.interaction;
|
|
29
|
-
if (interaction) {
|
|
30
|
-
if (await interaction.review({ title, terms }))
|
|
31
|
-
return null;
|
|
32
|
-
return interaction.result({
|
|
33
|
-
data: null,
|
|
34
|
-
errors: [{ code: "action_declined", message: "No changes were made.", retryable: false }],
|
|
35
|
-
exitCode: 1,
|
|
36
|
-
outcome: { class: "ended", phase: "declined" },
|
|
37
|
-
presentation: { lines: ["No changes were made."] },
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
return ctx.confirmed ? null : preview(action, payload);
|
|
41
|
-
}
|
|
42
|
-
export function microUsdLabel(value) {
|
|
43
|
-
return typeof value === "number" && Number.isFinite(value)
|
|
44
|
-
? `$${(value / 1_000_000).toFixed(2)} per GPU-hour`
|
|
45
|
-
: "market";
|
|
46
|
-
}
|