dataiku-sdk 3.4.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.
Files changed (110) hide show
  1. package/README.md +3 -3
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/src/cli/command-syntax.json +14524 -0
  4. package/dist/src/cli/commands/analysis.js +6 -9
  5. package/dist/src/cli/commands/api-deployer.js +15 -33
  6. package/dist/src/cli/commands/api-service.js +17 -27
  7. package/dist/src/cli/commands/app.js +22 -39
  8. package/dist/src/cli/commands/bundle.js +15 -24
  9. package/dist/src/cli/commands/business-app.js +10 -17
  10. package/dist/src/cli/commands/code-env.js +23 -36
  11. package/dist/src/cli/commands/code.js +4 -4
  12. package/dist/src/cli/commands/connection.js +12 -22
  13. package/dist/src/cli/commands/continuous-activity.js +6 -9
  14. package/dist/src/cli/commands/dashboard.js +8 -13
  15. package/dist/src/cli/commands/data-collection.js +9 -16
  16. package/dist/src/cli/commands/data-quality.js +14 -26
  17. package/dist/src/cli/commands/dataset.js +27 -50
  18. package/dist/src/cli/commands/discussion.js +7 -10
  19. package/dist/src/cli/commands/doctor.js +3 -3
  20. package/dist/src/cli/commands/flow-zone.js +8 -17
  21. package/dist/src/cli/commands/folder.js +12 -20
  22. package/dist/src/cli/commands/future.js +7 -10
  23. package/dist/src/cli/commands/group.js +7 -11
  24. package/dist/src/cli/commands/insight.js +7 -11
  25. package/dist/src/cli/commands/job.js +13 -23
  26. package/dist/src/cli/commands/knowledge-bank.js +5 -6
  27. package/dist/src/cli/commands/llm.js +5 -8
  28. package/dist/src/cli/commands/macro.js +9 -16
  29. package/dist/src/cli/commands/meaning.js +7 -11
  30. package/dist/src/cli/commands/metrics.js +7 -10
  31. package/dist/src/cli/commands/ml-task.js +12 -20
  32. package/dist/src/cli/commands/model-evaluation-store.js +7 -11
  33. package/dist/src/cli/commands/notebook.js +15 -27
  34. package/dist/src/cli/commands/plugin.js +35 -68
  35. package/dist/src/cli/commands/project-deployer.js +10 -22
  36. package/dist/src/cli/commands/project-folder.js +10 -17
  37. package/dist/src/cli/commands/project-git.js +38 -70
  38. package/dist/src/cli/commands/project-library.js +15 -23
  39. package/dist/src/cli/commands/project.js +9 -26
  40. package/dist/src/cli/commands/recipe.d.ts +16 -0
  41. package/dist/src/cli/commands/recipe.js +83 -94
  42. package/dist/src/cli/commands/saved-model.js +23 -39
  43. package/dist/src/cli/commands/scenario.js +20 -34
  44. package/dist/src/cli/commands/sql.js +4 -4
  45. package/dist/src/cli/commands/statistics.js +11 -18
  46. package/dist/src/cli/commands/streaming-endpoint.js +7 -11
  47. package/dist/src/cli/commands/user.js +11 -22
  48. package/dist/src/cli/commands/variable.js +3 -4
  49. package/dist/src/cli/commands/webapp.js +10 -16
  50. package/dist/src/cli/commands/wiki.js +9 -13
  51. package/dist/src/cli/commands/workspace.js +8 -14
  52. package/dist/src/cli/contract.d.ts +5 -15
  53. package/dist/src/cli/contract.js +156 -253
  54. package/dist/src/cli/doctor.js +1 -0
  55. package/dist/src/cli/env.d.ts +2 -0
  56. package/dist/src/cli/env.js +15 -1
  57. package/dist/src/cli/helpers/app-permissions.js +2 -1
  58. package/dist/src/cli/helpers/flow-zone.js +2 -1
  59. package/dist/src/cli/helpers/recipe.js +2 -1
  60. package/dist/src/cli/syntax.d.ts +83 -0
  61. package/dist/src/cli/syntax.js +217 -0
  62. package/dist/src/cli/types.d.ts +3 -0
  63. package/dist/src/cli/usage.d.ts +1 -1
  64. package/dist/src/cli/usage.js +4 -2
  65. package/dist/src/cli/version.js +2 -0
  66. package/dist/src/cli.js +37 -64
  67. package/dist/src/client.js +7 -6
  68. package/dist/src/errors.d.ts +12 -1
  69. package/dist/src/errors.js +28 -3
  70. package/dist/src/index.d.ts +1 -3
  71. package/dist/src/index.js +2 -2
  72. package/dist/src/resources/api-services.js +2 -3
  73. package/dist/src/resources/applications.js +2 -4
  74. package/dist/src/resources/code-envs.js +2 -2
  75. package/dist/src/resources/data-quality.js +4 -3
  76. package/dist/src/resources/datasets.d.ts +13 -0
  77. package/dist/src/resources/datasets.js +5 -4
  78. package/dist/src/resources/flow-zones.js +4 -2
  79. package/dist/src/resources/folders.js +4 -2
  80. package/dist/src/resources/futures.js +1 -3
  81. package/dist/src/resources/insights.js +3 -2
  82. package/dist/src/resources/jobs.js +4 -6
  83. package/dist/src/resources/macros.js +2 -4
  84. package/dist/src/resources/notebooks.js +2 -3
  85. package/dist/src/resources/project-git.js +9 -11
  86. package/dist/src/resources/project-library.d.ts +0 -2
  87. package/dist/src/resources/project-library.js +2 -7
  88. package/dist/src/resources/projects.js +4 -3
  89. package/dist/src/resources/recipes.d.ts +28 -0
  90. package/dist/src/resources/recipes.js +116 -84
  91. package/dist/src/resources/sql.js +8 -3
  92. package/dist/src/resources/statistics.js +2 -1
  93. package/dist/src/resources/variables.js +2 -1
  94. package/dist/src/resources/webapps.js +2 -3
  95. package/dist/src/schemas.d.ts +1 -2
  96. package/dist/src/schemas.js +1 -1
  97. package/dist/src/skill.js +6 -7
  98. package/dist/src/utils/cleanup-ledger.js +9 -8
  99. package/dist/src/utils/polling.d.ts +3 -0
  100. package/dist/src/utils/polling.js +3 -0
  101. package/dist/src/utils/project-archive.js +2 -2
  102. package/dist/src/utils/stable-hash.d.ts +3 -1
  103. package/dist/src/utils/stable-hash.js +2 -0
  104. package/package.json +4 -4
  105. package/plugin.json +1 -1
  106. package/skills/dataiku-dss/SKILL.md +2 -3
  107. package/skills/dataiku-dss/references/authentication.md +3 -3
  108. package/skills/dataiku-dss/references/coding.md +1 -1
  109. package/dist/src/utils/pagination.d.ts +0 -8
  110. package/dist/src/utils/pagination.js +0 -23
