@ornncompute/cli 0.2.23 → 1.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +304 -420
  2. package/package.json +12 -6
  3. package/src/admin-capacity-reservations.mjs +280 -0
  4. package/src/admin-command-handlers.mjs +57 -0
  5. package/src/admin-enrollment.mjs +680 -0
  6. package/src/admin-mutations.mjs +187 -0
  7. package/src/admin-node-diagnostics.mjs +412 -0
  8. package/src/admin-node-lifecycle.mjs +908 -0
  9. package/src/admin-shell.mjs +322 -0
  10. package/src/api-client.mjs +81 -28
  11. package/src/atomic-output.mjs +112 -0
  12. package/src/auth-store.mjs +182 -11
  13. package/src/capability-catalog.mjs +7 -0
  14. package/src/catalog-shell.mjs +1350 -0
  15. package/src/cli.mjs +123 -10266
  16. package/src/connect-mode-handlers.mjs +170 -0
  17. package/src/device-auth.mjs +178 -61
  18. package/src/evidence-commands.mjs +1340 -0
  19. package/src/external-bucket-commands.mjs +1374 -0
  20. package/src/interaction-runtime.mjs +310 -0
  21. package/src/kubernetes-commands.mjs +2229 -0
  22. package/src/launch-mode-handlers.mjs +119 -0
  23. package/src/machine-reference.mjs +277 -0
  24. package/src/mount-commands.mjs +1146 -0
  25. package/src/session-commands.mjs +565 -0
  26. package/src/slurm-commands.mjs +263 -0
  27. package/src/startup.mjs +34 -11
  28. package/src/storage-commands.mjs +1144 -0
  29. package/src/update.mjs +172 -24
  30. package/src/vm-commands.mjs +4273 -0
  31. package/src/vpn-secret-output.mjs +150 -0
  32. package/src/wait-runtime.mjs +231 -0
  33. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +126 -0
  34. package/vendor/capabilities/capabilities/admin-enrollment.js +321 -0
  35. package/vendor/capabilities/capabilities/admin-node-diagnostics.js +88 -0
  36. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +235 -0
  37. package/vendor/capabilities/capabilities/admin.js +89 -0
  38. package/vendor/capabilities/capabilities/capacity-offers.js +368 -0
  39. package/vendor/capabilities/capabilities/cli.js +156 -0
  40. package/vendor/capabilities/capabilities/clusters.js +150 -1
  41. package/vendor/capabilities/capabilities/compute.js +522 -0
  42. package/vendor/capabilities/capabilities/evidence.js +145 -0
  43. package/vendor/capabilities/capabilities/external-buckets.js +191 -0
  44. package/vendor/capabilities/capabilities/fleet.js +21 -5
  45. package/vendor/capabilities/capabilities/identity.js +114 -3
  46. package/vendor/capabilities/capabilities/listings.js +14 -8
  47. package/vendor/capabilities/capabilities/mounts.js +176 -0
  48. package/vendor/capabilities/capabilities/networks.js +1257 -0
  49. package/vendor/capabilities/capabilities/observability.js +38 -8
  50. package/vendor/capabilities/capabilities/tokens.js +20 -8
  51. package/vendor/capabilities/capabilities/volumes.js +178 -0
  52. package/vendor/capabilities/capability.js +19 -13
  53. package/vendor/capabilities/cli-catalog.js +65 -0
  54. package/vendor/capabilities/cli-migrations.js +288 -0
  55. package/vendor/capabilities/cli.js +7 -0
  56. package/vendor/capabilities/software-catalog.js +36 -0
  57. package/vendor/capabilities/spec-catalog.js +2 -2
  58. package/src/catalog-dispatch.mjs +0 -605
  59. package/src/color.mjs +0 -71
  60. package/vendor/capabilities/capabilities/clusters.d.ts +0 -2
  61. package/vendor/capabilities/capabilities/fleet.d.ts +0 -8
  62. package/vendor/capabilities/capabilities/identity.d.ts +0 -3
  63. package/vendor/capabilities/capabilities/listings.d.ts +0 -51
  64. package/vendor/capabilities/capabilities/observability.d.ts +0 -5
  65. package/vendor/capabilities/capabilities/staff-access.d.ts +0 -29
  66. package/vendor/capabilities/capabilities/staff-access.js +0 -278
  67. package/vendor/capabilities/capabilities/staff-commerce.d.ts +0 -160
  68. package/vendor/capabilities/capabilities/staff-commerce.js +0 -689
  69. package/vendor/capabilities/capabilities/staff-fleet.d.ts +0 -140
  70. package/vendor/capabilities/capabilities/staff-fleet.js +0 -538
  71. package/vendor/capabilities/capabilities/staff-gtm.d.ts +0 -142
  72. package/vendor/capabilities/capabilities/staff-gtm.js +0 -521
  73. package/vendor/capabilities/capabilities/staff-shared.d.ts +0 -47
  74. package/vendor/capabilities/capabilities/staff-shared.js +0 -98
  75. package/vendor/capabilities/capabilities/tokens.d.ts +0 -10
  76. package/vendor/capabilities/capabilities/users.d.ts +0 -2
  77. package/vendor/capabilities/capability.d.ts +0 -93
  78. package/vendor/capabilities/catalog.d.ts +0 -3
  79. package/vendor/capabilities/catalog.js +0 -26
  80. package/vendor/capabilities/index.d.ts +0 -16
  81. package/vendor/capabilities/index.js +0 -16
  82. package/vendor/capabilities/names.d.ts +0 -6
  83. package/vendor/capabilities/role.d.ts +0 -6
  84. package/vendor/capabilities/role.js +0 -17
  85. package/vendor/capabilities/spec-catalog.d.ts +0 -78
