dataiku-sdk 3.1.0 → 3.3.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 (149) hide show
  1. package/README.md +253 -38
  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/auth.js +1 -1
  6. package/dist/src/cli/coerce.js +11 -2
  7. package/dist/src/cli/commands/analysis.js +4 -3
  8. package/dist/src/cli/commands/api-service.js +5 -3
  9. package/dist/src/cli/commands/app.js +61 -18
  10. package/dist/src/cli/commands/bundle.js +3 -2
  11. package/dist/src/cli/commands/code-env.js +23 -16
  12. package/dist/src/cli/commands/connection.d.ts +9 -0
  13. package/dist/src/cli/commands/connection.js +267 -1
  14. package/dist/src/cli/commands/dashboard.js +7 -6
  15. package/dist/src/cli/commands/data-collection.d.ts +2 -0
  16. package/dist/src/cli/commands/data-collection.js +164 -0
  17. package/dist/src/cli/commands/data-quality.js +9 -8
  18. package/dist/src/cli/commands/dataset.js +170 -40
  19. package/dist/src/cli/commands/doctor.js +4 -2
  20. package/dist/src/cli/commands/flow-zone.js +9 -8
  21. package/dist/src/cli/commands/folder.js +9 -8
  22. package/dist/src/cli/commands/future.js +4 -3
  23. package/dist/src/cli/commands/group.d.ts +2 -0
  24. package/dist/src/cli/commands/group.js +109 -0
  25. package/dist/src/cli/commands/index.js +151 -78
  26. package/dist/src/cli/commands/insight.js +8 -7
  27. package/dist/src/cli/commands/job.js +4 -3
  28. package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
  29. package/dist/src/cli/commands/knowledge-bank.js +80 -0
  30. package/dist/src/cli/commands/llm.d.ts +2 -0
  31. package/dist/src/cli/commands/llm.js +93 -0
  32. package/dist/src/cli/commands/macro.d.ts +2 -0
  33. package/dist/src/cli/commands/macro.js +221 -0
  34. package/dist/src/cli/commands/meaning.js +5 -4
  35. package/dist/src/cli/commands/ml-task.js +21 -8
  36. package/dist/src/cli/commands/model-evaluation-store.js +4 -3
  37. package/dist/src/cli/commands/notebook.js +17 -12
  38. package/dist/src/cli/commands/plugin.d.ts +2 -0
  39. package/dist/src/cli/commands/plugin.js +799 -0
  40. package/dist/src/cli/commands/project-folder.d.ts +2 -0
  41. package/dist/src/cli/commands/project-folder.js +197 -0
  42. package/dist/src/cli/commands/project-git.js +14 -54
  43. package/dist/src/cli/commands/project-library.js +7 -6
  44. package/dist/src/cli/commands/project.js +55 -8
  45. package/dist/src/cli/commands/recipe.js +50 -14
  46. package/dist/src/cli/commands/saved-model.d.ts +5 -0
  47. package/dist/src/cli/commands/saved-model.js +392 -4
  48. package/dist/src/cli/commands/scenario.js +147 -9
  49. package/dist/src/cli/commands/sql.js +3 -2
  50. package/dist/src/cli/commands/statistics.js +7 -6
  51. package/dist/src/cli/commands/user.d.ts +8 -0
  52. package/dist/src/cli/commands/user.js +296 -0
  53. package/dist/src/cli/commands/variable.js +2 -1
  54. package/dist/src/cli/commands/webapp.js +2 -1
  55. package/dist/src/cli/commands/wiki.js +7 -6
  56. package/dist/src/cli/contract.d.ts +2 -3
  57. package/dist/src/cli/contract.js +1167 -163
  58. package/dist/src/cli/flags.d.ts +4 -0
  59. package/dist/src/cli/flags.js +50 -2
  60. package/dist/src/cli/helpers/app-successor.d.ts +19 -4
  61. package/dist/src/cli/helpers/app-successor.js +90 -30
  62. package/dist/src/cli/helpers/cleanup.js +3 -1
  63. package/dist/src/cli/helpers/flow-zone.js +1 -2
  64. package/dist/src/cli/output.d.ts +1 -1
  65. package/dist/src/cli/output.js +23 -10
  66. package/dist/src/cli/usage.d.ts +1 -0
  67. package/dist/src/cli/usage.js +47 -1
  68. package/dist/src/cli.js +225 -95
  69. package/dist/src/client.d.ts +86 -6
  70. package/dist/src/client.js +263 -50
  71. package/dist/src/errors.d.ts +3 -0
  72. package/dist/src/errors.js +4 -0
  73. package/dist/src/index.d.ts +16 -7
  74. package/dist/src/index.js +11 -2
  75. package/dist/src/resources/analyses.js +1 -8
  76. package/dist/src/resources/applications.d.ts +8 -6
  77. package/dist/src/resources/applications.js +21 -21
  78. package/dist/src/resources/base.d.ts +4 -0
  79. package/dist/src/resources/base.js +15 -0
  80. package/dist/src/resources/bundles.d.ts +9 -4
  81. package/dist/src/resources/bundles.js +10 -5
  82. package/dist/src/resources/connections.d.ts +78 -1
  83. package/dist/src/resources/connections.js +132 -1
  84. package/dist/src/resources/data-collections.d.ts +118 -0
  85. package/dist/src/resources/data-collections.js +49 -0
  86. package/dist/src/resources/datasets.d.ts +69 -0
  87. package/dist/src/resources/datasets.js +99 -1
  88. package/dist/src/resources/folders.js +2 -1
  89. package/dist/src/resources/futures.d.ts +4 -1
  90. package/dist/src/resources/futures.js +38 -6
  91. package/dist/src/resources/groups.d.ts +47 -0
  92. package/dist/src/resources/groups.js +56 -0
  93. package/dist/src/resources/jobs.d.ts +8 -13
  94. package/dist/src/resources/jobs.js +41 -16
  95. package/dist/src/resources/knowledge-banks.d.ts +58 -0
  96. package/dist/src/resources/knowledge-banks.js +61 -0
  97. package/dist/src/resources/llms.d.ts +92 -0
  98. package/dist/src/resources/llms.js +62 -0
  99. package/dist/src/resources/macros.d.ts +121 -0
  100. package/dist/src/resources/macros.js +231 -0
  101. package/dist/src/resources/ml-tasks.d.ts +37 -2
  102. package/dist/src/resources/ml-tasks.js +100 -20
  103. package/dist/src/resources/model-evaluation-stores.js +1 -8
  104. package/dist/src/resources/notebooks.d.ts +43 -7
  105. package/dist/src/resources/notebooks.js +53 -13
  106. package/dist/src/resources/plugins.d.ts +265 -0
  107. package/dist/src/resources/plugins.js +458 -0
  108. package/dist/src/resources/project-folders.d.ts +71 -0
  109. package/dist/src/resources/project-folders.js +57 -0
  110. package/dist/src/resources/project-git.d.ts +6 -0
  111. package/dist/src/resources/project-git.js +69 -42
  112. package/dist/src/resources/project-library.d.ts +0 -1
  113. package/dist/src/resources/project-library.js +1 -17
  114. package/dist/src/resources/projects.d.ts +19 -3
  115. package/dist/src/resources/projects.js +30 -18
  116. package/dist/src/resources/recipes.d.ts +15 -1
  117. package/dist/src/resources/recipes.js +73 -6
  118. package/dist/src/resources/saved-models.d.ts +134 -1
  119. package/dist/src/resources/saved-models.js +214 -6
  120. package/dist/src/resources/scenarios.d.ts +105 -0
  121. package/dist/src/resources/scenarios.js +232 -106
  122. package/dist/src/resources/sql.js +5 -1
  123. package/dist/src/resources/users.d.ts +129 -0
  124. package/dist/src/resources/users.js +128 -0
  125. package/dist/src/resources/webapps.d.ts +15 -2
  126. package/dist/src/resources/webapps.js +18 -4
  127. package/dist/src/schemas.d.ts +2 -2
  128. package/dist/src/schemas.js +1 -1
  129. package/dist/src/skill.d.ts +13 -4
  130. package/dist/src/skill.js +75 -24
  131. package/dist/src/utils/deep-merge.d.ts +1 -1
  132. package/dist/src/utils/polling.d.ts +20 -0
  133. package/dist/src/utils/polling.js +27 -0
  134. package/dist/src/utils/response-file.d.ts +2 -0
  135. package/dist/src/utils/response-file.js +34 -0
  136. package/dist/src/utils/secret-sanitize.d.ts +54 -0
  137. package/dist/src/utils/secret-sanitize.js +101 -0
  138. package/package.json +8 -5
  139. package/packages/types/dist/index.d.ts +102 -0
  140. package/packages/types/dist/index.js +76 -0
  141. package/plugin.json +1 -1
  142. package/skills/dataiku-dss/SKILL.md +47 -225
  143. package/skills/dataiku-dss/references/app-releases.md +28 -0
  144. package/skills/dataiku-dss/references/authentication.md +39 -0
  145. package/skills/dataiku-dss/references/coding.md +8 -0
  146. package/skills/dataiku-dss/references/discovery.md +30 -0
  147. package/skills/dataiku-dss/references/flow-maps.md +12 -0
  148. package/skills/dataiku-dss/references/mutations.md +33 -0
  149. package/skills/dataiku-dss/references/troubleshooting.md +29 -0
