dataiku-sdk 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/README.md +151 -46
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +102 -0
  4. package/dist/packages/types/src/index.js +76 -0
  5. package/dist/src/cli/commands/app.js +57 -15
  6. package/dist/src/cli/commands/bundle.js +1 -1
  7. package/dist/src/cli/commands/code-env.js +13 -7
  8. package/dist/src/cli/commands/code.js +1 -1
  9. package/dist/src/cli/commands/connection.d.ts +9 -0
  10. package/dist/src/cli/commands/connection.js +267 -1
  11. package/dist/src/cli/commands/data-collection.d.ts +2 -0
  12. package/dist/src/cli/commands/data-collection.js +164 -0
  13. package/dist/src/cli/commands/dataset.js +162 -33
  14. package/dist/src/cli/commands/future.js +2 -2
  15. package/dist/src/cli/commands/group.d.ts +2 -0
  16. package/dist/src/cli/commands/group.js +109 -0
  17. package/dist/src/cli/commands/index.js +151 -78
  18. package/dist/src/cli/commands/job.js +4 -2
  19. package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
  20. package/dist/src/cli/commands/knowledge-bank.js +80 -0
  21. package/dist/src/cli/commands/llm.d.ts +2 -0
  22. package/dist/src/cli/commands/llm.js +93 -0
  23. package/dist/src/cli/commands/macro.d.ts +2 -0
  24. package/dist/src/cli/commands/macro.js +221 -0
  25. package/dist/src/cli/commands/ml-task.js +18 -6
  26. package/dist/src/cli/commands/notebook.js +6 -2
  27. package/dist/src/cli/commands/plugin.d.ts +2 -0
  28. package/dist/src/cli/commands/plugin.js +799 -0
  29. package/dist/src/cli/commands/project-folder.d.ts +2 -0
  30. package/dist/src/cli/commands/project-folder.js +197 -0
  31. package/dist/src/cli/commands/project-git.js +15 -55
  32. package/dist/src/cli/commands/project.js +52 -6
  33. package/dist/src/cli/commands/recipe.js +36 -1
  34. package/dist/src/cli/commands/saved-model.d.ts +5 -0
  35. package/dist/src/cli/commands/saved-model.js +388 -1
  36. package/dist/src/cli/commands/scenario.js +139 -2
  37. package/dist/src/cli/commands/user.d.ts +8 -0
  38. package/dist/src/cli/commands/user.js +296 -0
  39. package/dist/src/cli/contract.d.ts +1 -2
  40. package/dist/src/cli/contract.js +1098 -297
  41. package/dist/src/cli/flags.js +30 -1
  42. package/dist/src/cli/helpers/app-successor.d.ts +19 -4
  43. package/dist/src/cli/helpers/app-successor.js +90 -30
  44. package/dist/src/cli/helpers/cleanup.js +3 -1
  45. package/dist/src/cli/helpers/flow-zone.js +1 -2
  46. package/dist/src/cli/output-schemas.d.ts +24 -0
  47. package/dist/src/cli/output-schemas.js +233 -0
  48. package/dist/src/cli/output.d.ts +1 -1
  49. package/dist/src/cli/output.js +23 -10
  50. package/dist/src/cli/usage.d.ts +1 -0
  51. package/dist/src/cli/usage.js +47 -1
  52. package/dist/src/cli.js +147 -41
  53. package/dist/src/client.d.ts +92 -5
  54. package/dist/src/client.js +202 -31
  55. package/dist/src/errors.d.ts +3 -0
  56. package/dist/src/errors.js +8 -4
  57. package/dist/src/generated/action-output-schemas.json +1735 -0
  58. package/dist/src/index.d.ts +16 -7
  59. package/dist/src/index.js +11 -2
  60. package/dist/src/resources/analyses.js +1 -8
  61. package/dist/src/resources/applications.d.ts +8 -6
  62. package/dist/src/resources/applications.js +21 -21
  63. package/dist/src/resources/base.d.ts +4 -0
  64. package/dist/src/resources/base.js +15 -0
  65. package/dist/src/resources/bundles.d.ts +9 -4
  66. package/dist/src/resources/bundles.js +10 -5
  67. package/dist/src/resources/connections.d.ts +78 -1
  68. package/dist/src/resources/connections.js +132 -1
  69. package/dist/src/resources/data-collections.d.ts +118 -0
  70. package/dist/src/resources/data-collections.js +49 -0
  71. package/dist/src/resources/datasets.d.ts +69 -0
  72. package/dist/src/resources/datasets.js +136 -5
  73. package/dist/src/resources/flow-zones.d.ts +6 -2
  74. package/dist/src/resources/flow-zones.js +6 -3
  75. package/dist/src/resources/futures.d.ts +6 -1
  76. package/dist/src/resources/futures.js +68 -22
  77. package/dist/src/resources/groups.d.ts +47 -0
  78. package/dist/src/resources/groups.js +56 -0
  79. package/dist/src/resources/jobs.d.ts +8 -13
  80. package/dist/src/resources/jobs.js +71 -34
  81. package/dist/src/resources/knowledge-banks.d.ts +58 -0
  82. package/dist/src/resources/knowledge-banks.js +61 -0
  83. package/dist/src/resources/llms.d.ts +92 -0
  84. package/dist/src/resources/llms.js +62 -0
  85. package/dist/src/resources/macros.d.ts +121 -0
  86. package/dist/src/resources/macros.js +231 -0
  87. package/dist/src/resources/ml-tasks.d.ts +37 -2
  88. package/dist/src/resources/ml-tasks.js +109 -20
  89. package/dist/src/resources/model-evaluation-stores.js +1 -8
  90. package/dist/src/resources/notebooks.d.ts +43 -7
  91. package/dist/src/resources/notebooks.js +53 -13
  92. package/dist/src/resources/plugins.d.ts +263 -0
  93. package/dist/src/resources/plugins.js +455 -0
  94. package/dist/src/resources/project-folders.d.ts +71 -0
  95. package/dist/src/resources/project-folders.js +57 -0
  96. package/dist/src/resources/project-git.d.ts +12 -12
  97. package/dist/src/resources/project-git.js +84 -82
  98. package/dist/src/resources/project-library.d.ts +0 -1
  99. package/dist/src/resources/project-library.js +1 -17
  100. package/dist/src/resources/projects.d.ts +23 -3
  101. package/dist/src/resources/projects.js +60 -34
  102. package/dist/src/resources/recipes.d.ts +15 -1
  103. package/dist/src/resources/recipes.js +73 -6
  104. package/dist/src/resources/saved-models.d.ts +134 -1
  105. package/dist/src/resources/saved-models.js +214 -6
  106. package/dist/src/resources/scenarios.d.ts +105 -0
  107. package/dist/src/resources/scenarios.js +232 -106
  108. package/dist/src/resources/sql.js +5 -1
  109. package/dist/src/resources/users.d.ts +129 -0
  110. package/dist/src/resources/users.js +128 -0
  111. package/dist/src/resources/webapps.d.ts +15 -2
  112. package/dist/src/resources/webapps.js +18 -4
  113. package/dist/src/schemas.d.ts +2 -2
  114. package/dist/src/schemas.js +1 -1
  115. package/dist/src/utils/deep-merge.d.ts +1 -1
  116. package/dist/src/utils/git-reference.d.ts +12 -0
  117. package/dist/src/utils/git-reference.js +38 -0
  118. package/dist/src/utils/polling.d.ts +20 -0
  119. package/dist/src/utils/polling.js +27 -0
  120. package/dist/src/utils/response-file.js +1 -1
  121. package/dist/src/utils/secret-sanitize.d.ts +54 -0
  122. package/dist/src/utils/secret-sanitize.js +101 -0
  123. package/package.json +9 -8
  124. package/packages/types/dist/index.d.ts +102 -0
  125. package/packages/types/dist/index.js +76 -0
  126. package/plugin.json +1 -1
  127. package/skills/dataiku-dss/SKILL.md +11 -7
  128. package/skills/dataiku-dss/references/app-releases.md +8 -8
  129. package/skills/dataiku-dss/references/authentication.md +6 -6
  130. package/skills/dataiku-dss/references/discovery.md +4 -4
  131. package/skills/dataiku-dss/references/mutations.md +1 -1
  132. package/skills/dataiku-dss/references/troubleshooting.md +2 -2
@@ -3,10 +3,11 @@ 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";
8
9
  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 { encodeGitReferencePath, validateGitReferencePath, } from "../utils/git-reference.js";
10
11
  import { jobBuildTargetTypeFromFlags, json, jsonInput, num, parseBooleanOption, parseJsonObject, recipeInputDatasetsFromFlags, requiredJsonInput, rewritePairsFromFlags, schemaColumnsInput, sha256Hex, stableHash, stringField, textInput, } from "./coerce.js";
11
12
  import { parseCodeRunIntegerFlag, resolveCodeInputWithSource, } from "./commands/code.js";
12
13
  import { commands, } from "./commands/index.js";
@@ -18,7 +19,7 @@ import { flowZoneColor, flowZoneMoveItems, flowZoneName, } from "./helpers/flow-
18
19
  import { recipeBackupPath, recipeRunShouldWait, } from "./helpers/recipe.js";
19
20
  import { encodedProjectEndpointForPlan, planResult, } from "./output.js";
20
21
  import { resolveLoginCredentials, } from "./runtime.js";
21
- import { requireArgs, UsageError, } from "./usage.js";
22
+ import { inferRequiresProject, requireArgs, UsageError, } from "./usage.js";
22
23
  import { AGENT_CONTRACT_SCHEMA_ID, AGENT_CONTRACT_VERSION, cliVersionResult, JSON_SCHEMA_DRAFT, } from "./version.js";
