dataiku-sdk 3.2.0 → 3.4.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 (132) hide show
  1. package/README.md +151 -46
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +102 -0
  4. package/dist/packages/types/src/index.js +76 -0
  5. package/dist/src/cli/commands/app.js +57 -15
  6. package/dist/src/cli/commands/bundle.js +1 -1
  7. package/dist/src/cli/commands/code-env.js +13 -7
  8. package/dist/src/cli/commands/code.js +1 -1
  9. package/dist/src/cli/commands/connection.d.ts +9 -0
  10. package/dist/src/cli/commands/connection.js +267 -1
  11. package/dist/src/cli/commands/data-collection.d.ts +2 -0
  12. package/dist/src/cli/commands/data-collection.js +164 -0
  13. package/dist/src/cli/commands/dataset.js +162 -33
  14. package/dist/src/cli/commands/future.js +2 -2
  15. package/dist/src/cli/commands/group.d.ts +2 -0
  16. package/dist/src/cli/commands/group.js +109 -0
  17. package/dist/src/cli/commands/index.js +151 -78
  18. package/dist/src/cli/commands/job.js +4 -2
  19. package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
  20. package/dist/src/cli/commands/knowledge-bank.js +80 -0
  21. package/dist/src/cli/commands/llm.d.ts +2 -0
  22. package/dist/src/cli/commands/llm.js +93 -0
  23. package/dist/src/cli/commands/macro.d.ts +2 -0
  24. package/dist/src/cli/commands/macro.js +221 -0
  25. package/dist/src/cli/commands/ml-task.js +18 -6
  26. package/dist/src/cli/commands/notebook.js +6 -2
  27. package/dist/src/cli/commands/plugin.d.ts +2 -0
  28. package/dist/src/cli/commands/plugin.js +799 -0
  29. package/dist/src/cli/commands/project-folder.d.ts +2 -0
  30. package/dist/src/cli/commands/project-folder.js +197 -0
  31. package/dist/src/cli/commands/project-git.js +15 -55
  32. package/dist/src/cli/commands/project.js +52 -6
  33. package/dist/src/cli/commands/recipe.js +36 -1
  34. package/dist/src/cli/commands/saved-model.d.ts +5 -0
  35. package/dist/src/cli/commands/saved-model.js +388 -1
  36. package/dist/src/cli/commands/scenario.js +139 -2
  37. package/dist/src/cli/commands/user.d.ts +8 -0
  38. package/dist/src/cli/commands/user.js +296 -0
  39. package/dist/src/cli/contract.d.ts +1 -2
  40. package/dist/src/cli/contract.js +1098 -297
  41. package/dist/src/cli/flags.js +30 -1
  42. package/dist/src/cli/helpers/app-successor.d.ts +19 -4
  43. package/dist/src/cli/helpers/app-successor.js +90 -30
  44. package/dist/src/cli/helpers/cleanup.js +3 -1
  45. package/dist/src/cli/helpers/flow-zone.js +1 -2
  46. package/dist/src/cli/output-schemas.d.ts +24 -0
  47. package/dist/src/cli/output-schemas.js +233 -0
  48. package/dist/src/cli/output.d.ts +1 -1
  49. package/dist/src/cli/output.js +23 -10
  50. package/dist/src/cli/usage.d.ts +1 -0
  51. package/dist/src/cli/usage.js +47 -1
  52. package/dist/src/cli.js +147 -41
  53. package/dist/src/client.d.ts +92 -5
  54. package/dist/src/client.js +202 -31
  55. package/dist/src/errors.d.ts +3 -0
  56. package/dist/src/errors.js +8 -4
  57. package/dist/src/generated/action-output-schemas.json +1735 -0
  58. package/dist/src/index.d.ts +16 -7
  59. package/dist/src/index.js +11 -2
  60. package/dist/src/resources/analyses.js +1 -8
  61. package/dist/src/resources/applications.d.ts +8 -6
  62. package/dist/src/resources/applications.js +21 -21
  63. package/dist/src/resources/base.d.ts +4 -0
  64. package/dist/src/resources/base.js +15 -0
  65. package/dist/src/resources/bundles.d.ts +9 -4
  66. package/dist/src/resources/bundles.js +10 -5
  67. package/dist/src/resources/connections.d.ts +78 -1
  68. package/dist/src/resources/connections.js +132 -1
  69. package/dist/src/resources/data-collections.d.ts +118 -0
  70. package/dist/src/resources/data-collections.js +49 -0
  71. package/dist/src/resources/datasets.d.ts +69 -0
  72. package/dist/src/resources/datasets.js +136 -5
  73. package/dist/src/resources/flow-zones.d.ts +6 -2
  74. package/dist/src/resources/flow-zones.js +6 -3
  75. package/dist/src/resources/futures.d.ts +6 -1
  76. package/dist/src/resources/futures.js +68 -22
  77. package/dist/src/resources/groups.d.ts +47 -0
  78. package/dist/src/resources/groups.js +56 -0
  79. package/dist/src/resources/jobs.d.ts +8 -13
  80. package/dist/src/resources/jobs.js +71 -34
  81. package/dist/src/resources/knowledge-banks.d.ts +58 -0
  82. package/dist/src/resources/knowledge-banks.js +61 -0
  83. package/dist/src/resources/llms.d.ts +92 -0
  84. package/dist/src/resources/llms.js +62 -0
  85. package/dist/src/resources/macros.d.ts +121 -0
  86. package/dist/src/resources/macros.js +231 -0
  87. package/dist/src/resources/ml-tasks.d.ts +37 -2
  88. package/dist/src/resources/ml-tasks.js +109 -20
  89. package/dist/src/resources/model-evaluation-stores.js +1 -8
  90. package/dist/src/resources/notebooks.d.ts +43 -7
  91. package/dist/src/resources/notebooks.js +53 -13
  92. package/dist/src/resources/plugins.d.ts +263 -0
  93. package/dist/src/resources/plugins.js +455 -0
  94. package/dist/src/resources/project-folders.d.ts +71 -0
  95. package/dist/src/resources/project-folders.js +57 -0
  96. package/dist/src/resources/project-git.d.ts +12 -12
  97. package/dist/src/resources/project-git.js +84 -82
  98. package/dist/src/resources/project-library.d.ts +0 -1
  99. package/dist/src/resources/project-library.js +1 -17
  100. package/dist/src/resources/projects.d.ts +23 -3
  101. package/dist/src/resources/projects.js +60 -34
  102. package/dist/src/resources/recipes.d.ts +15 -1
  103. package/dist/src/resources/recipes.js +73 -6
  104. package/dist/src/resources/saved-models.d.ts +134 -1
  105. package/dist/src/resources/saved-models.js +214 -6
  106. package/dist/src/resources/scenarios.d.ts +105 -0
  107. package/dist/src/resources/scenarios.js +232 -106
  108. package/dist/src/resources/sql.js +5 -1
  109. package/dist/src/resources/users.d.ts +129 -0
  110. package/dist/src/resources/users.js +128 -0
  111. package/dist/src/resources/webapps.d.ts +15 -2
  112. package/dist/src/resources/webapps.js +18 -4
  113. package/dist/src/schemas.d.ts +2 -2
  114. package/dist/src/schemas.js +1 -1
  115. package/dist/src/utils/deep-merge.d.ts +1 -1
  116. package/dist/src/utils/git-reference.d.ts +12 -0
  117. package/dist/src/utils/git-reference.js +38 -0
  118. package/dist/src/utils/polling.d.ts +20 -0
  119. package/dist/src/utils/polling.js +27 -0
  120. package/dist/src/utils/response-file.js +1 -1
  121. package/dist/src/utils/secret-sanitize.d.ts +54 -0
  122. package/dist/src/utils/secret-sanitize.js +101 -0
  123. package/package.json +9 -8
  124. package/packages/types/dist/index.d.ts +102 -0
  125. package/packages/types/dist/index.js +76 -0
  126. package/plugin.json +1 -1
  127. package/skills/dataiku-dss/SKILL.md +11 -7
  128. package/skills/dataiku-dss/references/app-releases.md +8 -8
  129. package/skills/dataiku-dss/references/authentication.md +6 -6
  130. package/skills/dataiku-dss/references/discovery.md +4 -4
  131. package/skills/dataiku-dss/references/mutations.md +1 -1
  132. package/skills/dataiku-dss/references/troubleshooting.md +2 -2
