@ornncompute/cli 0.1.9 → 0.2.0

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.
@@ -0,0 +1,59 @@
1
+ import { namesFromId } from "./names.js";
2
+ export const DOMAINS = [
3
+ "identity",
4
+ "listings",
5
+ "fleet",
6
+ "users",
7
+ "reservations",
8
+ "bids",
9
+ "access",
10
+ "clusters",
11
+ "slurm",
12
+ "kubernetes",
13
+ "networks",
14
+ "storage",
15
+ "vpn",
16
+ "billing",
17
+ ];
18
+ /** Fill CLI/MCP/Slack names from `id`. Slack parameters stay optional. */
19
+ export function defineCapability(spec) {
20
+ const names = namesFromId(spec.id);
21
+ return {
22
+ ...spec,
23
+ cli: { command: names.cli },
24
+ mcp: { name: names.mcp },
25
+ slack: {
26
+ name: names.slack,
27
+ ...(spec.slack?.parameters ? { parameters: spec.slack.parameters } : {}),
28
+ },
29
+ };
30
+ }
31
+ export function slackStaffVisible(capability) {
32
+ return capability.roles.some((role) => role === "reviewer" || role === "admin");
33
+ }
34
+ export function visibleToClient(capability, client, role) {
35
+ if (!capability.roles.includes(role))
36
+ return false;
37
+ if (client === "slack") {
38
+ if (role === "user")
39
+ return false;
40
+ return slackStaffVisible(capability) && capability.slack != null;
41
+ }
42
+ if (client === "cli")
43
+ return capability.cli != null;
44
+ return capability.mcp != null;
45
+ }
46
+ export function forRole(catalog, role, client) {
47
+ return catalog.filter((capability) => visibleToClient(capability, client, role));
48
+ }
49
+ export function byMcpName(catalog, name) {
50
+ return catalog.find((capability) => capability.mcp?.name === name);
51
+ }
52
+ export function bySlackName(catalog, name) {
53
+ return catalog.find((capability) => capability.slack?.name === name);
54
+ }
55
+ export function byCliCommand(catalog, command) {
56
+ return catalog.find((capability) => capability.cli != null &&
57
+ capability.cli.command.length === command.length &&
58
+ capability.cli.command.every((part, index) => part === command[index]));
59
+ }
@@ -0,0 +1,3 @@
1
+ import type { Capability } from "./capability.ts";
2
+ /** System of record for CLI, MCP, and Slack verbs. Add capabilities here. */
3
+ export declare const catalog: Capability[];
@@ -0,0 +1,14 @@
1
+ import { kubernetesCapabilities, slurmCapabilities } from "./capabilities/clusters.js";
2
+ import { fleetCapabilities } from "./capabilities/fleet.js";
3
+ import { identityCapabilities } from "./capabilities/identity.js";
4
+ import { listingCapabilities } from "./capabilities/listings.js";
5
+ import { userCapabilities } from "./capabilities/users.js";
6
+ /** System of record for CLI, MCP, and Slack verbs. Add capabilities here. */
7
+ export const catalog = [
8
+ ...identityCapabilities,
9
+ ...listingCapabilities,
10
+ ...fleetCapabilities,
11
+ ...userCapabilities,
12
+ ...slurmCapabilities,
13
+ ...kubernetesCapabilities,
14
+ ];
@@ -0,0 +1,10 @@
1
+ export { byCliCommand, byMcpName, bySlackName, defineCapability, forRole, slackStaffVisible, visibleToClient, type Capability, type CapabilityBindings, type CapabilityContext, type CapabilitySpec, type JsonSchema, type CapabilityTransport, type Domain, type HttpSpec, type Mutation, } from "./capability.ts";
2
+ export { namesFromId } from "./names.ts";
3
+ export { catalog } from "./catalog.ts";
4
+ export { fleetCapabilities, listFacilitiesCapability, listNodesCapability, listOperatorsCapability, nodesConsoleCapability, } from "./capabilities/fleet.ts";
5
+ export { identityCapabilities, statusCapability, whoamiCapability } from "./capabilities/identity.ts";
6
+ export { createListingCapability, listingCreateInput, listCatalogTermsCapability, listingCapabilities, listListingsCapability, proposeListingCapability, } from "./capabilities/listings.ts";
7
+ export { userCapabilities, usersQueryCapability } from "./capabilities/users.ts";
8
+ export { kubernetesCapabilities, slurmCapabilities } from "./capabilities/clusters.ts";
9
+ export { CLIENTS, parseRole, roleAtLeast, ROLES, type ClientKind, type Role } from "./role.ts";
10
+ export { bindHardwareFields, HARDWARE_CATALOG_PATH, HARDWARE_KINDS, HARDWARE_LISTING_FIELDS, HARDWARE_LISTING_FILTERS, loadHardwareCatalog, looksLikeSpecDump, normalizeCatalogQuery, parseHardwareCatalog, projectCatalogTerms, proposeListingPayload, resolveCatalogTerm, UNRESOLVED_CATALOG_HINT, type CatalogBindResult, type CatalogTerm, type HardwareCatalog, type HardwareKind, type ResolvedCatalogTerm, } from "./spec-catalog.ts";
@@ -0,0 +1,10 @@
1
+ export { byCliCommand, byMcpName, bySlackName, defineCapability, forRole, slackStaffVisible, visibleToClient, } from "./capability.js";
2
+ export { namesFromId } from "./names.js";
3
+ export { catalog } from "./catalog.js";
4
+ export { fleetCapabilities, listFacilitiesCapability, listNodesCapability, listOperatorsCapability, nodesConsoleCapability, } from "./capabilities/fleet.js";
5
+ export { identityCapabilities, statusCapability, whoamiCapability } from "./capabilities/identity.js";
6
+ export { createListingCapability, listingCreateInput, listCatalogTermsCapability, listingCapabilities, listListingsCapability, proposeListingCapability, } from "./capabilities/listings.js";
7
+ export { userCapabilities, usersQueryCapability } from "./capabilities/users.js";
8
+ export { kubernetesCapabilities, slurmCapabilities } from "./capabilities/clusters.js";
9
+ export { CLIENTS, parseRole, roleAtLeast, ROLES } from "./role.js";
10
+ export { bindHardwareFields, HARDWARE_CATALOG_PATH, HARDWARE_KINDS, HARDWARE_LISTING_FIELDS, HARDWARE_LISTING_FILTERS, loadHardwareCatalog, looksLikeSpecDump, normalizeCatalogQuery, parseHardwareCatalog, projectCatalogTerms, proposeListingPayload, resolveCatalogTerm, UNRESOLVED_CATALOG_HINT, } from "./spec-catalog.js";
@@ -0,0 +1,6 @@
1
+ /** Shared wire names derived from `Capability.id`. */
2
+ export declare function namesFromId(id: string): {
3
+ mcp: string;
4
+ slack: string;
5
+ cli: string[];
6
+ };
@@ -0,0 +1,6 @@
1
+ /** Shared wire names derived from `Capability.id`. */
2
+ export function namesFromId(id) {
3
+ const wire = id.replaceAll(".", "_");
4
+ const cli = id === "identity.whoami" ? ["whoami"] : id === "identity.status" ? ["status"] : id.split(".");
5
+ return { mcp: wire, slack: wire, cli };
6
+ }
@@ -0,0 +1,6 @@
1
+ export declare const ROLES: readonly ["user", "reviewer", "admin"];
2
+ export type Role = (typeof ROLES)[number];
3
+ export declare const CLIENTS: readonly ["cli", "mcp", "slack"];
4
+ export type ClientKind = (typeof CLIENTS)[number];
5
+ export declare function roleAtLeast(have: Role, min: Role): boolean;
6
+ export declare function parseRole(raw: unknown): Role | null;
@@ -0,0 +1,17 @@
1
+ export const ROLES = ["user", "reviewer", "admin"];
2
+ export const CLIENTS = ["cli", "mcp", "slack"];
3
+ const ROLE_RANK = {
4
+ user: 0,
5
+ reviewer: 1,
6
+ admin: 2,
7
+ };
8
+ export function roleAtLeast(have, min) {
9
+ return ROLE_RANK[have] >= ROLE_RANK[min];
10
+ }
11
+ export function parseRole(raw) {
12
+ if (raw === "admin" || raw === "reviewer" || raw === "user")
13
+ return raw;
14
+ if (raw === "operator")
15
+ return "reviewer";
16
+ return null;
17
+ }
@@ -0,0 +1,78 @@
1
+ import type { CapabilityTransport } from "./capability.ts";
2
+ /** Live orchestrator dictionary: `GET /v1/orchestrator/catalog` from `spec/dictionary.csv`. */
3
+ export declare const HARDWARE_CATALOG_PATH = "/v1/orchestrator/catalog";
4
+ export declare const HARDWARE_KINDS: readonly ["gpu", "cpu", "node", "link"];
5
+ export type HardwareKind = (typeof HARDWARE_KINDS)[number];
6
+ export declare const UNRESOLVED_CATALOG_HINT = "Leave this field empty. Only dictionary.csv product names are allowed. Do not ask to add spec-sheet text to the dictionary.";
7
+ export declare const HARDWARE_LISTING_FIELDS: readonly [{
8
+ readonly field: "gpu_type";
9
+ readonly kind: "gpu";
10
+ }, {
11
+ readonly field: "cpu";
12
+ readonly kind: "cpu";
13
+ }, {
14
+ readonly field: "node_model";
15
+ readonly kind: "node";
16
+ }, {
17
+ readonly field: "fabric_type";
18
+ readonly kind: "link";
19
+ }];
20
+ export declare const HARDWARE_LISTING_FILTERS: readonly [{
21
+ readonly field: "gpu";
22
+ readonly kind: "gpu";
23
+ }, {
24
+ readonly field: "cpu";
25
+ readonly kind: "cpu";
26
+ }, {
27
+ readonly field: "fabric";
28
+ readonly kind: "link";
29
+ }];
30
+ export type CatalogTerm = {
31
+ kind: string;
32
+ canonical: string;
33
+ display: string;
34
+ aliases?: string[];
35
+ link_category?: string;
36
+ };
37
+ export type HardwareCatalog = {
38
+ terms: CatalogTerm[];
39
+ };
40
+ export type ResolvedCatalogTerm = {
41
+ canonical: string | null;
42
+ display: string | null;
43
+ hint?: string;
44
+ kind?: string;
45
+ };
46
+ export type CatalogBindFailure = {
47
+ ok: false;
48
+ error: "unknown_catalog_term";
49
+ rejected: {
50
+ field: string;
51
+ value: string;
52
+ }[];
53
+ hint: string;
54
+ };
55
+ export type CatalogBindSuccess<T> = {
56
+ ok: true;
57
+ payload: T;
58
+ };
59
+ export type CatalogBindResult<T> = CatalogBindSuccess<T> | CatalogBindFailure;
60
+ export declare function looksLikeSpecDump(query: string): boolean;
61
+ export declare function normalizeCatalogQuery(value: string): string;
62
+ export declare function parseHardwareCatalog(payload: unknown): HardwareCatalog;
63
+ export declare function loadHardwareCatalog(fetchImpl: CapabilityTransport): Promise<HardwareCatalog>;
64
+ export declare function resolveCatalogTerm(terms: readonly CatalogTerm[], kind: string, query: string): ResolvedCatalogTerm;
65
+ export declare function bindHardwareFields<T extends Record<string, unknown>>(terms: readonly CatalogTerm[], input: T, fields: readonly {
66
+ field: string;
67
+ kind: HardwareKind;
68
+ }[]): CatalogBindResult<T>;
69
+ export declare function proposeListingPayload(terms: readonly CatalogTerm[], args: Record<string, unknown>): CatalogBindResult<Record<string, unknown>> & {
70
+ action?: "create_listing";
71
+ };
72
+ export declare function projectCatalogTerms(terms: readonly CatalogTerm[], kind?: string): {
73
+ terms: {
74
+ kind: string;
75
+ display: string;
76
+ aliases: string[];
77
+ }[];
78
+ };
@@ -0,0 +1,128 @@
1
+ /** Live orchestrator dictionary: `GET /v1/orchestrator/catalog` from `spec/dictionary.csv`. */
2
+ export const HARDWARE_CATALOG_PATH = "/v1/orchestrator/catalog";
3
+ export const HARDWARE_KINDS = ["gpu", "cpu", "node", "link"];
4
+ export const UNRESOLVED_CATALOG_HINT = "Leave this field empty. Only dictionary.csv product names are allowed. Do not ask to add spec-sheet text to the dictionary.";
5
+ export const HARDWARE_LISTING_FIELDS = [
6
+ { field: "gpu_type", kind: "gpu" },
7
+ { field: "cpu", kind: "cpu" },
8
+ { field: "node_model", kind: "node" },
9
+ { field: "fabric_type", kind: "link" },
10
+ ];
11
+ export const HARDWARE_LISTING_FILTERS = [
12
+ { field: "gpu", kind: "gpu" },
13
+ { field: "cpu", kind: "cpu" },
14
+ { field: "fabric", kind: "link" },
15
+ ];
16
+ export function looksLikeSpecDump(query) {
17
+ const trimmed = query.trim();
18
+ if (!trimmed)
19
+ return false;
20
+ if ([...trimmed].length > 72)
21
+ return true;
22
+ if ((trimmed.match(/,/g) ?? []).length >= 2)
23
+ return true;
24
+ const lower = trimmed.toLowerCase();
25
+ return (lower.includes("ghz") ||
26
+ lower.includes("tdp") ||
27
+ lower.includes("gt/s") ||
28
+ lower.includes("mb cache") ||
29
+ lower.includes("thread") ||
30
+ lower.includes("-core") ||
31
+ lower.includes("core/") ||
32
+ lower.includes("ddr5") ||
33
+ lower.includes("ddr4"));
34
+ }
35
+ export function normalizeCatalogQuery(value) {
36
+ let out = "";
37
+ let prevSpace = false;
38
+ for (const ch of value) {
39
+ const mapped = ch === "×" ? "x" : /[\u2010-\u2014]/.test(ch) ? "-" : ch.toLowerCase();
40
+ if (/[a-z0-9]/i.test(mapped)) {
41
+ out += mapped;
42
+ prevSpace = false;
43
+ }
44
+ else if (out && !prevSpace) {
45
+ out += " ";
46
+ prevSpace = true;
47
+ }
48
+ }
49
+ return out.trim();
50
+ }
51
+ export function parseHardwareCatalog(payload) {
52
+ const row = payload && typeof payload === "object" ? payload : null;
53
+ const raw = Array.isArray(row?.terms) ? row.terms : Array.isArray(payload) ? payload : [];
54
+ const terms = raw.filter((term) => {
55
+ if (!term || typeof term !== "object")
56
+ return false;
57
+ const candidate = term;
58
+ return (typeof candidate.kind === "string" &&
59
+ typeof candidate.canonical === "string" &&
60
+ typeof candidate.display === "string");
61
+ });
62
+ if (!terms.length)
63
+ throw new Error("catalog terms unavailable");
64
+ return { terms };
65
+ }
66
+ export async function loadHardwareCatalog(fetchImpl) {
67
+ return parseHardwareCatalog(await fetchImpl(HARDWARE_CATALOG_PATH, { method: "GET" }));
68
+ }
69
+ export function resolveCatalogTerm(terms, kind, query) {
70
+ if (looksLikeSpecDump(query) || !normalizeCatalogQuery(query) || !kind.trim()) {
71
+ return { canonical: null, display: null, hint: UNRESOLVED_CATALOG_HINT };
72
+ }
73
+ const needle = normalizeCatalogQuery(query);
74
+ for (const term of terms) {
75
+ if (term.kind.toLowerCase() !== kind.toLowerCase())
76
+ continue;
77
+ const aliases = [term.display, term.canonical, ...(term.aliases ?? [])];
78
+ if (aliases.some((alias) => normalizeCatalogQuery(alias) === needle)) {
79
+ return { canonical: term.canonical, display: term.display, kind: term.kind };
80
+ }
81
+ }
82
+ return { canonical: null, display: null, hint: UNRESOLVED_CATALOG_HINT };
83
+ }
84
+ export function bindHardwareFields(terms, input, fields) {
85
+ const payload = { ...input };
86
+ const rejected = [];
87
+ for (const { field, kind } of fields) {
88
+ const value = payload[field];
89
+ if (typeof value !== "string")
90
+ continue;
91
+ if (!value.trim()) {
92
+ delete payload[field];
93
+ continue;
94
+ }
95
+ const resolved = resolveCatalogTerm(terms, kind, value);
96
+ if (resolved.display) {
97
+ payload[field] = resolved.display;
98
+ continue;
99
+ }
100
+ rejected.push({ field, value });
101
+ }
102
+ if (rejected.length) {
103
+ return {
104
+ ok: false,
105
+ error: "unknown_catalog_term",
106
+ rejected,
107
+ hint: "Call listings_catalog_terms and retry with a display name from the dump. Do not ask to add spec-sheet text to the dictionary.",
108
+ };
109
+ }
110
+ return { ok: true, payload };
111
+ }
112
+ export function proposeListingPayload(terms, args) {
113
+ const bound = bindHardwareFields(terms, args, HARDWARE_LISTING_FIELDS);
114
+ if (!bound.ok)
115
+ return bound;
116
+ return { ok: true, action: "create_listing", payload: bound.payload };
117
+ }
118
+ export function projectCatalogTerms(terms, kind) {
119
+ return {
120
+ terms: terms
121
+ .filter((term) => (kind?.trim() ? term.kind.toLowerCase() === kind.toLowerCase() : true))
122
+ .map((term) => ({
123
+ kind: term.kind,
124
+ display: term.display,
125
+ aliases: term.aliases ?? [],
126
+ })),
127
+ };
128
+ }