dataiku-sdk 0.5.0 → 0.6.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.
package/dist/src/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync, } from "node:fs";
3
- import { writeFile, } from "node:fs/promises";
4
- import { dirname, resolve, } from "node:path";
3
+ import { mkdir, writeFile, } from "node:fs/promises";
4
+ import { dirname, join, resolve, } from "node:path";
5
5
  import { createInterface, } from "node:readline";
6
6
  import { Writable, } from "node:stream";
7
7
  import { fileURLToPath, } from "node:url";
@@ -12,6 +12,7 @@ import { DataikuError, dataikuErrorCode, } from "./errors.js";
12
12
  import { AGENTS, detectAgents, findWorkspaceRoot, installSkill, } from "./skill.js";
13
13
  import { appendCleanupLedgerEntry, readCleanupLedger, } from "./utils/cleanup-ledger.js";
14
14
  import { deepMerge, } from "./utils/deep-merge.js";
15
+ import { sanitizeFileName, } from "./utils/sanitize.js";
15
16
  // ---------------------------------------------------------------------------
16
17
  // Utility helpers
17
18
  // ---------------------------------------------------------------------------
@@ -43,12 +44,48 @@ function jobBuildTargetType(v) {
43
44
  if (v === undefined)
44
45
  return "DATASET";
45
46
  if (typeof v !== "string") {
46
- throw new UsageError("Invalid --type value for job build. Use DATASET or MANAGED_FOLDER.", "invalid_enum");
47
+ throw new UsageError("Invalid job target type. Use dataset or managed-folder.", "invalid_enum");
47
48
  }
48
49
  const normalized = v.trim().toUpperCase().replace(/-/g, "_");
49
50
  if (normalized === "DATASET" || normalized === "MANAGED_FOLDER")
50
51
  return normalized;
51
- throw new UsageError("Invalid --type value for job build. Use DATASET or MANAGED_FOLDER.", "invalid_enum");
52
+ throw new UsageError("Invalid job target type. Use dataset or managed-folder.", "invalid_enum");
53
+ }
54
+ function jobBuildTargetTypeFromFlags(flags) {
55
+ return jobBuildTargetType(flags["target-type"] ?? flags["type"]);
56
+ }
57
+ function maxLogLinesFromFlags(flags) {
58
+ return num(flags["max-log-lines"] ?? flags["max-lines"]);
59
+ }
60
+ function jobLogFilterFromFlag(v) {
61
+ if (v === undefined)
62
+ return undefined;
63
+ if (typeof v !== "string") {
64
+ throw new UsageError("Invalid --log-filter value. Use stdout, stderr, user, or errors.", "invalid_enum");
65
+ }
66
+ const normalized = v.trim().toLowerCase();
67
+ if (normalized === "stdout" || normalized === "stderr" || normalized === "user"
68
+ || normalized === "errors") {
69
+ return normalized;
70
+ }
71
+ throw new UsageError("Invalid --log-filter value. Use stdout, stderr, user, or errors.", "invalid_enum");
72
+ }
73
+ function recipePayloadBackupPath(recipeName, backupDir) {
74
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
75
+ return join(backupDir, `${sanitizeFileName(recipeName, "recipe")}-${stamp}.payload`);
76
+ }
77
+ function recipeRunShouldWait(flags) {
78
+ if (flags["wait"] === true && flags["no-wait"] === true) {
79
+ throw new UsageError("--wait and --no-wait are mutually exclusive.", "invalid_enum");
80
+ }
81
+ const waitImplied = flags["include-logs"] === true
82
+ || flags["summary"] === true
83
+ || flags["timeout"] !== undefined
84
+ || flags["poll-interval"] !== undefined;
85
+ if (flags["no-wait"] === true && waitImplied) {
86
+ throw new UsageError("--include-logs, --summary, --timeout, and --poll-interval require waiting; remove --no-wait.", "invalid_enum");
87
+ }
88
+ return flags["no-wait"] !== true && (flags["wait"] === true || waitImplied);
52
89
  }
53
90
  function splitCsvFlag(v) {
54
91
  if (typeof v !== "string")
@@ -130,7 +167,7 @@ function json(v) {
130
167
  return undefined;
131
168
  return JSON.parse(v);
132
169
  }
133
- const SQL_QUERY_USAGE = "dss sql query [SQL | --sql QUERY | --sql-file PATH | --sql - | --stdin] (--connection CONN | --dataset FULL_NAME) [--database DB] [--project-key KEY]";
170
+ const SQL_QUERY_USAGE = "dss sql query [SQL | --sql QUERY | --sql-file PATH | --sql - | --stdin] (--connection CONN | --dataset FULL_NAME) [--database DB] [--output PATH|--output-file PATH] [--request-timeout MS] [--project-key KEY]";
134
171
  function readStdinText() {
135
172
  return readFileSync(0, "utf-8");
136
173
  }
@@ -146,6 +183,48 @@ function jsonInput(flags) {
146
183
  }
147
184
  return undefined;
148
185
  }
