dataiku-sdk 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +3 -3
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/src/cli/command-syntax.json +14524 -0
  4. package/dist/src/cli/commands/analysis.js +6 -9
  5. package/dist/src/cli/commands/api-deployer.js +15 -33
  6. package/dist/src/cli/commands/api-service.js +17 -27
  7. package/dist/src/cli/commands/app.js +22 -39
  8. package/dist/src/cli/commands/bundle.js +15 -24
  9. package/dist/src/cli/commands/business-app.js +10 -17
  10. package/dist/src/cli/commands/code-env.js +23 -36
  11. package/dist/src/cli/commands/code.js +4 -4
  12. package/dist/src/cli/commands/connection.js +12 -22
  13. package/dist/src/cli/commands/continuous-activity.js +6 -9
  14. package/dist/src/cli/commands/dashboard.js +8 -13
  15. package/dist/src/cli/commands/data-collection.js +9 -16
  16. package/dist/src/cli/commands/data-quality.js +14 -26
  17. package/dist/src/cli/commands/dataset.js +27 -50
  18. package/dist/src/cli/commands/discussion.js +7 -10
  19. package/dist/src/cli/commands/doctor.js +3 -3
  20. package/dist/src/cli/commands/flow-zone.js +8 -17
  21. package/dist/src/cli/commands/folder.js +12 -20
  22. package/dist/src/cli/commands/future.js +7 -10
  23. package/dist/src/cli/commands/group.js +7 -11
  24. package/dist/src/cli/commands/insight.js +7 -11
  25. package/dist/src/cli/commands/job.js +13 -23
  26. package/dist/src/cli/commands/knowledge-bank.js +5 -6
  27. package/dist/src/cli/commands/llm.js +5 -8
  28. package/dist/src/cli/commands/macro.js +9 -16
  29. package/dist/src/cli/commands/meaning.js +7 -11
  30. package/dist/src/cli/commands/metrics.js +7 -10
  31. package/dist/src/cli/commands/ml-task.js +12 -20
  32. package/dist/src/cli/commands/model-evaluation-store.js +7 -11
  33. package/dist/src/cli/commands/notebook.js +15 -27
  34. package/dist/src/cli/commands/plugin.js +35 -68
  35. package/dist/src/cli/commands/project-deployer.js +10 -22
  36. package/dist/src/cli/commands/project-folder.js +10 -17
  37. package/dist/src/cli/commands/project-git.js +38 -70
  38. package/dist/src/cli/commands/project-library.js +15 -23
  39. package/dist/src/cli/commands/project.js +9 -26
  40. package/dist/src/cli/commands/recipe.d.ts +16 -0
  41. package/dist/src/cli/commands/recipe.js +83 -94
  42. package/dist/src/cli/commands/saved-model.js +23 -39
  43. package/dist/src/cli/commands/scenario.js +20 -34
  44. package/dist/src/cli/commands/sql.js +4 -4
  45. package/dist/src/cli/commands/statistics.js +11 -18
  46. package/dist/src/cli/commands/streaming-endpoint.js +7 -11
  47. package/dist/src/cli/commands/user.js +11 -22
  48. package/dist/src/cli/commands/variable.js +3 -4
  49. package/dist/src/cli/commands/webapp.js +10 -16
  50. package/dist/src/cli/commands/wiki.js +9 -13
  51. package/dist/src/cli/commands/workspace.js +8 -14
  52. package/dist/src/cli/contract.d.ts +5 -15
  53. package/dist/src/cli/contract.js +156 -253
  54. package/dist/src/cli/doctor.js +1 -0
  55. package/dist/src/cli/env.d.ts +2 -0
  56. package/dist/src/cli/env.js +15 -1
  57. package/dist/src/cli/helpers/app-permissions.js +2 -1
  58. package/dist/src/cli/helpers/flow-zone.js +2 -1
  59. package/dist/src/cli/helpers/recipe.js +2 -1
  60. package/dist/src/cli/syntax.d.ts +83 -0
  61. package/dist/src/cli/syntax.js +217 -0
  62. package/dist/src/cli/types.d.ts +3 -0
  63. package/dist/src/cli/usage.d.ts +1 -1
  64. package/dist/src/cli/usage.js +4 -2
  65. package/dist/src/cli/version.js +2 -0
  66. package/dist/src/cli.js +37 -64
  67. package/dist/src/client.js +7 -6
  68. package/dist/src/errors.d.ts +12 -1
  69. package/dist/src/errors.js +28 -3
  70. package/dist/src/index.d.ts +1 -3
  71. package/dist/src/index.js +2 -2
  72. package/dist/src/resources/api-services.js +2 -3
  73. package/dist/src/resources/applications.js +2 -4
  74. package/dist/src/resources/code-envs.js +2 -2
  75. package/dist/src/resources/data-quality.js +4 -3
  76. package/dist/src/resources/datasets.d.ts +13 -0
  77. package/dist/src/resources/datasets.js +5 -4
  78. package/dist/src/resources/flow-zones.js +4 -2
  79. package/dist/src/resources/folders.js +4 -2
  80. package/dist/src/resources/futures.js +1 -3
  81. package/dist/src/resources/insights.js +3 -2
  82. package/dist/src/resources/jobs.js +4 -6
  83. package/dist/src/resources/macros.js +2 -4
  84. package/dist/src/resources/notebooks.js +2 -3
  85. package/dist/src/resources/project-git.js +9 -11
  86. package/dist/src/resources/project-library.d.ts +0 -2
  87. package/dist/src/resources/project-library.js +2 -7
  88. package/dist/src/resources/projects.js +4 -3
  89. package/dist/src/resources/recipes.d.ts +28 -0
  90. package/dist/src/resources/recipes.js +116 -84
  91. package/dist/src/resources/sql.js +8 -3
  92. package/dist/src/resources/statistics.js +2 -1
  93. package/dist/src/resources/variables.js +2 -1
  94. package/dist/src/resources/webapps.js +2 -3
  95. package/dist/src/schemas.d.ts +1 -2
  96. package/dist/src/schemas.js +1 -1
  97. package/dist/src/skill.js +6 -7
  98. package/dist/src/utils/cleanup-ledger.js +9 -8
  99. package/dist/src/utils/polling.d.ts +3 -0
  100. package/dist/src/utils/polling.js +3 -0
  101. package/dist/src/utils/project-archive.js +2 -2
  102. package/dist/src/utils/stable-hash.d.ts +3 -1
  103. package/dist/src/utils/stable-hash.js +2 -0
  104. package/package.json +4 -4
  105. package/plugin.json +1 -1
  106. package/skills/dataiku-dss/SKILL.md +2 -3
  107. package/skills/dataiku-dss/references/authentication.md +3 -3
  108. package/skills/dataiku-dss/references/coding.md +1 -1
  109. package/dist/src/utils/pagination.d.ts +0 -8
  110. package/dist/src/utils/pagination.js +0 -23
