@ornncompute/cli 0.2.16 → 0.2.18

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 CHANGED
@@ -271,12 +271,15 @@ path.
271
271
 
272
272
  The operator resource commands share the same reviewer auth:
273
273
 
274
- - `ornn operators list` lists enrolled operators.
274
+ - `ornn operator list [--json]` lists operators from the capabilities
275
+ catalog (staff). Distinct from `ornn operators list`.
276
+ - `ornn operators list` lists enrolled provisioning operators
277
+ (`GET /provisioning/operators`).
275
278
  - `ornn facilities list [--operator <id-or-slug>]` lists facilities, optionally
276
279
  filtered to one operator.
277
280
  - `ornn tokens list` lists enrollment tokens.
278
281
  - `ornn tokens create --operator <id> [--facility <id>]
279
- [--expires-in <seconds>] [--mode bare-metal|vm] [--ip <addr>] [--host <ip>]
282
+ [--expires-in <seconds>] [--ip <addr>] [--host <ip>]
280
283
  [--identity-file <path>] [--yes]` mints an enrollment token and prints a
281
284
  `curl … | sudo bash` install command. With `--host` and `--identity-file`,
282
285
  orchestrator SSHs and runs that curl (same as Bulk Enroll). Asks y/N unless `--yes`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornncompute/cli",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "Command-line interface for Ornn compute access workflows.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -165,7 +165,7 @@ Usage:
165
165
  ornn operators list [--json]
166
166
  ornn facilities list [--operator <id-or-slug>] [--json]
167
167
  ornn tokens list [--json]
168
- ornn tokens create --operator <id> [--facility <id>] [--expires-in <seconds>] [--mode bare-metal|vm] [--ip <addr>] [--host <ip>] [--identity-file <path>] [--yes] [--json]
168
+ ornn tokens create --operator <id> [--facility <id>] [--expires-in <seconds>] [--ip <addr>] [--host <ip>] [--identity-file <path>] [--yes] [--json]
169
169
  ornn tokens revoke <token-id> [--json]
170
170
  ornn reservations transfer <reservation-id> --target-tenant <id> [--target-user <id>] [--node <id>] [--strategy reject|park] [--confirm] [--json]
171
171
  ornn reservations commerce list --tenant <email-or-id> [--listing <listing-id>] [--json]
