@ornncompute/cli 0.2.24 → 1.0.34
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 +351 -426
- package/package.json +12 -6
- package/src/admin-capacity-reservations.mjs +305 -0
- package/src/admin-command-handlers.mjs +57 -0
- package/src/admin-enrollment.mjs +681 -0
- package/src/admin-mutations.mjs +187 -0
- package/src/admin-node-diagnostics.mjs +412 -0
- package/src/admin-node-lifecycle.mjs +905 -0
- package/src/admin-shell.mjs +456 -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 +1363 -0
- package/src/cli.mjs +123 -10266
- package/src/connect-mode-handlers.mjs +170 -0
- package/src/device-auth.mjs +179 -62
- package/src/evidence-commands.mjs +1354 -0
- package/src/external-bucket-commands.mjs +1374 -0
- package/src/gpu-catalog.mjs +21 -0
- package/src/identity-file.mjs +28 -0
- package/src/interaction-runtime.mjs +310 -0
- package/src/kubernetes-commands.mjs +2235 -0
- package/src/launch-mode-handlers.mjs +119 -0
- package/src/machine-reference.mjs +285 -0
- package/src/mount-commands.mjs +1148 -0
- package/src/session-commands.mjs +565 -0
- package/src/slurm-commands.mjs +274 -0
- package/src/startup.mjs +34 -11
- package/src/storage-commands.mjs +1164 -0
- package/src/update.mjs +172 -24
- package/src/vm-commands.mjs +4308 -0
- package/src/vpn-secret-output.mjs +150 -0
- package/src/wait-runtime.mjs +231 -0
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +96 -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/cli.js +156 -0
- package/vendor/capabilities/capabilities/clusters.js +150 -1
- package/vendor/capabilities/capabilities/commerce-market.js +330 -0
- 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 +77 -25
- package/vendor/capabilities/capabilities/identity.js +114 -3
- package/vendor/capabilities/capabilities/listings.js +141 -144
- package/vendor/capabilities/capabilities/market-shared.js +44 -0
- package/vendor/capabilities/capabilities/markets.js +449 -0
- package/vendor/capabilities/capabilities/mounts.js +176 -0
- package/vendor/capabilities/capabilities/networks.js +1264 -0
- package/vendor/capabilities/capabilities/observability.js +38 -8
- package/vendor/capabilities/capabilities/tokens.js +32 -17
- package/vendor/capabilities/capabilities/volumes.js +178 -0
- package/vendor/capabilities/capability.js +21 -14
- package/vendor/capabilities/cli-catalog.js +67 -0
- package/vendor/capabilities/cli-migrations.js +261 -0
- package/vendor/capabilities/cli.js +7 -0
- package/vendor/capabilities/software-catalog.js +36 -0
- package/vendor/capabilities/spec-catalog.js +28 -5
- package/vendor/capabilities/supplier-directory.js +20 -0
- 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
|
@@ -21,6 +21,7 @@ const latestInput = z.object({
|
|
|
21
21
|
end: z.string().optional(),
|
|
22
22
|
span: z.string().optional(),
|
|
23
23
|
max_points: z.number().int().min(1).max(500).optional(),
|
|
24
|
+
metrics: z.string().optional(),
|
|
24
25
|
});
|
|
25
26
|
const logsLatestInput = z.object({
|
|
26
27
|
node_id: z.string().min(1),
|
|
@@ -28,6 +29,7 @@ const logsLatestInput = z.object({
|
|
|
28
29
|
stream: z.enum(["serial", "kernel"]).default("kernel"),
|
|
29
30
|
count: z.number().int().min(1).max(1000).optional(),
|
|
30
31
|
before: z.string().optional(),
|
|
32
|
+
start: z.string().optional(),
|
|
31
33
|
cursor: z.string().optional(),
|
|
32
34
|
});
|
|
33
35
|
const tailInput = z.object({
|
|
@@ -57,8 +59,8 @@ function expandSpan(span, start, end) {
|
|
|
57
59
|
export const telemetryLatestCapability = defineCapability({
|
|
58
60
|
id: "telemetry.latest",
|
|
59
61
|
domain: "observability",
|
|
60
|
-
roles: ["user", "
|
|
61
|
-
description: "One-shot telemetry window for one node (start/end or span plus max_points). Users may only query a reserved machine.
|
|
62
|
+
roles: ["user", "admin"],
|
|
63
|
+
description: "One-shot telemetry window for one node (start/end or span plus max_points). Users may only query a reserved machine. Admins may query any enrolled node. Pass a node UUID. Use this for heartbeat / last-seen / public_ip. Not a live stream.",
|
|
62
64
|
mutation: "read",
|
|
63
65
|
http: { method: "GET", path: "/v1/observability/nodes/{id}/telemetry/query" },
|
|
64
66
|
slack: {
|
|
@@ -70,6 +72,7 @@ export const telemetryLatestCapability = defineCapability({
|
|
|
70
72
|
start: { type: "string" },
|
|
71
73
|
end: { type: "string" },
|
|
72
74
|
max_points: { type: "number", description: "Default 240, max 500." },
|
|
75
|
+
metrics: { type: "string", description: "Optional comma-separated metric names; omit for all stored metrics." },
|
|
73
76
|
},
|
|
74
77
|
required: ["node_id"],
|
|
75
78
|
},
|
|
@@ -83,14 +86,15 @@ export const telemetryLatestCapability = defineCapability({
|
|
|
83
86
|
start: range.start,
|
|
84
87
|
end: range.end,
|
|
85
88
|
max_points: String(parsed.max_points ?? 240),
|
|
89
|
+
metrics: parsed.metrics,
|
|
86
90
|
}), { method: "GET" });
|
|
87
91
|
},
|
|
88
92
|
});
|
|
89
93
|
export const logsLatestCapability = defineCapability({
|
|
90
94
|
id: "logs.latest",
|
|
91
95
|
domain: "observability",
|
|
92
|
-
roles: ["user", "
|
|
93
|
-
description: "
|
|
96
|
+
roles: ["user", "admin"],
|
|
97
|
+
description: "Stored kernel or serial logs, all severities, newest first. Optional start/before bounds can query any retained history; default is the seven days before now. First call omits cursor; subsequent pages pass next_cursor with the same start/before. Users only on a reserved machine. Not a live tail.",
|
|
94
98
|
mutation: "read",
|
|
95
99
|
http: { method: "GET", path: "/v1/observability/nodes/{id}/logs/{stream}/query" },
|
|
96
100
|
slack: {
|
|
@@ -100,7 +104,8 @@ export const logsLatestCapability = defineCapability({
|
|
|
100
104
|
node_id: { type: "string", description: "Node UUID from nodes.list." },
|
|
101
105
|
stream: { type: "string", enum: ["serial", "kernel"] },
|
|
102
106
|
count: { type: "number", description: "Page size. Default 200, max 1000." },
|
|
103
|
-
before: { type: "string", description: "RFC3339 upper bound. Default now." },
|
|
107
|
+
before: { type: "string", description: "RFC3339 upper bound. Default now. Keep fixed when paging." },
|
|
108
|
+
start: { type: "string", description: "RFC3339 lower bound. Default seven days before the upper bound." },
|
|
104
109
|
cursor: { type: "string", description: "next_cursor from the previous page." },
|
|
105
110
|
},
|
|
106
111
|
required: ["node_id"],
|
|
@@ -111,7 +116,7 @@ export const logsLatestCapability = defineCapability({
|
|
|
111
116
|
const parsed = logsLatestInput.parse(input);
|
|
112
117
|
const id = nodeId(parsed);
|
|
113
118
|
const end = parsed.before?.trim() || new Date().toISOString();
|
|
114
|
-
const start = new Date(Date.parse(end) - 7 * 86_400_000).toISOString();
|
|
119
|
+
const start = parsed.start?.trim() || new Date(Date.parse(end) - 7 * 86_400_000).toISOString();
|
|
115
120
|
return ctx.fetch(queryPath(`${nodePrefix(ctx.role, id)}/logs/${parsed.stream}/query`, {
|
|
116
121
|
start,
|
|
117
122
|
end,
|
|
@@ -121,10 +126,34 @@ export const logsLatestCapability = defineCapability({
|
|
|
121
126
|
}), { method: "GET" });
|
|
122
127
|
},
|
|
123
128
|
});
|
|
129
|
+
/** Full stored telemetry rows, including context fields omitted by numeric windows. */
|
|
130
|
+
export const telemetrySnapshotCapability = defineCapability({
|
|
131
|
+
id: "telemetry.snapshot",
|
|
132
|
+
domain: "observability",
|
|
133
|
+
roles: ["admin"],
|
|
134
|
+
description: "Read the latest stored heartbeat with all staff-visible fields, including version, status, port context, and kernel-log coverage when stored. Works while the node is offline; check observed_at for staleness. limit=1 returns the latest row; larger limits only scan a bounded recent window, at most 24h. Use telemetry.latest for historical numeric windows.",
|
|
135
|
+
mutation: "read",
|
|
136
|
+
http: { method: "GET", path: "/v1/observability/nodes/{id}/telemetry/latest" },
|
|
137
|
+
mcp: false,
|
|
138
|
+
slack: {
|
|
139
|
+
parameters: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
node_id: { type: "string", description: "Node UUID." },
|
|
143
|
+
limit: { type: "number", description: "Recent full heartbeat rows. Default 1, max 1000." },
|
|
144
|
+
},
|
|
145
|
+
required: ["node_id"],
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
input: z.object({ node_id: z.string().min(1), limit: z.number().int().min(1).max(1000).optional() }),
|
|
149
|
+
execute: async (ctx, input) => ctx.fetch(queryPath(`/v1/observability/nodes/${encodeURIComponent(input.node_id)}/telemetry/latest`, {
|
|
150
|
+
limit: String(input.limit ?? 1),
|
|
151
|
+
}), { method: "GET" }),
|
|
152
|
+
});
|
|
124
153
|
export const telemetryTailCapability = defineCapability({
|
|
125
154
|
id: "telemetry.tail",
|
|
126
155
|
domain: "observability",
|
|
127
|
-
roles: ["user", "
|
|
156
|
+
roles: ["user", "admin"],
|
|
128
157
|
description: "Live telemetry follow on the same Observability subscribe feed the graphs use. Users only on a reserved machine. Slack and MCP attach for duration (default 20s) and return events. Not telemetry.latest.",
|
|
129
158
|
mutation: "read",
|
|
130
159
|
http: { method: "GET", path: "/v1/observability/nodes/{id}/telemetry/subscribe" },
|
|
@@ -154,7 +183,7 @@ export const telemetryTailCapability = defineCapability({
|
|
|
154
183
|
export const logsTailCapability = defineCapability({
|
|
155
184
|
id: "logs.tail",
|
|
156
185
|
domain: "observability",
|
|
157
|
-
roles: ["user", "
|
|
186
|
+
roles: ["user", "admin"],
|
|
158
187
|
description: "Live kernel or serial log follow on the same Observability subscribe feed LogViewer uses. Users only on a reserved machine. Slack and MCP attach for duration (default 20s). Not logs.latest.",
|
|
159
188
|
mutation: "read",
|
|
160
189
|
http: { method: "GET", path: "/v1/observability/nodes/{id}/logs/{stream}/subscribe" },
|
|
@@ -181,6 +210,7 @@ export const logsTailCapability = defineCapability({
|
|
|
181
210
|
},
|
|
182
211
|
});
|
|
183
212
|
export const observabilityCapabilities = [
|
|
213
|
+
telemetrySnapshotCapability,
|
|
184
214
|
telemetryLatestCapability,
|
|
185
215
|
telemetryTailCapability,
|
|
186
216
|
logsLatestCapability,
|
|
@@ -2,14 +2,13 @@ import { z } from "zod";
|
|
|
2
2
|
import { defineCapability, preview } from "../capability.js";
|
|
3
3
|
const optionalText = z.string().nullish();
|
|
4
4
|
const tokenCreateInput = z.object({
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
organization_id: z.string().min(1),
|
|
6
|
+
organizationId: optionalText,
|
|
7
7
|
facility_id: optionalText,
|
|
8
8
|
facilityId: optionalText,
|
|
9
9
|
expires_in_seconds: z.number().int().positive().nullish(),
|
|
10
10
|
expiresInSeconds: z.number().int().positive().nullish(),
|
|
11
|
-
|
|
12
|
-
nodeMode: z.enum(["bare-metal", "virtual-machine", "vm"]).nullish(),
|
|
11
|
+
filter: z.string().nullish(),
|
|
13
12
|
ip: optionalText,
|
|
14
13
|
installer_base: optionalText,
|
|
15
14
|
host: optionalText,
|
|
@@ -24,6 +23,10 @@ const emptyInput = z.object({});
|
|
|
24
23
|
export function buildEnrollmentInstallCommand(options) {
|
|
25
24
|
const base = (options.base ?? "https://api.ornn.com/v1/orchestrator").replace(/\/+$/, "");
|
|
26
25
|
const params = [`token=${encodeURIComponent(options.token)}`];
|
|
26
|
+
if (options.facilityId)
|
|
27
|
+
params.push(`facility_id=${encodeURIComponent(options.facilityId)}`);
|
|
28
|
+
if (options.filter)
|
|
29
|
+
params.push(`filter=${encodeURIComponent(options.filter)}`);
|
|
27
30
|
if (options.ip)
|
|
28
31
|
params.push(`ip=${encodeURIComponent(options.ip)}`);
|
|
29
32
|
if (options.restoreNodeId)
|
|
@@ -31,7 +34,10 @@ export function buildEnrollmentInstallCommand(options) {
|
|
|
31
34
|
if (options.force)
|
|
32
35
|
params.push("force=true");
|
|
33
36
|
const href = `${base}/enrollment/install?${params.join("&")}`;
|
|
34
|
-
|
|
37
|
+
// Without pipefail, a failed curl (bad installer_base, network error, non-2xx
|
|
38
|
+
// response) pipes empty stdin into `sudo bash`, which then exits 0 having run
|
|
39
|
+
// nothing -- the caller sees exit_code: 0 for an install that never happened.
|
|
40
|
+
return `set -o pipefail; curl -fsSL '${href.replace(/'/g, "'\\''")}' | sudo bash`;
|
|
35
41
|
}
|
|
36
42
|
function tokenString(row, keys) {
|
|
37
43
|
for (const key of keys) {
|
|
@@ -44,8 +50,8 @@ function tokenString(row, keys) {
|
|
|
44
50
|
export const tokensListCapability = defineCapability({
|
|
45
51
|
id: "tokens.list",
|
|
46
52
|
domain: "fleet",
|
|
47
|
-
roles: ["
|
|
48
|
-
description: "List issued enrollment tokens (id,
|
|
53
|
+
roles: ["admin"],
|
|
54
|
+
description: "List issued enrollment tokens (id, supplier organization_id, status, expiry). Does not return plaintext secrets.",
|
|
49
55
|
mutation: "read",
|
|
50
56
|
http: { method: "GET", path: "/enrollment-tokens" },
|
|
51
57
|
slack: { parameters: { type: "object", properties: {} } },
|
|
@@ -63,10 +69,16 @@ export const tokensCreateCapability = defineCapability({
|
|
|
63
69
|
parameters: {
|
|
64
70
|
type: "object",
|
|
65
71
|
properties: {
|
|
66
|
-
|
|
72
|
+
organization_id: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Supplier organization UUID from supplier.list.",
|
|
75
|
+
},
|
|
67
76
|
facility_id: { type: "string", description: "Optional facility UUID." },
|
|
68
77
|
expires_in_seconds: { type: "integer", description: "Token TTL in seconds." },
|
|
69
|
-
|
|
78
|
+
filter: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Optional comma-separated enrollment compartment ids. Omit to run every step.",
|
|
81
|
+
},
|
|
70
82
|
ip: { type: "string", description: "Optional IP stamped into the installer URL." },
|
|
71
83
|
host: { type: "string", description: "SSH host/IP to enroll now." },
|
|
72
84
|
hosts: {
|
|
@@ -76,26 +88,28 @@ export const tokensCreateCapability = defineCapability({
|
|
|
76
88
|
},
|
|
77
89
|
username: { type: "string", description: "SSH user. Default admin." },
|
|
78
90
|
port: { type: "integer", description: "SSH port. Default 22." },
|
|
79
|
-
private_key: {
|
|
91
|
+
private_key: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "PEM private key. Required to enroll over SSH.",
|
|
94
|
+
},
|
|
80
95
|
},
|
|
81
|
-
required: ["
|
|
96
|
+
required: ["organization_id"],
|
|
82
97
|
},
|
|
83
98
|
},
|
|
84
99
|
input: tokenCreateInput,
|
|
85
100
|
execute: async (ctx, input) => {
|
|
86
101
|
const parsed = tokenCreateInput.parse(input);
|
|
87
|
-
const
|
|
102
|
+
const organizationId = (parsed.organization_id || parsed.organizationId || "").trim();
|
|
88
103
|
const hosts = [
|
|
89
104
|
...(parsed.host?.trim() ? [parsed.host.trim()] : []),
|
|
90
105
|
...(parsed.hosts ?? []).map((host) => host.trim()).filter(Boolean),
|
|
91
106
|
];
|
|
92
107
|
const privateKey = parsed.private_key?.trim() || parsed.privateKey?.trim() || "";
|
|
93
|
-
const nodeMode = parsed.node_mode ?? parsed.nodeMode;
|
|
94
108
|
const payload = {
|
|
95
|
-
|
|
109
|
+
organization_id: organizationId,
|
|
96
110
|
facility_id: parsed.facility_id ?? parsed.facilityId,
|
|
97
111
|
expires_in_seconds: parsed.expires_in_seconds ?? parsed.expiresInSeconds,
|
|
98
|
-
|
|
112
|
+
filter: parsed.filter,
|
|
99
113
|
ip: parsed.ip,
|
|
100
114
|
hosts,
|
|
101
115
|
username: parsed.username?.trim() || "admin",
|
|
@@ -108,7 +122,7 @@ export const tokensCreateCapability = defineCapability({
|
|
|
108
122
|
const minted = (await ctx.fetch("/enrollment-tokens", {
|
|
109
123
|
method: "POST",
|
|
110
124
|
body: {
|
|
111
|
-
|
|
125
|
+
organization_id: organizationId,
|
|
112
126
|
...(payload.facility_id ? { facility_id: payload.facility_id } : {}),
|
|
113
127
|
...(payload.expires_in_seconds ? { expires_in_seconds: payload.expires_in_seconds } : {}),
|
|
114
128
|
},
|
|
@@ -119,6 +133,7 @@ export const tokensCreateCapability = defineCapability({
|
|
|
119
133
|
token,
|
|
120
134
|
base: parsed.installer_base ?? undefined,
|
|
121
135
|
ip: parsed.ip ?? undefined,
|
|
136
|
+
filter: parsed.filter ?? undefined,
|
|
122
137
|
force: payload.remote,
|
|
123
138
|
})
|
|
124
139
|
: null;
|
|
@@ -141,7 +156,7 @@ export const tokensCreateCapability = defineCapability({
|
|
|
141
156
|
}
|
|
142
157
|
return {
|
|
143
158
|
id: minted.id,
|
|
144
|
-
|
|
159
|
+
organization_id: minted.organization_id ?? organizationId,
|
|
145
160
|
expires_at: minted.expires_at,
|
|
146
161
|
hosts: results,
|
|
147
162
|
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineCapability, } from "../capability.js";
|
|
3
|
+
const storageCliInput = z
|
|
4
|
+
.object({
|
|
5
|
+
$positionals: z.array(z.string()).optional(),
|
|
6
|
+
contentType: z.string().optional(),
|
|
7
|
+
cursor: z.string().optional(),
|
|
8
|
+
idempotencyKey: z.string().optional(),
|
|
9
|
+
limit: z.union([z.string(), z.number()]).optional(),
|
|
10
|
+
name: z.string().optional(),
|
|
11
|
+
output: z.string().optional(),
|
|
12
|
+
overwrite: z.boolean().optional(),
|
|
13
|
+
path: z.string().optional(),
|
|
14
|
+
prefix: z.string().optional(),
|
|
15
|
+
source: z.string().optional(),
|
|
16
|
+
yes: z.boolean().optional(),
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
function storageExecutor(context) {
|
|
20
|
+
const interaction = context.cli?.interaction;
|
|
21
|
+
if (!interaction?.storage) {
|
|
22
|
+
throw new Error("This capability must execute through the Ornn CLI storage runtime.");
|
|
23
|
+
}
|
|
24
|
+
return interaction.storage.bind(interaction);
|
|
25
|
+
}
|
|
26
|
+
function storageCapability({ id, command, description, mutation, outcome, human, automation, acceptedReferences = [], review, related = [], }) {
|
|
27
|
+
return defineCapability({
|
|
28
|
+
id,
|
|
29
|
+
domain: "storage",
|
|
30
|
+
roles: ["user"],
|
|
31
|
+
description,
|
|
32
|
+
mutation,
|
|
33
|
+
input: storageCliInput,
|
|
34
|
+
cli: {
|
|
35
|
+
command,
|
|
36
|
+
vnext: {
|
|
37
|
+
arguments: "semantic",
|
|
38
|
+
audience: "customer",
|
|
39
|
+
category: "resource",
|
|
40
|
+
outcome,
|
|
41
|
+
human,
|
|
42
|
+
automation,
|
|
43
|
+
acceptedReferences,
|
|
44
|
+
...(review ? { review } : {}),
|
|
45
|
+
related,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
mcp: false,
|
|
49
|
+
slack: false,
|
|
50
|
+
execute: async (context, input) => storageExecutor(context)(id, input),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export const volumeCapabilities = [
|
|
54
|
+
storageCapability({
|
|
55
|
+
id: "storage.volumes.list",
|
|
56
|
+
command: ["volumes", "list"],
|
|
57
|
+
description: "List managed volumes in one bounded page.",
|
|
58
|
+
mutation: "read",
|
|
59
|
+
outcome: "List durable managed volumes independently from their mounts.",
|
|
60
|
+
human: "ornn volumes list [--limit <1-100>] [--cursor <cursor>]",
|
|
61
|
+
automation: "ornn volumes list --limit 50 --format json",
|
|
62
|
+
related: ["volumes show", "volumes create"],
|
|
63
|
+
}),
|
|
64
|
+
storageCapability({
|
|
65
|
+
id: "storage.volumes.show",
|
|
66
|
+
command: ["volumes", "show"],
|
|
67
|
+
description: "Inspect one managed volume by canonical ID or exact unique name.",
|
|
68
|
+
mutation: "read",
|
|
69
|
+
outcome: "Inspect one durable volume without changing its lifecycle.",
|
|
70
|
+
human: "ornn volumes show <volume>",
|
|
71
|
+
automation: "ornn volumes show <volume-id> --format json",
|
|
72
|
+
acceptedReferences: ["canonical volume ID", "exact unique volume name"],
|
|
73
|
+
related: ["volumes files list", "volume-mounts show"],
|
|
74
|
+
}),
|
|
75
|
+
storageCapability({
|
|
76
|
+
id: "storage.volumes.create",
|
|
77
|
+
command: ["volumes", "create"],
|
|
78
|
+
description: "Create or rediscover a uniquely named managed volume.",
|
|
79
|
+
mutation: "preview-confirm",
|
|
80
|
+
outcome: "Create one durable volume or safely replay its normalized intent.",
|
|
81
|
+
human: "ornn volumes create --name <name> [--source <volume>] [--yes]",
|
|
82
|
+
automation: "ornn volumes create --name <name> [--source <volume-id>] --yes --idempotency-key <key> --format json",
|
|
83
|
+
acceptedReferences: ["canonical source volume ID", "exact unique source volume name"],
|
|
84
|
+
related: ["volumes show", "volumes files upload"],
|
|
85
|
+
}),
|
|
86
|
+
storageCapability({
|
|
87
|
+
id: "storage.volumes.refresh",
|
|
88
|
+
command: ["volumes", "refresh"],
|
|
89
|
+
description: "Refresh the owner-backed view of one managed volume.",
|
|
90
|
+
mutation: "read",
|
|
91
|
+
outcome: "Refresh and inspect the current durable volume state.",
|
|
92
|
+
human: "ornn volumes refresh <volume>",
|
|
93
|
+
automation: "ornn volumes refresh <volume-id> --format json",
|
|
94
|
+
acceptedReferences: ["canonical volume ID", "exact unique volume name"],
|
|
95
|
+
related: ["volumes show"],
|
|
96
|
+
}),
|
|
97
|
+
storageCapability({
|
|
98
|
+
id: "storage.volumes.clear",
|
|
99
|
+
command: ["volumes", "clear"],
|
|
100
|
+
description: "Erase every file in one volume after a live default-no confirmation.",
|
|
101
|
+
mutation: "preview-confirm",
|
|
102
|
+
outcome: "Permanently clear one volume only from a live interactive terminal.",
|
|
103
|
+
human: "ornn volumes clear <volume>",
|
|
104
|
+
automation: "Unsupported: this command requires a live interactive terminal.",
|
|
105
|
+
acceptedReferences: ["canonical volume ID", "exact unique volume name"],
|
|
106
|
+
review: "Every file and folder in the volume is permanently deleted.",
|
|
107
|
+
related: ["volumes files list", "volume-mounts detach"],
|
|
108
|
+
}),
|
|
109
|
+
storageCapability({
|
|
110
|
+
id: "storage.volumes.delete",
|
|
111
|
+
command: ["volumes", "delete"],
|
|
112
|
+
description: "Delete an explicitly selected, relationship-free managed volume.",
|
|
113
|
+
mutation: "preview-confirm",
|
|
114
|
+
outcome: "Delete one detached volume after explicit consent.",
|
|
115
|
+
human: "ornn volumes delete <volume>",
|
|
116
|
+
automation: "ornn volumes delete <volume-id> --yes --idempotency-key <key> --format json",
|
|
117
|
+
acceptedReferences: ["canonical volume ID", "exact unique volume name"],
|
|
118
|
+
review: "The volume and all of its data are permanently deleted.",
|
|
119
|
+
related: ["volume-mounts detach", "volumes show"],
|
|
120
|
+
}),
|
|
121
|
+
storageCapability({
|
|
122
|
+
id: "storage.volumes.files.list",
|
|
123
|
+
command: ["volumes", "files", "list"],
|
|
124
|
+
description: "List nested files in one volume in a bounded page.",
|
|
125
|
+
mutation: "read",
|
|
126
|
+
outcome: "List nested volume files by semantic remote path.",
|
|
127
|
+
human: "ornn volumes files list <volume> [--prefix <remote-path>] [--limit <1-500>] [--cursor <cursor>]",
|
|
128
|
+
automation: "ornn volumes files list <volume-id> --limit 50 --format json",
|
|
129
|
+
acceptedReferences: ["canonical volume ID", "exact unique volume name"],
|
|
130
|
+
related: ["volumes files upload", "volumes files download"],
|
|
131
|
+
}),
|
|
132
|
+
storageCapability({
|
|
133
|
+
id: "storage.volumes.files.upload",
|
|
134
|
+
command: ["volumes", "files", "upload"],
|
|
135
|
+
description: "Stage, upload, and finalize one local file at a nested volume path.",
|
|
136
|
+
mutation: "preview-confirm",
|
|
137
|
+
outcome: "Upload one file through a reconciled staged transfer.",
|
|
138
|
+
human: "ornn volumes files upload <volume> <local-file> [--path <remote-path>] [--content-type <type>] [--yes]",
|
|
139
|
+
automation: "ornn volumes files upload <volume-id> <local-file> --path <remote-path> --yes --idempotency-key <key> --format json",
|
|
140
|
+
acceptedReferences: ["canonical volume ID", "exact unique volume name"],
|
|
141
|
+
related: ["volumes files list", "volumes refresh"],
|
|
142
|
+
}),
|
|
143
|
+
storageCapability({
|
|
144
|
+
id: "storage.volumes.files.download",
|
|
145
|
+
command: ["volumes", "files", "download"],
|
|
146
|
+
description: "Download one nested volume file to an explicit local path atomically.",
|
|
147
|
+
mutation: "local",
|
|
148
|
+
outcome: "Download one file without partially replacing local data.",
|
|
149
|
+
human: "ornn volumes files download <volume> <remote-path> --output <local-path> [--overwrite]",
|
|
150
|
+
automation: "ornn volumes files download <volume-id> <remote-path> --output <local-path> --format json",
|
|
151
|
+
acceptedReferences: ["canonical volume ID", "exact unique volume name"],
|
|
152
|
+
related: ["volumes files list"],
|
|
153
|
+
}),
|
|
154
|
+
storageCapability({
|
|
155
|
+
id: "storage.volumes.files.delete",
|
|
156
|
+
command: ["volumes", "files", "delete"],
|
|
157
|
+
description: "Delete one nested file after a live default-no confirmation.",
|
|
158
|
+
mutation: "preview-confirm",
|
|
159
|
+
outcome: "Permanently delete one file only from a live interactive terminal.",
|
|
160
|
+
human: "ornn volumes files delete <volume> <remote-path>",
|
|
161
|
+
automation: "Unsupported: this command requires a live interactive terminal.",
|
|
162
|
+
acceptedReferences: ["canonical volume ID", "exact unique volume name"],
|
|
163
|
+
review: "The selected file is permanently deleted.",
|
|
164
|
+
related: ["volumes files list"],
|
|
165
|
+
}),
|
|
166
|
+
storageCapability({
|
|
167
|
+
id: "storage.volumes.folders.delete",
|
|
168
|
+
command: ["volumes", "folders", "delete"],
|
|
169
|
+
description: "Delete one nested folder after a live default-no confirmation.",
|
|
170
|
+
mutation: "preview-confirm",
|
|
171
|
+
outcome: "Permanently delete one folder tree only from a live interactive terminal.",
|
|
172
|
+
human: "ornn volumes folders delete <volume> <remote-path>",
|
|
173
|
+
automation: "Unsupported: this command requires a live interactive terminal.",
|
|
174
|
+
acceptedReferences: ["canonical volume ID", "exact unique volume name"],
|
|
175
|
+
review: "The selected folder and every nested file are permanently deleted.",
|
|
176
|
+
related: ["volumes files list"],
|
|
177
|
+
}),
|
|
178
|
+
];
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { namesFromId } from "./names.js";
|
|
2
2
|
export const DOMAINS = [
|
|
3
|
+
"cli",
|
|
3
4
|
"identity",
|
|
4
5
|
"listings",
|
|
5
6
|
"fleet",
|
|
6
7
|
"users",
|
|
7
8
|
"reservations",
|
|
8
|
-
"
|
|
9
|
+
"markets",
|
|
9
10
|
"access",
|
|
10
11
|
"clusters",
|
|
11
12
|
"slurm",
|
|
@@ -14,6 +15,7 @@ export const DOMAINS = [
|
|
|
14
15
|
"storage",
|
|
15
16
|
"vpn",
|
|
16
17
|
"billing",
|
|
18
|
+
"downtime",
|
|
17
19
|
"observability",
|
|
18
20
|
"gtm",
|
|
19
21
|
];
|
|
@@ -29,19 +31,26 @@ export const DOMAINS = [
|
|
|
29
31
|
export function preview(action, payload) {
|
|
30
32
|
return { status: "preview", requiresConfirm: true, confirmed: false, action, payload };
|
|
31
33
|
}
|
|
32
|
-
/**
|
|
34
|
+
/** CLI is opt-in; MCP and Slack retain derived names unless explicitly unbound. */
|
|
33
35
|
export function defineCapability(spec) {
|
|
34
36
|
const names = namesFromId(spec.id);
|
|
35
|
-
const { slack, ...rest } = spec;
|
|
37
|
+
const { cli, mcp, slack, ...rest } = spec;
|
|
36
38
|
return {
|
|
37
39
|
...rest,
|
|
38
|
-
cli
|
|
39
|
-
|
|
40
|
+
...(cli !== false && cli !== undefined
|
|
41
|
+
? {
|
|
42
|
+
cli: {
|
|
43
|
+
command: cli.command ?? names.cli,
|
|
44
|
+
...(cli.vnext ? { vnext: cli.vnext } : {}),
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
: {}),
|
|
48
|
+
...(mcp === false ? {} : { mcp: { name: mcp?.name ?? names.mcp } }),
|
|
40
49
|
...(slack === false
|
|
41
50
|
? {}
|
|
42
51
|
: {
|
|
43
52
|
slack: {
|
|
44
|
-
name: names.slack,
|
|
53
|
+
name: slack?.name ?? names.slack,
|
|
45
54
|
...(slack?.parameters ? { parameters: slack.parameters } : {}),
|
|
46
55
|
},
|
|
47
56
|
}),
|
|
@@ -61,19 +70,17 @@ export function defineCapability(spec) {
|
|
|
61
70
|
export function defineStaffMcpCapability(spec) {
|
|
62
71
|
return { ...spec, mcp: { name: namesFromId(spec.id).mcp } };
|
|
63
72
|
}
|
|
64
|
-
export function slackStaffVisible(capability) {
|
|
65
|
-
return capability.roles.some((role) => role === "reviewer" || role === "admin");
|
|
66
|
-
}
|
|
67
73
|
export function visibleToClient(capability, client, role) {
|
|
68
74
|
if (!capability.roles.includes(role))
|
|
69
75
|
return false;
|
|
70
|
-
if (client === "slack")
|
|
71
|
-
|
|
76
|
+
if (client === "slack")
|
|
77
|
+
return capability.slack != null;
|
|
78
|
+
if (client === "cli") {
|
|
79
|
+
const help = capability.cli?.vnext;
|
|
80
|
+
if (!help || !capability.cli)
|
|
72
81
|
return false;
|
|
73
|
-
return
|
|
82
|
+
return help.audience === "customer" ? role === "user" : role === "admin";
|
|
74
83
|
}
|
|
75
|
-
if (client === "cli")
|
|
76
|
-
return capability.cli != null;
|
|
77
84
|
return capability.mcp != null;
|
|
78
85
|
}
|
|
79
86
|
export function forRole(catalog, role, client) {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { adminCapabilities, bindAdminCapabilities } from "./capabilities/admin.js";
|
|
2
|
+
import { adminCapacityReservationCapabilities } from "./capabilities/admin-capacity-reservations.js";
|
|
3
|
+
import { commerceMarketCapabilities } from "./capabilities/commerce-market.js";
|
|
4
|
+
import { marketCapabilities } from "./capabilities/markets.js";
|
|
5
|
+
import { adminEnrollmentCapabilities, bindAdminEnrollmentCapabilities, } from "./capabilities/admin-enrollment.js";
|
|
6
|
+
import { adminNodeDiagnosticCapabilities } from "./capabilities/admin-node-diagnostics.js";
|
|
7
|
+
import { adminNodeLifecycleCapabilities } from "./capabilities/admin-nodes-lifecycle.js";
|
|
8
|
+
import { computeCapabilities } from "./capabilities/compute.js";
|
|
9
|
+
import { cliShellCapabilities } from "./capabilities/cli.js";
|
|
10
|
+
import { clusterCliCapabilities, kubernetesCapabilities, slurmCapabilities, } from "./capabilities/clusters.js";
|
|
11
|
+
import { externalBucketCapabilities } from "./capabilities/external-buckets.js";
|
|
12
|
+
import { evidenceCapabilities } from "./capabilities/evidence.js";
|
|
13
|
+
import { fleetCapabilities } from "./capabilities/fleet.js";
|
|
14
|
+
import { tokenCapabilities } from "./capabilities/tokens.js";
|
|
15
|
+
import { mountCapabilities } from "./capabilities/mounts.js";
|
|
16
|
+
import { identityCapabilities } from "./capabilities/identity.js";
|
|
17
|
+
import { listingCapabilities } from "./capabilities/listings.js";
|
|
18
|
+
import { observabilityCapabilities } from "./capabilities/observability.js";
|
|
19
|
+
import { networkCapabilities } from "./capabilities/networks.js";
|
|
20
|
+
import { volumeCapabilities } from "./capabilities/volumes.js";
|
|
21
|
+
import { userCapabilities } from "./capabilities/users.js";
|
|
22
|
+
function canonicalCliCatalog(capabilities) {
|
|
23
|
+
return capabilities.map((capability) => {
|
|
24
|
+
const help = capability.cli?.vnext;
|
|
25
|
+
if (!help) {
|
|
26
|
+
const { cli: _retiredCliBinding, ...withoutCli } = capability;
|
|
27
|
+
return withoutCli;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
...capability,
|
|
31
|
+
cli: {
|
|
32
|
+
command: [...(help.command ?? capability.cli?.command ?? [])],
|
|
33
|
+
vnext: help,
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** Canonical operational catalog, without staff-only commerce or CRM modules. */
|
|
39
|
+
export const catalog = [
|
|
40
|
+
...canonicalCliCatalog([
|
|
41
|
+
...bindAdminEnrollmentCapabilities(bindAdminCapabilities([
|
|
42
|
+
...cliShellCapabilities,
|
|
43
|
+
...identityCapabilities,
|
|
44
|
+
...listingCapabilities,
|
|
45
|
+
...commerceMarketCapabilities,
|
|
46
|
+
...marketCapabilities,
|
|
47
|
+
...computeCapabilities,
|
|
48
|
+
...fleetCapabilities.filter((capability) => capability.id !== "node.exec"),
|
|
49
|
+
...networkCapabilities,
|
|
50
|
+
...clusterCliCapabilities,
|
|
51
|
+
...tokenCapabilities,
|
|
52
|
+
...userCapabilities,
|
|
53
|
+
...volumeCapabilities,
|
|
54
|
+
...mountCapabilities,
|
|
55
|
+
...externalBucketCapabilities,
|
|
56
|
+
...evidenceCapabilities,
|
|
57
|
+
...observabilityCapabilities,
|
|
58
|
+
...slurmCapabilities,
|
|
59
|
+
...kubernetesCapabilities,
|
|
60
|
+
...adminNodeLifecycleCapabilities,
|
|
61
|
+
])),
|
|
62
|
+
...adminCapabilities.filter((capability) => capability.id === "nodes.health"),
|
|
63
|
+
...adminCapacityReservationCapabilities.filter((capability) => capability.id !== "listings.create"),
|
|
64
|
+
...adminNodeDiagnosticCapabilities,
|
|
65
|
+
...adminEnrollmentCapabilities,
|
|
66
|
+
]),
|
|
67
|
+
];
|