@ornncompute/cli 0.2.17 → 0.2.19

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/src/color.mjs ADDED
@@ -0,0 +1,71 @@
1
+ // SGR open/close pairs for the four styles the CLI uses.
2
+ const CODES = { red: [31, 39], green: [32, 39], bold: [1, 22], dim: [2, 22] };
3
+
4
+ function createColors(enabled) {
5
+ return Object.fromEntries(
6
+ Object.entries(CODES).map(([name, [open, close]]) => [
7
+ name,
8
+ enabled ? (text) => `\x1b[${open}m${text}\x1b[${close}m` : (text) => text,
9
+ ])
10
+ );
11
+ }
12
+
13
+ const NO_COLOR_FLAG = "--no-color";
14
+
15
+ // Structured-error label prefixes that get dim/bold styling. Matched at the
16
+ // start of a line; the remainder of the line (the value) is left untouched.
17
+ const LABEL_PREFIX_RE = /^(Code|Reason|Next steps?):/;
18
+
19
+ // `--no-color` is global: strip it anywhere in argv so per-command parsing
20
+ // never sees it as an unknown option.
21
+ export function extractNoColorFlag(argv) {
22
+ return {
23
+ argv: argv.filter((arg) => arg !== NO_COLOR_FLAG),
24
+ noColor: argv.includes(NO_COLOR_FLAG),
25
+ };
26
+ }
27
+
28
+ function streamSupportsColor(stream, env, noColorFlag) {
29
+ if (noColorFlag || Object.hasOwn(env ?? {}, "NO_COLOR")) {
30
+ return false;
31
+ }
32
+ if (Object.hasOwn(env ?? {}, "FORCE_COLOR")) {
33
+ return String(env.FORCE_COLOR) !== "0";
34
+ }
35
+ return Boolean(stream.isTTY);
36
+ }
37
+
38
+ // stdout and stderr are evaluated independently: one can be a TTY while the
39
+ // other is piped.
40
+ export function attachStreamColors({ env, noColorFlag, stdout, stderr }) {
41
+ stdout.pc = createColors(streamSupportsColor(stdout, env, noColorFlag));
42
+ stderr.pc = createColors(streamSupportsColor(stderr, env, noColorFlag));
43
+ }
44
+
45
+ export function writeColored(stream, variant, text) {
46
+ const colorize = stream.pc?.[variant];
47
+ stream.write(`${colorize ? colorize(text) : text}\n`);
48
+ }
49
+
50
+ // First line is the error summary (red); later `Code:`/`Reason:`/`Next step:`
51
+ // label prefixes are dim/bold, their values left plain.
52
+ export function colorizeErrorMessage(message, stream) {
53
+ const pc = stream?.pc;
54
+ if (!pc) {
55
+ return message;
56
+ }
57
+ return message
58
+ .split("\n")
59
+ .map((line, index) => {
60
+ if (index === 0) {
61
+ return line ? pc.red(line) : line;
62
+ }
63
+ const match = line.match(LABEL_PREFIX_RE);
64
+ if (!match) {
65
+ return line;
66
+ }
67
+ const label = match[0];
68
+ return `${pc.bold(pc.dim(label))}${line.slice(label.length)}`;
69
+ })
70
+ .join("\n");
71
+ }
@@ -44,6 +44,7 @@ export declare const listingCreateInput: z.ZodObject<{
44
44
  }, z.core.$strip>;
45
45
  export declare const listCatalogTermsCapability: import("../capability.ts").Capability<unknown, unknown>;
46
46
  export declare const listListingsCapability: import("../capability.ts").Capability<unknown, unknown>;
47
+ export declare const forecastListingsCapability: import("../capability.ts").Capability<unknown, unknown>;
47
48
  export declare const createListingCapability: import("../capability.ts").Capability<unknown, unknown>;
48
49
  export declare const listingCapabilities: import("../capability.ts").Capability<unknown, unknown>[];
49
50
  /** @deprecated Use createListingCapability. */
@@ -1,6 +1,14 @@
1
1
  import { z } from "zod";
2
2
  import { defineCapability, preview } from "../capability.js";
3
3
  import { bindHardwareFields, HARDWARE_LISTING_FIELDS, HARDWARE_LISTING_FILTERS, loadHardwareCatalog, projectCatalogTerms, } from "../spec-catalog.js";