23
24
  function codeEnvWait(flags) {
24
25
  return flags["no-wait"] !== true;
@@ -39,19 +40,27 @@ function codeEnvParams(flags) {
39
40
  function splitPackageSpec(raw) {
40
41
  return raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
41
42
  }
43
+ /**
44
+ * Resolve requested package specs from --file/--packages/--package. An
45
+ * explicit source that resolves to zero specs is a legitimate clear
46
+ * (set-packages replaces specPackageList wholesale); only a call with no
47
+ * package source flag at all is a usage error.
48
+ */
42
49
  function codeEnvPackageList(flags) {
43
- const packages = [];
44
- if (typeof flags["file"] === "string") {
45
- packages.push(...splitPackageSpec(fs.readFileSync(flags["file"], "utf-8")));
46
- }
50
+ const file = flags["file"];
51
+ const packages = typeof file === "string"
52
+ ? splitPackageSpec(fs.readFileSync(file, "utf-8"))
53
+ : [];
47
54
  if (typeof flags["packages"] === "string") {
48
55
  packages.push(...splitPackageSpec(flags["packages"]));
49
56
  }
50
57
  if (typeof flags["package"] === "string") {
51
58
  packages.push(...splitPackageSpec(flags["package"]));
52
59
  }
53
- if (packages.length === 0) {
54
- throw new UsageError("--packages, --package, or --file is required. Use newline-separated package specs for version constraints.");
60
+ if (typeof file !== "string"
61
+ && typeof flags["packages"] !== "string"
62
+ && typeof flags["package"] !== "string") {
63
+ throw new UsageError("--packages, --package, or --file is required. Use newline-separated package specs for version constraints; an explicitly empty value (or empty file) clears the requested package list.");
55
64
  }
56
65
  return packages;
57
66
  }
@@ -127,26 +136,31 @@ const READ_ACTIONS = new Set([
127
136
  "usages",
128
137
  "verify-instance",
129
138
  ]);
130
- const PROJECT_SCOPED_RESOURCES = new Set([
131
- "analysis",
132
- "data-quality",
133
- "dashboard",
134
- "dataset",
135
- "flow-zone",
136
- "insight",
137
- "folder",
138
- "fixtures",
139
- "job",
140
- "notebook",
141
- "ml-task",
142
- "model-evaluation-store",
143
- "recipe",
144
- "scenario",
145
- "sql",
146
- "variable",
147
- "saved-model",
148
- "wiki",
149
- ]);
139
+ /**
140
+ * Security admin actions that hand back a DSS future instead of settling
141
+ * inline: resyncs mint sync jobs, external-user/group fetches mint a
142
+ * supplier-enumeration job, and provision runs a supplier import. Without
143
+ * this table they fall through to the read default and `--plan` rejects
144
+ * them as non-mutating.
145
+ */
146
+ const ADMIN_SECURITY_FUTURE_ACTIONS = {
147
+ "external-groups": true,
148
+ "external-users": true,
149
+ provision: true,
150
+ resync: true,
151
+ "resync-multi": true,
152
+ };
153
+ /** Connection tables-import actions return a DSS future reference. */
154
+ const CONNECTION_FUTURE_ACTIONS = {
155
+ "execute-import": true,
156
+ "prepare-import": true,
157
+ };
158
+ /** Plugin actions that only observe state despite POST shape (documented). */
159
+ const PLUGIN_GIT_OBSERVER_ACTIONS = {
160
+ "git-branches": true,
161
+ list: true,
162
+ usages: true,
163
+ };
150
164
  /**
151
165
  * Project Git actions that only observe repository state. Everything else in the
152
166
  * `project-git` resource is a mutation: the verb-shaped names (`fetch`, `pull`,
@@ -185,6 +199,17 @@ const PROJECT_GIT_DESTRUCTIVE_ACTIONS = {
185
199
  "revert-commit": true,
186
200
  "revert-to-revision": true,
187
201
  };
202
+ /** Plugin actions that discard work or publish it beyond the instance. */
203
+ const PLUGIN_DESTRUCTIVE_ACTIONS = {
204
+ "contents-delete": true,
205
+ delete: true,
206
+ "delete-git-remote": true,
207
+ fetch: true,
208
+ pull: true,
209
+ push: true,
210
+ "reset-local": true,
211
+ "reset-remote": true,
212
+ };
188
213
  /** Project Git actions that hand back a DSS future (`{jobId}`) instead of a result. */
189
214
  const PROJECT_GIT_FUTURE_ACTIONS = {
190
215
  "add-library": true,
@@ -409,200 +434,18 @@ function structuredExamples(examples, examplePayload) {
409
434
  };
410
435
  });
411
436
  }
412
- const PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA = {
413
- type: "object",
414
- additionalProperties: false,
415
- required: ["name",],
416
- properties: {
417
- name: { type: "string", },
418
- path: { type: "string", },
419
- size: { type: "integer", minimum: 0, },
420
- mimeType: { type: "string", },
421
- hasData: { type: "boolean", },
422
- lastModified: { type: "number", },
423
- children: { type: "array", items: { type: "object", additionalProperties: true, }, },
424
- },
425
- };
426
- const NOTEBOOK_SAVE_OUTPUT_SCHEMA = {
427
- type: "object",
428
- additionalProperties: false,
429
- required: ["saved", "resource", "created", "hash",],
430
- properties: {
431
- saved: { type: "string", },
432
- resource: { enum: ["jupyter-notebook", "sql-notebook",], },
433
- created: { type: "boolean", },
434
- hash: { type: "string", pattern: "^[a-f0-9]{64}$", },
435
- },
436
- };
437
- const COMMAND_OUTPUT_SCHEMAS = {
438
- "code-env.list": CodeEnvSummaryArraySchema,
439
- "code-env.get": CodeEnvDetailsSchema,
440
- "code-env.list-logs": CodeEnvLogSummaryArraySchema,
441
- "code-env.get-log": {
442
- oneOf: [
443
- {
444
- type: "object",
445
- required: ["log", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
446
- properties: {
447
- log: { type: "string", },
448
- bytes: { type: "integer", minimum: 0, },
449
- truncated: { type: "boolean", },
450
- tailed: { type: "boolean", },
451
- envLang: { enum: ["PYTHON", "R",], },
452
- envName: { type: "string", },
453
- logName: { type: "string", },
454
- },
455
- },
456
- {
457
- type: "object",
458
- required: ["path", "bytes", "truncated", "tailed", "envLang", "envName", "logName",],
459
- properties: {
460
- path: { type: "string", },
461
- bytes: { type: "integer", minimum: 0, },
462
- truncated: { type: "boolean", },
463
- tailed: { type: "boolean", },
464
- envLang: { enum: ["PYTHON", "R",], },
465
- envName: { type: "string", },
466
- logName: { type: "string", },
467
- },
468
- },
469
- ],
470
- },
471
- "code-env.version": CodeEnvVersionForProjectSchema,
472
- "code-env.usages": CodeEnvUsageArraySchema,
473
- "code.run": {
474
- type: "object",
475
- additionalProperties: false,
476
- required: [
477
- "outcome",
478
- "success",
479
- "runId",
480
- "elapsedMs",
481
- "pollCount",
482
- "output",
483
- "logTruncated",
484
- "maxLogBytes",
485
- "cleanup",
486
- ],
487
- properties: {
488
- outcome: { type: "string", },
489
- success: { type: "boolean", },
490
- runId: { type: "string", },
491
- elapsedMs: { type: "number", minimum: 0, },
492
- pollCount: { type: "integer", minimum: 0, },
493
- output: { type: "string", },
494
- log: { type: "string", },
495
- logTruncated: { type: "boolean", },
496
- maxLogBytes: { type: "integer", minimum: 0, },
497
- timedOut: { const: true, },
498
- timeoutMs: { type: "integer", minimum: 0, },
499
- cleanup: {
500
- type: "object",
501
- additionalProperties: false,
502
- required: ["status",],
503
- properties: {
504
- status: { enum: ["deleted", "kept", "failed",], },
505
- error: { type: "string", },
506
- },
507
- },
508
- },
509
- },
510
- "notebook.list-jupyter": JupyterNotebookSummaryArraySchema,
511
- "notebook.get-jupyter": JupyterNotebookContentSchema,
512
- "notebook.sessions-jupyter": NotebookSessionArraySchema,
513
- "notebook.list-sql": SqlNotebookSummaryArraySchema,
514
- "notebook.get-sql": SqlNotebookContentSchema,
515
- "notebook.history-sql": SqlNotebookHistorySchema,
516
- "notebook.save-jupyter": NOTEBOOK_SAVE_OUTPUT_SCHEMA,
517
- "notebook.save-sql": NOTEBOOK_SAVE_OUTPUT_SCHEMA,
518
- "project-library.list": { type: "array", items: PROJECT_LIBRARY_ITEM_OUTPUT_SCHEMA, },
519
- "project-library.get-bytes": {
520
- type: "object",
521
- additionalProperties: false,
522
- required: ["path", "bytes", "sha256",],
523
- properties: {
524
- path: { type: "string", },
525
- bytes: { type: "integer", minimum: 0, },
526
- sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
527
- },
528
- },
529
- "project-library.put": {
530
- type: "object",
531
- additionalProperties: false,
532
- required: ["updated", "bytes", "sha256",],
533
- properties: {
534
- updated: { type: "string", },
535
- bytes: { type: "integer", minimum: 0, },
536
- sha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
537
- beforeSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
538
- },
539
- },
540
- "project-library.diff": {
541
- type: "object",
542
- additionalProperties: false,
543
- required: [
544
- "path",
545
- "unchanged",
546
- "added",
547
- "removed",
548
- "diff",
549
- "diffTruncated",
550
- "localSha256",
551
- "localBytes",
552
- "maxLines",
553
- ],
554
- properties: {
555
- path: { type: "string", },
556
- unchanged: { type: "boolean", },
557
- added: { type: "integer", minimum: 0, },
558
- removed: { type: "integer", minimum: 0, },
559
- diff: { type: "string", },
560
- diffTruncated: { type: "boolean", },
561
- binary: { type: "boolean", },
562
- remoteAbsent: { type: "boolean", },
563
- remoteSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
564
- remoteBytes: { type: "integer", minimum: 0, },
565
- localSha256: { type: "string", pattern: "^[a-f0-9]{64}$", },
566
- localBytes: { type: "integer", minimum: 0, },
567
- maxLines: { type: "integer", minimum: 0, },
568
- },
569
- },
570
- "project.list": ProjectSummaryArraySchema,
571
- "project.get": ProjectDetailsSchema,
572
- "project.metadata": ProjectMetadataSchema,
573
- "dataset.list": DatasetSummaryArraySchema,
574
- "dataset.get": DatasetDetailsSchema,
575
- "dataset.schema": DatasetSchemaSchema,
576
- "recipe.list": RecipeSummaryArraySchema,
577
- "job.list": JobSummaryArraySchema,
578
- "job.wait": JobWaitResultSchema,
579
- "job.monitor": JobWaitResultSchema,
580
- "scenario.list": ScenarioSummaryArraySchema,
581
- "scenario.get": ScenarioDetailsSchema,
582
- "scenario.status": ScenarioStatusSchema,
583
- "flow-zone.list": FlowZoneArraySchema,
584
- "flow-zone.get": FlowZoneSchema,
585
- "sql.query": {
586
- anyOf: [
587
- SqlQueryResponseSchema,
588
- {
589
- type: "object",
590
- required: ["queryId", "rowCount", "preview",],
591
- additionalProperties: true,
592
- properties: {
593
- queryId: { type: "string", },
594
- rowCount: { type: "number", },
595
- preview: { type: "array", items: true, },
596
- truncated: { type: "boolean", },
597
- outputPath: { type: "string", },
598
- written: { type: "string", },
599
- },
600
- },
601
- ],
602
- },
603
- };
437
+ /**
438
+ * Discovery output schemas: the generated declarative metadata committed at
439
+ * `src/generated/action-output-schemas.json`. Plain data only, so discovery
440
+ * (`dss commands run`, `dss agent contract`) never loads the TypeBox graph.
441
+ * The content matches `typeBoxCommandOutputSchemas()` exactly; parity and
442
+ * freshness are enforced by tests/cli/discovery-output-schemas.test.ts and
443
+ * `bun run check`.
444
+ */
445
+ const actionOutputSchemas = actionOutputSchemasMetadata
446
+ .schemas;
604
447
  function outputJsonSchema(resource, action, shape) {
605
- const precise = COMMAND_OUTPUT_SCHEMAS[registryKey(resource, action)];
448
+ const precise = actionOutputSchemas[registryKey(resource, action)];
606
449
  if (precise)
607
450
  return precise;
608
451
  if (shape === "array")
@@ -916,10 +759,33 @@ function inferSideEffect(resource, action) {
916
759
  return "read";
917
760
  if (resource === "data-quality" && action === "compute")
918
761
  return "write";
762
+ if (resource === "user" && ADMIN_SECURITY_FUTURE_ACTIONS[action] === true)
763
+ return "write";
764
+ if (resource === "connection" && CONNECTION_FUTURE_ACTIONS[action] === true)
765
+ return "write";
766
+ // Saved-model evaluation triggers scoring work on DSS side (cost-bearing).
767
+ if (resource === "saved-model" && action === "evaluate-version")
768
+ return "write";
769
+ // LLM Mesh completions/embeddings invoke the LLM provider (cost-bearing,
770
+ // arbitrary-prompt execution); the verb shapes match no mutating pattern
771
+ // and would fall through to read, wrongly rejecting --plan. Knowledge-bank
772
+ // search stays a READ per Main's preservation directive.
773
+ if (resource === "llm" && (action === "completions" || action === "embeddings")) {
774
+ return "write";
775
+ }
919
776
  // Project Git is classified from the explicit read table, not by verb shape:
920
777
  // `switch`, `fetch`, `pull`, `push`, `commit`, the `reset-*`/`revert-*`
921
778
  // families, `drop-and-rebuild`, and `future-abort` all mutate repository or
922
779
  // future state yet match none of the mutating-verb patterns.
780
+ // Plugin dev-Git actions mutate repository/plugin state like project-git:
781
+ // `fetch`/`pull`/`push`/`reset-*` never match the generic mutating-verb
782
+ // regex, so they are classified explicitly as write (destructive level
783
+ // from the explicit table below). `git-branches` is a state observer
784
+ // (observed GET on DSS 15; the upstream docs' POST is wrong) and stays
785
+ // read.
786
+ if (resource === "plugin") {
787
+ return PLUGIN_GIT_OBSERVER_ACTIONS[action] === true ? "read" : "write";
788
+ }
923
789
  if (resource === "project-git") {
924
790
  return PROJECT_GIT_READ_ACTIONS[action] === true ? "read" : "write";
925
791
  }
@@ -927,8 +793,9 @@ function inferSideEffect(resource, action) {
927
793
  return "read";
928
794
  if (/^(create|clone|restore|update|delete|set|save|upload|run|build|abort|move|refresh|clear|unload|install|login|logout|add|remove|publish|activate|deploy|import|export|preload|upgrade|start|stop|restart|duplicate|put|rename|reply|compute|organize)/
929
795
  .test(action)
930
- // Compound actions whose mutating verb is a suffix (e.g. permissions-set, dataset-compute).
931
- || /-(set|compute|restore)$/.test(action)) {
796
+ // Compound actions whose mutating verb is a suffix (e.g. permissions-set,
797
+ // dataset-compute, saved-model external-metadata-put).
798
+ || /-(set|compute|restore|put)$/.test(action)) {
932
799
  return "write";
933
800
  }
934
801
  return "read";
@@ -940,19 +807,6 @@ function inferRequiresAuth(resource) {
940
807
  && resource !== "install-skill"
941
808
  && resource !== "version";
942
809
  }
943
- export function inferRequiresProject(resource, action, usage) {
944
- if (resource === "agent" || resource === "auth" || resource === "doctor" || resource === "commands"
945
- || resource === "install-skill" || resource === "version") {
946
- return false;
947
- }
948
- if (resource === "sql" && action === "query")
949
- return false;
950
- if (PROJECT_SCOPED_RESOURCES.has(resource))
951
- return true;
952
- if (resource === "project-git")
953
- return !action.startsWith("future-");
954
- return usage.includes("--project-key");
955
- }
956
810
  const ARRAY_OUTPUT_ACTIONS = new Set([
957
811
  "history",
958
812
  "find",
@@ -1166,7 +1020,7 @@ function cleanupCommandFromDeleteUsage(resource, action) {
1166
1020
  return "dss notebook delete-jupyter <name> --if-exists";
1167
1021
  }
1168
1022
  if (`${resource}.${action}` === "notebook.save-sql") {
1169
- return "dss notebook delete-sql <id> --if-exists";
1023
+ return "dss notebook delete-sql <the returned `saved` id> --if-exists";
1170
1024
  }
1171
1025
  if (`${resource}.${action}` === "project.import") {
1172
1026
  return "dss project delete <used-project-key> --if-exists --expect-project-incarnation <hash>";
@@ -1203,12 +1057,29 @@ export function supportsCleanupLedger(resource, action) {
1203
1057
  const EXPLICIT_DESTRUCTIVE_KEYS = {
1204
1058
  "dataset.upload-file": true,
1205
1059
  "sql.query": true,
1060
+ // Plugin dev-Git and content deletions discard work with no undo route.
1061
+ "plugin.delete": true,
1062
+ "plugin.delete-git-remote": true,
1063
+ "plugin.push": true,
1064
+ "plugin.pull": true,
1065
+ "plugin.fetch": true,
1066
+ "plugin.reset-local": true,
1067
+ "plugin.reset-remote": true,
1068
+ "plugin.contents-delete": true,
1069
+ // Macro runs execute arbitrary plugin code.
1070
+ "macro.run": true,
1071
+ "macro.run-and-wait": true,
1072
+ // Saved-model version deletion removes model versions permanently.
1073
+ "saved-model.delete-versions": true,
1206
1074
  };
1207
1075
  function inferDestructiveLevel(resource, sideEffect, action) {
1208
1076
  if (sideEffect !== "write")
1209
1077
  return "none";
1210
1078
  if (EXPLICIT_DESTRUCTIVE_KEYS[`${resource}.${action}`] === true)
1211
1079
  return "destructive";
1080
+ if (resource === "plugin") {
1081
+ return PLUGIN_DESTRUCTIVE_ACTIONS[action] === true ? "destructive" : "reversible";
1082
+ }
1212
1083
  if (resource === "project-git") {
1213
1084
  return PROJECT_GIT_DESTRUCTIVE_ACTIONS[action] === true ? "destructive" : "reversible";
1214
1085
  }
@@ -1229,6 +1100,10 @@ function inferAsyncKind(resource, action) {
1229
1100
  if (resource === "scenario" && ["run", "run-and-wait", "status",].includes(action)) {
1230
1101
  return "future";
1231
1102
  }
1103
+ if (resource === "user" && ADMIN_SECURITY_FUTURE_ACTIONS[action] === true)
1104
+ return "future";
1105
+ if (resource === "connection" && CONNECTION_FUTURE_ACTIONS[action] === true)
1106
+ return "future";
1232
1107
  if (resource === "data-quality" && action === "compute")
1233
1108
  return "future";
1234
1109
  // Project Git mutation results carry a future job id only for the library
@@ -1296,7 +1171,7 @@ export function inferCleanupHint(resource, action) {
1296
1171
  return "If created:true, delete with `dss notebook delete-jupyter <name> --if-exists`.";
1297
1172
  }
1298
1173
  if (key === "notebook.save-sql") {
1299
- return "If created:true, delete with `dss notebook delete-sql <id> --if-exists`.";
1174
+ return "If created:true, the persisted id is in `saved`; delete with `dss notebook delete-sql <saved> --if-exists`.";
1300
1175
  }
1301
1176
  if (!(action.startsWith("create") || action === "clone"))
1302
1177
  return undefined;
@@ -1392,6 +1267,7 @@ function buildRegistryEntry(resource, action, meta) {
1392
1267
  const uniqueRequiredFlags = uniqueStrings(requiredFlags);
1393
1268
  const uniqueOptionalFlags = uniqueStrings(optionalFlags);
1394
1269
  const unsafe = unsafeOutputs(resource, action, producesLocalFile);
1270
+ let schemas;
1395
1271
  return {
1396
1272
  resource,
1397
1273
  action,
@@ -1418,7 +1294,9 @@ function buildRegistryEntry(resource, action, meta) {
1418
1294
  ...(requiredOneOf.length > 0 ? { requiredOneOf, } : {}),
1419
1295
  ...(inputGroups.length > 0 ? { requiredInputGroups: inputGroups, } : {}),
1420
1296
  ...(payloadSchema ? { payloadSchema, } : {}),
1421
- schemas: buildCommandSchemas(resource, action, flagMetadata, uniqueRequiredFlags, requiredOneOf, payloadSchema, outputShape, meta.usage),
1297
+ get schemas() {
1298
+ return schemas ??= buildCommandSchemas(resource, action, flagMetadata, uniqueRequiredFlags, requiredOneOf, payloadSchema, outputShape, meta.usage);
1299
+ },
1422
1300
  ...(unsafe ? { unsafeOutputs: unsafe, } : {}),
1423
1301
  ...(examplePayload !== undefined ? { examplePayload, } : {}),
1424
1302
  ...(cleanupCommand ? { cleanupCommand, } : {}),
@@ -1427,97 +1305,109 @@ function buildRegistryEntry(resource, action, meta) {
1427
1305
  agentContractVersion: AGENT_CONTRACT_VERSION,
1428
1306
  };
1429
1307
  }
1430
- export function buildCommandRegistry(resourceFilter) {
1308
+ function commandDefinitions(resourceFilter) {
1431
1309
  const registry = {};
1432
- for (const [resource, actions,] of Object.entries(commands)) {
1310
+ for (const resource of Object.keys(commands)) {
1433
1311
  if (resourceFilter !== undefined && resource !== resourceFilter)
1434
1312
  continue;
1435
- registry[resource] = {};
1436
- for (const [action, meta,] of Object.entries(actions)) {
1437
- registry[resource][action] = buildRegistryEntry(resource, action, meta);
1438
- }
1313
+ registry[resource] = commands[resource];
1439
1314
  }
1440
1315
  if (resourceFilter === undefined || resourceFilter === "commands") {
1441
1316
  registry.commands = {
1442
- run: buildRegistryEntry("commands", "run", {
1317
+ run: {
1443
1318
  handler: async () => undefined,
1444
1319
  usage: COMMANDS_USAGE,
1445
1320
  description: COMMANDS_DESCRIPTION,
1446
1321
  examples: COMMANDS_EXAMPLES,
1447
- }),
1322
+ },
1448
1323
  };
1449
1324
  }
1450
1325
  if (resourceFilter === undefined || resourceFilter === "agent") {
1451
1326
  registry.agent = {
1452
- contract: buildRegistryEntry("agent", "contract", {
1327
+ contract: {
1453
1328
  handler: async () => undefined,
1454
1329
  usage: AGENT_CONTRACT_USAGE,
1455
1330
  description: AGENT_CONTRACT_DESCRIPTION,
1456
1331
  examples: AGENT_CONTRACT_EXAMPLES,
1457
- }),
1332
+ },
1458
1333
  };
1459
1334
  }
1460
1335
  if (resourceFilter === undefined || resourceFilter === "version") {
1461
1336
  registry.version = {
1462
- run: buildRegistryEntry("version", "run", {
1337
+ run: {
1463
1338
  handler: async () => undefined,
1464
1339
  usage: VERSION_USAGE,
1465
1340
  description: VERSION_DESCRIPTION,
1466
1341
  examples: VERSION_EXAMPLES,
1467
- }),
1342
+ },
1468
1343
  };
1469
1344
  }
1470
1345
  if (resourceFilter === undefined || resourceFilter === "install-skill") {
1471
1346
  registry["install-skill"] = {
1472
- run: buildRegistryEntry("install-skill", "run", {
1347
+ run: {
1473
1348
  handler: async () => undefined,
1474
1349
  usage: INSTALL_SKILL_USAGE,
1475
1350
  description: INSTALL_SKILL_DESCRIPTION,
1476
1351
  examples: INSTALL_SKILL_EXAMPLES,
1477
- }),
1352
+ },
1478
1353
  };
1479
1354
  }
1480
1355
  if (resourceFilter === undefined || resourceFilter === "cleanup") {
1481
1356
  registry.cleanup = {
1482
- run: buildRegistryEntry("cleanup", "run", {
1357
+ run: {
1483
1358
  handler: async () => undefined,
1484
1359
  usage: CLEANUP_USAGE,
1485
1360
  description: CLEANUP_DESCRIPTION,
1486
1361
  examples: CLEANUP_EXAMPLES,
1487
- }),
1362
+ },
1488
1363
  };
1489
1364
  }
1490
1365
  if (resourceFilter === undefined || resourceFilter === "fixtures") {
1491
1366
  registry.fixtures = {
1492
- run: buildRegistryEntry("fixtures", "run", {
1367
+ run: {
1493
1368
  handler: async () => undefined,
1494
1369
  usage: FIXTURES_USAGE,
1495
1370
  description: FIXTURES_DESCRIPTION,
1496
1371
  examples: FIXTURES_EXAMPLES,
1497
- }),
1372
+ },
1498
1373
  };
1499
1374
  }
1500
1375
  if (resourceFilter === undefined || resourceFilter === "batch") {
1501
1376
  registry.batch = {
1502
- run: buildRegistryEntry("batch", "run", {
1377
+ run: {
1503
1378
  handler: async () => undefined,
1504
1379
  usage: BATCH_USAGE,
1505
1380
  description: BATCH_DESCRIPTION,
1506
1381
  examples: BATCH_EXAMPLES,
1507
1382
  examplePayload: BATCH_EXAMPLE_PAYLOAD,
1508
1383
  payloadSchema: { stdin: true, dataFlag: true, dataFileFlag: true, jsonShape: "array", },
1509
- }),
1384
+ },
1510
1385
  };
1511
1386
  }
1512
1387
  if (resourceFilter === undefined || resourceFilter === "auth") {
1513
1388
  registry.auth = {};
1514
1389
  for (const [action, meta,] of Object.entries(AUTH_ACTIONS)) {
1515
- registry.auth[action] = buildRegistryEntry("auth", action, {
1390
+ registry.auth[action] = {
1516
1391
  handler: async () => undefined,
1517
1392
  usage: meta.usage,
1518
1393
  description: meta.description,
1519
1394
  examples: meta.examples,
1520
1395
  requiredFlags: meta.requiredFlags,
1396
+ };
1397
+ }
1398
+ }
1399
+ return registry;
1400
+ }
1401
+ export function buildCommandRegistry(resourceFilter) {
1402
+ const registry = {};
1403
+ for (const [resource, actions,] of Object.entries(commandDefinitions(resourceFilter))) {
1404
+ const entries = {};
1405
+ registry[resource] = entries;
1406
+ for (const [action, meta,] of Object.entries(actions)) {
1407
+ let entry;
1408
+ Object.defineProperty(entries, action, {
1409
+ enumerable: true,
1410
+ get: () => entry ??= buildRegistryEntry(resource, action, meta),
1521
1411
  });
1522
1412
  }
1523
1413
  }
@@ -1676,38 +1566,43 @@ export function agentContractJsonSchema() {
1676
1566
  },
1677
1567
  };
1678
1568
  }
1679
- export function commandActionSummary(registry) {
1569
+ export function commandActionSummary() {
1680
1570
  const summary = {};
1681
- for (const [resource, actions,] of Object.entries(registry)) {
1571
+ for (const [resource, actions,] of Object.entries(commandDefinitions())) {
1682
1572
  summary[resource] = Object.keys(actions).sort();
1683
1573
  }
1684
1574
  return summary;
1685
1575
  }
1686
1576
  export function buildAgentContract() {
1687
- const registry = buildCommandRegistry();
1577
+ let commandSection;
1578
+ let schemaSection;
1688
1579
  return {
1689
1580
  protocol: "dataiku-sdk-agent",
1690
1581
  agentContractVersion: AGENT_CONTRACT_VERSION,
1691
1582
  cli: cliVersionResult(),
1692
- commands: {
1693
- discoveryCommand: "dss commands run",
1694
- fullRegistryExportCommand: "dss commands run --output PATH",
1695
- scopedDiscoveryCommand: "dss commands run --fields RESOURCE[.ACTION[.FIELD...]]",
1696
- actionIndexCommand: "dss agent contract --fields commands.actions",
1697
- scopedDiscoveryExamples: [
1698
- "dss commands run --fields dataset",
1699
- "dss commands run --fields dataset.create",
1700
- ],
1701
- scopedDiscoveryHint: "Default: resource/action summary. --fields RESOURCE: all resource entries; RESOURCE.ACTION: one entry keyed by that path; append .FIELD for nested metadata. Comma-separate paths. --output PATH exports the full registry.",
1702
- actions: commandActionSummary(registry),
1583
+ get commands() {
1584
+ return commandSection ??= {
1585
+ discoveryCommand: "dss commands run",
1586
+ fullRegistryExportCommand: "dss commands run --output PATH",
1587
+ scopedDiscoveryCommand: "dss commands run --fields RESOURCE[.ACTION[.FIELD...]]",
1588
+ actionIndexCommand: "dss agent contract --fields commands.actions",
1589
+ scopedDiscoveryExamples: [
1590
+ "dss commands run --fields dataset",
1591
+ "dss commands run --fields dataset.create",
1592
+ ],
1593
+ scopedDiscoveryHint: "Default: resource/action summary. --fields RESOURCE: all resource entries; RESOURCE.ACTION: one entry keyed by that path; append .FIELD for nested metadata. Comma-separate paths. --output PATH exports the full registry.",
1594
+ actions: commandActionSummary(),
1595
+ };
1703
1596
  },
1704
- schemas: {
1705
- agentContract: agentContractJsonSchema(),
1706
- commandRegistry: commandRegistryJsonSchema(),
1707
- commandRegistryEntry: commandRegistryEntryJsonSchema(),
1708
- errorEnvelope: errorEnvelopeJsonSchema(),
1709
- warningEvent: warningEventJsonSchema(),
1710
- traceEvent: traceEventJsonSchema(),
1597
+ get schemas() {
1598
+ return schemaSection ??= {
1599
+ agentContract: agentContractJsonSchema(),
1600
+ commandRegistry: commandRegistryJsonSchema(),
1601
+ commandRegistryEntry: commandRegistryEntryJsonSchema(),
1602
+ errorEnvelope: errorEnvelopeJsonSchema(),
1603
+ warningEvent: warningEventJsonSchema(),
1604
+ traceEvent: traceEventJsonSchema(),
1605
+ };
1711
1606
  },
1712
1607
  stdio: {
1713
1608
  stdout: {
@@ -1795,6 +1690,19 @@ function requiredPlanFlag(flags, name, usage) {
1795
1690
  return value.trim();
1796
1691
  throw new UsageError(`--${name} is required. Usage: ${usage}`);
1797
1692
  }
1693
+ /**
1694
+ * Optional caller-chosen identifier. Absent selects the command's generated
1695
+ * mode; a present-but-empty value is an input error, never a silent omission.
1696
+ */
1697
+ function optionalPlanFlag(flags, name, usage) {
1698
+ const value = flags[name];
1699
+ if (value === undefined)
1700
+ return undefined;
1701
+ if (typeof value !== "string" || value.trim().length === 0) {
1702
+ throw new UsageError(`--${name} must not be empty when supplied; omit --${name} entirely to generate the successor key during apply. Usage: ${usage}`, "validation_failed");
1703
+ }
1704
+ return value.trim();
1705
+ }
1798
1706
  function optionalJsonFlag(flags, name) {
1799
1707
  const value = flags[name];
1800
1708
  return typeof value === "string" ? parseJsonObject(value, `--${name}`) : undefined;
@@ -1819,6 +1727,9 @@ function querySuffix(params) {
1819
1727
  const raw = search.toString();
1820
1728
  return raw ? `?${raw}` : "";
1821
1729
  }
1730
+ function projectFolderEndpoint(folderId) {
1731
+ return `/public/api/project-folders/${encodeURIComponent(folderId)}`;
1732
+ }
1822
1733
  /**
1823
1734
  * Git and future API mount. The official Python client mounts these routes on
1824
1735
  * `/dip/publicapi`, which is also what the project-git resource uses; do not
@@ -1863,6 +1774,92 @@ function validatedPlanRepositoryUrl(url, flag, usage) {
1863
1774
  }
1864
1775
  return candidate;
1865
1776
  }
1777
+ function pluginRootEndpoint(pluginId, suffix) {
1778
+ return `/public/api/plugins/${encodeURIComponent(pluginId)}${suffix}`;
1779
+ }
1780
+ function pluginActionEndpoint(pluginId, action) {
1781
+ return pluginRootEndpoint(pluginId, `/actions/${action}`);
1782
+ }
1783
+ function pluginContentsEndpoint(pluginId, contentPath) {
1784
+ return `${pluginRootEndpoint(pluginId, "/contents/")}${encodePluginSegmentsForPlan(contentPath)}`;
1785
+ }
1786
+ function pluginFoldersEndpoint(pluginId, contentPath) {
1787
+ return `${pluginRootEndpoint(pluginId, "/folders/")}${encodePluginSegmentsForPlan(contentPath)}`;
1788
+ }
1789
+ /** Encodes a plugin content path per segment for plan-time endpoints. */
1790
+ function encodePluginSegmentsForPlan(contentPath) {
1791
+ return contentPath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
1792
+ }
1793
+ /**
1794
+ * Plan payload for plugin git install/update bodies. The repository URL is
1795
+ * validated (embedded userinfo rejected) and secrets are never echoed: the
1796
+ * plan carries the URL only, never embedded credentials.
1797
+ */
1798
+ function pluginGitPlanPayload(flags) {
1799
+ const repository = requiredPlanFlag(flags, "repository", "dss plugin install-from-git --repository URL");
1800
+ const payload = {
1801
+ gitRepositoryUrl: validatedPlanRepositoryUrl(repository, "repository", "--repository URL"),
1802
+ };
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
+ }
1810
+ function optionalPlanProjectScope(flags) {
1811
+ const projectKey = flags["project-key"];
1812
+ if (typeof projectKey !== "string" || projectKey.trim() === "")
1813
+ return "";
1814
+ return `?projectKey=${encodeURIComponent(projectKey.trim())}`;
1815
+ }
1816
+ function settingsConfigKeys(flags) {
1817
+ const content = flags["content"];
1818
+ if (typeof content !== "string")
1819
+ return [];
1820
+ try {
1821
+ const parsed = JSON.parse(content);
1822
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
1823
+ return [];
1824
+ return Object.keys(parsed);
1825
+ }
1826
+ catch {
1827
+ return [];
1828
+ }
1829
+ }
1830
+ function contentSourceKind(flags) {
1831
+ if (flags["stdin"] === true)
1832
+ return "stdin";
1833
+ if (typeof flags["file"] === "string")
1834
+ return "file";
1835
+ if (typeof flags["content"] === "string")
1836
+ return "content";
1837
+ return "unknown";
1838
+ }
1839
+ /**
1840
+ * Plan-safe view of a user create/update body: password values are replaced by
1841
+ * a fixed marker so secrets never appear in --plan output.
1842
+ */
1843
+ function redactedUserPayload(payload) {
1844
+ if (payload["password"] === undefined)
1845
+ return payload;
1846
+ return { ...payload, password: "<omitted>", };
1847
+ }
1848
+ /**
1849
+ * Plan-safe view of a connection create/update body: the connection-type
1850
+ * specific `params` object may carry credentials (passwords, keys, tokens), so
1851
+ * the plan carries only the marker, never the raw params.
1852
+ */
1853
+ function redactedConnectionPayload(payload) {
1854
+ if (payload["params"] === undefined)
1855
+ return payload;
1856
+ return { ...payload, params: "<omitted; may include credentials>", };
1857
+ }
1858
+ /** Required CSV flag for plans (comma-separated logins list). */
1859
+ function requiredPlanCsv(flags, name, usage) {
1860
+ const raw = requiredPlanFlag(flags, name, usage);
1861
+ return raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1862
+ }
1866
1863
  function requiredPlanRepositoryUrl(flags, flag, usage) {
1867
1864
  const url = requiredPlanFlag(flags, flag, usage);
1868
1865
  return validatedPlanRepositoryUrl(url, flag, usage);
@@ -2092,8 +2089,8 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2092
2089
  };
2093
2090
  case "future.abort":
2094
2091
  return {
2095
- method: "POST",
2096
- endpoint: `/public/api/futures/${encodeURIComponent(id)}/abort`,
2092
+ method: "DELETE",
2093
+ endpoint: `/public/api/futures/${encodeURIComponent(id)}`,
2097
2094
  identifiers: { id, },
2098
2095
  };
2099
2096
  case "flow-zone.create": {
@@ -2221,8 +2218,38 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2221
2218
  method: "PUT",
2222
2219
  endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}`),
2223
2220
  identifiers: { name: id, },
2221
+ };
2222
+ case "dataset.metadata-set":
2223
+ return {
2224
+ method: "PUT",
2225
+ endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/metadata`),
2226
+ identifiers: { name: id, },
2224
2227
  payload: requiredPlanJsonInput(flags, entry.usage),
2225
2228
  };
2229
+ case "dataset.create-managed": {
2230
+ const name = requiredPlanFlag(flags, "name", entry.usage);
2231
+ const connection = requiredPlanFlag(flags, "connection", entry.usage);
2232
+ const creationSettings = { connectionId: connection, };
2233
+ const specificSettings = {};
2234
+ if (typeof flags["type-option-id"] === "string") {
2235
+ creationSettings.typeOptionId = flags["type-option-id"];
2236
+ }
2237
+ if (typeof flags["format-option-id"] === "string") {
2238
+ specificSettings.formatOptionId = flags["format-option-id"];
2239
+ }
2240
+ if (typeof flags["copy-partitioning-from"] === "string") {
2241
+ specificSettings.partitioningOptionId = `copy:${flags["partitioning-folder"] === true ? "folder" : "dataset"}:${flags["copy-partitioning-from"]}`;
2242
+ }
2243
+ if (Object.keys(specificSettings).length > 0) {
2244
+ creationSettings.specificSettings = specificSettings;
2245
+ }
2246
+ return {
2247
+ method: "POST",
2248
+ endpoint: projectEndpoint("/datasets/managed"),
2249
+ identifiers: { name, },
2250
+ payload: { name, creationSettings, },
2251
+ };
2252
+ }
2226
2253
  case "recipe.add-input":
2227
2254
  case "recipe.remove-input": {
2228
2255
  const role = flags["role"] ?? "main";
@@ -2411,6 +2438,42 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2411
2438
  payload: jobBuildPayload(id, projectKey, flags),
2412
2439
  wait: action === "build-and-wait" || flags["wait"] === true,
2413
2440
  };
2441
+ case "scenario.abort":
2442
+ return {
2443
+ method: "POST",
2444
+ endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/abort`),
2445
+ identifiers: { id, },
2446
+ };
2447
+ case "scenario.payload-set":
2448
+ return {
2449
+ method: "PUT",
2450
+ endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/payload`),
2451
+ identifiers: { id, },
2452
+ payload: requiredPlanJsonInput(flags, entry.usage),
2453
+ };
2454
+ case "scenario.active-set": {
2455
+ const activeRaw = args[1];
2456
+ if (activeRaw !== "true" && activeRaw !== "false") {
2457
+ throw new UsageError(`Usage: ${entry.usage}`);
2458
+ }
2459
+ return {
2460
+ method: "PUT",
2461
+ endpoint: projectEndpoint(`/scenarios/${encodeURIComponent(id)}/light`),
2462
+ identifiers: { id, },
2463
+ payload: { id, active: activeRaw === "true", },
2464
+ };
2465
+ }
2466
+ case "folder.create": {
2467
+ const name = requiredPlanFlag(flags, "name", entry.usage);
2468
+ const type = flags["type"];
2469
+ const connection = flags["connection"];
2470
+ return {
2471
+ method: "POST",
2472
+ endpoint: projectEndpoint("/managedfolders/"),
2473
+ identifiers: { name, },
2474
+ payload: { name, type, connection, path: flags["path"], projectKey, },
2475
+ };
2476
+ }
2414
2477
  case "job.abort":
2415
2478
  return {
2416
2479
  method: "POST",
@@ -2451,17 +2514,6 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2451
2514
  identifiers: { id, },
2452
2515
  payload: requiredPlanJsonInput(flags, entry.usage),
2453
2516
  };
2454
- case "folder.create": {
2455
- const name = requiredPlanFlag(flags, "name", entry.usage);
2456
- const type = flags["type"];
2457
- const connection = flags["connection"];
2458
- return {
2459
- method: "POST",
2460
- endpoint: projectEndpoint("/managedfolders/"),
2461
- identifiers: { name, },
2462
- payload: { name, type, connection, path: flags["path"], projectKey, },
2463
- };
2464
- }
2465
2517
  case "folder.update":
2466
2518
  return {
2467
2519
  method: "PUT",
@@ -2686,6 +2738,577 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2686
2738
  endpoint: `/public/api/meanings/${encodeURIComponent(id)}`,
2687
2739
  identifiers: { id, },
2688
2740
  };
2741
+ case "llm.list": {
2742
+ const params = new URLSearchParams();
2743
+ const purpose = flags["purpose"];
2744
+ if (typeof purpose === "string" && purpose.trim() !== "") {
2745
+ params.set("purpose", purpose.trim());
2746
+ }
2747
+ const query = params.size > 0 ? `?${params.toString()}` : "";
2748
+ return {
2749
+ method: "GET",
2750
+ endpoint: projectEndpoint(`/llms/${query}`),
2751
+ };
2752
+ }
2753
+ case "llm.completions":
2754
+ case "llm.embeddings": {
2755
+ const payload = requiredPlanJsonInput(flags, entry.usage);
2756
+ const llmId = stringField(payload, ["llmId",]);
2757
+ if (llmId === undefined || llmId.trim() === "") {
2758
+ throw new UsageError(`llmId is required and must be a non-empty string (e.g. "openai:openai1:gpt-4").\nUsage: ${entry.usage}`, "invalid_flag_value");
2759
+ }
2760
+ const queries = payload["queries"];
2761
+ if (!Array.isArray(queries) || queries.length === 0) {
2762
+ throw new UsageError(`queries is required and must be a non-empty array of query objects.\nUsage: ${entry.usage}`, "invalid_flag_value");
2763
+ }
2764
+ return {
2765
+ method: "POST",
2766
+ endpoint: projectEndpoint(action === "completions" ? "/llms/completions" : "/llms/embeddings"),
2767
+ identifiers: { llmId, },
2768
+ payload,
2769
+ };
2770
+ }
2771
+ case "knowledge-bank.search": {
2772
+ if (typeof id !== "string" || id.trim() === "") {
2773
+ throw new UsageError(`knowledgeBankId must be a non-empty string.\nUsage: ${entry.usage}`, "invalid_flag_value");
2774
+ }
2775
+ const payload = requiredPlanJsonInput(flags, entry.usage);
2776
+ const query = stringField(payload, ["query",]);
2777
+ if (query === undefined || query.trim() === "") {
2778
+ throw new UsageError(`query is required and must be a non-empty string.\nUsage: ${entry.usage}`, "invalid_flag_value");
2779
+ }
2780
+ return {
2781
+ method: "POST",
2782
+ endpoint: projectEndpoint(`/knowledge-banks/${encodeURIComponent(id)}/search`),
2783
+ identifiers: { knowledgeBankId: id, },
2784
+ payload,
2785
+ };
2786
+ }
2787
+ case "knowledge-bank.clear":
2788
+ if (typeof id !== "string" || id.trim() === "") {
2789
+ throw new UsageError(`knowledgeBankId must be a non-empty string.\nUsage: ${entry.usage}`, "invalid_flag_value");
2790
+ }
2791
+ return {
2792
+ method: "POST",
2793
+ endpoint: projectEndpoint(`/knowledge-banks/${encodeURIComponent(id)}/clear`),
2794
+ identifiers: { knowledgeBankId: id, },
2795
+ };
2796
+ case "data-collection.create":
2797
+ return {
2798
+ method: "POST",
2799
+ endpoint: "/public/api/data-collections/",
2800
+ payload: requiredPlanJsonInput(flags, entry.usage),
2801
+ };
2802
+ case "data-collection.settings-set":
2803
+ return {
2804
+ method: "PUT",
2805
+ endpoint: `/public/api/data-collections/${encodeURIComponent(id)}`,
2806
+ identifiers: { dataCollectionId: id, },
2807
+ payload: requiredPlanJsonInput(flags, entry.usage),
2808
+ };
2809
+ case "data-collection.delete":
2810
+ return {
2811
+ method: "DELETE",
2812
+ endpoint: `/public/api/data-collections/${encodeURIComponent(id)}`,
2813
+ identifiers: { dataCollectionId: id, },
2814
+ };
2815
+ case "data-collection.add-object": {
2816
+ const payload = requiredPlanJsonInput(flags, entry.usage);
2817
+ const cid = id
2818
+ ?? (typeof payload["dataCollectionId"] === "string"
2819
+ ? payload["dataCollectionId"]
2820
+ : undefined);
2821
+ if (!cid)
2822
+ throw new UsageError(`Usage: ${entry.usage}`);
2823
+ const reference = { ...payload, };
2824
+ delete reference["dataCollectionId"];
2825
+ return {
2826
+ method: "POST",
2827
+ endpoint: `/public/api/data-collections/${encodeURIComponent(cid)}/objects`,
2828
+ identifiers: { dataCollectionId: cid, },
2829
+ payload: reference,
2830
+ };
2831
+ }
2832
+ case "data-collection.remove-dataset": {
2833
+ const projectKeyArg = args[1];
2834
+ const datasetName = args[2];
2835
+ if (!projectKeyArg || !datasetName) {
2836
+ throw new UsageError(`Usage: ${entry.usage}`);
2837
+ }
2838
+ return {
2839
+ method: "DELETE",
2840
+ endpoint: `/public/api/data-collections/${encodeURIComponent(id)}/objects/dataset/${encodeURIComponent(projectKeyArg)}/${encodeURIComponent(datasetName)}`,
2841
+ identifiers: { dataCollectionId: id, projectKey: projectKeyArg, datasetName, },
2842
+ };
2843
+ }
2844
+ case "project.tags-set":
2845
+ return {
2846
+ method: "PUT",
2847
+ endpoint: encodedProjectEndpointForPlan(projectKey ?? requiredPlanProjectKey(flags, entry.usage), "/tags"),
2848
+ identifiers: { projectKey: projectKey ?? requiredPlanProjectKey(flags, entry.usage), },
2849
+ payload: requiredPlanJsonInput(flags, entry.usage),
2850
+ };
2851
+ case "project.metadata-set":
2852
+ return {
2853
+ method: "PUT",
2854
+ endpoint: encodedProjectEndpointForPlan(projectKey ?? requiredPlanProjectKey(flags, entry.usage), "/metadata"),
2855
+ identifiers: { projectKey: projectKey ?? requiredPlanProjectKey(flags, entry.usage), },
2856
+ payload: requiredPlanJsonInput(flags, entry.usage),
2857
+ };
2858
+ case "macro.run":
2859
+ case "macro.run-and-wait": {
2860
+ // The plan payload mirrors the live POST body exactly: params and
2861
+ // adminParams when provided via --data, {} otherwise. Validation is
2862
+ // identical to the live path (JSON object shape), zero network.
2863
+ const data = optionalJsonFlag(flags, "data");
2864
+ const runPayload = {};
2865
+ if (data && typeof data === "object" && !Array.isArray(data)) {
2866
+ if (data["params"] !== undefined)
2867
+ runPayload["params"] = data["params"];
2868
+ if (data["adminParams"] !== undefined)
2869
+ runPayload["adminParams"] = data["adminParams"];
2870
+ }
2871
+ return {
2872
+ method: "POST",
2873
+ endpoint: projectEndpoint(`/runnables/${encodeURIComponent(id)}?wait=false`),
2874
+ identifiers: { id, },
2875
+ payload: runPayload,
2876
+ wait: action === "macro.run-and-wait" || flags["wait"] === true,
2877
+ };
2878
+ }
2879
+ case "macro.abort": {
2880
+ const runId = args[1];
2881
+ if (!runId)
2882
+ throw new UsageError(`Usage: ${entry.usage}`);
2883
+ return {
2884
+ method: "POST",
2885
+ endpoint: projectEndpoint(`/runnables/${encodeURIComponent(id)}/abort/${encodeURIComponent(runId)}`),
2886
+ identifiers: { id, runId, },
2887
+ };
2888
+ }
2889
+ case "recipe.metadata-set":
2890
+ return {
2891
+ method: "PUT",
2892
+ endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}/metadata`),
2893
+ identifiers: { recipeName: id, },
2894
+ payload: requiredPlanJsonInput(flags, entry.usage),
2895
+ };
2896
+ case "user.create":
2897
+ return {
2898
+ method: "POST",
2899
+ endpoint: "/public/api/admin/users",
2900
+ payload: redactedUserPayload(requiredPlanJsonInput(flags, entry.usage)),
2901
+ };
2902
+ case "group.create":
2903
+ return {
2904
+ method: "POST",
2905
+ endpoint: "/public/api/admin/groups",
2906
+ payload: requiredPlanJsonInput(flags, entry.usage),
2907
+ };
2908
+ case "saved-model.create-external": {
2909
+ const config = optionalJsonFlag(flags, "configuration") ?? optionalJsonFlag(flags, "data");
2910
+ return {
2911
+ method: "POST",
2912
+ endpoint: projectEndpoint("/savedmodels/"),
2913
+ identifiers: { name: id, },
2914
+ payload: {
2915
+ savedModelType: requiredPlanFlag(flags, "type", entry.usage),
2916
+ name: id,
2917
+ ...(typeof flags["prediction-type"] === "string"
2918
+ ? { predictionType: flags["prediction-type"], }
2919
+ : {}),
2920
+ ...(config ? { proxyModelConfiguration: config, } : {}),
2921
+ },
2922
+ };
2923
+ }
2924
+ case "saved-model.update-settings":
2925
+ return {
2926
+ method: "PUT",
2927
+ endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}`),
2928
+ identifiers: { savedModelId: id, },
2929
+ payload: requiredPlanJsonInput(flags, entry.usage),
2930
+ };
2931
+ case "saved-model.delete-versions": {
2932
+ const versionsRaw = args[1];
2933
+ if (typeof versionsRaw !== "string" || versionsRaw.trim() === "") {
2934
+ throw new UsageError(`Usage: ${entry.usage}`);
2935
+ }
2936
+ return {
2937
+ method: "POST",
2938
+ endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/actions/delete-versions`),
2939
+ identifiers: { savedModelId: id, },
2940
+ payload: {
2941
+ versions: versionsRaw.split(",").map((v) => v.trim()).filter((v) => v.length > 0),
2942
+ removeIntermediate: parseBooleanOption(flags["remove-intermediate"], "--remove-intermediate") ?? true,
2943
+ },
2944
+ };
2945
+ }
2946
+ case "saved-model.import-mlflow-version":
2947
+ return {
2948
+ method: "POST",
2949
+ endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}`),
2950
+ identifiers: { savedModelId: id, versionId: args[1], },
2951
+ payload: {
2952
+ source: { kind: "local-archive", archive: requiredPlanFlag(flags, "archive", entry.usage), },
2953
+ ...(typeof flags["code-env"] === "string"
2954
+ ? { codeEnvName: flags["code-env"], }
2955
+ : {}),
2956
+ // Execute always sends the query parameter with the
2957
+ // external-caller default NONE; the plan mirrors it.
2958
+ containerExecConfigName: typeof flags["container-exec-config"] === "string"
2959
+ ? flags["container-exec-config"]
2960
+ : "NONE",
2961
+ setActive: parseBooleanOption(flags["set-active"], "--set-active") ?? true,
2962
+ ...(flags["binary-classification-threshold"] !== undefined
2963
+ ? {
2964
+ binaryClassificationThreshold: num(flags["binary-classification-threshold"], "--binary-classification-threshold"),
2965
+ }
2966
+ : {}),
2967
+ },
2968
+ };
2969
+ case "saved-model.import-mlflow-version-from-folder":
2970
+ return {
2971
+ method: "POST",
2972
+ endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}`),
2973
+ identifiers: { savedModelId: id, versionId: args[1], },
2974
+ payload: {
2975
+ source: {
2976
+ kind: "managed-folder",
2977
+ folderRef: requiredPlanFlag(flags, "folder", entry.usage),
2978
+ path: flags["path"],
2979
+ },
2980
+ ...(typeof flags["code-env"] === "string" ? { codeEnvName: flags["code-env"], } : {}),
2981
+ // Execute always sends the query parameter with the
2982
+ // external-caller default NONE; the plan mirrors it.
2983
+ containerExecConfigName: typeof flags["container-exec-config"] === "string"
2984
+ ? flags["container-exec-config"]
2985
+ : "NONE",
2986
+ setActive: parseBooleanOption(flags["set-active"], "--set-active") ?? true,
2987
+ },
2988
+ };
2989
+ case "saved-model.external-metadata-put": {
2990
+ // DSS requires containerExecConfigName on this endpoint; for an
2991
+ // external API caller it resolves as LOCAL-CONFIG -> NONE, so the
2992
+ // plan mirrors the NONE default unless --container-exec-config
2993
+ // overrides it. Same contract as execute.
2994
+ const containerExecConfigName = typeof flags["container-exec-config"] === "string"
2995
+ ? flags["container-exec-config"]
2996
+ : "NONE";
2997
+ return {
2998
+ method: "PUT",
2999
+ endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}/external-ml/metadata?containerExecConfigName=${encodeURIComponent(containerExecConfigName)}`),
3000
+ identifiers: { savedModelId: id, versionId: args[1], },
3001
+ payload: requiredPlanJsonInput(flags, entry.usage),
3002
+ };
3003
+ }
3004
+ case "saved-model.evaluate-version": {
3005
+ const payload = {
3006
+ datasetRef: requiredPlanFlag(flags, "dataset", entry.usage),
3007
+ // Execute always sends an explicit containerExecConfigName
3008
+ // (official external-caller semantics: LOCAL-CONFIG -> NONE);
3009
+ // the plan mirrors that default and any explicit override.
3010
+ containerExecConfigName: typeof flags["container-exec-config"] === "string"
3011
+ ? flags["container-exec-config"]
3012
+ : "NONE",
3013
+ };
3014
+ // --sampling is a JSON flag value; the plan carries the parsed
3015
+ // object under the same key as the execute body (samplingParam).
3016
+ if (flags["sampling"] !== undefined) {
3017
+ payload.samplingParam = json(flags["sampling"], "--sampling");
3018
+ }
3019
+ return {
3020
+ method: "POST",
3021
+ endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}/external-ml/actions/evaluate`),
3022
+ identifiers: { savedModelId: id, versionId: args[1], },
3023
+ payload,
3024
+ };
3025
+ }
3026
+ case "saved-model.set-user-meta":
3027
+ return {
3028
+ method: "PUT",
3029
+ endpoint: projectEndpoint(`/savedmodels/${encodeURIComponent(id)}/versions/${encodeURIComponent(args[1] ?? "")}/user-meta`),
3030
+ identifiers: { savedModelId: id, versionId: args[1], },
3031
+ payload: requiredPlanJsonInput(flags, entry.usage),
3032
+ };
3033
+ case "user.update":
3034
+ return {
3035
+ method: "PUT",
3036
+ endpoint: `/public/api/admin/users/${encodeURIComponent(id)}`,
3037
+ identifiers: { login: id, },
3038
+ payload: redactedUserPayload(requiredPlanJsonInput(flags, entry.usage)),
3039
+ };
3040
+ case "user.delete":
3041
+ case "group.delete":
3042
+ return {
3043
+ method: "DELETE",
3044
+ endpoint: resource === "user"
3045
+ ? `/public/api/admin/users/${encodeURIComponent(id)}`
3046
+ : `/public/api/admin/groups/${encodeURIComponent(id)}`,
3047
+ identifiers: resource === "user" ? { login: id, } : { name: id, },
3048
+ };
3049
+ case "user.resync":
3050
+ return {
3051
+ method: "POST",
3052
+ endpoint: `/public/api/admin/users/${encodeURIComponent(id)}/actions/resync`,
3053
+ identifiers: { login: id, },
3054
+ };
3055
+ case "user.resync-multi": {
3056
+ const logins = requiredPlanCsv(flags, "logins", entry.usage);
3057
+ return {
3058
+ method: "POST",
3059
+ endpoint: "/public/api/admin/users/actions/resync-multi",
3060
+ identifiers: { count: logins.length, },
3061
+ payload: logins,
3062
+ };
3063
+ }
3064
+ case "user.external-users":
3065
+ return {
3066
+ method: "GET",
3067
+ endpoint: "/public/api/admin/users/actions/external-users",
3068
+ };
3069
+ case "user.external-groups":
3070
+ return {
3071
+ method: "GET",
3072
+ endpoint: "/public/api/admin/users/actions/external-groups",
3073
+ };
3074
+ case "connection.test":
3075
+ return {
3076
+ method: "GET",
3077
+ endpoint: `/public/api/connections/${encodeURIComponent(id)}/test`,
3078
+ identifiers: { connectionName: id, },
3079
+ };
3080
+ case "user.provision":
3081
+ return {
3082
+ method: "POST",
3083
+ endpoint: "/public/api/admin/users/actions/provision",
3084
+ payload: requiredPlanJsonInput(flags, entry.usage),
3085
+ };
3086
+ case "group.update":
3087
+ return {
3088
+ method: "PUT",
3089
+ endpoint: `/public/api/admin/groups/${encodeURIComponent(id)}`,
3090
+ identifiers: { name: id, },
3091
+ payload: requiredPlanJsonInput(flags, entry.usage),
3092
+ };
3093
+ case "connection.create":
3094
+ return {
3095
+ method: "POST",
3096
+ endpoint: "/public/api/admin/connections",
3097
+ payload: redactedConnectionPayload(requiredPlanJsonInput(flags, entry.usage)),
3098
+ };
3099
+ case "connection.update":
3100
+ return {
3101
+ method: "PUT",
3102
+ endpoint: `/public/api/admin/connections/${encodeURIComponent(id)}`,
3103
+ identifiers: { connectionName: id, },
3104
+ payload: redactedConnectionPayload(requiredPlanJsonInput(flags, entry.usage)),
3105
+ };
3106
+ case "connection.delete":
3107
+ return {
3108
+ method: "DELETE",
3109
+ endpoint: `/public/api/admin/connections/${encodeURIComponent(id)}`,
3110
+ identifiers: { connectionName: id, },
3111
+ };
3112
+ case "connection.prepare-import": {
3113
+ const payload = requiredPlanJsonInput(flags, entry.usage);
3114
+ if (!projectKey)
3115
+ throw new UsageError(`Missing project key for ${resource} ${action}.`);
3116
+ return {
3117
+ method: "POST",
3118
+ endpoint: encodedProjectEndpointForPlan(projectKey, "/datasets/tables-import/actions/prepare-from-keys"),
3119
+ identifiers: { projectKey, },
3120
+ payload,
3121
+ };
3122
+ }
3123
+ case "connection.execute-import": {
3124
+ const payload = requiredPlanJsonInput(flags, entry.usage);
3125
+ if (!projectKey)
3126
+ throw new UsageError(`Missing project key for ${resource} ${action}.`);
3127
+ return {
3128
+ method: "POST",
3129
+ endpoint: encodedProjectEndpointForPlan(projectKey, "/datasets/tables-import/actions/execute-from-candidates"),
3130
+ identifiers: { projectKey, },
3131
+ payload,
3132
+ };
3133
+ }
3134
+ case "plugin.install-from-zip":
3135
+ return {
3136
+ method: "POST",
3137
+ endpoint: "/public/api/plugins/actions/installFromZip",
3138
+ payload: { file: requiredPlanFlag(flags, "file", entry.usage), upload: "multipart", },
3139
+ };
3140
+ case "plugin.install-from-store":
3141
+ return {
3142
+ method: "POST",
3143
+ endpoint: "/public/api/plugins/actions/installFromStore",
3144
+ identifiers: { pluginId: id, },
3145
+ payload: { pluginId: id, },
3146
+ };
3147
+ case "plugin.install-from-git": {
3148
+ const payload = pluginGitPlanPayload(flags);
3149
+ return {
3150
+ method: "POST",
3151
+ endpoint: "/public/api/plugins/actions/installFromGit",
3152
+ payload,
3153
+ };
3154
+ }
3155
+ case "plugin.update-from-zip":
3156
+ return {
3157
+ method: "POST",
3158
+ endpoint: pluginActionEndpoint(id, "updateFromZip"),
3159
+ identifiers: { pluginId: id, },
3160
+ payload: { file: requiredPlanFlag(flags, "file", entry.usage), upload: "multipart", },
3161
+ };
3162
+ case "plugin.update-from-store":
3163
+ return {
3164
+ method: "POST",
3165
+ endpoint: pluginActionEndpoint(id, "updateFromStore"),
3166
+ identifiers: { pluginId: id, },
3167
+ };
3168
+ case "plugin.update-from-git":
3169
+ return {
3170
+ method: "POST",
3171
+ endpoint: pluginActionEndpoint(id, "updateFromGit"),
3172
+ identifiers: { pluginId: id, },
3173
+ payload: pluginGitPlanPayload(flags),
3174
+ };
3175
+ case "plugin.settings-set": {
3176
+ const scope = optionalPlanProjectScope(flags);
3177
+ return {
3178
+ method: "POST",
3179
+ endpoint: `${pluginRootEndpoint(id, "/settings")}${scope}`,
3180
+ identifiers: { pluginId: id, },
3181
+ payload: { configKeys: settingsConfigKeys(flags), },
3182
+ };
3183
+ }
3184
+ case "plugin.code-env-create": {
3185
+ const conda = parseBooleanOption(flags["conda"], "--conda") ?? false;
3186
+ return {
3187
+ method: "POST",
3188
+ endpoint: `${pluginRootEndpoint(id, "/code-env/actions/create")}`,
3189
+ identifiers: { pluginId: id, },
3190
+ payload: {
3191
+ deploymentMode: "PLUGIN_MANAGED",
3192
+ conda,
3193
+ pythonInterpreter: flags["python-interpreter"] ?? null,
3194
+ },
3195
+ wait: flags["wait"] === true,
3196
+ };
3197
+ }
3198
+ case "plugin.code-env-update":
3199
+ return {
3200
+ method: "POST",
3201
+ endpoint: `${pluginRootEndpoint(id, "/code-env/actions/update")}`,
3202
+ identifiers: { pluginId: id, },
3203
+ wait: flags["wait"] === true,
3204
+ };
3205
+ case "plugin.move-to-dev":
3206
+ return {
3207
+ method: "POST",
3208
+ endpoint: pluginActionEndpoint(id, "moveToDev"),
3209
+ identifiers: { pluginId: id, },
3210
+ };
3211
+ case "plugin.delete":
3212
+ return {
3213
+ method: "POST",
3214
+ endpoint: pluginActionEndpoint(id, "delete"),
3215
+ identifiers: { pluginId: id, },
3216
+ payload: { force: parseBooleanOption(flags["force"], "--force") ?? false, },
3217
+ };
3218
+ case "plugin.create-dev": {
3219
+ const creationMode = requiredPlanFlag(flags, "creation-mode", entry.usage);
3220
+ const payload = {
3221
+ pluginId: id,
3222
+ creationMode,
3223
+ };
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
+ return {
3232
+ method: "POST",
3233
+ endpoint: "/public/api/plugins/actions/createDev",
3234
+ identifiers: { pluginId: id, creationMode, },
3235
+ payload,
3236
+ };
3237
+ }
3238
+ case "plugin.set-git-remote": {
3239
+ const repository = requiredPlanFlag(flags, "repository", entry.usage);
3240
+ return {
3241
+ method: "POST",
3242
+ endpoint: `${pluginRootEndpoint(id, "/gitRemote")}`,
3243
+ identifiers: { pluginId: id, },
3244
+ payload: { repositoryUrl: validatedPlanRepositoryUrl(repository, "repository", entry.usage), },
3245
+ };
3246
+ }
3247
+ case "plugin.delete-git-remote":
3248
+ return {
3249
+ method: "DELETE",
3250
+ endpoint: pluginRootEndpoint(id, "/gitRemote"),
3251
+ identifiers: { pluginId: id, },
3252
+ };
3253
+ case "plugin.push":
3254
+ case "plugin.pull":
3255
+ case "plugin.fetch":
3256
+ case "plugin.reset-local":
3257
+ case "plugin.reset-remote": {
3258
+ const actionMap = {
3259
+ "plugin.push": "push",
3260
+ "plugin.pull": "pullRebase",
3261
+ "plugin.fetch": "fetch",
3262
+ "plugin.reset-local": "resetToLocalHeadState",
3263
+ "plugin.reset-remote": "resetToRemoteHeadState",
3264
+ };
3265
+ return {
3266
+ method: "POST",
3267
+ endpoint: pluginActionEndpoint(id, actionMap[`${resource}.${action}`]),
3268
+ identifiers: { pluginId: id, },
3269
+ };
3270
+ }
3271
+ case "plugin.contents-put":
3272
+ return {
3273
+ method: "POST",
3274
+ endpoint: pluginContentsEndpoint(id, args[1] ?? ""),
3275
+ identifiers: { pluginId: id, path: args[1], },
3276
+ payload: { contentSource: contentSourceKind(flags), },
3277
+ };
3278
+ case "plugin.contents-delete":
3279
+ return {
3280
+ method: "DELETE",
3281
+ endpoint: pluginContentsEndpoint(id, args[1] ?? ""),
3282
+ identifiers: { pluginId: id, path: args[1], },
3283
+ };
3284
+ case "plugin.folder-add":
3285
+ return {
3286
+ method: "POST",
3287
+ endpoint: pluginFoldersEndpoint(id, args[1] ?? ""),
3288
+ identifiers: { pluginId: id, path: args[1], },
3289
+ };
3290
+ case "plugin.rename": {
3291
+ const newName = requiredPlanPositionals(entry.usage)[2];
3292
+ if (!newName)
3293
+ throw new UsageError(`Usage: ${entry.usage}`);
3294
+ return {
3295
+ method: "POST",
3296
+ endpoint: `${pluginRootEndpoint(id, "/contents-actions/rename")}`,
3297
+ identifiers: { pluginId: id, path: args[1], },
3298
+ payload: { oldPath: args[1], newName: args[2], },
3299
+ };
3300
+ }
3301
+ case "plugin.move": {
3302
+ const destination = args[2];
3303
+ if (!destination)
3304
+ throw new UsageError(`Usage: ${entry.usage}`);
3305
+ return {
3306
+ method: "POST",
3307
+ endpoint: `${pluginRootEndpoint(id, "/contents-actions/move")}`,
3308
+ identifiers: { pluginId: id, path: args[1], },
3309
+ payload: { oldPath: args[1], newPath: destination, },
3310
+ };
3311
+ }
2689
3312
  case "business-app.save-settings":
2690
3313
  return {
2691
3314
  method: "PUT",
@@ -2725,6 +3348,42 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2725
3348
  }
2726
3349
  case "app.create-instance": {
2727
3350
  const payload = requiredPlanJsonInput(flags, entry.usage);
3351
+ if (payload["targetProjectKey"] === undefined) {
3352
+ // Generated-at-apply mode: --plan never allocates or reserves a
3353
+ // random key. The payload stays exactly as supplied, the runtime
3354
+ // generates the key (and defaults the display name to it) during
3355
+ // apply, and no absence probe runs for a generated key, so the
3356
+ // plan carries no preflight requests and no fake concrete GET
3357
+ // path: `{targetProjectKey}` resolves at apply time.
3358
+ return {
3359
+ method: "POST",
3360
+ endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
3361
+ identifiers: {
3362
+ appId: id,
3363
+ targetProjectKeyGeneratedDuringApply: true,
3364
+ ...(payload["targetProjectName"] === undefined
3365
+ ? { targetProjectNameGeneratedDuringApply: true, }
3366
+ : {}),
3367
+ preflightExecuted: false,
3368
+ preflightWillRunDuringApply: false,
3369
+ preflightRequests: [],
3370
+ incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
3371
+ incarnationObservationRequests: [
3372
+ {
3373
+ method: "GET",
3374
+ endpointTemplate: "/public/api/projects/{targetProjectKey}/",
3375
+ when: flags["wait"] === true
3376
+ ? "after-terminal-future-target"
3377
+ : "conditional-inline-hasResult-target",
3378
+ intent: "After DSS reports inline or terminal creation success for the generated key, observe creationTag for later cleanup binding.",
3379
+ },
3380
+ ],
3381
+ note: "Generated-at-apply: a random APP_ targetProjectKey is generated client-side during apply (never at plan time); targetProjectName defaults to it when omitted. No absent-project preflight runs for a generated key.",
3382
+ },
3383
+ payload,
3384
+ wait: flags["wait"] === true,
3385
+ };
3386
+ }
2728
3387
  const rawTargetProjectKey = stringField(payload, ["targetProjectKey",]);
2729
3388
  if (!rawTargetProjectKey || rawTargetProjectKey.trim() === "") {
2730
3389
  throw new UsageError("Instance creation payload must include a non-empty targetProjectKey.", "validation_failed", `Usage: ${entry.usage}`);
@@ -2817,13 +3476,110 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
2817
3476
  }
2818
3477
  case "app.create-successor-instance": {
2819
3478
  const sourceProjectKey = requiredPlanFlag(flags, "from", entry.usage);
2820
- const targetProjectKey = requiredPlanFlag(flags, "to", entry.usage);
2821
- if (sourceProjectKey === targetProjectKey) {
3479
+ const targetProjectKey = optionalPlanFlag(flags, "to", entry.usage);
3480
+ if (targetProjectKey !== undefined && sourceProjectKey === targetProjectKey) {
2822
3481
  throw new UsageError("--from and --to must be different project keys.", "validation_failed", `Usage: ${entry.usage}`);
2823
3482
  }
2824
3483
  const targetProjectName = flags["name"];
2825
3484
  const copyPermissions = parseBooleanOption(flags["copy-permissions"], "--copy-permissions")
2826
3485
  ?? false;
3486
+ if (targetProjectKey === undefined) {
3487
+ // Generated-at-apply mode: --plan never allocates a key and no
3488
+ // target exists yet, so the plan makes no target-absence claim
3489
+ // and advertises no target probe. The key is generated once
3490
+ // during apply, immediately before the single instance POST;
3491
+ // `{targetProjectKey}` resolves only after the terminal future
3492
+ // names it. Source and template gates still run before the POST.
3493
+ return {
3494
+ method: "POST",
3495
+ endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
3496
+ identifiers: {
3497
+ appId: id,
3498
+ sourceProjectKey,
3499
+ targetProjectKeyGeneratedDuringApply: true,
3500
+ targetPreflight: "not-applicable-generated-key",
3501
+ preflightExecuted: false,
3502
+ preflightWillRunDuringApply: true,
3503
+ ...(targetProjectName !== undefined
3504
+ ? { targetProjectName, }
3505
+ : { targetProjectNameGeneratedDuringApply: true, }),
3506
+ copyPermissions,
3507
+ incarnationControl: "client-side-non-atomic-future-target-and-creation-tag-join",
3508
+ incarnationObservationRequests: [
3509
+ {
3510
+ method: "GET",
3511
+ endpointTemplate: "/public/api/projects/{targetProjectKey}/",
3512
+ when: "after-terminal-future-target",
3513
+ intent: "After the terminal future names the generated successor key, observe creationTag and bind later target checks and cleanup to that hash.",
3514
+ },
3515
+ ],
3516
+ preflightRequests: [
3517
+ {
3518
+ method: "GET",
3519
+ endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances/`,
3520
+ when: "before-create",
3521
+ intent: "Verify the --from project is a registered instance of the app.",
3522
+ },
3523
+ {
3524
+ method: "GET",
3525
+ endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/app-manifest`,
3526
+ when: "before-create",
3527
+ intent: "Verify the --from project is an APP_INSTANCE project.",
3528
+ },
3529
+ ],
3530
+ ...(copyPermissions
3531
+ ? {
3532
+ permissionConcurrencyControl: "client-side-non-atomic-stale-identity-and-hash-checks",
3533
+ permissionRequests: [
3534
+ {
3535
+ method: "GET",
3536
+ endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
3537
+ intent: "Snapshot the predecessor instance ACL before creation.",
3538
+ },
3539
+ {
3540
+ method: "GET",
3541
+ endpointTemplate: "/public/api/projects/{targetProjectKey}/permissions",
3542
+ intent: "Read the generated successor ACL to decide whether the copy is a no-op.",
3543
+ },
3544
+ {
3545
+ method: "GET",
3546
+ endpointTemplate: "/public/api/projects/{targetProjectKey}/",
3547
+ intent: "Recheck generated successor creationTag after reading its ACL; stop if the project key was reused.",
3548
+ },
3549
+ {
3550
+ method: "GET",
3551
+ endpoint: `/public/api/projects/${encodeURIComponent(sourceProjectKey)}/permissions`,
3552
+ intent: "Recheck the predecessor ACL immediately before the write and stop if its hash drifted.",
3553
+ },
3554
+ {
3555
+ method: "GET",
3556
+ endpointTemplate: "/public/api/projects/{targetProjectKey}/",
3557
+ intent: "Recheck generated successor creationTag immediately before the unconditional permission PUT.",
3558
+ },
3559
+ {
3560
+ method: "PUT",
3561
+ endpointTemplate: "/public/api/projects/{targetProjectKey}/permissions",
3562
+ intent: "Apply the predecessor ACL snapshot to the generated successor instance.",
3563
+ },
3564
+ {
3565
+ method: "GET",
3566
+ endpointTemplate: "/public/api/projects/{targetProjectKey}/permissions",
3567
+ intent: "Verify the generated successor ACL hash equals the predecessor snapshot hash.",
3568
+ },
3569
+ {
3570
+ method: "GET",
3571
+ endpointTemplate: "/public/api/projects/{targetProjectKey}/",
3572
+ intent: "Detect generated successor project-key reuse across the permission write and verification read.",
3573
+ },
3574
+ ],
3575
+ }
3576
+ : {}),
3577
+ note: "Generated-at-apply: the successor key is generated once during apply (never at plan time) and no absent-project preflight runs for a generated key, so the plan claims no target absence. Source and template gates run before the single instance POST, then the terminal future's own result names the successor key. The predecessor is never modified or deleted; cleanup targets the generated successor key only.",
3578
+ },
3579
+ payload: targetProjectName !== undefined ? { targetProjectName, } : {},
3580
+ wait: true,
3581
+ };
3582
+ }
2827
3583
  return {
2828
3584
  method: "POST",
2829
3585
  endpoint: `/public/api/apps/${encodeURIComponent(id)}/instances`,
@@ -4076,6 +4832,51 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
4076
4832
  endpoint: projectGitFutureEndpoint(id),
4077
4833
  identifiers: { jobId: id, },
4078
4834
  };
4835
+ case "project-folder.settings-set": {
4836
+ const payload = requiredPlanJsonInput(flags, `--data, --data-file, or --stdin is required. Usage: ${entry.usage}`);
4837
+ return {
4838
+ method: "PUT",
4839
+ endpoint: `${projectFolderEndpoint(id)}/settings`,
4840
+ identifiers: { folderId: id, },
4841
+ payload,
4842
+ };
4843
+ }
4844
+ case "project-folder.move": {
4845
+ const destination = args[1];
4846
+ if (!destination)
4847
+ throw new UsageError(`Usage: ${entry.usage}`);
4848
+ return {
4849
+ method: "POST",
4850
+ endpoint: `${projectFolderEndpoint(id)}/move${querySuffix({ destination, })}`,
4851
+ identifiers: { folderId: id, destination, },
4852
+ };
4853
+ }
4854
+ case "project-folder.delete":
4855
+ return {
4856
+ method: "DELETE",
4857
+ endpoint: projectFolderEndpoint(id),
4858
+ identifiers: { folderId: id, },
4859
+ };
4860
+ case "project-folder.create-child": {
4861
+ const name = requiredPlanFlag(flags, "name", entry.usage);
4862
+ return {
4863
+ method: "POST",
4864
+ endpoint: `${projectFolderEndpoint(id)}/children${querySuffix({ name, })}`,
4865
+ identifiers: { parentFolderId: id, name, },
4866
+ };
4867
+ }
4868
+ case "project-folder.move-project": {
4869
+ const projectKeyArg = args[1];
4870
+ const destination = args[2];
4871
+ if (!projectKeyArg || !destination) {
4872
+ throw new UsageError(`Usage: ${entry.usage}`);
4873
+ }
4874
+ return {
4875
+ method: "POST",
4876
+ endpoint: `${projectFolderEndpoint(id)}/projects/${encodeURIComponent(projectKeyArg)}/move${querySuffix({ destination, })}`,
4877
+ identifiers: { folderId: id, projectKey: projectKeyArg, destination, },
4878
+ };
4879
+ }
4079
4880
  default:
4080
4881
  return {
4081
4882
  exact: false,