dataiku-sdk 0.12.6 → 1.0.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 (55) hide show
  1. package/README.md +60 -14
  2. package/bin/dss.js +1 -1
  3. package/dist/packages/types/src/index.d.ts +128 -3
  4. package/dist/packages/types/src/index.js +106 -3
  5. package/dist/src/cli/coerce.d.ts +2 -1
  6. package/dist/src/cli/coerce.js +84 -26
  7. package/dist/src/cli/commands/app.js +6 -6
  8. package/dist/src/cli/commands/code.js +2 -2
  9. package/dist/src/cli/commands/dashboard.js +102 -13
  10. package/dist/src/cli/commands/data-quality.js +8 -8
  11. package/dist/src/cli/commands/dataset.js +11 -7
  12. package/dist/src/cli/commands/folder.js +5 -4
  13. package/dist/src/cli/commands/future.js +2 -2
  14. package/dist/src/cli/commands/index.js +2 -0
  15. package/dist/src/cli/commands/job.js +11 -11
  16. package/dist/src/cli/commands/notebook.js +1 -1
  17. package/dist/src/cli/commands/project-git.d.ts +2 -0
  18. package/dist/src/cli/commands/project-git.js +563 -0
  19. package/dist/src/cli/commands/project.js +2 -2
  20. package/dist/src/cli/commands/recipe.js +14 -17
  21. package/dist/src/cli/commands/scenario.js +4 -4
  22. package/dist/src/cli/contract.d.ts +5 -4
  23. package/dist/src/cli/contract.js +432 -62
  24. package/dist/src/cli/doctor.js +6 -6
  25. package/dist/src/cli/flags.js +25 -2
  26. package/dist/src/cli/helpers/dataset.js +1 -1
  27. package/dist/src/cli/output.d.ts +2 -1
  28. package/dist/src/cli/output.js +2 -6
  29. package/dist/src/cli/usage.d.ts +1 -1
  30. package/dist/src/cli/usage.js +2 -1
  31. package/dist/src/cli/version.d.ts +2 -2
  32. package/dist/src/cli/version.js +2 -2
  33. package/dist/src/cli.js +43 -26
  34. package/dist/src/client.d.ts +4 -0
  35. package/dist/src/client.js +12 -0
  36. package/dist/src/errors.d.ts +1 -1
  37. package/dist/src/index.d.ts +3 -2
  38. package/dist/src/index.js +2 -1
  39. package/dist/src/resources/connections.d.ts +3 -1
  40. package/dist/src/resources/connections.js +82 -21
  41. package/dist/src/resources/dashboards.d.ts +14 -0
  42. package/dist/src/resources/dashboards.js +156 -4
  43. package/dist/src/resources/datasets.js +42 -2
  44. package/dist/src/resources/folders.js +7 -3
  45. package/dist/src/resources/project-git.d.ts +154 -0
  46. package/dist/src/resources/project-git.js +427 -0
  47. package/dist/src/resources/recipes.js +4 -4
  48. package/dist/src/schemas.d.ts +2 -2
  49. package/dist/src/schemas.js +1 -1
  50. package/dist/src/skill.js +9 -211
  51. package/package.json +7 -4
  52. package/packages/types/dist/index.d.ts +128 -3
  53. package/packages/types/dist/index.js +106 -3
  54. package/plugin.json +15 -0
  55. package/skills/dataiku-dss/SKILL.md +220 -0
