dataiku-sdk 3.0.0 → 3.2.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 (109) hide show
  1. package/README.md +246 -19
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +39 -4
  4. package/dist/packages/types/src/index.js +31 -2
  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 +66 -18
  9. package/dist/src/cli/commands/app.js +4 -3
  10. package/dist/src/cli/commands/bundle.js +51 -16
  11. package/dist/src/cli/commands/code-env.js +219 -57
  12. package/dist/src/cli/commands/code.d.ts +19 -0
  13. package/dist/src/cli/commands/code.js +87 -15
  14. package/dist/src/cli/commands/dashboard.js +7 -6
  15. package/dist/src/cli/commands/data-quality.js +9 -8
  16. package/dist/src/cli/commands/dataset.js +17 -11
  17. package/dist/src/cli/commands/doctor.js +4 -2
  18. package/dist/src/cli/commands/flow-zone.js +42 -13
  19. package/dist/src/cli/commands/folder.js +9 -8
  20. package/dist/src/cli/commands/future.js +2 -1
  21. package/dist/src/cli/commands/insight.js +8 -7
  22. package/dist/src/cli/commands/job.js +4 -3
  23. package/dist/src/cli/commands/meaning.js +5 -4
  24. package/dist/src/cli/commands/ml-task.js +3 -2
  25. package/dist/src/cli/commands/model-evaluation-store.js +4 -3
  26. package/dist/src/cli/commands/notebook.js +82 -50
  27. package/dist/src/cli/commands/project-git.js +8 -5
  28. package/dist/src/cli/commands/project-library.d.ts +21 -0
  29. package/dist/src/cli/commands/project-library.js +128 -56
  30. package/dist/src/cli/commands/project.js +19 -4
  31. package/dist/src/cli/commands/recipe.js +51 -25
  32. package/dist/src/cli/commands/saved-model.js +4 -3
  33. package/dist/src/cli/commands/scenario.js +9 -8
  34. package/dist/src/cli/commands/sql.d.ts +10 -0
  35. package/dist/src/cli/commands/sql.js +42 -19
  36. package/dist/src/cli/commands/statistics.js +7 -6
  37. package/dist/src/cli/commands/variable.js +2 -1
  38. package/dist/src/cli/commands/webapp.js +68 -13
  39. package/dist/src/cli/commands/wiki.js +7 -6
  40. package/dist/src/cli/contract.d.ts +16 -1
  41. package/dist/src/cli/contract.js +967 -186
  42. package/dist/src/cli/env.d.ts +9 -0
  43. package/dist/src/cli/env.js +20 -2
  44. package/dist/src/cli/flags.d.ts +4 -0
  45. package/dist/src/cli/flags.js +29 -1
  46. package/dist/src/cli/helpers/cleanup.js +104 -3
  47. package/dist/src/cli/helpers/flow-zone.d.ts +3 -0
  48. package/dist/src/cli/helpers/flow-zone.js +33 -3
  49. package/dist/src/cli/helpers/recipe.d.ts +20 -0
  50. package/dist/src/cli/helpers/recipe.js +158 -3
  51. package/dist/src/cli/output.d.ts +2 -0
  52. package/dist/src/cli/output.js +33 -6
  53. package/dist/src/cli/runtime.d.ts +35 -1
  54. package/dist/src/cli/runtime.js +125 -18
  55. package/dist/src/cli/types.d.ts +7 -0
  56. package/dist/src/cli.js +226 -95
  57. package/dist/src/client.d.ts +63 -1
  58. package/dist/src/client.js +305 -53
  59. package/dist/src/errors.d.ts +8 -2
  60. package/dist/src/errors.js +42 -0
  61. package/dist/src/index.d.ts +4 -3
  62. package/dist/src/index.js +2 -1
  63. package/dist/src/resources/api-services.d.ts +48 -6
  64. package/dist/src/resources/api-services.js +54 -8
  65. package/dist/src/resources/bundles.d.ts +51 -6
  66. package/dist/src/resources/bundles.js +39 -9
  67. package/dist/src/resources/code-envs.d.ts +25 -3
  68. package/dist/src/resources/code-envs.js +104 -2
  69. package/dist/src/resources/datasets.d.ts +20 -0
  70. package/dist/src/resources/datasets.js +232 -52
  71. package/dist/src/resources/folders.js +2 -1
  72. package/dist/src/resources/jobs.d.ts +35 -10
  73. package/dist/src/resources/jobs.js +92 -17
  74. package/dist/src/resources/notebooks.d.ts +77 -7
  75. package/dist/src/resources/notebooks.js +143 -19
  76. package/dist/src/resources/project-git.d.ts +12 -0
  77. package/dist/src/resources/project-git.js +39 -8
  78. package/dist/src/resources/project-library.d.ts +122 -7
  79. package/dist/src/resources/project-library.js +494 -25
  80. package/dist/src/resources/projects.d.ts +5 -4
  81. package/dist/src/resources/projects.js +21 -19
  82. package/dist/src/resources/scenarios.d.ts +30 -0
  83. package/dist/src/resources/scenarios.js +73 -27
  84. package/dist/src/resources/sql.d.ts +6 -0
  85. package/dist/src/resources/sql.js +23 -3
  86. package/dist/src/resources/webapps.d.ts +35 -4
  87. package/dist/src/resources/webapps.js +54 -5
  88. package/dist/src/schemas.d.ts +2 -2
  89. package/dist/src/schemas.js +1 -1
  90. package/dist/src/skill.d.ts +18 -0
  91. package/dist/src/skill.js +93 -9
  92. package/dist/src/utils/flow-analysis.d.ts +53 -0
  93. package/dist/src/utils/flow-analysis.js +382 -0
  94. package/dist/src/utils/project-archive.d.ts +41 -2
  95. package/dist/src/utils/project-archive.js +159 -16
  96. package/dist/src/utils/response-file.d.ts +2 -0
  97. package/dist/src/utils/response-file.js +34 -0
  98. package/package.json +9 -7
  99. package/packages/types/dist/index.d.ts +39 -4
  100. package/packages/types/dist/index.js +31 -2
  101. package/plugin.json +1 -1
  102. package/skills/dataiku-dss/SKILL.md +44 -197
  103. package/skills/dataiku-dss/references/app-releases.md +27 -0
  104. package/skills/dataiku-dss/references/authentication.md +39 -0
  105. package/skills/dataiku-dss/references/coding.md +8 -0
  106. package/skills/dataiku-dss/references/discovery.md +30 -0
  107. package/skills/dataiku-dss/references/flow-maps.md +12 -0
  108. package/skills/dataiku-dss/references/mutations.md +33 -0
  109. package/skills/dataiku-dss/references/troubleshooting.md +29 -0