package/dist/src/cli.js CHANGED
@@ -1,22 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fs from "node:fs/promises";
3
+ import { homedir, } from "node:os";
4
+ import { resolve, } from "node:path";
3
5
  import { num, unknownJsonInput, } from "./cli/coerce.js";
4
6
  import { commands, } from "./cli/commands/index.js";
5
- import { AGENT_CONTRACT_USAGE, AUTH_ACTIONS, BATCH_EXAMPLE_PAYLOAD, BATCH_HINT, BATCH_PLAN_EXIT_CODES, BATCH_USAGE, buildAgentContract, buildCommandRegistry, buildMutationPlan, CLEANUP_USAGE, commandActionSummary, COMMANDS_USAGE, inferRequiresProject, isAllowedCleanupAction, supportsCleanupLedger, } from "./cli/contract.js";
6
- import { runDoctor, runFixtures, } from "./cli/doctor.js";
7
7
  import { dataikuEnvironmentEnabled, loadEnvFile, } from "./cli/env.js";
8
- import { FLAG_ALIASES, isNegativeNumberToken, parseArgs, SHORT_FLAGS, VALUE_FLAGS, } from "./cli/flags.js";
8
+ import { executionMode, FLAG_ALIASES, isNegativeNumberToken, parseArgs, SHORT_FLAGS, VALUE_FLAGS, } from "./cli/flags.js";
9
9
  import { cleanupLedgerEntry, } from "./cli/helpers/cleanup.js";
10
10
  import { commandFailureExitCode, CommandResultFailure, flushCliWarnings, isFailedWaitResult, planResult, projectResultFields, setOutputFieldProjection, writeCommandResult, } from "./cli/output.js";
11
11
  import { currentCommandContext, resolveCredentials, resolveLoginCredentials, } from "./cli/runtime.js";
12
- 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";
13
13
  import { cliVersionResult, } from "./cli/version.js";
14
- import { DataikuClient, } from "./client.js";
15
14
  import { getCredentialsPath, } from "./config.js";
16
15
  import { canonicalStatusText, ClientValidationError, DataikuError, dataikuErrorCode, } from "./errors.js";
