@ornncompute/cli 0.2.24 → 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 +298 -426
  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
@@ -0,0 +1,191 @@
1
+ import { z } from "zod";
2
+ import { defineCapability, } from "../capability.js";
3
+ const pageInput = z
4
+ .object({
5
+ $positionals: z.array(z.string()).optional(),
6
+ cursor: z.string().optional(),
7
+ limit: z.union([z.string(), z.number()]).optional(),
8
+ })
9
+ .strict();
10
+ const showInput = z
11
+ .object({
12
+ $positionals: z.array(z.string()).optional(),
13
+ })
14
+ .strict();
15
+ const verificationInput = z
16
+ .object({
17
+ $positionals: z.array(z.string()).optional(),
18
+ idempotencyKey: z.string().optional(),
19
+ })
20
+ .strict();
21
+ const disconnectInput = z
22
+ .object({
23
+ $positionals: z.array(z.string()).optional(),
24
+ idempotencyKey: z.string().optional(),
25
+ yes: z.boolean().optional(),
26
+ })
27
+ .strict();
28
+ const connectInput = z
29
+ .object({
30
+ $positionals: z.array(z.string()).optional(),
31
+ accessKeyId: z.string().optional(),
32
+ accountId: z.string().optional(),
33
+ bucket: z.string().optional(),
34
+ endpointUrl: z.string().optional(),
35
+ idempotencyKey: z.string().optional(),
36
+ name: z.string().optional(),
37
+ prefix: z.string().optional(),
38
+ readOnly: z.boolean().optional(),
39
+ readWrite: z.boolean().optional(),
40
+ region: z.string().optional(),
41
+ secretAccessKeyFile: z.string().optional(),
42
+ url: z.string().optional(),
43
+ yes: z.boolean().optional(),
44
+ })
45
+ .strict();
46
+ const credentialInput = z
47
+ .object({
48
+ $positionals: z.array(z.string()).optional(),
49
+ accessKeyId: z.string().optional(),
50
+ idempotencyKey: z.string().optional(),
51
+ secretAccessKeyFile: z.string().optional(),
52
+ yes: z.boolean().optional(),
53
+ })
54
+ .strict();
55
+ const importInput = z
56
+ .object({
57
+ $positionals: z.array(z.string()).optional(),
58
+ idempotencyKey: z.string().optional(),
59
+ noWait: z.boolean().optional(),
60
+ reservation: z.string().optional(),
61
+ timeout: z.string().optional(),
62
+ wait: z.boolean().optional(),
63
+ yes: z.boolean().optional(),
64
+ })
65
+ .strict();
66
+ function externalBucketExecutor(context) {
67
+ const interaction = context.cli?.interaction;
68
+ if (!interaction?.externalBuckets) {
69
+ throw new Error("This capability must execute through the Ornn CLI external-bucket runtime.");
70
+ }
71
+ return interaction.externalBuckets.bind(interaction);
72
+ }
73
+ function externalBucketCapability({ acceptedReferences = [], automation, command, description, human, id, input, mutation, outcome, related = [], resumableStates = [], review, }) {
74
+ return defineCapability({
75
+ id,
76
+ domain: "storage",
77
+ roles: ["user"],
78
+ description,
79
+ mutation,
80
+ input,
81
+ cli: {
82
+ command,
83
+ vnext: {
84
+ arguments: "semantic",
85
+ audience: "customer",
86
+ category: "resource",
87
+ outcome,
88
+ human,
89
+ automation,
90
+ acceptedReferences,
91
+ related,
92
+ resumableStates,
93
+ ...(review ? { review } : {}),
94
+ },
95
+ },
96
+ mcp: false,
97
+ slack: false,
98
+ execute: async (context, commandInput) => externalBucketExecutor(context)(id, commandInput),
99
+ });
100
+ }
101
+ const gcsImportGuidance = "GCS requires bucket-level read access for Ornn's Storage Transfer service agent, granted with gcloud. CLI 1.x GCS imports are currently blocked by unavailable source verification. Setup and version-specific steps: https://docs.ornn.com/cli#gcs-imports-and-storage-transfer-service";
102
+ export const externalBucketCapabilities = [
103
+ externalBucketCapability({
104
+ id: "storage.external-buckets.list",
105
+ command: ["external-buckets", "list"],
106
+ description: "List connected GCS, S3, and R2 buckets in one bounded page.",
107
+ mutation: "read",
108
+ input: pageInput,
109
+ outcome: "List external bucket connections without exposing stored credentials.",
110
+ human: "ornn external-buckets list [--limit <1-100>] [--cursor <cursor>]",
111
+ automation: "ornn external-buckets list --limit 50 --format json",
112
+ related: ["external-buckets show", "external-buckets connect"],
113
+ }),
114
+ externalBucketCapability({
115
+ id: "storage.external-buckets.show",
116
+ command: ["external-buckets", "show"],
117
+ description: "Inspect one connected external bucket by canonical ID or exact unique name.",
118
+ mutation: "read",
119
+ input: showInput,
120
+ outcome: "Inspect one credential-safe external bucket connection.",
121
+ human: "ornn external-buckets show <external-bucket>",
122
+ automation: "ornn external-buckets show <external-bucket-id> --format json",
123
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
124
+ related: ["external-buckets verify", "external-buckets import"],
125
+ }),
126
+ externalBucketCapability({
127
+ id: "storage.external-buckets.connect",
128
+ command: ["external-buckets", "connect"],
129
+ description: "Connect a typed GCS, AWS S3, or Cloudflare R2 source without verifying it implicitly.",
130
+ mutation: "preview-confirm",
131
+ input: connectInput,
132
+ outcome: "Create or rediscover one external bucket connection with verification kept separate.",
133
+ human: "ornn external-buckets connect <gcs|s3|r2> --bucket <bucket> [--name <name>] [--prefix <prefix>] [--region <region>] [--account-id <id>|--endpoint-url <url>] [--access-key-id <id> --secret-access-key-file <path>] [--read-only|--read-write]",
134
+ automation: "ornn external-buckets connect <gcs|s3|r2> --bucket <bucket> <provider-inputs> --yes --idempotency-key <key> --format json",
135
+ review: `Review the exact source, access mode, and credential presence before connecting it. ${gcsImportGuidance}`,
136
+ related: ["external-buckets verify", "external-buckets import"],
137
+ }),
138
+ externalBucketCapability({
139
+ id: "storage.external-buckets.update-credentials",
140
+ command: ["external-buckets", "update-credentials"],
141
+ description: "Replace scoped S3 or R2 credentials without returning credential bytes.",
142
+ mutation: "preview-confirm",
143
+ input: credentialInput,
144
+ outcome: "Update one connection's credentials and require a separate verification.",
145
+ human: "ornn external-buckets update-credentials <external-bucket> --access-key-id <id> --secret-access-key-file <path>",
146
+ automation: "ornn external-buckets update-credentials <external-bucket-id> --access-key-id <id> --secret-access-key-file <path> --yes --idempotency-key <key> --format json",
147
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
148
+ review: "Credential changes invalidate the prior verification and require renewed review.",
149
+ related: ["external-buckets verify"],
150
+ }),
151
+ externalBucketCapability({
152
+ id: "storage.external-buckets.verify",
153
+ command: ["external-buckets", "verify"],
154
+ description: "Verify current S3 or R2 source access as a separate operation.",
155
+ mutation: "preview-confirm",
156
+ input: verificationInput,
157
+ outcome: "Record current source-access verification without exposing credential material.",
158
+ human: "ornn external-buckets verify <external-bucket>",
159
+ automation: "ornn external-buckets verify <external-bucket-id> --idempotency-key <key> --format json",
160
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
161
+ review: gcsImportGuidance,
162
+ related: ["external-buckets import", "external-buckets update-credentials"],
163
+ }),
164
+ externalBucketCapability({
165
+ id: "storage.external-buckets.import",
166
+ command: ["external-buckets", "import"],
167
+ description: "Import a verified GCS or S3 source into its isolated managed working volume.",
168
+ mutation: "preview-confirm",
169
+ input: importInput,
170
+ outcome: "Create or replay exactly one managed working copy with source writeback disabled.",
171
+ human: "ornn external-buckets import <gcs|s3|r2> <external-bucket> --reservation <canonical-reservation-id> [--no-wait] [--timeout <duration>]",
172
+ automation: "ornn external-buckets import <gcs|s3> <external-bucket-id> --reservation <canonical-reservation-id> --yes --idempotency-key <key> [--wait --timeout <duration>] --format json",
173
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
174
+ review: `Review the isolated destination and explicit no-writeback guarantee before importing. ${gcsImportGuidance}`,
175
+ resumableStates: ["pending", "working", "partial", "failed", "interrupted"],
176
+ related: ["external-buckets show", "storage status", "volumes files list"],
177
+ }),
178
+ externalBucketCapability({
179
+ id: "storage.external-buckets.disconnect",
180
+ command: ["external-buckets", "disconnect"],
181
+ description: "Disconnect an external bucket record after its working volume is detached.",
182
+ mutation: "preview-confirm",
183
+ input: disconnectInput,
184
+ outcome: "Remove one external source connection without writing to its source bucket.",
185
+ human: "ornn external-buckets disconnect <external-bucket>",
186
+ automation: "ornn external-buckets disconnect <external-bucket-id> --yes --idempotency-key <key> --format json",
187
+ acceptedReferences: ["canonical external bucket ID", "exact unique external bucket name"],
188
+ review: "Disconnecting reduces access to the source and is blocked while mounts remain.",
189
+ related: ["storage detach", "external-buckets show"],
190
+ }),
191
+ ];
@@ -21,6 +21,9 @@ const nodesInput = z.object({
21
21
  facilityId: z.string().optional(),
22
22
  status: z.string().optional(),
23
23
  email: z.string().optional(),
24
+ cursor: z.string().optional(),
25
+ max: z.number().int().positive().max(5000).optional(),
26
+ limit: z.number().int().positive().max(5000).optional(),
24
27
  });