package/dist/src/cli.js CHANGED
@@ -1,22 +1,32 @@
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
- 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
6
  import { dataikuEnvironmentEnabled, loadEnvFile, } from "./cli/env.js";
8
- import { FLAG_ALIASES, isNegativeNumberToken, parseArgs, SHORT_FLAGS, VALUE_FLAGS, } from "./cli/flags.js";
7
+ import { executionMode, FLAG_ALIASES, isNegativeNumberToken, parseArgs, SHORT_FLAGS, VALUE_FLAGS, } from "./cli/flags.js";
9
8
  import { cleanupLedgerEntry, } from "./cli/helpers/cleanup.js";
10
- import { commandFailureExitCode, commandFailureMessage, CommandResultFailure, flushCliWarnings, isFailedWaitResult, planResult, projectResultFields, setOutputFieldProjection, writeCommandResult, } from "./cli/output.js";
11
- import { currentCommandContext, resolveCredentials, resolveTlsSettings, } from "./cli/runtime.js";
9
+ import { commandFailureExitCode, CommandResultFailure, flushCliWarnings, isFailedWaitResult, planResult, projectResultFields, setOutputFieldProjection, writeCommandResult, } from "./cli/output.js";
10
+ import { currentCommandContext, resolveCredentials, resolveLoginCredentials, } from "./cli/runtime.js";
12
11
  import { COMMANDS_RUN_HINT, missingActionError, noCommandError, requireArgs, unknownActionError, unknownResourceError, UsageError, } from "./cli/usage.js";
13
12
  import { cliVersionResult, } from "./cli/version.js";
14
- import { DataikuClient, } from "./client.js";
15
13
  import { getCredentialsPath, } from "./config.js";
16
14
  import { canonicalStatusText, ClientValidationError, DataikuError, dataikuErrorCode, } from "./errors.js";
17
- import { AGENTS, detectAgents, findWorkspaceRoot, installSkill, planSkillInstalls, } from "./skill.js";
18
15
  import { appendCleanupLedgerEntry, findCleanupLedgerBindingViolation, preflightCleanupLedgerPath, readCleanupLedger, reserveCleanupLedgerDssUrl, } from "./utils/cleanup-ledger.js";
19
16
  import { canonicalDssUrl, } from "./utils/dss-url.js";
17
+ let commands;
18
+ let contract;
19
+ let commandRuntimeLoad;
20
+ // Dispatch and error reporting initialize these bindings before synchronous validation.
21
+ 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;
28
+ });
29
+ }
20
30
  // ---------------------------------------------------------------------------
21
31
  // Arg parsing
22
32
  // ---------------------------------------------------------------------------