@@ -47,14 +47,15 @@ function projectionRecords(result) {
47
47
  const items = Array.isArray(result) ? result : [result,];
48
48
  return items.filter((item) => item !== null && typeof item === "object" && !Array.isArray(item));
49
49
  }
50
- function warnUnknownProjectionFields(result, fields) {
50
+ function warnUnknownProjectionFields(result, fields, availableFieldNames) {
51
51
  const records = projectionRecords(result);
52
52
  if (records.length === 0)
53
53
  return;
54
54
  const unknownFields = fields.filter((field) => !records.some((record) => resolveFieldPath(record, field).found));
55
55
  if (unknownFields.length === 0)
56
56
  return;
57
- const availableFields = [...new Set(records.flatMap((record) => Object.keys(record))),].sort();
57
+ const availableFields = availableFieldNames?.()
58
+ ?? [...new Set(records.flatMap((record) => Object.keys(record))),].sort();
58
59
  enqueueCliWarning({
59
60
  code: "field_projection_missing",
60
61
  fields: unknownFields,
@@ -67,8 +68,8 @@ function warnUnknownProjectionFields(result, fields) {
67
68
  * element-wise; scalars and string results pass through untouched. Requested keys
68
69
  * that are absent become null so every row keeps a stable, predictable shape.
69
70
  */
70
- export function projectResultFields(result, fields) {
71
- warnUnknownProjectionFields(result, fields);
71
+ export function projectResultFields(result, fields, availableFieldNames) {
72
+ warnUnknownProjectionFields(result, fields, availableFieldNames);
72
73
  if (Array.isArray(result))
73
74
  return result.map((item) => pickResultFields(item, fields));
74
75
  return pickResultFields(result, fields);
@@ -98,7 +99,7 @@ export function transientBodyWithTargetContext(body, target, elapsedMs) {
98
99
  }
99
100
  export function addTransientTargetContext(error, target, elapsedMs) {
100
101
  if (error instanceof DataikuError && error.category === "transient") {
101
- throw new DataikuError(error.status, error.statusText, transientBodyWithTargetContext(error.body, target, elapsedMs), error.retry, error.requestId, { target, elapsedMs, });
102
+ throw new DataikuError(error.status, error.statusText, transientBodyWithTargetContext(error.body, target, elapsedMs), error.retry, error.requestId, { target, elapsedMs, bodyTruncated: error.bodyTruncated, });
102
103
  }
103
104
  throw error;
104
105
  }
@@ -106,10 +107,16 @@ export function isFailedWaitResult(result) {
106
107
  if (result === null || typeof result !== "object" || Array.isArray(result))
107
108
  return false;
108
109
  const record = result;
109
- return record.success === false
110
- && typeof record.elapsedMs === "number"
111
- && typeof record.pollCount === "number"
112
- && (typeof record.state === "string" || typeof record.outcome === "string");
110
+ if (record.success !== false)
111
+ return false;
112
+ if (typeof record.elapsedMs !== "number" || typeof record.pollCount !== "number")
113
+ return false;
114
+ // Job/future wait results name the terminal state as a string; macro wait
115
+ // results identify themselves by their runnable identity fields with the
116
+ // poll state attached as an object (or omitted entirely).
117
+ if (typeof record.state === "string" || typeof record.outcome === "string")
118
+ return true;
119
+ return typeof record.runnableType === "string" && typeof record.runId === "string";
113
120
  }
114
121
  export function isAssertionFailureResult(result) {
115
122
  if (result === null || typeof result !== "object" || Array.isArray(result))
@@ -172,7 +179,13 @@ export class CommandResultFailure extends Error {
172
179
  export function commandFailureMessage(result) {
173
180
  if (isFailedWaitResult(result)) {
174
181
  const record = result;
175
- const state = typeof record.state === "string" ? record.state : record.outcome;
182
+ const state = typeof record.state === "string"
183
+ ? record.state
184
+ : typeof record.outcome === "string"
185
+ ? record.outcome
186
+ : typeof record.timedOut === "boolean"
187
+ ? "timed out"
188
+ : undefined;
176
189
  return `Command completed with failed long-running result${state ? `: ${state}` : ""}.`;
177
190
  }
178
191
  if (isAssertionFailureResult(result)) {
@@ -1,5 +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
4
  export declare class UsageError extends Error {
4
5
  readonly code: StableErrorCode;
5
6
  readonly hint?: string;
@@ -9,6 +9,15 @@ export const RESOURCE_NAMES = [
9
9
  "bundle",
10
10
  "project-deployer",
11
11
  "project-git",
12
+ "project-folder",
13
+ "data-collection",
14
+ "llm",
15
+ "knowledge-bank",
16
+ "macro",
17
+ "plugin",
18
+ "user",
19
+ "group",
20
+ "project",
12
21
  "project-library",
13
22
  "streaming-endpoint",
14
23
  "continuous-activity",
@@ -44,10 +53,47 @@ export const RESOURCE_NAMES = [
44
53
  "commands",
45
54
  "fixtures",
46
55
  "install-skill",
47
- "project",
48
56
  "version",
49
57
  ]
50
58
  .sort();
59
+ const PROJECT_SCOPED_RESOURCES = new Set([
60
+ "analysis",
61
+ "data-quality",
62
+ "dashboard",
63
+ "knowledge-bank",
64
+ "dataset",
65
+ "llm",
66
+ "flow-zone",
67
+ "insight",
68
+ "folder",
69
+ "fixtures",
70
+ "job",
71
+ "notebook",
72
+ "macro",
73
+ "ml-task",
74
+ "model-evaluation-store",
75
+ "recipe",
76
+ "scenario",
77
+ "sql",
78
+ "variable",
79
+ "saved-model",
80
+ "wiki",
81
+ ]);
82
+ export function inferRequiresProject(resource, action, usage) {
83
+ if (resource === "agent" || resource === "auth" || resource === "doctor" || resource === "commands"
84
+ || resource === "install-skill" || resource === "version")
85
+ return false;
86
+ if (resource === "sql" && action === "query")
87
+ return false;
88
+ if (PROJECT_SCOPED_RESOURCES.has(resource))
89
+ return true;
90
+ // Plugin project scope is optional, not a requirement.
91
+ if (resource === "plugin")
92
+ return false;
93
+ if (resource === "project-git")
94
+ return !action.startsWith("future-");
95
+ return usage.includes("--project-key");
96
+ }
51
97
  export class UsageError extends Error {
52
98
  code;
53
99
  hint;
package/dist/src/cli.js CHANGED
@@ -3,28 +3,26 @@ import * as fs from "node:fs/promises";
3
3
  import { homedir, } from "node:os";
4
4
  import { resolve, } from "node:path";
5
5
  import { num, unknownJsonInput, } from "./cli/coerce.js";
6
+ import { commands, } from "./cli/commands/index.js";
6
7
  import { dataikuEnvironmentEnabled, loadEnvFile, } from "./cli/env.js";
7
8
  import { executionMode, FLAG_ALIASES, isNegativeNumberToken, parseArgs, SHORT_FLAGS, VALUE_FLAGS, } from "./cli/flags.js";
8
9
  import { cleanupLedgerEntry, } from "./cli/helpers/cleanup.js";
9
10
  import { commandFailureExitCode, CommandResultFailure, flushCliWarnings, isFailedWaitResult, planResult, projectResultFields, setOutputFieldProjection, writeCommandResult, } from "./cli/output.js";
10
11
  import { currentCommandContext, resolveCredentials, resolveLoginCredentials, } from "./cli/runtime.js";
11
- import { COMMANDS_RUN_HINT, missingActionError, noCommandError, requireArgs, unknownActionError, unknownResourceError, UsageError, } from "./cli/usage.js";
12
+ import { COMMANDS_RUN_HINT, inferRequiresProject, missingActionError, noCommandError, requireArgs, RESOURCE_NAMES, unknownActionError, unknownResourceError, UsageError, } from "./cli/usage.js";
12
13
  import { cliVersionResult, } from "./cli/version.js";
13
14
  import { getCredentialsPath, } from "./config.js";
14
15
  import { canonicalStatusText, ClientValidationError, DataikuError, dataikuErrorCode, } from "./errors.js";
15
16
  import { appendCleanupLedgerEntry, findCleanupLedgerBindingViolation, preflightCleanupLedgerPath, readCleanupLedger, reserveCleanupLedgerDssUrl, } from "./utils/cleanup-ledger.js";
16
17
  import { canonicalDssUrl, } from "./utils/dss-url.js";
17
- let commands;
18
+ import { sanitizeSecrets, } from "./utils/secret-sanitize.js";
18
19
  let contract;
19
20
  let commandRuntimeLoad;
20
- // Dispatch and error reporting initialize these bindings before synchronous validation.
21
+ // Command definitions are resource-lazy; full contract metadata loads only
22
+ // after a valid action is selected or explicit discovery is requested.
21
23
  function loadCommandRuntime() {
22
- return commandRuntimeLoad ??= Promise.all([
23
- import("./cli/commands/index.js"),
24
- import("./cli/contract.js"),
25
- ]).then(([commandModule, contractModule,]) => {
26
- commands = commandModule.commands;
27
- contract = contractModule;
24
+ return commandRuntimeLoad ??= import("./cli/contract.js").then((module) => {
25
+ contract = module;
28
26
  });
29
27
  }
30
28
  // ---------------------------------------------------------------------------
@@ -279,6 +277,8 @@ const META_COMMAND_RESOURCES = {
279
277
  };
280
278
  const commandRegistryCache = new Map();
281
279
  function cachedCommandRegistry(resource) {
280
+ if (!contract)
281
+ return undefined;
282
282
  if (!commandRegistryCache.has(resource)) {
283
283
  commandRegistryCache.set(resource, contract.buildCommandRegistry(resource)[resource]);
284
284
  }
@@ -415,11 +415,21 @@ const batchDryRunClientHandler = {
415
415
  throw new BatchDryRunValidationComplete();
416
416
  },
417
417
  };
418
- const batchDryRunClient = new Proxy(batchDryRunClientTarget, batchDryRunClientHandler);
418
+ const batchDryRunResource = new Proxy(batchDryRunClientTarget, batchDryRunClientHandler);
419
+ let batchDryRunClient;
419
420
  async function validateDryRunHandlerPreconditions(handler, args, flags) {
420
421
  const readsFromStdin = flags["stdin"] === true || flags["sql"] === "-";
421
422
  if (readsFromStdin)
422
423
  return;
424
+ if (!batchDryRunClient) {
425
+ const { DataikuClient, } = await import("./client.js");
426
+ batchDryRunClient = new Proxy(new DataikuClient({
427
+ url: "http://dry-run.invalid",
428
+ apiKey: "dry-run-validation",
429
+ }), {
430
+ get: (_target, property) => property === "then" ? undefined : batchDryRunResource,
431
+ });
432
+ }
423
433
  try {
424
434
  await handler(batchDryRunClient, args, flags);
425
435
  }
@@ -760,9 +770,8 @@ function batchPlan(flags) {
760
770
  });
761
771
  }
762
772
  async function runMetaCommand(resource, action, flags) {
763
- if (resource !== "version")
764
- await loadCommandRuntime();
765
773
  if (resource === "doctor") {
774
+ await loadCommandRuntime();
766
775
  if (action !== undefined && action !== "run") {
767
776
  throw unknownActionError("doctor", action, ["run",]);
768
777
  }
@@ -772,6 +781,7 @@ async function runMetaCommand(resource, action, flags) {
772
781
  return { action: "run", result, exitCode, };
773
782
  }
774
783
  if (resource === "auth") {
784
+ await loadCommandRuntime();
775
785
  const validActions = Object.keys(contract.AUTH_ACTIONS);
776
786
  if (!action) {
777
787
  throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
@@ -787,6 +797,7 @@ async function runMetaCommand(resource, action, flags) {
787
797
  return { action, result: await authMeta.handler(flags), exitCode: 0, };
788
798
  }
789
799
  if (resource === "install-skill") {
800
+ await loadCommandRuntime();
790
801
  if (action !== undefined && action !== "run") {
791
802
  throw unknownActionError("install-skill", action, ["run",]);
792
803
  }
@@ -794,6 +805,7 @@ async function runMetaCommand(resource, action, flags) {
794
805
  return { action: "run", result: await runInstallSkill(flags), exitCode: 0, };
795
806
  }
796
807
  if (resource === "agent") {
808
+ await loadCommandRuntime();
797
809
  if (!action)
798
810
  throw missingActionError("agent", ["contract",], contract.AGENT_CONTRACT_USAGE);
799
811
  currentCommandContext.action = action;
@@ -802,6 +814,7 @@ async function runMetaCommand(resource, action, flags) {
802
814
  return { action, result: contract.buildAgentContract(), exitCode: 0, };
803
815
  }
804
816
  if (resource === "commands") {
817
+ await loadCommandRuntime();
805
818
  if (!action)
806
819
  throw missingActionError("commands", ["run",], contract.COMMANDS_USAGE);
807
820
  currentCommandContext.action = action;
@@ -809,24 +822,28 @@ async function runMetaCommand(resource, action, flags) {
809
822
  throw unknownActionError("commands", action, ["run",]);
810
823
  validateSupportedCommandFlags("commands", "run", flags);
811
824
  const selectors = commandRegistrySelectors(flags);
812
- const registry = contract.buildCommandRegistry();
825
+ const outputPath = flags["output"];
826
+ if (selectors.length === 0 && typeof outputPath !== "string") {
827
+ return { action, result: contract.commandActionSummary(), exitCode: 0, };
828
+ }
829
+ const registry = selectors.length === 0 ? contract.buildCommandRegistry() : {};
813
830
  for (const selector of selectors) {
814
831
  const selectorParts = selector.split(".");
815
832
  if (selectorParts.some((part) => part.length === 0)) {
816
833
  throw new UsageError(`Invalid --fields selector: ${selector}. Expected RESOURCE or RESOURCE.ACTION[.FIELD...].`, "usage_error", contract.COMMANDS_USAGE, { selector, });
817
834
  }
818
835
  const [selectedResource, selectedAction,] = selectorParts;
819
- const resourceActions = registry[selectedResource];
836
+ const resourceActions = cachedCommandRegistry(selectedResource);
820
837
  if (!resourceActions)
821
838
  throw unknownResourceError(selectedResource);
839
+ registry[selectedResource] = resourceActions;
822
840
  if (selectedAction && !resourceActions[selectedAction]) {
823
841
  throw unknownActionError(selectedResource, selectedAction, Object.keys(resourceActions));
824
842
  }
825
843
  }
826
- const outputPath = flags["output"];
827
844
  const selectedRegistry = selectors.length === 0
828
845
  ? registry
829
- : projectResultFields(registry, selectors);
846
+ : projectResultFields(registry, selectors, () => Object.keys(contract.commandActionSummary()).sort());
830
847
  if (typeof outputPath === "string") {
831
848
  const exported = selectedRegistry;
832
849
  await fs.writeFile(outputPath, `${JSON.stringify(exported)}\n`, "utf-8");
@@ -834,7 +851,7 @@ async function runMetaCommand(resource, action, flags) {
834
851
  }
835
852
  return {
836
853
  action,
837
- result: selectors.length === 0 ? contract.commandActionSummary(registry) : selectedRegistry,
854
+ result: selectedRegistry,
838
855
  exitCode: 0,
839
856
  };
840
857
  }
@@ -846,6 +863,7 @@ async function runMetaCommand(resource, action, flags) {
846
863
  return { action: "run", result: cliVersionResult(), exitCode: 0, };
847
864
  }
848
865
  if (resource === "cleanup") {
866
+ await loadCommandRuntime();
849
867
  if (action !== undefined && action !== "run") {
850
868
  throw unknownActionError("cleanup", action, ["run",]);
851
869
  }
@@ -857,6 +875,7 @@ async function runMetaCommand(resource, action, flags) {
857
875
  return { action: "run", result, exitCode, };
858
876
  }
859
877
  if (resource === "fixtures") {
878
+ await loadCommandRuntime();
860
879
  if (action !== undefined && action !== "run") {
861
880
  throw unknownActionError("fixtures", action, ["run",]);
862
881
  }
@@ -865,6 +884,7 @@ async function runMetaCommand(resource, action, flags) {
865
884
  return { action: "run", result: await runFixtures(flags), exitCode: 0, };
866
885
  }
867
886
  if (resource === "batch") {
887
+ await loadCommandRuntime();
868
888
  if (action !== undefined && action !== "run") {
869
889
  throw unknownActionError("batch", action, ["run",]);
870
890
  }
@@ -1181,27 +1201,12 @@ function summarizeCleanupFailures(failures) {
1181
1201
  : [];
1182
1202
  }));
1183
1203
  }
1184
- function rawFlagValue(argv, flagName) {
1185
- const longFlag = `--${flagName}`;
1186
- for (let index = 0; index < argv.length; index++) {
1187
- const arg = argv[index];
1188
- if (arg === longFlag) {
1189
- const next = argv[index + 1];
1190
- return next && !next.startsWith("-") ? next : undefined;
1191
- }
1192
- if (arg.startsWith(`${longFlag}=`))
1193
- return arg.slice(longFlag.length + 1);
1194
- }
1195
- return undefined;
1196
- }
1197
- function commandIsProjectScoped(resource, action) {
1198
- if (!resource)
1199
- return false;
1200
- const usage = commands[resource]?.[action ?? ""]?.usage ?? "";
1201
- return contract.inferRequiresProject(resource, action ?? "", usage);
1202
- }
1203
- function rawCommandContext() {
1204
- const argv = process.argv.slice(2);
1204
+ /**
1205
+ * Positional arguments scanned with the same flag grammar as `parseArgs`, so
1206
+ * error reporting can recover the invoked resource/action without depending
1207
+ * on the command registry being loaded.
1208
+ */
1209
+ function rawPositionals(argv) {
1205
1210
  const positionals = [];
1206
1211
  for (let index = 0; index < argv.length; index++) {
1207
1212
  const arg = argv[index];
@@ -1224,6 +1229,30 @@ function rawCommandContext() {
1224
1229
  }
1225
1230
  positionals.push(arg);
1226
1231
  }
1232
+ return positionals;
1233
+ }
1234
+ function rawFlagValue(argv, flagName) {
1235
+ const longFlag = `--${flagName}`;
1236
+ for (let index = 0; index < argv.length; index++) {
1237
+ const arg = argv[index];
1238
+ if (arg === longFlag) {
1239
+ const next = argv[index + 1];
1240
+ return next && !next.startsWith("-") ? next : undefined;
1241
+ }
1242
+ if (arg.startsWith(`${longFlag}=`))
1243
+ return arg.slice(longFlag.length + 1);
1244
+ }
1245
+ return undefined;
1246
+ }
1247
+ function commandIsProjectScoped(resource, action) {
1248
+ if (!resource)
1249
+ return false;
1250
+ const usage = commands[resource]?.[action ?? ""]?.usage ?? "";
1251
+ return inferRequiresProject(resource, action ?? "", usage);
1252
+ }
1253
+ function rawCommandContext() {
1254
+ const argv = process.argv.slice(2);
1255
+ const positionals = rawPositionals(argv);
1227
1256
  const resource = currentCommandContext.resource ?? positionals[0];
1228
1257
  const action = currentCommandContext.action ?? positionals[1];
1229
1258
  const explicitProjectKey = rawFlagValue(argv, "project-key") ?? rawFlagValue(argv, "project");
@@ -1274,6 +1303,67 @@ function requestIdFromBody(body) {
1274
1303
  }
1275
1304
  return undefined;
1276
1305
  }
1306
+ /** DSS diagnostic fields, in the resource layer's read order (detailedMessage → message → errorMessage). */
1307
+ const DSS_DIAGNOSTIC_MESSAGE_FIELDS = ["detailedMessage", "message", "errorMessage",];
1308
+ const MAX_DSS_DIAGNOSTIC_LENGTH = 300;
1309
+ /**
1310
+ * Credential material for redacting a server echo in a receipt: resolved
1311
+ * through the same resolver every command uses (flag → env → saved), so the
1312
+ * report path never grows a second credential-precedence convention.
1313
+ */
1314
+ function cliSecretValues() {
1315
+ try {
1316
+ const { apiKey, } = resolveCredentials(parseArgs(process.argv.slice(2)).flags);
1317
+ return apiKey ? [apiKey,] : [];
1318
+ }
1319
+ catch {
1320
+ return [];
1321
+ }
1322
+ }
1323
+ /**
1324
+ * Bounded, sanitized server diagnostic preserved on an ambiguous mutation
1325
+ * envelope (`dssMessage`/`dssErrorType`, next to the existing `dssCategory`):
1326
+ * the DSS error text and Java error type the response body carried, so the
1327
+ * receipt explains why the server failed while the mutation outcome stays
1328
+ * unknown. Whitespace collapses to one line, credential material is redacted,
1329
+ * long text truncates, and the raw body/stack never pass through.
1330
+ */
1331
+ function dssDiagnosticDetails(err) {
1332
+ try {
1333
+ const parsed = JSON.parse(err.body);
1334
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
1335
+ return {};
1336
+ const record = parsed;
1337
+ let secrets;
1338
+ const diagnostic = (value) => {
1339
+ if (typeof value !== "string" || value.trim() === "")
1340
+ return undefined;
1341
+ secrets ??= cliSecretValues();
1342
+ const sanitized = sanitizeSecrets(value.replace(/\s+/g, " ").trim(), {
1343
+ sensitiveKeys: {},
1344
+ secrets,
1345
+ redactUrlUserinfo: true,
1346
+ });
1347
+ return sanitized.length > MAX_DSS_DIAGNOSTIC_LENGTH
1348
+ ? `${sanitized.slice(0, MAX_DSS_DIAGNOSTIC_LENGTH)}…`
1349
+ : sanitized;
1350
+ };
1351
+ let message;
1352
+ for (const field of DSS_DIAGNOSTIC_MESSAGE_FIELDS) {
1353
+ message = diagnostic(record[field]);
1354
+ if (message !== undefined)
1355
+ break;
1356
+ }
1357
+ const errorType = diagnostic(record["errorType"]);
1358
+ return {
1359
+ ...(message !== undefined ? { dssMessage: message, } : {}),
1360
+ ...(errorType !== undefined ? { dssErrorType: errorType, } : {}),
1361
+ };
1362
+ }
1363
+ catch {
1364
+ return {};
1365
+ }
1366
+ }
1277
1367
  const MISSING_PROJECT_KEY_ERROR_PREFIX = "projectKey is required";
1278
1368
  function isAmbiguousMutationFailure(err, context) {
1279
1369
  if (!(err instanceof DataikuError))
@@ -1384,6 +1474,8 @@ function buildErrorReport(err) {
1384
1474
  requestId: err.requestId ?? requestIdFromBody(err.body),
1385
1475
  details: {
1386
1476
  dssCategory: err.category,
1477
+ ...(err.bodyTruncated ? { bodyTruncated: true, } : {}),
1478
+ ...dssDiagnosticDetails(err),
1387
1479
  statusText: canonicalStatusText(err.status),
1388
1480
  idempotency: "none",
1389
1481
  ...(err.retry ? { retry: err.retry, } : {}),
@@ -1417,6 +1509,7 @@ function buildErrorReport(err) {
1417
1509
  requestId: err.requestId ?? requestIdFromBody(err.body),
1418
1510
  details: {
1419
1511
  dssCategory: err.category,
1512
+ ...(err.bodyTruncated ? { bodyTruncated: true, } : {}),
1420
1513
  statusText: canonicalStatusText(err.status),
1421
1514
  body: safeBody,
1422
1515
  ...(err.retry ? { retry: err.retry, } : {}),
@@ -1451,6 +1544,14 @@ function writeErrorReport(err) {
1451
1544
  flushCliWarnings();
1452
1545
  process.stdout.write(`${JSON.stringify(buildErrorReport(err))}\n`);
1453
1546
  }
1547
+ /** Only ambiguous mutation failures require full contract metadata. */
1548
+ function errorReportNeedsCommandRuntime(err) {
1549
+ if (contract || !(err instanceof DataikuError))
1550
+ return false;
1551
+ const method = err.retry?.method.toUpperCase();
1552
+ return !!method && method !== "GET" && method !== "HEAD"
1553
+ && (err.status === 0 || err.status >= 500);
1554
+ }
1454
1555
  // ---------------------------------------------------------------------------
1455
1556
  // Main
1456
1557
  // ---------------------------------------------------------------------------
@@ -1489,7 +1590,8 @@ async function main() {
1489
1590
  process.exitCode = metaCommand.exitCode;
1490
1591
  return;
1491
1592
  }
1492
- if (!commands[resource])
1593
+ // Resource and action recovery require only this resource's definitions.
1594
+ if (!RESOURCE_NAMES.includes(resource))
1493
1595
  throw unknownResourceError(resource);
1494
1596
  const resourceActions = commands[resource];
1495
1597
  if (positional.length === 1) {
@@ -1500,8 +1602,12 @@ async function main() {
1500
1602
  const actionMeta = resourceActions[action];
1501
1603
  if (!actionMeta)
1502
1604
  throw unknownActionError(resource, action, Object.keys(resourceActions));
1605
+ await loadCommandRuntime();
1503
1606
  validateSupportedCommandFlags(resource, action, flags);
1504
1607
  const args = positional.slice(2);
1608
+ // Command-local validation runs for live, --plan, and --dry-run alike, so a
1609
+ // bad identifier is a usage error before any plan is built or request made.
1610
+ actionMeta.validate?.(args, flags);
1505
1611
  if (executionMode(flags).plan) {
1506
1612
  const plan = contract.buildMutationPlan(resource, action, actionMeta, args, flags);
1507
1613
  writeCommandResult(plan);
@@ -1515,7 +1621,6 @@ async function main() {
1515
1621
  writeCommandResult(result);
1516
1622
  return;
1517
1623
  }
1518
- actionMeta.validate?.(args, flags);
1519
1624
  const { url, apiKey, projectKey, tlsRejectUnauthorized, caCertPath, } = resolveCredentials(flags);
1520
1625
  currentCommandContext.projectKey = projectKey;
1521
1626
  if (!url) {
@@ -1547,7 +1652,8 @@ async function main() {
1547
1652
  writeCommandResult(result);
1548
1653
  }
1549
1654
  main().catch(async (err) => {
1550
- await loadCommandRuntime();
1655
+ if (errorReportNeedsCommandRuntime(err))
1656
+ await loadCommandRuntime();
1551
1657
  writeErrorReport(err);
1552
1658
  process.exitCode = errorExitCode(err);
1553
1659
  });