@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.
- package/README.md +298 -426
- package/package.json +12 -6
- package/src/admin-capacity-reservations.mjs +280 -0
- package/src/admin-command-handlers.mjs +57 -0
- package/src/admin-enrollment.mjs +680 -0
- package/src/admin-mutations.mjs +187 -0
- package/src/admin-node-diagnostics.mjs +412 -0
- package/src/admin-node-lifecycle.mjs +908 -0
- package/src/admin-shell.mjs +322 -0
- package/src/api-client.mjs +81 -28
- package/src/atomic-output.mjs +112 -0
- package/src/auth-store.mjs +182 -11
- package/src/capability-catalog.mjs +7 -0
- package/src/catalog-shell.mjs +1350 -0
- package/src/cli.mjs +123 -10266
- package/src/connect-mode-handlers.mjs +170 -0
- package/src/device-auth.mjs +178 -61
- package/src/evidence-commands.mjs +1340 -0
- package/src/external-bucket-commands.mjs +1374 -0
- package/src/interaction-runtime.mjs +310 -0
- package/src/kubernetes-commands.mjs +2229 -0
- package/src/launch-mode-handlers.mjs +119 -0
- package/src/machine-reference.mjs +277 -0
- package/src/mount-commands.mjs +1146 -0
- package/src/session-commands.mjs +565 -0
- package/src/slurm-commands.mjs +263 -0
- package/src/startup.mjs +34 -11
- package/src/storage-commands.mjs +1144 -0
- package/src/update.mjs +172 -24
- package/src/vm-commands.mjs +4273 -0
- package/src/vpn-secret-output.mjs +150 -0
- package/src/wait-runtime.mjs +231 -0
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +126 -0
- package/vendor/capabilities/capabilities/admin-enrollment.js +321 -0
- package/vendor/capabilities/capabilities/admin-node-diagnostics.js +88 -0
- package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +235 -0
- package/vendor/capabilities/capabilities/admin.js +89 -0
- package/vendor/capabilities/capabilities/capacity-offers.js +368 -0
- package/vendor/capabilities/capabilities/cli.js +156 -0
- package/vendor/capabilities/capabilities/clusters.js +150 -1
- package/vendor/capabilities/capabilities/compute.js +522 -0
- package/vendor/capabilities/capabilities/evidence.js +145 -0
- package/vendor/capabilities/capabilities/external-buckets.js +191 -0
- package/vendor/capabilities/capabilities/fleet.js +21 -5
- package/vendor/capabilities/capabilities/identity.js +114 -3
- package/vendor/capabilities/capabilities/listings.js +14 -8
- package/vendor/capabilities/capabilities/mounts.js +176 -0
- package/vendor/capabilities/capabilities/networks.js +1257 -0
- package/vendor/capabilities/capabilities/observability.js +38 -8
- package/vendor/capabilities/capabilities/tokens.js +20 -8
- package/vendor/capabilities/capabilities/volumes.js +178 -0
- package/vendor/capabilities/capability.js +19 -13
- package/vendor/capabilities/cli-catalog.js +65 -0
- package/vendor/capabilities/cli-migrations.js +288 -0
- package/vendor/capabilities/cli.js +7 -0
- package/vendor/capabilities/software-catalog.js +36 -0
- package/vendor/capabilities/spec-catalog.js +2 -2
- package/src/catalog-dispatch.mjs +0 -605
- package/src/color.mjs +0 -71
- package/vendor/capabilities/capabilities/clusters.d.ts +0 -2
- package/vendor/capabilities/capabilities/fleet.d.ts +0 -8
- package/vendor/capabilities/capabilities/identity.d.ts +0 -3
- package/vendor/capabilities/capabilities/listings.d.ts +0 -51
- package/vendor/capabilities/capabilities/observability.d.ts +0 -5
- package/vendor/capabilities/capabilities/staff-access.d.ts +0 -29
- package/vendor/capabilities/capabilities/staff-access.js +0 -278
- package/vendor/capabilities/capabilities/staff-commerce.d.ts +0 -160
- package/vendor/capabilities/capabilities/staff-commerce.js +0 -689
- package/vendor/capabilities/capabilities/staff-fleet.d.ts +0 -140
- package/vendor/capabilities/capabilities/staff-fleet.js +0 -538
- package/vendor/capabilities/capabilities/staff-gtm.d.ts +0 -142
- package/vendor/capabilities/capabilities/staff-gtm.js +0 -521
- package/vendor/capabilities/capabilities/staff-shared.d.ts +0 -47
- package/vendor/capabilities/capabilities/staff-shared.js +0 -98
- package/vendor/capabilities/capabilities/tokens.d.ts +0 -10
- package/vendor/capabilities/capabilities/users.d.ts +0 -2
- package/vendor/capabilities/capability.d.ts +0 -93
- package/vendor/capabilities/catalog.d.ts +0 -3
- package/vendor/capabilities/catalog.js +0 -26
- package/vendor/capabilities/index.d.ts +0 -16
- package/vendor/capabilities/index.js +0 -16
- package/vendor/capabilities/names.d.ts +0 -6
- package/vendor/capabilities/role.d.ts +0 -6
- package/vendor/capabilities/role.js +0 -17
- 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: ["
|
|
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: ["
|
|
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", "
|
|
80
|
-
description: "List nodes. Users see their own reserved machines.
|
|
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", "
|
|
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", "
|
|
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 = [
|
|
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
|
-
|
|
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: ["
|
|
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: ["
|
|
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: ["
|
|
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
|
+
];
|