@@ -6,19 +6,24 @@ import { DataikuError, } from "../errors.js";
6
6
  import actionOutputSchemasMetadata from "../generated/action-output-schemas.json" with { type: "json"
7
7
  };
8
8
  import { APP_MANIFEST_CONCURRENCY_CONTROL, } from "../resources/applications.js";
9
+ import { buildDatasetCreateBody, } from "../resources/datasets.js";
10
+ import { validatePluginDestinationPath, validatePluginPath, } from "../resources/plugins.js";
9
11
  import { encodeLibraryPath, PROJECT_LIBRARY_CONCURRENCY_CONTROL, validateLibraryDestinationPath, validateLibraryName, validateLibraryPath, } from "../resources/project-library.js";
12
+ import { buildRecipeCreateRequest, } from "../resources/recipes.js";
10
13
  import { encodeGitReferencePath, validateGitReferencePath, } from "../utils/git-reference.js";
11
- import { jobBuildTargetTypeFromFlags, json, jsonInput, num, parseBooleanOption, parseJsonObject, recipeInputDatasetsFromFlags, requiredJsonInput, rewritePairsFromFlags, schemaColumnsInput, sha256Hex, stableHash, stringField, textInput, } from "./coerce.js";
14
+ import { jobBuildTargetTypeFromFlags, json, jsonInput, num, parseBooleanOption, parseJsonObject, requiredJsonInput, rewritePairsFromFlags, schemaColumnsInput, sha256Hex, stableHash, stringField, textInput, } from "./coerce.js";
12
15
  import { parseCodeRunIntegerFlag, resolveCodeInputWithSource, } from "./commands/code.js";
13
16
  import { commands, } from "./commands/index.js";
14
17
  import { projectLibraryPutPayload, } from "./commands/project-library.js";
18
+ import { recipeCreateOptionsFromFlags, } from "./commands/recipe.js";
15
19
  import { resolveSqlQueryInvocation, } from "./commands/sql.js";
16
- import { dataikuEnvironmentEnabled, } from "./env.js";
17
- import { BOOLEAN_FLAGS, executionMode, FLAG_ALIASES, KNOWN_LONG_FLAGS, SHORT_FLAGS, } from "./flags.js";
20
+ import { ambientProjectKey, } from "./env.js";
21
+ import { BOOLEAN_FLAGS, executionMode, FLAG_ALIASES, SHORT_FLAGS, } from "./flags.js";
18
22
  import { flowZoneColor, flowZoneMoveItems, flowZoneName, } from "./helpers/flow-zone.js";
19
23
  import { recipeBackupPath, recipeRunShouldWait, } from "./helpers/recipe.js";
20
24
  import { encodedProjectEndpointForPlan, planResult, } from "./output.js";
21
25
  import { resolveLoginCredentials, } from "./runtime.js";
26
+ import { commandSyntax, commandSyntaxTree, commandUsage, renderNodes, syntaxCommandWords, syntaxHasFlag, } from "./syntax.js";
22
27
  import { inferRequiresProject, requireArgs, UsageError, } from "./usage.js";
23
28
  import { AGENT_CONTRACT_SCHEMA_ID, AGENT_CONTRACT_VERSION, cliVersionResult, JSON_SCHEMA_DRAFT, } from "./version.js";