@@ -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
  }
@@ -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;
@@ -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 Error(`Stalled writing app permissions snapshot at byte ${offset}.`);
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("--name is required. Usage: dss flow-zone create --name NAME [--color #RRGGBB]");
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 Error(`Stalled writing recipe backup at byte ${offset}.`);
130
+ throw new ClientValidationError(`Stalled writing recipe backup at byte ${offset}.`, "internal_error");
130
131
  }
131
132
  offset += written;
132
133
  }
@@ -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
+ }
@@ -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">;
@@ -1,6 +1,6 @@
1
1
  import type { StableErrorCode } from "../errors.js";
2
2
  export declare const RESOURCE_NAMES: string[];
3
- export declare function inferRequiresProject(resource: string, action: string, usage: string): boolean;
3
+ export declare function inferRequiresProject(resource: string, action: string): boolean;
4
4
  export declare class UsageError extends Error {
5
5
  readonly code: StableErrorCode;
6
6
  readonly hint?: string;
@@ -1,3 +1,4 @@
1
+ import { commandSyntaxTree, syntaxHasFlag, } from "./syntax.js";
1
2
  export const RESOURCE_NAMES = [
2
3
  "agent",
3
4
  "app",
@@ -79,7 +80,7 @@ const PROJECT_SCOPED_RESOURCES = new Set([
79
80
  "saved-model",
80
81
  "wiki",
81
82
  ]);
82
- export function inferRequiresProject(resource, action, usage) {
83
+ export function inferRequiresProject(resource, action) {
83
84
  if (resource === "agent" || resource === "auth" || resource === "doctor" || resource === "commands"
84
85
  || resource === "install-skill" || resource === "version")
85
86
  return false;
@@ -92,7 +93,8 @@ export function inferRequiresProject(resource, action, usage) {
92
93
  return false;
93
94
  if (resource === "project-git")
94
95
  return !action.startsWith("future-");
95
- return usage.includes("--project-key");
96
+ const syntax = commandSyntaxTree(resource, action);
97
+ return syntax !== undefined && syntaxHasFlag(syntax, "project-key");
96
98
  }
97
99
  export class UsageError extends Error {
98
100
  code;
@@ -212,8 +212,10 @@ export const AGENT_CONTRACT_VERSION = 2;
212
212
  export const JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
213
213
  export const AGENT_CONTRACT_SCHEMA_ID = "https://clssck.github.io/dataiku-sdk/schemas/agent-contract-v2.json";
214
214
  export function cliVersionResult() {
215
+ /* oxlint-disable dss/no-direct-process-env -- launcher-injected load source and build revision */
215
216
  const loadSource = process.env["DSS_LOAD_SOURCE"] ?? detectLoadSource();
216
217
  const envBuildRevision = process.env["DSS_BUILD_REVISION"];
218
+ /* oxlint-enable dss/no-direct-process-env */
217
219
  return buildVersionPayload({
218
220
  packageVersion: CLI_VERSION,
219
221
  checkoutRevision: CLI_GIT_REVISION,