dataiku-sdk 3.3.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 (126) hide show
  1. package/README.md +6 -4
  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 +5 -5
  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 +28 -51
  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 +17 -25
  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 +11 -18
  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 +39 -71
  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 +21 -35
  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 +179 -461
  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/output-schemas.d.ts +24 -0
  61. package/dist/src/cli/output-schemas.js +233 -0
  62. package/dist/src/cli/syntax.d.ts +83 -0
  63. package/dist/src/cli/syntax.js +217 -0
  64. package/dist/src/cli/types.d.ts +3 -0
  65. package/dist/src/cli/usage.d.ts +1 -1
  66. package/dist/src/cli/usage.js +4 -2
  67. package/dist/src/cli/version.js +2 -0
  68. package/dist/src/cli.js +37 -64
  69. package/dist/src/client.d.ts +10 -1
  70. package/dist/src/client.js +27 -11
  71. package/dist/src/errors.d.ts +12 -1
  72. package/dist/src/errors.js +34 -7
  73. package/dist/src/generated/action-output-schemas.json +1735 -0
  74. package/dist/src/index.d.ts +1 -3
  75. package/dist/src/index.js +2 -2
  76. package/dist/src/resources/api-services.js +2 -3
  77. package/dist/src/resources/applications.js +2 -4
  78. package/dist/src/resources/code-envs.js +2 -2
  79. package/dist/src/resources/data-quality.js +4 -3
  80. package/dist/src/resources/datasets.d.ts +13 -0
  81. package/dist/src/resources/datasets.js +42 -8
  82. package/dist/src/resources/flow-zones.d.ts +6 -2
  83. package/dist/src/resources/flow-zones.js +10 -5
  84. package/dist/src/resources/folders.js +4 -2
  85. package/dist/src/resources/futures.d.ts +2 -0
  86. package/dist/src/resources/futures.js +39 -27
  87. package/dist/src/resources/insights.js +3 -2
  88. package/dist/src/resources/jobs.js +41 -31
  89. package/dist/src/resources/macros.js +2 -4
  90. package/dist/src/resources/ml-tasks.js +28 -19
  91. package/dist/src/resources/notebooks.js +2 -3
  92. package/dist/src/resources/plugins.d.ts +0 -2
  93. package/dist/src/resources/plugins.js +0 -3
  94. package/dist/src/resources/project-git.d.ts +6 -12
  95. package/dist/src/resources/project-git.js +31 -58
  96. package/dist/src/resources/project-library.d.ts +0 -2
  97. package/dist/src/resources/project-library.js +2 -7
  98. package/dist/src/resources/projects.d.ts +4 -2
  99. package/dist/src/resources/projects.js +37 -36
  100. package/dist/src/resources/recipes.d.ts +28 -0
  101. package/dist/src/resources/recipes.js +116 -84
  102. package/dist/src/resources/sql.js +8 -3
  103. package/dist/src/resources/statistics.js +2 -1
  104. package/dist/src/resources/variables.js +2 -1
  105. package/dist/src/resources/webapps.js +2 -3
  106. package/dist/src/schemas.d.ts +1 -2
  107. package/dist/src/schemas.js +1 -1
  108. package/dist/src/skill.js +6 -7
  109. package/dist/src/utils/cleanup-ledger.js +9 -8
  110. package/dist/src/utils/git-reference.d.ts +12 -0
  111. package/dist/src/utils/git-reference.js +38 -0
  112. package/dist/src/utils/polling.d.ts +3 -0
  113. package/dist/src/utils/polling.js +3 -0
  114. package/dist/src/utils/project-archive.js +2 -2
  115. package/dist/src/utils/stable-hash.d.ts +3 -1
  116. package/dist/src/utils/stable-hash.js +2 -0
  117. package/package.json +8 -8
  118. package/plugin.json +1 -1
  119. package/skills/dataiku-dss/SKILL.md +3 -2
  120. package/skills/dataiku-dss/references/app-releases.md +2 -3
  121. package/skills/dataiku-dss/references/authentication.md +7 -7
  122. package/skills/dataiku-dss/references/coding.md +1 -1
  123. package/skills/dataiku-dss/references/mutations.md +1 -1
  124. package/skills/dataiku-dss/references/troubleshooting.md +2 -2
  125. package/dist/src/utils/pagination.d.ts +0 -8
  126. package/dist/src/utils/pagination.js +0 -23
@@ -3,21 +3,27 @@ import * as path from "node:path";
3
3
  import { validateCredentials, } from "../auth.js";
4
4
  import { getCredentialsPath, saveCredentials, } from "../config.js";
5
5
  import { DataikuError, } from "../errors.js";
6
+ import actionOutputSchemasMetadata from "../generated/action-output-schemas.json" with { type: "json"
7
+ };
6
8
  import { APP_MANIFEST_CONCURRENCY_CONTROL, } from "../resources/applications.js";
7
- import { encodeGitReferencePath, validateGitReferencePath, } from "../resources/project-git.js";
9
+ import { buildDatasetCreateBody, } from "../resources/datasets.js";
10
+ import { validatePluginDestinationPath, validatePluginPath, } from "../resources/plugins.js";
8
11
  import { encodeLibraryPath, PROJECT_LIBRARY_CONCURRENCY_CONTROL, validateLibraryDestinationPath, validateLibraryName, validateLibraryPath, } from "../resources/project-library.js";
9
- import { CodeEnvDetailsSchema, CodeEnvLogSummaryArraySchema, CodeEnvSummaryArraySchema, CodeEnvUsageArraySchema, CodeEnvVersionForProjectSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, FlowZoneArraySchema, FlowZoneSchema, JobSummaryArraySchema, JobWaitResultSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, NotebookSessionArraySchema, ProjectDetailsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, RecipeSummaryArraySchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioSummaryArraySchema, SqlNotebookContentSchema, SqlNotebookHistorySchema, SqlNotebookSummaryArraySchema, SqlQueryResponseSchema, } from "../schemas.js";
10
- import { jobBuildTargetTypeFromFlags, json, jsonInput, num, parseBooleanOption, parseJsonObject, recipeInputDatasetsFromFlags, requiredJsonInput, rewritePairsFromFlags, schemaColumnsInput, sha256Hex, stableHash, stringField, textInput, } from "./coerce.js";
12
+ import { buildRecipeCreateRequest, } from "../resources/recipes.js";
13
+ import { encodeGitReferencePath, validateGitReferencePath, } from "../utils/git-reference.js";
14
+ import { jobBuildTargetTypeFromFlags, json, jsonInput, num, parseBooleanOption, parseJsonObject, requiredJsonInput, rewritePairsFromFlags, schemaColumnsInput, sha256Hex, stableHash, stringField, textInput, } from "./coerce.js";
11
15
  import { parseCodeRunIntegerFlag, resolveCodeInputWithSource, } from "./commands/code.js";
12
16
  import { commands, } from "./commands/index.js";