24
29
  function codeEnvWait(flags) {
@@ -81,7 +86,6 @@ export const AUTH_ACTIONS = {
81
86
  saveCredentials({ url, apiKey, projectKey, ...tlsSettings, });
82
87
  return { saved: true, path: credentialsPath, };
83
88
  },
84
- usage: "dss auth login --url URL --api-key KEY [--project-key KEY] [--insecure] [--ca-cert PATH]",
85
89
  description: "Validate and save DSS credentials from flags or environment variables.",
86
90
  examples: [
87
91
  "dss auth login --url https://dss.example.com --api-key YOUR_KEY",
@@ -238,7 +242,7 @@ const AUTHENTICATED_AGENT_FLAGS = [
238
242
  "insecure",
239
243
  "ca-cert",
240
244
  ];
241
- export const COMMANDS_USAGE = "dss commands run [--fields PATHS] [--output PATH]";
245
+ export const COMMANDS_USAGE = commandUsage("commands", "run");
242
246
  const COMMANDS_DESCRIPTION = "Print a compact resource/action summary by default; use --fields for scoped command metadata or --output PATH to export the full registry without sending it through stdout.";
243
247
  const COMMANDS_EXAMPLES = [
244
248
  "dss commands run",
@@ -248,28 +252,28 @@ const COMMANDS_EXAMPLES = [
248
252
  "dss commands run --output commands.json",
249
253
  ];
250
254
  export const AGENT_CONTRACT_COMMAND = "dss agent contract";
251
- export const AGENT_CONTRACT_USAGE = "dss agent contract [--fields PATHS]";
255
+ export const AGENT_CONTRACT_USAGE = commandUsage("agent", "contract");
252
256
  const AGENT_CONTRACT_DESCRIPTION = "Print the versioned JSON agent contract; scope bootstrap fields, use commands.actions to enumerate the surface, and read schemas only when needed.";
253
257
  const AGENT_CONTRACT_EXAMPLES = [
254
258
  "dss agent contract --fields protocol,agentContractVersion,cli,stdio,planning,compatibility",
255
259
  "dss agent contract --fields commands.actions",
256
260
  ];
257
- const VERSION_USAGE = "dss version";
261
+ const VERSION_USAGE = commandUsage("version", "run");
258
262
  const VERSION_DESCRIPTION = "Print the CLI version, checkout/build revisions, load source, runtime, and stale-build status as JSON.";
259
263
  const VERSION_EXAMPLES = ["dss version", "dss --version",];
260
- const INSTALL_SKILL_USAGE = "dss install-skill [--global] [--agent NAME] [--target PATH] [--list-agents] [--dry-run] [--plan]";
264
+ const INSTALL_SKILL_USAGE = commandUsage("install-skill", "run");
261
265
  const INSTALL_SKILL_DESCRIPTION = "Report missing/stale/current skill status and atomically install changed dataiku-dss agent skills.";
262
266
  const INSTALL_SKILL_EXAMPLES = [
263
267
  "dss install-skill --list-agents",
264
268
  "dss install-skill --agent omp --dry-run",
265
269
  ];
266
- export const CLEANUP_USAGE = "dss cleanup --file PATH [--dry-run|--apply] [--continue-on-error]";
270
+ export const CLEANUP_USAGE = commandUsage("cleanup", "run");
267
271
  const CLEANUP_DESCRIPTION = "Replay cleanup entries in reverse order; failures include structured code/category/exitCode/retryability summaries.";
268
272
  const CLEANUP_EXAMPLES = [
269
273
  "dss cleanup --file cleanup.jsonl",
270
274
  "dss cleanup --file cleanup.jsonl --apply",
271
275
  ];
272
- const FIXTURES_USAGE = "dss fixtures [--project-key KEY] [--allow-types CSV]";
276
+ const FIXTURES_USAGE = commandUsage("fixtures", "run");
273
277
  const FIXTURES_DESCRIPTION = "Discover safe live-test fixtures for agent workflows.";
274
278
  const FIXTURES_EXAMPLES = [
275
279
  "dss fixtures",
@@ -500,10 +504,9 @@ function shortFlagPattern(flags) {
500
504
  * use an actionless prefix, while real command paths that spell `run` in their
501
505
  * usage (e.g. `dss commands run`) keep the action token.
502
506
  */
503
- function argvPrefix(resource, action, usage) {
504
- const usageTokens = usage.split(/\s+/).filter((token) => token.length > 0);
505
- const head = usageTokens.slice(1);
506
- return head[0] === resource && head[1] === action ? [resource, action,] : [resource,];
507
+ function argvPrefix(resource, action) {
508
+ const words = syntaxCommandWords(commandSyntaxTree(resource, action) ?? []);
509
+ return words[0] === resource && words[1] === action ? [resource, action,] : [resource,];
507
510
  }
508
511
  /**
509
512
  * Describe one invocation as a canonical argv token array. Agents emit the
@@ -520,7 +523,7 @@ function argvPrefix(resource, action, usage) {
520
523
  * negative-number and `--` separator rules.
521
524
  */
522
525
  function argvJsonSchema(resource, action, flags, usage, requiredFlags, requiredOneOf) {
523
- const prefix = argvPrefix(resource, action, usage);
526
+ const prefix = argvPrefix(resource, action);
524
527
  const aliases = {};
525
528
  for (const flag of flags) {
526
529
  aliases[flag.name] = flag.aliases ?? [];
@@ -528,7 +531,8 @@ function argvJsonSchema(resource, action, flags, usage, requiredFlags, requiredO
528
531
  const booleanNames = flags.filter((flag) => flag.kind === "boolean")
529
532
  .flatMap((flag) => [flag.name, ...(flag.aliases ?? []),]);
530
533
  const valueFlags = flags.filter((flag) => flag.kind === "value");
531
- const requiredPositionals = requiredPlanPositionals(usage);
534
+ const requiredPositionals = commandSyntax(resource, action).positionalArguments
535
+ .filter((positional) => positional.required).map((positional) => positional.name);
532
536
  const itemSchemas = [
533
537
  { not: { pattern: "^-", }, },
534
538
  { const: "-", },
@@ -659,45 +663,6 @@ const EXPLICIT_REGISTRY_OVERRIDES = {
659
663
  examplePayload: { article: { name: "Updated article", }, },
660
664
  },
661
665
  };
662
- function extractUsageFlags(usage) {
663
- const flags = [];
664
- for (const match of usage.matchAll(/--([a-z0-9-]+)/g)) {
665
- flags.push(FLAG_ALIASES[match[1]] ?? match[1]);
666
- }
667
- return uniqueStrings(flags).filter((flag) => KNOWN_LONG_FLAGS.has(flag));
668
- }
669
- function extractPositionalArguments(usage) {
670
- const positionals = [];
671
- const add = (name, required) => {
672
- const existing = positionals.find((positional) => positional.name === name);
673
- if (existing)
674
- existing.required ||= required;
675
- else
676
- positionals.push({ name, required, });
677
- };
678
- for (const match of usage.matchAll(/<([^>]+)>/g)) {
679
- const before = usage.slice(0, match.index);
680
- const openParen = before.lastIndexOf("(");
681
- const insideParen = openParen > before.lastIndexOf(")");
682
- const closeParen = insideParen ? usage.indexOf(")", match.index) : -1;
683
- const group = closeParen >= 0 ? usage.slice(openParen + 1, closeParen) : "";
684
- const hasFlagAlternative = group.split("|").some((alternative) => alternative.includes("--"));
685
- const required = before.lastIndexOf("[") <= before.lastIndexOf("]")
686
- && !hasFlagAlternative;
687
- add(match[1], required);
688
- }
689
- for (const suffix of usage.split("[").slice(1)) {
690
- const candidate = suffix.split("]", 1)[0];
691
- if (candidate && /^[A-Za-z][A-Za-z0-9_-]*$/.test(candidate))
692
- add(candidate, false);
693
- }
694
- for (const group of topLevelParenGroups(usage)) {
695
- const candidate = group.split("|", 1)[0]?.trim();
696
- if (candidate && /^[A-Z][A-Z0-9_-]*$/.test(candidate))
697
- add(candidate, false);
698
- }
699
- return positionals;
700
- }
701
666
  const COMMAND_REQUIRED_INPUT_GROUPS = {
702
667
  "notebook.unload-jupyter": [
703
668
  {
@@ -856,102 +821,6 @@ function inferOutputShape(resource, action) {
856
821
  return "string";
857
822
  return "object";
858
823
  }
859
- export function inferInputContract(usage) {
860
- return {
861
- ...(usage.includes("--stdin") ? { stdin: true, } : {}),
862
- ...(usage.includes("--data ") || usage.includes("--data JSON") ? { dataFlag: true, } : {}),
863
- ...(usage.includes("--data-file") ? { dataFileFlag: true, } : {}),
864
- };
865
- }
866
- function stripOptionalUsageGroups(usage) {
867
- return usage.replace(/\[[^\]]*\]/g, " ");
868
- }
869
- function stripAllUsageGroups(usage) {
870
- return usage.replace(/\[[^\]]*\]/g, " ").replace(/\([^)]*\)/g, " ");
871
- }
872
- function topLevelParenGroups(usage) {
873
- const groups = [];
874
- let depth = 0;
875
- let current = "";
876
- for (const char of usage) {
877
- if (char === "(") {
878
- if (depth > 0)
879
- current += char;
880
- else
881
- current = "";
882
- depth++;
883
- }
884
- else if (char === ")") {
885
- depth--;
886
- if (depth === 0)
887
- groups.push(current);
888
- else
889
- current += char;
890
- }
891
- else if (depth > 0) {
892
- current += char;
893
- }
894
- }
895
- return groups;
896
- }
897
- function splitTopLevelChoices(group) {
898
- const parts = [];
899
- let depth = 0;
900
- let current = "";
901
- for (let index = 0; index < group.length; index++) {
902
- const char = group[index];
903
- if (char === "[" || char === "(")
904
- depth++;
905
- else if (char === "]" || char === ")")
906
- depth--;
907
- const nextAlternative = group.slice(index + 1).trimStart();
908
- if (char === "|" && depth === 0 && nextAlternative.startsWith("--")) {
909
- parts.push(current);
910
- current = "";
911
- }
912
- else {
913
- current += char;
914
- }
915
- }
916
- parts.push(current);
917
- return parts;
918
- }
919
- function flagsInUsageFragment(fragment) {
920
- return extractUsageFlags(fragment.replace(/\[[^\]]*\]/g, " "));
921
- }
922
- /**
923
- * Split required usage flags into unconditional flags and required choice
924
- * groups. A required `(--a X | --b Y)` group becomes a requiredOneOf entry
925
- * (at least one alternative; an alternative listing several flags must be
926
- * supplied together) instead of marking every flag as unconditionally required.
927
- */
928
- function deriveRequiredUsage(usage) {
929
- const requiredFlags = extractUsageFlags(stripAllUsageGroups(usage));
930
- const requiredOneOf = [];
931
- for (const group of topLevelParenGroups(usage)) {
932
- const alternatives = splitTopLevelChoices(group);
933
- if (alternatives.length <= 1) {
934
- requiredFlags.push(...flagsInUsageFragment(group));
935
- continue;
936
- }
937
- const oneOf = alternatives
938
- .map((alternative) => flagsInUsageFragment(alternative))
939
- .filter((alternativeFlags) => alternativeFlags.length > 0);
940
- // A group mixing a positional alternative with flags (e.g. sql query's
941
- // `(SQL | --sql QUERY | --sql-file PATH | --sql - | --stdin)`) cannot be
942
- // expressed as a flag-only choice: filtering out the flagless alternative
943
- // would falsely require one of the flags. Omit the group from the flag
944
- // contract entirely; the handler stays the runtime authority.
945
- if (alternatives.some((alternative) => flagsInUsageFragment(alternative).length === 0)) {
946
- continue;
947
- }
948
- if (oneOf.length > 1)
949
- requiredOneOf.push({ oneOf, });
950
- else if (oneOf.length === 1)
951
- requiredFlags.push(...oneOf[0]);
952
- }
953
- return { requiredFlags: uniqueStrings(requiredFlags), requiredOneOf, };
954
- }
955
824
  const GLOBAL_FLAG_VALUE_HINTS = {
956
825
  url: { valueType: "URL", },
957
826
  fields: { valueType: "CSV", },
@@ -967,22 +836,6 @@ const GLOBAL_FLAG_VALUE_HINTS = {
967
836
  standard: { valueType: "JSON", },
968
837
  until: { valueType: "STATE", },
969
838
  };
970
- /** Derive a value placeholder (and enum members) for each value flag from its usage token. */
971
- function extractFlagValueHints(usage) {
972
- const hints = new Map();
973
- for (const match of usage.matchAll(/--([a-z0-9-]+)\s+([A-Za-z][A-Za-z0-9_-]*(?:\|[A-Za-z][A-Za-z0-9_-]*)+)(?![A-Za-z0-9_=-])/g)) {
974
- const flag = FLAG_ALIASES[match[1]] ?? match[1];
975
- if (!hints.has(flag)) {
976
- hints.set(flag, { valueType: "enum", enumValues: match[2].split("|"), });
977
- }
978
- }
979
- for (const match of usage.matchAll(/--([a-z0-9-]+)\s+(<[^>]+>|[A-Z][A-Za-z0-9_]*)/g)) {
980
- const flag = FLAG_ALIASES[match[1]] ?? match[1];
981
- if (!hints.has(flag))
982
- hints.set(flag, { valueType: match[2], });
983
- }
984
- return hints;
985
- }
986
839
  function inferPayloadSchema(inputContract) {
987
840
  if (!inputContract.stdin && !inputContract.dataFlag && !inputContract.dataFileFlag) {
988
841
  return undefined;
@@ -1036,11 +889,11 @@ function cleanupCommandFromDeleteUsage(resource, action) {
1036
889
  : action === "create-instance" || action === "create-successor-instance"
1037
890
  ? "delete-instance"
1038
891
  : "delete";
1039
- const deleteUsage = commands[resource]?.[deleteAction]?.usage;
1040
- if (!deleteUsage)
892
+ const deleteSyntax = commandSyntaxTree(resource, deleteAction);
893
+ if (!deleteSyntax)
1041
894
  return undefined;
1042
- const base = stripOptionalUsageGroups(deleteUsage).replace(/\s+/g, " ").trim();
1043
- if (deleteUsage.includes("--if-exists"))
895
+ const base = renderNodes(deleteSyntax, true);
896
+ if (syntaxHasFlag(deleteSyntax, "if-exists"))
1044
897
  return `${base} --if-exists`;
1045
898
  return base;
1046
899
  }
@@ -1123,14 +976,15 @@ function inferAsyncKind(resource, action) {
1123
976
  }
1124
977
  return "none";
1125
978
  }
1126
- function inferIdempotency(resource, sideEffect, action, usage) {
979
+ function inferIdempotency(resource, sideEffect, action, syntax) {
1127
980
  if (sideEffect === "read")
1128
981
  return "safe";
1129
982
  if (`${resource}.${action}` === "install-skill.run")
1130
983
  return "convergent";
1131
- if (action.startsWith("create") && usage.includes("--if-not-exists"))
984
+ if (action.startsWith("create") && syntaxHasFlag(syntax, "if-not-exists")) {
1132
985
  return "if-not-exists";
1133
- if (action.startsWith("delete") && usage.includes("--if-exists"))
986
+ }
987
+ if (action.startsWith("delete") && syntaxHasFlag(syntax, "if-exists"))
1134
988
  return "if-exists";
1135
989
  if (`${resource}.${action}` === "app.set-manifest-version")
1136
990
  return "none";
@@ -1182,10 +1036,10 @@ export function inferCleanupHint(resource, action) {
1182
1036
  : action === "create-instance" || action === "create-successor-instance"
1183
1037
  ? "delete-instance"
1184
1038
  : "delete";
1185
- const deleteUsage = commands[resource]?.[deleteAction]?.usage;
1186
- if (!deleteUsage)
1039
+ const deleteSyntax = commandSyntaxTree(resource, deleteAction);
1040
+ if (!deleteSyntax)
1187
1041
  return undefined;
1188
- const ifExists = deleteUsage.includes("--if-exists") ? " --if-exists" : "";
1042
+ const ifExists = syntaxHasFlag(deleteSyntax, "if-exists") ? " --if-exists" : "";
1189
1043
  if (resource === "code-env") {
1190
1044
  return `Delete with \`dss code-env delete <lang> <name>${ifExists}\`.`;
1191
1045
  }
@@ -1196,14 +1050,16 @@ export function inferCleanupHint(resource, action) {
1196
1050
  }
1197
1051
  function buildRegistryEntry(resource, action, meta) {
1198
1052
  const requiresAuth = meta.localHandler === undefined && inferRequiresAuth(resource);
1199
- const requiresProject = inferRequiresProject(resource, action, meta.usage);
1053
+ const requiresProject = inferRequiresProject(resource, action);
1200
1054
  const sideEffect = inferSideEffect(resource, action);
1201
1055
  const destructive = inferDestructiveLevel(resource, sideEffect, action);
1202
1056
  const asyncKind = inferAsyncKind(resource, action);
1203
1057
  const mutatesDss = sideEffect === "write" && resource !== "auth" && resource !== "install-skill";
1204
- const supportsPlan = mutatesDss || sideEffect === "write";
1058
+ // auth login writes local credentials only; its --plan previews that write.
1059
+ const supportsPlan = sideEffect === "write" || resource === "auth";
1205
1060
  const supportsCleanup = supportsCleanupLedger(resource, action);
1206
- const usageFlags = extractUsageFlags(meta.usage);
1061
+ const syntax = commandSyntax(resource, action);
1062
+ const usageFlags = syntax.flags;
1207
1063
  const flags = uniqueStrings([
1208
1064
  ...usageFlags,
1209
1065
  ...(supportsPlan ? ["plan",] : []),
@@ -1212,7 +1068,7 @@ function buildRegistryEntry(resource, action, meta) {
1212
1068
  ...(requiresAuth ? AUTHENTICATED_AGENT_FLAGS : []),
1213
1069
  ...(requiresProject ? ["project-key",] : []),
1214
1070
  ]);
1215
- const derivedRequired = deriveRequiredUsage(meta.usage);
1071
+ const derivedRequired = syntax;
1216
1072
  const requiredFlags = meta.requiredFlags
1217
1073
  ?? EXPLICIT_REGISTRY_OVERRIDES[registryKey(resource, action)]?.requiredFlags
1218
1074
  ?? derivedRequired.requiredFlags;
@@ -1223,8 +1079,8 @@ function buildRegistryEntry(resource, action, meta) {
1223
1079
  const optionalFlags = meta.optionalFlags
1224
1080
  ?? EXPLICIT_REGISTRY_OVERRIDES[registryKey(resource, action)]?.optionalFlags
1225
1081
  ?? flags.filter((flag) => !requiredFlags.includes(flag) && !oneOfFlags.has(flag));
1226
- const valueHints = extractFlagValueHints(meta.usage);
1227
- const inputContract = inferInputContract(meta.usage);
1082
+ const valueHints = syntax.valueHints;
1083
+ const inputContract = syntax.inputContract;
1228
1084
  const cleanupHint = inferCleanupHint(resource, action);
1229
1085
  const payloadSchema = meta.payloadSchema
1230
1086
  ?? EXPLICIT_REGISTRY_OVERRIDES[registryKey(resource, action)]?.payloadSchema
@@ -1238,15 +1094,13 @@ function buildRegistryEntry(resource, action, meta) {
1238
1094
  ? undefined
1239
1095
  : inferredCleanupCommand;
1240
1096
  const flagMetadata = flags.map((name) => {
1241
- const aliases = Object.entries(FLAG_ALIASES)
1242
- .filter(([raw, canonical,]) => canonical === name && new RegExp(`--${raw}(?![a-z0-9-])`).test(meta.usage))
1243
- .map(([raw,]) => raw);
1097
+ const aliases = syntax.aliases[name] ?? [];
1244
1098
  const aliasPart = aliases.length > 0 ? { aliases, } : {};
1245
1099
  const kind = flagKind(name);
1246
1100
  if (kind === "boolean")
1247
1101
  return { name, kind, ...aliasPart, };
1248
1102
  const allowEmptyPart = ALLOW_EMPTY_VALUE_FLAGS[name] === true ? { allowEmptyValue: true, } : {};
1249
- const hint = valueHints.get(name) ?? GLOBAL_FLAG_VALUE_HINTS[name];
1103
+ const hint = valueHints[name] ?? GLOBAL_FLAG_VALUE_HINTS[name];
1250
1104
  if (!hint)
1251
1105
  return { name, kind, ...aliasPart, ...allowEmptyPart, };
1252
1106
  return {
@@ -1258,12 +1112,11 @@ function buildRegistryEntry(resource, action, meta) {
1258
1112
  ...allowEmptyPart,
1259
1113
  };
1260
1114
  });
1261
- const positionalArguments = extractPositionalArguments(meta.usage);
1115
+ const positionalArguments = syntax.positionalArguments;
1262
1116
  const positionals = positionalArguments.map((positional) => positional.name);
1263
1117
  const inputGroups = requiredInputGroups(resource, action);
1264
1118
  const outputShape = inferOutputShape(resource, action);
1265
- const producesLocalFile = meta.usage.includes("--output PATH")
1266
- || meta.usage.includes("--output-file PATH");
1119
+ const producesLocalFile = syntax.producesLocalFile;
1267
1120
  const uniqueRequiredFlags = uniqueStrings(requiredFlags);
1268
1121
  const uniqueOptionalFlags = uniqueStrings(optionalFlags);
1269
1122
  const unsafe = unsafeOutputs(resource, action, producesLocalFile);
@@ -1287,8 +1140,8 @@ function buildRegistryEntry(resource, action, meta) {
1287
1140
  producesLocalFile,
1288
1141
  mutatesDss,
1289
1142
  async: asyncKind,
1290
- idempotency: inferIdempotency(resource, sideEffect, action, meta.usage),
1291
- dryRun: meta.usage.includes("--dry-run"),
1143
+ idempotency: inferIdempotency(resource, sideEffect, action, commandSyntaxTree(resource, action) ?? []),
1144
+ dryRun: syntax.dryRun,
1292
1145
  requiredFlags: uniqueRequiredFlags,
1293
1146
  optionalFlags: uniqueOptionalFlags,
1294
1147
  ...(requiredOneOf.length > 0 ? { requiredOneOf, } : {}),
@@ -1389,7 +1242,7 @@ function commandDefinitions(resourceFilter) {
1389
1242
  for (const [action, meta,] of Object.entries(AUTH_ACTIONS)) {
1390
1243
  registry.auth[action] = {
1391
1244
  handler: async () => undefined,
1392
- usage: meta.usage,
1245
+ usage: commandUsage("auth", action),
1393
1246
  description: meta.description,
1394
1247
  examples: meta.examples,
1395
1248
  requiredFlags: meta.requiredFlags,
@@ -1656,9 +1509,7 @@ function planProjectKeyFromArgs(flags) {
1656
1509
  const fromFlag = flags["project-key"];
1657
1510
  if (typeof fromFlag === "string" && fromFlag.trim().length > 0)
1658
1511
  return fromFlag.trim();
1659
- if (!dataikuEnvironmentEnabled())
1660
- return undefined;
1661
- const fromEnv = process.env.DATAIKU_PROJECT_KEY;
1512
+ const fromEnv = ambientProjectKey();
1662
1513
  return fromEnv !== undefined && fromEnv.trim().length > 0 ? fromEnv.trim() : undefined;
1663
1514
  }
1664
1515
  /**
@@ -1710,11 +1561,6 @@ function optionalJsonFlag(flags, name) {
1710
1561
  function requiredPlanJsonInput(flags, usage) {
1711
1562
  return requiredJsonInput(flags, `--data, --data-file, or --stdin is required. Usage: ${usage}`);
1712
1563
  }
1713
- function requiredPlanPositionals(usage) {
1714
- return extractPositionalArguments(usage)
1715
- .filter((positional) => positional.required)
1716
- .map((positional) => positional.name);
1717
- }
1718
1564
  function dataQualityEndpoint(projectKey, datasetName, suffix) {
1719
1565
  return encodedProjectEndpointForPlan(projectKey, `/datasets/${encodeURIComponent(datasetName)}/data-quality${suffix}`);
1720
1566
  }
@@ -1797,15 +1643,12 @@ function encodePluginSegmentsForPlan(contentPath) {
1797
1643
  */
1798
1644
  function pluginGitPlanPayload(flags) {
1799
1645
  const repository = requiredPlanFlag(flags, "repository", "dss plugin install-from-git --repository URL");
1800
- const payload = {
1646
+ // Mirrors PluginsResource install/update bodies: absent options are sent as null.
1647
+ return {
1801
1648
  gitRepositoryUrl: validatedPlanRepositoryUrl(repository, "repository", "--repository URL"),
1649
+ gitCheckout: typeof flags["checkout"] === "string" ? flags["checkout"] : null,
1650
+ gitSubpath: typeof flags["path-in-repository"] === "string" ? flags["path-in-repository"] : null,
1802
1651
  };
1803
- if (typeof flags["checkout"] === "string")
1804
- payload.gitCheckout = flags["checkout"];
1805
- if (typeof flags["path-in-repository"] === "string") {
1806
- payload.gitSubpath = flags["path-in-repository"];
1807
- }
1808
- return payload;
1809
1652
  }
1810
1653
  function optionalPlanProjectScope(flags) {
1811
1654
  const projectKey = flags["project-key"];
@@ -1938,16 +1781,28 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
1938
1781
  };
1939
1782
  case "wiki.create": {
1940
1783
  const name = requiredPlanFlag(flags, "name", entry.usage);
1941
- return {
1784
+ const content = textInput(flags);
1785
+ const create = {
1942
1786
  method: "POST",
1943
1787
  endpoint: projectEndpoint("/wiki/"),
1788
+ payload: { projectKey, name, parent: flags["parent"] ?? null, },
1789
+ };
1790
+ return {
1791
+ ...create,
1944
1792
  identifiers: { name, },
1945
- payload: {
1946
- projectKey,
1947
- name,
1948
- parent: flags["parent"] ?? null,
1949
- content: textInput(flags),
1950
- },
1793
+ // Content is not accepted by the create endpoint; it lands in a follow-up update.
1794
+ ...(content === undefined ? {} : {
1795
+ requests: [
1796
+ { sequence: 1, ...create, },
1797
+ { sequence: 2, method: "GET", endpoint: projectEndpoint("/wiki/{createdArticleId}"), },
1798
+ {
1799
+ sequence: 3,
1800
+ method: "PUT",
1801
+ endpoint: projectEndpoint("/wiki/{createdArticleId}"),
1802
+ payload: { payload: content, },
1803
+ },
1804
+ ],
1805
+ }),
1951
1806
  };
1952
1807
  }
1953
1808
  case "wiki.update":
@@ -1973,7 +1828,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
1973
1828
  const dataName = data?.["name"];
1974
1829
  const name = flagName ?? (typeof dataName === "string" ? dataName : undefined);
1975
1830
  if (!name) {
1976
- throw new UsageError("--name or dashboard settings containing a string name are required. Usage: dss dashboard create --name NAME");
1831
+ throw new UsageError(`--name or dashboard settings containing a string name are required. Usage: ${commandUsage("dashboard", "create")}`);
1977
1832
  }
1978
1833
  const listed = parseBooleanOption(flags["listed"], "--listed");
1979
1834
  const payload = { ...(data ?? { pages: [], }), name, };
@@ -2011,7 +1866,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2011
1866
  const name = flags["name"];
2012
1867
  const type = flags["type"];
2013
1868
  if (!data && (!name || !type)) {
2014
- throw new UsageError("--data or both --name and --type are required. Usage: dss insight create --name NAME --type TYPE");
1869
+ throw new UsageError(`--data or both --name and --type are required. Usage: ${commandUsage("insight", "create")}`);
2015
1870
  }
2016
1871
  const prototype = { ...data, };
2017
1872
  if (name !== undefined)
@@ -2095,7 +1950,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2095
1950
  };
2096
1951
  case "flow-zone.create": {
2097
1952
  const name = flowZoneName(flags["name"]);
2098
- const payload = { name, color: flowZoneColor(flags["color"]), projectKey, };
1953
+ const payload = { name, color: flowZoneColor(flags["color"]) ?? "#2ab1ac", };
2099
1954
  return {
2100
1955
  method: "POST",
2101
1956
  endpoint: projectEndpoint("/flow/zones"),
@@ -2134,17 +1989,25 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2134
1989
  if (!connection && dsType.toLowerCase() !== "uploadedfiles") {
2135
1990
  throw new UsageError("--connection is required unless --type is UploadedFiles.");
2136
1991
  }
1992
+ const endpoint = projectEndpoint("/datasets/"); // throws without a project key
2137
1993
  return {
2138
1994
  method: "POST",
2139
- endpoint: projectEndpoint("/datasets/"),
1995
+ endpoint,
2140
1996
  identifiers: { name, },
2141
- payload: { datasetName: name, connection, dsType, projectKey, },
1997
+ payload: buildDatasetCreateBody({
1998
+ projectKey: projectKey,
1999
+ datasetName: name,
2000
+ connection,
2001
+ dsType,
2002
+ }),
2142
2003
  };
2143
2004
  }
2144
2005
  case "dataset.clone": {
2145
2006
  const source = args[0];
2146
2007
  const target = args[1];
2147
2008
  return {
2009
+ exact: false,
2010
+ reason: "Apply GETs the source dataset and copies its current connection, format, and schema into the new dataset body.",
2148
2011
  method: "POST",
2149
2012
  endpoint: projectEndpoint("/datasets/"),
2150
2013
  identifiers: { source, target, },
@@ -2306,7 +2169,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2306
2169
  identifiers: { name: id, },
2307
2170
  };
2308
2171
  case "recipe.create": {
2309
- const type = requiredPlanFlag(flags, "type", entry.usage);
2172
+ requiredPlanFlag(flags, "type", entry.usage);
2310
2173
  const outputDataset = flags["output"];
2311
2174
  const outputFolder = flags["output-folder"];
2312
2175
  if (outputDataset && outputFolder) {
@@ -2318,19 +2181,14 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2318
2181
  if (outputFolder && !flags["output-connection"]) {
2319
2182
  throw new UsageError("--output-connection is required when using --output-folder.");
2320
2183
  }
2184
+ const endpoint = projectEndpoint("/recipes/"); // throws without a project key
2185
+ // Same flag mapping and body construction as the handler and RecipesResource.create.
2186
+ const { recipePrototype, creationSettings, } = buildRecipeCreateRequest(recipeCreateOptionsFromFlags(flags), projectKey);
2321
2187
  return {
2322
2188
  method: "POST",
2323
- endpoint: projectEndpoint("/recipes/"),
2324
- identifiers: { name: flags["name"], },
2325
- payload: {
2326
- type,
2327
- name: flags["name"],
2328
- inputDatasets: recipeInputDatasetsFromFlags(flags),
2329
- outputDataset,
2330
- outputFolder,
2331
- outputConnection: flags["output-connection"],
2332
- projectKey,
2333
- },
2189
+ endpoint,
2190
+ identifiers: { name: recipePrototype.name, },
2191
+ payload: { recipePrototype, creationSettings, },
2334
2192
  };
2335
2193
  }
2336
2194
  case "recipe.run":
@@ -2456,22 +2314,54 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2456
2314
  if (activeRaw !== "true" && activeRaw !== "false") {
2457
2315
  throw new UsageError(`Usage: ${entry.usage}`);
2458
2316
  }
2317
+ const lightEndpoint = projectEndpoint(`/scenarios/${encodeURIComponent(id)}/light`);
2318
+ const active = activeRaw === "true";
2319
+ // DSS parses the light PUT as a full Scenario, so the command echoes the
2320
+ // current light status with `active` overridden (see ScenariosResource.setActive).
2459
2321
  return {
2460
2322
  method: "PUT",
2461
- endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/light`),
2323
+ endpoint: lightEndpoint,
2462
2324
  identifiers: { id, },
2463
- payload: { id, active: activeRaw === "true", },
2325
+ payload: { active, },
2326
+ requests: [
2327
+ { sequence: 1, method: "GET", endpoint: lightEndpoint, },
2328
+ {
2329
+ sequence: 2,
2330
+ method: "PUT",
2331
+ endpoint: lightEndpoint,
2332
+ payload: { "...current": true, active, },
2333
+ },
2334
+ { sequence: 3, method: "GET", endpoint: lightEndpoint, },
2335
+ ],
2464
2336
  };
2465
2337
  }
2466
2338
  case "folder.create": {
2467
2339
  const name = requiredPlanFlag(flags, "name", entry.usage);
2468
2340
  const type = flags["type"];
2469
2341
  const connection = flags["connection"];
2342
+ const pathFlag = flags["path"];
2343
+ const body = {
2344
+ name,
2345
+ projectKey,
2346
+ type: type ?? null,
2347
+ params: { connection, path: pathFlag?.trim() || "/${projectKey}/${odbId}", },
2348
+ };
2349
+ // Mirrors FoldersResource.create.
2350
+ if (connection === undefined) {
2351
+ return {
2352
+ exact: false,
2353
+ reason: "Without --connection, apply reads DSS admin settings to pick the managed-folder connection (falling back to filesystem_folders); params.connection is set then.",
2354
+ method: "POST",
2355
+ endpoint: projectEndpoint("/managedfolders/"),
2356
+ identifiers: { name, },
2357
+ payload: body,
2358
+ };
2359
+ }
2470
2360
  return {
2471
2361
  method: "POST",
2472
2362
  endpoint: projectEndpoint("/managedfolders/"),
2473
2363
  identifiers: { name, },
2474
- payload: { name, type, connection, path: flags["path"], projectKey, },
2364
+ payload: body,
2475
2365
  };
2476
2366
  }
2477
2367
  case "job.abort":
@@ -2495,18 +2385,22 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2495
2385
  endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/`),
2496
2386
  identifiers: { id, },
2497
2387
  };
2498
- case "scenario.create":
2388
+ case "scenario.create": {
2389
+ const type = flags["type"] ?? "step_based";
2499
2390
  return {
2500
2391
  method: "POST",
2501
2392
  endpoint: projectEndpoint("/scenarios/"),
2502
2393
  identifiers: { id: args[0], name: args[1], },
2394
+ // Mirrors ScenariosResource.create's default params for step-based scenarios.
2503
2395
  payload: {
2504
2396
  id: args[0],
2505
2397
  name: args[1],
2506
2398
  projectKey,
2507
- type: flags["type"] ?? "step_based",
2399
+ type,
2400
+ params: type === "step_based" ? { steps: [], triggers: [], reporters: [], } : {},
2508
2401
  },
2509
2402
  };
2403
+ }
2510
2404
  case "scenario.update":
2511
2405
  return {
2512
2406
  method: "PUT",
@@ -2541,6 +2435,8 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2541
2435
  };
2542
2436
  case "variable.set":
2543
2437
  return {
2438
+ exact: false,
2439
+ reason: "The payload lists the requested changes; apply PUTs the full {standard, local} object (merged with the current variables unless --replace).",
2544
2440
  method: "PUT",
2545
2441
  endpoint: projectEndpoint("/variables/"),
2546
2442
  payload: {
@@ -2968,6 +2864,8 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2968
2864
  };
2969
2865
  case "saved-model.import-mlflow-version-from-folder":
2970
2866
  return {
2867
+ exact: false,
2868
+ reason: "Apply sends these values as query parameters (with codeEnvName and binaryClassificationThreshold defaults) on an empty multipart body; the payload shows them structured.",
2971
2869
  method: "POST",
2972
2870
  endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}`),
2973
2871
  identifiers: { savedModelId: id, versionId: args[1], },
@@ -3018,7 +2916,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3018
2916
  }
3019
2917
  return {
3020
2918
  method: "POST",
3021
- endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}/external-ml/actions/evaluate`),
2919
+ endpoint: `${projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}/external-ml/actions/evaluate`)}?useOptimalThreshold=${parseBooleanOption(flags["use-optimal-threshold"], "--use-optimal-threshold") ?? true}&skipExpensiveReports=${parseBooleanOption(flags["skip-expensive-reports"], "--skip-expensive-reports") ?? true}`,
3022
2920
  identifiers: { savedModelId: id, versionId: args[1], },
3023
2921
  payload,
3024
2922
  };
@@ -3217,17 +3115,19 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3217
3115
  };
3218
3116
  case "plugin.create-dev": {
3219
3117
  const creationMode = requiredPlanFlag(flags, "creation-mode", entry.usage);
3118
+ const needsGit = creationMode !== "EMPTY";
3119
+ // Mirrors PluginsResource.createDev: git fields are always present, null when unused.
3220
3120
  const payload = {
3221
3121
  pluginId: id,
3222
3122
  creationMode,
3123
+ gitRepository: needsGit && typeof flags["repository"] === "string"
3124
+ ? validatedPlanRepositoryUrl(flags["repository"], "repository", "--repository URL")
3125
+ : null,
3126
+ gitCheckout: needsGit && typeof flags["checkout"] === "string" ? flags["checkout"] : null,
3127
+ gitSubpath: creationMode === "GIT_EXPORT" && typeof flags["path-in-repository"] === "string"
3128
+ ? flags["path-in-repository"]
3129
+ : null,
3223
3130
  };
3224
- if (typeof flags["repository"] === "string")
3225
- payload.gitRepository = flags["repository"];
3226
- if (typeof flags["checkout"] === "string")
3227
- payload.gitCheckout = flags["checkout"];
3228
- if (typeof flags["path-in-repository"] === "string") {
3229
- payload.gitSubpath = flags["path-in-repository"];
3230
- }
3231
3131
  return {
3232
3132
  method: "POST",
3233
3133
  endpoint: "/public/api/plugins/actions/createDev",
@@ -3288,14 +3188,13 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3288
3188
  identifiers: { pluginId: id, path: args[1], },
3289
3189
  };
3290
3190
  case "plugin.rename": {
3291
- const newName = requiredPlanPositionals(entry.usage)[2];
3292
- if (!newName)
3191
+ if (!args[2])
3293
3192
  throw new UsageError(`Usage: ${entry.usage}`);
3294
3193
  return {
3295
3194
  method: "POST",
3296
3195
  endpoint: `${pluginRootEndpoint(id, "/contents-actions/rename")}`,
3297
3196
  identifiers: { pluginId: id, path: args[1], },
3298
- payload: { oldPath: args[1], newName: args[2], },
3197
+ payload: { oldPath: `/${validatePluginPath(args[1] ?? "")}`, newName: args[2], },
3299
3198
  };
3300
3199
  }
3301
3200
  case "plugin.move": {
@@ -3306,7 +3205,10 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3306
3205
  method: "POST",
3307
3206
  endpoint: `${pluginRootEndpoint(id, "/contents-actions/move")}`,
3308
3207
  identifiers: { pluginId: id, path: args[1], },
3309
- payload: { oldPath: args[1], newPath: destination, },
3208
+ payload: {
3209
+ oldPath: `/${validatePluginPath(args[1] ?? "")}`,
3210
+ newPath: validatePluginDestinationPath(destination),
3211
+ },
3310
3212
  };
3311
3213
  }
3312
3214
  case "business-app.save-settings":
@@ -3356,6 +3258,8 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3356
3258
  // plan carries no preflight requests and no fake concrete GET
3357
3259
  // path: `{targetProjectKey}` resolves at apply time.
3358
3260
  return {
3261
+ exact: false,
3262
+ reason: "targetProjectKey (and targetProjectName when omitted) are generated during apply; the sent body adds them.",
3359
3263
  method: "POST",
3360
3264
  endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
3361
3265
  identifiers: {
@@ -4037,7 +3941,6 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
4037
3941
  evaluateProjectStandardsChecks,
4038
3942
  }),
4039
3943
  identifiers: { bundleId: id, },
4040
- payload: {},
4041
3944
  };
4042
3945
  }
4043
3946
  case "bundle.publish":
@@ -4255,7 +4158,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
4255
4158
  case "code-env.set-jupyter": {
4256
4159
  const active = parseBooleanOption(flags["active"], "--active");
4257
4160
  if (active === undefined) {
4258
- throw new UsageError("--active is required. Usage: dss code-env set-jupyter <lang> <name> --active true|false");
4161
+ throw new UsageError(`--active is required. Usage: ${commandUsage("code-env", "set-jupyter")}`);
4259
4162
  }
4260
4163
  const wait = codeEnvWait(flags);
4261
4164
  return {
@@ -4890,7 +4793,7 @@ export function buildMutationPlan(resource, action, meta, args, flags) {
4890
4793
  if (!entry.mutatesDss && entry.sideEffect !== "write") {
4891
4794
  throw new UsageError(`--plan is only supported for mutating commands. Usage: ${meta.usage}`);
4892
4795
  }
4893
- const requiredPositionals = requiredPlanPositionals(meta.usage);
4796
+ const requiredPositionals = entry.positionalArguments.filter((positional) => positional.required);
4894
4797
  requireArgs(args, requiredPositionals.length, meta.usage);
4895
4798
  const projectKey = projectKeyForPlan(entry, flags);
4896
4799
  const shape = commandPlanShape(resource, action, args, flags, entry, projectKey);
@@ -4914,7 +4817,7 @@ export const BATCH_PLAN_EXIT_CODES = {
4914
4817
  longRunningFailure: 4,
4915
4818
  assertionFailure: 4,
4916
4819
  };
4917
- export const BATCH_USAGE = "dss batch (--data JSON|--data-file PATH|--stdin) [--continue-on-error] [--dry-run]";
4820
+ export const BATCH_USAGE = commandUsage("batch", "run");
4918
4821
  const BATCH_DESCRIPTION = "Run dss argv arrays fail-fast by default. The result aggregates failed/retryable/failureCodes; process exit remains the first failed step's exit code.";
4919
4822
  export const BATCH_HINT = 'Pass a JSON array of argv arrays, e.g. [["dataset","list"],["recipe","update","r","--data-file","p.json"]].';
4920
4823
  export const BATCH_EXAMPLE_PAYLOAD = [