dataiku-sdk 3.3.0 → 3.5.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.
- package/README.md +6 -4
- package/dist/build-metadata.json +1 -1
- package/dist/src/cli/command-syntax.json +14524 -0
- package/dist/src/cli/commands/analysis.js +6 -9
- package/dist/src/cli/commands/api-deployer.js +15 -33
- package/dist/src/cli/commands/api-service.js +17 -27
- package/dist/src/cli/commands/app.js +22 -39
- package/dist/src/cli/commands/bundle.js +15 -24
- package/dist/src/cli/commands/business-app.js +10 -17
- package/dist/src/cli/commands/code-env.js +23 -36
- package/dist/src/cli/commands/code.js +5 -5
- package/dist/src/cli/commands/connection.js +12 -22
- package/dist/src/cli/commands/continuous-activity.js +6 -9
- package/dist/src/cli/commands/dashboard.js +8 -13
- package/dist/src/cli/commands/data-collection.js +9 -16
- package/dist/src/cli/commands/data-quality.js +14 -26
- package/dist/src/cli/commands/dataset.js +28 -51
- package/dist/src/cli/commands/discussion.js +7 -10
- package/dist/src/cli/commands/doctor.js +3 -3
- package/dist/src/cli/commands/flow-zone.js +8 -17
- package/dist/src/cli/commands/folder.js +12 -20
- package/dist/src/cli/commands/future.js +7 -10
- package/dist/src/cli/commands/group.js +7 -11
- package/dist/src/cli/commands/insight.js +7 -11
- package/dist/src/cli/commands/job.js +17 -25
- package/dist/src/cli/commands/knowledge-bank.js +5 -6
- package/dist/src/cli/commands/llm.js +5 -8
- package/dist/src/cli/commands/macro.js +11 -18
- package/dist/src/cli/commands/meaning.js +7 -11
- package/dist/src/cli/commands/metrics.js +7 -10
- package/dist/src/cli/commands/ml-task.js +12 -20
- package/dist/src/cli/commands/model-evaluation-store.js +7 -11
- package/dist/src/cli/commands/notebook.js +15 -27
- package/dist/src/cli/commands/plugin.js +35 -68
- package/dist/src/cli/commands/project-deployer.js +10 -22
- package/dist/src/cli/commands/project-folder.js +10 -17
- package/dist/src/cli/commands/project-git.js +39 -71
- package/dist/src/cli/commands/project-library.js +15 -23
- package/dist/src/cli/commands/project.js +9 -26
- package/dist/src/cli/commands/recipe.d.ts +16 -0
- package/dist/src/cli/commands/recipe.js +83 -94
- package/dist/src/cli/commands/saved-model.js +23 -39
- package/dist/src/cli/commands/scenario.js +21 -35
- package/dist/src/cli/commands/sql.js +4 -4
- package/dist/src/cli/commands/statistics.js +11 -18
- package/dist/src/cli/commands/streaming-endpoint.js +7 -11
- package/dist/src/cli/commands/user.js +11 -22
- package/dist/src/cli/commands/variable.js +3 -4
- package/dist/src/cli/commands/webapp.js +10 -16
- package/dist/src/cli/commands/wiki.js +9 -13
- package/dist/src/cli/commands/workspace.js +8 -14
- package/dist/src/cli/contract.d.ts +5 -15
- package/dist/src/cli/contract.js +179 -461
- package/dist/src/cli/doctor.js +1 -0
- package/dist/src/cli/env.d.ts +2 -0
- package/dist/src/cli/env.js +15 -1
- package/dist/src/cli/helpers/app-permissions.js +2 -1
- package/dist/src/cli/helpers/flow-zone.js +2 -1
- package/dist/src/cli/helpers/recipe.js +2 -1
- package/dist/src/cli/output-schemas.d.ts +24 -0
- package/dist/src/cli/output-schemas.js +233 -0
- package/dist/src/cli/syntax.d.ts +83 -0
- package/dist/src/cli/syntax.js +217 -0
- package/dist/src/cli/types.d.ts +3 -0
- package/dist/src/cli/usage.d.ts +1 -1
- package/dist/src/cli/usage.js +4 -2
- package/dist/src/cli/version.js +2 -0
- package/dist/src/cli.js +37 -64
- package/dist/src/client.d.ts +10 -1
- package/dist/src/client.js +27 -11
- package/dist/src/errors.d.ts +12 -1
- package/dist/src/errors.js +34 -7
- package/dist/src/generated/action-output-schemas.json +1735 -0
- package/dist/src/index.d.ts +1 -3
- package/dist/src/index.js +2 -2
- package/dist/src/resources/api-services.js +2 -3
- package/dist/src/resources/applications.js +2 -4
- package/dist/src/resources/code-envs.js +2 -2
- package/dist/src/resources/data-quality.js +4 -3
- package/dist/src/resources/datasets.d.ts +13 -0
- package/dist/src/resources/datasets.js +42 -8
- package/dist/src/resources/flow-zones.d.ts +6 -2
- package/dist/src/resources/flow-zones.js +10 -5
- package/dist/src/resources/folders.js +4 -2
- package/dist/src/resources/futures.d.ts +2 -0
- package/dist/src/resources/futures.js +39 -27
- package/dist/src/resources/insights.js +3 -2
- package/dist/src/resources/jobs.js +41 -31
- package/dist/src/resources/macros.js +2 -4
- package/dist/src/resources/ml-tasks.js +28 -19
- package/dist/src/resources/notebooks.js +2 -3
- package/dist/src/resources/plugins.d.ts +0 -2
- package/dist/src/resources/plugins.js +0 -3
- package/dist/src/resources/project-git.d.ts +6 -12
- package/dist/src/resources/project-git.js +31 -58
- package/dist/src/resources/project-library.d.ts +0 -2
- package/dist/src/resources/project-library.js +2 -7
- package/dist/src/resources/projects.d.ts +4 -2
- package/dist/src/resources/projects.js +37 -36
- package/dist/src/resources/recipes.d.ts +28 -0
- package/dist/src/resources/recipes.js +116 -84
- package/dist/src/resources/sql.js +8 -3
- package/dist/src/resources/statistics.js +2 -1
- package/dist/src/resources/variables.js +2 -1
- package/dist/src/resources/webapps.js +2 -3
- package/dist/src/schemas.d.ts +1 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.js +6 -7
- package/dist/src/utils/cleanup-ledger.js +9 -8
- package/dist/src/utils/git-reference.d.ts +12 -0
- package/dist/src/utils/git-reference.js +38 -0
- package/dist/src/utils/polling.d.ts +3 -0
- package/dist/src/utils/polling.js +3 -0
- package/dist/src/utils/project-archive.js +2 -2
- package/dist/src/utils/stable-hash.d.ts +3 -1
- package/dist/src/utils/stable-hash.js +2 -0
- package/package.json +8 -8
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +3 -2
- package/skills/dataiku-dss/references/app-releases.md +2 -3
- package/skills/dataiku-dss/references/authentication.md +7 -7
- package/skills/dataiku-dss/references/coding.md +1 -1
- package/skills/dataiku-dss/references/mutations.md +1 -1
- package/skills/dataiku-dss/references/troubleshooting.md +2 -2
- package/dist/src/utils/pagination.d.ts +0 -8
- package/dist/src/utils/pagination.js +0 -23
package/dist/src/cli/doctor.js
CHANGED
|
@@ -29,6 +29,7 @@ export function firstStringField(items, fields) {
|
|
|
29
29
|
return null;
|
|
30
30
|
}
|
|
31
31
|
export function integrationFlag(name) {
|
|
32
|
+
// oxlint-disable-next-line dss/no-direct-process-env -- RUN_DATAIKU_* integration opt-in flags
|
|
32
33
|
const value = process.env[name];
|
|
33
34
|
return value === "1" || value?.toLowerCase() === "true";
|
|
34
35
|
}
|
package/dist/src/cli/env.d.ts
CHANGED
|
@@ -8,4 +8,6 @@ export type EnvFileProvenance = "project" | "install";
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function envFileProvenance(name: string): EnvFileProvenance | undefined;
|
|
10
10
|
export declare function dataikuEnvironmentEnabled(): boolean;
|
|
11
|
+
/** DATAIKU_PROJECT_KEY from the environment, unless `DATAIKU_DISABLE_ENV=1`. */
|
|
12
|
+
export declare function ambientProjectKey(): string | undefined;
|
|
11
13
|
export declare function loadEnvFile(): void;
|
package/dist/src/cli/env.js
CHANGED
|
@@ -20,6 +20,20 @@ export function envFileProvenance(name) {
|
|
|
20
20
|
export function dataikuEnvironmentEnabled() {
|
|
21
21
|
return process.env.DATAIKU_DISABLE_ENV !== "1";
|
|
22
22
|
}
|
|
23
|
+
/** DATAIKU_PROJECT_KEY from the environment, unless `DATAIKU_DISABLE_ENV=1`. */
|
|
24
|
+
export function ambientProjectKey() {
|
|
25
|
+
return dataikuEnvironmentEnabled() ? process.env.DATAIKU_PROJECT_KEY : undefined;
|
|
26
|
+
}
|
|
27
|
+
const allowedEnvKeys = new Set(["NODE_EXTRA_CA_CERTS", "NODE_TLS_REJECT_UNAUTHORIZED",]);
|
|
28
|
+
/**
|
|
29
|
+
* `.env` files are untrusted input: only the CLI's own `DATAIKU_*` variables and
|
|
30
|
+
* Node's TLS trust knobs are read. Every other key is ignored silently, so a
|
|
31
|
+
* project working tree cannot redirect the CLI (for example by smuggling in
|
|
32
|
+
* `DSS_CONFIG_DIR` and sending saved credentials elsewhere).
|
|
33
|
+
*/
|
|
34
|
+
function isAllowedEnvKey(key) {
|
|
35
|
+
return key.startsWith("DATAIKU_") || allowedEnvKeys.has(key);
|
|
36
|
+
}
|
|
23
37
|
export function loadEnvFile() {
|
|
24
38
|
if (!dataikuEnvironmentEnabled())
|
|
25
39
|
return;
|
|
@@ -42,7 +56,7 @@ export function loadEnvFile() {
|
|
|
42
56
|
continue;
|
|
43
57
|
const key = trimmed.slice(0, eq).trim();
|
|
44
58
|
const val = trimmed.slice(eq + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
45
|
-
if (!process.env[key]) {
|
|
59
|
+
if (isAllowedEnvKey(key) && !process.env[key]) {
|
|
46
60
|
process.env[key] = val;
|
|
47
61
|
(index === 0 ? projectEnvVars : installEnvVars).add(key);
|
|
48
62
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { ClientValidationError, } from "../../errors.js";
|
|
2
3
|
import { canonicalDssUrl, } from "../../utils/dss-url.js";
|
|
3
4
|
import { compareStrings, } from "../../utils/stable-hash.js";
|
|
4
5
|
import { parseJsonObject, plainRecord, stableHash, stableJson, } from "../coerce.js";
|
|
@@ -61,7 +62,7 @@ function writeAllSync(fd, payload) {
|
|
|
61
62
|
while (offset < payload.length) {
|
|
62
63
|
const written = fs.writeSync(fd, payload, offset, payload.length - offset, offset);
|
|
63
64
|
if (written <= 0) {
|
|
64
|
-
throw new
|
|
65
|
+
throw new ClientValidationError(`Stalled writing app permissions snapshot at byte ${offset}.`, "internal_error");
|
|
65
66
|
}
|
|
66
67
|
offset += written;
|
|
67
68
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, } from "node:fs";
|
|
2
2
|
import { compareStrings, } from "../../utils/stable-hash.js";
|
|
3
3
|
import { finiteNumberField, jsonInput, optionalStringField, parseJsonObject, plainRecord, requiredStringArray, splitCsvFlag, } from "../coerce.js";
|
|
4
|
+
import { commandUsage, } from "../syntax.js";
|
|
4
5
|
import { UsageError, } from "../usage.js";
|
|
5
6
|
export function flowZoneId(value) {
|
|
6
7
|
const trimmed = value.trim();
|
|
@@ -10,7 +11,7 @@ export function flowZoneId(value) {
|
|
|
10
11
|
}
|
|
11
12
|
export function flowZoneName(value) {
|
|
12
13
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
13
|
-
throw new UsageError(
|
|
14
|
+
throw new UsageError(`--name is required. Usage: ${commandUsage("flow-zone", "create")}`);
|
|
14
15
|
}
|
|
15
16
|
return value.trim();
|
|
16
17
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import { tmpdir, } from "node:os";
|
|
3
3
|
import { join, parse as parsePath, relative as relativePath, resolve as resolvePath, sep, } from "node:path";
|
|
4
|
+
import { ClientValidationError, } from "../../errors.js";
|
|
4
5
|
import { sanitizeFileName, } from "../../utils/sanitize.js";
|
|
5
6
|
import { normalizeLineEndings, sha256Hex, stableHash, } from "../coerce.js";
|
|
6
7
|
import { UsageError, } from "../usage.js";
|
|
@@ -126,7 +127,7 @@ function writeAllSync(fd, payload) {
|
|
|
126
127
|
while (offset < payload.length) {
|
|
127
128
|
const written = fs.writeSync(fd, payload, offset, payload.length - offset, offset);
|
|
128
129
|
if (written <= 0) {
|
|
129
|
-
throw new
|
|
130
|
+
throw new ClientValidationError(`Stalled writing recipe backup at byte ${offset}.`, "internal_error");
|
|
130
131
|
}
|
|
131
132
|
offset += written;
|
|
132
133
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical discovery output schemas, keyed by `resource.action`.
|
|
3
|
+
*
|
|
4
|
+
* TypeBox definitions from `packages/types` plus the inline literal schemas
|
|
5
|
+
* that exist only on the CLI side (code-env log envelope, code.run outcome,
|
|
6
|
+
* project-library payloads, notebook saves, sql.query handoff envelope).
|
|
7
|
+
* `scripts/generate-action-output-schemas.mjs` serializes this map into
|
|
8
|
+
* `src/generated/action-output-schemas.json`; discovery (src/cli/contract.ts)
|
|
9
|
+
* reads that generated JSON as plain data, so the TypeBox schema graph never
|
|
10
|
+
* loads on the discovery path. SDK validation keeps using the TypeBox schema
|
|
11
|
+
* objects from `packages/types` directly.
|
|
12
|
+
*
|
|
13
|
+
* Imported by the generator and the discovery parity test only.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Canonical TypeBox-derived output schemas, keyed by `resource.action`. This is
|
|
17
|
+
* the single source of truth for `src/generated/action-output-schemas.json`,
|
|
18
|
+
* which discovery reads as plain data so no TypeBox schema graph loads on the
|
|
19
|
+
* discovery path. Regenerate with
|
|
20
|
+
* `bun scripts/generate-action-output-schemas.mjs`; `bun run check` fails when
|
|
21
|
+
* the generated file is stale. SDK validation keeps using the TypeBox schema
|
|
22
|
+
* objects from `packages/types` directly and never depends on the generated file.
|
|
23
|
+
*/
|
|
24
|
+
export declare function typeBoxCommandOutputSchemas(): Record<string, Record<string, unknown>>;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical discovery output schemas, keyed by `resource.action`.
|
|
3
|
+
*
|
|
4
|
+
* TypeBox definitions from `packages/types` plus the inline literal schemas
|
|
5
|
+
* that exist only on the CLI side (code-env log envelope, code.run outcome,
|
|
6
|
+
* project-library payloads, notebook saves, sql.query handoff envelope).
|
|
7
|
+
* `scripts/generate-action-output-schemas.mjs` serializes this map into
|
|
8
|
+
* `src/generated/action-output-schemas.json`; discovery (src/cli/contract.ts)
|
|
9
|
+
* reads that generated JSON as plain data, so the TypeBox schema graph never
|
|
10
|
+
* loads on the discovery path. SDK validation keeps using the TypeBox schema
|
|
11
|
+
* objects from `packages/types` directly.
|
|
12
|
+
*
|
|
13
|
+
* Imported by the generator and the discovery parity test only.
|
|
14
|
+
*/
|
|
15
|
+
const PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA = {
|
|
16
|
+
type: "object",
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
required: ["name",],
|
|
19
|
+
properties: {
|
|
20
|
+
name: { type: "string", },
|
|
21
|
+
path: { type: "string", },
|
|
22
|
+
size: { type: "integer", minimum: 0, },
|
|
23
|
+
mimeType: { type: "string", },
|
|
24
|
+
hasData: { type: "boolean", },
|
|
25
|
+
lastModified: { type: "number", },
|
|
26
|
+
children: { type: "array", items: { type: "object", additionalProperties: true, }, },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const NOTEBOOK_SAVE_OUTPUT_SCHEMA = {
|
|
30
|
+
type: "object",
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
required: ["saved", "resource", "created", "hash",],
|
|
33
|
+
properties: {
|
|
34
|
+
saved: { type: "string", },
|
|
35
|
+
resource: { enum: ["jupyter-notebook", "sql-notebook",], },
|
|
36
|
+
created: { type: "boolean", },
|
|
37
|
+
hash: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* `notebook.save-sql` adds `requested`: on create DSS allocates the persisted
|
|
42
|
+
* id (`saved`), while the requested handle only becomes the display name.
|
|
43
|
+
*/
|
|
44
|
+
const NOTEBOOK_SAVE_SQL_OUTPUT_SCHEMA = {
|
|
45
|
+
...NOTEBOOK_SAVE_OUTPUT_SCHEMA,
|
|
46
|
+
required: ["saved", "requested", "resource", "created", "hash",],
|
|
47
|
+
properties: {
|
|
48
|
+
...NOTEBOOK_SAVE_OUTPUT_SCHEMA["properties"],
|
|
49
|
+
requested: { type: "string", },
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
let commandOutputSchemas;
|
|
53
|
+
/**
|
|
54
|
+
* Canonical TypeBox-derived output schemas, keyed by `resource.action`. This is
|
|
55
|
+
* the single source of truth for `src/generated/action-output-schemas.json`,
|
|
56
|
+
* which discovery reads as plain data so no TypeBox schema graph loads on the
|
|
57
|
+
* discovery path. Regenerate with
|
|
58
|
+
* `bun scripts/generate-action-output-schemas.mjs`; `bun run check` fails when
|
|
59
|
+
* the generated file is stale. SDK validation keeps using the TypeBox schema
|
|
60
|
+
* objects from `packages/types` directly and never depends on the generated file.
|
|
61
|
+
*/
|
|
62
|
+
export function typeBoxCommandOutputSchemas() {
|
|
63
|
+
if (commandOutputSchemas)
|
|
64
|
+
return commandOutputSchemas;
|
|
65
|
+
const { CodeEnvDetailsSchema, CodeEnvLogSummaryArraySchema, CodeEnvSummaryArraySchema, CodeEnvUsageArraySchema, CodeEnvVersionForProjectSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, FlowZoneArraySchema, FlowZoneSchema, JobSummaryArraySchema, JobWaitResultSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, NotebookSessionArraySchema, ProjectDetailsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, RecipeSummaryArraySchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioSummaryArraySchema, SqlNotebookContentSchema, SqlNotebookHistorySchema, SqlNotebookSummaryArraySchema, SqlQueryResponseSchema, } = require("../schemas.js");
|
|
66
|
+
return commandOutputSchemas = {
|
|
67
|
+
"code-env.list": CodeEnvSummaryArraySchema,
|
|
68
|
+
"code-env.get": CodeEnvDetailsSchema,
|
|
69
|
+
"code-env.list-logs": CodeEnvLogSummaryArraySchema,
|
|
70
|
+
"code-env.get-log": {
|
|
71
|
+
oneOf: [
|
|
72
|
+
{
|
|
73
|
+
type: "object",
|
|
74
|
+
required: ["log", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
|
|
75
|
+
properties: {
|
|
76
|
+
log: { type: "string", },
|
|
77
|
+
bytes: { type: "integer", minimum: 0, },
|
|
78
|
+
truncated: { type: "boolean", },
|
|
79
|
+
tailed: { type: "boolean", },
|
|
80
|
+
envLang: { enum: ["PYTHON", "R",], },
|
|
81
|
+
envName: { type: "string", },
|
|
82
|
+
logName: { type: "string", },
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: "object",
|
|
87
|
+
required: ["path", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
|
|
88
|
+
properties: {
|
|
89
|
+
path: { type: "string", },
|
|
90
|
+
bytes: { type: "integer", minimum: 0, },
|
|
91
|
+
truncated: { type: "boolean", },
|
|
92
|
+
tailed: { type: "boolean", },
|
|
93
|
+
envLang: { enum: ["PYTHON", "R",], },
|
|
94
|
+
envName: { type: "string", },
|
|
95
|
+
logName: { type: "string", },
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
"code-env.version": CodeEnvVersionForProjectSchema,
|
|
101
|
+
"code-env.usages": CodeEnvUsageArraySchema,
|
|
102
|
+
"code.run": {
|
|
103
|
+
type: "object",
|
|
104
|
+
additionalProperties: false,
|
|
105
|
+
required: [
|
|
106
|
+
"outcome",
|
|
107
|
+
"success",
|
|
108
|
+
"runId",
|
|
109
|
+
"elapsedMs",
|
|
110
|
+
"pollCount",
|
|
111
|
+
"output",
|
|
112
|
+
"logTruncated",
|
|
113
|
+
"maxLogBytes",
|
|
114
|
+
"cleanup",
|
|
115
|
+
],
|
|
116
|
+
properties: {
|
|
117
|
+
outcome: { type: "string", },
|
|
118
|
+
success: { type: "boolean", },
|
|
119
|
+
runId: { type: "string", },
|
|
120
|
+
elapsedMs: { type: "number", minimum: 0, },
|
|
121
|
+
pollCount: { type: "integer", minimum: 0, },
|
|
122
|
+
output: { type: "string", },
|
|
123
|
+
log: { type: "string", },
|
|
124
|
+
logTruncated: { type: "boolean", },
|
|
125
|
+
maxLogBytes: { type: "integer", minimum: 0, },
|
|
126
|
+
timedOut: { const: true, },
|
|
127
|
+
timeoutMs: { type: "integer", minimum: 0, },
|
|
128
|
+
cleanup: {
|
|
129
|
+
type: "object",
|
|
130
|
+
additionalProperties: false,
|
|
131
|
+
required: ["status",],
|
|
132
|
+
properties: {
|
|
133
|
+
status: { enum: ["deleted", "kept", "failed",], },
|
|
134
|
+
error: { type: "string", },
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
"notebook.list-jupyter": JupyterNotebookSummaryArraySchema,
|
|
140
|
+
"notebook.get-jupyter": JupyterNotebookContentSchema,
|
|
141
|
+
"notebook.sessions-jupyter": NotebookSessionArraySchema,
|
|
142
|
+
"notebook.list-sql": SqlNotebookSummaryArraySchema,
|
|
143
|
+
"notebook.get-sql": SqlNotebookContentSchema,
|
|
144
|
+
"notebook.history-sql": SqlNotebookHistorySchema,
|
|
145
|
+
"notebook.save-jupyter": NOTEBOOK_SAVE_OUTPUT_SCHEMA,
|
|
146
|
+
"notebook.save-sql": NOTEBOOK_SAVE_SQL_OUTPUT_SCHEMA,
|
|
147
|
+
"project-library.list": { type: "array", items: PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA, },
|
|
148
|
+
"project-library.get-bytes": {
|
|
149
|
+
type: "object",
|
|
150
|
+
additionalProperties: false,
|
|
151
|
+
required: ["path", "bytes", "sha256",],
|
|
152
|
+
properties: {
|
|
153
|
+
path: { type: "string", },
|
|
154
|
+
bytes: { type: "integer", minimum: 0, },
|
|
155
|
+
sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
"project-library.put": {
|
|
159
|
+
type: "object",
|
|
160
|
+
additionalProperties: false,
|
|
161
|
+
required: ["updated", "bytes", "sha256",],
|
|
162
|
+
properties: {
|
|
163
|
+
updated: { type: "string", },
|
|
164
|
+
bytes: { type: "integer", minimum: 0, },
|
|
165
|
+
sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
166
|
+
beforeSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
"project-library.diff": {
|
|
170
|
+
type: "object",
|
|
171
|
+
additionalProperties: false,
|
|
172
|
+
required: [
|
|
173
|
+
"path",
|
|
174
|
+
"unchanged",
|
|
175
|
+
"added",
|
|
176
|
+
"removed",
|
|
177
|
+
"diff",
|
|
178
|
+
"diffTruncated",
|
|
179
|
+
"localSha256",
|
|
180
|
+
"localBytes",
|
|
181
|
+
"maxLines",
|
|
182
|
+
],
|
|
183
|
+
properties: {
|
|
184
|
+
path: { type: "string", },
|
|
185
|
+
unchanged: { type: "boolean", },
|
|
186
|
+
added: { type: "integer", minimum: 0, },
|
|
187
|
+
removed: { type: "integer", minimum: 0, },
|
|
188
|
+
diff: { type: "string", },
|
|
189
|
+
diffTruncated: { type: "boolean", },
|
|
190
|
+
binary: { type: "boolean", },
|
|
191
|
+
remoteAbsent: { type: "boolean", },
|
|
192
|
+
remoteSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
193
|
+
remoteBytes: { type: "integer", minimum: 0, },
|
|
194
|
+
localSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
|
|
195
|
+
localBytes: { type: "integer", minimum: 0, },
|
|
196
|
+
maxLines: { type: "integer", minimum: 0, },
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
"project.list": ProjectSummaryArraySchema,
|
|
200
|
+
"project.get": ProjectDetailsSchema,
|
|
201
|
+
"project.metadata": ProjectMetadataSchema,
|
|
202
|
+
"dataset.list": DatasetSummaryArraySchema,
|
|
203
|
+
"dataset.get": DatasetDetailsSchema,
|
|
204
|
+
"dataset.schema": DatasetSchemaSchema,
|
|
205
|
+
"recipe.list": RecipeSummaryArraySchema,
|
|
206
|
+
"job.list": JobSummaryArraySchema,
|
|
207
|
+
"job.wait": JobWaitResultSchema,
|
|
208
|
+
"job.monitor": JobWaitResultSchema,
|
|
209
|
+
"scenario.list": ScenarioSummaryArraySchema,
|
|
210
|
+
"scenario.get": ScenarioDetailsSchema,
|
|
211
|
+
"scenario.status": ScenarioStatusSchema,
|
|
212
|
+
"flow-zone.list": FlowZoneArraySchema,
|
|
213
|
+
"flow-zone.get": FlowZoneSchema,
|
|
214
|
+
"sql.query": {
|
|
215
|
+
anyOf: [
|
|
216
|
+
SqlQueryResponseSchema,
|
|
217
|
+
{
|
|
218
|
+
type: "object",
|
|
219
|
+
required: ["queryId", "rowCount", "preview",],
|
|
220
|
+
additionalProperties: true,
|
|
221
|
+
properties: {
|
|
222
|
+
queryId: { type: "string", },
|
|
223
|
+
rowCount: { type: "number", },
|
|
224
|
+
preview: { type: "array", items: true, },
|
|
225
|
+
truncated: { type: "boolean", },
|
|
226
|
+
outputPath: { type: "string", },
|
|
227
|
+
written: { type: "string", },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { CommandDefinition, CommandFlagChoice, CommandMeta } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* One element of a command's usage line. `src/cli/command-syntax.json` holds an
|
|
4
|
+
* ordered node list per command; it is the source of truth for the usage text
|
|
5
|
+
* (rendered by {@link commandUsage}) and for every usage-level contract field
|
|
6
|
+
* (derived by {@link commandSyntax}).
|
|
7
|
+
*/
|
|
8
|
+
export type SyntaxNode =
|
|
9
|
+
/** Literal word: `dss`, the resource, the action. */
|
|
10
|
+
string
|
|
11
|
+
/** Positional argument, rendered `<name>` (or bare `name` when `bare`). */
|
|
12
|
+
| {
|
|
13
|
+
arg: string;
|
|
14
|
+
bare?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** Flag, rendered `--flag`, `--flag VALUE`, or `--flag a|b` (enum), plus an optional trailing note. */
|
|
17
|
+
| {
|
|
18
|
+
flag: string;
|
|
19
|
+
value?: string;
|
|
20
|
+
values?: string[];
|
|
21
|
+
note?: string;
|
|
22
|
+
}
|
|
23
|
+
/** Optional group `[a|b]`; each alternative is a node sequence. */
|
|
24
|
+
| {
|
|
25
|
+
optional: SyntaxNode[][];
|
|
26
|
+
sep?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Required choice `(a|b)`; each alternative is a node sequence. */
|
|
29
|
+
| {
|
|
30
|
+
oneOf: SyntaxNode[][];
|
|
31
|
+
sep?: string;
|
|
32
|
+
};
|
|
33
|
+
export type FlagNode = Extract<SyntaxNode, {
|
|
34
|
+
flag: string;
|
|
35
|
+
}>;
|
|
36
|
+
export interface CommandPositionalMetadata {
|
|
37
|
+
name: string;
|
|
38
|
+
required: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Input channels a command accepts for its JSON payload. */
|
|
41
|
+
export interface CommandInputContract {
|
|
42
|
+
stdin?: boolean;
|
|
43
|
+
dataFlag?: boolean;
|
|
44
|
+
dataFileFlag?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Usage-level contract fields derived from a command's syntax tree. */
|
|
47
|
+
export interface CommandSyntax {
|
|
48
|
+
/** Canonical names of the flags the usage line lists, in order. */
|
|
49
|
+
flags: string[];
|
|
50
|
+
requiredFlags: string[];
|
|
51
|
+
requiredOneOf: CommandFlagChoice[];
|
|
52
|
+
valueHints: Record<string, {
|
|
53
|
+
valueType: string;
|
|
54
|
+
enumValues?: string[];
|
|
55
|
+
}>;
|
|
56
|
+
inputContract: CommandInputContract;
|
|
57
|
+
positionalArguments: CommandPositionalMetadata[];
|
|
58
|
+
/** Alias spellings (canonical name -> raw names) the usage line uses. */
|
|
59
|
+
aliases: Record<string, string[]>;
|
|
60
|
+
dryRun: boolean;
|
|
61
|
+
producesLocalFile: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** The syntax tree for `resource.action`, or undefined when no such command exists. */
|
|
64
|
+
export declare function commandSyntaxTree(resource: string, action: string): SyntaxNode[] | undefined;
|
|
65
|
+
export declare function commandSyntaxKeys(): string[];
|
|
66
|
+
/** Render a node sequence as usage text; `omitOptional` drops every `[...]` group. */
|
|
67
|
+
export declare function renderNodes(nodes: SyntaxNode[], omitOptional?: boolean): string;
|
|
68
|
+
/** The human usage line for `resource.action`, rendered from its syntax tree. */
|
|
69
|
+
export declare function commandUsage(resource: string, action: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* Attach each action's usage line, rendered from its syntax tree. Every command
|
|
72
|
+
* module exports its definitions through this, so a command without a syntax
|
|
73
|
+
* entry fails at load instead of shipping an undocumented contract.
|
|
74
|
+
*/
|
|
75
|
+
export declare function withUsage(resource: string, definitions: Record<string, CommandDefinition>): Record<string, CommandMeta>;
|
|
76
|
+
/** Whether the syntax lists `--name` (the raw spelling, not an alias). */
|
|
77
|
+
export declare function syntaxHasFlag(nodes: SyntaxNode[], name: string): boolean;
|
|
78
|
+
/** Leading literal words after `dss` (resource and, when present, action). */
|
|
79
|
+
export declare function syntaxCommandWords(nodes: SyntaxNode[]): string[];
|
|
80
|
+
/** Usage-level contract fields of a syntax tree (flags, requirements, value hints, positionals, inputs). */
|
|
81
|
+
export declare function deriveSyntax(nodes: SyntaxNode[]): CommandSyntax;
|
|
82
|
+
/** Usage-level contract fields for `resource.action`, derived from its syntax tree. */
|
|
83
|
+
export declare function commandSyntax(resource: string, action: string): CommandSyntax;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { ClientValidationError, } from "../errors.js";
|
|
2
|
+
import commandSyntaxSource from "./command-syntax.json" with { type: "json" };
|
|
3
|
+
import { FLAG_ALIASES, KNOWN_LONG_FLAGS, } from "./flags.js";
|
|
4
|
+
const SOURCE = commandSyntaxSource.commands;
|
|
5
|
+
/** The syntax tree for `resource.action`, or undefined when no such command exists. */
|
|
6
|
+
export function commandSyntaxTree(resource, action) {
|
|
7
|
+
return Object.hasOwn(SOURCE, `${resource}.${action}`)
|
|
8
|
+
? SOURCE[`${resource}.${action}`]
|
|
9
|
+
: undefined;
|
|
10
|
+
}
|
|
11
|
+
function requiredSyntaxTree(resource, action) {
|
|
12
|
+
const nodes = commandSyntaxTree(resource, action);
|
|
13
|
+
if (!nodes) {
|
|
14
|
+
throw new ClientValidationError(`No syntax for ${resource} ${action} in src/cli/command-syntax.json.`, "internal_error");
|
|
15
|
+
}
|
|
16
|
+
return nodes;
|
|
17
|
+
}
|
|
18
|
+
export function commandSyntaxKeys() {
|
|
19
|
+
return Object.keys(SOURCE);
|
|
20
|
+
}
|
|
21
|
+
function renderNode(node, omitOptional) {
|
|
22
|
+
if (typeof node === "string")
|
|
23
|
+
return node;
|
|
24
|
+
if ("arg" in node)
|
|
25
|
+
return node.bare ? node.arg : `<${node.arg}>`;
|
|
26
|
+
if ("flag" in node) {
|
|
27
|
+
const value = node.values ? node.values.join("|") : node.value;
|
|
28
|
+
return `--${node.flag}${value === undefined ? "" : ` ${value}`}${node.note ? ` ${node.note}` : ""}`;
|
|
29
|
+
}
|
|
30
|
+
if ("optional" in node) {
|
|
31
|
+
return omitOptional ? "" : `[${renderAlternatives(node.optional, node.sep, omitOptional)}]`;
|
|
32
|
+
}
|
|
33
|
+
return `(${renderAlternatives(node.oneOf, node.sep, omitOptional)})`;
|
|
34
|
+
}
|
|
35
|
+
function renderAlternatives(alternatives, sep, omitOptional) {
|
|
36
|
+
return alternatives.map((alternative) => renderNodes(alternative, omitOptional)).join(sep ?? "|");
|
|
37
|
+
}
|
|
38
|
+
/** Render a node sequence as usage text; `omitOptional` drops every `[...]` group. */
|
|
39
|
+
export function renderNodes(nodes, omitOptional = false) {
|
|
40
|
+
return nodes.map((node) => renderNode(node, omitOptional)).filter((part) => part !== "").join(" ");
|
|
41
|
+
}
|
|
42
|
+
const usageCache = new Map();
|
|
43
|
+
/** The human usage line for `resource.action`, rendered from its syntax tree. */
|
|
44
|
+
export function commandUsage(resource, action) {
|
|
45
|
+
const key = `${resource}.${action}`;
|
|
46
|
+
let usage = usageCache.get(key);
|
|
47
|
+
if (usage === undefined) {
|
|
48
|
+
usage = renderNodes(requiredSyntaxTree(resource, action));
|
|
49
|
+
usageCache.set(key, usage);
|
|
50
|
+
}
|
|
51
|
+
return usage;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Attach each action's usage line, rendered from its syntax tree. Every command
|
|
55
|
+
* module exports its definitions through this, so a command without a syntax
|
|
56
|
+
* entry fails at load instead of shipping an undocumented contract.
|
|
57
|
+
*/
|
|
58
|
+
export function withUsage(resource, definitions) {
|
|
59
|
+
const commands = {};
|
|
60
|
+
for (const [action, definition,] of Object.entries(definitions)) {
|
|
61
|
+
commands[action] = { ...definition, usage: commandUsage(resource, action), };
|
|
62
|
+
}
|
|
63
|
+
return commands;
|
|
64
|
+
}
|
|
65
|
+
function walk(nodes, visit, context = {
|
|
66
|
+
optional: false,
|
|
67
|
+
oneOf: undefined,
|
|
68
|
+
}) {
|
|
69
|
+
for (const node of nodes) {
|
|
70
|
+
if (typeof node === "string")
|
|
71
|
+
continue;
|
|
72
|
+
visit(node, context);
|
|
73
|
+
if ("optional" in node) {
|
|
74
|
+
for (const alternative of node.optional) {
|
|
75
|
+
walk(alternative, visit, { ...context, optional: true, });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if ("oneOf" in node) {
|
|
79
|
+
for (const alternative of node.oneOf) {
|
|
80
|
+
walk(alternative, visit, { ...context, oneOf: node.oneOf, });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function flagNodes(nodes) {
|
|
86
|
+
const found = [];
|
|
87
|
+
walk(nodes, (node) => {
|
|
88
|
+
if ("flag" in node)
|
|
89
|
+
found.push(node);
|
|
90
|
+
});
|
|
91
|
+
return found;
|
|
92
|
+
}
|
|
93
|
+
/** Whether the syntax lists `--name` (the raw spelling, not an alias). */
|
|
94
|
+
export function syntaxHasFlag(nodes, name) {
|
|
95
|
+
return flagNodes(nodes).some((node) => node.flag === name);
|
|
96
|
+
}
|
|
97
|
+
/** Leading literal words after `dss` (resource and, when present, action). */
|
|
98
|
+
export function syntaxCommandWords(nodes) {
|
|
99
|
+
const words = [];
|
|
100
|
+
for (const node of nodes.slice(1)) {
|
|
101
|
+
if (typeof node !== "string")
|
|
102
|
+
break;
|
|
103
|
+
words.push(node);
|
|
104
|
+
}
|
|
105
|
+
return words;
|
|
106
|
+
}
|
|
107
|
+
function canonicalFlag(name) {
|
|
108
|
+
return FLAG_ALIASES[name] ?? name;
|
|
109
|
+
}
|
|
110
|
+
function knownCanonicalFlags(nodes) {
|
|
111
|
+
return unique(nodes.map((node) => canonicalFlag(node.flag))).filter((flag) => KNOWN_LONG_FLAGS.has(flag));
|
|
112
|
+
}
|
|
113
|
+
function unique(values) {
|
|
114
|
+
return [...new Set(values),];
|
|
115
|
+
}
|
|
116
|
+
/** Flags of a sequence outside its optional groups (a required choice's alternative). */
|
|
117
|
+
function requiredFlagsOf(nodes) {
|
|
118
|
+
const found = [];
|
|
119
|
+
walk(nodes, (node, context) => {
|
|
120
|
+
if ("flag" in node && !context.optional)
|
|
121
|
+
found.push(node);
|
|
122
|
+
});
|
|
123
|
+
return knownCanonicalFlags(found);
|
|
124
|
+
}
|
|
125
|
+
function deriveRequired(nodes) {
|
|
126
|
+
const requiredFlags = knownCanonicalFlags(nodes.filter((node) => typeof node !== "string" && "flag" in node));
|
|
127
|
+
const requiredOneOf = [];
|
|
128
|
+
for (const node of nodes) {
|
|
129
|
+
if (typeof node === "string" || !("oneOf" in node))
|
|
130
|
+
continue;
|
|
131
|
+
const alternatives = node.oneOf.map(requiredFlagsOf);
|
|
132
|
+
if (alternatives.length <= 1) {
|
|
133
|
+
requiredFlags.push(...(alternatives[0] ?? []));
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// A choice with a flagless alternative (a positional, e.g. sql query's
|
|
137
|
+
// `(SQL | --sql QUERY | ...)`) cannot be expressed as a flag-only choice:
|
|
138
|
+
// requiring one of the flags would be false. The handler stays the authority.
|
|
139
|
+
if (alternatives.some((alternative) => alternative.length === 0))
|
|
140
|
+
continue;
|
|
141
|
+
requiredOneOf.push({ oneOf: alternatives, });
|
|
142
|
+
}
|
|
143
|
+
return { requiredFlags: unique(requiredFlags), requiredOneOf, };
|
|
144
|
+
}
|
|
145
|
+
const PLACEHOLDER_VALUE = /^(<[^>]+>|[A-Z][A-Za-z0-9_]*)/;
|
|
146
|
+
function deriveValueHints(flags) {
|
|
147
|
+
const hints = {};
|
|
148
|
+
// Enumerations take precedence over a placeholder elsewhere in the line.
|
|
149
|
+
for (const node of flags) {
|
|
150
|
+
const flag = canonicalFlag(node.flag);
|
|
151
|
+
if (node.values && hints[flag] === undefined) {
|
|
152
|
+
hints[flag] = { valueType: "enum", enumValues: node.values, };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const node of flags) {
|
|
156
|
+
const flag = canonicalFlag(node.flag);
|
|
157
|
+
const placeholder = node.value?.match(PLACEHOLDER_VALUE)?.[1];
|
|
158
|
+
if (placeholder && hints[flag] === undefined)
|
|
159
|
+
hints[flag] = { valueType: placeholder, };
|
|
160
|
+
}
|
|
161
|
+
return hints;
|
|
162
|
+
}
|
|
163
|
+
function derivePositionals(nodes) {
|
|
164
|
+
const positionals = [];
|
|
165
|
+
walk(nodes, (node, context) => {
|
|
166
|
+
if (!("arg" in node))
|
|
167
|
+
return;
|
|
168
|
+
const arg = node;
|
|
169
|
+
// A positional inside a required choice that offers a flag instead is optional.
|
|
170
|
+
const flagAlternative = context.oneOf?.some((alternative) => flagNodes(alternative).length > 0)
|
|
171
|
+
?? false;
|
|
172
|
+
const required = !context.optional && !flagAlternative;
|
|
173
|
+
const existing = positionals.find((positional) => positional.name === arg.arg);
|
|
174
|
+
if (existing)
|
|
175
|
+
existing.required ||= required;
|
|
176
|
+
else
|
|
177
|
+
positionals.push({ name: arg.arg, required, });
|
|
178
|
+
});
|
|
179
|
+
return positionals;
|
|
180
|
+
}
|
|
181
|
+
/** Usage-level contract fields of a syntax tree (flags, requirements, value hints, positionals, inputs). */
|
|
182
|
+
export function deriveSyntax(nodes) {
|
|
183
|
+
const flags = flagNodes(nodes);
|
|
184
|
+
const has = (name) => flags.some((node) => node.flag === name);
|
|
185
|
+
const aliases = {};
|
|
186
|
+
for (const [raw, canonical,] of Object.entries(FLAG_ALIASES)) {
|
|
187
|
+
if (has(raw))
|
|
188
|
+
(aliases[canonical] ??= []).push(raw);
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
flags: knownCanonicalFlags(flags),
|
|
192
|
+
...deriveRequired(nodes),
|
|
193
|
+
valueHints: deriveValueHints(flags),
|
|
194
|
+
inputContract: {
|
|
195
|
+
...(has("stdin") ? { stdin: true, } : {}),
|
|
196
|
+
...(flags.some((node) => node.flag === "data" && (node.value !== undefined || node.values !== undefined))
|
|
197
|
+
? { dataFlag: true, }
|
|
198
|
+
: {}),
|
|
199
|
+
...(has("data-file") ? { dataFileFlag: true, } : {}),
|
|
200
|
+
},
|
|
201
|
+
positionalArguments: derivePositionals(nodes),
|
|
202
|
+
aliases,
|
|
203
|
+
dryRun: has("dry-run"),
|
|
204
|
+
producesLocalFile: flags.some((node) => (node.flag === "output" || node.flag === "output-file") && node.value === "PATH"),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const syntaxCache = new Map();
|
|
208
|
+
/** Usage-level contract fields for `resource.action`, derived from its syntax tree. */
|
|
209
|
+
export function commandSyntax(resource, action) {
|
|
210
|
+
const key = `${resource}.${action}`;
|
|
211
|
+
let syntax = syntaxCache.get(key);
|
|
212
|
+
if (!syntax) {
|
|
213
|
+
syntax = deriveSyntax(requiredSyntaxTree(resource, action));
|
|
214
|
+
syntaxCache.set(key, syntax);
|
|
215
|
+
}
|
|
216
|
+
return syntax;
|
|
217
|
+
}
|
package/dist/src/cli/types.d.ts
CHANGED
|
@@ -28,7 +28,10 @@ export interface CommandMeta extends CommandRegistryOverride {
|
|
|
28
28
|
* credentials are configured, instead of being masked by a missing-URL error.
|
|
29
29
|
*/
|
|
30
30
|
validate?: (args: string[], flags: Record<string, string | boolean>) => void;
|
|
31
|
+
/** Rendered from the command's syntax tree in `src/cli/command-syntax.json`; never hand-written. */
|
|
31
32
|
usage: string;
|
|
32
33
|
description?: string;
|
|
33
34
|
examples?: string[];
|
|
34
35
|
}
|
|
36
|
+
/** A command as authored: its usage line comes from `src/cli/command-syntax.json` (see `withUsage`). */
|
|
37
|
+
export type CommandDefinition = Omit<CommandMeta, "usage">;
|