25
28
  const nodeLifecycleInput = z.object({
26
29
  node_id: z.string().min(1),
@@ -42,7 +45,7 @@ function nodeIdOf(input) {
42
45
  export const listOperatorsCapability = defineCapability({
43
46
  id: "operator.list",
44
47
  domain: "fleet",
45
- roles: ["reviewer", "admin"],
48
+ roles: ["admin"],
46
49
  description: "List GPU operators (id, slug, display name, status). Resolve operator names with this before nodes.list.",
47
50
  mutation: "read",
48
51
  http: { method: "GET", path: "/provisioning/operators" },
@@ -53,7 +56,7 @@ export const listOperatorsCapability = defineCapability({
53
56
  export const listFacilitiesCapability = defineCapability({
54
57
  id: "facilities.list",
55
58
  domain: "fleet",
56
- roles: ["reviewer", "admin"],
59
+ roles: ["admin"],
57
60
  description: "List facilities (id, display name, region, operator). Optional operator_id filter. Resolve facility names with this before nodes.list.",
58
61
  mutation: "read",
59
62
  http: { method: "GET", path: "/provisioning/facilities" },
@@ -76,8 +79,8 @@ export const listFacilitiesCapability = defineCapability({
76
79
  export const listNodesCapability = defineCapability({
77
80
  id: "nodes.list",
78
81
  domain: "fleet",
79
- roles: ["user", "reviewer", "admin"],
80
- description: "List nodes. Users see their own reserved machines. Reviewers and admins see enrolled, ghost, and off-grid nodes. Call with no filters for the whole fleet. Optional email lists nodes for that user's organizations. Optional operator_id, facility_id, and status (ghost, off-grid, bare-metal) narrow the same list. Never invent nodes. Never print private keys.",
82
+ roles: ["user", "admin"],
83
+ description: "List nodes. Users see their own reserved machines. Admins see enrolled, ghost, and off-grid nodes. Call with no filters for the whole fleet. Optional email lists nodes for that user's organizations. Optional operator_id, facility_id, and status (ghost, off-grid, bare-metal) narrow the same list. Paginate large fleets with cursor + max: pass the response's next_cursor back as cursor to fetch the next page; next_cursor is omitted once the page is not full. Never invent nodes. Never print private keys.",
81
84
  mutation: "read",
82
85
  http: { method: "GET", path: "/nodes" },
83
86
  slack: {
@@ -98,20 +101,33 @@ export const listNodesCapability = defineCapability({
98
101
  enum: ["ghost", "off-grid", "bare-metal"],
99
102
  description: "Optional orchestrator status filter. Default is all statuses.",
100
103
  },
104
+ cursor: {
105
+ type: "string",
106
+ description: "Optional. Pass the previous response's next_cursor to fetch the next page.",
107
+ },
108
+ max: {
109
+ type: "integer",
110
+ description: "Optional page size (1-5000). Orchestrator default is 500.",
111
+ },
101
112
  },
102
113
  },
103
114
  },
104
115
  input: nodesInput,
105
116
  execute: async (ctx, input) => {
106
117
  const parsed = nodesInput.parse(input);
118
+ const pagination = {
119
+ cursor: parsed.cursor,
120
+ limit: parsed.max != null || parsed.limit != null ? String(parsed.max ?? parsed.limit) : undefined,
121
+ };
107
122
  if (ctx.role === "user") {
108
- return ctx.fetch("/nodes", { method: "GET" });
123
+ return ctx.fetch(queryPath("/nodes", pagination), { method: "GET" });
109
124
  }
110
125
  return ctx.fetch(queryPath("/nodes", {
111
126
  operator_id: parsed.operator_id ?? parsed.operatorId ?? parsed.operator,
112
127
  facility_id: parsed.facility_id ?? parsed.facilityId,
113
128
  status: parsed.status,
114
129
  email: parsed.email,
130
+ ...pagination,
115
131
  }), { method: "GET" });
116
132
  },
117
133
  });
@@ -1,10 +1,103 @@
1
1
  import { z } from "zod";
2
2
  import { defineCapability } from "../capability.js";
3
3
  const emptyInput = z.object({});
4
+ const contextInput = z.object({ organization: z.string().min(1).optional() });
5
+ const loginInput = z.object({
6
+ authBase: z.string().min(1).optional(),
7
+ noBrowser: z.boolean().optional(),
8
+ organization: z.string().min(1).optional(),
9
+ pollInterval: z.number().positive().optional(),
10
+ timeout: z.number().positive().optional(),
11
+ });
12
+ function executeSession(context, command, input) {
13
+ if (!context.cli?.session) {
14
+ throw new Error("This capability must execute through the Ornn CLI session runtime.");
15
+ }
16
+ return context.cli.session(command, input);
17
+ }
18
+ export const loginCapability = defineCapability({
19
+ id: "identity.login",
20
+ domain: "identity",
21
+ roles: ["user"],
22
+ description: "Authenticate the CLI with Ornn device authorization.",
23
+ mutation: "local",
24
+ http: { method: "POST", path: "/v1/cli/device/start" },
25
+ input: loginInput,
26
+ cli: {
27
+ command: ["login"],
28
+ vnext: {
29
+ audience: "customer",
30
+ category: "session",
31
+ arguments: "semantic",
32
+ outcome: "Authenticate in a browser and return to the terminal.",
33
+ human: "ornn login [--organization <exact-name>] [--no-browser] [--timeout <seconds>]",
34
+ automation: "ornn login --format json",
35
+ resumableStates: ["browser authorization"],
36
+ related: ["context", "logout"],
37
+ order: 1,
38
+ },
39
+ },
40
+ mcp: false,
41
+ slack: false,
42
+ execute: async (context, input) => executeSession(context, "login", input),
43
+ });
44
+ export const contextCapability = defineCapability({
45
+ id: "identity.context",
46
+ domain: "identity",
47
+ roles: ["user"],
48
+ description: "Show and select the current server-verified organization context.",
49
+ mutation: "read",
50
+ http: { method: "GET", path: "/v1/cli/session" },
51
+ input: contextInput,
52
+ cli: {
53
+ command: ["context"],
54
+ vnext: {
55
+ audience: "customer",
56
+ category: "session",
57
+ arguments: "semantic",
58
+ outcome: "Show the current user, selected organization, approval, and available organizations.",
59
+ human: "ornn context [--organization <exact-name-or-id>]",
60
+ automation: "ornn context --organization <canonical-id> --format json",
61
+ acceptedReferences: [
62
+ "exact organization name (human)",
63
+ "canonical organization ID (automation)",
64
+ ],
65
+ related: ["login", "logout"],
66
+ order: 2,
67
+ },
68
+ },
69
+ mcp: false,
70
+ slack: false,
71
+ execute: async (context, input) => executeSession(context, "context", input),
72
+ });
73
+ export const logoutCapability = defineCapability({
74
+ id: "identity.logout",
75
+ domain: "identity",
76
+ roles: ["user"],
77
+ description: "Revoke the remote CLI session and clear its local bearer.",
78
+ mutation: "local",
79
+ http: { method: "DELETE", path: "/v1/cli/session" },
80
+ input: emptyInput,
81
+ cli: {
82
+ command: ["logout"],
83
+ vnext: {
84
+ audience: "customer",
85
+ category: "session",
86
+ outcome: "End the remote session and always remove the local bearer.",
87
+ human: "ornn logout",
88
+ automation: "ornn logout --format json",
89
+ related: ["login", "context"],
90
+ order: 3,
91
+ },
92
+ },
93
+ mcp: false,
94
+ slack: false,
95
+ execute: async (context, input) => executeSession(context, "logout", input),
96
+ });
4
97
  export const whoamiCapability = defineCapability({
5
98
  id: "identity.whoami",
6
99
  domain: "identity",
7
- roles: ["user", "reviewer", "admin"],
100
+ roles: ["user", "admin"],
8
101
  description: "Show the signed-in Ornn user and organization.",
9
102
  mutation: "read",
10
103
  http: { method: "GET", path: "/v1/cli/session" },
@@ -14,11 +107,29 @@ export const whoamiCapability = defineCapability({
14
107
  export const statusCapability = defineCapability({
15
108
  id: "identity.status",
16
109
  domain: "identity",
17
- roles: ["user", "reviewer", "admin"],
110
+ roles: ["user", "admin"],
18
111
  description: "Show Ornn platform status.",
19
112
  mutation: "read",
20
113
  http: { method: "GET", path: "/v1/cli/status" },
21
114
  input: emptyInput,
22
115
  execute: async (ctx) => ctx.fetch("/v1/cli/status", { method: "GET" }),
116
+ cli: {
117
+ command: ["status"],
118
+ vnext: {
119
+ audience: "customer",
120
+ category: "platform",
121
+ outcome: "Check current Ornn platform availability.",
122
+ human: "ornn status",
123
+ automation: "ornn status --format json",
124
+ related: ["version", "resources"],
125
+ order: 10,
126
+ },
127
+ },
23
128
  });
24
- export const identityCapabilities = [whoamiCapability, statusCapability];
129
+ export const identityCapabilities = [
130
+ whoamiCapability,
131
+ loginCapability,
132
+ contextCapability,
133
+ logoutCapability,
134
+ statusCapability,
135
+ ];
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { defineCapability, preview } from "../capability.js";
3
3
  import { bindHardwareFields, HARDWARE_LISTING_FIELDS, HARDWARE_LISTING_FILTERS, loadHardwareCatalog, projectCatalogTerms, } from "../spec-catalog.js";
4
+ import { capacityOfferForecastVnext, executeCapacityOfferForecast } from "./capacity-offers.js";
4
5
  function queryPath(path, params) {
5
6
  const parts = [];
6
7
  for (const [key, value] of Object.entries(params)) {
@@ -54,8 +55,9 @@ export const listingCreateInput = z.object({
54
55
  confirm: z.boolean().optional(),
55
56
  });
56
57
  const catalogTermsInput = z.object({ kind: z.enum(["gpu", "cpu", "node", "link"]).optional() });
57
- const listingForecastInput = z.object({
58
- limit: z.number().int().min(1).max(500).optional(),
58
+ export const listingForecastInput = z.object({
59
+ $positionals: z.array(z.string()).max(0).default([]),
60
+ limit: z.coerce.number().int().min(1).max(500).optional(),
59
61
  cursor: z.string().min(1).optional(),
60
62
  });
61
63
  const slackListingCreateParams = {
@@ -84,10 +86,10 @@ const slackListingCreateParams = {
84
86
  export const listCatalogTermsCapability = defineCapability({
85
87
  id: "listings.catalog_terms",
86
88
  domain: "listings",
87
- roles: ["reviewer", "admin"],
89
+ roles: ["admin"],
88
90
  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.",
89
91
  mutation: "read",
90
- http: { method: "GET", path: "/v1/orchestrator/catalog" },
92
+ http: { method: "GET", path: "/v1/orchestrator/catalog/hardware" },
91
93
  slack: {
92
94
  parameters: {
93
95
  type: "object",
@@ -106,7 +108,7 @@ export const listCatalogTermsCapability = defineCapability({
106
108
  export const listListingsCapability = defineCapability({
107
109
  id: "listings.available",
108
110
  domain: "listings",
109
- roles: ["reviewer", "admin"],
111
+ roles: ["user", "admin"],
110
112
  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.",
111
113
  mutation: "read",
112
114
  slack: {
@@ -154,10 +156,16 @@ export const forecastListingsCapability = defineCapability({
154
156
  description: "List the next 48 hourly availability buckets for customer-visible compute listings.",
155
157
  mutation: "read",
156
158
  http: { method: "GET", path: "/v1/workflows/commerce/listings/forecast" },
159
+ cli: {
160
+ command: ["capacity-offers", "forecast"],
161
+ vnext: capacityOfferForecastVnext,
162
+ },
157
163
  slack: false,
158
164
  input: listingForecastInput,
159
165
  execute: async (ctx, input) => {
160
166
  const parsed = listingForecastInput.parse(input);
167
+ if (ctx.cli?.interaction)
168
+ return executeCapacityOfferForecast(ctx, parsed);
161
169
  return ctx.fetch(queryPath("/v1/workflows/commerce/listings/forecast", {
162
170
  limit: parsed.limit?.toString(),
163
171
  cursor: parsed.cursor,
@@ -199,7 +207,7 @@ const LISTING_CREATE_INPUT_ONLY_KEYS = [
199
207
  export const createListingCapability = defineCapability({
200
208
  id: "listings.create",
201
209
  domain: "listings",
202
- roles: ["reviewer", "admin"],
210
+ roles: ["admin"],
203
211
  description: "Create a marketplace listing. gpu_type, cpu, node_model, and fabric_type must exact-match an orchestrator catalog display name, canonical, or alias. CLI asks y/N. Slack never POSTs until a human replies confirm. MCP creates on this tool call.",
204
212
  mutation: "preview-confirm",
205
213
  http: { method: "POST", path: "/inventory" },
@@ -349,5 +357,3 @@ export const listingCapabilities = [
349
357
  forecastListingsCapability,
350
358
  createListingCapability,
351
359
  ];
352
- /** @deprecated Use createListingCapability. */
353
- export const proposeListingCapability = createListingCapability;
@@ -0,0 +1,176 @@
1
+ import { z } from "zod";
2
+ import { defineCapability, } from "../capability.js";
3
+ const mountInput = z
4
+ .object({
5
+ $positionals: z.array(z.string()).default([]),
6
+ access: z.string().optional(),
7
+ capacityGib: z.union([z.string(), z.number()]).optional(),
8
+ cursor: z.string().optional(),
9
+ idempotencyKey: z.string().optional(),
10
+ limit: z.union([z.string(), z.number()]).optional(),
11
+ noWait: z.boolean().optional(),
12
+ performanceTier: z.string().optional(),
13
+ timeout: z.string().optional(),
14
+ volume: z.string().optional(),
15
+ wait: z.boolean().optional(),
16
+ yes: z.boolean().optional(),
17
+ })
18
+ .strict();
19
+ function mountExecutor(context) {
20
+ const interaction = context.cli?.interaction;
21
+ if (!interaction?.mounts) {
22
+ throw new Error("This capability must execute through the Ornn CLI mount runtime.");
23
+ }
24
+ return interaction.mounts.bind(interaction);
25
+ }
26
+ function mountCapability({ acceptedReferences = [], automation, command, description, human, id, mutation, order, outcome, related = [], resumableStates = [], review, }) {
27
+ return defineCapability({
28
+ id,
29
+ domain: "storage",
30
+ roles: ["user"],
31
+ description,
32
+ mutation,
33
+ input: mountInput,
34
+ cli: {
35
+ command,
36
+ vnext: {
37
+ arguments: "semantic",
38
+ audience: "customer",
39
+ category: "resource",
40
+ order,
41
+ outcome,
42
+ human,
43
+ automation,
44
+ acceptedReferences,
45
+ related,
46
+ resumableStates,
47
+ ...(review ? { review } : {}),
48
+ },
49
+ },
50
+ mcp: false,
51
+ slack: false,
52
+ execute: async (context, input) => mountExecutor(context)(id, input),
53
+ });
54
+ }
55
+ export const mountCapabilities = [
56
+ mountCapability({
57
+ id: "storage.volume-mounts.candidates",
58
+ command: ["volume-mounts", "candidates"],
59
+ description: "List capacity reservations eligible for a managed-volume mount.",
60
+ mutation: "read",
61
+ order: 540,
62
+ outcome: "List one bounded page of safe Bare Metal volume-mount candidates.",
63
+ human: "ornn volume-mounts candidates [--limit <1-500>] [--cursor <cursor>]",
64
+ automation: "ornn volume-mounts candidates --limit 50 --format json",
65
+ related: ["volume-mounts attach", "volumes list"],
66
+ }),
67
+ mountCapability({
68
+ id: "storage.volume-mounts.show",
69
+ command: ["volume-mounts", "show"],
70
+ description: "Show one reservation's exact managed-volume relationship and per-machine readiness.",
71
+ mutation: "read",
72
+ order: 541,
73
+ outcome: "Show actual per-machine mount state without rewriting volume lifecycle.",
74
+ human: "ornn volume-mounts show <canonical-reservation-id>",
75
+ automation: "ornn volume-mounts show <canonical-reservation-id> --format json",
76
+ acceptedReferences: ["canonical capacity-reservation ID"],
77
+ related: ["volume-mounts wait", "volumes show"],
78
+ }),
79
+ mountCapability({
80
+ id: "storage.volume-mounts.attach",
81
+ command: ["volume-mounts", "attach"],
82
+ description: "Attach one exact managed volume to every machine in one exact capacity reservation.",
83
+ mutation: "preview-confirm",
84
+ order: 542,
85
+ outcome: "Reconcile one exact volume-mount goal and preserve per-machine readiness.",
86
+ human: "ornn volume-mounts attach <canonical-reservation-id> --volume <exact-name-or-id> --access read-only|read-write [--no-wait]",
87
+ automation: "ornn volume-mounts attach <canonical-reservation-id> --volume <canonical-volume-id> --access read-only|read-write --yes --idempotency-key <key> --format json",
88
+ acceptedReferences: [
89
+ "canonical capacity-reservation ID",
90
+ "canonical or exact unique managed-volume reference",
91
+ ],
92
+ review: "Review the exact capacity reservation, volume, and access mode.",
93
+ resumableStates: ["accepted", "partial", "ready"],
94
+ related: ["volume-mounts wait", "volume-mounts detach"],
95
+ }),
96
+ mountCapability({
97
+ id: "storage.volume-mounts.wait",
98
+ command: ["volume-mounts", "wait"],
99
+ description: "Wait locally for every requested machine mount to reach a durable terminal state.",
100
+ mutation: "read",
101
+ order: 543,
102
+ outcome: "Wait with a bounded deadline while preserving useful per-machine partial data.",
103
+ human: "ornn volume-mounts wait <canonical-reservation-id> [--timeout <duration>]",
104
+ automation: "ornn volume-mounts wait <canonical-reservation-id> --timeout <duration> --format json",
105
+ acceptedReferences: ["canonical capacity-reservation ID"],
106
+ resumableStates: ["partial", "ready"],
107
+ related: ["volume-mounts show", "volume-mounts detach"],
108
+ }),
109
+ mountCapability({
110
+ id: "storage.volume-mounts.detach",
111
+ command: ["volume-mounts", "detach"],
112
+ description: "Detach one exact reservation relationship without deleting its volume.",
113
+ mutation: "preview-confirm",
114
+ order: 544,
115
+ outcome: "Reconcile an exact detached goal and preserve the underlying volume.",
116
+ human: "ornn volume-mounts detach <canonical-reservation-id>",
117
+ automation: "ornn volume-mounts detach <canonical-reservation-id> --yes --idempotency-key <key> --format json",
118
+ acceptedReferences: ["canonical capacity-reservation ID"],
119
+ review: "Review the exact relationship and access loss; the managed volume remains.",
120
+ resumableStates: ["detaching", "ended"],
121
+ related: ["volumes show", "private-networks list"],
122
+ }),
123
+ mountCapability({
124
+ id: "storage.filesystems.deploy",
125
+ command: ["filesystems", "deploy"],
126
+ description: "Deploy one managed filesystem for an exact capacity reservation.",
127
+ mutation: "preview-confirm",
128
+ order: 550,
129
+ outcome: "Create or rediscover one filesystem deployment and expose every machine mount.",
130
+ human: "ornn filesystems deploy <canonical-reservation-id> --performance-tier <tier> --capacity-gib <gib> [--no-wait]",
131
+ automation: "ornn filesystems deploy <canonical-reservation-id> --performance-tier <tier> --capacity-gib <gib> --yes --idempotency-key <key> --format json",
132
+ acceptedReferences: ["canonical capacity-reservation ID"],
133
+ review: "Review the exact reservation, performance tier, capacity, and managed filesystem consequence.",
134
+ resumableStates: ["accepted", "partial", "ready"],
135
+ related: ["filesystems wait", "filesystems delete"],
136
+ }),
137
+ mountCapability({
138
+ id: "storage.filesystems.show",
139
+ command: ["filesystems", "show"],
140
+ description: "Summarize one filesystem deployment from its underlying per-machine mounts.",
141
+ mutation: "read",
142
+ order: 551,
143
+ outcome: "Show actual per-machine filesystem readiness without changing volume state.",
144
+ human: "ornn filesystems show <canonical-reservation-id>",
145
+ automation: "ornn filesystems show <canonical-reservation-id> --format json",
146
+ acceptedReferences: ["canonical capacity-reservation ID"],
147
+ related: ["filesystems wait", "volume-mounts show"],
148
+ }),
149
+ mountCapability({
150
+ id: "storage.filesystems.wait",
151
+ command: ["filesystems", "wait"],
152
+ description: "Wait locally for a filesystem deployment's per-machine mount goal.",
153
+ mutation: "read",
154
+ order: 552,
155
+ outcome: "Wait for every filesystem member while retaining partial member data.",
156
+ human: "ornn filesystems wait <canonical-reservation-id> [--timeout <duration>]",
157
+ automation: "ornn filesystems wait <canonical-reservation-id> --timeout <duration> --format json",
158
+ acceptedReferences: ["canonical capacity-reservation ID"],
159
+ resumableStates: ["partial", "ready"],
160
+ related: ["filesystems show", "filesystems delete"],
161
+ }),
162
+ mountCapability({
163
+ id: "storage.filesystems.delete",
164
+ command: ["filesystems", "delete"],
165
+ description: "Remove one filesystem deployment relationship without deleting its managed volume.",
166
+ mutation: "preview-confirm",
167
+ order: 553,
168
+ outcome: "Detach every filesystem member while preserving the underlying managed volume.",
169
+ human: "ornn filesystems delete <canonical-reservation-id>",
170
+ automation: "ornn filesystems delete <canonical-reservation-id> --yes --idempotency-key <key> --format json",
171
+ acceptedReferences: ["canonical capacity-reservation ID"],
172
+ review: "Review the exact deployment and access loss; its managed volume remains.",
173
+ resumableStates: ["detaching", "ended"],
174
+ related: ["volumes show", "filesystems deploy"],
175
+ }),
176
+ ];