@ornncompute/cli 0.2.22 → 0.2.23

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
@@ -137,9 +137,6 @@ ornn networks show <network-id> [--json]
137
137
  ornn networks create --name <name> [--cidr <cidr>] [--description <text>] [--json]
138
138
  ornn networks update <network-id> [--name <name>] [--description <text>] [--clear-description] [--json]
139
139
  ornn networks delete <network-id> [--yes] [--json]
140
- ornn networks reservation <reservation-id> [--json]
141
- ornn networks attach <reservation-id> --network <network-id> [--json]
142
- ornn networks detach <reservation-id> [--json]
143
140
  ornn storage volumes list [--json]
144
141
  ornn storage volumes show <drive-id> [--json]
145
142
  ornn storage volumes create --name <name> [--source <drive-id>] [--json]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornncompute/cli",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
4
4
  "description": "Command-line interface for Ornn compute access workflows.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -214,9 +214,6 @@ Usage:
214
214
  ornn networks create --name <name> [--cidr <cidr>] [--description <text>] [--json]
215
215
  ornn networks update <network-id> [--name <name>] [--description <text>] [--clear-description] [--json]
216
216
  ornn networks delete <network-id> [--yes] [--json]
217
- ornn networks reservation <reservation-id> [--json]
218
- ornn networks attach <reservation-id> --network <network-id> [--json]
219
- ornn networks detach <reservation-id> [--json]
220
217
  ornn storage volumes list [--json]
221
218
  ornn storage files ls <drive-id> [--prefix <path>] [--json]
222
219
  ornn storage files upload <drive-id> <local-file> [--destination <path>] [--content-type <type>] [--json]
@@ -3002,7 +2999,7 @@ function nodeOpUsage(subcommand) {
3002
2999
  return `Usage: ornn node ${subcommand} <node-id> [--json]`;
3003
3000
  }
3004
3001
 