4
+ function queryPath(path, params) {
5
+ const parts = [];
6
+ for (const [key, value] of Object.entries(params)) {
7
+ if (value?.trim())
8
+ parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.trim())}`);
9
+ }
10
+ return parts.length ? `${path}?${parts.join("&")}` : path;
11
+ }
4
12
  export const listingCreateInput = z.object({
5
13
  operatorId: z.string().optional(),
6
14
  operator_id: z.string().optional(),
@@ -46,6 +54,10 @@ export const listingCreateInput = z.object({
46
54
  confirm: z.boolean().optional(),
47
55
  });
48
56
  const catalogTermsInput = z.object({ kind: z.enum(["gpu", "cpu", "node", "link"]).optional() });
57
+ const listingForecastInput = z.object({
58
+ limit: z.number().int().min(1).max(500).optional(),
59
+ cursor: z.string().min(1).optional(),
60
+ });
49
61
  const slackListingCreateParams = {
50
62
  type: "object",
51
63
  properties: {
@@ -135,6 +147,23 @@ export const listListingsCapability = defineCapability({
135
147
  return ctx.fetch("/listings/available", { method: "GET", body: bound.payload });
136
148
  },
137
149
  });
150
+ export const forecastListingsCapability = defineCapability({
151
+ id: "listings.forecast",
152
+ domain: "listings",
153
+ roles: ["user"],
154
+ description: "List the next 48 hourly availability buckets for customer-visible compute listings.",
155
+ mutation: "read",
156
+ http: { method: "GET", path: "/v1/cli/compute/listings/forecast" },
157
+ slack: false,
158
+ input: listingForecastInput,
159
+ execute: async (ctx, input) => {
160
+ const parsed = listingForecastInput.parse(input);
161
+ return ctx.fetch(queryPath("/v1/cli/compute/listings/forecast", {
162
+ limit: parsed.limit?.toString(),
163
+ cursor: parsed.cursor,
164
+ }), { method: "GET" });
165
+ },
166
+ });
138
167
  /** Commerce parses listing windows as RFC3339 timestamps, not bare dates. */
139
168
  function rfc3339(value) {
140
169
  const trimmed = value.trim();
@@ -317,6 +346,7 @@ export const createListingCapability = defineCapability({
317
346
  export const listingCapabilities = [
318
347
  listCatalogTermsCapability,
319
348
  listListingsCapability,
349
+ forecastListingsCapability,
320
350
  createListingCapability,
321
351
  ];
322
352
  /** @deprecated Use createListingCapability. */
@@ -65,7 +65,7 @@ export type Capability<I = unknown, O = unknown> = CapabilityBindings & {
65
65
  execute: (ctx: CapabilityContext, input: I) => Promise<O>;
66
66
  };
67
67
  export type CapabilitySpec<I = unknown, O = unknown> = Omit<Capability<I, O>, "cli" | "mcp" | "slack"> & {
68
- slack?: {
68
+ slack?: false | {
69
69
  parameters?: JsonSchema;
70
70
  };
71
71
  };
@@ -31,14 +31,19 @@ export function preview(action, payload) {
31
31
  /** Fill CLI/MCP/Slack names from `id`. Slack parameters stay optional. */
32
32
  export function defineCapability(spec) {
33
33
  const names = namesFromId(spec.id);
34
+ const { slack, ...rest } = spec;
34
35
  return {
35
- ...spec,
36
+ ...rest,
36
37
  cli: { command: names.cli },
37
38
  mcp: { name: names.mcp },
38
- slack: {
39
- name: names.slack,
40
- ...(spec.slack?.parameters ? { parameters: spec.slack.parameters } : {}),
41
- },
39
+ ...(slack === false
40
+ ? {}
41
+ : {
42
+ slack: {
43
+ name: names.slack,
44
+ ...(slack?.parameters ? { parameters: slack.parameters } : {}),
45
+ },
46
+ }),
42
47
  };
43
48
  }
44
49
  /**
@@ -4,7 +4,7 @@ export { catalog } from "./catalog.ts";
4
4
  export { fleetCapabilities, listFacilitiesCapability, listNodesCapability, listOperatorsCapability, nodeExecCapability, nodeOffGridCapability, nodeOnGridCapability, nodeTerminateCapability, } from "./capabilities/fleet.ts";
5
5
  export { buildEnrollmentInstallCommand, tokenCapabilities, tokensCreateCapability, tokensListCapability, } from "./capabilities/tokens.ts";
6
6
  export { identityCapabilities, statusCapability, whoamiCapability } from "./capabilities/identity.ts";
7
- export { createListingCapability, listingCreateInput, listCatalogTermsCapability, listingCapabilities, listListingsCapability, proposeListingCapability, } from "./capabilities/listings.ts";
7
+ export { createListingCapability, forecastListingsCapability, listingCreateInput, listCatalogTermsCapability, listingCapabilities, listListingsCapability, proposeListingCapability, } from "./capabilities/listings.ts";
8
8
  export { userCapabilities, usersQueryCapability } from "./capabilities/users.ts";
9
9
  export { kubernetesCapabilities, slurmCapabilities } from "./capabilities/clusters.ts";
10
10
  export { logsLatestCapability, logsTailCapability, observabilityCapabilities, telemetryLatestCapability, telemetryTailCapability, } from "./capabilities/observability.ts";
@@ -4,7 +4,7 @@ export { catalog } from "./catalog.js";
4
4
  export { fleetCapabilities, listFacilitiesCapability, listNodesCapability, listOperatorsCapability, nodeExecCapability, nodeOffGridCapability, nodeOnGridCapability, nodeTerminateCapability, } from "./capabilities/fleet.js";
5
5
  export { buildEnrollmentInstallCommand, tokenCapabilities, tokensCreateCapability, tokensListCapability, } from "./capabilities/tokens.js";
6
6
  export { identityCapabilities, statusCapability, whoamiCapability } from "./capabilities/identity.js";
7
- export { createListingCapability, listingCreateInput, listCatalogTermsCapability, listingCapabilities, listListingsCapability, proposeListingCapability, } from "./capabilities/listings.js";
7
+ export { createListingCapability, forecastListingsCapability, listingCreateInput, listCatalogTermsCapability, listingCapabilities, listListingsCapability, proposeListingCapability, } from "./capabilities/listings.js";
8
8
  export { userCapabilities, usersQueryCapability } from "./capabilities/users.js";
9
9
  export { kubernetesCapabilities, slurmCapabilities } from "./capabilities/clusters.js";
10
10
  export { logsLatestCapability, logsTailCapability, observabilityCapabilities, telemetryLatestCapability, telemetryTailCapability, } from "./capabilities/observability.js";