17
- import { AGENTS, detectAgents, findWorkspaceRoot, installSkill, planSkillInstalls, } from "./skill.js";
18
16
  import { appendCleanupLedgerEntry, findCleanupLedgerBindingViolation, preflightCleanupLedgerPath, readCleanupLedger, reserveCleanupLedgerDssUrl, } from "./utils/cleanup-ledger.js";
19
17
  import { canonicalDssUrl, } from "./utils/dss-url.js";
18
+ import { sanitizeSecrets, } from "./utils/secret-sanitize.js";
19
+ let contract;
20
+ let commandRuntimeLoad;
21
+ // Command definitions are resource-lazy; full contract metadata loads only
22
+ // after a valid action is selected or explicit discovery is requested.
23
+ function loadCommandRuntime() {
24
+ return commandRuntimeLoad ??= import("./cli/contract.js").then((module) => {
25
+ contract = module;
26
+ });
27
+ }
20
28
  // ---------------------------------------------------------------------------
21
29
  // Arg parsing
22
30
  // ---------------------------------------------------------------------------
@@ -60,7 +68,7 @@ function findUnboundLifecycleCleanup(entries) {
60
68
  async function runCleanup(flags) {
61
69
  const filePath = flags["file"];
62
70
  if (typeof filePath !== "string" || filePath.trim().length === 0) {
63
- throw new UsageError(`--file is required. Usage: ${CLEANUP_USAGE}`);
71
+ throw new UsageError(`--file is required. Usage: ${contract.CLEANUP_USAGE}`);
64
72
  }
65
73
  let entries;
66
74
  try {
@@ -95,6 +103,7 @@ async function runCleanup(flags) {
95
103
  }
96
104
  const requestTimeoutMs = num(flags["request-timeout"], "--request-timeout");
97
105
  const retryMaxAttempts = num(flags["retries"], "--retries");
106
+ const { DataikuClient, } = await import("./client.js");
98
107
  const client = new DataikuClient({
99
108
  url,
100
109
  apiKey,
@@ -158,7 +167,7 @@ async function runCleanup(flags) {
158
167
  action,
159
168
  projectKey: parsed.flags["project-key"],
160
169
  });
161
- if (!resource || !action || !isAllowedCleanupAction(resource, action)
170
+ if (!resource || !action || !contract.isAllowedCleanupAction(resource, action)
162
171
  || !commands[resource]?.[action]) {
163
172
  throw new UsageError(`Invalid cleanup argv: ${entry.cleanup.argv.join(" ")}`);
164
173
  }
@@ -231,11 +240,11 @@ async function runCleanup(flags) {
231
240
  }
232
241
  function parseBatchSteps(payload) {
233
242
  if (!Array.isArray(payload)) {
234
- throw new UsageError("Batch payload must be a JSON array of command-argument arrays.", "validation_failed", BATCH_HINT, { example: BATCH_EXAMPLE_PAYLOAD, });
243
+ throw new UsageError("Batch payload must be a JSON array of command-argument arrays.", "validation_failed", contract.BATCH_HINT, { example: contract.BATCH_EXAMPLE_PAYLOAD, });
235
244
  }
236
245
  return payload.map((step, index) => {
237
246
  if (!Array.isArray(step) || !step.every((token) => typeof token === "string")) {
238
- throw new UsageError(`Batch step ${index} must be an array of string arguments.`, "validation_failed", BATCH_HINT);
247
+ throw new UsageError(`Batch step ${index} must be an array of string arguments.`, "validation_failed", contract.BATCH_HINT);
239
248
  }
240
249
  return step;
241
250
  });
@@ -266,12 +275,14 @@ const META_COMMAND_RESOURCES = {
266
275
  fixtures: true,
267
276
  batch: true,
268
277
  };
269
- let commandRegistryCache;
270
- function cachedCommandRegistry() {
271
- if (commandRegistryCache === undefined) {
272
- commandRegistryCache = buildCommandRegistry();
273
- }
274
- return commandRegistryCache;
278
+ const commandRegistryCache = new Map();
279
+ function cachedCommandRegistry(resource) {
280
+ if (!contract)
281
+ return undefined;
282
+ if (!commandRegistryCache.has(resource)) {
283
+ commandRegistryCache.set(resource, contract.buildCommandRegistry(resource)[resource]);
284
+ }
285
+ return commandRegistryCache.get(resource);
275
286
  }
276
287
  function supportedCommandFlags(entry) {
277
288
  const supported = { ...ALWAYS_ALLOWED_COMMAND_FLAGS, };
@@ -309,7 +320,7 @@ function commandRegistrySelectors(flags) {
309
320
  return selectors;
310
321
  }
311
322
  function validateSupportedCommandFlags(resource, action, flags) {
312
- const entry = cachedCommandRegistry()[resource]?.[action];
323
+ const entry = cachedCommandRegistry(resource)?.[action];
313
324
  if (!entry)
314
325
  return;
315
326
  const supported = supportedCommandFlags(entry);
@@ -318,7 +329,7 @@ function validateSupportedCommandFlags(resource, action, flags) {
318
329
  throw new UsageError(`Unknown flag --${flagName} for ${resource} ${action}`, "unknown_flag", scopedCommandDiscoveryHint(resource, action));
319
330
  }
320
331
  }
321
- if (flags["dry-run"] === true && !entry.dryRun) {
332
+ if (executionMode(flags).dryRun && !entry.dryRun) {
322
333
  throw new UsageError(`--dry-run is not supported for ${resource} ${action}.`, "unknown_flag", entry.flags.some((flag) => flag.name === "plan")
323
334
  ? "Remove --dry-run; use --plan to preview the mutation instead."
324
335
  : `Remove --dry-run; ${resource} ${action} has no dry-run mode.`);
@@ -381,7 +392,7 @@ function validateRequiredCommandInputs(resource, action, args, flags, entry) {
381
392
  }
382
393
  }
383
394
  function validateRegistryCommandInputs(resource, action, args, flags) {
384
- const entry = cachedCommandRegistry()[resource]?.[action];
395
+ const entry = cachedCommandRegistry(resource)?.[action];
385
396
  if (!entry)
386
397
  return;
387
398
  validateSupportedCommandFlags(resource, action, flags);
@@ -404,11 +415,21 @@ const batchDryRunClientHandler = {
404
415
  throw new BatchDryRunValidationComplete();
405
416
  },
406
417
  };
407
- const batchDryRunClient = new Proxy(batchDryRunClientTarget, batchDryRunClientHandler);
418
+ const batchDryRunResource = new Proxy(batchDryRunClientTarget, batchDryRunClientHandler);
419
+ let batchDryRunClient;
408
420
  async function validateDryRunHandlerPreconditions(handler, args, flags) {
409
421
  const readsFromStdin = flags["stdin"] === true || flags["sql"] === "-";
410
422
  if (readsFromStdin)
411
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
+ }
412
433
  try {
413
434
  await handler(batchDryRunClient, args, flags);
414
435
  }
@@ -418,7 +439,7 @@ async function validateDryRunHandlerPreconditions(handler, args, flags) {
418
439
  throw error;
419
440
  }
420
441
  }
421
- function validateMetaCommandInputs(resource, action, flags) {
442
+ async function validateMetaCommandInputs(resource, action, flags) {
422
443
  if (resource === "doctor") {
423
444
  if (action !== undefined && action !== "run") {
424
445
  throw unknownActionError("doctor", action, ["run",]);
@@ -426,11 +447,11 @@ function validateMetaCommandInputs(resource, action, flags) {
426
447
  return "run";
427
448
  }
428
449
  if (resource === "auth") {
429
- const validActions = Object.keys(AUTH_ACTIONS);
450
+ const validActions = Object.keys(contract.AUTH_ACTIONS);
430
451
  if (!action) {
431
452
  throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
432
453
  }
433
- if (!AUTH_ACTIONS[action]) {
454
+ if (!contract.AUTH_ACTIONS[action]) {
434
455
  throw unknownActionError("auth", action, validActions, "auth only supports 'login'. To check credentials/connectivity, run 'dss doctor'.");
435
456
  }
436
457
  return action;
@@ -439,6 +460,7 @@ function validateMetaCommandInputs(resource, action, flags) {
439
460
  if (action !== undefined && action !== "run") {
440
461
  throw unknownActionError("install-skill", action, ["run",]);
441
462
  }
463
+ const { AGENTS, } = await import("./skill.js");
442
464
  const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
443
465
  if (agentFilter && !AGENTS[agentFilter]) {
444
466
  throw new UsageError(`Unknown agent: ${agentFilter}.`, "usage_error", COMMANDS_RUN_HINT, { agent: agentFilter, validAgents: Object.keys(AGENTS), });
@@ -447,14 +469,14 @@ function validateMetaCommandInputs(resource, action, flags) {
447
469
  }
448
470
  if (resource === "agent") {
449
471
  if (!action)
450
- throw missingActionError("agent", ["contract",], AGENT_CONTRACT_USAGE);
472
+ throw missingActionError("agent", ["contract",], contract.AGENT_CONTRACT_USAGE);
451
473
  if (action !== "contract")
452
474
  throw unknownActionError("agent", action, ["contract",]);
453
475
  return action;
454
476
  }
455
477
  if (resource === "commands") {
456
478
  if (!action)
457
- throw missingActionError("commands", ["run",], COMMANDS_USAGE);
479
+ throw missingActionError("commands", ["run",], contract.COMMANDS_USAGE);
458
480
  if (action !== "run")
459
481
  throw unknownActionError("commands", action, ["run",]);
460
482
  validateSupportedCommandFlags("commands", "run", flags);
@@ -471,7 +493,7 @@ function validateMetaCommandInputs(resource, action, flags) {
471
493
  if (action !== undefined && action !== "run") {
472
494
  throw unknownActionError("cleanup", action, ["run",]);
473
495
  }
474
- const entry = cachedCommandRegistry().cleanup?.run;
496
+ const entry = cachedCommandRegistry("cleanup")?.run;
475
497
  if (entry)
476
498
  validateRequiredCommandInputs("cleanup", "run", [], flags, entry);
477
499
  return "run";
@@ -486,7 +508,7 @@ function validateMetaCommandInputs(resource, action, flags) {
486
508
  if (action !== undefined && action !== "run") {
487
509
  throw unknownActionError("batch", action, ["run",]);
488
510
  }
489
- const entry = cachedCommandRegistry().batch?.run;
511
+ const entry = cachedCommandRegistry("batch")?.run;
490
512
  if (entry)
491
513
  validateRequiredCommandInputs("batch", "run", [], flags, entry);
492
514
  return "run";
@@ -499,7 +521,7 @@ async function validateBatchStep(argv) {
499
521
  const action = positional[1];
500
522
  if (!resource)
501
523
  throw noCommandError();
502
- const metaAction = validateMetaCommandInputs(resource, action, flags);
524
+ const metaAction = await validateMetaCommandInputs(resource, action, flags);
503
525
  if (metaAction)
504
526
  return { positional, flags, resource, action: metaAction, };
505
527
  const resourceActions = commands[resource];
@@ -518,7 +540,7 @@ async function validateBatchStep(argv) {
518
540
  function batchStepNeedsClient(argv) {
519
541
  try {
520
542
  const { positional, flags, } = parseArgs(argv);
521
- if (flags["plan"] === true)
543
+ if (executionMode(flags).plan)
522
544
  return false;
523
545
  const resource = positional[0];
524
546
  if (!resource)
@@ -600,7 +622,7 @@ function validateBatchStepMode(argv, index) {
600
622
  catch {
601
623
  return;
602
624
  }
603
- if (flags["plan"] !== true && flags["dry-run"] !== true)
625
+ if (!executionMode(flags).plan && !executionMode(flags).dryRun)
604
626
  return;
605
627
  const resource = positional[0];
606
628
  const action = positional[1];
@@ -614,13 +636,13 @@ function validateBatchStepMode(argv, index) {
614
636
  const meta = resourceActions[action ?? ""];
615
637
  if (!meta)
616
638
  return;
617
- const entry = cachedCommandRegistry()[resource]?.[action ?? ""];
639
+ const entry = cachedCommandRegistry(resource)?.[action ?? ""];
618
640
  if (!entry)
619
641
  return;
620
- if (flags["plan"] === true && !entry.flags.some((flag) => flag.name === "plan")) {
642
+ if (executionMode(flags).plan && !entry.flags.some((flag) => flag.name === "plan")) {
621
643
  throw new UsageError(`Batch step ${index} requests --plan, which is not supported for ${resource} ${action}.`, "unknown_flag", "Remove --plan from the step; only mutating commands support --plan.");
622
644
  }
623
- if (flags["dry-run"] === true && !entry.dryRun) {
645
+ if (executionMode(flags).dryRun && !entry.dryRun) {
624
646
  throw new UsageError(`Batch step ${index} requests --dry-run, which is not supported for ${resource} ${action}.`, "unknown_flag", "Remove --dry-run from the step.");
625
647
  }
626
648
  }
@@ -629,7 +651,8 @@ function validateBatchStepModes(steps) {
629
651
  validateBatchStepMode(steps[index], index);
630
652
  }
631
653
  }
632
- function runInstallSkill(flags) {
654
+ async function runInstallSkill(flags) {
655
+ const { AGENTS, detectAgents, findWorkspaceRoot, installSkill, planSkillInstalls, } = await import("./skill.js");
633
656
  const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
634
657
  const isGlobal = flags["global"] === true;
635
658
  const targetDir = typeof flags["target"] === "string" ? flags["target"] : undefined;
@@ -655,25 +678,29 @@ function runInstallSkill(flags) {
655
678
  throw new UsageError("No coding agents detected.", "usage_error", "Use --agent NAME to choose one of the supported agents.", { validAgents: Object.keys(AGENTS), });
656
679
  }
657
680
  const scope = isGlobal ? "global" : "project";
658
- const cwd = targetDir ?? (isGlobal ? process.cwd() : findWorkspaceRoot(process.cwd()));
681
+ const cwd = isGlobal
682
+ ? homedir()
683
+ : targetDir
684
+ ? resolve(targetDir)
685
+ : findWorkspaceRoot(process.cwd());
659
686
  const installed = planSkillInstalls(targets, { global: isGlobal, cwd, });
660
- if (flags["plan"] === true) {
687
+ if (executionMode(flags).plan) {
661
688
  return planResult("install-skill", "run", {
662
689
  identifiers: { scope, target: cwd, },
663
690
  payload: { installed, },
664
691
  idempotency: "none",
665
692
  asyncKind: "none",
666
693
  exitCodesOnFailure: { usage: 1, error: 2, transient: 3, },
667
- plannedAndDryRun: flags["dry-run"] === true,
694
+ plannedAndDryRun: executionMode(flags).dryRun,
668
695
  });
669
696
  }
670
697
  return {
671
698
  scope,
672
699
  target: cwd,
673
- installed: flags["dry-run"] === true
700
+ installed: executionMode(flags).dryRun
674
701
  ? installed
675
702
  : installSkill(targets, { global: isGlobal, cwd, }),
676
- ...(flags["dry-run"] === true ? { dryRun: true, } : {}),
703
+ ...(executionMode(flags).dryRun ? { dryRun: true, } : {}),
677
704
  };
678
705
  }
679
706
  const META_PLAN_EXIT_CODES = { usage: 1, error: 2, transient: 3, };
@@ -706,7 +733,7 @@ function authLoginPlan(flags) {
706
733
  function cleanupPlan(flags) {
707
734
  const filePath = flags["file"];
708
735
  if (typeof filePath !== "string" || filePath.trim().length === 0) {
709
- throw new UsageError(`--file is required. Usage: ${CLEANUP_USAGE}`);
736
+ throw new UsageError(`--file is required. Usage: ${contract.CLEANUP_USAGE}`);
710
737
  }
711
738
  return planResult("cleanup", "run", {
712
739
  identifiers: { file: filePath, },
@@ -717,13 +744,13 @@ function cleanupPlan(flags) {
717
744
  idempotency: "none",
718
745
  asyncKind: "none",
719
746
  exitCodesOnFailure: META_PLAN_EXIT_CODES,
720
- plannedAndDryRun: flags["dry-run"] === true,
747
+ plannedAndDryRun: executionMode(flags).dryRun,
721
748
  });
722
749
  }
723
750
  function batchPlan(flags) {
724
751
  const payload = unknownJsonInput(flags);
725
752
  if (payload === undefined) {
726
- throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${BATCH_USAGE}`, "missing_required_flag", BATCH_HINT);
753
+ throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${contract.BATCH_USAGE}`, "missing_required_flag", contract.BATCH_HINT);
727
754
  }
728
755
  const steps = parseBatchSteps(payload);
729
756
  return planResult("batch", "run", {
@@ -734,79 +761,89 @@ function batchPlan(flags) {
734
761
  payload: {
735
762
  steps: steps.map((argv) => redactArgv(argv)),
736
763
  continueOnError: flags["continue-on-error"] === true,
737
- dryRun: flags["dry-run"] === true,
764
+ dryRun: executionMode(flags).dryRun,
738
765
  },
739
766
  idempotency: "none",
740
767
  asyncKind: "none",
741
- exitCodesOnFailure: BATCH_PLAN_EXIT_CODES,
742
- plannedAndDryRun: flags["dry-run"] === true,
768
+ exitCodesOnFailure: contract.BATCH_PLAN_EXIT_CODES,
769
+ plannedAndDryRun: executionMode(flags).dryRun,
743
770
  });
744
771
  }
745
772
  async function runMetaCommand(resource, action, flags) {
746
773
  if (resource === "doctor") {
774
+ await loadCommandRuntime();
747
775
  if (action !== undefined && action !== "run") {
748
776
  throw unknownActionError("doctor", action, ["run",]);
749
777
  }
750
778
  currentCommandContext.action = action ?? "run";
779
+ const { runDoctor, } = await import("./cli/doctor.js");
751
780
  const { result, exitCode, } = await runDoctor(flags);
752
781
  return { action: "run", result, exitCode, };
753
782
  }
754
783
  if (resource === "auth") {
755
- const validActions = Object.keys(AUTH_ACTIONS);
784
+ await loadCommandRuntime();
785
+ const validActions = Object.keys(contract.AUTH_ACTIONS);
756
786
  if (!action) {
757
787
  throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
758
788
  }
759
789
  currentCommandContext.action = action;
760
- const authMeta = AUTH_ACTIONS[action];
790
+ const authMeta = contract.AUTH_ACTIONS[action];
761
791
  if (!authMeta) {
762
792
  throw unknownActionError("auth", action, validActions, "auth only supports 'login'. To check credentials/connectivity, run 'dss doctor'.");
763
793
  }
764
- if (flags["plan"] === true) {
794
+ if (executionMode(flags).plan) {
765
795
  return { action, result: authLoginPlan(flags), exitCode: 0, };
766
796
  }
767
797
  return { action, result: await authMeta.handler(flags), exitCode: 0, };
768
798
  }
769
799
  if (resource === "install-skill") {
800
+ await loadCommandRuntime();
770
801
  if (action !== undefined && action !== "run") {
771
802
  throw unknownActionError("install-skill", action, ["run",]);
772
803
  }
773
804
  currentCommandContext.action = action ?? "run";
774
- return { action: "run", result: runInstallSkill(flags), exitCode: 0, };
805
+ return { action: "run", result: await runInstallSkill(flags), exitCode: 0, };
775
806
  }
776
807
  if (resource === "agent") {
808
+ await loadCommandRuntime();
777
809
  if (!action)
778
- throw missingActionError("agent", ["contract",], AGENT_CONTRACT_USAGE);
810
+ throw missingActionError("agent", ["contract",], contract.AGENT_CONTRACT_USAGE);
779
811
  currentCommandContext.action = action;
780
812
  if (action !== "contract")
781
813
  throw unknownActionError("agent", action, ["contract",]);
782
- return { action, result: buildAgentContract(), exitCode: 0, };
814
+ return { action, result: contract.buildAgentContract(), exitCode: 0, };
783
815
  }
784
816
  if (resource === "commands") {
817
+ await loadCommandRuntime();
785
818
  if (!action)
786
- throw missingActionError("commands", ["run",], COMMANDS_USAGE);
819
+ throw missingActionError("commands", ["run",], contract.COMMANDS_USAGE);
787
820
  currentCommandContext.action = action;
788
821
  if (action !== "run")
789
822
  throw unknownActionError("commands", action, ["run",]);
790
823
  validateSupportedCommandFlags("commands", "run", flags);
791
824
  const selectors = commandRegistrySelectors(flags);
792
- const registry = 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() : {};
793
830
  for (const selector of selectors) {
794
831
  const selectorParts = selector.split(".");
795
832
  if (selectorParts.some((part) => part.length === 0)) {
796
- throw new UsageError(`Invalid --fields selector: ${selector}. Expected RESOURCE or RESOURCE.ACTION[.FIELD...].`, "usage_error", COMMANDS_USAGE, { selector, });
833
+ throw new UsageError(`Invalid --fields selector: ${selector}. Expected RESOURCE or RESOURCE.ACTION[.FIELD...].`, "usage_error", contract.COMMANDS_USAGE, { selector, });
797
834
  }
798
835
  const [selectedResource, selectedAction,] = selectorParts;
799
- const resourceActions = registry[selectedResource];
836
+ const resourceActions = cachedCommandRegistry(selectedResource);
800
837
  if (!resourceActions)
801
838
  throw unknownResourceError(selectedResource);
839
+ registry[selectedResource] = resourceActions;
802
840
  if (selectedAction && !resourceActions[selectedAction]) {
803
841
  throw unknownActionError(selectedResource, selectedAction, Object.keys(resourceActions));
804
842
  }
805
843
  }
806
- const outputPath = flags["output"];
807
844
  const selectedRegistry = selectors.length === 0
808
845
  ? registry
809
- : projectResultFields(registry, selectors);
846
+ : projectResultFields(registry, selectors, () => Object.keys(contract.commandActionSummary()).sort());
810
847
  if (typeof outputPath === "string") {
811
848
  const exported = selectedRegistry;
812
849
  await fs.writeFile(outputPath, `${JSON.stringify(exported)}\n`, "utf-8");
@@ -814,7 +851,7 @@ async function runMetaCommand(resource, action, flags) {
814
851
  }
815
852
  return {
816
853
  action,
817
- result: selectors.length === 0 ? commandActionSummary(registry) : selectedRegistry,
854
+ result: selectedRegistry,
818
855
  exitCode: 0,
819
856
  };
820
857
  }
@@ -826,29 +863,33 @@ async function runMetaCommand(resource, action, flags) {
826
863
  return { action: "run", result: cliVersionResult(), exitCode: 0, };
827
864
  }
828
865
  if (resource === "cleanup") {
866
+ await loadCommandRuntime();
829
867
  if (action !== undefined && action !== "run") {
830
868
  throw unknownActionError("cleanup", action, ["run",]);
831
869
  }
832
870
  currentCommandContext.action = action ?? "run";
833
- if (flags["plan"] === true) {
871
+ if (executionMode(flags).plan) {
834
872
  return { action: "run", result: cleanupPlan(flags), exitCode: 0, };
835
873
  }
836
874
  const { result, exitCode, } = await runCleanup(flags);
837
875
  return { action: "run", result, exitCode, };
838
876
  }
839
877
  if (resource === "fixtures") {
878
+ await loadCommandRuntime();
840
879
  if (action !== undefined && action !== "run") {
841
880
  throw unknownActionError("fixtures", action, ["run",]);
842
881
  }
843
882
  currentCommandContext.action = action ?? "run";
883
+ const { runFixtures, } = await import("./cli/doctor.js");
844
884
  return { action: "run", result: await runFixtures(flags), exitCode: 0, };
845
885
  }
846
886
  if (resource === "batch") {
887
+ await loadCommandRuntime();
847
888
  if (action !== undefined && action !== "run") {
848
889
  throw unknownActionError("batch", action, ["run",]);
849
890
  }
850
891
  currentCommandContext.action = action ?? "run";
851
- if (flags["plan"] === true) {
892
+ if (executionMode(flags).plan) {
852
893
  return { action: "run", result: batchPlan(flags), exitCode: 0, };
853
894
  }
854
895
  const { result, exitCode, } = await runBatch(flags);
@@ -862,9 +903,9 @@ async function runMetaCommand(resource, action, flags) {
862
903
  * share this guard.
863
904
  */
864
905
  function assertCleanupLedgerSupported(resource, action, flags) {
865
- if (typeof flags["record-cleanup"] !== "string" || flags["dry-run"] === true)
906
+ if (typeof flags["record-cleanup"] !== "string" || executionMode(flags).dryRun)
866
907
  return;
867
- if (!supportsCleanupLedger(resource, action)) {
908
+ if (!contract.supportsCleanupLedger(resource, action)) {
868
909
  throw new UsageError(`--record-cleanup is not supported for ${resource} ${action}.`);
869
910
  }
870
911
  if (resource === "workspace" && action === "create" && flags["stdin"] === true) {
@@ -878,7 +919,7 @@ function assertCleanupLedgerSupported(resource, action, flags) {
878
919
  * steps both call it, and dry-run/preview runs never touch the ledger.
879
920
  */
880
921
  async function preflightCleanupLedgerForFlags(client, flags) {
881
- if (flags["dry-run"] === true)
922
+ if (executionMode(flags).dryRun)
882
923
  return;
883
924
  const ledgerPath = flags["record-cleanup"];
884
925
  if (typeof ledgerPath !== "string")
@@ -929,7 +970,7 @@ async function preflightCleanupLedgerForFlags(client, flags) {
929
970
  */
930
971
  async function recordCleanupLedgerEntry(client, resource, action, args, flags, result, projectKey) {
931
972
  const ledgerPath = flags["record-cleanup"];
932
- if (typeof ledgerPath !== "string" || flags["dry-run"] === true)
973
+ if (typeof ledgerPath !== "string" || executionMode(flags).dryRun)
933
974
  return;
934
975
  const entry = cleanupLedgerEntry(resource, action, args, flags, result, projectKey);
935
976
  if (entry) {
@@ -946,10 +987,10 @@ async function recordCleanupLedgerEntry(client, resource, action, args, flags, r
946
987
  async function runBatch(flags) {
947
988
  const payload = unknownJsonInput(flags);
948
989
  if (payload === undefined) {
949
- throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${BATCH_USAGE}`, "missing_required_flag", BATCH_HINT);
990
+ throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${contract.BATCH_USAGE}`, "missing_required_flag", contract.BATCH_HINT);
950
991
  }
951
992
  const steps = parseBatchSteps(payload);
952
- if (flags["dry-run"] === true) {
993
+ if (executionMode(flags).dryRun) {
953
994
  const batchProjectKey = typeof flags["project-key"] === "string"
954
995
  ? flags["project-key"]
955
996
  : dataikuEnvironmentEnabled()
@@ -1016,6 +1057,7 @@ async function runBatch(flags) {
1016
1057
  env: ["DATAIKU_API_KEY",],
1017
1058
  });
1018
1059
  }
1060
+ const { DataikuClient, } = await import("./client.js");
1019
1061
  client = new DataikuClient({
1020
1062
  url,
1021
1063
  apiKey,
@@ -1078,8 +1120,8 @@ async function runBatch(flags) {
1078
1120
  if (!meta)
1079
1121
  throw unknownActionError(resource, action, Object.keys(resourceActions));
1080
1122
  validateSupportedCommandFlags(resource, action, stepFlags);
1081
- if (stepFlags["plan"] === true) {
1082
- result = buildMutationPlan(resource, action, meta, positional.slice(2), stepFlags);
1123
+ if (executionMode(stepFlags).plan) {
1124
+ result = contract.buildMutationPlan(resource, action, meta, positional.slice(2), stepFlags);
1083
1125
  }
1084
1126
  else {
1085
1127
  if (!client) {
@@ -1159,27 +1201,12 @@ function summarizeCleanupFailures(failures) {
1159
1201
  : [];
1160
1202
  }));
1161
1203
  }
1162
- function rawFlagValue(argv, flagName) {
1163
- const longFlag = `--${flagName}`;
1164
- for (let index = 0; index < argv.length; index++) {
1165
- const arg = argv[index];
1166
- if (arg === longFlag) {
1167
- const next = argv[index + 1];
1168
- return next && !next.startsWith("-") ? next : undefined;
1169
- }
1170
- if (arg.startsWith(`${longFlag}=`))
1171
- return arg.slice(longFlag.length + 1);
1172
- }
1173
- return undefined;
1174
- }
1175
- function commandIsProjectScoped(resource, action) {
1176
- if (!resource)
1177
- return false;
1178
- const usage = commands[resource]?.[action ?? ""]?.usage ?? "";
1179
- return inferRequiresProject(resource, action ?? "", usage);
1180
- }
1181
- function rawCommandContext() {
1182
- 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) {
1183
1210
  const positionals = [];
1184
1211
  for (let index = 0; index < argv.length; index++) {
1185
1212
  const arg = argv[index];
@@ -1202,6 +1229,30 @@ function rawCommandContext() {
1202
1229
  }
1203
1230
  positionals.push(arg);
1204
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);
1205
1256
  const resource = currentCommandContext.resource ?? positionals[0];
1206
1257
  const action = currentCommandContext.action ?? positionals[1];
1207
1258
  const explicitProjectKey = rawFlagValue(argv, "project-key") ?? rawFlagValue(argv, "project");
@@ -1252,6 +1303,67 @@ function requestIdFromBody(body) {
1252
1303
  }
1253
1304
  return undefined;
1254
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
+ }
1255
1367
  const MISSING_PROJECT_KEY_ERROR_PREFIX = "projectKey is required";
1256
1368
  function isAmbiguousMutationFailure(err, context) {
1257
1369
  if (!(err instanceof DataikuError))
@@ -1262,7 +1374,7 @@ function isAmbiguousMutationFailure(err, context) {
1262
1374
  if (err.status !== 0 && err.status < 500)
1263
1375
  return false;
1264
1376
  const entry = context.resource && context.action
1265
- ? cachedCommandRegistry()[context.resource]?.[context.action]
1377
+ ? cachedCommandRegistry(context.resource)?.[context.action]
1266
1378
  : undefined;
1267
1379
  return entry?.sideEffect === "write" && entry.idempotency === "none";
1268
1380
  }
@@ -1362,6 +1474,8 @@ function buildErrorReport(err) {
1362
1474
  requestId: err.requestId ?? requestIdFromBody(err.body),
1363
1475
  details: {
1364
1476
  dssCategory: err.category,
1477
+ ...(err.bodyTruncated ? { bodyTruncated: true, } : {}),
1478
+ ...dssDiagnosticDetails(err),
1365
1479
  statusText: canonicalStatusText(err.status),
1366
1480
  idempotency: "none",
1367
1481
  ...(err.retry ? { retry: err.retry, } : {}),
@@ -1395,6 +1509,7 @@ function buildErrorReport(err) {
1395
1509
  requestId: err.requestId ?? requestIdFromBody(err.body),
1396
1510
  details: {
1397
1511
  dssCategory: err.category,
1512
+ ...(err.bodyTruncated ? { bodyTruncated: true, } : {}),
1398
1513
  statusText: canonicalStatusText(err.status),
1399
1514
  body: safeBody,
1400
1515
  ...(err.retry ? { retry: err.retry, } : {}),
@@ -1429,6 +1544,14 @@ function writeErrorReport(err) {
1429
1544
  flushCliWarnings();
1430
1545
  process.stdout.write(`${JSON.stringify(buildErrorReport(err))}\n`);
1431
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
+ }
1432
1555
  // ---------------------------------------------------------------------------
1433
1556
  // Main
1434
1557
  // ---------------------------------------------------------------------------
@@ -1467,7 +1590,8 @@ async function main() {
1467
1590
  process.exitCode = metaCommand.exitCode;
1468
1591
  return;
1469
1592
  }
1470
- if (!commands[resource])
1593
+ // Resource and action recovery require only this resource's definitions.
1594
+ if (!RESOURCE_NAMES.includes(resource))
1471
1595
  throw unknownResourceError(resource);
1472
1596
  const resourceActions = commands[resource];
1473
1597
  if (positional.length === 1) {
@@ -1478,10 +1602,14 @@ async function main() {
1478
1602
  const actionMeta = resourceActions[action];
1479
1603
  if (!actionMeta)
1480
1604
  throw unknownActionError(resource, action, Object.keys(resourceActions));
1605
+ await loadCommandRuntime();
1481
1606
  validateSupportedCommandFlags(resource, action, flags);
1482
1607
  const args = positional.slice(2);
1483
- if (flags["plan"] === true) {
1484
- const plan = buildMutationPlan(resource, action, actionMeta, args, flags);
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);
1611
+ if (executionMode(flags).plan) {
1612
+ const plan = contract.buildMutationPlan(resource, action, actionMeta, args, flags);
1485
1613
  writeCommandResult(plan);
1486
1614
  return;
1487
1615
  }
@@ -1493,7 +1621,6 @@ async function main() {
1493
1621
  writeCommandResult(result);
1494
1622
  return;
1495
1623
  }
1496
- actionMeta.validate?.(args, flags);
1497
1624
  const { url, apiKey, projectKey, tlsRejectUnauthorized, caCertPath, } = resolveCredentials(flags);
1498
1625
  currentCommandContext.projectKey = projectKey;
1499
1626
  if (!url) {
@@ -1504,6 +1631,7 @@ async function main() {
1504
1631
  }
1505
1632
  const requestTimeoutMs = num(flags["request-timeout"], "--request-timeout");
1506
1633
  const retryMaxAttempts = num(flags["retries"], "--retries");
1634
+ const { DataikuClient, } = await import("./client.js");
1507
1635
  const client = new DataikuClient({
1508
1636
  url,
1509
1637
  apiKey,
@@ -1523,7 +1651,9 @@ async function main() {
1523
1651
  throw new CommandResultFailure(result, failureExitCode);
1524
1652
  writeCommandResult(result);
1525
1653
  }
1526
- main().catch((err) => {
1654
+ main().catch(async (err) => {
1655
+ if (errorReportNeedsCommandRuntime(err))
1656
+ await loadCommandRuntime();
1527
1657
  writeErrorReport(err);
1528
1658
  process.exitCode = errorExitCode(err);
1529
1659
  });