186
+ function unknownJsonInput(flags) {
187
+ if (flags["stdin"] === true)
188
+ return JSON.parse(readStdinText());
189
+ if (typeof flags["data-file"] === "string") {
190
+ return JSON.parse(readFileSync(flags["data-file"], "utf-8"));
191
+ }
192
+ if (typeof flags["data"] === "string")
193
+ return JSON.parse(flags["data"]);
194
+ return undefined;
195
+ }
196
+ function schemaColumnsInput(flags, usage) {
197
+ const input = unknownJsonInput(flags);
198
+ if (input === undefined) {
199
+ throw new UsageError(`--data, --data-file, or --stdin is required. Usage: ${usage}`);
200
+ }
201
+ const columns = Array.isArray(input)
202
+ ? input
203
+ : input && typeof input === "object" && Array.isArray(input.columns)
204
+ ? input.columns
205
+ : undefined;
206
+ if (!columns) {
207
+ throw new UsageError("Schema input must be an array of columns or an object with a columns array.");
208
+ }
209
+ return columns.map((column, index) => {
210
+ if (!column || typeof column !== "object" || Array.isArray(column)) {
211
+ throw new UsageError(`Schema column at index ${index} must be an object.`);
212
+ }
213
+ const record = column;
214
+ if (typeof record.name !== "string" || record.name.length === 0) {
215
+ throw new UsageError(`Schema column at index ${index} is missing string field "name".`);
216
+ }
217
+ if (typeof record.type !== "string" || record.type.length === 0) {
218
+ throw new UsageError(`Schema column "${record.name}" is missing string field "type".`);
219
+ }
220
+ return {
221
+ ...record,
222
+ name: record.name,
223
+ type: record.type,
224
+ ...(typeof record.comment === "string" ? { comment: record.comment, } : {}),
225
+ };
226
+ });
227
+ }
149
228
  function textInput(flags) {
150
229
  if (typeof flags["content"] === "string")
151
230
  return flags["content"];
@@ -289,6 +368,28 @@ function formatLineDiff(remoteName, localPath, remoteContent, localContent) {
289
368
  function writeCommandResult(result) {
290
369
  process.stdout.write(`${JSON.stringify(result ?? { ok: true, }, null, 2)}\n`);
291
370
  }
371
+ function transientBodyWithTargetContext(body, target, elapsedMs) {
372
+ try {
373
+ const parsed = JSON.parse(body);
374
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
375
+ const record = parsed;
376
+ const message = typeof record.message === "string" && record.message.length > 0
377
+ ? `Target: ${target}\nElapsed: ${elapsedMs}ms\n${record.message}`
378
+ : `Target: ${target}\nElapsed: ${elapsedMs}ms`;
379
+ return JSON.stringify({ ...record, message, target, elapsedMs, });
380
+ }
381
+ }
382
+ catch {
383
+ // Non-JSON DSS bodies are wrapped as text below.
384
+ }
385
+ return `Target: ${target}\nElapsed: ${elapsedMs}ms\n${body}`;
386
+ }
387
+ function addTransientTargetContext(error, target, elapsedMs) {
388
+ if (error instanceof DataikuError && error.category === "transient") {
389
+ throw new DataikuError(error.status, error.statusText, transientBodyWithTargetContext(error.body, target, elapsedMs), error.retry);
390
+ }
391
+ throw error;
392
+ }
292
393
  function isFailedWaitResult(result) {
293
394
  if (result === null || typeof result !== "object" || Array.isArray(result))
294
395
  return false;
@@ -331,6 +432,7 @@ function planResult(resource, action, options) {
331
432
  ...(options.method ? { method: options.method, } : {}),
332
433
  ...(options.endpoint ? { endpoint: options.endpoint, } : {}),
333
434
  ...(options.payload !== undefined ? { payload: options.payload, } : {}),
435
+ ...(options.localWrites !== undefined ? { localWrites: options.localWrites, } : {}),
334
436
  ...(options.wait !== undefined ? { wait: options.wait, } : {}),
335
437
  idempotency: options.idempotency,
336
438
  async: options.asyncKind,
@@ -507,6 +609,7 @@ const BOOLEAN_FLAGS = new Set([
507
609
  "include-payload",
508
610
  "no-payload",
509
611
  "include-logs",
612
+ "summary",
510
613
  "replace",
511
614
  "dry-run",
512
615
  "plan",
@@ -542,7 +645,9 @@ const VALUE_FLAGS = new Set([
542
645
  "agent",
543
646
  "api-key",
544
647
  "build-mode",
648
+ "backup-dir",
545
649
  "ca-cert",
650
+ "catalog",
546
651
  "cell-id",
547
652
  "allow-types",
548
653
  "color",
@@ -568,6 +673,7 @@ const VALUE_FLAGS = new Set([
568
673
  "local",
569
674
  "max-edges",
570
675
  "max-lines",
676
+ "max-log-lines",
571
677
  "listed",
572
678
  "max-nodes",
573
679
  "max-rows",
@@ -575,10 +681,12 @@ const VALUE_FLAGS = new Set([
575
681
  "only-monitored",
576
682
  "min-timestamp",
577
683
  "mode",
684
+ "log-filter",
578
685
  "model-evaluation-store",
579
686
  "name",
580
687
  "object",
581
688
  "output",
689
+ "output-file",
582
690
  "output-connection",
583
691
  "output-folder",
584
692
  "page",
@@ -592,15 +700,18 @@ const VALUE_FLAGS = new Set([
592
700
  "results-per-page",
593
701
  "record-cleanup",
594
702
  "rule-id",
703
+ "retries",
595
704
  "poll-interval",
596
705
  "python-interpreter",
597
706
  "retain",
598
707
  "saved-model",
599
708
  "sql",
709
+ "schema",
600
710
  "sql-file",
601
711
  "standard",
602
712
  "streaming-endpoint",
603
713
  "target",
714
+ "target-type",
604
715
  "timeout",
605
716
  "type",
606
717
  "url",
@@ -1532,6 +1643,41 @@ const commands = {
1532
1643
  description: "Show the column schema of a dataset.",
1533
1644
  examples: ["dss dataset schema orders",],
1534
1645
  },
1646
+ "refresh-schema": {
1647
+ handler: async (c, a, f) => {
1648
+ const usage = "dss dataset refresh-schema <name> [--data JSON | --data-file PATH | --stdin] [--dry-run] [--project-key KEY]";
1649
+ requireArgs(a, 1, usage);
1650
+ const columns = schemaColumnsInput(f, usage);
1651
+ const pk = f["project-key"];
1652
+ if (f["dry-run"] === true) {
1653
+ const current = await c.datasets.schema(a[0], pk);
1654
+ return {
1655
+ dryRun: true,
1656
+ action: "refresh-schema",
1657
+ resource: "dataset",
1658
+ name: a[0],
1659
+ current,
1660
+ next: { columns, },
1661
+ };
1662
+ }
1663
+ await c.datasets.updateSchema(a[0], columns, pk);
1664
+ return { updated: a[0], resource: "dataset", schema: { columns, }, };
1665
+ },
1666
+ usage: "dss dataset refresh-schema <name> [--data JSON | --data-file PATH | --stdin] [--dry-run] [--project-key KEY]",
1667
+ description: "Replace a dataset schema through the DSS schema endpoint.",
1668
+ examples: [
1669
+ `dss dataset refresh-schema orders --data '{"columns":[{"name":"id","type":"bigint"}]}' --dry-run`,
1670
+ ],
1671
+ },
1672
+ "validate-build": {
1673
+ handler: (c, a, f) => {
1674
+ requireArgs(a, 1, "dss dataset validate-build <name>");
1675
+ return c.datasets.validateBuildSettings(a[0], f["project-key"]);
1676
+ },
1677
+ usage: "dss dataset validate-build <name> [--project-key KEY]",
1678
+ description: "Check common dataset settings that can make file-backed builds fail.",
1679
+ examples: ["dss dataset validate-build orders",],
1680
+ },
1535
1681
  preview: {
1536
1682
  handler: (c, a, f) => {
1537
1683
  requireArgs(a, 1, "dss dataset preview <name>");
@@ -1674,6 +1820,60 @@ const commands = {
1674
1820
  "dss recipe get compute_orders --include-payload",
1675
1821
  ],
1676
1822
  },
1823
+ "validate-graph": {
1824
+ handler: (c, a, f) => {
1825
+ requireArgs(a, 1, "dss recipe validate-graph <name>");
1826
+ return c.recipes.validateGraph(a[0], {
1827
+ projectKey: f["project-key"],
1828
+ });
1829
+ },
1830
+ usage: "dss recipe validate-graph <name> [--project-key KEY]",
1831
+ description: "Validate declared recipe input/output graph references before building.",
1832
+ examples: ["dss recipe validate-graph compute_orders",],
1833
+ },
1834
+ run: {
1835
+ handler: async (c, a, f) => {
1836
+ requireArgs(a, 1, "dss recipe run <name>");
1837
+ const pk = f["project-key"];
1838
+ const wait = recipeRunShouldWait(f);
1839
+ const options = {
1840
+ buildMode: f["build-mode"],
1841
+ includeLogs: f["include-logs"] === true,
1842
+ logFilter: jobLogFilterFromFlag(f["log-filter"]),
1843
+ maxLogLines: maxLogLinesFromFlags(f),
1844
+ partition: f["partition"],
1845
+ pollIntervalMs: num(f["poll-interval"]),
1846
+ projectKey: pk,
1847
+ timeoutMs: num(f["timeout"]),
1848
+ summary: f["summary"] === true,
1849
+ wait,
1850
+ };
1851
+ if (f["dry-run"] === true) {
1852
+ const outputs = await c.recipes.resolveRunOutputs(a[0], {
1853
+ partition: options.partition,
1854
+ projectKey: pk,
1855
+ });
1856
+ return {
1857
+ dryRun: true,
1858
+ action: "run",
1859
+ resource: "recipe",
1860
+ recipe: a[0],
1861
+ outputs,
1862
+ ...options,
1863
+ endpoint: encodedProjectEndpoint(c, pk, "/jobs/"),
1864
+ method: "POST",
1865
+ };
1866
+ }
1867
+ return c.recipes.run(a[0], options);
1868
+ },
1869
+ usage: "dss recipe run <name> [--wait|--no-wait] [--build-mode MODE] [--include-logs] [--log-filter stdout|stderr|user|errors] [--summary] [--max-log-lines N] [--timeout MS] [--poll-interval MS] [--partition PARTITION] [--dry-run] [--project-key KEY]",
1870
+ description: "Run a recipe by resolving its outputs and submitting the correct dataset or managed-folder build job.",
1871
+ examples: [
1872
+ "dss recipe run compute_orders --wait",
1873
+ "dss recipe run compute_exports --include-logs --log-filter stdout --summary --timeout 600000",
1874
+ "dss recipe run compute_exports --dry-run",
1875
+ ],
1876
+ },
1677
1877
  delete: {
1678
1878
  handler: async (c, a, f) => {
1679
1879
  requireArgs(a, 1, "dss recipe delete <name>");
@@ -1860,6 +2060,8 @@ const commands = {
1860
2060
  if (!filePath)
1861
2061
  throw new UsageError("--file is required.");
1862
2062
  const content = readFileSync(filePath, "utf-8");
2063
+ const backupDir = f["backup-dir"];
2064
+ const backupPath = backupDir ? recipePayloadBackupPath(a[0], backupDir) : undefined;
1863
2065
  if (f["dry-run"] === true) {
1864
2066
  const current = await c.recipes.get(a[0], {
1865
2067
  projectKey: f["project-key"],
@@ -1873,16 +2075,33 @@ const commands = {
1873
2075
  file: filePath,
1874
2076
  current,
1875
2077
  next: { ...current, payload: content, },
2078
+ ...(backupPath ? { backupPath, } : {}),
1876
2079
  };
1877
2080
  }
2081
+ if (backupDir && backupPath) {
2082
+ const current = await c.recipes.get(a[0], {
2083
+ projectKey: f["project-key"],
2084
+ includePayload: true,
2085
+ });
2086
+ await mkdir(backupDir, { recursive: true, });
2087
+ await writeFile(backupPath, current.payload ?? "", "utf-8");
2088
+ }
1878
2089
  await c.recipes.setPayload(a[0], content, {
1879
2090
  projectKey: f["project-key"],
1880
2091
  });
1881
- return { updated: a[0], resource: "recipe", file: filePath, };
2092
+ return {
2093
+ updated: a[0],
2094
+ resource: "recipe",
2095
+ file: filePath,
2096
+ ...(backupPath ? { backupPath, } : {}),
2097
+ };
1882
2098
  },
1883
- usage: "dss recipe set-payload <name> --file PATH [--dry-run] [--project-key KEY]",
1884
- description: "Upload recipe code from a local file.",
1885
- examples: ["dss recipe set-payload compute_orders --file code.py --dry-run",],
2099
+ usage: "dss recipe set-payload <name> --file PATH [--backup-dir DIR] [--dry-run] [--project-key KEY]",
2100
+ description: "Upload recipe code from a local file, optionally backing up the remote payload first.",
2101
+ examples: [
2102
+ "dss recipe set-payload compute_orders --file code.py --dry-run",
2103
+ "dss recipe set-payload compute_orders --file code.py --backup-dir ./backups",
2104
+ ],
1886
2105
  },
1887
2106
  },
1888
2107
  job: {
@@ -1906,12 +2125,13 @@ const commands = {
1906
2125
  requireArgs(a, 1, "dss job log <id>");
1907
2126
  return c.jobs.log(a[0], {
1908
2127
  activity: f["activity"],
1909
- maxLogLines: num(f["max-lines"]),
2128
+ maxLogLines: maxLogLinesFromFlags(f),
2129
+ projectKey: f["project-key"],
1910
2130
  });
1911
2131
  },
1912
- usage: "dss job log <id> [--activity NAME] [--max-lines N]",
2132
+ usage: "dss job log <id> [--activity NAME] [--max-lines N|--max-log-lines N]",
1913
2133
  description: "Get log output for a job.",
1914
- examples: ["dss job log JOB_ID", "dss job log JOB_ID --activity main --max-lines 200",],
2134
+ examples: ["dss job log JOB_ID", "dss job log JOB_ID --activity main --max-log-lines 200",],
1915
2135
  },
1916
2136
  build: {
1917
2137
  handler: async (c, a, f) => {
@@ -1919,8 +2139,9 @@ const commands = {
1919
2139
  const pk = f["project-key"];
1920
2140
  const options = {
1921
2141
  buildMode: f["build-mode"],
2142
+ partition: f["partition"],
1922
2143
  pollIntervalMs: num(f["poll-interval"]),
1923
- targetType: jobBuildTargetType(f["type"]),
2144
+ targetType: jobBuildTargetTypeFromFlags(f),
1924
2145
  timeoutMs: num(f["timeout"]),
1925
2146
  };
1926
2147
  if (f["dry-run"] === true) {
@@ -1939,12 +2160,12 @@ const commands = {
1939
2160
  }
1940
2161
  return c.jobs.build(a[0], { ...options, projectKey: pk, });
1941
2162
  },
1942
- usage: "dss job build <target> [--type DATASET|MANAGED_FOLDER] [--build-mode MODE] [--wait] [--timeout MS] [--poll-interval MS] [--dry-run] [--project-key KEY]",
2163
+ usage: "dss job build <target> [--target-type dataset|managed-folder] [--type DATASET|MANAGED_FOLDER] [--build-mode MODE] [--wait] [--timeout MS] [--poll-interval MS] [--partition PARTITION] [--dry-run] [--project-key KEY]",
1943
2164
  description: "Start a dataset or managed-folder build, optionally waiting for completion.",
1944
2165
  examples: [
1945
2166
  "dss job build orders",
1946
2167
  "dss job build orders --build-mode RECURSIVE_BUILD --wait",
1947
- "dss job build LT7TUHJ8 --type MANAGED_FOLDER --dry-run",
2168
+ "dss job build LT7TUHJ8 --target-type managed-folder --dry-run",
1948
2169
  ],
1949
2170
  },
1950
2171
  "build-and-wait": {
@@ -1954,9 +2175,13 @@ const commands = {
1954
2175
  const options = {
1955
2176
  buildMode: f["build-mode"],
1956
2177
  includeLogs: f["include-logs"] === true,
2178
+ logFilter: jobLogFilterFromFlag(f["log-filter"]),
2179
+ maxLogLines: maxLogLinesFromFlags(f),
2180
+ partition: f["partition"],
1957
2181
  pollIntervalMs: num(f["poll-interval"]),
1958
2182
  timeoutMs: num(f["timeout"]),
1959
- targetType: jobBuildTargetType(f["type"]),
2183
+ summary: f["summary"] === true,
2184
+ targetType: jobBuildTargetTypeFromFlags(f),
1960
2185
  };
1961
2186
  if (f["dry-run"] === true) {
1962
2187
  return {
@@ -1971,13 +2196,13 @@ const commands = {
1971
2196
  }
1972
2197
  return c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, });
1973
2198
  },
1974
- usage: "dss job build-and-wait <target> [--type DATASET|MANAGED_FOLDER] [--build-mode MODE] [--include-logs] [--timeout MS] [--poll-interval MS] [--dry-run] [--project-key KEY]",
2199
+ usage: "dss job build-and-wait <target> [--target-type dataset|managed-folder] [--type DATASET|MANAGED_FOLDER] [--build-mode MODE] [--include-logs] [--log-filter stdout|stderr|user|errors] [--summary] [--max-log-lines N] [--timeout MS] [--poll-interval MS] [--partition PARTITION] [--dry-run] [--project-key KEY]",
1975
2200
  description: "Build a dataset or managed folder and wait for completion.",
1976
2201
  examples: [
1977
2202
  "dss job build-and-wait orders",
1978
- "dss job build-and-wait orders --include-logs",
2203
+ "dss job build-and-wait orders --include-logs --log-filter stdout --summary",
1979
2204
  "dss job build-and-wait orders --timeout 300000",
1980
- "dss job build-and-wait LT7TUHJ8 --type MANAGED_FOLDER --dry-run",
2205
+ "dss job build-and-wait LT7TUHJ8 --target-type managed-folder --dry-run",
1981
2206
  ],
1982
2207
  },
1983
2208
  wait: {
@@ -1985,13 +2210,20 @@ const commands = {
1985
2210
  requireArgs(a, 1, "dss job wait <id>");
1986
2211
  return c.jobs.wait(a[0], {
1987
2212
  includeLogs: f["include-logs"] === true,
2213
+ logFilter: jobLogFilterFromFlag(f["log-filter"]),
2214
+ maxLogLines: maxLogLinesFromFlags(f),
1988
2215
  pollIntervalMs: num(f["poll-interval"]),
1989
2216
  timeoutMs: num(f["timeout"]),
2217
+ summary: f["summary"] === true,
2218
+ projectKey: f["project-key"],
1990
2219
  });
1991
2220
  },
1992
- usage: "dss job wait <id> [--include-logs] [--timeout MS] [--poll-interval MS]",
2221
+ usage: "dss job wait <id> [--include-logs] [--log-filter stdout|stderr|user|errors] [--summary] [--max-log-lines N] [--timeout MS] [--poll-interval MS]",
1993
2222
  description: "Wait for an existing job to complete.",
1994
- examples: ["dss job wait JOB_ID", "dss job wait JOB_ID --include-logs --timeout 60000",],
2223
+ examples: [
2224
+ "dss job wait JOB_ID",
2225
+ "dss job wait JOB_ID --include-logs --log-filter stdout --summary --timeout 60000",
2226
+ ],
1995
2227
  },
1996
2228
  abort: {
1997
2229
  handler: async (c, a, f) => {
@@ -2297,13 +2529,24 @@ const commands = {
2297
2529
  contents: {
2298
2530
  handler: async (c, a, f) => {
2299
2531
  requireArgs(a, 1, "dss folder contents <name-or-id>");
2300
- return c.folders.contents(await resolveFolderId(c, a[0], f), {
2301
- projectKey: f["project-key"],
2302
- });
2532
+ const startedAt = Date.now();
2533
+ let folderId = a[0];
2534
+ try {
2535
+ folderId = await resolveFolderId(c, a[0], f);
2536
+ return await c.folders.contents(folderId, {
2537
+ projectKey: f["project-key"],
2538
+ });
2539
+ }
2540
+ catch (error) {
2541
+ addTransientTargetContext(error, `folder:${folderId}`, Date.now() - startedAt);
2542
+ }
2303
2543
  },
2304
- usage: "dss folder contents <name-or-id> [--project-key KEY]",
2544
+ usage: "dss folder contents <name-or-id> [--retries N] [--request-timeout MS] [--project-key KEY]",
2305
2545
  description: "List files in a managed folder.",
2306
- examples: ["dss folder contents my_folder",],
2546
+ examples: [
2547
+ "dss folder contents my_folder",
2548
+ "dss folder contents my_folder --retries 8 --request-timeout 60000",
2549
+ ],
2307
2550
  },
2308
2551
  download: {
2309
2552
  handler: async (c, a, f) => {
@@ -2425,6 +2668,40 @@ const commands = {
2425
2668
  description: "List connections with inferred types and metadata.",
2426
2669
  examples: ["dss connection infer", "dss connection infer --mode rich",],
2427
2670
  },
2671
+ schemas: {
2672
+ handler: (c, _a, f) => {
2673
+ const connection = f["connection"];
2674
+ if (!connection) {
2675
+ throw new UsageError("--connection is required. Usage: dss connection schemas --connection CONN");
2676
+ }
2677
+ return c.connections.schemas({
2678
+ connection,
2679
+ projectKey: f["project-key"],
2680
+ });
2681
+ },
2682
+ usage: "dss connection schemas --connection CONN [--project-key KEY]",
2683
+ description: "List schemas in a SQL connection.",
2684
+ examples: ["dss connection schemas --connection ATHENA_CONN --project-key MYPROJ",],
2685
+ },
2686
+ tables: {
2687
+ handler: (c, _a, f) => {
2688
+ const connection = f["connection"];
2689
+ if (!connection) {
2690
+ throw new UsageError("--connection is required. Usage: dss connection tables --connection CONN");
2691
+ }
2692
+ return c.connections.tables({
2693
+ connection,
2694
+ catalog: f["catalog"],
2695
+ schema: f["schema"],
2696
+ projectKey: f["project-key"],
2697
+ });
2698
+ },
2699
+ usage: "dss connection tables --connection CONN [--catalog CATALOG] [--schema SCHEMA] [--project-key KEY]",
2700
+ description: "List importable tables in a SQL connection, optionally scoped by catalog and schema.",
2701
+ examples: [
2702
+ "dss connection tables --connection ATHENA_CONN --schema analytics --project-key MYPROJ",
2703
+ ],
2704
+ },
2428
2705
  },
2429
2706
  "code-env": {
2430
2707
  list: {
@@ -2644,20 +2921,35 @@ const commands = {
2644
2921
  },
2645
2922
  sql: {
2646
2923
  query: {
2647
- handler: (c, a, f) => {
2924
+ handler: async (c, a, f) => {
2648
2925
  const query = resolveSqlInput(a, f);
2649
2926
  const connection = f["connection"];
2650
2927
  const datasetFullName = f["dataset"];
2651
2928
  if ((connection ? 1 : 0) + (datasetFullName ? 1 : 0) !== 1) {
2652
2929
  throw new UsageError(`Pass exactly one of --connection or --dataset. Usage: ${SQL_QUERY_USAGE}`);
2653
2930
  }
2654
- return c.sql.query({
2931
+ const result = await c.sql.query({
2655
2932
  query,
2656
2933
  connection,
2657
2934
  datasetFullName,
2658
2935
  database: f["database"],
2659
2936
  projectKey: f["project-key"],
2660
2937
  });
2938
+ const outputFile = f["output"]
2939
+ ?? f["output-file"];
2940
+ if (!outputFile)
2941
+ return result;
2942
+ const outputPath = resolve(outputFile);
2943
+ await mkdir(dirname(outputPath), { recursive: true, });
2944
+ await writeFile(outputPath, `${JSON.stringify(result, null, 2)}\n`, "utf-8");
2945
+ return {
2946
+ queryId: result.queryId,
2947
+ schema: result.schema,
2948
+ columns: result.columns ?? result.schema,
2949
+ rowCount: result.rows.length,
2950
+ outputPath,
2951
+ written: outputPath,
2952
+ };
2661
2953
  },
2662
2954
  usage: SQL_QUERY_USAGE,
2663
2955
  description: "Run a SQL query against a DSS connection or dataset.",
@@ -2665,6 +2957,7 @@ const commands = {
2665
2957
  "dss sql query 'SELECT * FROM orders LIMIT 10' --connection my_pg",
2666
2958
  "dss sql query --sql-file query.sql --connection my_pg",
2667
2959
  "echo 'SELECT 1' | dss sql query --stdin --dataset MYPROJ.orders",
2960
+ "dss sql query --sql-file query.sql --connection my_pg --output results.json --request-timeout 120000",
2668
2961
  ],
2669
2962
  },
2670
2963
  },
@@ -2920,7 +3213,7 @@ function printTopLevelHelp() {
2920
3213
  " --url URL Dataiku DSS base URL (env: DATAIKU_URL)",
2921
3214
  " --api-key KEY API key (env: DATAIKU_API_KEY)",
2922
3215
  " --project-key KEY Default project key (env: DATAIKU_PROJECT_KEY)",
2923
- " --timeout MS Operation timeout (build-and-wait, run-and-wait)",
3216
+ " --timeout MS Operation timeout (build-and-wait, run-and-wait, recipe run)",
2924
3217
  " --request-timeout MS HTTP request timeout in ms (default: 30000)",
2925
3218
  " --dry-run Preview destructive actions without executing",
2926
3219
  " --if-not-exists Skip create if resource already exists",
@@ -3139,7 +3432,9 @@ function doctorEnvironment(projectKey) {
3139
3432
  ...(projectKey ? { projectKey, } : {}),
3140
3433
  integrationFlags: {
3141
3434
  mutating: integrationFlag("RUN_DATAIKU_INTEGRATION_MUTATING"),
3435
+ adminMutating: integrationFlag("RUN_DATAIKU_ADMIN_MUTATING"),
3142
3436
  variables: integrationFlag("RUN_DATAIKU_INTEGRATION_VARIABLES"),
3437
+ sqlLive: integrationFlag("RUN_DATAIKU_SQL_LIVE"),
3143
3438
  bundles: integrationFlag("RUN_DATAIKU_INTEGRATION_BUNDLES"),
3144
3439
  apiServices: integrationFlag("RUN_DATAIKU_INTEGRATION_API_SERVICES"),
3145
3440
  },
@@ -3345,12 +3640,14 @@ async function runDoctor(flags) {
3345
3640
  let accessibleProjects;
3346
3641
  if (credentialsOk) {
3347
3642
  const requestTimeoutMs = num(flags["request-timeout"]);
3643
+ const retryMaxAttempts = num(flags["retries"]);
3348
3644
  const client = new DataikuClient({
3349
3645
  url,
3350
3646
  apiKey,
3351
3647
  projectKey,
3352
3648
  verbose: flags["verbose"] === true,
3353
3649
  requestTimeoutMs,
3650
+ retryMaxAttempts,
3354
3651
  tlsRejectUnauthorized,
3355
3652
  caCertPath,
3356
3653
  });
@@ -3398,13 +3695,14 @@ async function runDoctor(flags) {
3398
3695
  const result = { ok: checks.every((check) => check.ok), checks, context, };
3399
3696
  if (flags["capabilities"] === true && credentialsOk) {
3400
3697
  const requestTimeoutMs = num(flags["request-timeout"]);
3698
+ const retryMaxAttempts = num(flags["retries"]) ?? 1;
3401
3699
  const client = new DataikuClient({
3402
3700
  url,
3403
3701
  apiKey,
3404
3702
  projectKey,
3405
3703
  verbose: flags["verbose"] === true,
3406
3704
  requestTimeoutMs,
3407
- retryMaxAttempts: 1,
3705
+ retryMaxAttempts,
3408
3706
  tlsRejectUnauthorized,
3409
3707
  caCertPath,
3410
3708
  });
@@ -3426,13 +3724,14 @@ async function runFixtures(flags) {
3426
3724
  }
3427
3725
  currentCommandContext.projectKey = projectKey;
3428
3726
  const requestTimeoutMs = num(flags["request-timeout"]);
3727
+ const retryMaxAttempts = num(flags["retries"]) ?? 1;
3429
3728
  const client = new DataikuClient({
3430
3729
  url,
3431
3730
  apiKey,
3432
3731
  projectKey,
3433
3732
  verbose: flags["verbose"] === true,
3434
3733
  requestTimeoutMs,
3435
- retryMaxAttempts: 1,
3734
+ retryMaxAttempts,
3436
3735
  tlsRejectUnauthorized,
3437
3736
  caCertPath,
3438
3737
  });
@@ -3466,6 +3765,7 @@ const READ_ACTIONS = new Set([
3466
3765
  "preview",
3467
3766
  "query",
3468
3767
  "schema",
3768
+ "schemas",
3469
3769
  "sessions-jupyter",
3470
3770
  "status",
3471
3771
  "rules",
@@ -3490,7 +3790,14 @@ const PROJECT_SCOPED_RESOURCES = new Set([
3490
3790
  "wiki",
3491
3791
  ]);
3492
3792
  const GLOBAL_AGENT_FLAGS = ["help", "json", "report-json", "verbose",];
3493
- const AUTHENTICATED_AGENT_FLAGS = ["url", "api-key", "request-timeout", "insecure", "ca-cert",];
3793
+ const AUTHENTICATED_AGENT_FLAGS = [
3794
+ "url",
3795
+ "api-key",
3796
+ "request-timeout",
3797
+ "retries",
3798
+ "insecure",
3799
+ "ca-cert",
3800
+ ];
3494
3801
  const COMMANDS_USAGE = "dss commands [--json]";
3495
3802
  const COMMANDS_DESCRIPTION = "Print the machine-readable command registry for agent planning.";
3496
3803
  const COMMANDS_EXAMPLES = ["dss commands", "dss commands --json",];
@@ -3584,7 +3891,7 @@ function inferSideEffect(resource, action) {
3584
3891
  return "write";
3585
3892
  if (READ_ACTIONS.has(action))
3586
3893
  return "read";
3587
- if (/^(create|update|delete|set|save|upload|run|build|abort|move|clear|unload|install|login|logout)/
3894
+ if (/^(create|update|delete|set|save|upload|run|build|abort|move|refresh|clear|unload|install|login|logout)/
3588
3895
  .test(action)) {
3589
3896
  return "write";
3590
3897
  }
@@ -3610,6 +3917,7 @@ const ARRAY_OUTPUT_ACTIONS = new Set([
3610
3917
  "list-jupyter",
3611
3918
  "list-sql",
3612
3919
  "rules",
3920
+ "schemas",
3613
3921
  "sessions-jupyter",
3614
3922
  "usages",
3615
3923
  ]);
@@ -3684,6 +3992,8 @@ function inferDestructiveLevel(sideEffect, action) {
3684
3992
  function inferAsyncKind(resource, action) {
3685
3993
  if (resource === "job" && ["build", "build-and-wait", "wait",].includes(action))
3686
3994
  return "job";
3995
+ if (resource === "recipe" && action === "run")
3996
+ return "job";
3687
3997
  if (resource === "future" && ["get", "peek", "wait", "abort",].includes(action))
3688
3998
  return "future";
3689
3999
  if (resource === "scenario" && ["run", "run-and-wait", "status",].includes(action)) {
@@ -3760,7 +4070,8 @@ function buildRegistryEntry(resource, action, meta) {
3760
4070
  outputShape: inferOutputShape(resource, action),
3761
4071
  inputContract,
3762
4072
  destructive,
3763
- producesLocalFile: meta.usage.includes("--output PATH"),
4073
+ producesLocalFile: meta.usage.includes("--output PATH")
4074
+ || meta.usage.includes("--output-file PATH"),
3764
4075
  mutatesDss,
3765
4076
  async: asyncKind,
3766
4077
  idempotency: inferIdempotency(sideEffect, action, meta.usage),
@@ -3872,9 +4183,20 @@ function querySuffix(params) {
3872
4183
  return raw ? `?${raw}` : "";
3873
4184
  }
3874
4185
  function jobBuildPayload(target, projectKey, flags) {
3875
- const targetType = jobBuildTargetType(flags["type"]);
4186
+ const targetType = jobBuildTargetTypeFromFlags(flags);
4187
+ const partition = flags["partition"];
4188
+ const output = { projectKey, id: target, type: targetType, };
4189
+ if (targetType === "DATASET") {
4190
+ if (partition !== undefined)
4191
+ output.partition = partition;
4192
+ }
4193
+ else {
4194
+ output.targetManagedFolderProjectKey = projectKey;
4195
+ output.targetManagedFolder = target;
4196
+ output.targetPartition = partition ?? "NP";
4197
+ }
3876
4198
  const payload = {
3877
- outputs: [{ projectKey, id: target, type: targetType, },],
4199
+ outputs: [output,],
3878
4200
  type: flags["build-mode"] ?? "NON_RECURSIVE_FORCED_BUILD",
3879
4201
  };
3880
4202
  if (flags["force-rebuild"] === true && targetType === "DATASET") {
@@ -4060,9 +4382,9 @@ function commandPlanShape(resource, action, args, flags, entry, projectKey) {
4060
4382
  case "flow-zone.move":
4061
4383
  return {
4062
4384
  method: "POST",
4063
- endpoint: projectEndpoint(`/flow/zones/${encodeURIComponent(id)}/move-to`),
4385
+ endpoint: projectEndpoint(`/flow/zones/${encodeURIComponent(id)}/add-items`),
4064
4386
  identifiers: { id, },
4065
- payload: { items: flowZoneMoveItems(flags), },
4387
+ payload: flowZoneMoveItems(flags),
4066
4388
  };
4067
4389
  case "dataset.create": {
4068
4390
  const name = requiredPlanFlag(flags, "name", entry.usage);
@@ -4081,6 +4403,15 @@ function commandPlanShape(resource, action, args, flags, entry, projectKey) {
4081
4403
  endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}`),
4082
4404
  identifiers: { name: id, },
4083
4405
  };
4406
+ case "dataset.refresh-schema": {
4407
+ const columns = schemaColumnsInput(flags, entry.usage);
4408
+ return {
4409
+ method: "PUT",
4410
+ endpoint: projectEndpoint(`/datasets/${encodeURIComponent(id)}/schema`),
4411
+ identifiers: { name: id, },
4412
+ payload: { columns, },
4413
+ };
4414
+ }
4084
4415
  case "dataset.update":
4085
4416
  return {
4086
4417
  method: "PUT",
@@ -4122,6 +4453,19 @@ function commandPlanShape(resource, action, args, flags, entry, projectKey) {
4122
4453
  },
4123
4454
  };
4124
4455
  }
4456
+ case "recipe.run":
4457
+ return {
4458
+ method: "POST",
4459
+ endpoint: projectEndpoint("/jobs/"),
4460
+ identifiers: { recipe: id, },
4461
+ payload: {
4462
+ recipe: id,
4463
+ outputResolution: "dynamic",
4464
+ projectKey,
4465
+ partition: flags["partition"],
4466
+ },
4467
+ wait: recipeRunShouldWait(flags),
4468
+ };
4125
4469
  case "recipe.update":
4126
4470
  return {
4127
4471
  method: "PUT",
@@ -4129,13 +4473,24 @@ function commandPlanShape(resource, action, args, flags, entry, projectKey) {
4129
4473
  identifiers: { name: id, },
4130
4474
  payload: requiredPlanJsonInput(flags, entry.usage),
4131
4475
  };
4132
- case "recipe.set-payload":
4476
+ case "recipe.set-payload": {
4477
+ const file = requiredPlanFlag(flags, "file", entry.usage);
4478
+ const backupDir = flags["backup-dir"];
4479
+ const backupPath = backupDir ? recipePayloadBackupPath(id, backupDir) : undefined;
4133
4480
  return {
4134
4481
  method: "PUT",
4135
- endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}/payload`),
4482
+ endpoint: projectEndpoint(`/recipes/${encodeURIComponent(id)}`),
4136
4483
  identifiers: { name: id, },
4137
- payload: { file: flags["file"], content: textInput(flags), },
4484
+ payload: {
4485
+ file,
4486
+ content: textInput(flags),
4487
+ ...(backupPath ? { backupPath, } : {}),
4488
+ },
4489
+ ...(backupPath
4490
+ ? { localWrites: [{ path: backupPath, source: "remote recipe payload", before: "PUT", },], }
4491
+ : {}),
4138
4492
  };
4493
+ }
4139
4494
  case "job.build":
4140
4495
  case "job.build-and-wait":
4141
4496
  return {
@@ -4402,12 +4757,14 @@ async function runCleanup(flags) {
4402
4757
  throw new UsageError("Missing API key. Set DATAIKU_API_KEY, pass --api-key, or run: dss auth login");
4403
4758
  }
4404
4759
  const requestTimeoutMs = num(flags["request-timeout"]);
4760
+ const retryMaxAttempts = num(flags["retries"]);
4405
4761
  const client = new DataikuClient({
4406
4762
  url,
4407
4763
  apiKey,
4408
4764
  projectKey,
4409
4765
  verbose: flags["verbose"] === true,
4410
4766
  requestTimeoutMs,
4767
+ retryMaxAttempts,
4411
4768
  tlsRejectUnauthorized,
4412
4769
  caCertPath,
4413
4770
  });
@@ -4938,12 +5295,14 @@ async function main() {
4938
5295
  throw new UsageError("Missing API key. Set DATAIKU_API_KEY, pass --api-key, or run: dss auth login");
4939
5296
  }
4940
5297
  const requestTimeoutMs = num(flags["request-timeout"]);
5298
+ const retryMaxAttempts = num(flags["retries"]);
4941
5299
  const client = new DataikuClient({
4942
5300
  url,
4943
5301
  apiKey,
4944
5302
  projectKey,
4945
5303
  verbose: flags["verbose"] === true,
4946
5304
  requestTimeoutMs,
5305
+ retryMaxAttempts,
4947
5306
  tlsRejectUnauthorized,
4948
5307
  caCertPath,
4949
5308
  });