13
17
  import { projectLibraryPutPayload, } from "./commands/project-library.js";
18
+ import { recipeCreateOptionsFromFlags, } from "./commands/recipe.js";
14
19
  import { resolveSqlQueryInvocation, } from "./commands/sql.js";
15
- import { dataikuEnvironmentEnabled, } from "./env.js";
16
- 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";
17
22
  import { flowZoneColor, flowZoneMoveItems, flowZoneName, } from "./helpers/flow-zone.js";
18
23
  import { recipeBackupPath, recipeRunShouldWait, } from "./helpers/recipe.js";
19
24
  import { encodedProjectEndpointForPlan, planResult, } from "./output.js";
20
25
  import { resolveLoginCredentials, } from "./runtime.js";
26
+ import { commandSyntax, commandSyntaxTree, commandUsage, renderNodes, syntaxCommandWords, syntaxHasFlag, } from "./syntax.js";
21
27
  import { inferRequiresProject, requireArgs, UsageError, } from "./usage.js";
22
28
  import { AGENT_CONTRACT_SCHEMA_ID, AGENT_CONTRACT_VERSION, cliVersionResult, JSON_SCHEMA_DRAFT, } from "./version.js";
23
29
  function codeEnvWait(flags) {
@@ -80,7 +86,6 @@ export const AUTH_ACTIONS = {
80
86
  saveCredentials({ url, apiKey, projectKey, ...tlsSettings, });
81
87
  return { saved: true, path: credentialsPath, };
82
88
  },
83
- usage: "dss auth login --url URL --api-key KEY [--project-key KEY] [--insecure] [--ca-cert PATH]",
84
89
  description: "Validate and save DSS credentials from flags or environment variables.",
85
90
  examples: [
86
91
  "dss auth login --url https://dss.example.com --api-key YOUR_KEY",
@@ -237,7 +242,7 @@ const AUTHENTICATED_AGENT_FLAGS = [
237
242
  "insecure",
238
243
  "ca-cert",
239
244
  ];
240
- export const COMMANDS_USAGE = "dss commands run [--fields PATHS] [--output PATH]";
245
+ export const COMMANDS_USAGE = commandUsage("commands", "run");
241
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.";
242
247
  const COMMANDS_EXAMPLES = [
243
248
  "dss commands run",
@@ -247,28 +252,28 @@ const COMMANDS_EXAMPLES = [
247
252
  "dss commands run --output commands.json",
248
253
  ];
249
254
  export const AGENT_CONTRACT_COMMAND = "dss agent contract";
250
- export const AGENT_CONTRACT_USAGE = "dss agent contract [--fields PATHS]";
255
+ export const AGENT_CONTRACT_USAGE = commandUsage("agent", "contract");
251
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.";
252
257
  const AGENT_CONTRACT_EXAMPLES = [
253
258
  "dss agent contract --fields protocol,agentContractVersion,cli,stdio,planning,compatibility",
254
259
  "dss agent contract --fields commands.actions",
255
260
  ];
256
- const VERSION_USAGE = "dss version";
261
+ const VERSION_USAGE = commandUsage("version", "run");
257
262
  const VERSION_DESCRIPTION = "Print the CLI version, checkout/build revisions, load source, runtime, and stale-build status as JSON.";
258
263
  const VERSION_EXAMPLES = ["dss version", "dss --version",];
259
- 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");
260
265
  const INSTALL_SKILL_DESCRIPTION = "Report missing/stale/current skill status and atomically install changed dataiku-dss agent skills.";
261
266
  const INSTALL_SKILL_EXAMPLES = [
262
267
  "dss install-skill --list-agents",
263
268
  "dss install-skill --agent omp --dry-run",
264
269
  ];
265
- export const CLEANUP_USAGE = "dss cleanup --file PATH [--dry-run|--apply] [--continue-on-error]";
270
+ export const CLEANUP_USAGE = commandUsage("cleanup", "run");
266
271
  const CLEANUP_DESCRIPTION = "Replay cleanup entries in reverse order; failures include structured code/category/exitCode/retryability summaries.";
267
272
  const CLEANUP_EXAMPLES = [
268
273
  "dss cleanup --file cleanup.jsonl",
269
274
  "dss cleanup --file cleanup.jsonl --apply",
270
275
  ];
271
- const FIXTURES_USAGE = "dss fixtures [--project-key KEY] [--allow-types CSV]";
276
+ const FIXTURES_USAGE = commandUsage("fixtures", "run");
272
277
  const FIXTURES_DESCRIPTION = "Discover safe live-test fixtures for agent workflows.";
273
278
  const FIXTURES_EXAMPLES = [
274
279
  "dss fixtures",
@@ -433,212 +438,18 @@ function structuredExamples(examples, examplePayload) {
433
438
  };
434
439
  });
435
440
  }
436
- const PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA = {
437
- type: "object",
438
- additionalProperties: false,
439
- required: ["name",],
440
- properties: {
441
- name: { type: "string", },
442
- path: { type: "string", },
443
- size: { type: "integer", minimum: 0, },
444
- mimeType: { type: "string", },
445
- hasData: { type: "boolean", },
446
- lastModified: { type: "number", },
447
- children: { type: "array", items: { type: "object", additionalProperties: true, }, },
448
- },
449
- };
450
- const NOTEBOOK_SAVE_OUTPUT_SCHEMA = {
451
- type: "object",
452
- additionalProperties: false,
453
- required: ["saved", "resource", "created", "hash",],
454
- properties: {
455
- saved: { type: "string", },
456
- resource: { enum: ["jupyter-notebook", "sql-notebook",], },
457
- created: { type: "boolean", },
458
- hash: { type: "string", pattern: "^[a-f0-9]{64}$", },
459
- },
460
- };
461
441
  /**
462
- * `notebook.save-sql` adds `requested`: on create DSS allocates the persisted
463
- * id (`saved`), while the requested handle only becomes the display name.
442
+ * Discovery output schemas: the generated declarative metadata committed at
443
+ * `src/generated/action-output-schemas.json`. Plain data only, so discovery
444
+ * (`dss commands run`, `dss agent contract`) never loads the TypeBox graph.
445
+ * The content matches `typeBoxCommandOutputSchemas()` exactly; parity and
446
+ * freshness are enforced by tests/cli/discovery-output-schemas.test.ts and
447
+ * `bun run check`.
464
448
  */
465
- const NOTEBOOK_SAVE_SQL_OUTPUT_SCHEMA = {
466
- ...NOTEBOOK_SAVE_OUTPUT_SCHEMA,
467
- required: ["saved", "requested", "resource", "created", "hash",],
468
- properties: {
469
- ...NOTEBOOK_SAVE_OUTPUT_SCHEMA["properties"],
470
- requested: { type: "string", },
471
- },
472
- };
473
- const COMMAND_OUTPUT_SCHEMAS = {
474
- "code-env.list": CodeEnvSummaryArraySchema,
475
- "code-env.get": CodeEnvDetailsSchema,
476
- "code-env.list-logs": CodeEnvLogSummaryArraySchema,
477
- "code-env.get-log": {
478
- oneOf: [
479
- {
480
- type: "object",
481
- required: ["log", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
482
- properties: {
483
- log: { type: "string", },
484
- bytes: { type: "integer", minimum: 0, },
485
- truncated: { type: "boolean", },
486
- tailed: { type: "boolean", },
487
- envLang: { enum: ["PYTHON", "R",], },
488
- envName: { type: "string", },
489
- logName: { type: "string", },
490
- },
491
- },
492
- {
493
- type: "object",
494
- required: ["path", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
495
- properties: {
496
- path: { type: "string", },
497
- bytes: { type: "integer", minimum: 0, },
498
- truncated: { type: "boolean", },
499
- tailed: { type: "boolean", },
500
- envLang: { enum: ["PYTHON", "R",], },
501
- envName: { type: "string", },
502
- logName: { type: "string", },
503
- },
504
- },
505
- ],
506
- },
507
- "code-env.version": CodeEnvVersionForProjectSchema,
508
- "code-env.usages": CodeEnvUsageArraySchema,
509
- "code.run": {
510
- type: "object",
511
- additionalProperties: false,
512
- required: [
513
- "outcome",
514
- "success",
515
- "runId",
516
- "elapsedMs",
517
- "pollCount",
518
- "output",
519
- "logTruncated",
520
- "maxLogBytes",
521
- "cleanup",
522
- ],
523
- properties: {
524
- outcome: { type: "string", },
525
- success: { type: "boolean", },
526
- runId: { type: "string", },
527
- elapsedMs: { type: "number", minimum: 0, },
528
- pollCount: { type: "integer", minimum: 0, },
529
- output: { type: "string", },
530
- log: { type: "string", },
531
- logTruncated: { type: "boolean", },
532
- maxLogBytes: { type: "integer", minimum: 0, },
533
- timedOut: { const: true, },
534
- timeoutMs: { type: "integer", minimum: 0, },
535
- cleanup: {
536
- type: "object",
537
- additionalProperties: false,
538
- required: ["status",],
539
- properties: {
540
- status: { enum: ["deleted", "kept", "failed",], },
541
- error: { type: "string", },
542
- },
543
- },
544
- },
545
- },
546
- "notebook.list-jupyter": JupyterNotebookSummaryArraySchema,
547
- "notebook.get-jupyter": JupyterNotebookContentSchema,
548
- "notebook.sessions-jupyter": NotebookSessionArraySchema,
549
- "notebook.list-sql": SqlNotebookSummaryArraySchema,
550
- "notebook.get-sql": SqlNotebookContentSchema,
551
- "notebook.history-sql": SqlNotebookHistorySchema,
552
- "notebook.save-jupyter": NOTEBOOK_SAVE_OUTPUT_SCHEMA,
553
- "notebook.save-sql": NOTEBOOK_SAVE_SQL_OUTPUT_SCHEMA,
554
- "project-library.list": { type: "array", items: PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA, },
555
- "project-library.get-bytes": {
556
- type: "object",
557
- additionalProperties: false,
558
- required: ["path", "bytes", "sha256",],
559
- properties: {
560
- path: { type: "string", },
561
- bytes: { type: "integer", minimum: 0, },
562
- sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
563
- },
564
- },
565
- "project-library.put": {
566
- type: "object",
567
- additionalProperties: false,
568
- required: ["updated", "bytes", "sha256",],
569
- properties: {
570
- updated: { type: "string", },
571
- bytes: { type: "integer", minimum: 0, },
572
- sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
573
- beforeSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
574
- },
575
- },
576
- "project-library.diff": {
577
- type: "object",
578
- additionalProperties: false,
579
- required: [
580
- "path",
581
- "unchanged",
582
- "added",
583
- "removed",
584
- "diff",
585
- "diffTruncated",
586
- "localSha256",
587
- "localBytes",
588
- "maxLines",
589
- ],
590
- properties: {
591
- path: { type: "string", },
592
- unchanged: { type: "boolean", },
593
- added: { type: "integer", minimum: 0, },
594
- removed: { type: "integer", minimum: 0, },
595
- diff: { type: "string", },
596
- diffTruncated: { type: "boolean", },
597
- binary: { type: "boolean", },
598
- remoteAbsent: { type: "boolean", },
599
- remoteSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
600
- remoteBytes: { type: "integer", minimum: 0, },
601
- localSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
602
- localBytes: { type: "integer", minimum: 0, },
603
- maxLines: { type: "integer", minimum: 0, },
604
- },
605
- },
606
- "project.list": ProjectSummaryArraySchema,
607
- "project.get": ProjectDetailsSchema,
608
- "project.metadata": ProjectMetadataSchema,
609
- "dataset.list": DatasetSummaryArraySchema,
610
- "dataset.get": DatasetDetailsSchema,
611
- "dataset.schema": DatasetSchemaSchema,
612
- "recipe.list": RecipeSummaryArraySchema,
613
- "job.list": JobSummaryArraySchema,
614
- "job.wait": JobWaitResultSchema,
615
- "job.monitor": JobWaitResultSchema,
616
- "scenario.list": ScenarioSummaryArraySchema,
617
- "scenario.get": ScenarioDetailsSchema,
618
- "scenario.status": ScenarioStatusSchema,
619
- "flow-zone.list": FlowZoneArraySchema,
620
- "flow-zone.get": FlowZoneSchema,
621
- "sql.query": {
622
- anyOf: [
623
- SqlQueryResponseSchema,
624
- {
625
- type: "object",
626
- required: ["queryId", "rowCount", "preview",],
627
- additionalProperties: true,
628
- properties: {
629
- queryId: { type: "string", },
630
- rowCount: { type: "number", },
631
- preview: { type: "array", items: true, },
632
- truncated: { type: "boolean", },
633
- outputPath: { type: "string", },
634
- written: { type: "string", },
635
- },
636
- },
637
- ],
638
- },
639
- };
449
+ const actionOutputSchemas = actionOutputSchemasMetadata
450
+ .schemas;
640
451
  function outputJsonSchema(resource, action, shape) {
641
- const precise = COMMAND_OUTPUT_SCHEMAS[registryKey(resource, action)];
452
+ const precise = actionOutputSchemas[registryKey(resource, action)];
642
453
  if (precise)
643
454
  return precise;
644
455
  if (shape === "array")
@@ -693,10 +504,9 @@ function shortFlagPattern(flags) {
693
504
  * use an actionless prefix, while real command paths that spell `run` in their
694
505
  * usage (e.g. `dss commands run`) keep the action token.
695
506
  */
696
- function argvPrefix(resource, action, usage) {
697
- const usageTokens = usage.split(/\s+/).filter((token) => token.length > 0);
698
- const head = usageTokens.slice(1);
699
- 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,];
700
510
  }
701
511
  /**
702
512
  * Describe one invocation as a canonical argv token array. Agents emit the
@@ -713,7 +523,7 @@ function argvPrefix(resource, action, usage) {
713
523
  * negative-number and `--` separator rules.
714
524
  */
715
525
  function argvJsonSchema(resource, action, flags, usage, requiredFlags, requiredOneOf) {
716
- const prefix = argvPrefix(resource, action, usage);
526
+ const prefix = argvPrefix(resource, action);
717
527
  const aliases = {};
718
528
  for (const flag of flags) {
719
529
  aliases[flag.name] = flag.aliases ?? [];
@@ -721,7 +531,8 @@ function argvJsonSchema(resource, action, flags, usage, requiredFlags, requiredO
721
531
  const booleanNames = flags.filter((flag) => flag.kind === "boolean")
722
532
  .flatMap((flag) => [flag.name, ...(flag.aliases ?? []),]);
723
533
  const valueFlags = flags.filter((flag) => flag.kind === "value");
724
- const requiredPositionals = requiredPlanPositionals(usage);
534
+ const requiredPositionals = commandSyntax(resource, action).positionalArguments
535
+ .filter((positional) => positional.required).map((positional) => positional.name);
725
536
  const itemSchemas = [
726
537
  { not: { pattern: "^-", }, },
727
538
  { const: "-", },
@@ -852,45 +663,6 @@ const EXPLICIT_REGISTRY_OVERRIDES = {
852
663
  examplePayload: { article: { name: "Updated article", }, },
853
664
  },
854
665
  };
855
- function extractUsageFlags(usage) {
856
- const flags = [];
857
- for (const match of usage.matchAll(/--([a-z0-9-]+)/g)) {
858
- flags.push(FLAG_ALIASES[match[1]] ?? match[1]);
859
- }
860
- return uniqueStrings(flags).filter((flag) => KNOWN_LONG_FLAGS.has(flag));
861
- }
862
- function extractPositionalArguments(usage) {
863
- const positionals = [];
864
- const add = (name, required) => {
865
- const existing = positionals.find((positional) => positional.name === name);
866
- if (existing)
867
- existing.required ||= required;
868
- else
869
- positionals.push({ name, required, });
870
- };
871
- for (const match of usage.matchAll(/<([^>]+)>/g)) {
872
- const before = usage.slice(0, match.index);
873
- const openParen = before.lastIndexOf("(");
874
- const insideParen = openParen > before.lastIndexOf(")");
875
- const closeParen = insideParen ? usage.indexOf(")", match.index) : -1;
876
- const group = closeParen >= 0 ? usage.slice(openParen + 1, closeParen) : "";
877
- const hasFlagAlternative = group.split("|").some((alternative) => alternative.includes("--"));
878
- const required = before.lastIndexOf("[") <= before.lastIndexOf("]")
879
- && !hasFlagAlternative;
880
- add(match[1], required);
881
- }
882
- for (const suffix of usage.split("[").slice(1)) {
883
- const candidate = suffix.split("]", 1)[0];
884
- if (candidate && /^[A-Za-z][A-Za-z0-9_-]*$/.test(candidate))
885
- add(candidate, false);
886
- }
887
- for (const group of topLevelParenGroups(usage)) {
888
- const candidate = group.split("|", 1)[0]?.trim();
889
- if (candidate && /^[A-Z][A-Z0-9_-]*$/.test(candidate))
890
- add(candidate, false);
891
- }
892
- return positionals;
893
- }
894
666
  const COMMAND_REQUIRED_INPUT_GROUPS = {
895
667
  "notebook.unload-jupyter": [
896
668
  {
@@ -1049,102 +821,6 @@ function inferOutputShape(resource, action) {
1049
821
  return "string";
1050
822
  return "object";
1051
823
  }
1052
- export function inferInputContract(usage) {
1053
- return {
1054
- ...(usage.includes("--stdin") ? { stdin: true, } : {}),
1055
- ...(usage.includes("--data ") || usage.includes("--data JSON") ? { dataFlag: true, } : {}),
1056
- ...(usage.includes("--data-file") ? { dataFileFlag: true, } : {}),
1057
- };
1058
- }
1059
- function stripOptionalUsageGroups(usage) {
1060
- return usage.replace(/\[[^\]]*\]/g, " ");
1061
- }
1062
- function stripAllUsageGroups(usage) {
1063
- return usage.replace(/\[[^\]]*\]/g, " ").replace(/\([^)]*\)/g, " ");
1064
- }
1065
- function topLevelParenGroups(usage) {
1066
- const groups = [];
1067
- let depth = 0;
1068
- let current = "";
1069
- for (const char of usage) {
1070
- if (char === "(") {
1071
- if (depth > 0)
1072
- current += char;
1073
- else
1074
- current = "";
1075
- depth++;
1076
- }
1077
- else if (char === ")") {
1078
- depth--;
1079
- if (depth === 0)
1080
- groups.push(current);
1081
- else
1082
- current += char;
1083
- }
1084
- else if (depth > 0) {
1085
- current += char;
1086
- }
1087
- }
1088
- return groups;
1089
- }
1090
- function splitTopLevelChoices(group) {
1091
- const parts = [];
1092
- let depth = 0;
1093
- let current = "";
1094
- for (let index = 0; index < group.length; index++) {
1095
- const char = group[index];
1096
- if (char === "[" || char === "(")
1097
- depth++;
1098
- else if (char === "]" || char === ")")
1099
- depth--;
1100
- const nextAlternative = group.slice(index + 1).trimStart();
1101
- if (char === "|" && depth === 0 && nextAlternative.startsWith("--")) {
1102
- parts.push(current);
1103
- current = "";
1104
- }
1105
- else {
1106
- current += char;
1107
- }
1108
- }
1109
- parts.push(current);
1110
- return parts;
1111
- }
1112
- function flagsInUsageFragment(fragment) {
1113
- return extractUsageFlags(fragment.replace(/\[[^\]]*\]/g, " "));
1114
- }
1115
- /**
1116
- * Split required usage flags into unconditional flags and required choice
1117
- * groups. A required `(--a X | --b Y)` group becomes a requiredOneOf entry
1118
- * (at least one alternative; an alternative listing several flags must be
1119
- * supplied together) instead of marking every flag as unconditionally required.
1120
- */
1121
- function deriveRequiredUsage(usage) {
1122
- const requiredFlags = extractUsageFlags(stripAllUsageGroups(usage));
1123
- const requiredOneOf = [];
1124
- for (const group of topLevelParenGroups(usage)) {
1125
- const alternatives = splitTopLevelChoices(group);
1126
- if (alternatives.length <= 1) {
1127
- requiredFlags.push(...flagsInUsageFragment(group));
1128
- continue;
1129
- }
1130
- const oneOf = alternatives
1131
- .map((alternative) => flagsInUsageFragment(alternative))
1132
- .filter((alternativeFlags) => alternativeFlags.length > 0);
1133
- // A group mixing a positional alternative with flags (e.g. sql query's
1134
- // `(SQL | --sql QUERY | --sql-file PATH | --sql - | --stdin)`) cannot be
1135
- // expressed as a flag-only choice: filtering out the flagless alternative
1136
- // would falsely require one of the flags. Omit the group from the flag
1137
- // contract entirely; the handler stays the runtime authority.
1138
- if (alternatives.some((alternative) => flagsInUsageFragment(alternative).length === 0)) {
1139
- continue;
1140
- }
1141
- if (oneOf.length > 1)
1142
- requiredOneOf.push({ oneOf, });
1143
- else if (oneOf.length === 1)
1144
- requiredFlags.push(...oneOf[0]);
1145
- }
1146
- return { requiredFlags: uniqueStrings(requiredFlags), requiredOneOf, };
1147
- }
1148
824
  const GLOBAL_FLAG_VALUE_HINTS = {
1149
825
  url: { valueType: "URL", },
1150
826
  fields: { valueType: "CSV", },
@@ -1160,22 +836,6 @@ const GLOBAL_FLAG_VALUE_HINTS = {
1160
836
  standard: { valueType: "JSON", },
1161
837
  until: { valueType: "STATE", },
1162
838
  };
1163
- /** Derive a value placeholder (and enum members) for each value flag from its usage token. */
1164
- function extractFlagValueHints(usage) {
1165
- const hints = new Map();
1166
- 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)) {
1167
- const flag = FLAG_ALIASES[match[1]] ?? match[1];
1168
- if (!hints.has(flag)) {
1169
- hints.set(flag, { valueType: "enum", enumValues: match[2].split("|"), });
1170
- }
1171
- }
1172
- for (const match of usage.matchAll(/--([a-z0-9-]+)\s+(<[^>]+>|[A-Z][A-Za-z0-9_]*)/g)) {
1173
- const flag = FLAG_ALIASES[match[1]] ?? match[1];
1174
- if (!hints.has(flag))
1175
- hints.set(flag, { valueType: match[2], });
1176
- }
1177
- return hints;
1178
- }
1179
839
  function inferPayloadSchema(inputContract) {
1180
840
  if (!inputContract.stdin && !inputContract.dataFlag && !inputContract.dataFileFlag) {
1181
841
  return undefined;
@@ -1229,11 +889,11 @@ function cleanupCommandFromDeleteUsage(resource, action) {
1229
889
  : action === "create-instance" || action === "create-successor-instance"
1230
890
  ? "delete-instance"
1231
891
  : "delete";
1232
- const deleteUsage = commands[resource]?.[deleteAction]?.usage;
1233
- if (!deleteUsage)
892
+ const deleteSyntax = commandSyntaxTree(resource, deleteAction);
893
+ if (!deleteSyntax)
1234
894
  return undefined;
1235
- const base = stripOptionalUsageGroups(deleteUsage).replace(/\s+/g, " ").trim();
1236
- if (deleteUsage.includes("--if-exists"))
895
+ const base = renderNodes(deleteSyntax, true);
896
+ if (syntaxHasFlag(deleteSyntax, "if-exists"))
1237
897
  return `${base} --if-exists`;
1238
898
  return base;
1239
899
  }
@@ -1316,14 +976,15 @@ function inferAsyncKind(resource, action) {
1316
976
  }
1317
977
  return "none";
1318
978
  }
1319
- function inferIdempotency(resource, sideEffect, action, usage) {
979
+ function inferIdempotency(resource, sideEffect, action, syntax) {
1320
980
  if (sideEffect === "read")
1321
981
  return "safe";
1322
982
  if (`${resource}.${action}` === "install-skill.run")
1323
983
  return "convergent";
1324
- if (action.startsWith("create") && usage.includes("--if-not-exists"))
984
+ if (action.startsWith("create") && syntaxHasFlag(syntax, "if-not-exists")) {
1325
985
  return "if-not-exists";
1326
- if (action.startsWith("delete") && usage.includes("--if-exists"))
986
+ }
987
+ if (action.startsWith("delete") && syntaxHasFlag(syntax, "if-exists"))
1327
988
  return "if-exists";
1328
989
  if (`${resource}.${action}` === "app.set-manifest-version")
1329
990
  return "none";
@@ -1375,10 +1036,10 @@ export function inferCleanupHint(resource, action) {
1375
1036
  : action === "create-instance" || action === "create-successor-instance"
1376
1037
  ? "delete-instance"
1377
1038
  : "delete";
1378
- const deleteUsage = commands[resource]?.[deleteAction]?.usage;
1379
- if (!deleteUsage)
1039
+ const deleteSyntax = commandSyntaxTree(resource, deleteAction);
1040
+ if (!deleteSyntax)
1380
1041
  return undefined;
1381
- const ifExists = deleteUsage.includes("--if-exists") ? " --if-exists" : "";
1042
+ const ifExists = syntaxHasFlag(deleteSyntax, "if-exists") ? " --if-exists" : "";
1382
1043
  if (resource === "code-env") {
1383
1044
  return `Delete with \`dss code-env delete <lang> <name>${ifExists}\`.`;
1384
1045
  }
@@ -1389,14 +1050,16 @@ export function inferCleanupHint(resource, action) {
1389
1050
  }
1390
1051
  function buildRegistryEntry(resource, action, meta) {
1391
1052
  const requiresAuth = meta.localHandler === undefined && inferRequiresAuth(resource);
1392
- const requiresProject = inferRequiresProject(resource, action, meta.usage);
1053
+ const requiresProject = inferRequiresProject(resource, action);
1393
1054
  const sideEffect = inferSideEffect(resource, action);
1394
1055
  const destructive = inferDestructiveLevel(resource, sideEffect, action);
1395
1056
  const asyncKind = inferAsyncKind(resource, action);
1396
1057
  const mutatesDss = sideEffect === "write" && resource !== "auth" && resource !== "install-skill";
1397
- const supportsPlan = mutatesDss || sideEffect === "write";
1058
+ // auth login writes local credentials only; its --plan previews that write.
1059
+ const supportsPlan = sideEffect === "write" || resource === "auth";
1398
1060
  const supportsCleanup = supportsCleanupLedger(resource, action);
1399
- const usageFlags = extractUsageFlags(meta.usage);
1061
+ const syntax = commandSyntax(resource, action);
1062
+ const usageFlags = syntax.flags;
1400
1063
  const flags = uniqueStrings([
1401
1064
  ...usageFlags,
1402
1065
  ...(supportsPlan ? ["plan",] : []),
@@ -1405,7 +1068,7 @@ function buildRegistryEntry(resource, action, meta) {
1405
1068
  ...(requiresAuth ? AUTHENTICATED_AGENT_FLAGS : []),
1406
1069
  ...(requiresProject ? ["project-key",] : []),
1407
1070
  ]);
1408
- const derivedRequired = deriveRequiredUsage(meta.usage);
1071
+ const derivedRequired = syntax;
1409
1072
  const requiredFlags = meta.requiredFlags
1410
1073
  ?? EXPLICIT_REGISTRY_OVERRIDES[registryKey(resource, action)]?.requiredFlags
1411
1074
  ?? derivedRequired.requiredFlags;
@@ -1416,8 +1079,8 @@ function buildRegistryEntry(resource, action, meta) {
1416
1079
  const optionalFlags = meta.optionalFlags
1417
1080
  ?? EXPLICIT_REGISTRY_OVERRIDES[registryKey(resource, action)]?.optionalFlags
1418
1081
  ?? flags.filter((flag) => !requiredFlags.includes(flag) && !oneOfFlags.has(flag));
1419
- const valueHints = extractFlagValueHints(meta.usage);
1420
- const inputContract = inferInputContract(meta.usage);
1082
+ const valueHints = syntax.valueHints;
1083
+ const inputContract = syntax.inputContract;
1421
1084
  const cleanupHint = inferCleanupHint(resource, action);
1422
1085
  const payloadSchema = meta.payloadSchema
1423
1086
  ?? EXPLICIT_REGISTRY_OVERRIDES[registryKey(resource, action)]?.payloadSchema
@@ -1431,15 +1094,13 @@ function buildRegistryEntry(resource, action, meta) {
1431
1094
  ? undefined
1432
1095
  : inferredCleanupCommand;
1433
1096
  const flagMetadata = flags.map((name) => {
1434
- const aliases = Object.entries(FLAG_ALIASES)
1435
- .filter(([raw, canonical,]) => canonical === name && new RegExp(`--${raw}(?![a-z0-9-])`).test(meta.usage))
1436
- .map(([raw,]) => raw);
1097
+ const aliases = syntax.aliases[name] ?? [];
1437
1098
  const aliasPart = aliases.length > 0 ? { aliases, } : {};
1438
1099
  const kind = flagKind(name);
1439
1100
  if (kind === "boolean")
1440
1101
  return { name, kind, ...aliasPart, };
1441
1102
  const allowEmptyPart = ALLOW_EMPTY_VALUE_FLAGS[name] === true ? { allowEmptyValue: true, } : {};
1442
- const hint = valueHints.get(name) ?? GLOBAL_FLAG_VALUE_HINTS[name];
1103
+ const hint = valueHints[name] ?? GLOBAL_FLAG_VALUE_HINTS[name];
1443
1104
  if (!hint)
1444
1105
  return { name, kind, ...aliasPart, ...allowEmptyPart, };
1445
1106
  return {
@@ -1451,15 +1112,15 @@ function buildRegistryEntry(resource, action, meta) {
1451
1112
  ...allowEmptyPart,
1452
1113
  };
1453
1114
  });
1454
- const positionalArguments = extractPositionalArguments(meta.usage);
1115
+ const positionalArguments = syntax.positionalArguments;
1455
1116
  const positionals = positionalArguments.map((positional) => positional.name);
1456
1117
  const inputGroups = requiredInputGroups(resource, action);
1457
1118
  const outputShape = inferOutputShape(resource, action);
1458
- const producesLocalFile = meta.usage.includes("--output PATH")
1459
- || meta.usage.includes("--output-file PATH");
1119
+ const producesLocalFile = syntax.producesLocalFile;
1460
1120
  const uniqueRequiredFlags = uniqueStrings(requiredFlags);
1461
1121
  const uniqueOptionalFlags = uniqueStrings(optionalFlags);
1462
1122
  const unsafe = unsafeOutputs(resource, action, producesLocalFile);
1123
+ let schemas;
1463
1124
  return {
1464
1125
  resource,
1465
1126
  action,
@@ -1479,14 +1140,16 @@ function buildRegistryEntry(resource, action, meta) {
1479
1140
  producesLocalFile,
1480
1141
  mutatesDss,
1481
1142
  async: asyncKind,
1482
- idempotency: inferIdempotency(resource, sideEffect, action, meta.usage),
1483
- dryRun: meta.usage.includes("--dry-run"),
1143
+ idempotency: inferIdempotency(resource, sideEffect, action, commandSyntaxTree(resource, action) ?? []),
1144
+ dryRun: syntax.dryRun,
1484
1145
  requiredFlags: uniqueRequiredFlags,
1485
1146
  optionalFlags: uniqueOptionalFlags,
1486
1147
  ...(requiredOneOf.length > 0 ? { requiredOneOf, } : {}),
1487
1148
  ...(inputGroups.length > 0 ? { requiredInputGroups: inputGroups, } : {}),
1488
1149
  ...(payloadSchema ? { payloadSchema, } : {}),
1489
- schemas: buildCommandSchemas(resource, action, flagMetadata, uniqueRequiredFlags, requiredOneOf, payloadSchema, outputShape, meta.usage),
1150
+ get schemas() {
1151
+ return schemas ??= buildCommandSchemas(resource, action, flagMetadata, uniqueRequiredFlags, requiredOneOf, payloadSchema, outputShape, meta.usage);
1152
+ },
1490
1153
  ...(unsafe ? { unsafeOutputs: unsafe, } : {}),
1491
1154
  ...(examplePayload !== undefined ? { examplePayload, } : {}),
1492
1155
  ...(cleanupCommand ? { cleanupCommand, } : {}),
@@ -1579,7 +1242,7 @@ function commandDefinitions(resourceFilter) {
1579
1242
  for (const [action, meta,] of Object.entries(AUTH_ACTIONS)) {
1580
1243
  registry.auth[action] = {
1581
1244
  handler: async () => undefined,
1582
- usage: meta.usage,
1245
+ usage: commandUsage("auth", action),
1583
1246
  description: meta.description,
1584
1247
  examples: meta.examples,
1585
1248
  requiredFlags: meta.requiredFlags,
@@ -1591,9 +1254,14 @@ function commandDefinitions(resourceFilter) {
1591
1254
  export function buildCommandRegistry(resourceFilter) {
1592
1255
  const registry = {};
1593
1256
  for (const [resource, actions,] of Object.entries(commandDefinitions(resourceFilter))) {
1594
- registry[resource] = {};
1257
+ const entries = {};
1258
+ registry[resource] = entries;
1595
1259
  for (const [action, meta,] of Object.entries(actions)) {
1596
- registry[resource][action] = buildRegistryEntry(resource, action, meta);
1260
+ let entry;
1261
+ Object.defineProperty(entries, action, {
1262
+ enumerable: true,
1263
+ get: () => entry ??= buildRegistryEntry(resource, action, meta),
1264
+ });
1597
1265
  }
1598
1266
  }
1599
1267
  return registry;
@@ -1841,9 +1509,7 @@ function planProjectKeyFromArgs(flags) {
1841
1509
  const fromFlag = flags["project-key"];
1842
1510
  if (typeof fromFlag === "string" && fromFlag.trim().length > 0)
1843
1511
  return fromFlag.trim();
1844
- if (!dataikuEnvironmentEnabled())
1845
- return undefined;
1846
- const fromEnv = process.env.DATAIKU_PROJECT_KEY;
1512
+ const fromEnv = ambientProjectKey();
1847
1513
  return fromEnv !== undefined && fromEnv.trim().length > 0 ? fromEnv.trim() : undefined;
1848
1514
  }
1849
1515
  /**
@@ -1895,11 +1561,6 @@ function optionalJsonFlag(flags, name) {
1895
1561
  function requiredPlanJsonInput(flags, usage) {
1896
1562
  return requiredJsonInput(flags, `--data, --data-file, or --stdin is required. Usage: ${usage}`);
1897
1563
  }
1898
- function requiredPlanPositionals(usage) {
1899
- return extractPositionalArguments(usage)
1900
- .filter((positional) => positional.required)
1901
- .map((positional) => positional.name);
1902
- }
1903
1564
  function dataQualityEndpoint(projectKey, datasetName, suffix) {
1904
1565
  return encodedProjectEndpointForPlan(projectKey, `/datasets/${encodeURIComponent(datasetName)}/data-quality${suffix}`);
1905
1566
  }
@@ -1982,15 +1643,12 @@ function encodePluginSegmentsForPlan(contentPath) {
1982
1643
  */
1983
1644
  function pluginGitPlanPayload(flags) {
1984
1645
  const repository = requiredPlanFlag(flags, "repository", "dss plugin install-from-git --repository URL");
1985
- const payload = {
1646
+ // Mirrors PluginsResource install/update bodies: absent options are sent as null.
1647
+ return {
1986
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,
1987
1651
  };
1988
- if (typeof flags["checkout"] === "string")
1989
- payload.gitCheckout = flags["checkout"];
1990
- if (typeof flags["path-in-repository"] === "string") {
1991
- payload.gitSubpath = flags["path-in-repository"];
1992
- }
1993
- return payload;
1994
1652
  }
1995
1653
  function optionalPlanProjectScope(flags) {
1996
1654
  const projectKey = flags["project-key"];
@@ -2123,16 +1781,28 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2123
1781
  };
2124
1782
  case "wiki.create": {
2125
1783
  const name = requiredPlanFlag(flags, "name", entry.usage);
2126
- return {
1784
+ const content = textInput(flags);
1785
+ const create = {
2127
1786
  method: "POST",
2128
1787
  endpoint: projectEndpoint("/wiki/"),
1788
+ payload: { projectKey, name, parent: flags["parent"] ?? null, },
1789
+ };
1790
+ return {
1791
+ ...create,
2129
1792
  identifiers: { name, },
2130
- payload: {
2131
- projectKey,
2132
- name,
2133
- parent: flags["parent"] ?? null,
2134
- content: textInput(flags),
2135
- },
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
+ }),
2136
1806
  };
2137
1807
  }
2138
1808
  case "wiki.update":
@@ -2158,7 +1828,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2158
1828
  const dataName = data?.["name"];
2159
1829
  const name = flagName ?? (typeof dataName === "string" ? dataName : undefined);
2160
1830
  if (!name) {
2161
- 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")}`);
2162
1832
  }
2163
1833
  const listed = parseBooleanOption(flags["listed"], "--listed");
2164
1834
  const payload = { ...(data ?? { pages: [], }), name, };
@@ -2196,7 +1866,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2196
1866
  const name = flags["name"];
2197
1867
  const type = flags["type"];
2198
1868
  if (!data && (!name || !type)) {
2199
- 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")}`);
2200
1870
  }
2201
1871
  const prototype = { ...data, };
2202
1872
  if (name !== undefined)
@@ -2280,7 +1950,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2280
1950
  };
2281
1951
  case "flow-zone.create": {
2282
1952
  const name = flowZoneName(flags["name"]);
2283
- const payload = { name, color: flowZoneColor(flags["color"]), projectKey, };
1953
+ const payload = { name, color: flowZoneColor(flags["color"]) ?? "#2ab1ac", };
2284
1954
  return {
2285
1955
  method: "POST",
2286
1956
  endpoint: projectEndpoint("/flow/zones"),
@@ -2319,17 +1989,25 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2319
1989
  if (!connection && dsType.toLowerCase() !== "uploadedfiles") {
2320
1990
  throw new UsageError("--connection is required unless --type is UploadedFiles.");
2321
1991
  }
1992
+ const endpoint = projectEndpoint("/datasets/"); // throws without a project key
2322
1993
  return {
2323
1994
  method: "POST",
2324
- endpoint: projectEndpoint("/datasets/"),
1995
+ endpoint,
2325
1996
  identifiers: { name, },
2326
- payload: { datasetName: name, connection, dsType, projectKey, },
1997
+ payload: buildDatasetCreateBody({
1998
+ projectKey: projectKey,
1999
+ datasetName: name,
2000
+ connection,
2001
+ dsType,
2002
+ }),
2327
2003
  };
2328
2004
  }
2329
2005
  case "dataset.clone": {
2330
2006
  const source = args[0];
2331
2007
  const target = args[1];
2332
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.",
2333
2011
  method: "POST",
2334
2012
  endpoint: projectEndpoint("/datasets/"),
2335
2013
  identifiers: { source, target, },
@@ -2491,7 +2169,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2491
2169
  identifiers: { name: id, },
2492
2170
  };
2493
2171
  case "recipe.create": {
2494
- const type = requiredPlanFlag(flags, "type", entry.usage);
2172
+ requiredPlanFlag(flags, "type", entry.usage);
2495
2173
  const outputDataset = flags["output"];
2496
2174
  const outputFolder = flags["output-folder"];
2497
2175
  if (outputDataset && outputFolder) {
@@ -2503,19 +2181,14 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2503
2181
  if (outputFolder && !flags["output-connection"]) {
2504
2182
  throw new UsageError("--output-connection is required when using --output-folder.");
2505
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);
2506
2187
  return {
2507
2188
  method: "POST",
2508
- endpoint: projectEndpoint("/recipes/"),
2509
- identifiers: { name: flags["name"], },
2510
- payload: {
2511
- type,
2512
- name: flags["name"],
2513
- inputDatasets: recipeInputDatasetsFromFlags(flags),
2514
- outputDataset,
2515
- outputFolder,
2516
- outputConnection: flags["output-connection"],
2517
- projectKey,
2518
- },
2189
+ endpoint,
2190
+ identifiers: { name: recipePrototype.name, },
2191
+ payload: { recipePrototype, creationSettings, },
2519
2192
  };
2520
2193
  }
2521
2194
  case "recipe.run":
@@ -2641,22 +2314,54 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2641
2314
  if (activeRaw !== "true" && activeRaw !== "false") {
2642
2315
  throw new UsageError(`Usage: ${entry.usage}`);
2643
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).
2644
2321
  return {
2645
2322
  method: "PUT",
2646
- endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/light`),
2323
+ endpoint: lightEndpoint,
2647
2324
  identifiers: { id, },
2648
- 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
+ ],
2649
2336
  };
2650
2337
  }
2651
2338
  case "folder.create": {
2652
2339
  const name = requiredPlanFlag(flags, "name", entry.usage);
2653
2340
  const type = flags["type"];
2654
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
+ }
2655
2360
  return {
2656
2361
  method: "POST",
2657
2362
  endpoint: projectEndpoint("/managedfolders/"),
2658
2363
  identifiers: { name, },
2659
- payload: { name, type, connection, path: flags["path"], projectKey, },
2364
+ payload: body,
2660
2365
  };
2661
2366
  }
2662
2367
  case "job.abort":
@@ -2680,18 +2385,22 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2680
2385
  endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/`),
2681
2386
  identifiers: { id, },
2682
2387
  };
2683
- case "scenario.create":
2388
+ case "scenario.create": {
2389
+ const type = flags["type"] ?? "step_based";
2684
2390
  return {
2685
2391
  method: "POST",
2686
2392
  endpoint: projectEndpoint("/scenarios/"),
2687
2393
  identifiers: { id: args[0], name: args[1], },
2394
+ // Mirrors ScenariosResource.create's default params for step-based scenarios.
2688
2395
  payload: {
2689
2396
  id: args[0],
2690
2397
  name: args[1],
2691
2398
  projectKey,
2692
- type: flags["type"] ?? "step_based",
2399
+ type,
2400
+ params: type === "step_based" ? { steps: [], triggers: [], reporters: [], } : {},
2693
2401
  },
2694
2402
  };
2403
+ }
2695
2404
  case "scenario.update":
2696
2405
  return {
2697
2406
  method: "PUT",
@@ -2726,6 +2435,8 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2726
2435
  };
2727
2436
  case "variable.set":
2728
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).",
2729
2440
  method: "PUT",
2730
2441
  endpoint: projectEndpoint("/variables/"),
2731
2442
  payload: {
@@ -3153,6 +2864,8 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3153
2864
  };
3154
2865
  case "saved-model.import-mlflow-version-from-folder":
3155
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.",
3156
2869
  method: "POST",
3157
2870
  endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}`),
3158
2871
  identifiers: { savedModelId: id, versionId: args[1], },
@@ -3203,7 +2916,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3203
2916
  }
3204
2917
  return {
3205
2918
  method: "POST",
3206
- 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}`,
3207
2920
  identifiers: { savedModelId: id, versionId: args[1], },
3208
2921
  payload,
3209
2922
  };
@@ -3402,17 +3115,19 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3402
3115
  };
3403
3116
  case "plugin.create-dev": {
3404
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.
3405
3120
  const payload = {
3406
3121
  pluginId: id,
3407
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,
3408
3130
  };
3409
- if (typeof flags["repository"] === "string")
3410
- payload.gitRepository = flags["repository"];
3411
- if (typeof flags["checkout"] === "string")
3412
- payload.gitCheckout = flags["checkout"];
3413
- if (typeof flags["path-in-repository"] === "string") {
3414
- payload.gitSubpath = flags["path-in-repository"];
3415
- }
3416
3131
  return {
3417
3132
  method: "POST",
3418
3133
  endpoint: "/public/api/plugins/actions/createDev",
@@ -3473,14 +3188,13 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3473
3188
  identifiers: { pluginId: id, path: args[1], },
3474
3189
  };
3475
3190
  case "plugin.rename": {
3476
- const newName = requiredPlanPositionals(entry.usage)[2];
3477
- if (!newName)
3191
+ if (!args[2])
3478
3192
  throw new UsageError(`Usage: ${entry.usage}`);
3479
3193
  return {
3480
3194
  method: "POST",
3481
3195
  endpoint: `${pluginRootEndpoint(id, "/contents-actions/rename")}`,
3482
3196
  identifiers: { pluginId: id, path: args[1], },
3483
- payload: { oldPath: args[1], newName: args[2], },
3197
+ payload: { oldPath: `/${validatePluginPath(args[1] ?? "")}`, newName: args[2], },
3484
3198
  };
3485
3199
  }
3486
3200
  case "plugin.move": {
@@ -3491,7 +3205,10 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3491
3205
  method: "POST",
3492
3206
  endpoint: `${pluginRootEndpoint(id, "/contents-actions/move")}`,
3493
3207
  identifiers: { pluginId: id, path: args[1], },
3494
- payload: { oldPath: args[1], newPath: destination, },
3208
+ payload: {
3209
+ oldPath: `/${validatePluginPath(args[1] ?? "")}`,
3210
+ newPath: validatePluginDestinationPath(destination),
3211
+ },
3495
3212
  };
3496
3213
  }
3497
3214
  case "business-app.save-settings":
@@ -3541,6 +3258,8 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3541
3258
  // plan carries no preflight requests and no fake concrete GET
3542
3259
  // path: `{targetProjectKey}` resolves at apply time.
3543
3260
  return {
3261
+ exact: false,
3262
+ reason: "targetProjectKey (and targetProjectName when omitted) are generated during apply; the sent body adds them.",
3544
3263
  method: "POST",
3545
3264
  endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
3546
3265
  identifiers: {
@@ -4222,7 +3941,6 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
4222
3941
  evaluateProjectStandardsChecks,
4223
3942
  }),
4224
3943
  identifiers: { bundleId: id, },
4225
- payload: {},
4226
3944
  };
4227
3945
  }
4228
3946
  case "bundle.publish":
@@ -4440,7 +4158,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
4440
4158
  case "code-env.set-jupyter": {
4441
4159
  const active = parseBooleanOption(flags["active"], "--active");
4442
4160
  if (active === undefined) {
4443
- 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")}`);
4444
4162
  }
4445
4163
  const wait = codeEnvWait(flags);
4446
4164
  return {
@@ -5075,7 +4793,7 @@ export function buildMutationPlan(resource, action, meta, args, flags) {
5075
4793
  if (!entry.mutatesDss && entry.sideEffect !== "write") {
5076
4794
  throw new UsageError(`--plan is only supported for mutating commands. Usage: ${meta.usage}`);
5077
4795
  }
5078
- const requiredPositionals = requiredPlanPositionals(meta.usage);
4796
+ const requiredPositionals = entry.positionalArguments.filter((positional) => positional.required);
5079
4797
  requireArgs(args, requiredPositionals.length, meta.usage);
5080
4798
  const projectKey = projectKeyForPlan(entry, flags);
5081
4799
  const shape = commandPlanShape(resource, action, args, flags, entry, projectKey);
@@ -5099,7 +4817,7 @@ export const BATCH_PLAN_EXIT_CODES = {
5099
4817
  longRunningFailure: 4,
5100
4818
  assertionFailure: 4,
5101
4819
  };
5102
- 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");
5103
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.";
5104
4822
  export const BATCH_HINT = 'Pass a JSON array of argv arrays, e.g. [["dataset","list"],["recipe","update","r","--data-file","p.json"]].';
5105
4823
  export const BATCH_EXAMPLE_PAYLOAD = [