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
@@ -0,0 +1,799 @@
1
+ import * as fs from "node:fs";
2
+ import { validatedPluginGitUrl, validatePluginDestinationPath, validatePluginId, validatePluginName, validatePluginPath, } from "../../resources/plugins.js";
3
+ import { num, readStdinText, } from "../coerce.js";
4
+ import { executionMode, } from "../flags.js";
5
+ import { planResult, } from "../output.js";
6
+ import { requireArgs, requireNoArgs, UsageError, } from "../usage.js";
7
+ const PLUGIN_EXIT_CODES = { usage: 1, error: 2, transient: 3, };
8
+ /** Plan-time endpoint helpers. Paths are validated, then encoded per segment. */
9
+ function pluginActionEndpoint(pluginId, action) {
10
+ return `/public/api/plugins/${encodeURIComponent(validatePluginId(pluginId))}/actions/${action}`;
11
+ }
12
+ function pluginRootEndpoint(pluginId, suffix) {
13
+ return `/public/api/plugins/${encodeURIComponent(validatePluginId(pluginId))}${suffix}`;
14
+ }
15
+ function pluginContentsEndpoint(pluginId, path) {
16
+ return `${pluginRootEndpoint(pluginId, "/contents/")}${encodePluginPathForPlan(path)}`;
17
+ }
18
+ function pluginFoldersEndpoint(pluginId, path) {
19
+ return `${pluginRootEndpoint(pluginId, "/folders/")}${encodePluginPathForPlan(path)}`;
20
+ }
21
+ function encodePluginPathForPlan(path) {
22
+ return validatePluginPath(path)
23
+ .split("/")
24
+ .map((segment) => encodeURIComponent(segment))
25
+ .join("/");
26
+ }
27
+ function scopeQueryForPlan(projectKey) {
28
+ if (typeof projectKey !== "string" || projectKey.trim() === "")
29
+ return "";
30
+ return `?projectKey=${encodeURIComponent(projectKey.trim())}`;
31
+ }
32
+ /**
33
+ * Dry-run plan for a mutating plugin command. Never contains file bytes,
34
+ * git credentials, or zip paths' contents — only the request shape.
35
+ */
36
+ function pluginPlan(action, options) {
37
+ return planResult("plugin", action, {
38
+ asyncKind: options.asyncKind ?? "none",
39
+ endpoint: options.endpoint,
40
+ identifiers: options.identifiers,
41
+ idempotency: options.idempotency ?? "none",
42
+ method: options.method,
43
+ payload: options.payload,
44
+ exitCodesOnFailure: PLUGIN_EXIT_CODES,
45
+ plannedAndDryRun: true,
46
+ });
47
+ }
48
+ function requireFileFlag(flags, usage) {
49
+ const value = flags["file"];
50
+ if (typeof value !== "string" || value.trim() === "") {
51
+ throw new UsageError(`--file PATH is required. Usage: ${usage}`, "missing_required_flag");
52
+ }
53
+ return value;
54
+ }
55
+ function optionalStringFlag(flags, name) {
56
+ const value = flags[name];
57
+ if (value === undefined)
58
+ return undefined;
59
+ if (typeof value !== "string" || value.trim() === "") {
60
+ throw new UsageError(`--${name} requires a value.`, "invalid_flag_value", undefined, {
61
+ flag: `--${name}`,
62
+ });
63
+ }
64
+ return value.trim();
65
+ }
66
+ const LIST_USAGE = "dss plugin list";
67
+ const INSTALL_ZIP_USAGE = "dss plugin install-from-zip --file PATH [--dry-run] [--plan]";
68
+ const INSTALL_STORE_USAGE = "dss plugin install-from-store <pluginId> [--dry-run] [--plan]";
69
+ const INSTALL_GIT_USAGE = "dss plugin install-from-git --repository URL [--checkout REF] [--path-in-repository PATH] [--dry-run] [--plan]";
70
+ const DOWNLOAD_USAGE = "dss plugin download <pluginId> --output PATH";
71
+ const UPDATE_ZIP_USAGE = "dss plugin update-from-zip <pluginId> --file PATH [--dry-run] [--plan]";
72
+ const UPDATE_STORE_USAGE = "dss plugin update-from-store <pluginId> [--dry-run] [--plan]";
73
+ const UPDATE_GIT_USAGE = "dss plugin update-from-git <pluginId> --repository URL [--checkout REF] [--path-in-repository PATH] [--dry-run] [--plan]";
74
+ const SETTINGS_GET_USAGE = "dss plugin settings-get <pluginId> [--project-key KEY]";
75
+ const SETTINGS_SET_USAGE = "dss plugin settings-set <pluginId> (--content JSON|--file PATH|--stdin) [--project-key KEY] [--dry-run] [--plan]";
76
+ const CODE_ENV_CREATE_USAGE = "dss plugin code-env-create <pluginId> [--conda] [--python-interpreter PY] [--wait] [--timeout MS] [--poll-interval MS] [--dry-run] [--plan]";
77
+ const CODE_ENV_UPDATE_USAGE = "dss plugin code-env-update <pluginId> [--wait] [--timeout MS] [--poll-interval MS] [--dry-run] [--plan]";
78
+ const MOVE_TO_DEV_USAGE = "dss plugin move-to-dev <pluginId> [--dry-run] [--plan]";
79
+ const USAGES_USAGE = "dss plugin usages <pluginId> [--project-key KEY]";
80
+ const DELETE_USAGE = "dss plugin delete <pluginId> [--force] [--dry-run] [--plan]";
81
+ const CREATE_DEV_USAGE = "dss plugin create-dev <pluginId> --creation-mode EMPTY|GIT_CLONE|GIT_EXPORT [--repository URL] [--checkout REF] [--path-in-repository PATH] [--dry-run] [--plan]";
82
+ const GET_GIT_REMOTE_USAGE = "dss plugin get-git-remote <pluginId>";
83
+ const SET_GIT_REMOTE_USAGE = "dss plugin set-git-remote <pluginId> --repository URL [--dry-run] [--plan]";
84
+ const DELETE_GIT_REMOTE_USAGE = "dss plugin delete-git-remote <pluginId> [--dry-run] [--plan]";
85
+ const GIT_BRANCHES_USAGE = "dss plugin git-branches <pluginId>";
86
+ const PUSH_USAGE = "dss plugin push <pluginId> [--dry-run] [--plan]";
87
+ const PULL_USAGE = "dss plugin pull <pluginId> [--dry-run] [--plan]";
88
+ const FETCH_USAGE = "dss plugin fetch <pluginId> [--dry-run] [--plan]";
89
+ const RESET_LOCAL_USAGE = "dss plugin reset-local <pluginId> [--dry-run] [--plan]";
90
+ const RESET_REMOTE_USAGE = "dss plugin reset-remote <pluginId> [--dry-run] [--plan]";
91
+ const CONTENTS_LIST_USAGE = "dss plugin contents-list <pluginId>";
92
+ const CONTENTS_GET_USAGE = "dss plugin contents-get <pluginId> <path> [--output PATH]";
93
+ const CONTENTS_PUT_USAGE = "dss plugin contents-put <pluginId> <path> (--content TEXT|--file PATH|--stdin) [--dry-run] [--plan]";
94
+ const CONTENTS_DELETE_USAGE = "dss plugin contents-delete <pluginId> <path> [--dry-run] [--plan]";
95
+ const DETAILS_USAGE = "dss plugin details <pluginId> <path>";
96
+ const FOLDER_ADD_USAGE = "dss plugin folder-add <pluginId> <path> [--dry-run] [--plan]";
97
+ const RENAME_USAGE = "dss plugin rename <pluginId> <path> <new-name> [--dry-run] [--plan]";
98
+ const MOVE_USAGE = "dss plugin move <pluginId> <path> <destination-folder> [--dry-run] [--plan]";
99
+ async function waitFuture(client, jobId, flags) {
100
+ return client
101
+ .futures.wait(jobId, {
102
+ pollIntervalMs: num(flags["poll-interval"], "--poll-interval"),
103
+ timeoutMs: num(flags["timeout"], "--timeout"),
104
+ });
105
+ }
106
+ export const pluginCommands = {
107
+ list: {
108
+ handler: (c) => c.plugins.list(),
109
+ usage: LIST_USAGE,
110
+ description: "List installed plugins (including development plugins).",
111
+ examples: ["dss plugin list",],
112
+ },
113
+ "install-from-zip": {
114
+ handler: async (c, _a, f) => {
115
+ requireNoArgs(_a, INSTALL_ZIP_USAGE);
116
+ const file = requireFileFlag(f, INSTALL_ZIP_USAGE);
117
+ if (executionMode(f).dryRun) {
118
+ return pluginPlan("install-from-zip", {
119
+ method: "POST",
120
+ endpoint: "/public/api/plugins/actions/installFromZip",
121
+ identifiers: { file, upload: "multipart zip", },
122
+ idempotency: "none",
123
+ });
124
+ }
125
+ await c.plugins.installFromZip(file);
126
+ return { installed: true, from: "zip", };
127
+ },
128
+ usage: INSTALL_ZIP_USAGE,
129
+ description: "Install a plugin from a zip file. Fails when the plugin is already installed (use update-from-zip to replace).",
130
+ examples: ["dss plugin install-from-zip --file ./my-plugin.zip",],
131
+ },
132
+ "install-from-store": {
133
+ handler: async (c, a, f) => {
134
+ requireArgs(a, 1, INSTALL_STORE_USAGE);
135
+ const pluginId = validatePluginId(a[0]);
136
+ if (executionMode(f).dryRun) {
137
+ return pluginPlan("install-from-store", {
138
+ method: "POST",
139
+ endpoint: "/public/api/plugins/actions/installFromStore",
140
+ identifiers: { pluginId, },
141
+ payload: { pluginId, },
142
+ });
143
+ }
144
+ await c.plugins.installFromStore(pluginId);
145
+ return { installed: pluginId, from: "store", };
146
+ },
147
+ usage: INSTALL_STORE_USAGE,
148
+ description: "Install a plugin from the Dataiku store. Fails when already installed.",
149
+ examples: ["dss plugin install-from-store my-plugin",],
150
+ },
151
+ "install-from-git": {
152
+ handler: async (_c, _a, f) => {
153
+ requireNoArgs(_a, INSTALL_GIT_USAGE);
154
+ const repository = optionalStringFlag(f, "repository");
155
+ const checkout = optionalStringFlag(f, "checkout");
156
+ const subpath = optionalStringFlag(f, "path-in-repository");
157
+ if (repository === undefined) {
158
+ throw new UsageError("--repository URL is required.", "missing_required_flag");
159
+ }
160
+ const url = validatedPluginGitUrl(repository);
161
+ if (executionMode(f).dryRun) {
162
+ return pluginPlan("install-from-git", {
163
+ method: "POST",
164
+ endpoint: "/public/api/plugins/actions/installFromGit",
165
+ identifiers: { repositoryUrl: url, },
166
+ payload: {
167
+ gitRepositoryUrl: url,
168
+ gitCheckout: checkout ?? null,
169
+ gitSubpath: subpath ?? null,
170
+ },
171
+ });
172
+ }
173
+ await _c.plugins.installFromGit({
174
+ gitRepositoryUrl: url,
175
+ gitCheckout: checkout,
176
+ gitSubpath: subpath,
177
+ });
178
+ return { installed: true, from: "git", repositoryUrl: url, };
179
+ },
180
+ usage: INSTALL_GIT_USAGE,
181
+ description: "Install a plugin by checking out a Git repository (must contain plugin.json). Fails when already installed. HTTP(S) URLs must not embed credentials.",
182
+ examples: [
183
+ "dss plugin install-from-git --repository git@github.com:acme/my-plugin.git --checkout main",
184
+ ],
185
+ },
186
+ download: {
187
+ handler: async (c, a, f) => {
188
+ requireArgs(a, 1, DOWNLOAD_USAGE);
189
+ const out = f["output"];
190
+ if (typeof out !== "string" || out.trim() === "") {
191
+ throw new UsageError("--output PATH is required.", "missing_required_flag");
192
+ }
193
+ const pluginId = validatePluginId(a[0]);
194
+ const res = await c.plugins.download(pluginId);
195
+ const bytes = await writeResponseToFileForPlugin(out, res);
196
+ return { path: out, bytes, };
197
+ },
198
+ usage: DOWNLOAD_USAGE,
199
+ description: "Download a development plugin as a zip archive to a local file.",
200
+ examples: ["dss plugin download my-plugin --output ./my-plugin.zip",],
201
+ },
202
+ "update-from-zip": {
203
+ handler: async (c, a, f) => {
204
+ requireArgs(a, 1, UPDATE_ZIP_USAGE);
205
+ const file = requireFileFlag(f, UPDATE_ZIP_USAGE);
206
+ const pluginId = validatePluginId(a[0]);
207
+ if (executionMode(f).dryRun) {
208
+ return pluginPlan("update-from-zip", {
209
+ method: "POST",
210
+ endpoint: pluginActionEndpoint(pluginId, "updateFromZip"),
211
+ identifiers: { pluginId, file, upload: "multipart", },
212
+ idempotency: "none",
213
+ });
214
+ }
215
+ await c.plugins.updateFromZip(pluginId, file);
216
+ return { updated: pluginId, from: "zip", };
217
+ },
218
+ usage: UPDATE_ZIP_USAGE,
219
+ description: "Re-install a plugin from a zip file. Fails when the plugin is not already installed.",
220
+ examples: ["dss plugin update-from-zip my-plugin --file ./my-plugin.zip",],
221
+ },
222
+ "update-from-store": {
223
+ handler: async (c, a, f) => {
224
+ requireArgs(a, 1, UPDATE_STORE_USAGE);
225
+ const pluginId = validatePluginId(a[0]);
226
+ if (executionMode(f).dryRun) {
227
+ return pluginPlan("update-from-store", {
228
+ method: "POST",
229
+ endpoint: pluginActionEndpoint(pluginId, "updateFromStore"),
230
+ identifiers: { pluginId, },
231
+ });
232
+ }
233
+ await c.plugins.updateFromStore(pluginId);
234
+ return { updated: pluginId, from: "store", };
235
+ },
236
+ usage: UPDATE_STORE_USAGE,
237
+ description: "Update a plugin from the Dataiku Store.",
238
+ examples: ["dss plugin update-from-store my-plugin",],
239
+ },
240
+ "update-from-git": {
241
+ handler: async (c, a, f) => {
242
+ requireArgs(a, 1, UPDATE_GIT_USAGE);
243
+ const pluginId = validatePluginId(a[0]);
244
+ const repository = optionalStringFlag(f, "repository");
245
+ const checkout = optionalStringFlag(f, "checkout");
246
+ const subpath = optionalStringFlag(f, "path-in-repository");
247
+ if (repository === undefined) {
248
+ throw new UsageError("--repository URL is required.", "missing_required_flag");
249
+ }
250
+ const url = validatedPluginGitUrl(repository);
251
+ if (executionMode(f).dryRun) {
252
+ return pluginPlan("update-from-git", {
253
+ method: "POST",
254
+ endpoint: pluginActionEndpoint(pluginId, "updateFromGit"),
255
+ identifiers: { pluginId, repositoryUrl: url, },
256
+ payload: {
257
+ gitRepositoryUrl: url,
258
+ gitCheckout: checkout ?? null,
259
+ gitSubpath: subpath ?? null,
260
+ },
261
+ });
262
+ }
263
+ await c.plugins.updateFromGit(pluginId, {
264
+ gitRepositoryUrl: url,
265
+ gitCheckout: checkout,
266
+ gitSubpath: subpath,
267
+ });
268
+ return { updated: true, from: "git", repositoryUrl: url, };
269
+ },
270
+ usage: UPDATE_GIT_USAGE,
271
+ description: "Update an installed plugin from a Git repository (must contain plugin.json). Fails when not installed. HTTP(S) URLs must not embed credentials.",
272
+ examples: [
273
+ "dss plugin update-from-git my-plugin --repository git@github.com:acme/my-plugin.git --checkout v2",
274
+ ],
275
+ },
276
+ "settings-get": {
277
+ handler: (c, a, f) => {
278
+ requireArgs(a, 1, SETTINGS_GET_USAGE);
279
+ return c.plugins.getSettings(validatePluginId(a[0]), {
280
+ projectKey: optionalStringFlag(f, "project-key"),
281
+ });
282
+ },
283
+ usage: SETTINGS_GET_USAGE,
284
+ description: "Get plugin settings (config parameters and code env name), optionally scoped to a project.",
285
+ examples: ["dss plugin settings-get my-plugin --project-key MYPROJECT",],
286
+ },
287
+ "settings-set": {
288
+ handler: async (c, a, f) => {
289
+ requireArgs(a, 1, SETTINGS_SET_USAGE);
290
+ const pluginId = validatePluginId(a[0]);
291
+ const mode = executionMode(f);
292
+ const settings = readSettingsInput(f);
293
+ if (mode.dryRun) {
294
+ return pluginPlan("settings-set", {
295
+ method: "POST",
296
+ endpoint: pluginRootEndpoint(pluginId, "/settings")
297
+ + scopeQueryForPlan(f["project-key"]),
298
+ identifiers: { pluginId, },
299
+ payload: {
300
+ configKeys: Object.keys(settings.config ?? {}),
301
+ codeEnvName: settings.codeEnvName,
302
+ },
303
+ });
304
+ }
305
+ await c.plugins.setSettings(pluginId, settings, {
306
+ projectKey: optionalStringFlag(f, "project-key"),
307
+ });
308
+ return { updated: pluginId, settingsKeys: Object.keys(settings), };
309
+ },
310
+ usage: SETTINGS_SET_USAGE,
311
+ description: "Replace plugin settings. Send only settings previously obtained through settings-get; DSS rejects unknown shapes.",
312
+ examples: [
313
+ 'dss plugin settings-set my-plugin --content \'{"config":{"key":"value"},"codeEnvName":"py-env"}\'',
314
+ ],
315
+ },
316
+ "code-env-create": {
317
+ handler: async (c, a, f) => {
318
+ requireArgs(a, 1, CODE_ENV_CREATE_USAGE);
319
+ const pluginId = validatePluginId(a[0]);
320
+ const conda = f["conda"] === true;
321
+ const pythonInterpreter = optionalStringFlag(f, "python-interpreter");
322
+ if (executionMode(f).dryRun) {
323
+ return pluginPlan("code-env-create", {
324
+ method: "POST",
325
+ endpoint: pluginRootEndpoint(pluginId, "/code-env/actions/create"),
326
+ identifiers: { pluginId, },
327
+ payload: {
328
+ deploymentMode: "PLUGIN_MANAGED",
329
+ conda,
330
+ pythonInterpreter: pythonInterpreter ?? null,
331
+ },
332
+ asyncKind: "future",
333
+ });
334
+ }
335
+ const future = await c.plugins.createCodeEnv(pluginId, { conda, pythonInterpreter, });
336
+ if (f["wait"] !== true)
337
+ return future;
338
+ return await waitFuture(c, future.jobId, f);
339
+ },
340
+ usage: CODE_ENV_CREATE_USAGE,
341
+ description: "Create the code env of a plugin. Returns the DSS future (jobId); --wait polls it to completion.",
342
+ examples: [
343
+ "dss plugin code-env-create my-plugin --python-interpreter PYTHON36",
344
+ "dss plugin code-env-create my-plugin --conda --wait --timeout 300000",
345
+ ],
346
+ },
347
+ "code-env-update": {
348
+ handler: async (c, a, f) => {
349
+ requireArgs(a, 1, CODE_ENV_UPDATE_USAGE);
350
+ const pluginId = validatePluginId(a[0]);
351
+ if (executionMode(f).dryRun) {
352
+ return pluginPlan("code-env-update", {
353
+ method: "POST",
354
+ endpoint: pluginRootEndpoint(pluginId, "/code-env/actions/update"),
355
+ identifiers: { pluginId, },
356
+ asyncKind: "future",
357
+ });
358
+ }
359
+ const future = await c.plugins.updateCodeEnv(pluginId);
360
+ if (f["wait"] !== true)
361
+ return future;
362
+ return await waitFuture(c, future.jobId, f);
363
+ },
364
+ usage: CODE_ENV_UPDATE_USAGE,
365
+ description: "Update (rebuild) the code env of a plugin. Returns the DSS future (jobId); --wait polls it to completion.",
366
+ examples: ["dss plugin code-env-update my-plugin --wait",],
367
+ },
368
+ "move-to-dev": {
369
+ handler: async (c, a, f) => {
370
+ requireArgs(a, 1, MOVE_TO_DEV_USAGE);
371
+ const pluginId = validatePluginId(a[0]);
372
+ if (executionMode(f).dryRun) {
373
+ return pluginPlan("move-to-dev", {
374
+ method: "POST",
375
+ endpoint: pluginActionEndpoint(pluginId, "moveToDev"),
376
+ identifiers: { pluginId, },
377
+ });
378
+ }
379
+ await c.plugins.moveToDev(pluginId);
380
+ return { moved: pluginId, to: "dev", };
381
+ },
382
+ usage: MOVE_TO_DEV_USAGE,
383
+ description: "Move an installed plugin to the development environment for editing.",
384
+ examples: ["dss plugin move-to-dev my-plugin",],
385
+ },
386
+ usages: {
387
+ handler: (c, a, f) => {
388
+ requireArgs(a, 1, USAGES_USAGE);
389
+ return c.plugins.listUsages(validatePluginId(a[0]), {
390
+ projectKey: optionalStringFlag(f, "project-key"),
391
+ });
392
+ },
393
+ usage: USAGES_USAGE,
394
+ description: "List usages of a plugin's elements in projects or globally; missingTypes reports types the usage analysis could not resolve.",
395
+ examples: ["dss plugin usages my-plugin --project-key MYPROJECT",],
396
+ },
397
+ delete: {
398
+ handler: async (c, a, f) => {
399
+ requireArgs(a, 1, DELETE_USAGE);
400
+ const pluginId = validatePluginId(a[0]);
401
+ const force = f["force"] === true;
402
+ if (executionMode(f).dryRun) {
403
+ return pluginPlan("delete", {
404
+ method: "POST",
405
+ endpoint: pluginActionEndpoint(pluginId, "delete"),
406
+ identifiers: { pluginId, },
407
+ payload: { force, },
408
+ idempotency: "none",
409
+ });
410
+ }
411
+ await c.plugins.delete(pluginId, { force, });
412
+ return { deleted: pluginId, force, };
413
+ },
414
+ usage: DELETE_USAGE,
415
+ description: "Delete a plugin. DSS refuses when usages are detected unless --force is passed.",
416
+ examples: ["dss plugin delete my-plugin", "dss plugin delete my-plugin --force",],
417
+ },
418
+ "create-dev": {
419
+ handler: async (c, a, f) => {
420
+ requireArgs(a, 1, CREATE_DEV_USAGE);
421
+ const pluginId = validatePluginId(a[0]);
422
+ const creationModeRaw = optionalStringFlag(f, "creation-mode") ?? "EMPTY";
423
+ const creationMode = normalizeCreationMode(creationModeRaw);
424
+ const repository = optionalStringFlag(f, "repository");
425
+ const checkout = optionalStringFlag(f, "checkout");
426
+ const subpath = optionalStringFlag(f, "path-in-repository");
427
+ const needsGit = creationMode !== "EMPTY";
428
+ if (needsGit && repository === undefined) {
429
+ throw new UsageError(`--repository URL is required when --creation-mode is ${creationMode}.`, "missing_required_flag");
430
+ }
431
+ const url = repository === undefined ? undefined : validatedPluginGitUrl(repository);
432
+ if (executionMode(f).dryRun) {
433
+ return pluginPlan("create-dev", {
434
+ method: "POST",
435
+ endpoint: "/public/api/plugins/actions/createDev",
436
+ identifiers: { pluginId, creationMode, ...(url ? { repositoryUrl: url, } : {}), },
437
+ payload: {
438
+ pluginId,
439
+ creationMode,
440
+ gitRepository: url ?? null,
441
+ gitCheckout: needsGit ? checkout ?? null : null,
442
+ gitSubpath: creationMode === "GIT_EXPORT" ? subpath ?? null : null,
443
+ },
444
+ });
445
+ }
446
+ await c.plugins.createDev({
447
+ pluginId,
448
+ creationMode,
449
+ ...(needsGit ? { gitRepository: url, gitCheckout: checkout, gitSubpath: subpath, } : {}),
450
+ });
451
+ return { created: pluginId, creationMode, };
452
+ },
453
+ usage: CREATE_DEV_USAGE,
454
+ description: "Create a new development plugin: EMPTY, GIT_CLONE (clone the repository as the plugin), or GIT_EXPORT (use a subpath of the repository). HTTP(S) URLs must not embed credentials.",
455
+ examples: [
456
+ "dss plugin create-dev my-plugin",
457
+ "dss plugin create-dev my-plugin --creation-mode GIT_CLONE --repository git@github.com:acme/my-plugin.git --checkout main",
458
+ ],
459
+ },
460
+ "get-git-remote": {
461
+ handler: (c, a) => {
462
+ requireArgs(a, 1, GET_GIT_REMOTE_USAGE);
463
+ return c.plugins.getGitRemote(validatePluginId(a[0]));
464
+ },
465
+ usage: GET_GIT_REMOTE_USAGE,
466
+ description: "Get the Git remote declared for a development plugin (repositoryUrl is null when unset).",
467
+ examples: ["dss plugin get-git-remote my-plugin",],
468
+ },
469
+ "set-git-remote": {
470
+ handler: async (c, a, f) => {
471
+ requireArgs(a, 1, SET_GIT_REMOTE_USAGE);
472
+ const pluginId = validatePluginId(a[0]);
473
+ const repository = optionalStringFlag(f, "repository");
474
+ if (repository === undefined) {
475
+ throw new UsageError("--repository URL is required.", "missing_required_flag");
476
+ }
477
+ const url = validatedPluginGitUrl(repository);
478
+ if (executionMode(f).dryRun) {
479
+ return pluginPlan("set-git-remote", {
480
+ method: "POST",
481
+ endpoint: pluginRootEndpoint(pluginId, "/gitRemote"),
482
+ identifiers: { pluginId, },
483
+ payload: { repositoryUrl: url, },
484
+ });
485
+ }
486
+ const result = await c.plugins.setGitRemote(pluginId, url);
487
+ return { pluginId, ...result, };
488
+ },
489
+ usage: SET_GIT_REMOTE_USAGE,
490
+ description: "Declare the Git remote for a development plugin. HTTP(S) URLs must not embed credentials; use SSH remotes or DSS-managed Git credentials.",
491
+ examples: ["dss plugin set-git-remote my-plugin --repository git@github.com:acme/my-plugin.git",],
492
+ },
493
+ "delete-git-remote": {
494
+ handler: async (c, a, f) => {
495
+ requireArgs(a, 1, DELETE_GIT_REMOTE_USAGE);
496
+ const pluginId = validatePluginId(a[0]);
497
+ if (executionMode(f).dryRun) {
498
+ return pluginPlan("delete-git-remote", {
499
+ method: "DELETE",
500
+ endpoint: pluginRootEndpoint(pluginId, "/gitRemote"),
501
+ identifiers: { pluginId, },
502
+ });
503
+ }
504
+ await c.plugins.deleteGitRemote(pluginId);
505
+ return { deleted: "gitRemote", pluginId, };
506
+ },
507
+ usage: DELETE_GIT_REMOTE_USAGE,
508
+ description: "Delete the Git remote declared for a development plugin.",
509
+ examples: ["dss plugin delete-git-remote my-plugin",],
510
+ },
511
+ "git-branches": {
512
+ handler: (c, a) => {
513
+ requireArgs(a, 1, GIT_BRANCHES_USAGE);
514
+ return c.plugins.listGitBranches(validatePluginId(a[0]));
515
+ },
516
+ usage: GIT_BRANCHES_USAGE,
517
+ description: "List the Git branches of a development plugin's declared remote.",
518
+ examples: ["dss plugin git-branches my-plugin",],
519
+ },
520
+ push: {
521
+ handler: async (c, a, f) => {
522
+ requireArgs(a, 1, PUSH_USAGE);
523
+ const pluginId = validatePluginId(a[0]);
524
+ if (executionMode(f).dryRun) {
525
+ return pluginPlan("push", {
526
+ method: "POST",
527
+ endpoint: pluginActionEndpoint(pluginId, "push"),
528
+ identifiers: { pluginId, },
529
+ });
530
+ }
531
+ const result = await c.plugins.push(pluginId);
532
+ return { pushed: pluginId, result, };
533
+ },
534
+ usage: PUSH_USAGE,
535
+ description: "Push the development plugin's content to its previously-declared Git remote.",
536
+ examples: ["dss plugin push my-plugin",],
537
+ },
538
+ pull: {
539
+ handler: async (c, a, f) => {
540
+ requireArgs(a, 1, PULL_USAGE);
541
+ const pluginId = validatePluginId(a[0]);
542
+ if (executionMode(f).dryRun) {
543
+ return pluginPlan("pull", {
544
+ method: "POST",
545
+ endpoint: pluginActionEndpoint(pluginId, "pullRebase"),
546
+ identifiers: { pluginId, },
547
+ });
548
+ }
549
+ const result = await c.plugins.pull(pluginId);
550
+ return { pulled: pluginId, result, };
551
+ },
552
+ usage: PULL_USAGE,
553
+ description: "Pull (and rebase) the development plugin's content from its Git remote.",
554
+ examples: ["dss plugin pull my-plugin",],
555
+ },
556
+ fetch: {
557
+ handler: async (c, a, f) => {
558
+ requireArgs(a, 1, FETCH_USAGE);
559
+ const pluginId = validatePluginId(a[0]);
560
+ if (executionMode(f).dryRun) {
561
+ return pluginPlan("fetch", {
562
+ method: "POST",
563
+ endpoint: pluginActionEndpoint(pluginId, "fetch"),
564
+ identifiers: { pluginId, },
565
+ });
566
+ }
567
+ const result = await c.plugins.fetch(pluginId);
568
+ return { fetched: pluginId, result, };
569
+ },
570
+ usage: FETCH_USAGE,
571
+ description: "Fetch the development plugin's content from its Git remote without changing files.",
572
+ examples: ["dss plugin fetch my-plugin",],
573
+ },
574
+ "reset-local": {
575
+ handler: async (c, a, f) => {
576
+ requireArgs(a, 1, RESET_LOCAL_USAGE);
577
+ const pluginId = validatePluginId(a[0]);
578
+ if (executionMode(f).dryRun) {
579
+ return pluginPlan("reset-local", {
580
+ method: "POST",
581
+ endpoint: pluginActionEndpoint(pluginId, "resetToLocalHeadState"),
582
+ identifiers: { pluginId, },
583
+ });
584
+ }
585
+ const result = await c.plugins.resetToLocalHeadState(pluginId);
586
+ return { reset: "localHeadState", pluginId, result, };
587
+ },
588
+ usage: RESET_LOCAL_USAGE,
589
+ description: "Reset the development plugin to its local HEAD state, discarding uncommitted edits.",
590
+ examples: ["dss plugin reset-local my-plugin",],
591
+ },
592
+ "reset-remote": {
593
+ handler: async (c, a, f) => {
594
+ requireArgs(a, 1, RESET_REMOTE_USAGE);
595
+ const pluginId = validatePluginId(a[0]);
596
+ if (executionMode(f).dryRun) {
597
+ return pluginPlan("reset-remote", {
598
+ method: "POST",
599
+ endpoint: pluginActionEndpoint(pluginId, "resetToRemoteHeadState"),
600
+ identifiers: { pluginId, },
601
+ });
602
+ }
603
+ const result = await c.plugins.resetToRemoteHeadState(pluginId);
604
+ return { reset: "remoteHeadState", pluginId, result, };
605
+ },
606
+ usage: RESET_REMOTE_USAGE,
607
+ description: "Reset the development plugin to the remote HEAD state, discarding local work. The remote must be declared first.",
608
+ examples: ["dss plugin reset-remote my-plugin",],
609
+ },
610
+ "contents-list": {
611
+ handler: (c, a) => {
612
+ requireArgs(a, 1, CONTENTS_LIST_USAGE);
613
+ return c.plugins.listContents(validatePluginId(a[0]));
614
+ },
615
+ usage: CONTENTS_LIST_USAGE,
616
+ description: "List the file tree of a development plugin.",
617
+ examples: ["dss plugin contents-list my-plugin",],
618
+ },
619
+ "contents-get": {
620
+ handler: async (c, a, f) => {
621
+ requireArgs(a, 2, CONTENTS_GET_USAGE);
622
+ const pluginId = validatePluginId(a[0]);
623
+ const path = validatePluginPath(a[1]);
624
+ const out = f["output"];
625
+ if (typeof out === "string" && out.trim() !== "") {
626
+ const res = await c.plugins.downloadFile(pluginId, path);
627
+ const bytes = await writeResponseToFileForPlugin(out, res);
628
+ return { path: out, bytes, };
629
+ }
630
+ return { data: await c.plugins.getFile(pluginId, path), };
631
+ },
632
+ usage: CONTENTS_GET_USAGE,
633
+ description: "Read a development plugin file as text. --output PATH writes the raw bytes to a local file instead.",
634
+ examples: [
635
+ "dss plugin contents-get my-plugin python/my-plugin.py",
636
+ "dss plugin contents-get my-plugin static/logo.png --output ./logo.png",
637
+ ],
638
+ },
639
+ "contents-put": {
640
+ handler: async (c, a, f) => {
641
+ requireArgs(a, 2, CONTENTS_PUT_USAGE);
642
+ const pluginId = validatePluginId(a[0]);
643
+ const path = validatePluginPath(a[1]);
644
+ if (executionMode(f).dryRun) {
645
+ const source = typeof f["file"] === "string"
646
+ ? { contentSource: "file", file: f["file"], }
647
+ : typeof f["content"] === "string"
648
+ ? { contentSource: "flag", }
649
+ : { contentSource: "stdin", };
650
+ return pluginPlan("contents-put", {
651
+ method: "POST",
652
+ endpoint: pluginContentsEndpoint(pluginId, path),
653
+ identifiers: { pluginId, path, },
654
+ payload: source,
655
+ });
656
+ }
657
+ const content = typeof f["file"] === "string"
658
+ ? fs.readFileSync(f["file"])
659
+ : typeof f["content"] === "string"
660
+ ? f["content"]
661
+ : await readStdinText();
662
+ await c.plugins.putFile(pluginId, path, content);
663
+ const bytes = typeof content === "string" ? Buffer.byteLength(content, "utf8") : content.length;
664
+ const hasher = new Bun.CryptoHasher("sha256");
665
+ hasher.update(typeof content === "string" ? content : new Uint8Array(content));
666
+ const sha256 = hasher.digest("hex");
667
+ return { written: path, pluginId, bytes, sha256, };
668
+ },
669
+ usage: CONTENTS_PUT_USAGE,
670
+ description: "Create or replace a development plugin file with text or binary content; reports the written byte count and sha256.",
671
+ examples: [
672
+ "dss plugin contents-put my-plugin python/lib.py --file ./lib.py",
673
+ "dss plugin contents-put my-plugin python/lib.py --content 'print(1)'",
674
+ ],
675
+ },
676
+ "contents-delete": {
677
+ handler: async (c, a, f) => {
678
+ requireArgs(a, 2, CONTENTS_DELETE_USAGE);
679
+ const pluginId = validatePluginId(a[0]);
680
+ const path = validatePluginPath(a[1]);
681
+ if (executionMode(f).dryRun) {
682
+ return pluginPlan("contents-delete", {
683
+ method: "DELETE",
684
+ endpoint: pluginContentsEndpoint(pluginId, path),
685
+ identifiers: { pluginId, path, },
686
+ });
687
+ }
688
+ await c.plugins.deleteFile(pluginId, path);
689
+ return { deleted: path, pluginId, };
690
+ },
691
+ usage: CONTENTS_DELETE_USAGE,
692
+ description: "Delete a file (or folder) from a development plugin.",
693
+ examples: ["dss plugin contents-delete my-plugin python/old.py",],
694
+ },
695
+ details: {
696
+ handler: (c, a) => {
697
+ requireArgs(a, 2, DETAILS_USAGE);
698
+ return c.plugins.getFileDetails(validatePluginId(a[0]), validatePluginPath(a[1]));
699
+ },
700
+ usage: DETAILS_USAGE,
701
+ description: "Get a single development plugin file's detail record (size, mime type, timestamps).",
702
+ examples: ["dss plugin details my-plugin python/my-plugin.py",],
703
+ },
704
+ "folder-add": {
705
+ handler: async (c, a, f) => {
706
+ requireArgs(a, 2, FOLDER_ADD_USAGE);
707
+ const pluginId = validatePluginId(a[0]);
708
+ const path = validatePluginPath(a[1]);
709
+ if (executionMode(f).dryRun) {
710
+ return pluginPlan("folder-add", {
711
+ method: "POST",
712
+ endpoint: pluginFoldersEndpoint(pluginId, path),
713
+ identifiers: { pluginId, path, },
714
+ });
715
+ }
716
+ await c.plugins.addFolder(pluginId, path);
717
+ return { created: path, pluginId, kind: "folder", };
718
+ },
719
+ usage: FOLDER_ADD_USAGE,
720
+ description: "Add a folder to a development plugin.",
721
+ examples: ["dss plugin folder-add my-plugin python/mylib",],
722
+ },
723
+ rename: {
724
+ handler: async (c, a, f) => {
725
+ requireArgs(a, 3, RENAME_USAGE);
726
+ const pluginId = validatePluginId(a[0]);
727
+ const path = validatePluginPath(a[1]);
728
+ const newName = validatePluginName(a[2]);
729
+ if (executionMode(f).dryRun) {
730
+ return pluginPlan("rename", {
731
+ method: "POST",
732
+ endpoint: pluginRootEndpoint(pluginId, "/contents-actions/rename"),
733
+ identifiers: { pluginId, path, newName, },
734
+ payload: { oldPath: `/${path}`, newName, },
735
+ });
736
+ }
737
+ await c.plugins.rename(pluginId, path, newName);
738
+ return { renamed: path, to: newName, pluginId, };
739
+ },
740
+ usage: RENAME_USAGE,
741
+ description: "Rename a file or folder inside a development plugin (same parent folder).",
742
+ examples: ["dss plugin rename my-plugin python/old.py new.py",],
743
+ },
744
+ move: {
745
+ handler: async (c, a, f) => {
746
+ requireArgs(a, 3, MOVE_USAGE);
747
+ const pluginId = validatePluginId(a[0]);
748
+ const path = validatePluginPath(a[1]);
749
+ const destination = validatePluginDestinationPath(a[2]);
750
+ if (executionMode(f).dryRun) {
751
+ return pluginPlan("move", {
752
+ method: "POST",
753
+ endpoint: pluginRootEndpoint(pluginId, "/contents-actions/move"),
754
+ identifiers: { pluginId, path, destinationFolder: destination, },
755
+ payload: { oldPath: `/${path}`, newPath: destination, },
756
+ });
757
+ }
758
+ await c.plugins.move(pluginId, path, destination);
759
+ return { moved: path, to: destination, pluginId, };
760
+ },
761
+ usage: MOVE_USAGE,
762
+ description: "Move a file or folder inside a development plugin. The destination folder must already exist.",
763
+ examples: ["dss plugin move my-plugin python/old.py python/mylib",],
764
+ },
765
+ };
766
+ /* ------------------------------------------------------------------ */
767
+ /* Helpers */
768
+ /* ------------------------------------------------------------------ */
769
+ function normalizeCreationMode(value) {
770
+ const normalized = value.trim().toUpperCase().replace(/-/g, "_");
771
+ if (normalized === "EMPTY" || normalized === "GIT_CLONE" || normalized === "GIT_EXPORT") {
772
+ return normalized;
773
+ }
774
+ throw new UsageError("--creation-mode must be one of EMPTY, GIT_CLONE, GIT_EXPORT.", "invalid_enum");
775
+ }
776
+ function readSettingsInput(flags) {
777
+ let text;
778
+ if (typeof flags["file"] === "string") {
779
+ text = fs.readFileSync(flags["file"], "utf-8");
780
+ }
781
+ else if (typeof flags["content"] === "string") {
782
+ text = flags["content"];
783
+ }
784
+ else if (flags["stdin"] === true) {
785
+ text = readStdinText();
786
+ }
787
+ else {
788
+ throw new UsageError("--content JSON, --file PATH, or --stdin is required.", "missing_required_flag");
789
+ }
790
+ const parsed = JSON.parse(text);
791
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
792
+ throw new UsageError("Plugin settings must be a JSON object.", "validation_failed");
793
+ }
794
+ return parsed;
795
+ }
796
+ async function writeResponseToFileForPlugin(path, res) {
797
+ const { writeResponseToFile, } = await import("../../utils/response-file.js");
798
+ return writeResponseToFile(path, res);
799
+ }