@@ -60,7 +70,7 @@ function findUnboundLifecycleCleanup(entries) {
60
70
  async function runCleanup(flags) {
61
71
  const filePath = flags["file"];
62
72
  if (typeof filePath !== "string" || filePath.trim().length === 0) {
63
- throw new UsageError(`--file is required. Usage: ${CLEANUP_USAGE}`);
73
+ throw new UsageError(`--file is required. Usage: ${contract.CLEANUP_USAGE}`);
64
74
  }
65
75
  let entries;
66
76
  try {
@@ -95,6 +105,7 @@ async function runCleanup(flags) {
95
105
  }
96
106
  const requestTimeoutMs = num(flags["request-timeout"], "--request-timeout");
97
107
  const retryMaxAttempts = num(flags["retries"], "--retries");
108
+ const { DataikuClient, } = await import("./client.js");
98
109
  const client = new DataikuClient({
99
110
  url,
100
111
  apiKey,
@@ -153,7 +164,12 @@ async function runCleanup(flags) {
153
164
  try {
154
165
  const parsed = parseArgs(entry.cleanup.argv);
155
166
  const [resource, action, ...args] = parsed.positional;
156
- if (!resource || !action || !isAllowedCleanupAction(resource, action)
167
+ Object.assign(currentCommandContext, {
168
+ resource,
169
+ action,
170
+ projectKey: parsed.flags["project-key"],
171
+ });
172
+ if (!resource || !action || !contract.isAllowedCleanupAction(resource, action)
157
173
  || !commands[resource]?.[action]) {
158
174
  throw new UsageError(`Invalid cleanup argv: ${entry.cleanup.argv.join(" ")}`);
159
175
  }
@@ -173,13 +189,18 @@ async function runCleanup(flags) {
173
189
  const failure = {
174
190
  index,
175
191
  cleanup: entry.cleanup,
176
- error: commandFailureMessage(result),
177
- result,
192
+ error: buildErrorReport(new CommandResultFailure(result, 4)),
178
193
  };
179
194
  failures.push(failure);
180
195
  if (flags["continue-on-error"] !== true) {
181
196
  return {
182
- result: { applied: true, steps, results: applied, failures, },
197
+ result: {
198
+ applied: true,
199
+ steps,
200
+ results: applied,
201
+ failures,
202
+ ...summarizeCleanupFailures(failures),
203
+ },
183
204
  exitCode: 2,
184
205
  };
185
206
  }
@@ -191,29 +212,41 @@ async function runCleanup(flags) {
191
212
  const failure = {
192
213
  index,
193
214
  cleanup: entry.cleanup,
194
- error: error instanceof Error ? error.message : String(error),
215
+ error: buildErrorReport(error),
195
216
  };
196
217
  failures.push(failure);
197
218
  if (flags["continue-on-error"] !== true) {
198
219
  return {
199
- result: { applied: true, steps, results: applied, failures, },
220
+ result: {
221
+ applied: true,
222
+ steps,
223
+ results: applied,
224
+ failures,
225
+ ...summarizeCleanupFailures(failures),
226
+ },
200
227
  exitCode: 2,
201
228
  };
202
229
  }
203
230
  }
204
231
  }
205
232
  return {
206
- result: { applied: true, steps, results: applied, failures, },
233
+ result: {
234
+ applied: true,
235
+ steps,
236
+ results: applied,
237
+ failures,
238
+ ...summarizeCleanupFailures(failures),
239
+ },
207
240
  exitCode: failures.length > 0 ? 2 : 0,
208
241
  };
209
242
  }
210
243
  function parseBatchSteps(payload) {
211
244
  if (!Array.isArray(payload)) {
212
- throw new UsageError("Batch payload must be a JSON array of command-argument arrays.", "validation_failed", BATCH_HINT, { example: BATCH_EXAMPLE_PAYLOAD, });
245
+ throw new UsageError("Batch payload must be a JSON array of command-argument arrays.", "validation_failed", contract.BATCH_HINT, { example: contract.BATCH_EXAMPLE_PAYLOAD, });
213
246
  }
214
247
  return payload.map((step, index) => {
215
248
  if (!Array.isArray(step) || !step.every((token) => typeof token === "string")) {
216
- throw new UsageError(`Batch step ${index} must be an array of string arguments.`, "validation_failed", BATCH_HINT);
249
+ throw new UsageError(`Batch step ${index} must be an array of string arguments.`, "validation_failed", contract.BATCH_HINT);
217
250
  }
218
251
  return step;
219
252
  });
@@ -244,12 +277,12 @@ const META_COMMAND_RESOURCES = {
244
277
  fixtures: true,
245
278
  batch: true,
246
279
  };
247
- let commandRegistryCache;
248
- function cachedCommandRegistry() {
249
- if (commandRegistryCache === undefined) {
250
- commandRegistryCache = buildCommandRegistry();
280
+ const commandRegistryCache = new Map();
281
+ function cachedCommandRegistry(resource) {
282
+ if (!commandRegistryCache.has(resource)) {
283
+ commandRegistryCache.set(resource, contract.buildCommandRegistry(resource)[resource]);
251
284
  }
252
- return commandRegistryCache;
285
+ return commandRegistryCache.get(resource);
253
286
  }
254
287
  function supportedCommandFlags(entry) {
255
288
  const supported = { ...ALWAYS_ALLOWED_COMMAND_FLAGS, };
@@ -287,7 +320,7 @@ function commandRegistrySelectors(flags) {
287
320
  return selectors;
288
321
  }
289
322
  function validateSupportedCommandFlags(resource, action, flags) {
290
- const entry = cachedCommandRegistry()[resource]?.[action];
323
+ const entry = cachedCommandRegistry(resource)?.[action];
291
324
  if (!entry)
292
325
  return;
293
326
  const supported = supportedCommandFlags(entry);
@@ -296,7 +329,7 @@ function validateSupportedCommandFlags(resource, action, flags) {
296
329
  throw new UsageError(`Unknown flag --${flagName} for ${resource} ${action}`, "unknown_flag", scopedCommandDiscoveryHint(resource, action));
297
330
  }
298
331
  }
299
- if (flags["dry-run"] === true && !entry.dryRun) {
332
+ if (executionMode(flags).dryRun && !entry.dryRun) {
300
333
  throw new UsageError(`--dry-run is not supported for ${resource} ${action}.`, "unknown_flag", entry.flags.some((flag) => flag.name === "plan")
301
334
  ? "Remove --dry-run; use --plan to preview the mutation instead."
302
335
  : `Remove --dry-run; ${resource} ${action} has no dry-run mode.`);
@@ -359,7 +392,7 @@ function validateRequiredCommandInputs(resource, action, args, flags, entry) {
359
392
  }
360
393
  }
361
394
  function validateRegistryCommandInputs(resource, action, args, flags) {
362
- const entry = cachedCommandRegistry()[resource]?.[action];
395
+ const entry = cachedCommandRegistry(resource)?.[action];
363
396
  if (!entry)
364
397
  return;
365
398
  validateSupportedCommandFlags(resource, action, flags);
@@ -396,7 +429,7 @@ async function validateDryRunHandlerPreconditions(handler, args, flags) {
396
429
  throw error;
397
430
  }
398
431
  }
399
- function validateMetaCommandInputs(resource, action, flags) {
432
+ async function validateMetaCommandInputs(resource, action, flags) {
400
433
  if (resource === "doctor") {
401
434
  if (action !== undefined && action !== "run") {
402
435
  throw unknownActionError("doctor", action, ["run",]);
@@ -404,11 +437,11 @@ function validateMetaCommandInputs(resource, action, flags) {
404
437
  return "run";
405
438
  }
406
439
  if (resource === "auth") {
407
- const validActions = Object.keys(AUTH_ACTIONS);
440
+ const validActions = Object.keys(contract.AUTH_ACTIONS);
408
441
  if (!action) {
409
442
  throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
410
443
  }
411
- if (!AUTH_ACTIONS[action]) {
444
+ if (!contract.AUTH_ACTIONS[action]) {
412
445
  throw unknownActionError("auth", action, validActions, "auth only supports 'login'. To check credentials/connectivity, run 'dss doctor'.");
413
446
  }
414
447
  return action;
@@ -417,6 +450,7 @@ function validateMetaCommandInputs(resource, action, flags) {
417
450
  if (action !== undefined && action !== "run") {
418
451
  throw unknownActionError("install-skill", action, ["run",]);
419
452
  }
453
+ const { AGENTS, } = await import("./skill.js");
420
454
  const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
421
455
  if (agentFilter && !AGENTS[agentFilter]) {
422
456
  throw new UsageError(`Unknown agent: ${agentFilter}.`, "usage_error", COMMANDS_RUN_HINT, { agent: agentFilter, validAgents: Object.keys(AGENTS), });
@@ -425,14 +459,14 @@ function validateMetaCommandInputs(resource, action, flags) {
425
459
  }
426
460
  if (resource === "agent") {
427
461
  if (!action)
428
- throw missingActionError("agent", ["contract",], AGENT_CONTRACT_USAGE);
462
+ throw missingActionError("agent", ["contract",], contract.AGENT_CONTRACT_USAGE);
429
463
  if (action !== "contract")
430
464
  throw unknownActionError("agent", action, ["contract",]);
431
465
  return action;
432
466
  }
433
467
  if (resource === "commands") {
434
468
  if (!action)
435
- throw missingActionError("commands", ["run",], COMMANDS_USAGE);
469
+ throw missingActionError("commands", ["run",], contract.COMMANDS_USAGE);
436
470
  if (action !== "run")
437
471
  throw unknownActionError("commands", action, ["run",]);
438
472
  validateSupportedCommandFlags("commands", "run", flags);
@@ -449,7 +483,7 @@ function validateMetaCommandInputs(resource, action, flags) {
449
483
  if (action !== undefined && action !== "run") {
450
484
  throw unknownActionError("cleanup", action, ["run",]);
451
485
  }
452
- const entry = cachedCommandRegistry().cleanup?.run;
486
+ const entry = cachedCommandRegistry("cleanup")?.run;
453
487
  if (entry)
454
488
  validateRequiredCommandInputs("cleanup", "run", [], flags, entry);
455
489
  return "run";
@@ -464,7 +498,7 @@ function validateMetaCommandInputs(resource, action, flags) {
464
498
  if (action !== undefined && action !== "run") {
465
499
  throw unknownActionError("batch", action, ["run",]);
466
500
  }
467
- const entry = cachedCommandRegistry().batch?.run;
501
+ const entry = cachedCommandRegistry("batch")?.run;
468
502
  if (entry)
469
503
  validateRequiredCommandInputs("batch", "run", [], flags, entry);
470
504
  return "run";
@@ -477,7 +511,7 @@ async function validateBatchStep(argv) {
477
511
  const action = positional[1];
478
512
  if (!resource)
479
513
  throw noCommandError();
480
- const metaAction = validateMetaCommandInputs(resource, action, flags);
514
+ const metaAction = await validateMetaCommandInputs(resource, action, flags);
481
515
  if (metaAction)
482
516
  return { positional, flags, resource, action: metaAction, };
483
517
  const resourceActions = commands[resource];
@@ -496,7 +530,7 @@ async function validateBatchStep(argv) {
496
530
  function batchStepNeedsClient(argv) {
497
531
  try {
498
532
  const { positional, flags, } = parseArgs(argv);
499
- if (flags["plan"] === true)
533
+ if (executionMode(flags).plan)
500
534
  return false;
501
535
  const resource = positional[0];
502
536
  if (!resource)
@@ -578,7 +612,7 @@ function validateBatchStepMode(argv, index) {
578
612
  catch {
579
613
  return;
580
614
  }
581
- if (flags["plan"] !== true && flags["dry-run"] !== true)
615
+ if (!executionMode(flags).plan && !executionMode(flags).dryRun)
582
616
  return;
583
617
  const resource = positional[0];
584
618
  const action = positional[1];
@@ -592,13 +626,13 @@ function validateBatchStepMode(argv, index) {
592
626
  const meta = resourceActions[action ?? ""];
593
627
  if (!meta)
594
628
  return;
595
- const entry = cachedCommandRegistry()[resource]?.[action ?? ""];
629
+ const entry = cachedCommandRegistry(resource)?.[action ?? ""];
596
630
  if (!entry)
597
631
  return;
598
- if (flags["plan"] === true && !entry.flags.some((flag) => flag.name === "plan")) {
632
+ if (executionMode(flags).plan && !entry.flags.some((flag) => flag.name === "plan")) {
599
633
  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.");
600
634
  }
601
- if (flags["dry-run"] === true && !entry.dryRun) {
635
+ if (executionMode(flags).dryRun && !entry.dryRun) {
602
636
  throw new UsageError(`Batch step ${index} requests --dry-run, which is not supported for ${resource} ${action}.`, "unknown_flag", "Remove --dry-run from the step.");
603
637
  }
604
638
  }
@@ -607,7 +641,8 @@ function validateBatchStepModes(steps) {
607
641
  validateBatchStepMode(steps[index], index);
608
642
  }
609
643
  }
610
- function runInstallSkill(flags) {
644
+ async function runInstallSkill(flags) {
645
+ const { AGENTS, detectAgents, findWorkspaceRoot, installSkill, planSkillInstalls, } = await import("./skill.js");
611
646
  const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
612
647
  const isGlobal = flags["global"] === true;
613
648
  const targetDir = typeof flags["target"] === "string" ? flags["target"] : undefined;
@@ -633,46 +668,34 @@ function runInstallSkill(flags) {
633
668
  throw new UsageError("No coding agents detected.", "usage_error", "Use --agent NAME to choose one of the supported agents.", { validAgents: Object.keys(AGENTS), });
634
669
  }
635
670
  const scope = isGlobal ? "global" : "project";
636
- const cwd = targetDir ?? (isGlobal ? process.cwd() : findWorkspaceRoot(process.cwd()));
671
+ const cwd = isGlobal
672
+ ? homedir()
673
+ : targetDir
674
+ ? resolve(targetDir)
675
+ : findWorkspaceRoot(process.cwd());
637
676
  const installed = planSkillInstalls(targets, { global: isGlobal, cwd, });
638
- if (flags["plan"] === true) {
677
+ if (executionMode(flags).plan) {
639
678
  return planResult("install-skill", "run", {
640
679
  identifiers: { scope, target: cwd, },
641
680
  payload: { installed, },
642
681
  idempotency: "none",
643
682
  asyncKind: "none",
644
683
  exitCodesOnFailure: { usage: 1, error: 2, transient: 3, },
645
- plannedAndDryRun: flags["dry-run"] === true,
684
+ plannedAndDryRun: executionMode(flags).dryRun,
646
685
  });
647
686
  }
648
687
  return {
649
688
  scope,
650
689
  target: cwd,
651
- installed: flags["dry-run"] === true
690
+ installed: executionMode(flags).dryRun
652
691
  ? installed
653
692
  : installSkill(targets, { global: isGlobal, cwd, }),
654
- ...(flags["dry-run"] === true ? { dryRun: true, } : {}),
693
+ ...(executionMode(flags).dryRun ? { dryRun: true, } : {}),
655
694
  };
656
695
  }
657
696
  const META_PLAN_EXIT_CODES = { usage: 1, error: 2, transient: 3, };
658
697
  function authLoginPlan(flags) {
659
- const tlsSettings = resolveTlsSettings(flags);
660
- const useEnv = dataikuEnvironmentEnabled();
661
- const url = typeof flags["url"] === "string"
662
- ? flags["url"]
663
- : useEnv
664
- ? process.env.DATAIKU_URL ?? ""
665
- : "";
666
- const apiKey = typeof flags["api-key"] === "string"
667
- ? flags["api-key"]
668
- : useEnv
669
- ? process.env.DATAIKU_API_KEY ?? ""
670
- : "";
671
- const projectKey = typeof flags["project-key"] === "string"
672
- ? flags["project-key"]
673
- : useEnv
674
- ? process.env.DATAIKU_PROJECT_KEY
675
- : undefined;
698
+ const { url, apiKey, projectKey, tlsSettings, } = resolveLoginCredentials(flags);
676
699
  if (!url || !apiKey) {
677
700
  throw new UsageError("Missing --url and/or --api-key for auth login.", "missing_required_flag", "Pass --url and --api-key, or set DATAIKU_URL and DATAIKU_API_KEY.", { requiredFlags: ["url", "api-key",], env: ["DATAIKU_URL", "DATAIKU_API_KEY",], });
678
701
  }
@@ -700,7 +723,7 @@ function authLoginPlan(flags) {
700
723
  function cleanupPlan(flags) {
701
724
  const filePath = flags["file"];
702
725
  if (typeof filePath !== "string" || filePath.trim().length === 0) {
703
- throw new UsageError(`--file is required. Usage: ${CLEANUP_USAGE}`);
726
+ throw new UsageError(`--file is required. Usage: ${contract.CLEANUP_USAGE}`);
704
727
  }
705
728
  return planResult("cleanup", "run", {
706
729
  identifiers: { file: filePath, },
@@ -711,13 +734,13 @@ function cleanupPlan(flags) {
711
734
  idempotency: "none",
712
735
  asyncKind: "none",
713
736
  exitCodesOnFailure: META_PLAN_EXIT_CODES,
714
- plannedAndDryRun: flags["dry-run"] === true,
737
+ plannedAndDryRun: executionMode(flags).dryRun,
715
738
  });
716
739
  }
717
740
  function batchPlan(flags) {
718
741
  const payload = unknownJsonInput(flags);
719
742
  if (payload === undefined) {
720
- throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${BATCH_USAGE}`, "missing_required_flag", BATCH_HINT);
743
+ throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${contract.BATCH_USAGE}`, "missing_required_flag", contract.BATCH_HINT);
721
744
  }
722
745
  const steps = parseBatchSteps(payload);
723
746
  return planResult("batch", "run", {
@@ -728,34 +751,37 @@ function batchPlan(flags) {
728
751
  payload: {
729
752
  steps: steps.map((argv) => redactArgv(argv)),
730
753
  continueOnError: flags["continue-on-error"] === true,
731
- dryRun: flags["dry-run"] === true,
754
+ dryRun: executionMode(flags).dryRun,
732
755
  },
733
756
  idempotency: "none",
734
757
  asyncKind: "none",
735
- exitCodesOnFailure: BATCH_PLAN_EXIT_CODES,
736
- plannedAndDryRun: flags["dry-run"] === true,
758
+ exitCodesOnFailure: contract.BATCH_PLAN_EXIT_CODES,
759
+ plannedAndDryRun: executionMode(flags).dryRun,
737
760
  });
738
761
  }
739
762
  async function runMetaCommand(resource, action, flags) {
763
+ if (resource !== "version")
764
+ await loadCommandRuntime();
740
765
  if (resource === "doctor") {
741
766
  if (action !== undefined && action !== "run") {
742
767
  throw unknownActionError("doctor", action, ["run",]);
743
768
  }
744
769
  currentCommandContext.action = action ?? "run";
770
+ const { runDoctor, } = await import("./cli/doctor.js");
745
771
  const { result, exitCode, } = await runDoctor(flags);
746
772
  return { action: "run", result, exitCode, };
747
773
  }
748
774
  if (resource === "auth") {
749
- const validActions = Object.keys(AUTH_ACTIONS);
775
+ const validActions = Object.keys(contract.AUTH_ACTIONS);
750
776
  if (!action) {
751
777
  throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
752
778
  }
753
779
  currentCommandContext.action = action;
754
- const authMeta = AUTH_ACTIONS[action];
780
+ const authMeta = contract.AUTH_ACTIONS[action];
755
781
  if (!authMeta) {
756
782
  throw unknownActionError("auth", action, validActions, "auth only supports 'login'. To check credentials/connectivity, run 'dss doctor'.");
757
783
  }
758
- if (flags["plan"] === true) {
784
+ if (executionMode(flags).plan) {
759
785
  return { action, result: authLoginPlan(flags), exitCode: 0, };
760
786
  }
761
787
  return { action, result: await authMeta.handler(flags), exitCode: 0, };
@@ -765,29 +791,29 @@ async function runMetaCommand(resource, action, flags) {
765
791
  throw unknownActionError("install-skill", action, ["run",]);
766
792
  }
767
793
  currentCommandContext.action = action ?? "run";
768
- return { action: "run", result: runInstallSkill(flags), exitCode: 0, };
794
+ return { action: "run", result: await runInstallSkill(flags), exitCode: 0, };
769
795
  }
770
796
  if (resource === "agent") {
771
797
  if (!action)
772
- throw missingActionError("agent", ["contract",], AGENT_CONTRACT_USAGE);
798
+ throw missingActionError("agent", ["contract",], contract.AGENT_CONTRACT_USAGE);
773
799
  currentCommandContext.action = action;
774
800
  if (action !== "contract")
775
801
  throw unknownActionError("agent", action, ["contract",]);
776
- return { action, result: buildAgentContract(), exitCode: 0, };
802
+ return { action, result: contract.buildAgentContract(), exitCode: 0, };
777
803
  }
778
804
  if (resource === "commands") {
779
805
  if (!action)
780
- throw missingActionError("commands", ["run",], COMMANDS_USAGE);
806
+ throw missingActionError("commands", ["run",], contract.COMMANDS_USAGE);
781
807
  currentCommandContext.action = action;
782
808
  if (action !== "run")
783
809
  throw unknownActionError("commands", action, ["run",]);
784
810
  validateSupportedCommandFlags("commands", "run", flags);
785
811
  const selectors = commandRegistrySelectors(flags);
786
- const registry = buildCommandRegistry();
812
+ const registry = contract.buildCommandRegistry();
787
813
  for (const selector of selectors) {
788
814
  const selectorParts = selector.split(".");
789
815
  if (selectorParts.some((part) => part.length === 0)) {
790
- throw new UsageError(`Invalid --fields selector: ${selector}. Expected RESOURCE or RESOURCE.ACTION[.FIELD...].`, "usage_error", COMMANDS_USAGE, { selector, });
816
+ throw new UsageError(`Invalid --fields selector: ${selector}. Expected RESOURCE or RESOURCE.ACTION[.FIELD...].`, "usage_error", contract.COMMANDS_USAGE, { selector, });
791
817
  }
792
818
  const [selectedResource, selectedAction,] = selectorParts;
793
819
  const resourceActions = registry[selectedResource];
@@ -808,7 +834,7 @@ async function runMetaCommand(resource, action, flags) {
808
834
  }
809
835
  return {
810
836
  action,
811
- result: selectors.length === 0 ? commandActionSummary(registry) : selectedRegistry,
837
+ result: selectors.length === 0 ? contract.commandActionSummary(registry) : selectedRegistry,
812
838
  exitCode: 0,
813
839
  };
814
840
  }
@@ -824,7 +850,7 @@ async function runMetaCommand(resource, action, flags) {
824
850
  throw unknownActionError("cleanup", action, ["run",]);
825
851
  }
826
852
  currentCommandContext.action = action ?? "run";
827
- if (flags["plan"] === true) {
853
+ if (executionMode(flags).plan) {
828
854
  return { action: "run", result: cleanupPlan(flags), exitCode: 0, };
829
855
  }
830
856
  const { result, exitCode, } = await runCleanup(flags);
@@ -835,6 +861,7 @@ async function runMetaCommand(resource, action, flags) {
835
861
  throw unknownActionError("fixtures", action, ["run",]);
836
862
  }
837
863
  currentCommandContext.action = action ?? "run";
864
+ const { runFixtures, } = await import("./cli/doctor.js");
838
865
  return { action: "run", result: await runFixtures(flags), exitCode: 0, };
839
866
  }
840
867
  if (resource === "batch") {
@@ -842,7 +869,7 @@ async function runMetaCommand(resource, action, flags) {
842
869
  throw unknownActionError("batch", action, ["run",]);
843
870
  }
844
871
  currentCommandContext.action = action ?? "run";
845
- if (flags["plan"] === true) {
872
+ if (executionMode(flags).plan) {
846
873
  return { action: "run", result: batchPlan(flags), exitCode: 0, };
847
874
  }
848
875
  const { result, exitCode, } = await runBatch(flags);
@@ -856,11 +883,14 @@ async function runMetaCommand(resource, action, flags) {
856
883
  * share this guard.
857
884
  */
858
885
  function assertCleanupLedgerSupported(resource, action, flags) {
859
- if (typeof flags["record-cleanup"] !== "string" || flags["dry-run"] === true)
886
+ if (typeof flags["record-cleanup"] !== "string" || executionMode(flags).dryRun)
860
887
  return;
861
- if (!supportsCleanupLedger(resource, action)) {
888
+ if (!contract.supportsCleanupLedger(resource, action)) {
862
889
  throw new UsageError(`--record-cleanup is not supported for ${resource} ${action}.`);
863
890
  }
891
+ if (resource === "workspace" && action === "create" && flags["stdin"] === true) {
892
+ throw new UsageError("workspace create cannot combine --record-cleanup with --stdin because the cleanup identifier cannot be replayed from a consumed stream.", "conflicting_input_sources", "Use --data or --data-file so the workspaceKey remains available for the cleanup ledger.");
893
+ }
864
894
  }
865
895
  /**
866
896
  * Preflight a requested cleanup ledger path before any remote mutation so an
@@ -869,7 +899,7 @@ function assertCleanupLedgerSupported(resource, action, flags) {
869
899
  * steps both call it, and dry-run/preview runs never touch the ledger.
870
900
  */
871
901
  async function preflightCleanupLedgerForFlags(client, flags) {
872
- if (flags["dry-run"] === true)
902
+ if (executionMode(flags).dryRun)
873
903
  return;
874
904
  const ledgerPath = flags["record-cleanup"];
875
905
  if (typeof ledgerPath !== "string")
@@ -920,19 +950,27 @@ async function preflightCleanupLedgerForFlags(client, flags) {
920
950
  */
921
951
  async function recordCleanupLedgerEntry(client, resource, action, args, flags, result, projectKey) {
922
952
  const ledgerPath = flags["record-cleanup"];
923
- if (typeof ledgerPath !== "string" || flags["dry-run"] === true)
953
+ if (typeof ledgerPath !== "string" || executionMode(flags).dryRun)
924
954
  return;
925
955
  const entry = cleanupLedgerEntry(resource, action, args, flags, result, projectKey);
926
- if (entry)
956
+ if (entry) {
927
957
  await appendCleanupLedgerEntry(ledgerPath, entry, client.getBaseUrl());
958
+ return;
959
+ }
960
+ const record = result !== null && typeof result === "object" && !Array.isArray(result)
961
+ ? result
962
+ : {};
963
+ if (record.skipped !== undefined || record.cleanupEligible === false)
964
+ return;
965
+ throw new ClientValidationError(`Mutation completed but no cleanup entry could be derived for ${resource} ${action}.`, "ambiguous_outcome", "Inspect the created object and remove it manually; do not assume the requested cleanup ledger contains it.", { resource, action, ledgerPath, });
928
966
  }
929
967
  async function runBatch(flags) {
930
968
  const payload = unknownJsonInput(flags);
931
969
  if (payload === undefined) {
932
- throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${BATCH_USAGE}`, "missing_required_flag", BATCH_HINT);
970
+ throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${contract.BATCH_USAGE}`, "missing_required_flag", contract.BATCH_HINT);
933
971
  }
934
972
  const steps = parseBatchSteps(payload);
935
- if (flags["dry-run"] === true) {
973
+ if (executionMode(flags).dryRun) {
936
974
  const batchProjectKey = typeof flags["project-key"] === "string"
937
975
  ? flags["project-key"]
938
976
  : dataikuEnvironmentEnabled()
@@ -999,6 +1037,7 @@ async function runBatch(flags) {
999
1037
  env: ["DATAIKU_API_KEY",],
1000
1038
  });
1001
1039
  }
1040
+ const { DataikuClient, } = await import("./client.js");
1002
1041
  client = new DataikuClient({
1003
1042
  url,
1004
1043
  apiKey,
@@ -1061,8 +1100,8 @@ async function runBatch(flags) {
1061
1100
  if (!meta)
1062
1101
  throw unknownActionError(resource, action, Object.keys(resourceActions));
1063
1102
  validateSupportedCommandFlags(resource, action, stepFlags);
1064
- if (stepFlags["plan"] === true) {
1065
- result = buildMutationPlan(resource, action, meta, positional.slice(2), stepFlags);
1103
+ if (executionMode(stepFlags).plan) {
1104
+ result = contract.buildMutationPlan(resource, action, meta, positional.slice(2), stepFlags);
1066
1105
  }
1067
1106
  else {
1068
1107
  if (!client) {
@@ -1102,16 +1141,46 @@ async function runBatch(flags) {
1102
1141
  }
1103
1142
  }
1104
1143
  const ok = firstFailureExit === undefined;
1144
+ const failureSummary = summarizeFailureEnvelopes(results.flatMap((step) => step.error ? [{ index: step.index, error: step.error, },] : []));
1105
1145
  return {
1106
1146
  result: {
1107
1147
  ok,
1108
1148
  total: steps.length,
1109
1149
  completed: results.filter((step) => step.ok !== null).length,
1150
+ ...failureSummary,
1110
1151
  steps: results,
1111
1152
  },
1112
1153
  exitCode: ok ? 0 : firstFailureExit ?? 2,
1113
1154
  };
1114
1155
  }
1156
+ function summarizeFailureEnvelopes(failures) {
1157
+ const failureCodes = failures.map(({ index, error, }) => ({
1158
+ index,
1159
+ code: error.code,
1160
+ category: error.category,
1161
+ exitCode: error.exitCode,
1162
+ retryable: error.retryable === true,
1163
+ }));
1164
+ return {
1165
+ failed: failures.length,
1166
+ retryable: failures.length > 0 && failureCodes.every((failure) => failure.retryable),
1167
+ hasRetryableFailures: failureCodes.some((failure) => failure.retryable),
1168
+ failureCodes,
1169
+ };
1170
+ }
1171
+ function summarizeCleanupFailures(failures) {
1172
+ return summarizeFailureEnvelopes(failures.flatMap((failure) => {
1173
+ const index = failure.index;
1174
+ const error = failure.error;
1175
+ return typeof index === "number"
1176
+ && error !== null
1177
+ && typeof error === "object"
1178
+ && !Array.isArray(error)
1179
+ && error.type === "error"
1180
+ ? [{ index, error: error, },]
1181
+ : [];
1182
+ }));
1183
+ }
1115
1184
  function rawFlagValue(argv, flagName) {
1116
1185
  const longFlag = `--${flagName}`;
1117
1186
  for (let index = 0; index < argv.length; index++) {
@@ -1129,7 +1198,7 @@ function commandIsProjectScoped(resource, action) {
1129
1198
  if (!resource)
1130
1199
  return false;
1131
1200
  const usage = commands[resource]?.[action ?? ""]?.usage ?? "";
1132
- return inferRequiresProject(resource, action ?? "", usage);
1201
+ return contract.inferRequiresProject(resource, action ?? "", usage);
1133
1202
  }
1134
1203
  function rawCommandContext() {
1135
1204
  const argv = process.argv.slice(2);
@@ -1206,13 +1275,48 @@ function requestIdFromBody(body) {
1206
1275
  return undefined;
1207
1276
  }
1208
1277
  const MISSING_PROJECT_KEY_ERROR_PREFIX = "projectKey is required";
1278
+ function isAmbiguousMutationFailure(err, context) {
1279
+ if (!(err instanceof DataikuError))
1280
+ return false;
1281
+ const method = err.retry?.method.toUpperCase();
1282
+ if (!method || method === "GET" || method === "HEAD")
1283
+ return false;
1284
+ if (err.status !== 0 && err.status < 500)
1285
+ return false;
1286
+ const entry = context.resource && context.action
1287
+ ? cachedCommandRegistry(context.resource)?.[context.action]
1288
+ : undefined;
1289
+ return entry?.sideEffect === "write" && entry.idempotency === "none";
1290
+ }
1291
+ const MAX_FAILURE_RESULT_BYTES = 64 * 1024;
1292
+ function boundedFailureResult(result) {
1293
+ let serialized;
1294
+ try {
1295
+ serialized = JSON.stringify(result);
1296
+ }
1297
+ catch {
1298
+ return { result: "[unserializable command result]", resultTruncated: true, };
1299
+ }
1300
+ if (serialized === undefined || Buffer.byteLength(serialized, "utf8") <= MAX_FAILURE_RESULT_BYTES) {
1301
+ return { result, };
1302
+ }
1303
+ return {
1304
+ resultPreview: Buffer.from(serialized, "utf8").subarray(0, MAX_FAILURE_RESULT_BYTES).toString("utf8"),
1305
+ resultBytes: Buffer.byteLength(serialized, "utf8"),
1306
+ resultTruncated: true,
1307
+ };
1308
+ }
1209
1309
  function errorExitCode(err) {
1210
1310
  if (err instanceof CommandResultFailure)
1211
1311
  return err.exitCode;
1312
+ if (err instanceof ClientValidationError && err.code === "assertion_failed")
1313
+ return 4;
1212
1314
  if (err instanceof ClientValidationError && err.code === "ambiguous_outcome")
1213
1315
  return 2;
1214
1316
  if (err instanceof UsageError || err instanceof ClientValidationError)
1215
1317
  return 1;
1318
+ if (isAmbiguousMutationFailure(err, rawCommandContext()))
1319
+ return 2;
1216
1320
  if (err instanceof DataikuError)
1217
1321
  return err.category === "transient" ? 3 : 2;
1218
1322
  if (err instanceof Error && err.message.startsWith(MISSING_PROJECT_KEY_ERROR_PREFIX))
@@ -1222,7 +1326,8 @@ function errorExitCode(err) {
1222
1326
  function buildErrorReport(err) {
1223
1327
  const context = rawCommandContext();
1224
1328
  const exitCode = errorExitCode(err);
1225
- if (err instanceof ClientValidationError && err.code === "ambiguous_outcome") {
1329
+ if (err instanceof ClientValidationError
1330
+ && (err.code === "ambiguous_outcome" || err.code === "assertion_failed")) {
1226
1331
  return {
1227
1332
  type: "error",
1228
1333
  ok: false,
@@ -1230,6 +1335,7 @@ function buildErrorReport(err) {
1230
1335
  code: err.code,
1231
1336
  category: "dss",
1232
1337
  exitCode,
1338
+ retryable: false,
1233
1339
  ...(err.hint ? { hint: err.hint, } : {}),
1234
1340
  ...(err.details ? { details: err.details, } : {}),
1235
1341
  ...context,
@@ -1259,7 +1365,29 @@ function buildErrorReport(err) {
1259
1365
  code: err.code,
1260
1366
  category: "dss",
1261
1367
  exitCode: err.exitCode,
1262
- details: { result: err.result, },
1368
+ details: boundedFailureResult(err.result),
1369
+ ...context,
1370
+ };
1371
+ }
1372
+ if (isAmbiguousMutationFailure(err, context)) {
1373
+ const method = err.retry?.method.toUpperCase() ?? "mutation";
1374
+ return {
1375
+ type: "error",
1376
+ ok: false,
1377
+ error: `The ${method} request failed after dispatch; the mutation outcome is unknown.`,
1378
+ code: "ambiguous_outcome",
1379
+ category: "dss",
1380
+ exitCode: 2,
1381
+ hint: "Read the target state and verify whether the mutation took effect before retrying.",
1382
+ status: err.status,
1383
+ retryable: false,
1384
+ requestId: err.requestId ?? requestIdFromBody(err.body),
1385
+ details: {
1386
+ dssCategory: err.category,
1387
+ statusText: canonicalStatusText(err.status),
1388
+ idempotency: "none",
1389
+ ...(err.retry ? { retry: err.retry, } : {}),
1390
+ },
1263
1391
  ...context,
1264
1392
  };
1265
1393
  }
@@ -1374,8 +1502,8 @@ async function main() {
1374
1502
  throw unknownActionError(resource, action, Object.keys(resourceActions));
1375
1503
  validateSupportedCommandFlags(resource, action, flags);
1376
1504
  const args = positional.slice(2);
1377
- if (flags["plan"] === true) {
1378
- const plan = buildMutationPlan(resource, action, actionMeta, args, flags);
1505
+ if (executionMode(flags).plan) {
1506
+ const plan = contract.buildMutationPlan(resource, action, actionMeta, args, flags);
1379
1507
  writeCommandResult(plan);
1380
1508
  return;
1381
1509
  }
@@ -1387,6 +1515,7 @@ async function main() {
1387
1515
  writeCommandResult(result);
1388
1516
  return;
1389
1517
  }
1518
+ actionMeta.validate?.(args, flags);
1390
1519
  const { url, apiKey, projectKey, tlsRejectUnauthorized, caCertPath, } = resolveCredentials(flags);
1391
1520
  currentCommandContext.projectKey = projectKey;
1392
1521
  if (!url) {
@@ -1397,6 +1526,7 @@ async function main() {
1397
1526
  }
1398
1527
  const requestTimeoutMs = num(flags["request-timeout"], "--request-timeout");
1399
1528
  const retryMaxAttempts = num(flags["retries"], "--retries");
1529
+ const { DataikuClient, } = await import("./client.js");
1400
1530
  const client = new DataikuClient({
1401
1531
  url,
1402
1532
  apiKey,
@@ -1416,7 +1546,8 @@ async function main() {
1416
1546
  throw new CommandResultFailure(result, failureExitCode);
1417
1547
  writeCommandResult(result);
1418
1548
  }
1419
- main().catch((err) => {
1549
+ main().catch(async (err) => {
1550
+ await loadCommandRuntime();
1420
1551
  writeErrorReport(err);
1421
1552
  process.exitCode = errorExitCode(err);
1422
1553
  });