3005
- // Catalog: @ornn/capabilities fleet.nodes.list → GET /internal/nodes
3002
+ // Catalog: @ornn/capabilities fleet.nodes.list → GET /nodes
3006
3003
  async function nodeOpList(rest, context) {
3007
3004
  const options = parseCommandOptions(
3008
3005
  rest,
@@ -3133,11 +3130,15 @@ async function resolveNodeInstanceId(nodeId, context) {
3133
3130
  throw new CliApiError(`Node ${nodeId} has no assigned reservation to reboot.`);
3134
3131
  }
3135
3132
  const machines = await operatorRequest({
3136
- endpoint: `/v1/orchestrator/nodes/reservations/${encodeURIComponent(reservationId)}/machines`,
3133
+ endpoint: `/v1/orchestrator/internal/nodes?reservation_id=${encodeURIComponent(reservationId)}`,
3137
3134
  env: context.env,
3138
3135
  fetchImpl: context.fetchImpl,
3139
3136
  });
3140
- const rows = Array.isArray(machines?.machines) ? machines.machines : [];
3137
+ const rows = Array.isArray(machines?.nodes)
3138
+ ? machines.nodes
3139
+ : Array.isArray(machines?.machines)
3140
+ ? machines.machines
3141
+ : [];
3141
3142
  const nodeRefs = new Set(
3142
3143
  [nodeId, node?.id, node?.k8s_node_name]
3143
3144
  .filter((value) => value !== null && value !== undefined && String(value) !== "")
@@ -3151,7 +3152,7 @@ async function resolveNodeInstanceId(nodeId, context) {
3151
3152
  `Multiple live machines found for node ${nodeId} (reservation ${reservationId}).`
3152
3153
  );
3153
3154
  }
3154
- const instanceId = matches[0]?.id;
3155
+ const instanceId = matches[0]?.node_id ?? matches[0]?.id;
3155
3156
  if (!instanceId) {
3156
3157
  throw new CliApiError(
3157
3158
  `No live machine found for node ${nodeId} (reservation ${reservationId}).`
@@ -3370,7 +3371,7 @@ function writeNodeHealth(stdout, node) {
3370
3371
 
3371
3372
  stdout.write(`Validation: ${node?.validation_status || "unknown"}\n`);
3372
3373
  writeOptionalStatusLine(stdout, "Validation checked", node?.validation_checked_at);
3373
- writeOptionalStatusLine(stdout, "Last heartbeat", node?.last_heartbeat_at);
3374
+ writeOptionalStatusLine(stdout, "Last heartbeat", node?.observed_at);
3374
3375
  }
3375
3376
 
3376
3377
  async function nodes(args, context) {
@@ -3706,10 +3707,11 @@ async function clusters(args, context) {
3706
3707
  "Usage: ornn clusters list [--json]"
3707
3708
  );
3708
3709
  const rows = await fetchTenantClusters(context);
3710
+ const clusters = withNumberedClusterNames(requireArrayPayload(rows, "clusters"));
3709
3711
  if (options.json) {
3710
- writeJson(context.stdout, rows);
3712
+ writeJson(context.stdout, clusters);
3711
3713
  } else {
3712
- writeClusterList(context.stdout, requireArrayPayload(rows, "clusters"));
3714
+ writeClusterList(context.stdout, clusters);
3713
3715
  }
3714
3716
  return 0;
3715
3717
  }
@@ -3802,9 +3804,16 @@ async function clusters(args, context) {
3802
3804
  const wait = options.wait
3803
3805
  ? await waitForClusterActive(selectedReservationId, type, waitOptions(options), context)
3804
3806
  : null;
3805
- const result = { cluster: launch, reservation_id: selectedReservationId, type, wait };
3807
+ const result = {
3808
+ cluster: withNumberedClusterName(launch, [launch]),
3809
+ reservation_id: selectedReservationId,
3810
+ type,
3811
+ wait: wait
3812
+ ? { ...wait, cluster: withNumberedClusterName(wait.cluster, [wait.cluster]) }
3813
+ : null,
3814
+ };
3806
3815
  if (options.json) {
3807
- writeJson(context.stdout, result);
3816
+ writeJson(context.stdout, omitClusterName(result));
3808
3817
  } else {
3809
3818
  writeClusterLaunchResult(context.stdout, result);
3810
3819
  writeNextStep(
@@ -3825,8 +3834,20 @@ async function clusters(args, context) {
3825
3834
  { boolean: ["json"], value: ["type", "mode"] },
3826
3835
  "Usage: ornn clusters show <reservation-id> [--type kubernetes|slurm] [--json]"
3827
3836
  );
3828
- const type = await resolveClusterTypeForReservation(reservationId, options, context);
3829
- const cluster = await fetchCluster(reservationId, type, context);
3837
+ const listed = requireArrayPayload(await fetchTenantClusters(context), "clusters");
3838
+ let type;
3839
+ if (options.type || options.mode) {
3840
+ type = normalizeClusterType(options.type || options.mode);
3841
+ } else {
3842
+ const listedType = listed.find(
3843
+ (candidate) => String(candidate.reservation_id || "") === String(reservationId)
3844
+ )?.access_mode;
3845
+ if (!listedType) {
3846
+ throw new Error("--type is required when the cluster is not listed yet.");
3847
+ }
3848
+ type = normalizeClusterType(listedType);
3849
+ }
3850
+ const cluster = withNumberedClusterName(await fetchCluster(reservationId, type, context), listed);
3830
3851
  if (options.json) {
3831
3852
  writeJson(context.stdout, cluster);
3832
3853
  } else {
@@ -3843,10 +3864,14 @@ async function clusters(args, context) {
3843
3864
  );
3844
3865
  const type = await resolveClusterTypeForReservation(reservationId, options, context);
3845
3866
  const result = await waitForClusterActive(reservationId, type, waitOptions(options), context);
3867
+ const named = {
3868
+ ...result,
3869
+ cluster: withNumberedClusterName(result.cluster, [result.cluster]),
3870
+ };
3846
3871
  if (options.json) {
3847
- writeJson(context.stdout, result);
3872
+ writeJson(context.stdout, omitClusterName(named));
3848
3873
  } else {
3849
- writeClusterWaitResult(context.stdout, result);
3874
+ writeClusterWaitResult(context.stdout, named);
3850
3875
  }
3851
3876
  return 0;
3852
3877
  }
@@ -3952,13 +3977,14 @@ async function clusters(args, context) {
3952
3977
  subcommand === "add-node"
3953
3978
  ? await addClusterNode(reservationId, nodeId, context)
3954
3979
  : await removeClusterNode(reservationId, nodeId, context);
3980
+ const named = withNumberedClusterName(cluster, [cluster]);
3955
3981
  if (options.json) {
3956
- writeJson(context.stdout, cluster);
3982
+ writeJson(context.stdout, named);
3957
3983
  } else {
3958
3984
  context.stdout.write(
3959
3985
  `Cluster node ${subcommand === "add-node" ? "add" : "remove"} queued.\n`
3960
3986
  );
3961
- writeClusterDetail(context.stdout, cluster);
3987
+ writeClusterDetail(context.stdout, named);
3962
3988
  }
3963
3989
  return 0;
3964
3990
  }
@@ -3971,7 +3997,7 @@ async function clusters(args, context) {
3971
3997
  );
3972
3998
  if (!(await confirmClusterTeardown(options, context, reservationId))) return 1;
3973
3999
  const type = await resolveClusterTypeForReservation(reservationId, options, context);
3974
- const cluster = await teardownCluster(reservationId, type, context);
4000
+ const cluster = withNumberedClusterName(await teardownCluster(reservationId, type, context), []);
3975
4001
  if (options.json) {
3976
4002
  writeJson(context.stdout, cluster);
3977
4003
  } else {
@@ -4002,9 +4028,16 @@ async function slurm(args, context) {
4002
4028
  const wait = options.wait
4003
4029
  ? await waitForClusterActive(reservationId, "slurm", waitOptions(options), context)
4004
4030
  : null;
4005
- const result = { cluster: launch, reservation_id: reservationId, type: "slurm", wait };
4031
+ const result = {
4032
+ cluster: withNumberedClusterName(launch, [launch]),
4033
+ reservation_id: reservationId,
4034
+ type: "slurm",
4035
+ wait: wait
4036
+ ? { ...wait, cluster: withNumberedClusterName(wait.cluster, [wait.cluster]) }
4037
+ : null,
4038
+ };
4006
4039
  if (options.json) {
4007
- writeJson(context.stdout, result);
4040
+ writeJson(context.stdout, omitClusterName(result));
4008
4041
  } else {
4009
4042
  writeClusterLaunchResult(context.stdout, result);
4010
4043
  }
@@ -4018,7 +4051,10 @@ async function slurm(args, context) {
4018
4051
  "Usage: ornn slurm teardown <reservation-id> [--yes] [--json]"
4019
4052
  );
4020
4053
  if (!(await confirmClusterTeardown(options, context, reservationId))) return 1;
4021
- const cluster = await teardownCluster(reservationId, "slurm", context);
4054
+ const cluster = withNumberedClusterName(
4055
+ await teardownCluster(reservationId, "slurm", context),
4056
+ []
4057
+ );
4022
4058
  if (options.json) {
4023
4059
  writeJson(context.stdout, cluster);
4024
4060
  } else {
@@ -4034,7 +4070,7 @@ async function slurm(args, context) {
4034
4070
  { boolean: ["json"] },
4035
4071
  "Usage: ornn slurm status <reservation-id> [--json]"
4036
4072
  );
4037
- const cluster = await fetchCluster(reservationId, "slurm", context);
4073
+ const cluster = withNumberedClusterName(await fetchCluster(reservationId, "slurm", context), []);
4038
4074
  if (options.json) {
4039
4075
  writeJson(context.stdout, cluster);
4040
4076
  } else {
@@ -4100,9 +4136,16 @@ async function kubernetes(args, context) {
4100
4136
  const wait = options.wait
4101
4137
  ? await waitForClusterActive(reservationId, "kubernetes", waitOptions(options), context)
4102
4138
  : null;
4103
- const result = { cluster: launch, reservation_id: reservationId, type: "kubernetes", wait };
4139
+ const result = {
4140
+ cluster: withNumberedClusterName(launch, [launch]),
4141
+ reservation_id: reservationId,
4142
+ type: "kubernetes",
4143
+ wait: wait
4144
+ ? { ...wait, cluster: withNumberedClusterName(wait.cluster, [wait.cluster]) }
4145
+ : null,
4146
+ };
4104
4147
  if (options.json) {
4105
- writeJson(context.stdout, result);
4148
+ writeJson(context.stdout, omitClusterName(result));
4106
4149
  } else {
4107
4150
  writeClusterLaunchResult(context.stdout, result);
4108
4151
  }
@@ -4116,7 +4159,10 @@ async function kubernetes(args, context) {
4116
4159
  "Usage: ornn kubernetes teardown <reservation-id> [--yes] [--json]"
4117
4160
  );
4118
4161
  if (!(await confirmClusterTeardown(options, context, reservationId))) return 1;
4119
- const cluster = await teardownCluster(reservationId, "kubernetes", context);
4162
+ const cluster = withNumberedClusterName(
4163
+ await teardownCluster(reservationId, "kubernetes", context),
4164
+ []
4165
+ );
4120
4166
  if (options.json) {
4121
4167
  writeJson(context.stdout, cluster);
4122
4168
  } else {
@@ -4132,7 +4178,10 @@ async function kubernetes(args, context) {
4132
4178
  { boolean: ["json"] },
4133
4179
  "Usage: ornn kubernetes status <reservation-id> [--json]"
4134
4180
  );
4135
- const cluster = await fetchCluster(reservationId, "kubernetes", context);
4181
+ const cluster = withNumberedClusterName(
4182
+ await fetchCluster(reservationId, "kubernetes", context),
4183
+ []
4184
+ );
4136
4185
  if (options.json) {
4137
4186
  writeJson(context.stdout, cluster);
4138
4187
  } else {
@@ -4326,71 +4375,11 @@ async function networks(args, context) {
4326
4375
  return 0;
4327
4376
  }
4328
4377
 
4329
- if (subcommand === "reservation" && id) {
4330
- const options = parseCommandOptions(
4331
- rest,
4332
- { boolean: ["json"] },
4333
- "Usage: ornn networks reservation <reservation-id> [--json]"
4334
- );
4335
- const payload = await cliRequest({
4336
- endpoint: `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(id)}/network`,
4337
- env: context.env,
4338
- fetchImpl: context.fetchImpl,
4339
- });
4340
- if (options.json) {
4341
- writeJson(context.stdout, payload);
4342
- } else {
4343
- writeReservationNetwork(context.stdout, payload);
4344
- }
4345
- return 0;
4346
- }
4347
-
4348
- if (subcommand === "attach" && id) {
4349
- const options = parseCommandOptions(
4350
- rest,
4351
- { boolean: ["json"], value: ["network", "network-id"] },
4352
- "Usage: ornn networks attach <reservation-id> --network <network-id> [--json]"
4353
- );
4354
- const networkId = requiredOption(options.network || options.networkId, "--network");
4355
- const payload = await cliRequest({
4356
- body: { network_id: networkId },
4357
- endpoint: `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(id)}/network`,
4358
- env: context.env,
4359
- fetchImpl: context.fetchImpl,
4360
- method: "POST",
4361
- });
4362
- if (options.json) {
4363
- writeJson(context.stdout, payload);
4364
- } else {
4365
- writeColored(context.stdout, "green", "Reservation network attached.");
4366
- writeReservationNetwork(context.stdout, payload);
4367
- }
4368
- return 0;
4369
- }
4370
-
4371
- if (subcommand === "detach" && id) {
4372
- const options = parseCommandOptions(
4373
- rest,
4374
- { boolean: ["json"] },
4375
- "Usage: ornn networks detach <reservation-id> [--json]"
4376
- );
4377
- const payload = await cliRequest({
4378
- body: { network_id: null },
4379
- endpoint: `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(id)}/network`,
4380
- env: context.env,
4381
- fetchImpl: context.fetchImpl,
4382
- method: "POST",
4383
- });
4384
- if (options.json) {
4385
- writeJson(context.stdout, payload);
4386
- } else {
4387
- writeColored(context.stdout, "green", "Reservation network detached.");
4388
- writeReservationNetwork(context.stdout, payload);
4389
- }
4390
- return 0;
4378
+ if (subcommand === "reservation" || subcommand === "attach" || subcommand === "detach") {
4379
+ throw new Error("network_attach_removed");
4391
4380
  }
4392
4381
 
4393
- throw new Error("Usage: ornn networks list|show|create|update|delete|reservation|attach|detach");
4382
+ throw new Error("Usage: ornn networks list|show|create|update|delete");
4394
4383
  }
4395
4384
 
4396
4385
  function normalizeStorageDestination(value) {
@@ -6061,6 +6050,8 @@ function inventoryAvailableGpuCount(record) {
6061
6050
  function normalizeInventoryListing(record) {
6062
6051
  const gpuCount = inventoryAvailableGpuCount(record);
6063
6052
  const facility = record.site_nickname ?? record.facility ?? record.site ?? "Unknown facility";
6053
+ // Customer availability never exposes real supplier/operator identity.
6054
+ // Display branding stays in the text formatter, not on the JSON envelope.
6064
6055
  return {
6065
6056
  id: record.id,
6066
6057
  kind: "inventory",
@@ -6855,13 +6846,21 @@ async function resolveStorageMountUsers(attachment, context) {
6855
6846
  return users.size ? users : null;
6856
6847
  }
6857
6848
 
6849
+ function reservationHostsFromPayload(payload) {
6850
+ if (Array.isArray(payload)) return payload;
6851
+ if (Array.isArray(payload?.nodes)) return payload.nodes;
6852
+ if (Array.isArray(payload?.machines)) return payload.machines;
6853
+ return [];
6854
+ }
6855
+
6858
6856
  async function getReservationMachines(reservationId, context) {
6859
6857
  const payload = await cliRequest({
6860
- endpoint: `/v1/workflows/compute/nodes/reservations/${reservationId}/machines`,
6858
+ endpoint: `/v1/workflows/compute/nodes?reservation_id=${encodeURIComponent(reservationId)}`,
6861
6859
  env: context.env,
6862
6860
  fetchImpl: context.fetchImpl,
6863
6861
  });
6864
- return Array.isArray(payload) ? { machines: payload } : payload;
6862
+ const nodes = reservationHostsFromPayload(payload);
6863
+ return { ...(payload && typeof payload === "object" ? payload : {}), machines: nodes };
6865
6864
  }
6866
6865
 
6867
6866
  async function fetchBillingInvoices(context) {
@@ -6967,13 +6966,6 @@ async function launchReservationAccess(
6967
6966
  "--storage-save-drive-id"
6968
6967
  );
6969
6968
  }
6970
- const access = await cliRequest({
6971
- body: { access_mode: mode, image_id: null },
6972
- endpoint: `/v1/workflows/commerce/reservations/${reservationId}/access-mode`,
6973
- env: context.env,
6974
- fetchImpl: context.fetchImpl,
6975
- method: "POST",
6976
- });
6977
6969
  const launch = await cliRequest({
6978
6970
  body: launchPayload,
6979
6971
  endpoint: `/v1/workflows/compute/nodes/reservations/${reservationId}/launch`,
@@ -6992,7 +6984,7 @@ async function launchReservationAccess(
6992
6984
  ? await waitForSshReady(reservationId, waitOptions(options), context)
6993
6985
  : null;
6994
6986
  return {
6995
- access,
6987
+ access: launch,
6996
6988
  launch,
6997
6989
  mode,
6998
6990
  opened,
@@ -7027,7 +7019,7 @@ async function switchReservationAccess(reservationId, options, context) {
7027
7019
  fetchImpl: context.fetchImpl,
7028
7020
  method: "POST",
7029
7021
  });
7030
- const machines = Array.isArray(switched?.machines) ? switched.machines : [];
7022
+ const machines = reservationHostsFromPayload(switched);
7031
7023
  const wait = options.wait
7032
7024
  ? await waitForSshReady(reservationId, waitOptions(options), context)
7033
7025
  : null;
@@ -7045,7 +7037,7 @@ async function switchReservationAccess(reservationId, options, context) {
7045
7037
 
7046
7038
  function accessActivateJsonResult(result) {
7047
7039
  return {
7048
- access: result.access,
7040
+ access: { access_mode: result.mode },
7049
7041
  launch: result.launch,
7050
7042
  opened: Boolean(result.opened?.opened),
7051
7043
  url: result.opened?.url ?? null,
@@ -7062,11 +7054,11 @@ async function fetchTenantClusters(context) {
7062
7054
 
7063
7055
  async function fetchEligibleClusterNodes(reservationId, type, networkMode, context) {
7064
7056
  const payload = await cliRequest({
7065
- endpoint: `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(reservationId)}/machines`,
7057
+ endpoint: `/v1/workflows/compute/nodes?reservation_id=${encodeURIComponent(reservationId)}`,
7066
7058
  env: context.env,
7067
7059
  fetchImpl: context.fetchImpl,
7068
7060
  });
7069
- const machines = Array.isArray(payload?.machines) ? payload.machines : [];
7061
+ const machines = reservationHostsFromPayload(payload);
7070
7062
  return {
7071
7063
  reservation_id: reservationId,
7072
7064
  access_mode: type,
@@ -7803,7 +7795,7 @@ function writeAccessDetail(stdout, payload = {}) {
7803
7795
  }
7804
7796
 
7805
7797
  function writeAccessLaunchSummary(stdout, launch = {}) {
7806
- const machines = Array.isArray(launch.machines) ? launch.machines : [];
7798
+ const machines = reservationHostsFromPayload(launch);
7807
7799
  if (!machines.length) {
7808
7800
  stdout.write("Provisioning response received.\n");
7809
7801
  return;
@@ -8011,18 +8003,6 @@ function writeNetworkDetail(stdout, network = {}) {
8011
8003
  writeOptionalStatusLine(stdout, "Updated", network.updated_at);
8012
8004
  }
8013
8005
 
8014
- function writeReservationNetwork(stdout, payload = {}) {
8015
- stdout.write(`${payload.reservation_id || "reservation"}\n`);
8016
- if (!payload.network_id || !payload.network) {
8017
- stdout.write("Network: shared\n");
8018
- return;
8019
- }
8020
- stdout.write(`Network: ${payload.network.name || payload.network_id}\n`);
8021
- stdout.write(`Network ID: ${payload.network_id}\n`);
8022
- writeOptionalStatusLine(stdout, "CIDR", payload.network.cidr);
8023
- writeOptionalStatusLine(stdout, "Status", payload.network.status);
8024
- }
8025
-
8026
8006
  function writeReservationStorageAttachment(stdout, attachment = {}, options = {}) {
8027
8007
  if (!attachment) {
8028
8008
  stdout.write("No volume is attached to this reservation.\n");
@@ -8950,10 +8930,10 @@ function metricSnapshotFromLivePayload(fallbackMachine, payload = {}) {
8950
8930
  function metricSnapshotFromMachine(machine, extras = {}) {
8951
8931
  const resourceMetrics = machine.resource_metrics || machine.resourceMetrics || {};
8952
8932
  const lastHeartbeatAt =
8933
+ machine.observed_at ||
8934
+ resourceMetrics.observed_at ||
8953
8935
  resourceMetrics.last_heartbeat_at ||
8954
8936
  resourceMetrics.lastHeartbeatAt ||
8955
- machine.last_heartbeat_at ||
8956
- machine.lastHeartbeatAt ||
8957
8937
  null;
8958
8938
  return {
8959
8939
  connection_status:
@@ -9033,6 +9013,51 @@ function writeWaitResult(stdout, result) {
9033
9013
  stdout.write(`SSH: ${result.command}\n`);
9034
9014
  }
9035
9015
 
9016
+ function clusterIdentity(cluster) {
9017
+ return String(cluster?.controller_id || cluster?.id || cluster?.reservation_id || "").trim();
9018
+ }
9019
+
9020
+ function numberedClusterName(clusters, target) {
9021
+ const ordered = [...clusters].sort((left, right) => {
9022
+ const leftCreatedAt = Date.parse(left.created_at ?? "");
9023
+ const rightCreatedAt = Date.parse(right.created_at ?? "");
9024
+ const leftTime = Number.isFinite(leftCreatedAt) ? leftCreatedAt : Number.MAX_SAFE_INTEGER;
9025
+ const rightTime = Number.isFinite(rightCreatedAt) ? rightCreatedAt : Number.MAX_SAFE_INTEGER;
9026
+ return leftTime - rightTime || clusterIdentity(left).localeCompare(clusterIdentity(right));
9027
+ });
9028
+ const index = ordered.findIndex((cluster) => clusterIdentity(cluster) === clusterIdentity(target));
9029
+ return `Cluster ${index >= 0 ? index + 1 : 1}`;
9030
+ }
9031
+
9032
+ function omitClusterName(value) {
9033
+ if (Array.isArray(value)) {
9034
+ return value.map(omitClusterName);
9035
+ }
9036
+ if (!value || typeof value !== "object") {
9037
+ return value;
9038
+ }
9039
+ const { cluster_name: _ignored, ...rest } = value;
9040
+ if ("cluster" in rest) {
9041
+ rest.cluster = omitClusterName(rest.cluster);
9042
+ }
9043
+ if (rest.wait && typeof rest.wait === "object") {
9044
+ rest.wait = omitClusterName(rest.wait);
9045
+ }
9046
+ return rest;
9047
+ }
9048
+
9049
+ function withNumberedClusterName(cluster, siblings) {
9050
+ const rows = siblings.length ? siblings : [cluster];
9051
+ return {
9052
+ ...omitClusterName(cluster),
9053
+ name: numberedClusterName(rows, cluster),
9054
+ };
9055
+ }
9056
+
9057
+ function withNumberedClusterNames(clusters) {
9058
+ return clusters.map((cluster) => withNumberedClusterName(cluster, clusters));
9059
+ }
9060
+
9036
9061
  function writeClusterList(stdout, clusters) {
9037
9062
  if (!clusters.length) {
9038
9063
  stdout.write("No clusters found.\n");
@@ -9067,7 +9092,7 @@ function writeClusterList(stdout, clusters) {
9067
9092
  stdout.write("Clusters:\n");
9068
9093
  for (const cluster of clusters) {
9069
9094
  stdout.write(
9070
- `- ${cluster.reservation_id || cluster.id || "cluster"} ${displayClusterType(cluster.access_mode)} ${cluster.state || "unknown"}`
9095
+ `- ${cluster.name || numberedClusterName(clusters, cluster)} ${cluster.reservation_id || cluster.id || "cluster"} ${displayClusterType(cluster.access_mode)} ${cluster.state || "unknown"}`
9071
9096
  );
9072
9097
  stdout.write(` ${cluster.node_count ?? "?"} nodes/${cluster.gpu_count ?? "?"} GPUs`);
9073
9098
  stdout.write(` network=${cluster.network_mode || "public"}`);
@@ -9157,13 +9182,13 @@ function writeEligibleClusterNodes(stdout, payload) {
9157
9182
 
9158
9183
  function writeClusterDetail(stdout, cluster = {}) {
9159
9184
  stdout.write(`${cluster.reservation_id || cluster.id || "cluster"}\n`);
9185
+ stdout.write(`Name: ${cluster.name || numberedClusterName([cluster], cluster)}\n`);
9160
9186
  stdout.write(`Type: ${displayClusterType(cluster.access_mode)}\n`);
9161
9187
  stdout.write(`State: ${cluster.state || "unknown"}\n`);
9162
9188
  stdout.write(`Network: ${cluster.network_mode || "public"}\n`);
9163
9189
  stdout.write(`Nodes: ${cluster.node_count ?? "unknown"}\n`);
9164
9190
  stdout.write(`GPUs: ${cluster.gpu_count ?? "unknown"}\n`);
9165
9191
  writeOptionalStatusLine(stdout, "GPU type", cluster.gpu_type);
9166
- writeOptionalStatusLine(stdout, "Name", cluster.cluster_name);
9167
9192
  writeOptionalStatusLine(stdout, "Endpoint", cluster.endpoint);
9168
9193
  writeOptionalStatusLine(stdout, "Namespace", cluster.namespace);
9169
9194
  writeOptionalStatusLine(stdout, "Credentials", cluster.credential_state);
@@ -9635,7 +9660,12 @@ function looksLikePublicKey(value) {
9635
9660
  function looksLikePath(value) {
9636
9661
  const text = String(value || "").trim();
9637
9662
  return (
9638
- text.startsWith("~") || text.startsWith(".") || text.includes("/") || text.endsWith(".pub")
9663
+ text.startsWith("~") ||
9664
+ text.startsWith(".") ||
9665
+ text.includes("/") ||
9666
+ text.includes("\\") ||
9667
+ /^[A-Za-z]:/.test(text) ||
9668
+ text.endsWith(".pub")
9639
9669
  );
9640
9670
  }
9641
9671
 
@@ -79,7 +79,7 @@ export const listNodesCapability = defineCapability({
79
79
  roles: ["user", "reviewer", "admin"],
80
80
  description: "List nodes. Users see their own reserved machines. Reviewers and admins see enrolled, ghost, and off-grid nodes. Call with no filters for the whole fleet. Optional email lists nodes for that user's organizations. Optional operator_id, facility_id, and status (ghost, off-grid, bare-metal) narrow the same list. Never invent nodes. Never print private keys.",
81
81
  mutation: "read",
82
- http: { method: "GET", path: "/internal/nodes" },
82
+ http: { method: "GET", path: "/nodes" },
83
83
  slack: {
84
84
  parameters: {
85
85
  type: "object",
@@ -107,7 +107,7 @@ export const listNodesCapability = defineCapability({
107
107
  if (ctx.role === "user") {
108
108
  return ctx.fetch("/nodes", { method: "GET" });
109
109
  }
110
- return ctx.fetch(queryPath("/internal/nodes", {
110
+ return ctx.fetch(queryPath("/nodes", {
111
111
  operator_id: parsed.operator_id ?? parsed.operatorId ?? parsed.operator,
112
112
  facility_id: parsed.facility_id ?? parsed.facilityId,
113
113
  status: parsed.status,