@zhuoyuezs/ml-platform 0.1.9 → 0.1.10
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 +7 -3
- package/package.json +1 -1
- package/checksums.json +0 -155
- package/release.json +0 -52
- package/runtime/business-client/README.md +0 -78
- package/runtime/business-client/package-lock.json +0 -19
- package/runtime/business-client/package.json +0 -23
- package/runtime/business-client/src/catalog.js +0 -206
- package/runtime/business-client/src/cli.js +0 -533
- package/runtime/business-client/src/config.js +0 -56
- package/runtime/business-client/src/http.js +0 -254
- package/skills/feature-management/SKILL.md +0 -480
- package/skills/feature-management/agents/openai.yaml +0 -4
- package/skills/feature-management/assets/catalog-template/catalog.json +0 -23
- package/skills/feature-management/assets/catalog-template/datasets/example_temperature_training.v1.json +0 -40
- package/skills/feature-management/assets/catalog-template/feature_sets/example_temperature_core.v1.json +0 -14
- package/skills/feature-management/assets/catalog-template/features/example_temperature_mean_5m.v1.json +0 -28
- package/skills/feature-management/assets/catalog-template/operator_package/pyproject.toml +0 -12
- package/skills/feature-management/assets/catalog-template/operator_package/src/business_feature_operator_template/__init__.py +0 -39
- package/skills/feature-management/assets/catalog-template/operator_package/tests/test_operator.py +0 -83
- package/skills/feature-management/assets/catalog-template/operators/example_temperature_features.v1.json +0 -58
- package/skills/feature-management/assets/catalog-template/parameters/example_temperature.v1.json +0 -58
- package/skills/feature-management/references/commands.md +0 -358
- package/skills/feature-management/references/contracts.md +0 -731
- package/skills/feature-management/references/operator-authoring.md +0 -175
- package/skills/feature-management/references/platform-capability-guide.md +0 -75
- package/skills/feature-management/references/supervised-datasets.md +0 -101
- package/skills/model-lifecycle-management/SKILL.md +0 -38
- package/skills/model-lifecycle-management/agents/openai.yaml +0 -4
- package/skills/model-lifecycle-management/references/discovery.md +0 -89
- package/skills/model-lifecycle-management/references/evaluation.md +0 -172
- package/skills/model-lifecycle-management/references/packaging.md +0 -56
- package/skills/model-lifecycle-management/references/training-contracts.md +0 -164
- package/skills/model-lifecycle-management/references/training.md +0 -85
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
"use strict";
|
|
4
|
-
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
const path = require("path");
|
|
7
|
-
const { configPath, expandHome, loadApiUrl, normalizeApiUrl, saveApiUrl } = require("./config");
|
|
8
|
-
const { PlatformApiClient } = require("./http");
|
|
9
|
-
const { applyCatalog, loadCatalog, normalizeDataset, normalizeRegistryDataset } = require("./catalog");
|
|
10
|
-
const { version: CLIENT_VERSION } = require("../package.json");
|
|
11
|
-
|
|
12
|
-
const LIFECYCLE_COLLECTIONS = new Set(["training-runs", "training-jobs", "model-artifacts", "evaluation-configs", "evaluation-runs", "evaluation-jobs", "metric-definitions", "executable-packages", "model-packages"]);
|
|
13
|
-
|
|
14
|
-
const BUSINESS_COMMANDS = new Set([
|
|
15
|
-
...[...LIFECYCLE_COLLECTIONS].map((name) => `list-${name}`),
|
|
16
|
-
"get-model-runtime-release", "get-training-run", "get-model-artifact", "get-executable-package",
|
|
17
|
-
"validate-training-job", "validate-model-package",
|
|
18
|
-
"version", "create-project", "list-projects", "get-project", "delete-project",
|
|
19
|
-
"configure", "show-config", "health", "list-parameters", "list-operators", "list-features",
|
|
20
|
-
"list-feature-sets", "list-datasets", "get-dataset", "list-jobs", "resolve-manifest",
|
|
21
|
-
"resolve-dataset", "build-dataset", "build-registered-dataset",
|
|
22
|
-
"get-parameter", "get-feature", "get-feature-set", "get-operator",
|
|
23
|
-
"delete-parameter", "delete-feature", "delete-feature-set",
|
|
24
|
-
"restore-parameter", "restore-feature", "restore-feature-set",
|
|
25
|
-
"get-parameter-lifecycle", "get-feature-lifecycle", "get-feature-set-lifecycle",
|
|
26
|
-
"list-parameter-references", "list-feature-references", "list-feature-set-references",
|
|
27
|
-
"list-parameter-versions", "list-feature-versions", "list-feature-set-versions", "list-operator-versions",
|
|
28
|
-
"add-parameter", "add-feature", "add-feature-set", "add-operator", "publish-operator",
|
|
29
|
-
"add-dataset", "update-dataset", "delete-dataset", "list-dataset-artifacts",
|
|
30
|
-
"register-operator", "show-operator-specs", "delete-operator", "run-operator",
|
|
31
|
-
"get-job", "wait-job", "retry-job", "cancel-job", "get-dataset-artifact", "download-dataset-artifact", "fetch-inference-data", "fetch-inference-context", "apply",
|
|
32
|
-
"register-trainer", "list-trainer-definitions", "get-trainer-definition", "register-training-run", "validate-training-run", "submit-training-job", "get-training-job", "retry-training-job", "cancel-training-job",
|
|
33
|
-
"register-evaluation-config", "get-evaluation-config", "register-metric-definition", "get-metric-definition",
|
|
34
|
-
"register-executable-package", "verify-executable-package", "publish-executable-package",
|
|
35
|
-
"validate-evaluation-run", "submit-evaluation", "get-evaluation-run", "get-evaluation-job", "retry-evaluation-job", "cancel-evaluation-job", "list-evaluation-attempts",
|
|
36
|
-
"get-evaluation-result", "get-evaluation-result-metrics", "get-evaluation-result-artifacts", "summarize-evaluation-run", "get-evaluation-summary",
|
|
37
|
-
"create-model-package", "get-model-package", "cancel-model-package", "validate-model-artifact",
|
|
38
|
-
]);
|
|
39
|
-
|
|
40
|
-
const COMMAND_USAGE = {
|
|
41
|
-
...Object.fromEntries([...LIFECYCLE_COLLECTIONS].map((name) => [`list-${name}`, `list-${name} [--limit N] [--offset N] [-q QUERY]`])),
|
|
42
|
-
"get-model-runtime-release": "get-model-runtime-release",
|
|
43
|
-
"get-training-run": "get-training-run RUN_ID",
|
|
44
|
-
"get-model-artifact": "get-model-artifact ARTIFACT_ID",
|
|
45
|
-
"get-executable-package": "get-executable-package NAME VERSION",
|
|
46
|
-
"validate-training-job": "validate-training-job REQUEST_JSON",
|
|
47
|
-
"validate-model-package": "validate-model-package REQUEST_JSON",
|
|
48
|
-
version: "version",
|
|
49
|
-
configure: "configure --api-url URL",
|
|
50
|
-
"show-config": "show-config",
|
|
51
|
-
health: "health",
|
|
52
|
-
"create-project": "create-project NAME [--display-name TEXT] [--description TEXT] [--owner OWNER]",
|
|
53
|
-
"list-projects": "list-projects",
|
|
54
|
-
"get-project": "get-project NAME",
|
|
55
|
-
"delete-project": "delete-project NAME",
|
|
56
|
-
"list-parameters": "list-parameters [--project PROJECT] [--name NAME] [--version VERSION] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
57
|
-
"list-operators": "list-operators [--project PROJECT] [--name NAME] [--version VERSION] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
58
|
-
"list-features": "list-features [--project PROJECT] [--name NAME] [--version VERSION] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
59
|
-
"list-feature-sets": "list-feature-sets [--project PROJECT] [--name NAME] [--version VERSION] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
60
|
-
"list-datasets": "list-datasets [--project PROJECT] [--dataset-id ID] [--dataset-version VERSION] [-q QUERY] [--limit N] [--offset N] [--include-deleted] [--all]",
|
|
61
|
-
"get-parameter": "get-parameter NAME VERSION [--project PROJECT]",
|
|
62
|
-
"get-feature": "get-feature NAME VERSION [--project PROJECT]",
|
|
63
|
-
"get-feature-set": "get-feature-set NAME VERSION [--project PROJECT]",
|
|
64
|
-
"get-operator": "get-operator NAME VERSION [--project PROJECT]",
|
|
65
|
-
"delete-parameter": "delete-parameter NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
66
|
-
"delete-feature": "delete-feature NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
67
|
-
"delete-feature-set": "delete-feature-set NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
68
|
-
"restore-parameter": "restore-parameter NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
69
|
-
"restore-feature": "restore-feature NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
70
|
-
"restore-feature-set": "restore-feature-set NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
71
|
-
"get-parameter-lifecycle": "get-parameter-lifecycle NAME VERSION [--project PROJECT]",
|
|
72
|
-
"get-feature-lifecycle": "get-feature-lifecycle NAME VERSION [--project PROJECT]",
|
|
73
|
-
"get-feature-set-lifecycle": "get-feature-set-lifecycle NAME VERSION [--project PROJECT]",
|
|
74
|
-
"list-parameter-references": "list-parameter-references NAME VERSION [--project PROJECT] [--direct]",
|
|
75
|
-
"list-feature-references": "list-feature-references NAME VERSION [--project PROJECT] [--direct]",
|
|
76
|
-
"list-feature-set-references": "list-feature-set-references NAME VERSION [--project PROJECT] [--direct]",
|
|
77
|
-
"list-parameter-versions": "list-parameter-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
78
|
-
"list-feature-versions": "list-feature-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
79
|
-
"list-feature-set-versions": "list-feature-set-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
80
|
-
"list-operator-versions": "list-operator-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
81
|
-
"add-parameter": "add-parameter SPEC_JSON",
|
|
82
|
-
"add-feature": "add-feature SPEC_JSON",
|
|
83
|
-
"add-feature-set": "add-feature-set SPEC_JSON",
|
|
84
|
-
"add-operator": "add-operator SPEC_JSON",
|
|
85
|
-
"publish-operator": "publish-operator SPEC_JSON --package PATH",
|
|
86
|
-
"add-dataset": "add-dataset MANIFEST_JSON [--force]",
|
|
87
|
-
"update-dataset": "update-dataset MANIFEST_JSON [--force]",
|
|
88
|
-
"delete-dataset": "delete-dataset DATASET_ID DATASET_VERSION [--project PROJECT]",
|
|
89
|
-
"list-dataset-artifacts": "list-dataset-artifacts [--project PROJECT] [--dataset-id ID] [--dataset-version VERSION] [--include-deleted] [--limit N] [--offset N]",
|
|
90
|
-
"get-dataset": "get-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--include-deleted]",
|
|
91
|
-
"list-jobs": "list-jobs [filters] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
92
|
-
"resolve-manifest": "resolve-manifest MANIFEST [--out PATH]",
|
|
93
|
-
"resolve-dataset": "resolve-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--out PATH]",
|
|
94
|
-
"build-dataset": "build-dataset MANIFEST [--engine chronon] [--source-mode direct] [--upload-chronon-metadata] [--partition-duration DURATION] [--max-parallelism N] [--no-resume] [--wait] [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
|
|
95
|
-
"build-registered-dataset": "build-registered-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--engine chronon] [--source-mode direct] [--upload-chronon-metadata] [--partition-duration DURATION] [--max-parallelism N] [--no-resume] [--wait] [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
|
|
96
|
-
"get-job": "get-job JOB_ID",
|
|
97
|
-
"wait-job": "wait-job JOB_ID [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
|
|
98
|
-
"retry-job": "retry-job JOB_ID",
|
|
99
|
-
"cancel-job": "cancel-job JOB_ID",
|
|
100
|
-
"register-operator": "register-operator SPEC_JSON",
|
|
101
|
-
"show-operator-specs": "show-operator-specs [--project PROJECT] [-q QUERY] [--limit N] [--offset N]",
|
|
102
|
-
"delete-operator": "delete-operator NAME VERSION [--project PROJECT]",
|
|
103
|
-
"run-operator": "run-operator NAME VERSION [--project PROJECT] [--config JSON]",
|
|
104
|
-
"get-dataset-artifact": "get-dataset-artifact DATASET_ID MANIFEST_HASH [--project PROJECT]",
|
|
105
|
-
"download-dataset-artifact": "download-dataset-artifact DATASET_ID MANIFEST_HASH [--project PROJECT] --out-dir PATH [--file PATH] [--force]",
|
|
106
|
-
"fetch-inference-data": "fetch-inference-data MANIFEST --cutoff-time TIMESTAMP [--max-workers N] [--max-source-lag-hours HOURS] [--no-validate-freshness] [--allow-missing]",
|
|
107
|
-
"fetch-inference-context": "fetch-inference-context REQUEST_JSON",
|
|
108
|
-
"register-trainer": "register-trainer DEFINITION_JSON",
|
|
109
|
-
"list-trainer-definitions": "list-trainer-definitions",
|
|
110
|
-
"get-trainer-definition": "get-trainer-definition NAME VERSION [--project PROJECT]",
|
|
111
|
-
"register-training-run": "register-training-run RUN_JSON",
|
|
112
|
-
"validate-training-run": "validate-training-run RUN_JSON",
|
|
113
|
-
"submit-training-job": "submit-training-job REQUEST_JSON",
|
|
114
|
-
"get-training-job": "get-training-job JOB_ID",
|
|
115
|
-
"retry-training-job": "retry-training-job JOB_ID",
|
|
116
|
-
"cancel-training-job": "cancel-training-job JOB_ID",
|
|
117
|
-
"register-evaluation-config": "register-evaluation-config CONFIG_JSON",
|
|
118
|
-
"get-evaluation-config": "get-evaluation-config NAME VERSION",
|
|
119
|
-
"register-metric-definition": "register-metric-definition DEFINITION_JSON",
|
|
120
|
-
"get-metric-definition": "get-metric-definition NAME VERSION",
|
|
121
|
-
"register-executable-package": "register-executable-package SPEC_JSON",
|
|
122
|
-
"verify-executable-package": "verify-executable-package NAME VERSION",
|
|
123
|
-
"publish-executable-package": "publish-executable-package NAME VERSION",
|
|
124
|
-
"validate-evaluation-run": "validate-evaluation-run REQUEST_JSON",
|
|
125
|
-
"submit-evaluation": "submit-evaluation REQUEST_JSON",
|
|
126
|
-
"get-evaluation-run": "get-evaluation-run RUN_ID",
|
|
127
|
-
"get-evaluation-job": "get-evaluation-job JOB_ID",
|
|
128
|
-
"retry-evaluation-job": "retry-evaluation-job JOB_ID",
|
|
129
|
-
"cancel-evaluation-job": "cancel-evaluation-job JOB_ID",
|
|
130
|
-
"list-evaluation-attempts": "list-evaluation-attempts JOB_ID",
|
|
131
|
-
"get-evaluation-result": "get-evaluation-result RESULT_ID",
|
|
132
|
-
"get-evaluation-result-metrics": "get-evaluation-result-metrics RESULT_ID",
|
|
133
|
-
"get-evaluation-result-artifacts": "get-evaluation-result-artifacts RESULT_ID",
|
|
134
|
-
"summarize-evaluation-run": "summarize-evaluation-run RUN_ID",
|
|
135
|
-
"get-evaluation-summary": "get-evaluation-summary SUMMARY_ID",
|
|
136
|
-
"create-model-package": "create-model-package REQUEST_JSON",
|
|
137
|
-
"get-model-package": "get-model-package PACKAGE_ID [--package-version VERSION]",
|
|
138
|
-
"cancel-model-package": "cancel-model-package PACKAGE_ID [--package-version VERSION]",
|
|
139
|
-
"validate-model-artifact": "validate-model-artifact ARTIFACT_ID",
|
|
140
|
-
apply: "apply CATALOG_DIRECTORY [--dry-run] [--project PROJECT]",
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
function usage(command) {
|
|
144
|
-
const prefix = "ml-platform [--profile server] [--api-url URL] [--request-timeout-seconds SECONDS]";
|
|
145
|
-
if (command) return `usage: ${prefix} ${COMMAND_USAGE[command]}\n`;
|
|
146
|
-
return `usage: ${prefix} COMMAND [ARGS]\n\ncommands:\n ${[...BUSINESS_COMMANDS].join("\n ")}\n`;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function parse(args) {
|
|
150
|
-
const timeoutValue = process.env.ML_PLATFORM_API_TIMEOUT_SECONDS
|
|
151
|
-
|| process.env.DATA_PLATFORM_DEMO_API_TIMEOUT_SECONDS
|
|
152
|
-
|| 30;
|
|
153
|
-
const options = { profile: "server", timeout: finiteNumber(timeoutValue, "--request-timeout-seconds") };
|
|
154
|
-
let index = 0;
|
|
155
|
-
while (index < args.length && args[index].startsWith("--")) {
|
|
156
|
-
const flag = args[index++];
|
|
157
|
-
if (!["--profile", "--api-url", "--request-timeout-seconds"].includes(flag)) throw new Error(`unknown global argument: ${flag}`);
|
|
158
|
-
if (index >= args.length) throw new Error(`${flag} requires a value`);
|
|
159
|
-
if (flag === "--profile") options.profile = args[index++];
|
|
160
|
-
else if (flag === "--api-url") options.apiUrl = args[index++];
|
|
161
|
-
else options.timeout = finiteNumber(args[index++], "--request-timeout-seconds");
|
|
162
|
-
}
|
|
163
|
-
if (options.profile !== "server") throw new Error("--profile must be server");
|
|
164
|
-
if (!(options.timeout > 0)) throw new Error("--request-timeout-seconds must be positive");
|
|
165
|
-
options.command = args[index++];
|
|
166
|
-
options.rest = args.slice(index);
|
|
167
|
-
options.apiUrl ??= process.env.ML_PLATFORM_API_URL || process.env.DATA_PLATFORM_DEMO_API_URL || loadApiUrl();
|
|
168
|
-
return options;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function take(rest, flag, fallback = undefined) {
|
|
172
|
-
const index = rest.indexOf(flag);
|
|
173
|
-
if (index < 0) return fallback;
|
|
174
|
-
if (index + 1 >= rest.length) throw new Error(`${flag} requires a value`);
|
|
175
|
-
const value = rest[index + 1];
|
|
176
|
-
rest.splice(index, 2);
|
|
177
|
-
return value;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function boolean(rest, flag) {
|
|
181
|
-
const index = rest.indexOf(flag);
|
|
182
|
-
if (index < 0) return false;
|
|
183
|
-
rest.splice(index, 1);
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function finiteNumber(value, label) {
|
|
188
|
-
if (typeof value === "string" && value.trim() === "") throw new Error(`${label} must be numeric`);
|
|
189
|
-
const parsed = Number(value);
|
|
190
|
-
if (!Number.isFinite(parsed)) throw new Error(`${label} must be numeric`);
|
|
191
|
-
return parsed;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function integer(value, label) {
|
|
195
|
-
const parsed = finiteNumber(value, label);
|
|
196
|
-
if (!Number.isInteger(parsed)) throw new Error(`${label} must be an integer`);
|
|
197
|
-
return parsed;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function resolvedPath(value) { return path.resolve(expandHome(value)); }
|
|
201
|
-
|
|
202
|
-
function positional(rest, label) { const value = rest.shift(); if (!value || value.startsWith("--")) throw new Error(`${label} is required`); return value; }
|
|
203
|
-
|
|
204
|
-
function client(options) {
|
|
205
|
-
if (options.profile !== "server") throw new Error("the business client supports only --profile server");
|
|
206
|
-
if (!options.apiUrl) throw new Error("server profile requires --api-url, ML_PLATFORM_API_URL, DATA_PLATFORM_DEMO_API_URL, or a saved configure target");
|
|
207
|
-
return new PlatformApiClient(options.apiUrl, options.timeout);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function readObject(file) {
|
|
211
|
-
const payload = JSON.parse(fs.readFileSync(resolvedPath(file), "utf8"));
|
|
212
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error(`JSON file must contain an object: ${file}`);
|
|
213
|
-
return payload;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function readDataset(file) { return normalizeDataset(readObject(file)); }
|
|
217
|
-
function readSpec(file) { return readObject(file); }
|
|
218
|
-
|
|
219
|
-
function datasetEndpoint(project, datasetId, datasetVersion) {
|
|
220
|
-
return `/datasets/${encodeURIComponent(project)}/${encodeURIComponent(datasetId)}/${encodeURIComponent(datasetVersion)}`;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function lifecycleResource(command) {
|
|
224
|
-
if (command.includes("feature-set")) return "feature-sets";
|
|
225
|
-
if (command.includes("parameter")) return "parameters";
|
|
226
|
-
if (command.includes("feature")) return "features";
|
|
227
|
-
throw new Error(`unsupported lifecycle command: ${command}`);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function lifecycleEndpoint(resource, project, name, version, suffix = "") {
|
|
231
|
-
return `/registry/${resource}/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}${suffix}`;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function registeredDataset(api, project, datasetId, datasetVersion, includeDeleted = false) {
|
|
235
|
-
const payload = await api.get(datasetEndpoint(project, datasetId, datasetVersion), { include_deleted: includeDeleted });
|
|
236
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("registered dataset response must be a JSON object");
|
|
237
|
-
return payload;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function buildOptions(rest) {
|
|
241
|
-
const engine = take(rest, "--engine", "chronon");
|
|
242
|
-
if (engine !== "chronon") throw new Error("--engine must be chronon");
|
|
243
|
-
const sourceMode = take(rest, "--source-mode");
|
|
244
|
-
if (sourceMode !== undefined && sourceMode !== "direct") throw new Error("--source-mode must be direct");
|
|
245
|
-
const maxParallelism = integer(take(rest, "--max-parallelism", 1), "--max-parallelism");
|
|
246
|
-
if (maxParallelism < 1) throw new Error("--max-parallelism must be at least 1");
|
|
247
|
-
const wait = boolean(rest, "--wait");
|
|
248
|
-
const pollInterval = finiteNumber(take(rest, "--poll-interval", 2), "--poll-interval");
|
|
249
|
-
if (!(pollInterval > 0)) throw new Error("--poll-interval must be positive");
|
|
250
|
-
const rawTimeout = take(rest, "--wait-timeout-seconds");
|
|
251
|
-
const waitTimeout = rawTimeout === undefined ? undefined : finiteNumber(rawTimeout, "--wait-timeout-seconds");
|
|
252
|
-
if (waitTimeout !== undefined && !(waitTimeout > 0)) throw new Error("--wait-timeout-seconds must be positive");
|
|
253
|
-
return {
|
|
254
|
-
engine,
|
|
255
|
-
sourceMode,
|
|
256
|
-
upload: boolean(rest, "--upload-chronon-metadata"),
|
|
257
|
-
partitionDuration: take(rest, "--partition-duration"),
|
|
258
|
-
maxParallelism,
|
|
259
|
-
resume: !boolean(rest, "--no-resume"),
|
|
260
|
-
wait,
|
|
261
|
-
pollInterval,
|
|
262
|
-
waitTimeout,
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function submitBuild(api, manifest, build) {
|
|
267
|
-
let result = await api.post("/datasets/build", manifest, { engine: build.engine, source_mode: build.sourceMode, upload_chronon_metadata: build.upload, partition_duration: build.partitionDuration, max_parallelism: build.maxParallelism, resume: build.resume });
|
|
268
|
-
let exitCode = 0;
|
|
269
|
-
if (build.wait) {
|
|
270
|
-
if (!result || typeof result !== "object" || !result.job_id) throw new Error("server build did not return a job_id to wait for");
|
|
271
|
-
result = await waitForJob(api, String(result.job_id), build.pollInterval, build.waitTimeout);
|
|
272
|
-
if (["failed", "cancelled"].includes(result.status)) exitCode = 1;
|
|
273
|
-
}
|
|
274
|
-
return { result, exitCode };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async function waitForJob(api, jobId, pollInterval, timeoutSeconds) {
|
|
278
|
-
if (!(pollInterval > 0)) throw new Error("job poll interval must be positive");
|
|
279
|
-
if (timeoutSeconds !== undefined && !(timeoutSeconds > 0)) throw new Error("job wait timeout must be positive");
|
|
280
|
-
const deadline = timeoutSeconds === undefined ? Infinity : Date.now() + timeoutSeconds * 1000;
|
|
281
|
-
let previous;
|
|
282
|
-
while (true) {
|
|
283
|
-
const payload = await api.get(`/jobs/${encodeURIComponent(jobId)}`);
|
|
284
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("job status response must be a JSON object");
|
|
285
|
-
const status = String(payload.status ?? "unknown");
|
|
286
|
-
if (status !== previous) { process.stderr.write(`job ${jobId}: ${status}\n`); previous = status; }
|
|
287
|
-
if (["succeeded", "failed", "cancelled"].includes(status)) return payload;
|
|
288
|
-
if (Date.now() >= deadline) throw new Error(`timed out waiting for job ${jobId}`);
|
|
289
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async function runBusinessCli(argv) {
|
|
294
|
-
if (argv.length === 0) throw new Error("a business command is required");
|
|
295
|
-
if (argv.includes("--help") || argv.includes("-h")) {
|
|
296
|
-
const command = argv.find((item) => BUSINESS_COMMANDS.has(item));
|
|
297
|
-
if (command || argv[0] === "--help" || argv[0] === "-h") {
|
|
298
|
-
process.stdout.write(usage(command));
|
|
299
|
-
return 0;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const options = parse(argv);
|
|
303
|
-
const rest = [...options.rest];
|
|
304
|
-
let result;
|
|
305
|
-
let exitCode = 0;
|
|
306
|
-
if (options.command === "version") result = CLIENT_VERSION;
|
|
307
|
-
else if (options.command === "configure") {
|
|
308
|
-
const apiUrl = take(rest, "--api-url");
|
|
309
|
-
if (!apiUrl) throw new Error("--api-url is required");
|
|
310
|
-
const file = saveApiUrl(apiUrl);
|
|
311
|
-
result = { status: "configured", config_path: file, profile: "server", api_url: normalizeApiUrl(apiUrl) };
|
|
312
|
-
} else if (options.command === "show-config") {
|
|
313
|
-
const file = configPath();
|
|
314
|
-
result = { config_path: file, exists: fs.existsSync(file), stored: { profile: "server", api_url: loadApiUrl() }, effective: { profile: options.profile, api_url: options.apiUrl, request_timeout_seconds: options.timeout } };
|
|
315
|
-
} else if (options.command === "health") result = await client(options).get("/health");
|
|
316
|
-
else if (options.command === "create-project") {
|
|
317
|
-
const name = positional(rest, "project name"); result = await client(options).post("/projects", { schema_version: "ml_data_platform.project/v1", name, display_name: take(rest, "--display-name", name), description: take(rest, "--description", ""), owner: take(rest, "--owner", "demo") });
|
|
318
|
-
} else if (options.command === "list-projects") result = await client(options).get("/projects");
|
|
319
|
-
else if (options.command === "get-project") result = await client(options).get(`/projects/${encodeURIComponent(positional(rest, "project name"))}`);
|
|
320
|
-
else if (options.command === "delete-project") result = await client(options).delete(`/projects/${encodeURIComponent(positional(rest, "project name"))}`);
|
|
321
|
-
else if (["add-parameter", "add-feature", "add-feature-set", "add-operator", "register-operator"].includes(options.command)) {
|
|
322
|
-
const spec = readSpec(positional(rest, "spec JSON"));
|
|
323
|
-
const endpoint = { "add-parameter": "/parameters", "add-feature": "/features", "add-feature-set": "/feature-sets", "add-operator": "/operators", "register-operator": "/operators" }[options.command];
|
|
324
|
-
result = await client(options).post(endpoint, spec);
|
|
325
|
-
} else if (options.command === "publish-operator") {
|
|
326
|
-
const specPath = positional(rest, "OperatorSpec JSON"); const packagePath = take(rest, "--package");
|
|
327
|
-
if (!packagePath) throw new Error("--package is required");
|
|
328
|
-
const spec = readSpec(specPath); const api = client(options); const project = spec.project || "default";
|
|
329
|
-
const upload = await api.uploadOperatorPackage(project, spec.name, spec.version, packagePath);
|
|
330
|
-
if (!upload || typeof upload.code_artifact !== "object") throw new Error("operator package upload response has no code_artifact");
|
|
331
|
-
const published = { ...spec, code_hash: upload.code_artifact.sha256, package_uri: upload.code_artifact.uri, code_artifact: upload.code_artifact };
|
|
332
|
-
result = { status: "published", profile: "server", path: null, operator: published, registration: await api.post("/operators", published) };
|
|
333
|
-
} else if (options.command === "get-dataset") {
|
|
334
|
-
const datasetId = positional(rest, "dataset id"); const datasetVersion = positional(rest, "dataset version"); const project = take(rest, "--project", "default");
|
|
335
|
-
result = await registeredDataset(client(options), project, datasetId, datasetVersion, boolean(rest, "--include-deleted"));
|
|
336
|
-
} else if (["get-parameter", "get-feature", "get-feature-set", "get-operator"].includes(options.command)) {
|
|
337
|
-
const resource = options.command.slice(4);
|
|
338
|
-
const endpoint = resource === "feature-set" ? "feature-sets" : `${resource}s`;
|
|
339
|
-
const name = positional(rest, `${resource} name`);
|
|
340
|
-
const version = positional(rest, `${resource} version`);
|
|
341
|
-
const project = take(rest, "--project", "default");
|
|
342
|
-
result = await client(options).get(`/${endpoint}/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
343
|
-
} else if (/^delete-(parameter|feature|feature-set)$/.test(options.command)) {
|
|
344
|
-
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const project = take(rest, "--project", "default");
|
|
345
|
-
result = await client(options).delete(lifecycleEndpoint(lifecycleResource(options.command), project, name, version), {
|
|
346
|
-
actor: take(rest, "--actor"), reason: take(rest, "--reason"), idempotency_key: take(rest, "--idempotency-key"),
|
|
347
|
-
});
|
|
348
|
-
} else if (/^restore-(parameter|feature|feature-set)$/.test(options.command)) {
|
|
349
|
-
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const project = take(rest, "--project", "default");
|
|
350
|
-
result = await client(options).post(lifecycleEndpoint(lifecycleResource(options.command), project, name, version, "/restore"), {
|
|
351
|
-
actor: take(rest, "--actor") || "anonymous", reason: take(rest, "--reason", ""), idempotency_key: take(rest, "--idempotency-key"),
|
|
352
|
-
});
|
|
353
|
-
} else if (/^get-(parameter|feature|feature-set)-lifecycle$/.test(options.command)) {
|
|
354
|
-
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const project = take(rest, "--project", "default");
|
|
355
|
-
result = await client(options).get(lifecycleEndpoint(lifecycleResource(options.command), project, name, version, "/lifecycle"));
|
|
356
|
-
} else if (/^list-(parameter|feature|feature-set)-references$/.test(options.command)) {
|
|
357
|
-
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const project = take(rest, "--project", "default");
|
|
358
|
-
result = await client(options).get(lifecycleEndpoint(lifecycleResource(options.command), project, name, version, "/references"), { transitive: !boolean(rest, "--direct") });
|
|
359
|
-
} else if (["list-parameter-versions", "list-feature-versions", "list-feature-set-versions", "list-operator-versions"].includes(options.command)) {
|
|
360
|
-
const resource = options.command.replace(/^list-/, "").replace(/-versions$/, "");
|
|
361
|
-
const endpoint = resource === "feature-set" ? "feature-sets" : `${resource}s`;
|
|
362
|
-
const name = positional(rest, `${resource} name`);
|
|
363
|
-
const project = take(rest, "--project", "default");
|
|
364
|
-
const allPages = boolean(rest, "--all");
|
|
365
|
-
const rawLimit = take(rest, "--limit"); const rawOffset = take(rest, "--offset");
|
|
366
|
-
if (allPages && (rawLimit !== undefined || rawOffset !== undefined)) throw new Error("--all cannot be combined with --limit or --offset");
|
|
367
|
-
if (allPages) result = await fetchAll(client(options), `/${endpoint}/${encodeURIComponent(project)}/${encodeURIComponent(name)}`);
|
|
368
|
-
else { const limit = Number(rawLimit ?? 50); const offset = Number(rawOffset ?? 0); if (!Number.isInteger(limit) || limit < 1 || limit > 500) throw new Error("limit must be between 1 and 500"); if (!Number.isInteger(offset) || offset < 0) throw new Error("offset must be >= 0"); result = await client(options).get(`/${endpoint}/${encodeURIComponent(project)}/${encodeURIComponent(name)}`, { limit, offset }); }
|
|
369
|
-
} else if (["list-parameters", "list-operators", "list-features", "list-feature-sets", "list-datasets"].includes(options.command)) {
|
|
370
|
-
const endpoints = { "list-parameters": "/parameters", "list-operators": "/operators", "list-features": "/features", "list-feature-sets": "/feature-sets", "list-datasets": "/datasets" };
|
|
371
|
-
const project = take(rest, "--project");
|
|
372
|
-
const name = take(rest, "--name"); const version = take(rest, "--version");
|
|
373
|
-
const query = take(rest, "--q", take(rest, "-q")); const includeDeleted = options.command === "list-datasets" && boolean(rest, "--include-deleted"); const rawLimit = take(rest, "--limit"); const rawOffset = take(rest, "--offset");
|
|
374
|
-
const allPages = boolean(rest, "--all");
|
|
375
|
-
if (allPages && (rawLimit !== undefined || rawOffset !== undefined)) throw new Error("--all cannot be combined with --limit or --offset");
|
|
376
|
-
if (allPages) result = await fetchAll(client(options), endpoints[options.command], { q: query, project, name, version, include_deleted: includeDeleted });
|
|
377
|
-
else { const limit = Number(rawLimit ?? 50); const offset = Number(rawOffset ?? 0); if (!Number.isInteger(limit) || limit < 1 || limit > 500) throw new Error("limit must be between 1 and 500"); if (!Number.isInteger(offset) || offset < 0) throw new Error("offset must be >= 0"); result = await client(options).get(endpoints[options.command], { q: query, limit, offset, project, name, version, include_deleted: includeDeleted }); }
|
|
378
|
-
} else if (options.command === "list-jobs") {
|
|
379
|
-
const query = take(rest, "--q", take(rest, "-q")); const rawLimit = take(rest, "--limit"); const rawOffset = take(rest, "--offset");
|
|
380
|
-
const filters = {}; for (const field of ["project", "dataset-id", "dataset-version", "manifest-hash", "status", "parent-job-id", "created-from", "created-to", "updated-from", "updated-to"]) filters[field.replaceAll("-", "_")] = take(rest, `--${field}`);
|
|
381
|
-
const allPages = boolean(rest, "--all");
|
|
382
|
-
if (allPages && (rawLimit !== undefined || rawOffset !== undefined)) throw new Error("--all cannot be combined with --limit or --offset");
|
|
383
|
-
if (allPages) result = await fetchAll(client(options), "/jobs", { q: query, ...filters });
|
|
384
|
-
else { const limit = Number(rawLimit ?? 50); const offset = Number(rawOffset ?? 0); if (!Number.isInteger(limit) || limit < 1 || limit > 500) throw new Error("limit must be between 1 and 500"); if (!Number.isInteger(offset) || offset < 0) throw new Error("offset must be >= 0"); result = await client(options).get("/jobs", { q: query, limit, offset, ...filters }); }
|
|
385
|
-
}
|
|
386
|
-
else if (options.command === "resolve-manifest") {
|
|
387
|
-
const manifest = positional(rest, "manifest");
|
|
388
|
-
result = await client(options).post("/datasets/resolve", readDataset(manifest));
|
|
389
|
-
const out = take(rest, "--out");
|
|
390
|
-
if (out) { const target = resolvedPath(out); fs.mkdirSync(path.dirname(target), { recursive: true }); fs.writeFileSync(target, `${JSON.stringify(result, null, 2)}\n`); }
|
|
391
|
-
} else if (options.command === "resolve-dataset") {
|
|
392
|
-
const datasetId = positional(rest, "dataset id"); const datasetVersion = positional(rest, "dataset version"); const project = take(rest, "--project", "default"); const api = client(options);
|
|
393
|
-
const manifest = normalizeRegistryDataset(await registeredDataset(api, project, datasetId, datasetVersion));
|
|
394
|
-
result = await api.post("/datasets/resolve", manifest);
|
|
395
|
-
const out = take(rest, "--out");
|
|
396
|
-
if (out) { const target = resolvedPath(out); fs.mkdirSync(path.dirname(target), { recursive: true }); fs.writeFileSync(target, `${JSON.stringify(result, null, 2)}\n`); }
|
|
397
|
-
} else if (options.command === "build-dataset") {
|
|
398
|
-
const manifest = positional(rest, "manifest");
|
|
399
|
-
const build = buildOptions(rest); const api = client(options);
|
|
400
|
-
({ result, exitCode } = await submitBuild(api, readDataset(manifest), build));
|
|
401
|
-
} else if (options.command === "build-registered-dataset") {
|
|
402
|
-
const datasetId = positional(rest, "dataset id"); const datasetVersion = positional(rest, "dataset version"); const project = take(rest, "--project", "default"); const build = buildOptions(rest); const api = client(options);
|
|
403
|
-
const manifest = normalizeRegistryDataset(await registeredDataset(api, project, datasetId, datasetVersion));
|
|
404
|
-
({ result, exitCode } = await submitBuild(api, manifest, build));
|
|
405
|
-
} else if (["add-dataset", "update-dataset"].includes(options.command)) {
|
|
406
|
-
const manifest = readDataset(positional(rest, "manifest JSON")); const force = boolean(rest, "--force"); const project = manifest.project || "default";
|
|
407
|
-
if (options.command === "add-dataset") result = await client(options).post("/datasets", manifest, { force });
|
|
408
|
-
else result = await client(options).put(`/datasets/${encodeURIComponent(project)}/${encodeURIComponent(manifest.dataset_id)}/${encodeURIComponent(manifest.dataset_version)}`, manifest, { force });
|
|
409
|
-
} else if (options.command === "delete-dataset") {
|
|
410
|
-
const id = positional(rest, "dataset id"); const version = positional(rest, "dataset version"); const project = take(rest, "--project", "default");
|
|
411
|
-
result = await client(options).delete(`/datasets/${encodeURIComponent(project)}/${encodeURIComponent(id)}/${encodeURIComponent(version)}`);
|
|
412
|
-
} else if (options.command === "list-dataset-artifacts") {
|
|
413
|
-
const project = take(rest, "--project"); const datasetId = take(rest, "--dataset-id"); const datasetVersion = take(rest, "--dataset-version"); const includeDeleted = boolean(rest, "--include-deleted");
|
|
414
|
-
result = await listCollection(client(options), "/dataset-artifacts", { project, dataset_id: datasetId, dataset_version: datasetVersion, include_deleted: includeDeleted }, rest);
|
|
415
|
-
} else if (options.command === "show-operator-specs") {
|
|
416
|
-
const project = take(rest, "--project"); const query = take(rest, "--q", take(rest, "-q"));
|
|
417
|
-
result = await listCollection(client(options), "/operators", { project, q: query }, rest);
|
|
418
|
-
} else if (options.command === "delete-operator") {
|
|
419
|
-
const name = positional(rest, "operator name"); const version = positional(rest, "operator version"); const project = take(rest, "--project", "default");
|
|
420
|
-
result = await client(options).delete(`/operators/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
421
|
-
} else if (options.command === "run-operator") {
|
|
422
|
-
const name = positional(rest, "operator name"); const version = positional(rest, "operator version"); const project = take(rest, "--project", "default"); const rawConfig = take(rest, "--config", "{}");
|
|
423
|
-
let config; try { config = JSON.parse(rawConfig); } catch (_) { throw new Error("--config must be valid JSON"); }
|
|
424
|
-
result = await client(options).post(`/operators/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}/run`, config);
|
|
425
|
-
} else if (options.command === "get-job") result = await client(options).get(`/jobs/${encodeURIComponent(positional(rest, "job id"))}`);
|
|
426
|
-
else if (options.command === "wait-job") {
|
|
427
|
-
const jobId = positional(rest, "job id"); const poll = finiteNumber(take(rest, "--poll-interval", 2), "--poll-interval"); const rawTimeout = take(rest, "--wait-timeout-seconds");
|
|
428
|
-
result = await waitForJob(client(options), jobId, poll, rawTimeout === undefined ? undefined : finiteNumber(rawTimeout, "--wait-timeout-seconds"));
|
|
429
|
-
if (["failed", "cancelled"].includes(result.status)) exitCode = 1;
|
|
430
|
-
}
|
|
431
|
-
else if (options.command === "cancel-job") {
|
|
432
|
-
result = await client(options).post(`/jobs/${encodeURIComponent(positional(rest, "job id"))}/cancel`);
|
|
433
|
-
}
|
|
434
|
-
else if (options.command === "retry-job") {
|
|
435
|
-
result = await client(options).post(`/jobs/${encodeURIComponent(positional(rest, "job id"))}/retry`);
|
|
436
|
-
}
|
|
437
|
-
else if (options.command === "get-dataset-artifact") {
|
|
438
|
-
const dataset = positional(rest, "dataset id"); const hash = positional(rest, "manifest hash"); const project = take(rest, "--project", "default");
|
|
439
|
-
result = await client(options).get(`/dataset-artifacts/${encodeURIComponent(project)}/${encodeURIComponent(dataset)}/${encodeURIComponent(hash)}`);
|
|
440
|
-
} else if (options.command === "download-dataset-artifact") {
|
|
441
|
-
const dataset = positional(rest, "dataset id"); const hash = positional(rest, "manifest hash"); const project = take(rest, "--project", "default"); const outDir = take(rest, "--out-dir");
|
|
442
|
-
if (!outDir) throw new Error("--out-dir is required");
|
|
443
|
-
const files = [];
|
|
444
|
-
while (rest.includes("--file")) files.push(take(rest, "--file"));
|
|
445
|
-
result = files.length
|
|
446
|
-
? await client(options).downloadDatasetArtifactFiles(project, dataset, hash, files, resolvedPath(outDir), boolean(rest, "--force"))
|
|
447
|
-
: await client(options).downloadDatasetArtifact(project, dataset, hash, resolvedPath(outDir), boolean(rest, "--force"));
|
|
448
|
-
} else if (options.command === "fetch-inference-data") {
|
|
449
|
-
const manifest = positional(rest, "manifest"); const cutoffTime = take(rest, "--cutoff-time"); if (!cutoffTime) throw new Error("--cutoff-time is required");
|
|
450
|
-
const realtime = {}; const maxWorkers = take(rest, "--max-workers"); const maxLag = take(rest, "--max-source-lag-hours"); if (maxWorkers !== undefined) { realtime.max_workers = integer(maxWorkers, "--max-workers"); if (realtime.max_workers < 1) throw new Error("--max-workers must be at least 1"); } if (maxLag !== undefined) { realtime.max_source_lag_hours = finiteNumber(maxLag, "--max-source-lag-hours"); if (realtime.max_source_lag_hours < 0) throw new Error("--max-source-lag-hours must be non-negative"); } if (boolean(rest, "--no-validate-freshness")) realtime.validate_freshness = false; if (boolean(rest, "--allow-missing")) realtime.allow_missing = true;
|
|
451
|
-
const request = { manifest: readDataset(manifest), cutoff_time: cutoffTime }; if (Object.keys(realtime).length) request.realtime_fetch = realtime; result = await client(options).post("/inference-data/fetch", request);
|
|
452
|
-
} else if (options.command === "fetch-inference-context") {
|
|
453
|
-
result = await client(options).post("/inference-data/context", readSpec(positional(rest, "request JSON")));
|
|
454
|
-
} else if (options.command === "get-model-runtime-release") {
|
|
455
|
-
result = await client(options).get("/model-runtime-release");
|
|
456
|
-
} else if (options.command.startsWith("list-") && LIFECYCLE_COLLECTIONS.has(options.command.slice(5))) {
|
|
457
|
-
const params = {};
|
|
458
|
-
const limit = take(rest, "--limit"); const offset = take(rest, "--offset"); const query = take(rest, "-q");
|
|
459
|
-
if (limit !== undefined) { params.limit = integer(limit, "--limit"); if (params.limit < 1 || params.limit > 500) throw new Error("--limit must be between 1 and 500"); }
|
|
460
|
-
if (offset !== undefined) { params.offset = integer(offset, "--offset"); if (params.offset < 0) throw new Error("--offset must be non-negative"); }
|
|
461
|
-
if (query !== undefined) params.q = query;
|
|
462
|
-
result = await client(options).get(`/${options.command.slice(5)}`, params);
|
|
463
|
-
} else if (options.command === "get-training-run" || options.command === "get-model-artifact") {
|
|
464
|
-
const endpoint = options.command === "get-training-run" ? "training-runs" : "model-artifacts";
|
|
465
|
-
result = await client(options).get(`/${endpoint}/${encodeURIComponent(positional(rest, "resource id"))}`);
|
|
466
|
-
} else if (options.command === "get-executable-package") {
|
|
467
|
-
const name = positional(rest, "package name"); const version = positional(rest, "package version");
|
|
468
|
-
result = await client(options).get(`/executable-packages/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
469
|
-
} else if (options.command === "list-trainer-definitions") {
|
|
470
|
-
result = await client(options).get("/trainer-definitions");
|
|
471
|
-
} else if (options.command === "get-trainer-definition") {
|
|
472
|
-
const name = positional(rest, "trainer name"); const version = positional(rest, "trainer version"); const project = take(rest, "--project", "default");
|
|
473
|
-
result = await client(options).get(`/trainer-definitions/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
474
|
-
} else if ({
|
|
475
|
-
"register-trainer": "/trainer-definitions", "register-training-run": "/training-runs", "validate-training-run": "/training-runs/validate",
|
|
476
|
-
"validate-training-job": "/training-jobs/validate", "validate-model-package": "/model-packages/validate", "submit-training-job": "/training-jobs", "register-evaluation-config": "/evaluation-configs", "register-metric-definition": "/metric-definitions",
|
|
477
|
-
"register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "submit-evaluation": "/evaluation-runs",
|
|
478
|
-
"create-model-package": "/model-packages",
|
|
479
|
-
}[options.command]) {
|
|
480
|
-
const endpoint = { "register-trainer": "/trainer-definitions", "register-training-run": "/training-runs", "validate-training-run": "/training-runs/validate", "validate-training-job": "/training-jobs/validate", "validate-model-package": "/model-packages/validate", "submit-training-job": "/training-jobs", "register-evaluation-config": "/evaluation-configs", "register-metric-definition": "/metric-definitions", "register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "submit-evaluation": "/evaluation-runs", "create-model-package": "/model-packages" }[options.command];
|
|
481
|
-
result = await client(options).post(endpoint, readSpec(positional(rest, "request JSON")));
|
|
482
|
-
} else if (/^(get|retry|cancel)-training-job$/.test(options.command)) {
|
|
483
|
-
const jobId = positional(rest, "training job id"); const endpoint = `/training-jobs/${encodeURIComponent(jobId)}`;
|
|
484
|
-
result = options.command === "get-training-job" ? await client(options).get(endpoint) : await client(options).post(`${endpoint}/${options.command.split("-")[0]}`);
|
|
485
|
-
} else if (/^(get|retry|cancel)-evaluation-job$/.test(options.command) || options.command === "list-evaluation-attempts") {
|
|
486
|
-
const jobId = positional(rest, "evaluation job id"); const endpoint = `/evaluation-jobs/${encodeURIComponent(jobId)}`;
|
|
487
|
-
if (options.command === "get-evaluation-job") result = await client(options).get(endpoint);
|
|
488
|
-
else if (options.command === "list-evaluation-attempts") result = await client(options).get(`${endpoint}/attempts`);
|
|
489
|
-
else result = await client(options).post(`${endpoint}/${options.command.split("-")[0]}`);
|
|
490
|
-
} else if (options.command === "get-evaluation-run") result = await client(options).get(`/evaluation-runs/${encodeURIComponent(positional(rest, "evaluation run id"))}`);
|
|
491
|
-
else if (options.command === "summarize-evaluation-run") result = await client(options).post(`/evaluation-runs/${encodeURIComponent(positional(rest, "evaluation run id"))}/summarize`);
|
|
492
|
-
else if (options.command === "get-evaluation-summary") result = await client(options).get(`/evaluation-summaries/${encodeURIComponent(positional(rest, "evaluation summary id"))}`);
|
|
493
|
-
else if (/^get-evaluation-result(?:-metrics|-artifacts)?$/.test(options.command)) {
|
|
494
|
-
const resultId = positional(rest, "evaluation result id"); const suffix = options.command.endsWith("-metrics") ? "/metrics" : options.command.endsWith("-artifacts") ? "/artifacts" : "";
|
|
495
|
-
result = await client(options).get(`/evaluation-results/${encodeURIComponent(resultId)}${suffix}`);
|
|
496
|
-
} else if (/^(get-evaluation-config|get-metric-definition)$/.test(options.command)) {
|
|
497
|
-
const name = positional(rest, "resource name"); const version = positional(rest, "resource version"); const base = options.command === "get-evaluation-config" ? "evaluation-configs" : "metric-definitions";
|
|
498
|
-
result = await client(options).get(`/${base}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
499
|
-
} else if (/^(verify|publish)-executable-package$/.test(options.command)) {
|
|
500
|
-
const name = positional(rest, "package name"); const version = positional(rest, "package version"); const action = options.command.split("-")[0];
|
|
501
|
-
result = await client(options).post(`/executable-packages/${encodeURIComponent(name)}/${encodeURIComponent(version)}/${action}`);
|
|
502
|
-
} else if (options.command === "validate-model-artifact") result = await client(options).get(`/model-artifacts/${encodeURIComponent(positional(rest, "model artifact id"))}/validate`);
|
|
503
|
-
else if (/^(get|cancel)-model-package$/.test(options.command)) {
|
|
504
|
-
const packageId = positional(rest, "model package id"); const params = { package_version: take(rest, "--package-version") }; const endpoint = `/model-packages/${encodeURIComponent(packageId)}`;
|
|
505
|
-
result = options.command === "get-model-package" ? await client(options).get(endpoint, params) : await client(options).post(`${endpoint}/cancel`, undefined, params);
|
|
506
|
-
} else if (options.command === "apply") {
|
|
507
|
-
const directory = positional(rest, "catalog directory"); const dryRun = boolean(rest, "--dry-run"); const targetProject = take(rest, "--project");
|
|
508
|
-
result = await applyCatalog(loadCatalog(resolvedPath(directory)), client(options), dryRun, targetProject);
|
|
509
|
-
} else throw new Error(`unknown business command: ${options.command}`);
|
|
510
|
-
if (rest.length) throw new Error(`unknown arguments: ${rest.join(" ")}`);
|
|
511
|
-
process.stdout.write(typeof result === "string" ? `${result}\n` : `${JSON.stringify(result, null, 2)}\n`);
|
|
512
|
-
return exitCode;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
async function fetchAll(api, endpoint, params = {}) { const items = []; for (let offset = 0; ; offset += 500) { const page = await api.get(endpoint, { ...params, limit: 500, offset }); if (Array.isArray(page)) return page; if (!page || !Array.isArray(page.items)) throw new Error(`${endpoint} returned an invalid paginated response`); items.push(...page.items); if (page.total == null || items.length >= page.total || page.items.length < 500) return items; } }
|
|
516
|
-
|
|
517
|
-
async function listCollection(api, endpoint, params, rest) {
|
|
518
|
-
const rawLimit = take(rest, "--limit"); const rawOffset = take(rest, "--offset");
|
|
519
|
-
if (rawLimit === undefined && rawOffset === undefined) return fetchAll(api, endpoint, params);
|
|
520
|
-
const limit = Number(rawLimit ?? 50); const offset = Number(rawOffset ?? 0);
|
|
521
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 500) throw new Error("limit must be between 1 and 500");
|
|
522
|
-
if (!Number.isInteger(offset) || offset < 0) throw new Error("offset must be >= 0");
|
|
523
|
-
return api.get(endpoint, { ...params, limit, offset });
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
module.exports = { BUSINESS_COMMANDS, fetchAll, listCollection, parse, runBusinessCli, usage, waitForJob };
|
|
527
|
-
|
|
528
|
-
if (require.main === module) {
|
|
529
|
-
runBusinessCli(process.argv.slice(2)).then((code) => { process.exitCode = code; }).catch((error) => {
|
|
530
|
-
process.stderr.write(`error: ${error.message}\n`);
|
|
531
|
-
process.exitCode = 2;
|
|
532
|
-
});
|
|
533
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const fs = require("fs");
|
|
4
|
-
const os = require("os");
|
|
5
|
-
const path = require("path");
|
|
6
|
-
|
|
7
|
-
function expandHome(value) {
|
|
8
|
-
if (value === "~") return os.homedir();
|
|
9
|
-
if (value.startsWith(`~${path.sep}`)) return path.join(os.homedir(), value.slice(2));
|
|
10
|
-
return value;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function configPath() {
|
|
14
|
-
for (const variable of ["ML_PLATFORM_CONFIG_PATH", "DATA_PLATFORM_DEMO_CONFIG_PATH"]) {
|
|
15
|
-
if (process.env[variable]?.trim()) return path.resolve(expandHome(process.env[variable]));
|
|
16
|
-
}
|
|
17
|
-
const base = process.env.XDG_CONFIG_HOME
|
|
18
|
-
? path.resolve(process.env.XDG_CONFIG_HOME)
|
|
19
|
-
: path.join(os.homedir(), ".config");
|
|
20
|
-
const primary = path.join(base, "ml-platform", "config.json");
|
|
21
|
-
const legacy = path.join(base, "data-platform-demo", "config.json");
|
|
22
|
-
return !fs.existsSync(primary) && fs.existsSync(legacy) ? legacy : primary;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function normalizeApiUrl(value) {
|
|
26
|
-
const normalized = String(value).trim().replace(/\/+$/, "");
|
|
27
|
-
let parsed;
|
|
28
|
-
try { parsed = new URL(normalized); } catch (_) { throw new Error("api_url must be an absolute http:// or https:// URL"); }
|
|
29
|
-
if (!["http:", "https:"].includes(parsed.protocol) || !parsed.host) {
|
|
30
|
-
throw new Error("api_url must be an absolute http:// or https:// URL");
|
|
31
|
-
}
|
|
32
|
-
return normalized;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function loadApiUrl() {
|
|
36
|
-
const file = configPath();
|
|
37
|
-
if (!fs.existsSync(file)) return null;
|
|
38
|
-
let payload;
|
|
39
|
-
try { payload = JSON.parse(fs.readFileSync(file, "utf8")); }
|
|
40
|
-
catch (error) { throw new Error(`cannot read CLI config ${file}: ${error.message}`); }
|
|
41
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
42
|
-
throw new Error(`CLI config must contain a JSON object: ${file}`);
|
|
43
|
-
}
|
|
44
|
-
return payload.api_url ? normalizeApiUrl(payload.api_url) : null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function saveApiUrl(value) {
|
|
48
|
-
const file = configPath();
|
|
49
|
-
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
50
|
-
const temporary = `${file}.${process.pid}.tmp`;
|
|
51
|
-
fs.writeFileSync(temporary, `${JSON.stringify({ profile: "server", api_url: normalizeApiUrl(value) }, null, 2)}\n`, { mode: 0o600 });
|
|
52
|
-
fs.renameSync(temporary, file);
|
|
53
|
-
return file;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
module.exports = { configPath, expandHome, loadApiUrl, normalizeApiUrl, saveApiUrl };
|