@@ -2467,12 +2467,12 @@ async function tokensCommand(args, context) {
2467
2467
 
2468
2468
  if (subcommand === "create") {
2469
2469
  const createUsage =
2470
- "Usage: ornn tokens create --operator <id-or-slug> [--facility <id>] [--expires-in <seconds>] [--mode bare-metal|vm] [--ip <addr>] [--host <ip>] [--identity-file <path>] [--yes] [--json]";
2470
+ "Usage: ornn tokens create --operator <id-or-slug> [--facility <id>] [--expires-in <seconds>] [--ip <addr>] [--host <ip>] [--identity-file <path>] [--yes] [--json]";
2471
2471
  const options = parseCommandOptions(
2472
2472
  rest,
2473
2473
  {
2474
2474
  boolean: ["json", "force", "yes"],
2475
- value: ["operator", "facility", "expires-in", "mode", "ip", "host", "identity-file"],
2475
+ value: ["operator", "facility", "expires-in", "ip", "host", "identity-file"],
2476
2476
  },
2477
2477
  createUsage
2478
2478
  );
@@ -2495,7 +2495,6 @@ async function tokensCommand(args, context) {
2495
2495
  ...(optionalStringOption(options.expiresIn)
2496
2496
  ? { expires_in_seconds: Number.parseInt(optionalStringOption(options.expiresIn), 10) }
2497
2497
  : {}),
2498
- ...(optionalStringOption(options.mode) ? { node_mode: optionalStringOption(options.mode) } : {}),
2499
2498
  ...(optionalStringOption(options.ip) ? { ip: optionalStringOption(options.ip) } : {}),
2500
2499
  ...(optionalStringOption(options.host) ? { host: optionalStringOption(options.host) } : {}),
2501
2500
  ...(privateKey ? { private_key: privateKey } : {}),
package/src/update.mjs CHANGED
@@ -125,7 +125,7 @@ export function detectInstallContext(binPath, env = process.env) {
125
125
  if (isNpmGlobalInstall(lower, env)) {
126
126
  return { kind: "npm", binPath: resolved };
127
127
  }
128
- if (isProjectInstall(resolved, lower)) {
128
+ if (isProjectInstall(resolved)) {
129
129
  return { kind: "project", binPath: resolved };
130
130
  }
131
131
  if (has(".bun") || isUnderDir(lower, env.BUN_INSTALL)) {
@@ -140,14 +140,15 @@ export function detectInstallContext(binPath, env = process.env) {
140
140
  return { kind: "npm", binPath: resolved };
141
141
  }
142
142
 
143
- function isProjectInstall(resolvedPath, lower) {
143
+ function isProjectInstall(resolvedPath) {
144
144
  // Global layouts have no package.json above node_modules; projects do.
145
- const index = lower.indexOf("/node_modules/");
146
- if (index < 0) {
145
+ const parts = resolvedPath.replaceAll("\\", "/").split("/");
146
+ const index = parts.findIndex((segment) => segment.toLowerCase() === "node_modules");
147
+ if (index <= 0) {
147
148
  return false;
148
149
  }
149
150
  try {
150
- return existsSync(join(resolvedPath.slice(0, index), "package.json"));
151
+ return existsSync(join(parts.slice(0, index).join("/"), "package.json"));
151
152
  } catch {
152
153
  return false;
153
154
  }
@@ -212,9 +213,13 @@ function stableInstallRoot(binPath) {
212
213
  try {
213
214
  resolved = realpathSync(binPath);
214
215
  } catch {}
215
- const normalized = resolved.replaceAll("\\", "/");
216
- const markerIndex = normalized.toLowerCase().indexOf("/node_modules/");
217
- return markerIndex > 0 ? normalized.slice(0, markerIndex) : null;
216
+ const parts = resolved.replaceAll("\\", "/").split("/");
217
+ const index = parts.findIndex((segment) => segment.toLowerCase() === "node_modules");
218
+ if (index <= 0) {
219
+ return null;
220
+ }
221
+ const prefix = parts.slice(0, index).join("/");
222
+ return prefix.length > 0 ? prefix : null;
218
223
  }
219
224
 
220
225
  function pathIdentity(path) {
@@ -200,7 +200,21 @@ export const sshKeyAddCapability = defineStaffMcpCapability({
200
200
  // (gateway `add_reservation_key`), which this tool previously bypassed by
201
201
  // posting `public_key` into a body that only reads `sshKeyId`/`userId`.
202
202
  let sshKeyId = parsed.sshKeyId;
203
- if (!sshKeyId) {
203
+ if (sshKeyId) {
204
+ // Commerce stores the id as a soft key without checking authorization,
205
+ // so gate it here the way gateway `add_reservation_key` does.
206
+ const listed = (await ctx.fetch(`/v1/organizations/${seg(parsed.tenantId)}/ssh-keys`, {
207
+ method: "GET",
208
+ }));
209
+ const key = (listed.ssh_keys ?? []).find((candidate) => candidate.id === sshKeyId);
210
+ if (!key) {
211
+ throw new Error("SSH key not found.");
212
+ }
213
+ if (key.status !== "active" || key.revoked_at) {
214
+ throw new Error("SSH key is not active.");
215
+ }
216
+ }
217
+ else {
204
218
  const registered = (await ctx.fetch(`/v1/organizations/${seg(parsed.tenantId)}/ssh-keys`, {
205
219
  method: "POST",
206
220
  body: { public_key: parsed.publicKey, ...mapBody(parsed, { label: "label" }) },