@ornncompute/cli 1.0.107 → 1.0.110
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 +5 -1
- package/package.json +1 -1
- package/src/catalog-shell.mjs +16 -0
- package/src/ornn-logo.txt +8 -0
- package/src/registry-commands.mjs +96 -0
- package/vendor/capabilities/cli.js +390 -57
package/README.md
CHANGED
|
@@ -78,6 +78,10 @@ market books | book | trades | bid | orders | cancel
|
|
|
78
78
|
offers list | post | accept | cancel
|
|
79
79
|
machines list | show | wait | switch | reboot | reset | metrics | logs
|
|
80
80
|
clusters list | show | wait | eligible-machines | add-node | remove-node | teardown | metrics | logs
|
|
81
|
+
registry login | show | usage
|
|
82
|
+
registry repositories list
|
|
83
|
+
registry tags list | delete
|
|
84
|
+
registry copies list
|
|
81
85
|
ssh-keys list | add | delete
|
|
82
86
|
ssh-keys assignments list | assign | sync | status
|
|
83
87
|
private-networks list | show | create | update | delete
|
|
@@ -110,7 +114,7 @@ reservations, schedule and spot alike; `reservations buy` books one window of a
|
|
|
110
114
|
relisted (secondary) listing term with Markets `POST /v1/markets/bookings`
|
|
111
115
|
(`{legs: [...]}`), at the term price `terms` shows; it becomes a reservation
|
|
112
116
|
when the window starts. `market` and `offers` call Markets (`/v1/markets`):
|
|
113
|
-
buy `--nodes` at market (Markets also takes
|
|
117
|
+
buy `--nodes` at market (Markets also takes limit bids; the
|
|
114
118
|
CLI does not send them yet), end a fill with
|
|
115
119
|
`market end` (Commerce `DELETE /commerce/reservations/{id}`), post an offer
|
|
116
120
|
on a whole primary listing for one of its terms (`--listing-term-id`, optional
|
package/package.json
CHANGED
package/src/catalog-shell.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
CliApiError,
|
|
3
5
|
cliRequest,
|
|
@@ -35,6 +37,7 @@ import {
|
|
|
35
37
|
writeExternalBucketResultHuman,
|
|
36
38
|
} from "./external-bucket-commands.mjs";
|
|
37
39
|
import { executeEvidenceCommand, supplierOwnedRecord } from "./evidence-commands.mjs";
|
|
40
|
+
import { executeRegistryCommand } from "./registry-commands.mjs";
|
|
38
41
|
import {
|
|
39
42
|
executeKubernetesClusterCommand,
|
|
40
43
|
kubernetesConnectModeHandler,
|
|
@@ -78,6 +81,9 @@ const MACHINE_SCHEMA = "ornn.cli/v1";
|
|
|
78
81
|
const UPDATE_CHANNEL = "stable";
|
|
79
82
|
const FORMATS = { human: true, json: true, ndjson: true };
|
|
80
83
|
const SESSION_CAPABILITY_IDS = new Set(["identity.context", "identity.login", "identity.logout"]);
|
|
84
|
+
// Cloud Shell's welcome prints this file from the installed package.
|
|
85
|
+
const LOGO_URL = new URL("./ornn-logo.txt", import.meta.url);
|
|
86
|
+
const LOGO_COLUMNS = 72;
|
|
81
87
|
export const CLI_EXIT_STATUS = Object.freeze({
|
|
82
88
|
success: 0,
|
|
83
89
|
failure: 1,
|
|
@@ -584,6 +590,9 @@ function writeRootHuman(stdout, catalog) {
|
|
|
584
590
|
}
|
|
585
591
|
stdout.write("\n");
|
|
586
592
|
};
|
|
593
|
+
if (stdout.isTTY && stdout.columns >= LOGO_COLUMNS) {
|
|
594
|
+
stdout.write(`\n${readFileSync(LOGO_URL, "utf8")}\n`);
|
|
595
|
+
}
|
|
587
596
|
stdout.write("Ornn CLI\n\n");
|
|
588
597
|
stdout.write("Usage:\n ornn <command> [options]\n\n");
|
|
589
598
|
stdout.write("Available commands:\n\n");
|
|
@@ -950,6 +959,13 @@ async function executeCapability(
|
|
|
950
959
|
interaction: this,
|
|
951
960
|
});
|
|
952
961
|
},
|
|
962
|
+
registry(command, registryInput) {
|
|
963
|
+
return executeRegistryCommand(command, registryInput, {
|
|
964
|
+
fetch: (path, init) => customerCapabilityFetch(io, path, init, authSnapshot),
|
|
965
|
+
interaction: this,
|
|
966
|
+
spawnProcess: io.spawnProcess,
|
|
967
|
+
});
|
|
968
|
+
},
|
|
953
969
|
storage(command, storageInput) {
|
|
954
970
|
return executeStorageCommand(command, storageInput, {
|
|
955
971
|
env: io.env,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
▄██████▀▀▀▀▀▀▀▀ ▄▄█▀▀▀█▄▄
|
|
2
|
+
▀▀▀▀▀▀ ▄▄▄▄▄▄▄ ▄█▀ ▀█▄
|
|
3
|
+
████████ ▀▀▀▀▀▀ ██▀ ▀██ ▄▄▄ ▄▄ ▄▄▄ ▄▄▄ ▄▄▄ ▄▄▄▄
|
|
4
|
+
▄▄▄▄▄▄▄ ██ ██▄ ████▀▀▀ ███▄▀▀▀██▄ ██▄▀▀▀▀██
|
|
5
|
+
▀▀▀▀▀▀▀▀ ██ ██▀ ███ ███ ███ ██▀ ██
|
|
6
|
+
████████ ██▄ ▄██ ██▄ ███ ███ ██ ██
|
|
7
|
+
▀▀▀▀▀▀▀ ▀█▄ ▄██ ███ ███ ███ ██ ██
|
|
8
|
+
▄▄▄▄▄▄▄███████▀ ▀██▄▄▄██▀▀ ███ ███ ███ ██▄ ███
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { spawn as spawnProcessDefault } from "node:child_process";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
|
|
4
|
+
/** One key per machine: a new login revokes the key this machine's last one made. */
|
|
5
|
+
const KEY_NAME_PREFIX = "Docker login on ";
|
|
6
|
+
|
|
7
|
+
export async function executeRegistryCommand(command, input, runtime) {
|
|
8
|
+
if (command !== "registry.login") {
|
|
9
|
+
runtime.interaction.error("Unknown registry command.", { code: "unknown_command" });
|
|
10
|
+
}
|
|
11
|
+
return login(runtime);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function login({
|
|
15
|
+
fetch,
|
|
16
|
+
interaction,
|
|
17
|
+
spawnProcess = spawnProcessDefault,
|
|
18
|
+
hostname = os.hostname(),
|
|
19
|
+
}) {
|
|
20
|
+
const registry = await fetch("/v1/orchestrator/registry");
|
|
21
|
+
const session = await fetch("/v1/cli/session");
|
|
22
|
+
const organizationId = (session?.selectedOrganization ?? session?.selected_organization)?.id;
|
|
23
|
+
if (typeof organizationId !== "string" || !organizationId) {
|
|
24
|
+
interaction.usage("Join an organization before logging in to its registry.", {
|
|
25
|
+
code: "organization_required",
|
|
26
|
+
field: "organization",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const keysPath = `/v1/organizations/${encodeURIComponent(organizationId)}/api-keys`;
|
|
30
|
+
const name = `${KEY_NAME_PREFIX}${hostname}`.slice(0, 100);
|
|
31
|
+
|
|
32
|
+
const existing = await fetch(keysPath);
|
|
33
|
+
for (const key of existing?.api_keys ?? []) {
|
|
34
|
+
if (key?.name === name && typeof key.id === "string") {
|
|
35
|
+
await fetch(`${keysPath}/${encodeURIComponent(key.id)}`, { method: "DELETE" });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const created = await fetch(keysPath, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
body: { name, access: "read_write" },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
let exit;
|
|
44
|
+
try {
|
|
45
|
+
exit = await dockerLogin(spawnProcess, registry.host, created.token);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
await fetch(`${keysPath}/${encodeURIComponent(created.id)}`, { method: "DELETE" });
|
|
48
|
+
const missing = error?.code === "ENOENT";
|
|
49
|
+
interaction.error(
|
|
50
|
+
missing
|
|
51
|
+
? "Docker is not installed, or `docker` is not on your PATH. Install Docker and run ornn registry login again."
|
|
52
|
+
: `Docker could not be started: ${error?.message ?? error}`,
|
|
53
|
+
{ code: missing ? "docker_not_found" : "docker_unavailable", exitCode: 6 }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (exit.code !== 0) {
|
|
57
|
+
await fetch(`${keysPath}/${encodeURIComponent(created.id)}`, { method: "DELETE" });
|
|
58
|
+
interaction.error(`docker login ${registry.host} failed; no key was kept.`, {
|
|
59
|
+
code: "docker_login_failed",
|
|
60
|
+
exitCode: 1,
|
|
61
|
+
retryable: true,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return interaction.result({
|
|
66
|
+
data: {
|
|
67
|
+
host: registry.host,
|
|
68
|
+
namespace: registry.namespace,
|
|
69
|
+
api_key: { id: created.id, name: created.name },
|
|
70
|
+
},
|
|
71
|
+
presentation: {
|
|
72
|
+
lines: [
|
|
73
|
+
`Logged in to ${registry.host}.`,
|
|
74
|
+
`Push images under ${registry.host}/${registry.namespace}/<name>:<tag>.`,
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** `docker login --password-stdin`: the key never reaches argv or the shell history. */
|
|
81
|
+
function dockerLogin(spawnProcess, host, token) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
let child;
|
|
84
|
+
try {
|
|
85
|
+
child = spawnProcess("docker", ["login", host, "--username", "ornn", "--password-stdin"], {
|
|
86
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
reject(error);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
child.once("error", reject);
|
|
93
|
+
child.once("exit", (code, signal) => resolve({ code: signal ? 1 : code, signal }));
|
|
94
|
+
child.stdin.end(token);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -6852,15 +6852,347 @@ var networkVerbCapabilities = [
|
|
|
6852
6852
|
})
|
|
6853
6853
|
];
|
|
6854
6854
|
|
|
6855
|
-
// ../../packages/capabilities/dist/capabilities/
|
|
6855
|
+
// ../../packages/capabilities/dist/capabilities/registry.js
|
|
6856
6856
|
import { z as z28 } from "zod";
|
|
6857
|
-
var
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6857
|
+
var REGISTRY_PATH = "/v1/orchestrator/registry";
|
|
6858
|
+
var globalInput2 = {
|
|
6859
|
+
$positionals: z28.array(z28.string()).default([]),
|
|
6860
|
+
yes: z28.boolean().optional()
|
|
6861
|
+
};
|
|
6862
|
+
var emptyInput4 = z28.object(globalInput2).strict();
|
|
6863
|
+
var repositoryInput = z28.object({
|
|
6864
|
+
...globalInput2,
|
|
6865
|
+
repository: z28.string().optional().describe("Repository name, with or without your organization's namespace.")
|
|
6866
|
+
}).strict();
|
|
6867
|
+
var tagInput = z28.object({
|
|
6868
|
+
...globalInput2,
|
|
6869
|
+
repository: z28.string().optional().describe("Repository name, with or without your organization's namespace."),
|
|
6870
|
+
tag: z28.string().optional().describe("Tag to delete, such as v1.")
|
|
6871
|
+
}).strict();
|
|
6872
|
+
function cliInteraction(context) {
|
|
6873
|
+
return context.cli?.interaction;
|
|
6874
|
+
}
|
|
6875
|
+
function qualifiedRepository(value, registry) {
|
|
6876
|
+
let name = value.trim();
|
|
6877
|
+
if (name.startsWith(`${registry.host}/`))
|
|
6878
|
+
name = name.slice(registry.host.length + 1);
|
|
6879
|
+
return name.startsWith(`${registry.namespace}/`) ? name : `${registry.namespace}/${name}`;
|
|
6880
|
+
}
|
|
6881
|
+
function shortName(repository, registry) {
|
|
6882
|
+
return repository.startsWith(`${registry.namespace}/`) ? repository.slice(registry.namespace.length + 1) : repository;
|
|
6883
|
+
}
|
|
6884
|
+
function formatBytes(bytes) {
|
|
6885
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
6886
|
+
let value = bytes;
|
|
6887
|
+
let unit = 0;
|
|
6888
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
6889
|
+
value /= 1024;
|
|
6890
|
+
unit += 1;
|
|
6891
|
+
}
|
|
6892
|
+
return `${value >= 10 || unit === 0 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
6893
|
+
}
|
|
6894
|
+
async function registryOf(context) {
|
|
6895
|
+
return await context.fetch(REGISTRY_PATH, { method: "GET" });
|
|
6896
|
+
}
|
|
6897
|
+
function repositoryArgument(input, interaction2) {
|
|
6898
|
+
const raw = input.repository ?? input.$positionals[0];
|
|
6899
|
+
if (!raw) {
|
|
6900
|
+
const message = "Name a repository, such as trainer.";
|
|
6901
|
+
if (interaction2)
|
|
6902
|
+
interaction2.usage(message, { code: "repository_required", field: "repository" });
|
|
6903
|
+
throw new Error(message);
|
|
6904
|
+
}
|
|
6905
|
+
const reference = raw.trim();
|
|
6906
|
+
const slash = reference.lastIndexOf("/");
|
|
6907
|
+
const colon = reference.lastIndexOf(":");
|
|
6908
|
+
if (colon > slash) {
|
|
6909
|
+
return { repository: reference.slice(0, colon), tag: input.tag ?? reference.slice(colon + 1) };
|
|
6910
|
+
}
|
|
6911
|
+
return { repository: reference, tag: input.tag ?? input.$positionals[1] };
|
|
6912
|
+
}
|
|
6913
|
+
var registryShowCapability = defineCapability({
|
|
6914
|
+
id: "registry.show",
|
|
6915
|
+
domain: "registry",
|
|
6916
|
+
roles: ["user"],
|
|
6917
|
+
description: "Show the organization's container registry: the host to docker login to and the namespace every image is pushed under.",
|
|
6918
|
+
mutation: "read",
|
|
6919
|
+
http: { method: "GET", path: REGISTRY_PATH },
|
|
6920
|
+
input: emptyInput4,
|
|
6921
|
+
cli: {
|
|
6922
|
+
command: ["registry", "show"],
|
|
6923
|
+
vnext: {
|
|
6924
|
+
audience: "customer",
|
|
6925
|
+
category: "resource",
|
|
6926
|
+
order: 600,
|
|
6927
|
+
outcome: "Show where your organization pushes container images.",
|
|
6928
|
+
human: "ornn registry show",
|
|
6929
|
+
automation: "ornn registry show --format json",
|
|
6930
|
+
related: ["registry login", "registry repositories list"]
|
|
6931
|
+
}
|
|
6932
|
+
},
|
|
6933
|
+
slack: false,
|
|
6934
|
+
execute: async (context) => {
|
|
6935
|
+
const registry = await registryOf(context);
|
|
6936
|
+
const interaction2 = cliInteraction(context);
|
|
6937
|
+
if (!interaction2)
|
|
6938
|
+
return registry;
|
|
6939
|
+
return interaction2.result({
|
|
6940
|
+
data: registry,
|
|
6941
|
+
presentation: {
|
|
6942
|
+
lines: [`Image prefix: ${registry.host}/${registry.namespace}`],
|
|
6943
|
+
next: "ornn registry login"
|
|
6944
|
+
}
|
|
6945
|
+
});
|
|
6946
|
+
}
|
|
6947
|
+
});
|
|
6948
|
+
var registryRepositoriesListCapability = defineCapability({
|
|
6949
|
+
id: "registry.repositories.list",
|
|
6950
|
+
domain: "registry",
|
|
6951
|
+
roles: ["user"],
|
|
6952
|
+
description: "List the repositories in the organization's container registry that have a tag.",
|
|
6953
|
+
mutation: "read",
|
|
6954
|
+
http: { method: "GET", path: `${REGISTRY_PATH}/repositories` },
|
|
6955
|
+
input: emptyInput4,
|
|
6956
|
+
cli: {
|
|
6957
|
+
command: ["registry", "repositories", "list"],
|
|
6958
|
+
vnext: {
|
|
6959
|
+
audience: "customer",
|
|
6960
|
+
category: "resource",
|
|
6961
|
+
order: 601,
|
|
6962
|
+
outcome: "List the repositories your organization has pushed.",
|
|
6963
|
+
human: "ornn registry repositories list",
|
|
6964
|
+
automation: "ornn registry repositories list --format json",
|
|
6965
|
+
related: ["registry tags list", "registry show"]
|
|
6966
|
+
}
|
|
6967
|
+
},
|
|
6968
|
+
slack: false,
|
|
6969
|
+
execute: async (context) => {
|
|
6970
|
+
const listed = await context.fetch(`${REGISTRY_PATH}/repositories`, { method: "GET" });
|
|
6971
|
+
const interaction2 = cliInteraction(context);
|
|
6972
|
+
if (!interaction2)
|
|
6973
|
+
return listed;
|
|
6974
|
+
const registry = await registryOf(context);
|
|
6975
|
+
return interaction2.result({
|
|
6976
|
+
data: listed,
|
|
6977
|
+
presentation: {
|
|
6978
|
+
lines: listed.repositories.length ? listed.repositories.map((repository) => `${shortName(repository.name, registry)} ${repository.tag_count} ${repository.tag_count === 1 ? "tag" : "tags"}`) : ["No images yet. Push one with docker push after ornn registry login."]
|
|
6979
|
+
}
|
|
6980
|
+
});
|
|
6981
|
+
}
|
|
6982
|
+
});
|
|
6983
|
+
var registryTagsListCapability = defineCapability({
|
|
6984
|
+
id: "registry.tags.list",
|
|
6985
|
+
domain: "registry",
|
|
6986
|
+
roles: ["user"],
|
|
6987
|
+
description: "List one repository's tags with the digest each names, the bytes the image occupies, and when it was built.",
|
|
6988
|
+
mutation: "read",
|
|
6989
|
+
http: { method: "GET", path: `${REGISTRY_PATH}/tags` },
|
|
6990
|
+
input: repositoryInput,
|
|
6991
|
+
cli: {
|
|
6992
|
+
command: ["registry", "tags", "list"],
|
|
6993
|
+
vnext: {
|
|
6994
|
+
arguments: "semantic",
|
|
6995
|
+
audience: "customer",
|
|
6996
|
+
category: "resource",
|
|
6997
|
+
order: 602,
|
|
6998
|
+
outcome: "List a repository's tags.",
|
|
6999
|
+
human: "ornn registry tags list <repository>",
|
|
7000
|
+
automation: "ornn registry tags list <repository> --format json",
|
|
7001
|
+
acceptedReferences: ["repository name", "<namespace>/<repository>"],
|
|
7002
|
+
related: ["registry tags delete", "registry copies list"]
|
|
7003
|
+
}
|
|
7004
|
+
},
|
|
7005
|
+
slack: false,
|
|
7006
|
+
execute: async (context, input) => {
|
|
7007
|
+
const interaction2 = cliInteraction(context);
|
|
7008
|
+
const registry = await registryOf(context);
|
|
7009
|
+
const repository = qualifiedRepository(repositoryArgument(input, interaction2).repository, registry);
|
|
7010
|
+
const listed = await context.fetch(`${REGISTRY_PATH}/tags?${new URLSearchParams({ repository })}`, { method: "GET" });
|
|
7011
|
+
if (!interaction2)
|
|
7012
|
+
return listed;
|
|
7013
|
+
return interaction2.result({
|
|
7014
|
+
data: listed,
|
|
7015
|
+
presentation: {
|
|
7016
|
+
lines: listed.tags.map((tag) => `${tag.tag} ${tag.digest.slice(0, 19)} ${formatBytes(tag.size_bytes)}${tag.created_at ? ` built ${tag.created_at}` : ""}`)
|
|
7017
|
+
}
|
|
7018
|
+
});
|
|
7019
|
+
}
|
|
7020
|
+
});
|
|
7021
|
+
var registryTagsDeleteCapability = defineCapability({
|
|
7022
|
+
id: "registry.tags.delete",
|
|
7023
|
+
domain: "registry",
|
|
7024
|
+
roles: ["user"],
|
|
7025
|
+
description: "Delete a tag from the organization's container registry. Pods and Slurm jobs that name it can no longer pull it; each cluster drops its copy within a minute, and the registry frees the space no other tag uses, pausing pushes for about a minute while it does.",
|
|
7026
|
+
mutation: "preview-confirm",
|
|
7027
|
+
http: { method: "DELETE", path: `${REGISTRY_PATH}/tags` },
|
|
7028
|
+
input: tagInput,
|
|
7029
|
+
cli: {
|
|
7030
|
+
command: ["registry", "tags", "delete"],
|
|
7031
|
+
vnext: {
|
|
7032
|
+
arguments: "semantic",
|
|
7033
|
+
audience: "customer",
|
|
7034
|
+
category: "resource",
|
|
7035
|
+
order: 603,
|
|
7036
|
+
outcome: "Delete one tag after review.",
|
|
7037
|
+
human: "ornn registry tags delete <repository>:<tag>",
|
|
7038
|
+
automation: "ornn registry tags delete <repository>:<tag> --yes --format json",
|
|
7039
|
+
acceptedReferences: ["<repository>:<tag>"],
|
|
7040
|
+
review: "Shows the tag before deleting it; --yes skips the review.",
|
|
7041
|
+
related: ["registry tags list"]
|
|
7042
|
+
}
|
|
7043
|
+
},
|
|
7044
|
+
slack: false,
|
|
7045
|
+
execute: async (context, input) => {
|
|
7046
|
+
const interaction2 = cliInteraction(context);
|
|
7047
|
+
const registry = await registryOf(context);
|
|
7048
|
+
const argument = repositoryArgument(input, interaction2);
|
|
7049
|
+
const repository = qualifiedRepository(argument.repository, registry);
|
|
7050
|
+
if (!argument.tag) {
|
|
7051
|
+
const message = "Name the tag to delete, such as trainer:v1.";
|
|
7052
|
+
if (interaction2)
|
|
7053
|
+
interaction2.usage(message, { code: "tag_required", field: "tag" });
|
|
7054
|
+
throw new Error(message);
|
|
7055
|
+
}
|
|
7056
|
+
const target = { repository, tag: argument.tag };
|
|
7057
|
+
if (interaction2) {
|
|
7058
|
+
if (!input.yes) {
|
|
7059
|
+
const approved = await interaction2.review({
|
|
7060
|
+
title: "Delete this image tag?",
|
|
7061
|
+
terms: [
|
|
7062
|
+
{ label: "Image", value: `${registry.host}/${repository}:${argument.tag}` },
|
|
7063
|
+
{
|
|
7064
|
+
label: "Effect",
|
|
7065
|
+
value: "Pulls of this tag stop working; clusters drop their copy within a minute."
|
|
7066
|
+
}
|
|
7067
|
+
],
|
|
7068
|
+
prompt: "Delete it?"
|
|
7069
|
+
});
|
|
7070
|
+
if (!approved) {
|
|
7071
|
+
return interaction2.result({
|
|
7072
|
+
data: target,
|
|
7073
|
+
outcome: { class: "cancelled", phase: "review" },
|
|
7074
|
+
presentation: { lines: ["Nothing was deleted."] }
|
|
7075
|
+
});
|
|
7076
|
+
}
|
|
7077
|
+
}
|
|
7078
|
+
} else if (!context.confirmed) {
|
|
7079
|
+
return preview("registry.tags.delete", target);
|
|
7080
|
+
}
|
|
7081
|
+
await context.fetch(`${REGISTRY_PATH}/tags?${new URLSearchParams(target)}`, {
|
|
7082
|
+
method: "DELETE"
|
|
7083
|
+
});
|
|
7084
|
+
const deleted = { deleted: true, ...target };
|
|
7085
|
+
if (!interaction2)
|
|
7086
|
+
return deleted;
|
|
7087
|
+
return interaction2.result({
|
|
7088
|
+
data: deleted,
|
|
7089
|
+
presentation: { lines: [`Deleted ${shortName(repository, registry)}:${argument.tag}.`] }
|
|
7090
|
+
});
|
|
7091
|
+
}
|
|
7092
|
+
});
|
|
7093
|
+
var registryUsageCapability = defineCapability({
|
|
7094
|
+
id: "registry.usage",
|
|
7095
|
+
domain: "registry",
|
|
7096
|
+
roles: ["user"],
|
|
7097
|
+
description: "Show the bytes the organization's tagged images occupy in its container registry, each layer counted once.",
|
|
7098
|
+
mutation: "read",
|
|
7099
|
+
http: { method: "GET", path: `${REGISTRY_PATH}/usage` },
|
|
7100
|
+
input: emptyInput4,
|
|
7101
|
+
cli: {
|
|
7102
|
+
command: ["registry", "usage"],
|
|
7103
|
+
vnext: {
|
|
7104
|
+
audience: "customer",
|
|
7105
|
+
category: "resource",
|
|
7106
|
+
order: 604,
|
|
7107
|
+
outcome: "Show how much registry storage your images use.",
|
|
7108
|
+
human: "ornn registry usage",
|
|
7109
|
+
automation: "ornn registry usage --format json",
|
|
7110
|
+
related: ["registry repositories list"]
|
|
7111
|
+
}
|
|
7112
|
+
},
|
|
7113
|
+
slack: false,
|
|
7114
|
+
execute: async (context) => {
|
|
7115
|
+
const usage = await context.fetch(`${REGISTRY_PATH}/usage`, { method: "GET" });
|
|
7116
|
+
const interaction2 = cliInteraction(context);
|
|
7117
|
+
if (!interaction2)
|
|
7118
|
+
return usage;
|
|
7119
|
+
return interaction2.result({
|
|
7120
|
+
data: usage,
|
|
7121
|
+
presentation: { lines: [`Storage used: ${formatBytes(usage.bytes)}`] }
|
|
7122
|
+
});
|
|
7123
|
+
}
|
|
7124
|
+
});
|
|
7125
|
+
var registryCopiesListCapability = defineCapability({
|
|
7126
|
+
id: "registry.copies.list",
|
|
7127
|
+
domain: "registry",
|
|
7128
|
+
roles: ["user"],
|
|
7129
|
+
description: "List, for each of the organization's clusters, the registry tags its own copy holds now and the digest it holds them at, so a push or delete can be followed until every cluster has it.",
|
|
7130
|
+
mutation: "read",
|
|
7131
|
+
http: { method: "GET", path: `${REGISTRY_PATH}/caches` },
|
|
7132
|
+
input: emptyInput4,
|
|
7133
|
+
cli: {
|
|
7134
|
+
command: ["registry", "copies", "list"],
|
|
7135
|
+
vnext: {
|
|
7136
|
+
audience: "customer",
|
|
7137
|
+
category: "resource",
|
|
7138
|
+
order: 605,
|
|
7139
|
+
outcome: "Show which tags each of your clusters already holds.",
|
|
7140
|
+
human: "ornn registry copies list",
|
|
7141
|
+
automation: "ornn registry copies list --format json",
|
|
7142
|
+
related: ["registry tags list"]
|
|
7143
|
+
}
|
|
7144
|
+
},
|
|
7145
|
+
slack: false,
|
|
7146
|
+
execute: async (context) => context.fetch(`${REGISTRY_PATH}/caches`, { method: "GET" })
|
|
7147
|
+
});
|
|
7148
|
+
var registryLoginCapability = defineCapability({
|
|
7149
|
+
id: "registry.login",
|
|
7150
|
+
domain: "registry",
|
|
7151
|
+
roles: ["user"],
|
|
7152
|
+
description: "Log Docker on this machine in to the organization's container registry, with a read-write API key the CLI creates for it.",
|
|
7153
|
+
mutation: "local",
|
|
7154
|
+
input: emptyInput4,
|
|
7155
|
+
cli: {
|
|
7156
|
+
command: ["registry", "login"],
|
|
7157
|
+
vnext: {
|
|
7158
|
+
audience: "customer",
|
|
7159
|
+
category: "resource",
|
|
7160
|
+
order: 599,
|
|
7161
|
+
outcome: "Log Docker in to your organization's registry.",
|
|
7162
|
+
human: "ornn registry login",
|
|
7163
|
+
automation: "ornn registry login --format json",
|
|
7164
|
+
related: ["registry show"]
|
|
7165
|
+
}
|
|
7166
|
+
},
|
|
7167
|
+
mcp: false,
|
|
7168
|
+
slack: false,
|
|
7169
|
+
execute: async (context, input) => {
|
|
7170
|
+
const interaction2 = context.cli?.interaction;
|
|
7171
|
+
if (!interaction2?.registry) {
|
|
7172
|
+
throw new Error("ornn registry login runs only in the Ornn CLI.");
|
|
7173
|
+
}
|
|
7174
|
+
return interaction2.registry.call(interaction2, "registry.login", input);
|
|
7175
|
+
}
|
|
7176
|
+
});
|
|
7177
|
+
var registryCapabilities = [
|
|
7178
|
+
registryLoginCapability,
|
|
7179
|
+
registryShowCapability,
|
|
7180
|
+
registryRepositoriesListCapability,
|
|
7181
|
+
registryTagsListCapability,
|
|
7182
|
+
registryTagsDeleteCapability,
|
|
7183
|
+
registryUsageCapability,
|
|
7184
|
+
registryCopiesListCapability
|
|
7185
|
+
];
|
|
7186
|
+
|
|
7187
|
+
// ../../packages/capabilities/dist/capabilities/reservation-list.js
|
|
7188
|
+
import { z as z29 } from "zod";
|
|
7189
|
+
var reservationListInput = z29.object({
|
|
7190
|
+
customerId: z29.string().uuid().optional(),
|
|
7191
|
+
listingId: z29.string().uuid().optional(),
|
|
7192
|
+
listingTermId: z29.string().uuid().optional(),
|
|
7193
|
+
status: z29.enum(["confirmed", "active", "completed", "cancelled"]).optional(),
|
|
7194
|
+
cursor: z29.string().optional(),
|
|
7195
|
+
limit: z29.number().int().min(1).max(100).optional()
|
|
6864
7196
|
});
|
|
6865
7197
|
var reservationListCapability = defineCapability({
|
|
6866
7198
|
id: "reservation.list",
|
|
@@ -6912,20 +7244,20 @@ var reservationListCapability = defineCapability({
|
|
|
6912
7244
|
});
|
|
6913
7245
|
|
|
6914
7246
|
// ../../packages/capabilities/dist/capabilities/volumes.js
|
|
6915
|
-
import { z as
|
|
6916
|
-
var storageCliInput =
|
|
6917
|
-
$positionals:
|
|
6918
|
-
contentType:
|
|
6919
|
-
cursor:
|
|
6920
|
-
idempotencyKey:
|
|
6921
|
-
limit:
|
|
6922
|
-
name:
|
|
6923
|
-
output:
|
|
6924
|
-
overwrite:
|
|
6925
|
-
path:
|
|
6926
|
-
prefix:
|
|
6927
|
-
source:
|
|
6928
|
-
yes:
|
|
7247
|
+
import { z as z30 } from "zod";
|
|
7248
|
+
var storageCliInput = z30.object({
|
|
7249
|
+
$positionals: z30.array(z30.string()).optional(),
|
|
7250
|
+
contentType: z30.string().optional(),
|
|
7251
|
+
cursor: z30.string().optional(),
|
|
7252
|
+
idempotencyKey: z30.string().optional(),
|
|
7253
|
+
limit: z30.union([z30.string(), z30.number()]).optional(),
|
|
7254
|
+
name: z30.string().optional(),
|
|
7255
|
+
output: z30.string().optional(),
|
|
7256
|
+
overwrite: z30.boolean().optional(),
|
|
7257
|
+
path: z30.string().optional(),
|
|
7258
|
+
prefix: z30.string().optional(),
|
|
7259
|
+
source: z30.string().optional(),
|
|
7260
|
+
yes: z30.boolean().optional()
|
|
6929
7261
|
}).strict();
|
|
6930
7262
|
function storageExecutor(context) {
|
|
6931
7263
|
const interaction2 = context.cli?.interaction;
|
|
@@ -7098,22 +7430,22 @@ function storageDrivePath(driveId, action) {
|
|
|
7098
7430
|
function reservationStorageRoute(reservationId, suffix) {
|
|
7099
7431
|
return `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(reservationId)}/${suffix}`;
|
|
7100
7432
|
}
|
|
7101
|
-
var reservationInput =
|
|
7102
|
-
var reservationWrite = reservationInput.extend({ confirm:
|
|
7103
|
-
var driveInput =
|
|
7104
|
-
var driveWrite = driveInput.extend({ confirm:
|
|
7105
|
-
var bucketConnectInput =
|
|
7106
|
-
provider:
|
|
7107
|
-
bucket:
|
|
7108
|
-
name:
|
|
7109
|
-
prefix:
|
|
7110
|
-
region:
|
|
7111
|
-
endpointUrl:
|
|
7112
|
-
accountId:
|
|
7113
|
-
accessKeyId:
|
|
7114
|
-
secretAccessKey:
|
|
7115
|
-
readOnly:
|
|
7116
|
-
confirm:
|
|
7433
|
+
var reservationInput = z30.object({ reservationId: z30.string() });
|
|
7434
|
+
var reservationWrite = reservationInput.extend({ confirm: z30.boolean().optional() });
|
|
7435
|
+
var driveInput = z30.object({ driveId: z30.string() });
|
|
7436
|
+
var driveWrite = driveInput.extend({ confirm: z30.boolean().optional() });
|
|
7437
|
+
var bucketConnectInput = z30.object({
|
|
7438
|
+
provider: z30.enum(["gcs", "s3", "r2"]),
|
|
7439
|
+
bucket: z30.string(),
|
|
7440
|
+
name: z30.string().optional(),
|
|
7441
|
+
prefix: z30.string().optional(),
|
|
7442
|
+
region: z30.string().optional(),
|
|
7443
|
+
endpointUrl: z30.string().optional(),
|
|
7444
|
+
accountId: z30.string().optional(),
|
|
7445
|
+
accessKeyId: z30.string().optional(),
|
|
7446
|
+
secretAccessKey: z30.string().optional(),
|
|
7447
|
+
readOnly: z30.boolean().optional(),
|
|
7448
|
+
confirm: z30.boolean().optional()
|
|
7117
7449
|
}).refine((input) => !(input.provider === "gcs" && (input.accessKeyId || input.secretAccessKey)), {
|
|
7118
7450
|
message: "Access key credentials are only supported for S3-compatible buckets."
|
|
7119
7451
|
}).refine((input) => Boolean(input.accessKeyId) === Boolean(input.secretAccessKey), {
|
|
@@ -7132,7 +7464,7 @@ var storageVerbCapabilities = [
|
|
|
7132
7464
|
method: "POST",
|
|
7133
7465
|
path: "/v1/workflows/compute/nodes/reservations/{reservationId}/storage-deployment"
|
|
7134
7466
|
},
|
|
7135
|
-
input: reservationWrite.extend({ driveId:
|
|
7467
|
+
input: reservationWrite.extend({ driveId: z30.string(), readOnly: z30.boolean().optional() }),
|
|
7136
7468
|
slack: false,
|
|
7137
7469
|
execute: (ctx, input) => ctx.fetch(reservationStorageRoute(input.reservationId, "storage-deployment"), {
|
|
7138
7470
|
method: "POST",
|
|
@@ -7182,8 +7514,8 @@ var storageVerbCapabilities = [
|
|
|
7182
7514
|
path: "/v1/workflows/compute/nodes/reservations/{reservationId}/filesystem-deployment"
|
|
7183
7515
|
},
|
|
7184
7516
|
input: reservationWrite.extend({
|
|
7185
|
-
performanceTier:
|
|
7186
|
-
capacityGib:
|
|
7517
|
+
performanceTier: z30.enum(["standard", "performance", "enterprise"]),
|
|
7518
|
+
capacityGib: z30.number().int().positive()
|
|
7187
7519
|
}),
|
|
7188
7520
|
slack: false,
|
|
7189
7521
|
execute: (ctx, input) => ctx.fetch(reservationStorageRoute(input.reservationId, "filesystem-deployment"), {
|
|
@@ -7214,7 +7546,7 @@ var storageVerbCapabilities = [
|
|
|
7214
7546
|
description: "List reservations that can receive a storage deploy, grouped the same way as the console picker.",
|
|
7215
7547
|
mutation: "read",
|
|
7216
7548
|
http: { method: "GET", path: "/v1/orchestrator/storage-targets" },
|
|
7217
|
-
input:
|
|
7549
|
+
input: z30.object({}),
|
|
7218
7550
|
slack: false,
|
|
7219
7551
|
execute: (ctx) => ctx.fetch("/v1/orchestrator/storage-targets", { method: "GET" })
|
|
7220
7552
|
}),
|
|
@@ -7225,9 +7557,9 @@ var storageVerbCapabilities = [
|
|
|
7225
7557
|
description: "List a page of storage volumes, newest updated first. Pass next_cursor as cursor to continue.",
|
|
7226
7558
|
mutation: "read",
|
|
7227
7559
|
http: { method: "GET", path: DRIVES_ROUTE },
|
|
7228
|
-
input:
|
|
7229
|
-
limit:
|
|
7230
|
-
cursor:
|
|
7560
|
+
input: z30.object({
|
|
7561
|
+
limit: z30.number().int().min(1).max(100).optional(),
|
|
7562
|
+
cursor: z30.string().optional()
|
|
7231
7563
|
}),
|
|
7232
7564
|
mcp: { name: "storage_volumes_list" },
|
|
7233
7565
|
slack: false,
|
|
@@ -7257,10 +7589,10 @@ var storageVerbCapabilities = [
|
|
|
7257
7589
|
description: "Create a storage volume (drive), optionally cloned from an existing source drive.",
|
|
7258
7590
|
mutation: "preview-confirm",
|
|
7259
7591
|
http: { method: "POST", path: DRIVES_ROUTE },
|
|
7260
|
-
input:
|
|
7261
|
-
name:
|
|
7262
|
-
sourceDriveId:
|
|
7263
|
-
confirm:
|
|
7592
|
+
input: z30.object({
|
|
7593
|
+
name: z30.string(),
|
|
7594
|
+
sourceDriveId: z30.string().optional(),
|
|
7595
|
+
confirm: z30.boolean().optional()
|
|
7264
7596
|
}),
|
|
7265
7597
|
mcp: { name: "storage_volumes_create" },
|
|
7266
7598
|
slack: false,
|
|
@@ -7362,7 +7694,7 @@ var storageVerbCapabilities = [
|
|
|
7362
7694
|
description: "Update a connected S3-compatible bucket's access key credentials.",
|
|
7363
7695
|
mutation: "preview-confirm",
|
|
7364
7696
|
http: { method: "PATCH", path: `${STORAGE_DRIVES_ROUTE}/{driveId}/source-credentials` },
|
|
7365
|
-
input: driveWrite.extend({ accessKeyId:
|
|
7697
|
+
input: driveWrite.extend({ accessKeyId: z30.string(), secretAccessKey: z30.string() }),
|
|
7366
7698
|
mcp: { name: "storage_buckets_update_credentials" },
|
|
7367
7699
|
slack: false,
|
|
7368
7700
|
execute: (ctx, input) => ctx.fetch(storageDrivePath(input.driveId, "source-credentials"), {
|
|
@@ -7382,9 +7714,9 @@ var storageCompositionCapabilities = [
|
|
|
7382
7714
|
description: "List a page of connected external buckets.",
|
|
7383
7715
|
mutation: "read",
|
|
7384
7716
|
http: { method: "GET", path: DRIVES_ROUTE },
|
|
7385
|
-
input:
|
|
7386
|
-
limit:
|
|
7387
|
-
cursor:
|
|
7717
|
+
input: z30.object({
|
|
7718
|
+
limit: z30.number().int().min(1).max(100).optional(),
|
|
7719
|
+
cursor: z30.string().optional()
|
|
7388
7720
|
}),
|
|
7389
7721
|
mcp: false,
|
|
7390
7722
|
slack: false,
|
|
@@ -7402,10 +7734,10 @@ var storageCompositionCapabilities = [
|
|
|
7402
7734
|
description: "List one page of a storage volume's contents.",
|
|
7403
7735
|
mutation: "read",
|
|
7404
7736
|
http: { method: "GET", path: `${DRIVES_ROUTE}/{driveId}/contents` },
|
|
7405
|
-
input:
|
|
7406
|
-
driveId:
|
|
7407
|
-
path:
|
|
7408
|
-
cursor:
|
|
7737
|
+
input: z30.object({
|
|
7738
|
+
driveId: z30.string(),
|
|
7739
|
+
path: z30.string().optional(),
|
|
7740
|
+
cursor: z30.union([z30.string(), z30.number()]).optional()
|
|
7409
7741
|
}),
|
|
7410
7742
|
mcp: false,
|
|
7411
7743
|
slack: false,
|
|
@@ -7464,6 +7796,7 @@ var catalog = [
|
|
|
7464
7796
|
machinePushKeysCapability,
|
|
7465
7797
|
...machineMetricsCapabilities,
|
|
7466
7798
|
...billingCapabilities,
|
|
7799
|
+
...registryCapabilities,
|
|
7467
7800
|
...adminNodeLifecycleCapabilities
|
|
7468
7801
|
])),
|
|
7469
7802
|
...adminCapabilities.filter((capability) => capability.id === "nodes.health"),
|