@@ -1,689 +0,0 @@
1
- import { z } from "zod";
2
- import { defineStaffMcpCapability, preview } from "../capability.js";
3
- import { errorTextOf, mapBody, requireUuid, resolveVersion, seg, statusOf } from "./staff-shared.js";
4
- // Staff commerce verbs: reservations, bids, forward inventory, and the tenant
5
- // credit profile. MCP-only — see `defineStaffMcpCapability`.
6
- export const reservationCancelInput = z.object({
7
- reservationId: z.string(),
8
- confirm: z.boolean().optional(),
9
- });
10
- export const reservationCancelCapability = defineStaffMcpCapability({
11
- id: "reservation.cancel",
12
- domain: "reservations",
13
- roles: ["admin"],
14
- description: "Internal staff only: cancel a reservation. Mirrors the internal dashboard's Cancel/Reject action.",
15
- mutation: "preview-confirm",
16
- http: { method: "POST", path: "/internal/reservations/{reservationId}/cancel" },
17
- input: reservationCancelInput,
18
- execute: async (ctx, input) => {
19
- const parsed = reservationCancelInput.parse(input);
20
- if (!ctx.confirmed) {
21
- return preview("reservation.cancel", { reservationId: parsed.reservationId });
22
- }
23
- return ctx.fetch(`/internal/reservations/${seg(parsed.reservationId)}/cancel`, {
24
- method: "POST",
25
- });
26
- },
27
- });
28
- export const reservationTransferInput = z.object({
29
- reservationId: z.string(),
30
- targetTenantId: z.string(),
31
- reservedStrategy: z.enum(["reject", "park"]),
32
- confirmReservedTransfer: z.boolean().optional(),
33
- notes: z.string().max(2000).optional(),
34
- confirm: z.boolean().optional(),
35
- });
36
- export const reservationTransferCapability = defineStaffMcpCapability({
37
- id: "reservation.transfer",
38
- domain: "reservations",
39
- roles: ["admin"],
40
- description: "Internal staff only: transfer a reservation to a different tenant. Mirrors the internal node-overlay transfer action. " +
41
- "reservedStrategy has no default and must be chosen explicitly: \"reject\" (transfer only if the reservation isn't " +
42
- "currently provisioned/assigned) or \"park\" (clean up old access and park it for the target tenant, for a live handoff). " +
43
- "The dashboard always computes this from the reservation's live state before sending it rather than relying on the " +
44
- "backend's own default of \"reject\", because a silent \"reject\" default on a reservation the backend doesn't detect as " +
45
- "actively provisioned/assigned would transfer it without ever asking for confirmReservedTransfer.",
46
- mutation: "preview-confirm",
47
- http: { method: "POST", path: "/internal/reservations/{reservationId}/transfer" },
48
- input: reservationTransferInput,
49
- execute: async (ctx, input) => {
50
- const parsed = reservationTransferInput.parse(input);
51
- // camelCase: commerce's transferReservationRequest has no snake aliases,
52
- // so the previous all-snake body bound nothing but `notes` and every
53
- // transfer failed with "targetOrganizationId is required".
54
- const body = {
55
- targetOrganizationId: requireUuid(parsed.targetTenantId, "targetTenantId"),
56
- reservedStrategy: parsed.reservedStrategy,
57
- confirmReservedTransfer: parsed.confirmReservedTransfer ?? false,
58
- ...mapBody(parsed, { notes: "notes" }),
59
- };
60
- if (!ctx.confirmed)
61
- return preview("reservation.transfer", body);
62
- try {
63
- return await ctx.fetch(`/internal/reservations/${seg(parsed.reservationId)}/transfer`, {
64
- method: "POST",
65
- body,
66
- });
67
- }
68
- catch (error) {
69
- throw inTenantVocabulary(error);
70
- }
71
- },
72
- });
73
- /**
74
- * Commerce names the transfer target `targetOrganizationId`; this tool's
75
- * parameter is `targetTenantId`. Passing the wire name through tells the
76
- * caller to go fix a parameter that does not exist, so translate it back.
77
- */
78
- function inTenantVocabulary(error) {
79
- const text = errorTextOf(error);
80
- if (!text.includes("targetOrganizationId"))
81
- return error;
82
- return new Error(text.replaceAll("targetOrganizationId", "targetTenantId"));
83
- }
84
- export const reservationMachinesListInput = z.object({ reservationId: z.string() });
85
- export const reservationMachinesListCapability = defineStaffMcpCapability({
86
- id: "reservation.machines.list",
87
- domain: "reservations",
88
- roles: ["reviewer", "admin"],
89
- description: "Internal staff only: list machines for a reservation.",
90
- mutation: "read",
91
- http: { method: "GET", path: "/internal/nodes" },
92
- input: reservationMachinesListInput,
93
- execute: async (ctx, input) => {
94
- const parsed = reservationMachinesListInput.parse(input);
95
- return ctx.fetch(`/internal/nodes?reservation_id=${seg(parsed.reservationId)}`, {
96
- method: "GET",
97
- });
98
- },
99
- });
100
- export const reservationSshKeyAddInput = z.object({
101
- reservationId: z.string(),
102
- tenantId: z.string(),
103
- userId: z.string(),
104
- sshKeyId: z.string().optional(),
105
- keyId: z.string().optional(),
106
- publicKey: z.string().min(1).optional(),
107
- label: z.string().optional(),
108
- confirm: z.boolean().optional(),
109
- });
110
- export const reservationSshKeyAddCapability = defineStaffMcpCapability({
111
- id: "reservation.ssh-key.add",
112
- domain: "reservations",
113
- roles: ["admin"],
114
- description: "Internal staff only: attach an SSH key to a reservation for a tenant user. " +
115
- "Pass sshKeyId to attach a key the organization already has, or publicKey (with label) to register a new " +
116
- "organization key and attach it. Attaching does not itself push the key to live machines; use ssh_key_push for that. " +
117
- "Requires confirm: true.",
118
- mutation: "preview-confirm",
119
- http: { method: "POST", path: "/internal/v1/reservations/{reservationId}/ssh-keys" },
120
- input: reservationSshKeyAddInput,
121
- execute: async (ctx, input) => {
122
- const parsed = reservationSshKeyAddInput.parse(input);
123
- const keyId = parsed.sshKeyId ?? parsed.keyId;
124
- if (!keyId && !parsed.publicKey) {
125
- throw new Error("Pass sshKeyId to attach an existing organization key, or publicKey to register a new one.");
126
- }
127
- if (!ctx.confirmed) {
128
- return preview("reservation.ssh-key.add", {
129
- reservationId: parsed.reservationId,
130
- userId: parsed.userId,
131
- ...(keyId ? { sshKeyId: keyId } : { publicKey: parsed.publicKey, label: parsed.label }),
132
- });
133
- }
134
- // Commerce attaches an existing authorization key by id; it reads only
135
- // sshKeyId/userId. The old body also sent public_key, label and node_id,
136
- // which bound nothing, so a publicKey-only call always failed.
137
- let sshKeyId = keyId;
138
- if (!sshKeyId) {
139
- const registered = (await ctx.fetch(`/v1/organizations/${seg(parsed.tenantId)}/ssh-keys`, {
140
- method: "POST",
141
- body: { public_key: parsed.publicKey, ...mapBody(parsed, { label: "label" }) },
142
- }));
143
- if (!registered.id) {
144
- throw new Error("Authorization did not return an SSH key id.");
145
- }
146
- sshKeyId = registered.id;
147
- }
148
- return ctx.fetch(`/internal/v1/reservations/${seg(parsed.reservationId)}/ssh-keys`, {
149
- method: "POST",
150
- body: { sshKeyId, userId: parsed.userId },
151
- });
152
- },
153
- });
154
- export const reservationSshKeyRemoveInput = z.object({
155
- reservationId: z.string(),
156
- keyId: z.string(),
157
- userId: z.string(),
158
- confirm: z.boolean().optional(),
159
- });
160
- export const reservationSshKeyRemoveCapability = defineStaffMcpCapability({
161
- id: "reservation.ssh-key.remove",
162
- domain: "reservations",
163
- roles: ["admin"],
164
- description: "Internal staff only: detach an SSH key from a reservation for a tenant user. " +
165
- "Detaching does not itself remove the key from live machines; use ssh_key_push to resync them. Requires confirm: true.",
166
- mutation: "preview-confirm",
167
- http: { method: "DELETE", path: "/internal/v1/reservations/{reservationId}/ssh-keys/{keyId}" },
168
- input: reservationSshKeyRemoveInput,
169
- execute: async (ctx, input) => {
170
- const parsed = reservationSshKeyRemoveInput.parse(input);
171
- if (!ctx.confirmed) {
172
- return preview("reservation.ssh-key.remove", {
173
- reservationId: parsed.reservationId,
174
- keyId: parsed.keyId,
175
- userId: parsed.userId,
176
- });
177
- }
178
- // The handler reads userId from the query string, not the body. Sent as a
179
- // body it was dropped and the detach ran unscoped across every user.
180
- const result = await ctx.fetch(`/internal/v1/reservations/${seg(parsed.reservationId)}/ssh-keys/${seg(parsed.keyId)}?userId=${seg(parsed.userId)}`, { method: "DELETE" });
181
- return (result ?? {
182
- removed: true,
183
- reservationId: parsed.reservationId,
184
- keyId: parsed.keyId,
185
- userId: parsed.userId,
186
- });
187
- },
188
- });
189
- export const reservationPublishInput = z.object({
190
- reservationId: z.string().optional(),
191
- reservation_id: z.string().optional(),
192
- version: z.number().int().positive().optional(),
193
- awaitDownPayment: z.boolean().optional(),
194
- await_down_payment: z.boolean().optional(),
195
- confirm: z.boolean().optional(),
196
- });
197
- export const reservationPublishCapability = defineStaffMcpCapability({
198
- id: "reservation.publish",
199
- domain: "reservations",
200
- roles: ["admin"],
201
- description: "Internal staff only: publish a commerce draft or uncommitted bid reservation into a live Commerce reservation. Requires confirm: true.",
202
- mutation: "preview-confirm",
203
- http: { method: "POST", path: "/internal/reservations/{reservationId}/publish" },
204
- input: reservationPublishInput,
205
- execute: async (ctx, input) => {
206
- const parsed = reservationPublishInput.parse(input);
207
- const reservationId = parsed.reservationId || parsed.reservation_id;
208
- if (!reservationId) {
209
- throw new Error("reservationId is required");
210
- }
211
- const version = await resolveVersion(ctx, `/internal/reservations/${seg(reservationId)}`, parsed.version);
212
- const awaitDownPayment = parsed.awaitDownPayment ?? parsed.await_down_payment;
213
- const body = {
214
- ...(version !== undefined ? { version } : {}),
215
- ...(awaitDownPayment !== undefined ? { awaitDownPayment, await_down_payment: awaitDownPayment } : {}),
216
- };
217
- if (!ctx.confirmed) {
218
- return preview("reservation.publish", { reservationId, ...body });
219
- }
220
- return ctx.fetch(`/internal/reservations/${seg(reservationId)}/publish`, {
221
- method: "POST",
222
- ...(Object.keys(body).length > 0 ? { body } : {}),
223
- });
224
- },
225
- });
226
- export const reservationShowInput = z.object({ reservationId: z.string() });
227
- // Accepting a bid inserts its fill reservation as 'uncommitted' (commerce's
228
- // InsertBidFillReservation): it holds no capacity and carries no invoice until
229
- // it is published, and buyer-facing reads deliberately filter it out. Staff had
230
- // no read of their own, so the reservationId that bid.accept returns could not
231
- // be looked up anywhere at all. These two verbs are the admin-side read, which
232
- // does surface uncommitted rows.
233
- export const reservationShowCapability = defineStaffMcpCapability({
234
- id: "reservation.show",
235
- domain: "reservations",
236
- roles: ["reviewer", "admin"],
237
- description: "Internal staff only: show one commerce reservation, including draft and uncommitted rows that " +
238
- "customer-facing reads hide (an accepted bid's fill reservation is uncommitted until it is published).",
239
- mutation: "read",
240
- http: { method: "GET", path: "/internal/reservations/{reservationId}" },
241
- input: reservationShowInput,
242
- execute: async (ctx, input) => {
243
- const parsed = reservationShowInput.parse(input);
244
- return ctx.fetch(`/internal/reservations/${seg(parsed.reservationId)}`, { method: "GET" });
245
- },
246
- });
247
- export const reservationListInput = z.object({
248
- organizationId: z.string().optional(),
249
- status: z.string().optional(),
250
- cursor: z.string().optional(),
251
- limit: z.number().int().min(1).max(500).optional(),
252
- });
253
- export const reservationListCapability = defineStaffMcpCapability({
254
- id: "reservation.list",
255
- domain: "reservations",
256
- roles: ["reviewer", "admin"],
257
- description: "Internal staff only: list commerce reservations, optionally filtered by tenant and status. " +
258
- "Includes draft and uncommitted rows that customer-facing reads hide.",
259
- mutation: "read",
260
- http: { method: "GET", path: "/internal/reservations" },
261
- input: reservationListInput,
262
- execute: async (ctx, input) => {
263
- const parsed = reservationListInput.parse(input ?? {});
264
- const parts = [];
265
- if (parsed.organizationId) {
266
- parts.push(`organizationId=${encodeURIComponent(parsed.organizationId)}`);
267
- }
268
- if (parsed.status)
269
- parts.push(`status=${encodeURIComponent(parsed.status)}`);
270
- if (parsed.cursor)
271
- parts.push(`cursor=${encodeURIComponent(parsed.cursor)}`);
272
- if (parsed.limit)
273
- parts.push(`limit=${encodeURIComponent(String(parsed.limit))}`);
274
- const qs = parts.length > 0 ? `?${parts.join("&")}` : "";
275
- return ctx.fetch(`/internal/reservations${qs}`, { method: "GET" });
276
- },
277
- });
278
- // Kept a plain object because tool registration reads `schema.shape`; the
279
- // required-ness is enforced in execute below.
280
- export const bidAcceptInput = z.object({
281
- bidId: z.string().optional(),
282
- bid_id: z.string().optional(),
283
- acceptedGpuCount: z.number().int().min(1).optional(),
284
- accepted_gpu_count: z.number().int().min(1).optional(),
285
- fillNodeCount: z.number().int().min(1).optional(),
286
- fill_node_count: z.number().int().min(1).optional(),
287
- confirm: z.boolean().optional(),
288
- });
289
- export const bidAcceptCapability = defineStaffMcpCapability({
290
- id: "bid.accept",
291
- domain: "bids",
292
- roles: ["admin"],
293
- description: "Internal staff only: accept an active bid. acceptedGpuCount (alias fillNodeCount) is required - commerce " +
294
- "has no default; pass the bid's full nodeCount to fill it completely, or a smaller number for a partial " +
295
- "fill. Mirrors the internal dashboard's Accept action. Automatically publishes the resulting reservation " +
296
- "so it holds capacity and is immediately visible to customer reads.",
297
- mutation: "preview-confirm",
298
- http: { method: "POST", path: "/internal/bids/{bidId}/accept" },
299
- input: bidAcceptInput,
300
- execute: async (ctx, input) => {
301
- const parsed = bidAcceptInput.parse(input);
302
- const bidId = parsed.bidId || parsed.bid_id;
303
- if (!bidId) {
304
- throw new Error("bidId is required");
305
- }
306
- const count = parsed.acceptedGpuCount ?? parsed.accepted_gpu_count ?? parsed.fillNodeCount ?? parsed.fill_node_count;
307
- // commerce's acceptBidRequest.Validate rejects a missing count outright
308
- // (services/commerce/internal/api/admin/bid_types.go), so refuse here
309
- // rather than sending a call that always 400s.
310
- if (count === undefined) {
311
- throw new Error("acceptedGpuCount (or fillNodeCount) is required; commerce has no default fill count. Pass the bid's nodeCount to fill it completely.");
312
- }
313
- const body = { accepted_gpu_count: count };
314
- if (!ctx.confirmed)
315
- return preview("bid.accept", { bidId, ...body });
316
- const accepted = (await ctx.fetch(`/internal/bids/${seg(bidId)}/accept`, { method: "POST", body }));
317
- const reservationId = (accepted?.reservationId ?? accepted?.reservation_id);
318
- if (reservationId) {
319
- try {
320
- await ctx.fetch(`/internal/reservations/${seg(reservationId)}/publish`, {
321
- method: "POST",
322
- body: {},
323
- });
324
- }
325
- catch (publishErr) {
326
- return {
327
- ...accepted,
328
- publishWarning: `Bid accepted successfully, but automatic reservation publish failed: ${publishErr instanceof Error ? publishErr.message : String(publishErr)}. Use admin_reservation_publish to retry.`,
329
- };
330
- }
331
- }
332
- return accepted;
333
- },
334
- });
335
- export const bidRejectInput = z.object({
336
- bidId: z.string(),
337
- confirm: z.boolean().optional(),
338
- });
339
- export const bidRejectCapability = defineStaffMcpCapability({
340
- id: "bid.reject",
341
- domain: "bids",
342
- roles: ["admin"],
343
- description: "Internal staff only: reject an active bid. Mirrors the internal dashboard's Reject action.",
344
- mutation: "preview-confirm",
345
- http: { method: "POST", path: "/internal/bids/{bidId}/reject" },
346
- input: bidRejectInput,
347
- execute: async (ctx, input) => {
348
- const parsed = bidRejectInput.parse(input);
349
- if (!ctx.confirmed)
350
- return preview("bid.reject", { bidId: parsed.bidId });
351
- return ctx.fetch(`/internal/bids/${seg(parsed.bidId)}/reject`, { method: "POST" });
352
- },
353
- });
354
- export const bidShowInput = z.object({
355
- bidId: z.string().optional(),
356
- bid_id: z.string().optional(),
357
- });
358
- export const bidShowCapability = defineStaffMcpCapability({
359
- id: "bid.show",
360
- domain: "bids",
361
- roles: ["reviewer", "admin"],
362
- description: "Internal staff only: fetch a single buyer bid by ID.",
363
- mutation: "read",
364
- http: { method: "GET", path: "/internal/bids/{bidId}" },
365
- input: bidShowInput,
366
- execute: async (ctx, input) => {
367
- const parsed = bidShowInput.parse(input);
368
- const bidId = parsed.bidId || parsed.bid_id;
369
- if (!bidId) {
370
- throw new Error("bidId is required");
371
- }
372
- return ctx.fetch(`/internal/bids/${seg(bidId)}`, { method: "GET" });
373
- },
374
- });
375
- /** Commerce parses listing windows as RFC3339 timestamps, not bare dates. */
376
- function rfc3339(value) {
377
- const trimmed = value.trim();
378
- if (!trimmed.includes("T"))
379
- return `${trimmed}T00:00:00Z`;
380
- return trimmed.endsWith("Z") || /[+-]\d{2}:\d{2}$/.test(trimmed) ? trimmed : `${trimmed}Z`;
381
- }
382
- export const inventoryUpdateInput = z.object({
383
- inventoryId: z.string(),
384
- version: z
385
- .number()
386
- .int()
387
- .positive()
388
- .optional()
389
- .describe("Optimistic-concurrency version. Read from the listing when omitted."),
390
- operatorId: z.string().min(1).optional(),
391
- facilityId: z.string().min(1).optional(),
392
- gpuType: z.string().min(1).optional(),
393
- nodeCount: z
394
- .number()
395
- .int()
396
- .positive()
397
- .optional()
398
- .describe("Total nodes (listing capacity), not total GPUs."),
399
- gpusPerNode: z.number().int().min(1).max(32).optional(),
400
- availableFrom: z.string().optional(),
401
- availableTo: z.string().optional(),
402
- availableStartAt: z.string().optional(),
403
- availableEndAt: z.string().optional(),
404
- cpu: z.string().nullable().optional(),
405
- ram: z.string().nullable().optional(),
406
- fabricType: z.string().nullable().optional(),
407
- storage: z.string().nullable().optional(),
408
- internet: z.string().nullable().optional(),
409
- buyNowPricePerGpuHour: z.number().positive().nullable().optional(),
410
- confirm: z.boolean().optional(),
411
- });
412
- export const inventoryUpdateCapability = defineStaffMcpCapability({
413
- id: "inventory.update",
414
- domain: "listings",
415
- roles: ["admin"],
416
- description: "Internal staff only: patch editable fields on a forward inventory listing. Requires confirm: true. " +
417
- "At least one editable field is required. buyNowPricePerGpuHour may be null to clear the price. " +
418
- "operatorId/facilityId take UUIDs. Site nickname and network hardware are not patchable on a listing.",
419
- mutation: "preview-confirm",
420
- http: { method: "PATCH", path: "/inventory/{inventoryId}" },
421
- input: inventoryUpdateInput,
422
- execute: async (ctx, input) => {
423
- const parsed = inventoryUpdateInput.parse(input);
424
- const body = mapBody(parsed, {
425
- operatorId: "operatorId",
426
- facilityId: "facilityId",
427
- gpuType: "gpuType",
428
- nodeCount: "capacityNodes",
429
- gpusPerNode: "gpusPerNode",
430
- cpu: "cpu",
431
- ram: "ram",
432
- fabricType: "fabricType",
433
- storage: "storage",
434
- internet: "internet",
435
- });
436
- const startAt = parsed.availableStartAt ?? parsed.availableFrom;
437
- if (startAt !== undefined)
438
- body.startAt = rfc3339(startAt);
439
- const endAt = parsed.availableEndAt ?? parsed.availableTo;
440
- if (endAt !== undefined)
441
- body.endAt = rfc3339(endAt);
442
- // The rate is a decimal string on the wire; null clears the buy-now price.
443
- if (parsed.buyNowPricePerGpuHour !== undefined) {
444
- body.buyNowRatePerGpuHour =
445
- parsed.buyNowPricePerGpuHour === null ? null : String(parsed.buyNowPricePerGpuHour);
446
- }
447
- // commerce's patchListingRequest.Validate refuses version <= 0, and the
448
- // schema never carried one, so every update 400'd with "missing version".
449
- // Resolve it the way inventory.delist does.
450
- body.version = await resolveVersion(ctx, `/inventory/${seg(parsed.inventoryId)}`, parsed.version);
451
- if (!ctx.confirmed)
452
- return preview("inventory.update", body);
453
- return ctx.fetch(`/inventory/${seg(parsed.inventoryId)}`, { method: "PATCH", body });
454
- },
455
- });
456
- export const tenantCreditProfileShowInput = z.object({ tenantId: z.string() });
457
- export const tenantCreditProfileShowCapability = defineStaffMcpCapability({
458
- id: "tenant.credit-profile.show",
459
- domain: "users",
460
- roles: ["reviewer", "admin"],
461
- description: "Internal staff only: show a tenant's credit profile, approval status, and onboarding details. Read-only — does not approve or disapprove.",
462
- mutation: "read",
463
- http: { method: "GET", path: "/internal/organizations/{tenantId}" },
464
- input: tenantCreditProfileShowInput,
465
- execute: async (ctx, input) => {
466
- const parsed = tenantCreditProfileShowInput.parse(input);
467
- try {
468
- return await ctx.fetch(`/internal/organizations/${seg(parsed.tenantId)}`, { method: "GET" });
469
- }
470
- catch (error) {
471
- // Commerce holds a projection, not the roster: an organization only
472
- // appears there once authorization has pushed it across, so this read
473
- // returned ORGANIZATION_NOT_FOUND for live tenants that had never
474
- // transacted. Authorization owns the record, so fall back to it and say
475
- // plainly that no commerce credit profile exists yet.
476
- if (statusOf(error) !== 404)
477
- throw error;
478
- const organization = await ctx.fetch(`/v1/organizations/${seg(parsed.tenantId)}`, {
479
- method: "GET",
480
- });
481
- return { organization, creditProfile: null, creditProfileSource: "authorization" };
482
- }
483
- },
484
- });
485
- export const reservationDraftCreateInput = z.object({
486
- organizationId: z.string(),
487
- nodeCount: z.number().int().positive(),
488
- startAt: z.string(),
489
- endAt: z.string(),
490
- billingMode: z.string().default("reserved"),
491
- pricePerGpuHr: z.string(),
492
- listingId: z.string().optional(),
493
- accessMode: z.string().optional(),
494
- notes: z.string().optional(),
495
- confirm: z.boolean().optional(),
496
- });
497
- export const reservationDraftCreateCapability = defineStaffMcpCapability({
498
- id: "reservation.draft-create",
499
- domain: "reservations",
500
- roles: ["admin"],
501
- description: "Internal staff only: create a draft reservation in Commerce. Requires confirm: true.",
502
- mutation: "preview-confirm",
503
- http: { method: "POST", path: "/internal/reservations" },
504
- input: reservationDraftCreateInput,
505
- execute: async (ctx, input) => {
506
- const parsed = reservationDraftCreateInput.parse(input);
507
- const body = {
508
- organizationId: parsed.organizationId,
509
- nodeCount: parsed.nodeCount,
510
- startAt: parsed.startAt,
511
- endAt: parsed.endAt,
512
- billingMode: parsed.billingMode,
513
- pricePerGpuHr: parsed.pricePerGpuHr,
514
- draft: true,
515
- ...mapBody(parsed, {
516
- listingId: "listingId",
517
- accessMode: "accessMode",
518
- notes: "notes",
519
- }),
520
- };
521
- if (!ctx.confirmed)
522
- return preview("reservation.draft-create", body);
523
- return ctx.fetch("/internal/reservations", { method: "POST", body });
524
- },
525
- });
526
- export const reservationUpdateInput = z.object({
527
- reservationId: z.string(),
528
- startAt: z.string().optional(),
529
- endAt: z.string().optional(),
530
- nodeCount: z.number().int().positive().optional(),
531
- notes: z.string().nullable().optional(),
532
- expectedStatus: z.string().optional(),
533
- confirm: z.boolean().optional(),
534
- });
535
- export const reservationUpdateCapability = defineStaffMcpCapability({
536
- id: "reservation.update",
537
- domain: "reservations",
538
- roles: ["admin"],
539
- description: "Internal staff only: update an existing reservation's dates, node count, notes, or draft status. Requires confirm: true.",
540
- mutation: "preview-confirm",
541
- http: { method: "PATCH", path: "/internal/reservations/{reservationId}" },
542
- input: reservationUpdateInput,
543
- execute: async (ctx, input) => {
544
- const parsed = reservationUpdateInput.parse(input);
545
- const body = mapBody(parsed, {
546
- startAt: "startAt",
547
- endAt: "endAt",
548
- nodeCount: "nodeCount",
549
- notes: "notes",
550
- expectedStatus: "expectedStatus",
551
- });
552
- if (!ctx.confirmed) {
553
- return preview("reservation.update", { reservationId: parsed.reservationId, ...body });
554
- }
555
- return ctx.fetch(`/internal/reservations/${seg(parsed.reservationId)}`, {
556
- method: "PATCH",
557
- body,
558
- });
559
- },
560
- });
561
- export const reservationActivateInput = z.object({
562
- reservationId: z.string(),
563
- version: z.number().int().optional(),
564
- confirm: z.boolean().optional(),
565
- });
566
- export const reservationActivateCapability = defineStaffMcpCapability({
567
- id: "reservation.activate",
568
- domain: "reservations",
569
- roles: ["admin"],
570
- description: "Internal staff only: activate a confirmed reservation. Requires confirm: true.",
571
- mutation: "preview-confirm",
572
- http: { method: "POST", path: "/internal/reservations/{reservationId}/activate" },
573
- input: reservationActivateInput,
574
- execute: async (ctx, input) => {
575
- const parsed = reservationActivateInput.parse(input);
576
- const body = {
577
- version: await resolveVersion(ctx, `/internal/reservations/${seg(parsed.reservationId)}`, parsed.version),
578
- };
579
- if (!ctx.confirmed)
580
- return preview("reservation.activate", { reservationId: parsed.reservationId, ...body });
581
- return ctx.fetch(`/internal/reservations/${seg(parsed.reservationId)}/activate`, {
582
- method: "POST",
583
- body,
584
- });
585
- },
586
- });
587
- export const reservationCompleteInput = z.object({
588
- reservationId: z.string(),
589
- version: z.number().int().optional(),
590
- confirm: z.boolean().optional(),
591
- });
592
- export const reservationCompleteCapability = defineStaffMcpCapability({
593
- id: "reservation.complete",
594
- domain: "reservations",
595
- roles: ["admin"],
596
- description: "Internal staff only: complete an active reservation. Requires confirm: true.",
597
- mutation: "preview-confirm",
598
- http: { method: "POST", path: "/internal/reservations/{reservationId}/complete" },
599
- input: reservationCompleteInput,
600
- execute: async (ctx, input) => {
601
- const parsed = reservationCompleteInput.parse(input);
602
- const body = {
603
- version: await resolveVersion(ctx, `/internal/reservations/${seg(parsed.reservationId)}`, parsed.version),
604
- };
605
- if (!ctx.confirmed)
606
- return preview("reservation.complete", { reservationId: parsed.reservationId, ...body });
607
- return ctx.fetch(`/internal/reservations/${seg(parsed.reservationId)}/complete`, {
608
- method: "POST",
609
- body,
610
- });
611
- },
612
- });
613
- export const inventoryDelistInput = z.object({
614
- inventoryId: z.string(),
615
- version: z.number().int().positive().optional(),
616
- confirm: z.boolean().optional(),
617
- });
618
- export const inventoryDelistCapability = defineStaffMcpCapability({
619
- id: "inventory.delist",
620
- domain: "listings",
621
- roles: ["admin"],
622
- description: "Internal staff only: delist an inventory listing. Sets its state to delisted. Requires confirm: true.",
623
- mutation: "preview-confirm",
624
- http: { method: "PATCH", path: "/inventory/{inventoryId}" },
625
- input: inventoryDelistInput,
626
- execute: async (ctx, input) => {
627
- const parsed = inventoryDelistInput.parse(input);
628
- const body = {
629
- state: "delisted",
630
- version: await resolveVersion(ctx, `/inventory/${seg(parsed.inventoryId)}`, parsed.version),
631
- };
632
- if (!ctx.confirmed)
633
- return preview("inventory.delist", { inventoryId: parsed.inventoryId, ...body });
634
- return ctx.fetch(`/inventory/${seg(parsed.inventoryId)}`, {
635
- method: "PATCH",
636
- body,
637
- });
638
- },
639
- });
640
- export const billingInvoicesListInput = z.object({
641
- status: z
642
- .enum(["due", "open", "paid", "draft", "void", "uncollectible"])
643
- .default("due")
644
- .optional(),
645
- cursor: z.string().optional(),
646
- limit: z.number().int().positive().max(100).optional(),
647
- });
648
- export const billingInvoicesListCapability = defineStaffMcpCapability({
649
- id: "billing.invoices-list",
650
- domain: "billing",
651
- roles: ["reviewer", "admin"],
652
- description: "Internal staff only: list commerce invoices with an optional status filter (defaults to due).",
653
- mutation: "read",
654
- http: { method: "GET", path: "/internal/invoices" },
655
- input: billingInvoicesListInput,
656
- execute: async (ctx, input) => {
657
- const parsed = billingInvoicesListInput.parse(input ?? {});
658
- const parts = [];
659
- if (parsed.status)
660
- parts.push(`status=${encodeURIComponent(parsed.status)}`);
661
- if (parsed.cursor)
662
- parts.push(`cursor=${encodeURIComponent(parsed.cursor)}`);
663
- if (parsed.limit)
664
- parts.push(`limit=${encodeURIComponent(String(parsed.limit))}`);
665
- const qs = parts.length > 0 ? `?${parts.join("&")}` : "";
666
- return ctx.fetch(`/internal/invoices${qs}`, { method: "GET" });
667
- },
668
- });
669
- export const staffCommerceCapabilities = [
670
- reservationShowCapability,
671
- reservationListCapability,
672
- reservationCancelCapability,
673
- reservationTransferCapability,
674
- reservationMachinesListCapability,
675
- reservationSshKeyAddCapability,
676
- reservationSshKeyRemoveCapability,
677
- reservationPublishCapability,
678
- reservationDraftCreateCapability,
679
- reservationUpdateCapability,
680
- reservationActivateCapability,
681
- reservationCompleteCapability,
682
- bidShowCapability,
683
- bidAcceptCapability,
684
- bidRejectCapability,
685
- inventoryUpdateCapability,
686
- inventoryDelistCapability,
687
- tenantCreditProfileShowCapability,
688
- billingInvoicesListCapability,
689
- ];