@@ -32,9 +32,9 @@ export const codeCommands = {
32
32
  const run = await c.scenarios.runScript(script, {
33
33
  envName: f["env"],
34
34
  projectKey: f["project-key"],
35
- timeoutMs: num(f["timeout"]),
35
+ timeoutMs: num(f["timeout"], "--timeout"),
36
36
  keepScenario: f["keep"] === true,
37
- maxLogBytes: num(f["max-log-bytes"]),
37
+ maxLogBytes: num(f["max-log-bytes"], "--max-log-bytes"),
38
38
  });
39
39
  const result = {
40
40
  outcome: run.outcome,
@@ -1,5 +1,7 @@
1
+ import * as fs from "node:fs";
2
+ import { ClientValidationError, } from "../../errors.js";
1
3
  import { deepMerge, } from "../../utils/deep-merge.js";
2
- import { jsonInput, } from "../coerce.js";
4
+ import { jsonInput, parseBooleanOption, } from "../coerce.js";
3
5
  import { readIfExists, skipResult, } from "../output.js";
4
6
  import { requireArgs, UsageError, } from "../usage.js";
5
7
  export const dashboardCommands = {
@@ -18,12 +20,91 @@ export const dashboardCommands = {
18
20
  description: "Get dashboard definition.",
19
21
  examples: ["dss dashboard get DASHBOARD_ID",],
20
22
  },
23
+ export: {
24
+ handler: async (c, a, f) => {
25
+ requireArgs(a, 1, "dss dashboard export <id> --output PATH [--paper-size SIZE] [--orientation PORTRAIT|LANDSCAPE] [--file-type PDF] [--slide-index N] [--project-key KEY]");
26
+ const outputPath = f["output"];
27
+ if (typeof outputPath !== "string" || !outputPath.trim()) {
28
+ throw new UsageError("--output PATH is required.", "missing_required_flag");
29
+ }
30
+ const paperSizeFlag = f["paper-size"];
31
+ if (paperSizeFlag !== undefined && typeof paperSizeFlag !== "string") {
32
+ throw new UsageError("--paper-size requires a value.");
33
+ }
34
+ const paperSize = (paperSizeFlag ?? "A4").trim();
35
+ if (!paperSize)
36
+ throw new UsageError("--paper-size must not be empty.");
37
+ const orientationFlag = f["orientation"];
38
+ if (orientationFlag !== undefined && typeof orientationFlag !== "string") {
39
+ throw new UsageError("--orientation requires a value.");
40
+ }
41
+ const orientation = (orientationFlag ?? "LANDSCAPE").trim().toUpperCase();
42
+ if (orientation !== "PORTRAIT" && orientation !== "LANDSCAPE") {
43
+ throw new UsageError("--orientation must be PORTRAIT or LANDSCAPE.", "invalid_enum");
44
+ }
45
+ const fileTypeFlag = f["file-type"];
46
+ if (fileTypeFlag !== undefined && typeof fileTypeFlag !== "string") {
47
+ throw new UsageError("--file-type requires a value.");
48
+ }
49
+ const fileType = (fileTypeFlag ?? "PDF").trim().toUpperCase();
50
+ if (fileType !== "PDF") {
51
+ throw new UsageError("--file-type must be PDF.", "invalid_enum");
52
+ }
53
+ const slideIndexFlag = f["slide-index"];
54
+ if (slideIndexFlag !== undefined
55
+ && (typeof slideIndexFlag !== "string" || !/^(0|[1-9]\d*)$/u.test(slideIndexFlag))) {
56
+ throw new UsageError("--slide-index must be a non-negative integer.");
57
+ }
58
+ const slideIndex = slideIndexFlag === undefined ? 0 : Number(slideIndexFlag);
59
+ const response = await c.dashboards.export(a[0], {
60
+ paperSize,
61
+ orientation,
62
+ fileType,
63
+ slideIndex,
64
+ projectKey: f["project-key"],
65
+ });
66
+ const bytes = Buffer.from(await response.arrayBuffer());
67
+ const isPdf = bytes.length >= 5
68
+ && bytes[0] === 0x25
69
+ && bytes[1] === 0x50
70
+ && bytes[2] === 0x44
71
+ && bytes[3] === 0x46
72
+ && bytes[4] === 0x2d;
73
+ if (!isPdf) {
74
+ throw new ClientValidationError("DSS dashboard export did not return a PDF.", "validation_failed", "Confirm DSS graphics export is configured and retry the export.");
75
+ }
76
+ fs.writeFileSync(outputPath, bytes);
77
+ return {
78
+ path: outputPath,
79
+ bytes: bytes.length,
80
+ contentType: response.headers.get("content-type") ?? undefined,
81
+ dashboardId: a[0],
82
+ paperSize,
83
+ orientation,
84
+ fileType,
85
+ slideIndex,
86
+ };
87
+ },
88
+ usage: "dss dashboard export <id> --output PATH [--paper-size SIZE] [--orientation PORTRAIT|LANDSCAPE] [--file-type PDF] [--slide-index N] [--project-key KEY]",
89
+ description: "Render a dashboard page as PDF. Requires DSS graphics export to be configured.",
90
+ examples: [
91
+ "dss dashboard export DASHBOARD_ID --output dashboard.pdf",
92
+ "dss dashboard export DASHBOARD_ID --output dashboard.pdf --orientation LANDSCAPE --slide-index 0",
93
+ ],
94
+ },
21
95
  create: {
22
96
  handler: async (c, _a, f) => {
23
- const name = f["name"];
24
- if (!name)
25
- throw new UsageError("--name is required. Usage: dss dashboard create --name NAME");
26
97
  const settings = jsonInput(f);
98
+ const flagName = f["name"];
99
+ const settingsName = settings?.["name"];
100
+ const name = flagName ?? (typeof settingsName === "string" ? settingsName : undefined);
101
+ const listed = parseBooleanOption(f["listed"], "--listed");
102
+ if (!name) {
103
+ throw new UsageError("--name or dashboard settings containing a string name are required. Usage: dss dashboard create --name NAME");
104
+ }
105
+ const payload = { ...(settings ?? { pages: [], }), name, };
106
+ if (listed !== undefined)
107
+ payload.listed = listed;
27
108
  const pk = f["project-key"];
28
109
  if (f["if-not-exists"] === true || f["dry-run"] === true) {
29
110
  const existing = (await c.dashboards.list(pk)).find((dashboard) => dashboard.name === name);
@@ -36,46 +117,54 @@ export const dashboardCommands = {
36
117
  action: "create",
37
118
  resource: "dashboard",
38
119
  name,
39
- payload: settings ?? { pages: [], },
120
+ payload,
40
121
  ...(existing ? { current: existing, } : {}),
41
122
  };
42
123
  }
43
124
  }
44
125
  const created = await c.dashboards.create({
45
126
  name,
127
+ listed,
46
128
  settings,
47
129
  projectKey: pk,
48
130
  });
49
131
  return { created: created.id, resource: "dashboard", ...created, };
50
132
  },
51
- usage: "dss dashboard create --name NAME [--data JSON|--data-file PATH|--stdin] [--if-not-exists] [--dry-run] [--project-key KEY]",
52
- description: "Create a dashboard. Defaults to an empty pages array.",
53
- examples: ["dss dashboard create --name 'Agent dashboard' --dry-run",],
133
+ usage: "dss dashboard create (--name NAME|--data JSON|--data-file PATH|--stdin) [--listed true|false] [--if-not-exists] [--dry-run] [--project-key KEY]",
134
+ description: "Create a dashboard from raw settings or minimal name/listed fields. Defaults to an empty pages array.",
135
+ examples: [
136
+ "dss dashboard create --name 'Agent dashboard' --listed true --dry-run",
137
+ 'dss dashboard create --data \'{"name":"Agent dashboard","pages":[]}\'',
138
+ ],
54
139
  },
55
140
  update: {
56
141
  handler: async (c, a, f) => {
57
- requireArgs(a, 1, "dss dashboard update <id> [--name NAME|--data JSON]");
142
+ requireArgs(a, 1, "dss dashboard update <id> [--name NAME|--listed true|false|--data JSON]");
58
143
  const name = f["name"];
144
+ const listed = parseBooleanOption(f["listed"], "--listed");
59
145
  const data = jsonInput(f);
60
- if (!name && !data) {
61
- throw new UsageError("--name, --data, --data-file, or --stdin is required.");
146
+ if (name === undefined && listed === undefined && !data) {
147
+ throw new UsageError("--name, --listed, --data, --data-file, or --stdin is required.");
62
148
  }
63
149
  if (f["dry-run"] === true) {
64
150
  const current = await c.dashboards.get(a[0], f["project-key"]);
65
151
  const next = deepMerge(current, data ?? {});
66
152
  if (name !== undefined)
67
153
  next.name = name;
154
+ if (listed !== undefined)
155
+ next.listed = listed;
68
156
  return { dryRun: true, action: "update", resource: "dashboard", id: a[0], current, next, };
69
157
  }
70
158
  return c.dashboards.update(a[0], {
71
159
  name,
160
+ listed,
72
161
  data,
73
162
  projectKey: f["project-key"],
74
163
  });
75
164
  },
76
- usage: "dss dashboard update <id> (--name NAME|--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]",
165
+ usage: "dss dashboard update <id> (--name NAME|--listed true|false|--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]",
77
166
  description: "Update dashboard settings via merge.",
78
- examples: ["dss dashboard update DASHBOARD_ID --name 'New name' --dry-run",],
167
+ examples: ["dss dashboard update DASHBOARD_ID --listed true --dry-run",],
79
168
  },
80
169
  delete: {
81
170
  handler: async (c, a, f) => {
@@ -164,10 +164,10 @@ export const dataQualityCommands = {
164
164
  handler: (c, a, f) => {
165
165
  requireArgs(a, 1, "dss data-quality history <dataset>");
166
166
  return c.dataQuality.history(a[0], {
167
- minTimestamp: num(f["min-timestamp"]),
168
- maxTimestamp: num(f["max-timestamp"]),
169
- resultsPerPage: num(f["results-per-page"]),
170
- page: num(f["page"]),
167
+ minTimestamp: num(f["min-timestamp"], "--min-timestamp"),
168
+ maxTimestamp: num(f["max-timestamp"], "--max-timestamp"),
169
+ resultsPerPage: num(f["results-per-page"], "--results-per-page"),
170
+ page: num(f["page"], "--page"),
171
171
  ruleId: f["rule-id"],
172
172
  projectKey: f["project-key"],
173
173
  });
@@ -187,8 +187,8 @@ export const dataQualityCommands = {
187
187
  },
188
188
  "project-timeline": {
189
189
  handler: (c, _a, f) => c.dataQuality.projectTimeline({
190
- minTimestamp: num(f["min-timestamp"]),
191
- maxTimestamp: num(f["max-timestamp"]),
190
+ minTimestamp: num(f["min-timestamp"], "--min-timestamp"),
191
+ maxTimestamp: num(f["max-timestamp"], "--max-timestamp"),
192
192
  projectKey: f["project-key"],
193
193
  }),
194
194
  usage: "dss data-quality project-timeline [--min-timestamp MS] [--max-timestamp MS] [--project-key KEY]",
@@ -201,10 +201,10 @@ export const dataQualityCommands = {
201
201
  const pk = f["project-key"];
202
202
  const options = {
203
203
  partition: f["partition"],
204
- pollIntervalMs: num(f["poll-interval"]),
204
+ pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
205
205
  ruleId: f["rule-id"],
206
206
  projectKey: pk,
207
- timeoutMs: num(f["timeout"]),
207
+ timeoutMs: num(f["timeout"], "--timeout"),
208
208
  };
209
209
  if (f["dry-run"] === true) {
210
210
  const params = new URLSearchParams();
@@ -108,9 +108,9 @@ export const datasetCommands = {
108
108
  handler: (c, a, f) => {
109
109
  requireArgs(a, 1, "dss dataset preview <name>");
110
110
  return c.datasets.preview(a[0], {
111
- maxRows: num(f["max-rows"]),
111
+ maxRows: num(f["max-rows"], "--max-rows"),
112
112
  projectKey: f["project-key"],
113
- timeoutMs: num(f["timeout"]),
113
+ timeoutMs: num(f["timeout"], "--timeout"),
114
114
  });
115
115
  },
116
116
  usage: "dss dataset preview <name> [--max-rows N] [--rows N] [--project-key KEY] [--timeout MS]",
@@ -132,7 +132,7 @@ export const datasetCommands = {
132
132
  const result = await c.datasets.download(a[0], {
133
133
  outputPath: f["output"],
134
134
  projectKey: f["project-key"],
135
- limit: num(f["limit"]),
135
+ limit: num(f["limit"], "--limit"),
136
136
  });
137
137
  if (result.truncated) {
138
138
  enqueueCliWarning({
@@ -166,8 +166,11 @@ export const datasetCommands = {
166
166
  const name = f["name"];
167
167
  const connection = f["connection"];
168
168
  const dsType = f["type"];
169
- if (!name || !connection || !dsType) {
170
- throw new UsageError("--name, --connection, and --type are required. Usage: dss dataset create --name NAME --connection CONN --type TYPE");
169
+ if (!name || !dsType) {
170
+ throw new UsageError("--name and --type are required. Usage: dss dataset create --name NAME --type TYPE [--connection CONN]");
171
+ }
172
+ if (!connection && dsType.toLowerCase() !== "uploadedfiles") {
173
+ throw new UsageError("--connection is required unless --type is UploadedFiles.");
171
174
  }
172
175
  const payload = {
173
176
  datasetName: name,
@@ -198,9 +201,10 @@ export const datasetCommands = {
198
201
  const moved = await moveCreatedItemsToZone(c, f, [{ objectId: name, objectType: "DATASET", },], pk);
199
202
  return { created: name, resource: "dataset", ...moved, };
200
203
  },
201
- usage: "dss dataset create --name NAME --connection CONN --type TYPE [--zone ZONE|--zone-id ID] [--if-not-exists] [--dry-run] [--project-key KEY]",
202
- description: "Create a new dataset.",
204
+ usage: "dss dataset create --name NAME --type TYPE [--connection CONN] [--zone ZONE|--zone-id ID] [--if-not-exists] [--dry-run] [--project-key KEY]",
205
+ description: "Create a new dataset. UploadedFiles uses the server's default upload connection when omitted.",
203
206
  examples: [
207
+ "dss dataset create --name uploads --type UploadedFiles",
204
208
  "dss dataset create --name orders --connection filesystem --type Filesystem",
205
209
  "dss dataset create --name orders --connection filesystem --type Filesystem --zone Experiments --dry-run",
206
210
  ],
@@ -18,8 +18,8 @@ export const folderCommands = {
18
18
  const type = f["type"];
19
19
  const connection = f["connection"];
20
20
  const pk = f["project-key"];
21
- if (!name || !type || !connection) {
22
- throw new UsageError("--name, --type, and --connection are required. Usage: dss folder create --name NAME --type TYPE --connection CONN [--path PATH]");
21
+ if (!name) {
22
+ throw new UsageError("--name is required. Usage: dss folder create --name NAME [--type TYPE] [--connection CONN] [--path PATH]");
23
23
  }
24
24
  const payload = {
25
25
  name,
@@ -48,9 +48,10 @@ export const folderCommands = {
48
48
  const created = await c.folders.create(payload);
49
49
  return { created: created.id ?? name, resource: "folder", ...created, };
50
50
  },
51
- usage: "dss folder create --name NAME --type TYPE --connection CONN [--path PATH] [--if-not-exists] [--dry-run] [--project-key KEY]",
52
- description: "Create a managed folder.",
51
+ usage: "dss folder create --name NAME [--type TYPE] [--connection CONN] [--path PATH] [--if-not-exists] [--dry-run] [--project-key KEY]",
52
+ description: "Create a managed folder; when omitted, the connection is selected from writable managed-folder storage and DSS infers the folder type.",
53
53
  examples: [
54
+ "dss folder create --name exports",
54
55
  "dss folder create --name exports --type S3 --connection s3_connection",
55
56
  "dss folder create --name exports --type S3 --connection s3_connection --path /dataiku/MYPROJ/exports --dry-run",
56
57
  ],
@@ -24,8 +24,8 @@ export const futureCommands = {
24
24
  handler: (c, a, f) => {
25
25
  requireArgs(a, 1, "dss future wait <id>");
26
26
  return c.futures.wait(a[0], {
27
- pollIntervalMs: num(f["poll-interval"]),
28
- timeoutMs: num(f["timeout"]),
27
+ pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
28
+ timeoutMs: num(f["timeout"], "--timeout"),
29
29
  });
30
30
  },
31
31
  usage: "dss future wait <id> [--timeout MS] [--poll-interval MS]",
@@ -24,6 +24,7 @@ import { mlTaskCommands, } from "./ml-task.js";
24
24
  import { modelEvaluationStoreCommands, } from "./model-evaluation-store.js";
25
25
  import { notebookCommands, } from "./notebook.js";
26
26
  import { projectDeployerCommands, } from "./project-deployer.js";
27
+ import { projectGitCommands, } from "./project-git.js";
27
28
  import { projectLibraryCommands, } from "./project-library.js";
28
29
  import { projectCommands, } from "./project.js";
29
30
  import { recipeCommands, } from "./recipe.js";
@@ -49,6 +50,7 @@ export const commands = {
49
50
  "api-deployer": apiDeployerCommands,
50
51
  bundle: bundleCommands,
51
52
  "project-deployer": projectDeployerCommands,
53
+ "project-git": projectGitCommands,
52
54
  "project-library": projectLibraryCommands,
53
55
  "streaming-endpoint": streamingEndpointCommands,
54
56
  "continuous-activity": continuousActivityCommands,
@@ -67,7 +67,7 @@ function filteredJobList(jobs, flags) {
67
67
  return false;
68
68
  return true;
69
69
  });
70
- const limit = flags["latest"] === true ? 1 : num(flags["limit"]);
70
+ const limit = flags["latest"] === true ? 1 : num(flags["limit"], "--limit");
71
71
  if (limit !== undefined)
72
72
  result = result.slice(0, Math.max(0, limit));
73
73
  return result;
@@ -259,9 +259,9 @@ export const jobCommands = {
259
259
  const options = {
260
260
  buildMode: f["build-mode"],
261
261
  partition: f["partition"],
262
- pollIntervalMs: num(f["poll-interval"]),
262
+ pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
263
263
  targetType: jobBuildTargetTypeFromFlags(f),
264
- timeoutMs: num(f["timeout"]),
264
+ timeoutMs: num(f["timeout"], "--timeout"),
265
265
  };
266
266
  if (f["dry-run"] === true) {
267
267
  return {
@@ -297,8 +297,8 @@ export const jobCommands = {
297
297
  logFilter: jobLogFilterFromFlag(f["log-filter"]),
298
298
  maxLogLines: maxLogLinesFromFlags(f),
299
299
  partition: f["partition"],
300
- pollIntervalMs: num(f["poll-interval"]),
301
- timeoutMs: num(f["timeout"]),
300
+ pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
301
+ timeoutMs: num(f["timeout"], "--timeout"),
302
302
  summary: f["summary"] === true,
303
303
  targetType: jobBuildTargetTypeFromFlags(f),
304
304
  };
@@ -331,8 +331,8 @@ export const jobCommands = {
331
331
  includeLogs: f["include-logs"] === true,
332
332
  logFilter: jobLogFilterFromFlag(f["log-filter"]),
333
333
  maxLogLines: maxLogLinesFromFlags(f),
334
- pollIntervalMs: num(f["poll-interval"]),
335
- timeoutMs: num(f["timeout"]),
334
+ pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
335
+ timeoutMs: num(f["timeout"], "--timeout"),
336
336
  summary: f["summary"] === true,
337
337
  projectKey: f["project-key"],
338
338
  });
@@ -351,8 +351,8 @@ export const jobCommands = {
351
351
  includeLogs: f["include-logs"] === true,
352
352
  logFilter: jobLogFilterFromFlag(f["log-filter"]),
353
353
  maxLogLines: maxLogLinesFromFlags(f),
354
- pollIntervalMs: num(f["poll-interval"]),
355
- timeoutMs: num(f["timeout"]),
354
+ pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
355
+ timeoutMs: num(f["timeout"], "--timeout"),
356
356
  summary: f["summary"] !== false,
357
357
  projectKey: f["project-key"],
358
358
  };
@@ -370,8 +370,8 @@ export const jobCommands = {
370
370
  includeLogs: f["include-logs"] === true,
371
371
  logFilter: jobLogFilterFromFlag(f["log-filter"]),
372
372
  maxLogLines: maxLogLinesFromFlags(f),
373
- pollIntervalMs: num(f["poll-interval"]),
374
- timeoutMs: num(f["timeout"]),
373
+ pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
374
+ timeoutMs: num(f["timeout"], "--timeout"),
375
375
  summary: true,
376
376
  projectKey: f["project-key"],
377
377
  };
@@ -217,7 +217,7 @@ export const notebookCommands = {
217
217
  const pk = f["project-key"];
218
218
  const options = {
219
219
  cellId: f["cell-id"],
220
- numRunsToRetain: num(f["retain"]),
220
+ numRunsToRetain: num(f["retain"], "--retain"),
221
221
  projectKey: pk,
222
222
  };
223
223
  if (f["dry-run"] === true) {
@@ -0,0 +1,2 @@
1
+ import type { CommandMeta } from "../types.js";
2
+ export declare const projectGitCommands: Record<string, CommandMeta>;