@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
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { chmod, link, lstat, open, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
export class AtomicOutputExistsError extends Error {
|
|
8
|
+
constructor(path) {
|
|
9
|
+
super(`Refusing to replace existing file: ${path}`);
|
|
10
|
+
this.name = "AtomicOutputExistsError";
|
|
11
|
+
this.code = "destination_exists";
|
|
12
|
+
this.path = path;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function destinationExists(path) {
|
|
17
|
+
try {
|
|
18
|
+
await lstat(path);
|
|
19
|
+
return true;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error?.code === "ENOENT") return false;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function assertAtomicOutputAvailable(destination, { overwrite = false } = {}) {
|
|
27
|
+
if (typeof destination !== "string" || !destination.trim()) {
|
|
28
|
+
throw new TypeError("An output path is required.");
|
|
29
|
+
}
|
|
30
|
+
if (!overwrite && (await destinationExists(destination))) {
|
|
31
|
+
throw new AtomicOutputExistsError(destination);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function nodeReadable(content) {
|
|
36
|
+
if (content && typeof content.getReader === "function") return Readable.fromWeb(content);
|
|
37
|
+
if (content && typeof content.pipe === "function") return content;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
async function writeReadable(handle, readable) {
|
|
41
|
+
for await (const chunk of readable) {
|
|
42
|
+
const bytes =
|
|
43
|
+
typeof chunk === "string"
|
|
44
|
+
? Buffer.from(chunk)
|
|
45
|
+
: chunk instanceof Uint8Array || chunk instanceof DataView
|
|
46
|
+
? Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength)
|
|
47
|
+
: null;
|
|
48
|
+
if (!bytes) throw new TypeError("Readable atomic output chunks must be bytes or text.");
|
|
49
|
+
let offset = 0;
|
|
50
|
+
while (offset < bytes.byteLength) {
|
|
51
|
+
const result = await handle.write(bytes, offset, bytes.byteLength - offset);
|
|
52
|
+
if (result.bytesWritten === 0) throw new Error("Atomic output write made no progress.");
|
|
53
|
+
offset += result.bytesWritten;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Writes bytes through a unique same-directory temporary file and publishes
|
|
60
|
+
* them atomically. Without overwrite, hard-link publication provides an atomic
|
|
61
|
+
* no-replace operation; replacement uses an atomic rename.
|
|
62
|
+
*/
|
|
63
|
+
export async function writeAtomicOutput(
|
|
64
|
+
destination,
|
|
65
|
+
content,
|
|
66
|
+
{ mode = 0o644, overwrite = false } = {}
|
|
67
|
+
) {
|
|
68
|
+
await assertAtomicOutputAvailable(destination, { overwrite });
|
|
69
|
+
if (!Number.isInteger(mode) || mode < 0 || mode > 0o777) {
|
|
70
|
+
throw new TypeError("Output mode must be an integer between 0000 and 0777.");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const directory = dirname(destination);
|
|
74
|
+
const temporary = join(directory, `.${process.pid}.${randomUUID()}.ornn-partial`);
|
|
75
|
+
let handle;
|
|
76
|
+
try {
|
|
77
|
+
handle = await open(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, mode);
|
|
78
|
+
const readable = nodeReadable(content);
|
|
79
|
+
if (readable) {
|
|
80
|
+
await writeReadable(handle, readable);
|
|
81
|
+
} else if (
|
|
82
|
+
typeof content === "string" ||
|
|
83
|
+
content instanceof Uint8Array ||
|
|
84
|
+
content instanceof DataView
|
|
85
|
+
) {
|
|
86
|
+
await handle.writeFile(content);
|
|
87
|
+
} else {
|
|
88
|
+
throw new TypeError("Atomic output content must be bytes, text, or a readable stream.");
|
|
89
|
+
}
|
|
90
|
+
await handle.sync();
|
|
91
|
+
await handle.close();
|
|
92
|
+
handle = undefined;
|
|
93
|
+
await chmod(temporary, mode);
|
|
94
|
+
|
|
95
|
+
if (overwrite) {
|
|
96
|
+
await rename(temporary, destination);
|
|
97
|
+
} else {
|
|
98
|
+
try {
|
|
99
|
+
await link(temporary, destination);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error?.code === "EEXIST") throw new AtomicOutputExistsError(destination);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
await rm(temporary);
|
|
105
|
+
}
|
|
106
|
+
return { mode, path: destination };
|
|
107
|
+
} catch (error) {
|
|
108
|
+
await handle?.close().catch(() => {});
|
|
109
|
+
await rm(temporary, { force: true }).catch(() => {});
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/auth-store.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { mkdir, readFile, rm
|
|
3
|
-
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
4
3
|
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { writeAtomicOutput } from "./atomic-output.mjs";
|
|
5
7
|
|
|
6
8
|
const APP_DIR = "ornn";
|
|
7
9
|
const AUTH_FILE = "auth.json";
|
|
@@ -46,6 +48,15 @@ export function configuredApiBaseUrl(env = process.env) {
|
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
export function configuredInstallerBaseUrl(env = process.env) {
|
|
52
|
+
try {
|
|
53
|
+
const value = JSON.parse(readFileSync(getCliConfigPath(env), "utf8"))?.installerBaseUrl;
|
|
54
|
+
return typeof value === "string" ? value.trim() : "";
|
|
55
|
+
} catch {
|
|
56
|
+
return "";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
export async function loadAuthSession({ env = process.env } = {}) {
|
|
50
61
|
const path = getAuthConfigPath(env);
|
|
51
62
|
|
|
@@ -60,20 +71,180 @@ export async function loadAuthSession({ env = process.env } = {}) {
|
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
async function writeAuthSession(session, env) {
|
|
64
75
|
const path = getAuthConfigPath(env);
|
|
76
|
+
await mkdir(getAuthConfigDir(env), { recursive: true, mode: 0o700 });
|
|
77
|
+
await writeAtomicOutput(path, `${JSON.stringify(session, null, 2)}\n`, {
|
|
78
|
+
mode: 0o600,
|
|
79
|
+
overwrite: true,
|
|
80
|
+
});
|
|
81
|
+
return session;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function saveAuthSession(session, { env = process.env, now = new Date() } = {}) {
|
|
85
|
+
const existing = await loadAuthSession({ env });
|
|
86
|
+
const payload = { ...session };
|
|
87
|
+
delete payload.pendingPurchase;
|
|
88
|
+
delete payload.pendingWorkspace;
|
|
89
|
+
delete payload.recoveryUserId;
|
|
90
|
+
const userId = pendingString(session?.user, "id");
|
|
91
|
+
const existingOwnerId = recoveryOwnerId(existing);
|
|
92
|
+
const existingRecovery =
|
|
93
|
+
userId && existingOwnerId === userId
|
|
94
|
+
? durableRecoveryFields(existing)
|
|
95
|
+
: !userId && existingOwnerId
|
|
96
|
+
? durableRecoveryFields(existing)
|
|
97
|
+
: {};
|
|
98
|
+
const suppliedRecovery = userId ? durableRecoveryFields(session) : {};
|
|
99
|
+
const retainedRecoveryOwnerId =
|
|
100
|
+
!userId && Object.keys(existingRecovery).length > 0 ? existingOwnerId : null;
|
|
101
|
+
return writeAuthSession(
|
|
102
|
+
{
|
|
103
|
+
...payload,
|
|
104
|
+
...existingRecovery,
|
|
105
|
+
...suppliedRecovery,
|
|
106
|
+
...(retainedRecoveryOwnerId ? { recoveryUserId: retainedRecoveryOwnerId } : {}),
|
|
107
|
+
savedAt: now.toISOString(),
|
|
108
|
+
},
|
|
109
|
+
env
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function bindAuthUser(user, { env = process.env, expectedAccessToken } = {}) {
|
|
114
|
+
const userId = pendingString(user, "id");
|
|
115
|
+
if (!userId) return false;
|
|
116
|
+
const existing = await loadAuthSession({ env });
|
|
117
|
+
if (
|
|
118
|
+
!existing?.accessToken ||
|
|
119
|
+
(expectedAccessToken && existing.accessToken !== expectedAccessToken)
|
|
120
|
+
) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
65
123
|
const payload = {
|
|
66
|
-
...
|
|
67
|
-
|
|
124
|
+
...existing,
|
|
125
|
+
user: { ...(existing.user ?? {}), ...user, id: userId },
|
|
68
126
|
};
|
|
127
|
+
const ownerId = recoveryOwnerId(existing);
|
|
128
|
+
if (ownerId && ownerId !== userId) {
|
|
129
|
+
delete payload.pendingPurchase;
|
|
130
|
+
delete payload.pendingWorkspace;
|
|
131
|
+
}
|
|
132
|
+
delete payload.recoveryUserId;
|
|
133
|
+
await writeAuthSession(payload, env);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
69
136
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
});
|
|
137
|
+
export async function saveOrganizationHint(
|
|
138
|
+
selectedOrganizationId,
|
|
139
|
+
{ env = process.env, expectedAccessToken } = {}
|
|
140
|
+
) {
|
|
141
|
+
const session = await loadAuthSession({ env });
|
|
142
|
+
if (
|
|
143
|
+
!session?.accessToken ||
|
|
144
|
+
(expectedAccessToken && session.accessToken !== expectedAccessToken)
|
|
145
|
+
) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const payload = { ...session };
|
|
149
|
+
if (typeof selectedOrganizationId === "string" && selectedOrganizationId.trim()) {
|
|
150
|
+
payload.selectedOrganizationId = selectedOrganizationId.trim();
|
|
151
|
+
} else {
|
|
152
|
+
delete payload.selectedOrganizationId;
|
|
153
|
+
}
|
|
154
|
+
return writeAuthSession(payload, env);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function pendingString(value, field) {
|
|
158
|
+
return typeof value?.[field] === "string" ? value[field].trim() : "";
|
|
159
|
+
}
|
|
75
160
|
|
|
76
|
-
|
|
161
|
+
function pendingWorkspaceValue(value) {
|
|
162
|
+
const pending = value && typeof value === "object" ? value : {};
|
|
163
|
+
const required = [
|
|
164
|
+
"id",
|
|
165
|
+
"organizationId",
|
|
166
|
+
"reservationId",
|
|
167
|
+
"keyId",
|
|
168
|
+
"networkId",
|
|
169
|
+
"volumeId",
|
|
170
|
+
"mountAccess",
|
|
171
|
+
];
|
|
172
|
+
const normalized = Object.fromEntries(
|
|
173
|
+
required.map((field) => [
|
|
174
|
+
field,
|
|
175
|
+
typeof pending[field] === "string" ? pending[field].trim() : "",
|
|
176
|
+
])
|
|
177
|
+
);
|
|
178
|
+
if (required.some((field) => !normalized[field])) return null;
|
|
179
|
+
return {
|
|
180
|
+
...normalized,
|
|
181
|
+
username:
|
|
182
|
+
typeof pending.username === "string" && pending.username.trim()
|
|
183
|
+
? pending.username.trim()
|
|
184
|
+
: null,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function durableRecoveryFields(value) {
|
|
189
|
+
const pendingWorkspace = pendingWorkspaceValue(value?.pendingWorkspace);
|
|
190
|
+
return {
|
|
191
|
+
...(pendingWorkspace ? { pendingWorkspace } : {}),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function recoveryOwnerId(value) {
|
|
196
|
+
return pendingString(value?.user, "id") || pendingString(value, "recoveryUserId");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export async function loadPendingWorkspace({ env = process.env, session: suppliedSession } = {}) {
|
|
200
|
+
const session = suppliedSession === undefined ? await loadAuthSession({ env }) : suppliedSession;
|
|
201
|
+
return pendingWorkspaceValue(session?.pendingWorkspace);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function savePendingWorkspace(value, { env = process.env, expectedAccessToken } = {}) {
|
|
205
|
+
const pendingWorkspace = pendingWorkspaceValue(value);
|
|
206
|
+
if (!pendingWorkspace) return false;
|
|
207
|
+
const session = await loadAuthSession({ env });
|
|
208
|
+
if (
|
|
209
|
+
!session?.accessToken ||
|
|
210
|
+
(expectedAccessToken && session.accessToken !== expectedAccessToken)
|
|
211
|
+
) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
const existing = pendingWorkspaceValue(session.pendingWorkspace);
|
|
215
|
+
if (existing && existing.id !== pendingWorkspace.id) return false;
|
|
216
|
+
await writeAuthSession({ ...session, pendingWorkspace }, env);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function clearPendingWorkspace(id, { env = process.env, expectedAccessToken } = {}) {
|
|
221
|
+
const session = await loadAuthSession({ env });
|
|
222
|
+
if (!session?.pendingWorkspace) return false;
|
|
223
|
+
if (expectedAccessToken && session.accessToken !== expectedAccessToken) return false;
|
|
224
|
+
if (id && session.pendingWorkspace.id !== id) return false;
|
|
225
|
+
const payload = { ...session };
|
|
226
|
+
delete payload.pendingWorkspace;
|
|
227
|
+
await writeAuthSession(payload, env);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function clearAuthCredentials({ env = process.env, expectedAccessToken } = {}) {
|
|
232
|
+
const existing = await loadAuthSession({ env });
|
|
233
|
+
if (
|
|
234
|
+
expectedAccessToken &&
|
|
235
|
+
existing?.accessToken &&
|
|
236
|
+
existing.accessToken !== expectedAccessToken
|
|
237
|
+
) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
const recovery = durableRecoveryFields(existing);
|
|
241
|
+
const recoveryUserId = recoveryOwnerId(existing);
|
|
242
|
+
if (Object.keys(recovery).length === 0 || !recoveryUserId) {
|
|
243
|
+
await rm(getAuthConfigPath(env), { force: true });
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
await writeAuthSession({ ...recovery, recoveryUserId }, env);
|
|
247
|
+
return true;
|
|
77
248
|
}
|
|
78
249
|
|
|
79
250
|
export async function clearAuthSession({ env = process.env } = {}) {
|