dataiku-sdk 0.12.5 → 0.12.7
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/README.md +1 -0
- package/dist/src/cli/commands/dashboard.js +102 -13
- package/dist/src/cli/commands/sql.js +17 -2
- package/dist/src/cli/contract.js +19 -7
- package/dist/src/cli/flags.js +5 -0
- package/dist/src/client.d.ts +8 -1
- package/dist/src/client.js +25 -5
- package/dist/src/resources/dashboards.d.ts +14 -0
- package/dist/src/resources/dashboards.js +156 -4
- package/dist/src/resources/sql.d.ts +5 -0
- package/dist/src/resources/sql.js +5 -4
- package/dist/src/skill.js +9 -210
- package/package.json +10 -5
- package/plugin.json +15 -0
- package/skills/dataiku-dss/SKILL.md +213 -0
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ Bun is the primary development runtime and package manager. Examples below assum
|
|
|
29
29
|
- `--fields a,b,c` projects those fields from an object or array-of-objects result; dotted paths (`a.b.c`) drill into nested objects, and missing fields become `null`; string and scalar results pass through unchanged.
|
|
30
30
|
- No prompts, help screens, tables, banners, or prose output are part of the contract.
|
|
31
31
|
- Exit codes: `0` success, `1` usage/configuration error, `2` DSS/internal error, `3` transient DSS error, `4` a failed long-running result or synchronous assertion.
|
|
32
|
+
- `--retries N` controls idempotent GET retries only. `dss sql query --start-retries N` explicitly retries transient failures while starting a query; use it only when executing the SQL more than once is safe.
|
|
32
33
|
- The exit code is the success signal. For portable multi-step mutations, prefer `dss batch`; shell chaining and pipeline exit semantics differ across POSIX shells, Windows PowerShell, PowerShell 7, and Command Prompt.
|
|
33
34
|
- `--raw` is only for recipe payload commands. Without `--output`, stdout is raw bytes; with `--output PATH`, stdout is the JSON string equal to `PATH` and the file contains the exact raw bytes.
|
|
34
35
|
|
|
@@ -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
|
|
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
|
|
52
|
-
description: "Create a dashboard. Defaults to an empty pages array.",
|
|
53
|
-
examples: [
|
|
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 (
|
|
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 --
|
|
167
|
+
examples: ["dss dashboard update DASHBOARD_ID --listed true --dry-run",],
|
|
79
168
|
},
|
|
80
169
|
delete: {
|
|
81
170
|
handler: async (c, a, f) => {
|
|
@@ -3,7 +3,7 @@ import { mkdir, writeFile, } from "node:fs/promises";
|
|
|
3
3
|
import { dirname, resolve, } from "node:path";
|
|
4
4
|
import { readStdinText, stripUtf8Bom, } from "../coerce.js";
|
|
5
5
|
import { UsageError, } from "../usage.js";
|
|
6
|
-
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] [--preview N] [--request-timeout MS] [--project-key KEY]";
|
|
6
|
+
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] [--preview N] [--start-retries N] [--request-timeout MS] [--project-key KEY]";
|
|
7
7
|
const DEFAULT_SQL_PREVIEW_ROWS = 5;
|
|
8
8
|
/**
|
|
9
9
|
* Parse `--preview N` into a non-negative row count. Rejects non-integers,
|
|
@@ -21,6 +21,19 @@ function parseSqlPreviewCount(value) {
|
|
|
21
21
|
}
|
|
22
22
|
return parsed;
|
|
23
23
|
}
|
|
24
|
+
function parseSqlStartRetries(value) {
|
|
25
|
+
if (value === undefined)
|
|
26
|
+
return undefined;
|
|
27
|
+
if (typeof value !== "string") {
|
|
28
|
+
throw new UsageError(`--start-retries requires a positive integer. Usage: ${SQL_QUERY_USAGE}`, "validation_failed");
|
|
29
|
+
}
|
|
30
|
+
const trimmed = value.trim();
|
|
31
|
+
const parsed = Number(trimmed);
|
|
32
|
+
if (trimmed.length === 0 || !Number.isInteger(parsed) || parsed < 1) {
|
|
33
|
+
throw new UsageError(`--start-retries must be a positive integer (got "${value}"). Usage: ${SQL_QUERY_USAGE}`, "validation_failed");
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
24
37
|
function resolveSqlInput(args, flags) {
|
|
25
38
|
const sources = [];
|
|
26
39
|
if (typeof flags["sql"] === "string") {
|
|
@@ -80,6 +93,7 @@ export const sqlCommands = {
|
|
|
80
93
|
datasetFullName,
|
|
81
94
|
database: f["database"],
|
|
82
95
|
projectKey: f["project-key"],
|
|
96
|
+
retryMaxAttempts: parseSqlStartRetries(f["start-retries"]),
|
|
83
97
|
});
|
|
84
98
|
if (!outputFile)
|
|
85
99
|
return result;
|
|
@@ -97,13 +111,14 @@ export const sqlCommands = {
|
|
|
97
111
|
};
|
|
98
112
|
},
|
|
99
113
|
usage: SQL_QUERY_USAGE,
|
|
100
|
-
description: "Run a SQL query against a DSS connection or dataset.",
|
|
114
|
+
description: "Run a SQL query against a DSS connection or dataset. --start-retries may execute the SQL more than once; use it only when repetition is safe.",
|
|
101
115
|
examples: [
|
|
102
116
|
"dss sql query 'SELECT * FROM orders LIMIT 10' --connection my_pg",
|
|
103
117
|
"dss sql query --sql-file query.sql --connection my_pg",
|
|
104
118
|
"echo 'SELECT 1' | dss sql query --stdin --dataset MYPROJ.orders",
|
|
105
119
|
"dss sql query --sql-file query.sql --connection my_pg --output results.json --request-timeout 120000",
|
|
106
120
|
"dss sql query --sql-file query.sql --connection my_pg --output results.json --preview 10",
|
|
121
|
+
"dss sql query 'SELECT 1' --connection my_pg --start-retries 4",
|
|
107
122
|
],
|
|
108
123
|
},
|
|
109
124
|
};
|
package/dist/src/cli/contract.js
CHANGED
|
@@ -519,7 +519,7 @@ export function buildCommandSchemas(resource, action, flags, requiredFlags, requ
|
|
|
519
519
|
}
|
|
520
520
|
const EXPLICIT_REGISTRY_OVERRIDES = {
|
|
521
521
|
"dashboard.create": {
|
|
522
|
-
examplePayload: { pages: [], },
|
|
522
|
+
examplePayload: { name: "Agent dashboard", pages: [], },
|
|
523
523
|
},
|
|
524
524
|
"dashboard.update": {
|
|
525
525
|
examplePayload: { name: "Updated dashboard", },
|
|
@@ -583,10 +583,9 @@ function inferSideEffect(resource, action) {
|
|
|
583
583
|
}
|
|
584
584
|
if (resource === "install-skill")
|
|
585
585
|
return "write";
|
|
586
|
-
//
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
if (resource === "project" && action === "export")
|
|
586
|
+
// Project and dashboard exports stream a rendered/archive artifact and write it
|
|
587
|
+
// locally; the DSS-side resource is untouched.
|
|
588
|
+
if ((resource === "project" || resource === "dashboard") && action === "export")
|
|
590
589
|
return "read";
|
|
591
590
|
if (resource === "data-quality" && action === "compute")
|
|
592
591
|
return "write";
|
|
@@ -1449,18 +1448,31 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
|
|
|
1449
1448
|
identifiers: { article: id, },
|
|
1450
1449
|
};
|
|
1451
1450
|
case "dashboard.create": {
|
|
1452
|
-
const
|
|
1451
|
+
const data = jsonInput(flags);
|
|
1452
|
+
const flagName = flags["name"];
|
|
1453
|
+
const dataName = data?.["name"];
|
|
1454
|
+
const name = flagName ?? (typeof dataName === "string" ? dataName : undefined);
|
|
1455
|
+
if (!name) {
|
|
1456
|
+
throw new UsageError("--name or dashboard settings containing a string name are required. Usage: dss dashboard create --name NAME");
|
|
1457
|
+
}
|
|
1458
|
+
const listed = parseBooleanOption(flags["listed"], "--listed");
|
|
1459
|
+
const payload = { ...(data ?? { pages: [], }), name, };
|
|
1460
|
+
if (listed !== undefined)
|
|
1461
|
+
payload.listed = listed;
|
|
1453
1462
|
return {
|
|
1454
1463
|
method: "POST",
|
|
1455
1464
|
endpoint: projectEndpoint("/dashboards/"),
|
|
1456
1465
|
identifiers: { name, },
|
|
1457
|
-
payload
|
|
1466
|
+
payload,
|
|
1458
1467
|
};
|
|
1459
1468
|
}
|
|
1460
1469
|
case "dashboard.update": {
|
|
1461
1470
|
const payload = { ...jsonInput(flags), };
|
|
1462
1471
|
if (typeof flags["name"] === "string")
|
|
1463
1472
|
payload.name = flags["name"];
|
|
1473
|
+
const listed = parseBooleanOption(flags["listed"], "--listed");
|
|
1474
|
+
if (listed !== undefined)
|
|
1475
|
+
payload.listed = listed;
|
|
1464
1476
|
return {
|
|
1465
1477
|
method: "PUT",
|
|
1466
1478
|
endpoint: projectEndpoint(`/dashboards/${encodeURIComponent(id)}/`),
|
package/dist/src/cli/flags.js
CHANGED
|
@@ -91,6 +91,7 @@ export const VALUE_FLAGS = new Set([
|
|
|
91
91
|
"database",
|
|
92
92
|
"dataset",
|
|
93
93
|
"file",
|
|
94
|
+
"file-type",
|
|
94
95
|
"env",
|
|
95
96
|
"install-core-packages",
|
|
96
97
|
"fuzzy-on",
|
|
@@ -133,7 +134,9 @@ export const VALUE_FLAGS = new Set([
|
|
|
133
134
|
"output-file",
|
|
134
135
|
"output-connection",
|
|
135
136
|
"output-folder",
|
|
137
|
+
"orientation",
|
|
136
138
|
"page",
|
|
139
|
+
"paper-size",
|
|
137
140
|
"partition",
|
|
138
141
|
"parent",
|
|
139
142
|
"path",
|
|
@@ -157,9 +160,11 @@ export const VALUE_FLAGS = new Set([
|
|
|
157
160
|
"retain",
|
|
158
161
|
"saved-model",
|
|
159
162
|
"session-name",
|
|
163
|
+
"slide-index",
|
|
160
164
|
"sql",
|
|
161
165
|
"schema",
|
|
162
166
|
"sql-file",
|
|
167
|
+
"start-retries",
|
|
163
168
|
"standard",
|
|
164
169
|
"state",
|
|
165
170
|
"streaming-endpoint",
|
package/dist/src/client.d.ts
CHANGED
|
@@ -165,8 +165,15 @@ export declare class DataikuClient {
|
|
|
165
165
|
text: string;
|
|
166
166
|
truncated: boolean;
|
|
167
167
|
}>;
|
|
168
|
-
|
|
168
|
+
/**
|
|
169
|
+
* POST with optional transient retry. Only opt in when repeating the request
|
|
170
|
+
* is safe: the server may have accepted an attempt whose response was lost.
|
|
171
|
+
*/
|
|
172
|
+
post<T = unknown>(path: string, body?: unknown, options?: {
|
|
173
|
+
retryMaxAttempts?: number;
|
|
174
|
+
}): Promise<T>;
|
|
169
175
|
postText(path: string, body?: unknown): Promise<string>;
|
|
176
|
+
postStream(path: string, body?: unknown): Promise<Response>;
|
|
170
177
|
put<T = unknown>(path: string, body: unknown): Promise<T>;
|
|
171
178
|
del(path: string): Promise<void>;
|
|
172
179
|
putVoid(path: string, body: unknown): Promise<void>;
|
package/dist/src/client.js
CHANGED
|
@@ -406,12 +406,21 @@ export class DataikuClient {
|
|
|
406
406
|
const usable = truncated ? trimToUtf8Boundary(collected) : collected;
|
|
407
407
|
return { text: new TextDecoder().decode(usable), truncated, };
|
|
408
408
|
}
|
|
409
|
-
|
|
409
|
+
/**
|
|
410
|
+
* POST with optional transient retry. Only opt in when repeating the request
|
|
411
|
+
* is safe: the server may have accepted an attempt whose response was lost.
|
|
412
|
+
*/
|
|
413
|
+
async post(path, body, options) {
|
|
414
|
+
const retryMaxAttempts = options?.retryMaxAttempts;
|
|
415
|
+
if (retryMaxAttempts !== undefined
|
|
416
|
+
&& (!Number.isInteger(retryMaxAttempts) || retryMaxAttempts < 1)) {
|
|
417
|
+
throw new ClientValidationError("retryMaxAttempts must be a positive integer.");
|
|
418
|
+
}
|
|
410
419
|
const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
411
420
|
method: "POST",
|
|
412
421
|
headers: this.getHeaders(),
|
|
413
422
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
414
|
-
});
|
|
423
|
+
}, retryMaxAttempts);
|
|
415
424
|
return this.parseJsonResponse(res);
|
|
416
425
|
}
|
|
417
426
|
async postText(path, body) {
|
|
@@ -422,6 +431,13 @@ export class DataikuClient {
|
|
|
422
431
|
});
|
|
423
432
|
return res.text();
|
|
424
433
|
}
|
|
434
|
+
async postStream(path, body) {
|
|
435
|
+
return this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
436
|
+
method: "POST",
|
|
437
|
+
headers: { ...this.getAnyHeaders(), "Content-Type": "application/json", },
|
|
438
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
425
441
|
async put(path, body) {
|
|
426
442
|
const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
427
443
|
method: "PUT",
|
|
@@ -538,10 +554,14 @@ export class DataikuClient {
|
|
|
538
554
|
}
|
|
539
555
|
}
|
|
540
556
|
/* ---- private: retry loop ---- */
|
|
541
|
-
async fetchWithRetry(url, init) {
|
|
557
|
+
async fetchWithRetry(url, init, retryMaxAttempts) {
|
|
542
558
|
const method = (init.method ?? "GET").toUpperCase();
|
|
543
|
-
const retryEnabled = shouldRetryMethod(method);
|
|
544
|
-
const maxAttempts =
|
|
559
|
+
const retryEnabled = shouldRetryMethod(method) || retryMaxAttempts !== undefined;
|
|
560
|
+
const maxAttempts = retryMaxAttempts === undefined
|
|
561
|
+
? retryEnabled
|
|
562
|
+
? this.retryMaxAttempts
|
|
563
|
+
: 1
|
|
564
|
+
: Math.min(retryMaxAttempts, MAX_RETRY_ATTEMPTS_CAP);
|
|
545
565
|
const delaysMs = [];
|
|
546
566
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
547
567
|
let timedOut = false;
|
|
@@ -2,18 +2,32 @@ import type { DashboardDetails, DashboardSummary } from "../schemas.js";
|
|
|
2
2
|
import { BaseResource } from "./base.js";
|
|
3
3
|
export interface DashboardCreateOptions {
|
|
4
4
|
name: string;
|
|
5
|
+
listed?: boolean;
|
|
5
6
|
settings?: Record<string, unknown>;
|
|
6
7
|
projectKey?: string;
|
|
7
8
|
}
|
|
8
9
|
export interface DashboardUpdateOptions {
|
|
9
10
|
name?: string;
|
|
11
|
+
listed?: boolean;
|
|
10
12
|
data?: Record<string, unknown>;
|
|
11
13
|
projectKey?: string;
|
|
12
14
|
}
|
|
15
|
+
export type DashboardExportOrientation = "PORTRAIT" | "LANDSCAPE";
|
|
16
|
+
export type DashboardExportFileType = "PDF";
|
|
17
|
+
export interface DashboardExportOptions {
|
|
18
|
+
paperSize?: string;
|
|
19
|
+
orientation?: DashboardExportOrientation;
|
|
20
|
+
fileType?: DashboardExportFileType;
|
|
21
|
+
slideIndex?: number;
|
|
22
|
+
projectKey?: string;
|
|
23
|
+
}
|
|
13
24
|
export declare class DashboardsResource extends BaseResource {
|
|
14
25
|
list(projectKey?: string): Promise<DashboardSummary[]>;
|
|
15
26
|
get(dashboardId: string, projectKey?: string): Promise<DashboardDetails>;
|
|
27
|
+
private referencedInsightIds;
|
|
28
|
+
private validateReferences;
|
|
16
29
|
create(opts: DashboardCreateOptions): Promise<DashboardDetails>;
|
|
17
30
|
update(dashboardId: string, opts: DashboardUpdateOptions): Promise<DashboardDetails>;
|
|
31
|
+
export(dashboardId: string, opts?: DashboardExportOptions): Promise<Response>;
|
|
18
32
|
delete(dashboardId: string, projectKey?: string): Promise<void>;
|
|
19
33
|
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
2
|
+
import { DashboardDetailsSchema, DashboardSummaryArraySchema, InsightDetailsSchema, } from "../schemas.js";
|
|
2
3
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
3
4
|
import { BaseResource, } from "./base.js";
|
|
5
|
+
const REFERENCE_VALIDATION_CONCURRENCY = 8;
|
|
6
|
+
async function validateInBatches(items, validate) {
|
|
7
|
+
for (let offset = 0; offset < items.length; offset += REFERENCE_VALIDATION_CONCURRENCY) {
|
|
8
|
+
const batch = items.slice(offset, offset + REFERENCE_VALIDATION_CONCURRENCY);
|
|
9
|
+
const results = await Promise.allSettled(batch.map(validate));
|
|
10
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
11
|
+
if (failure)
|
|
12
|
+
throw failure.reason;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
4
15
|
export class DashboardsResource extends BaseResource {
|
|
5
16
|
async list(projectKey) {
|
|
6
17
|
const raw = await this.client.get(`/public/api/projects/${this.enc(projectKey)}/dashboards/`);
|
|
@@ -10,19 +21,160 @@ export class DashboardsResource extends BaseResource {
|
|
|
10
21
|
const raw = await this.client.get(`/public/api/projects/${this.enc(projectKey)}/dashboards/${encodeURIComponent(dashboardId)}/`);
|
|
11
22
|
return this.client.safeParse(DashboardDetailsSchema, raw, "dashboards.get");
|
|
12
23
|
}
|
|
24
|
+
referencedInsightIds(settings) {
|
|
25
|
+
const pages = settings.pages;
|
|
26
|
+
if (pages === undefined)
|
|
27
|
+
return [];
|
|
28
|
+
if (!Array.isArray(pages)) {
|
|
29
|
+
throw new ClientValidationError("Dashboard pages must be an array.");
|
|
30
|
+
}
|
|
31
|
+
const insightIds = new Set();
|
|
32
|
+
for (const [pageIndex, page,] of pages.entries()) {
|
|
33
|
+
if (!page || typeof page !== "object" || Array.isArray(page)) {
|
|
34
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} must be an object.`);
|
|
35
|
+
}
|
|
36
|
+
const grid = page.grid;
|
|
37
|
+
if (grid === undefined)
|
|
38
|
+
continue;
|
|
39
|
+
if (!grid || typeof grid !== "object" || Array.isArray(grid)) {
|
|
40
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} grid must be an object.`);
|
|
41
|
+
}
|
|
42
|
+
const tiles = grid.tiles;
|
|
43
|
+
if (tiles === undefined)
|
|
44
|
+
continue;
|
|
45
|
+
if (!Array.isArray(tiles)) {
|
|
46
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} grid tiles must be an array.`);
|
|
47
|
+
}
|
|
48
|
+
for (const [tileIndex, tile,] of tiles.entries()) {
|
|
49
|
+
if (!tile || typeof tile !== "object" || Array.isArray(tile)) {
|
|
50
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} must be an object.`);
|
|
51
|
+
}
|
|
52
|
+
const record = tile;
|
|
53
|
+
const insightId = record.insightId ?? undefined;
|
|
54
|
+
const targetInsightId = record.targetInsightId ?? undefined;
|
|
55
|
+
const isInsight = record.tileType === "INSIGHT"
|
|
56
|
+
|| insightId !== undefined
|
|
57
|
+
|| targetInsightId !== undefined;
|
|
58
|
+
if (!isInsight)
|
|
59
|
+
continue;
|
|
60
|
+
if (insightId !== undefined
|
|
61
|
+
&& (typeof insightId !== "string" || insightId.trim().length === 0)) {
|
|
62
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} insightId must be a non-empty string.`);
|
|
63
|
+
}
|
|
64
|
+
if (targetInsightId !== undefined
|
|
65
|
+
&& (typeof targetInsightId !== "string" || targetInsightId.trim().length === 0)) {
|
|
66
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} targetInsightId must be a non-empty string.`);
|
|
67
|
+
}
|
|
68
|
+
const normalizedInsightId = typeof insightId === "string" ? insightId.trim() : undefined;
|
|
69
|
+
const normalizedTargetId = typeof targetInsightId === "string"
|
|
70
|
+
? targetInsightId.trim()
|
|
71
|
+
: undefined;
|
|
72
|
+
if (normalizedInsightId !== undefined
|
|
73
|
+
&& normalizedTargetId !== undefined
|
|
74
|
+
&& normalizedInsightId !== normalizedTargetId) {
|
|
75
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} has mismatched insightId and targetInsightId.`);
|
|
76
|
+
}
|
|
77
|
+
const reference = normalizedInsightId ?? normalizedTargetId;
|
|
78
|
+
if (!reference) {
|
|
79
|
+
throw new ClientValidationError(`Dashboard page ${pageIndex} tile ${tileIndex} requires insightId or targetInsightId.`);
|
|
80
|
+
}
|
|
81
|
+
insightIds.add(reference);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return [...insightIds,];
|
|
85
|
+
}
|
|
86
|
+
async validateReferences(settings, projectKey) {
|
|
87
|
+
const insightIds = this.referencedInsightIds(settings);
|
|
88
|
+
const datasetNames = new Set();
|
|
89
|
+
await validateInBatches(insightIds, async (insightId) => {
|
|
90
|
+
let raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = await this.client.get(`/public/api/projects/${encodeURIComponent(projectKey)}/insights/${encodeURIComponent(insightId)}/`);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error instanceof DataikuError && error.status === 404) {
|
|
96
|
+
throw new ClientValidationError(`Dashboard references missing insight ${insightId}.`, "validation_failed", "Create or restore the insight, then update the tile reference before saving the dashboard.", { insightId, });
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
const insight = this.client.safeParse(InsightDetailsSchema, raw, `dashboards.validateReferences.insight.${insightId}`);
|
|
101
|
+
const params = raw && typeof raw === "object" && !Array.isArray(raw)
|
|
102
|
+
? raw.params
|
|
103
|
+
: undefined;
|
|
104
|
+
const datasetSmartName = params && typeof params === "object" && !Array.isArray(params)
|
|
105
|
+
? params.datasetSmartName
|
|
106
|
+
: undefined;
|
|
107
|
+
if (insight.type === "dataset_table" && (typeof datasetSmartName !== "string" || datasetSmartName.trim().length === 0)) {
|
|
108
|
+
throw new ClientValidationError(`Dashboard insight ${insightId} does not name a backing dataset.`, "validation_failed", "Retarget the dataset-table insight before saving the dashboard.", { insightId, });
|
|
109
|
+
}
|
|
110
|
+
if (typeof datasetSmartName === "string" && datasetSmartName.trim().length > 0) {
|
|
111
|
+
datasetNames.add(datasetSmartName.trim());
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
await validateInBatches([...datasetNames,], async (datasetSmartName) => {
|
|
115
|
+
const separatorIndex = datasetSmartName.indexOf(".");
|
|
116
|
+
if (separatorIndex === 0 || separatorIndex === datasetSmartName.length - 1) {
|
|
117
|
+
throw new ClientValidationError(`Dashboard insight has invalid dataset smart name ${datasetSmartName}.`, "validation_failed", "Use DATASET for the current project or PROJECT.DATASET for a cross-project dataset.", { datasetSmartName, });
|
|
118
|
+
}
|
|
119
|
+
const datasetProjectKey = separatorIndex > 0
|
|
120
|
+
? datasetSmartName.slice(0, separatorIndex)
|
|
121
|
+
: projectKey;
|
|
122
|
+
const datasetName = separatorIndex > 0
|
|
123
|
+
? datasetSmartName.slice(separatorIndex + 1)
|
|
124
|
+
: datasetSmartName;
|
|
125
|
+
try {
|
|
126
|
+
await this.client.get(`/public/api/projects/${encodeURIComponent(datasetProjectKey)}/datasets/${encodeURIComponent(datasetName)}/`);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (error instanceof DataikuError && error.status === 403) {
|
|
130
|
+
throw new ClientValidationError(`Dashboard cannot validate inaccessible dataset ${datasetSmartName}.`, "validation_failed", "Grant the API key read access to the dataset project before saving the dashboard.", { datasetSmartName, datasetProjectKey, datasetName, });
|
|
131
|
+
}
|
|
132
|
+
if (error instanceof DataikuError && error.status === 404) {
|
|
133
|
+
throw new ClientValidationError(`Dashboard insight references missing dataset ${datasetSmartName}.`, "validation_failed", "Create or restore the dataset, or retarget the insight before saving the dashboard.", { datasetSmartName, datasetProjectKey, datasetName, });
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
13
139
|
async create(opts) {
|
|
14
140
|
const pk = this.resolveProjectKey(opts.projectKey);
|
|
15
141
|
const body = { ...(opts.settings ?? { pages: [], }), name: opts.name, };
|
|
142
|
+
if (opts.listed !== undefined)
|
|
143
|
+
body.listed = opts.listed;
|
|
144
|
+
await this.validateReferences(body, pk);
|
|
16
145
|
const created = await this.client.post(`/public/api/projects/${encodeURIComponent(pk)}/dashboards/`, body);
|
|
17
146
|
return this.get(created.id, pk);
|
|
18
147
|
}
|
|
19
148
|
async update(dashboardId, opts) {
|
|
20
|
-
const
|
|
149
|
+
const pk = this.resolveProjectKey(opts.projectKey);
|
|
150
|
+
const current = await this.get(dashboardId, pk);
|
|
21
151
|
const next = deepMerge(current, opts.data ?? {});
|
|
22
152
|
if (opts.name !== undefined)
|
|
23
153
|
next.name = opts.name;
|
|
24
|
-
|
|
25
|
-
|
|
154
|
+
if (opts.listed !== undefined)
|
|
155
|
+
next.listed = opts.listed;
|
|
156
|
+
await this.validateReferences(next, pk);
|
|
157
|
+
await this.client.put(`/public/api/projects/${encodeURIComponent(pk)}/dashboards/${encodeURIComponent(dashboardId)}/`, next);
|
|
158
|
+
return this.get(dashboardId, pk);
|
|
159
|
+
}
|
|
160
|
+
async export(dashboardId, opts = {}) {
|
|
161
|
+
const pk = this.resolveProjectKey(opts.projectKey);
|
|
162
|
+
const paperSize = (opts.paperSize ?? "A4").trim();
|
|
163
|
+
if (!paperSize)
|
|
164
|
+
throw new ClientValidationError("Dashboard export paperSize must not be empty.");
|
|
165
|
+
const orientation = (opts.orientation ?? "LANDSCAPE").trim().toUpperCase();
|
|
166
|
+
if (orientation !== "PORTRAIT" && orientation !== "LANDSCAPE") {
|
|
167
|
+
throw new ClientValidationError("Dashboard export orientation must be PORTRAIT or LANDSCAPE.");
|
|
168
|
+
}
|
|
169
|
+
const fileType = (opts.fileType ?? "PDF").trim().toUpperCase();
|
|
170
|
+
if (fileType !== "PDF") {
|
|
171
|
+
throw new ClientValidationError("Dashboard export fileType must be PDF.");
|
|
172
|
+
}
|
|
173
|
+
const slideIndex = opts.slideIndex ?? 0;
|
|
174
|
+
if (!Number.isInteger(slideIndex) || slideIndex < 0) {
|
|
175
|
+
throw new ClientValidationError("Dashboard export slideIndex must be a non-negative integer.");
|
|
176
|
+
}
|
|
177
|
+
return this.client.postStream(`/public/api/projects/${encodeURIComponent(pk)}/dashboards/${encodeURIComponent(dashboardId)}/action/export`, { paperSize, orientation, fileType, slideIndex, });
|
|
26
178
|
}
|
|
27
179
|
async delete(dashboardId, projectKey) {
|
|
28
180
|
await this.client.del(`/public/api/projects/${this.enc(projectKey)}/dashboards/${encodeURIComponent(dashboardId)}/`);
|
|
@@ -9,6 +9,11 @@ type SqlQueryOptions = {
|
|
|
9
9
|
postQueries?: string[];
|
|
10
10
|
type?: string;
|
|
11
11
|
projectKey?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Opt in to transient retries while starting the query. The SQL may execute
|
|
14
|
+
* more than once when DSS accepts a request but loses its response.
|
|
15
|
+
*/
|
|
16
|
+
retryMaxAttempts?: number;
|
|
12
17
|
};
|
|
13
18
|
export declare class SqlResource extends BaseResource {
|
|
14
19
|
private resolveOptionalProjectKey;
|
|
@@ -126,11 +126,12 @@ export class SqlResource extends BaseResource {
|
|
|
126
126
|
* or `datasetFullName` (run against a dataset's connection).
|
|
127
127
|
*/
|
|
128
128
|
async startQuery(opts) {
|
|
129
|
+
const { retryMaxAttempts, ...queryOpts } = opts;
|
|
129
130
|
return this.client.post("/public/api/sql/queries/", {
|
|
130
|
-
...
|
|
131
|
-
projectKey:
|
|
132
|
-
type:
|
|
133
|
-
});
|
|
131
|
+
...queryOpts,
|
|
132
|
+
projectKey: queryOpts.projectKey ?? this.resolveOptionalProjectKey(queryOpts.projectKey),
|
|
133
|
+
type: queryOpts.type ?? "sql",
|
|
134
|
+
}, retryMaxAttempts === undefined ? undefined : { retryMaxAttempts, });
|
|
134
135
|
}
|
|
135
136
|
/**
|
|
136
137
|
* Stream results of a started query as parsed JSON (array of arrays).
|
package/dist/src/skill.js
CHANGED
|
@@ -2,217 +2,16 @@ import { execFileSync, } from "node:child_process";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
\`--no-env-file\` disables Bun's automatic preloading only; the CLI still applies its documented \`.env\` handling unless \`DATAIKU_DISABLE_ENV=1\` is set.
|
|
10
|
-
|
|
11
|
-
## Contract
|
|
12
|
-
|
|
13
|
-
- Command results write exactly one JSON value to stdout, including \`doctor\`, \`batch\`, and \`cleanup\` failure reports; use the exit code and result fields.
|
|
14
|
-
- Dispatch/runtime failures write one JSONL error event to stderr with \`type:"error"\`, \`ok:false\`, \`error\`, \`code\`, and \`exitCode\`, while stdout stays empty.
|
|
15
|
-
- Non-fatal warnings and \`--verbose\` HTTP traces are JSONL stderr events (\`type:"warning"\` / \`type:"trace"\`), never prose.
|
|
16
|
-
- No prompts, help screens, tables, banners, or prose output are part of the contract.
|
|
17
|
-
- Exit codes: 0 success, 1 usage/configuration error, 2 DSS or internal error, 3 transient/retryable DSS error, 4 a failed long-running result or synchronous assertion.
|
|
18
|
-
- Pass \`--json\` for compact single-line JSON on stdout; use it for agent discovery and other JSON results to minimize tokens. Without it, success JSON is pretty-printed.
|
|
19
|
-
- \`--raw\` is the only stdout escape hatch: recipe payload commands emit raw bytes to stdout unless \`--output PATH\` is also set; with \`--output\`, stdout is the JSON string equal to \`PATH\` and the file receives exact raw bytes.
|
|
20
|
-
- \`--fields a,b,c\` projects those fields from object or array-of-objects results; dotted paths (\`a.b.c\`) drill into nested objects, and missing fields become \`null\`; string and scalar results pass through unchanged.
|
|
21
|
-
|
|
22
|
-
## Discover commands
|
|
23
|
-
|
|
24
|
-
\`\`\`text
|
|
25
|
-
dss agent contract --fields protocol,agentContractVersion,cli,stdio,planning,compatibility --json
|
|
26
|
-
dss commands run --fields dataset.create --json
|
|
27
|
-
dss commands run --fields dataset.create.usage,dataset.create.description,dataset.create.flags,dataset.create.examples --json
|
|
28
|
-
dss commands run --fields dataset --json
|
|
29
|
-
dss agent contract --fields commands.actions --json
|
|
30
|
-
\`\`\`
|
|
31
|
-
|
|
32
|
-
Bootstrap with the scoped \`agent contract\` call above: \`protocol,agentContractVersion,cli,stdio,planning,compatibility\` cover protocol/schema compatibility, stderr event semantics, preferred discovery commands, non-JSON escape hatches, planning rules, and compatibility guarantees in ~250 tokens. Request \`schemas\` (the JSON schemas) or \`commands\` (the contract's discovery section) only when you actually need them.
|
|
33
|
-
|
|
34
|
-
Before choosing command syntax, look the action up in the command registry: \`--fields RESOURCE.ACTION\` returns one complete entry, and appended \`.FIELD\` paths return only the metadata you need. Prefer the four-field projection \`usage,description,flags,examples\` by default — it is the smallest self-sufficient starting point for constructing an invocation. The registry entry is the canonical schema for flags, positional arguments, side effects, auth requirements, output shape, idempotency, dry-run support, structured examples, payload schemas, unsafe outputs, cleanup hints, and exit codes.
|
|
35
|
-
|
|
36
|
-
\`--fields\` takes a comma-separated list, so request several actions in one call. Each key is echoed exactly as requested (\`--fields dataset.create\` returns \`{"dataset.create": {...}}\`). Enumerate every resource and action with \`dss agent contract --fields commands.actions --json\` (~1k tokens); the hundreds-of-thousands-token unscoped registry is compatibility-only. An unknown resource or action exits with code 1 and a JSON error envelope on stderr containing the valid options.
|
|
37
|
-
Credential lookup order is flags first, then \`DATAIKU_*\` environment variables, then saved credentials.
|
|
38
|
-
Set \`DATAIKU_DISABLE_ENV=1\` when a test must ignore both \`.env\` files and \`DATAIKU_*\` environment variables.
|
|
39
|
-
When \`.env\` loading is enabled, the CLI reads \`.env\` from the command's current working directory first and then the CLI build/root directory; the invocation directory wins on conflicting keys. Put test-specific \`.env\` files in the directory where you invoke \`dss\`.
|
|
40
|
-
For disposable agent tests, set \`DSS_CONFIG_DIR\` to a temporary directory so saved credentials never touch the real profile.
|
|
41
|
-
|
|
42
|
-
## Planning, safety, and generic inputs
|
|
43
|
-
|
|
44
|
-
- Before any registry entry with \`sideEffect:"write"\`, run the exact argv with \`--plan\` first. Planning is local: it returns the derived operation without resolving credentials or calling DSS. Check \`destructive\`, \`idempotency\`, \`async\`, \`unsafeOutputs\`, and \`exitCodes\` before execution.
|
|
45
|
-
- Use \`--dry-run\` only when that action's registry entry has \`dryRun:true\`. \`--plan\` explains the operation; \`--dry-run\` exercises the action-specific simulation path. Never add an unsupported flag.
|
|
46
|
-
- When a create/upload action advertises \`--record-cleanup\`, pass \`--record-cleanup cleanup.jsonl\`. \`dss cleanup --file cleanup.jsonl\` previews the recorded cleanup steps in reverse order and does not mutate DSS; add \`--apply\` only after checking the preview.
|
|
47
|
-
- For JSON payload actions, follow \`inputContract\`, \`requiredFlags\`, and \`requiredOneOf\`. Prefer \`--data-file PATH\` or \`--stdin\` when advertised instead of inline \`--data\`; this preserves exact JSON across shells and keeps large or sensitive payloads out of process arguments.
|
|
48
|
-
- Authenticated actions advertise \`--request-timeout MS\` and \`--retries N\`. Long-running actions separately advertise controls such as \`--timeout MS\`, \`--poll-interval MS\`, and log limits; use only the flags in that action's registry entry.
|
|
49
|
-
- Before live mutation tests, use \`dss fixtures --json\` to discover compatible test resources instead of guessing project objects.
|
|
50
|
-
|
|
51
|
-
## Authentication
|
|
52
|
-
|
|
53
|
-
Prefer environment variables for ephemeral agent runs. Use the syntax for the active shell:
|
|
54
|
-
|
|
55
|
-
POSIX shell:
|
|
56
|
-
|
|
57
|
-
\`\`\`sh
|
|
58
|
-
export DATAIKU_URL=https://dss.example.com
|
|
59
|
-
export DATAIKU_API_KEY=your-api-key
|
|
60
|
-
export DATAIKU_PROJECT_KEY=MYPROJ
|
|
61
|
-
\`\`\`
|
|
62
|
-
|
|
63
|
-
PowerShell:
|
|
64
|
-
|
|
65
|
-
\`\`\`powershell
|
|
66
|
-
$env:DATAIKU_URL = "https://dss.example.com"
|
|
67
|
-
$env:DATAIKU_API_KEY = "your-api-key"
|
|
68
|
-
$env:DATAIKU_PROJECT_KEY = "MYPROJ"
|
|
69
|
-
\`\`\`
|
|
70
|
-
|
|
71
|
-
Windows Command Prompt:
|
|
72
|
-
|
|
73
|
-
\`\`\`bat
|
|
74
|
-
set "DATAIKU_URL=https://dss.example.com"
|
|
75
|
-
set "DATAIKU_API_KEY=your-api-key"
|
|
76
|
-
set "DATAIKU_PROJECT_KEY=MYPROJ"
|
|
77
|
-
\`\`\`
|
|
78
|
-
|
|
79
|
-
To persist credentials for later invocations:
|
|
80
|
-
|
|
81
|
-
\`\`\`bash
|
|
82
|
-
dss auth login --url https://dss.example.com --api-key YOUR_KEY --project-key MYPROJ
|
|
83
|
-
\`\`\`
|
|
84
|
-
|
|
85
|
-
The command saves credentials and returns \`{"saved":true,"path":"..."}\`. \`DSS_CONFIG_DIR\` wins when set. Otherwise credentials use \`XDG_CONFIG_HOME/dataiku/credentials.json\`, the \`dataiku/credentials.json\` directory under \`APPDATA\` on Windows, or \`~/.config/dataiku/credentials.json\`.
|
|
86
|
-
\`auth login\` validates by listing accessible projects before saving credentials, so the API key must be allowed to call DSS project-list APIs.
|
|
87
|
-
|
|
88
|
-
TLS flags: \`--insecure\` disables certificate verification; \`--ca-cert PATH\` adds a PEM CA bundle. Environment equivalents: \`NODE_TLS_REJECT_UNAUTHORIZED\`, \`NODE_EXTRA_CA_CERTS\`.
|
|
89
|
-
|
|
90
|
-
## Common workflows
|
|
91
|
-
|
|
92
|
-
\`\`\`text
|
|
93
|
-
dss version
|
|
94
|
-
dss project list
|
|
95
|
-
dss dataset list --project-key MYPROJ
|
|
96
|
-
dss recipe get-payload compute_orders --raw --output code.py --project-key MYPROJ
|
|
97
|
-
dss recipe diff compute_orders --file code.py --project-key MYPROJ
|
|
98
|
-
dss recipe set-payload compute_orders --file code.py --project-key MYPROJ
|
|
99
|
-
dss job build-and-wait orders --include-logs --project-key MYPROJ
|
|
100
|
-
dss sql query --connection analytics --sql "select 1" --project-key MYPROJ
|
|
101
|
-
dss batch --data-file steps.json
|
|
102
|
-
\`\`\`
|
|
103
|
-
For fake-DSS smoke tests, return project lists as JSON arrays such as \`[{"projectKey":"MYPROJ","name":"My Project"}]\` from \`/public/api/projects/\`; recipe payload commands read \`/public/api/projects/<PROJECT>/recipes/<NAME>?includePayload=true\` and expect a JSON object shaped like \`{"recipe":{"name":"<NAME>","type":"python"},"payload":"..."}\`.
|
|
104
|
-
|
|
105
|
-
## Application release safety
|
|
106
|
-
|
|
107
|
-
Treat app release as explicit validate, compare, version, successor, verify, and permission gates.
|
|
108
|
-
The public app-manifest \`version\` field is raw metadata: writing it is NOT a publish transaction.
|
|
109
|
-
New instances inherit the template's raw \`version\`; existing instances are never upgraded in
|
|
110
|
-
place — create an additive successor (the old instance is preserved), verify it, then retire the
|
|
111
|
-
old one as a separate guarded step. Never infer private publish/recreate/rename,
|
|
112
|
-
recipient-sharing, or UI-click operations that the public DSS API does not expose.
|
|
113
|
-
|
|
114
|
-
\`\`\`text
|
|
115
|
-
dss app validate-manifest --project-key APP_TEMPLATE
|
|
116
|
-
dss app compare-manifest APP_ID --project-key RELEASE_INSTANCE
|
|
117
|
-
dss app manifest-version --project-key APP_TEMPLATE
|
|
118
|
-
dss app set-manifest-version --manifest-version 1.4.0 --project-key APP_TEMPLATE
|
|
119
|
-
# targetProjectKey in instance.json must be confirmed absent
|
|
120
|
-
dss app create-instance APP_ID --data-file instance.json --wait --record-cleanup cleanup.jsonl
|
|
121
|
-
dss app create-successor-instance APP_ID --from RELEASE_INSTANCE --to RELEASE_INSTANCE_V2 --copy-permissions --record-cleanup cleanup.jsonl
|
|
122
|
-
dss app verify-instance APP_ID --project-key RELEASE_INSTANCE_V2 --expect-version 1.4.0
|
|
123
|
-
dss cleanup --file cleanup.jsonl
|
|
124
|
-
dss app permissions-snapshot --project-key RELEASE_INSTANCE --output permissions.json
|
|
125
|
-
dss app permissions-diff --project-key RELEASE_INSTANCE --file permissions.json
|
|
126
|
-
dss app permissions-restore --project-key RELEASE_INSTANCE --file permissions.json --dry-run
|
|
127
|
-
\`\`\`
|
|
128
|
-
|
|
129
|
-
An invalid manifest exits non-zero with the validation result in the structured error details.
|
|
130
|
-
Review the cleanup preview before \`dss cleanup --file cleanup.jsonl --apply\`. Every new entry is
|
|
131
|
-
bound to the canonical DSS URL. App cleanup records a \`creationTag\` hash observed after the DSS
|
|
132
|
-
future identifies its target key; unconfirmed creation stops unresolved. The future target and
|
|
133
|
-
later \`creationTag\` are independent, non-atomic observations because the public API exposes
|
|
134
|
-
neither an immutable project ID joined to the future nor a conditional DELETE. Cleanup rechecks
|
|
135
|
-
type and \`creationTag\` immediately before deletion, rejecting detected key reuse but unable to
|
|
136
|
-
eliminate replacement in the remaining check-to-DELETE gap. Apply validates the full ledger before
|
|
137
|
-
any request and rejects legacy, mixed-server, mismatched-server, or unbound app cleanup entries.
|
|
138
|
-
The successor's cleanup ledger entry targets only the new project key;
|
|
139
|
-
the predecessor is never targeted. Some DSS deployments return 403 for an unknown target project:
|
|
140
|
-
instance creation requires confirmed target absence before POST, so an inaccessible target absent
|
|
141
|
-
from the visible project list is still rejected as unconfirmed rather than risking cleanup against
|
|
142
|
-
a pre-existing project. A definitive create rejection never produces a cleanup entry.
|
|
143
|
-
|
|
144
|
-
\`app set-manifest-version\` reports
|
|
145
|
-
\`concurrencyControl:"client-side-non-atomic-stale-read-check"\`. \`--expect-hash SHA256\` refuses the
|
|
146
|
-
write when the manifest already changed, but the PUT itself stays unconditional: this command can
|
|
147
|
-
overwrite a writer that commits inside the read-then-write window, and the post-write read cannot
|
|
148
|
-
detect that lost update when this command's payload wins. Never treat the hash as a serializing lock.
|
|
149
|
-
An ambiguous manifest PUT or verification read exits non-zero with \`persisted:null\`,
|
|
150
|
-
\`after:null\`, and \`outcome:"indeterminate"\`; it never claims success or rejection.
|
|
151
|
-
|
|
152
|
-
The API key authenticates public REST only. \`app verify-instance\` reports \`apiReady:true\`,
|
|
153
|
-
\`status:"API_VERIFIED_UI_PENDING"\`, and \`uiPublicationVerified:false\`. Its \`visual-ui\` gate
|
|
154
|
-
requires a pre-authenticated SSO browser session or dedicated UI test identity and evidence from
|
|
155
|
-
exercising the affected tiles, forms, and actions; the CLI never marks that external gate complete.
|
|
156
|
-
Permission snapshots have an integrity hash bound to the canonical DSS URL, project key, and
|
|
157
|
-
observed concrete project incarnation from \`creationTag\`; diff and restore reject detected server,
|
|
158
|
-
key, or incarnation mismatches. DSS exposes no conditional permission PUT, so these client-side checks
|
|
159
|
-
narrow and detect key-reuse races but cannot serialize the final check with the write. Snapshots
|
|
160
|
-
contain access-control identities; keep them mode \`0600\` and commit them only when repository
|
|
161
|
-
policy permits.
|
|
162
|
-
|
|
163
|
-
## Confirming mutations
|
|
164
|
-
|
|
165
|
-
Mutations print a small JSON ack to stdout and exit 0 on success (e.g. \`{"updated":"NAME","resource":"recipe"}\`); on failure they print the error envelope to stderr and exit non-zero. The exit code is the source of truth.
|
|
166
|
-
|
|
167
|
-
- For portable multi-step writes, prefer \`dss batch\` (payload: a JSON array of argv arrays). It runs fail-fast, returns one envelope with per-step \`ok\`/\`result\`/\`error\`, and exits non-zero if any step fails.
|
|
168
|
-
- If separate processes are required, inspect each exit code and stop before launching the next step. Do not rely on shell chaining: syntax differs between POSIX shells, Windows PowerShell 5.1, PowerShell 7, and Command Prompt.
|
|
169
|
-
- Never pipe a mutation into a command that prints a fixed string or merges stderr: a pipeline may return the helper's exit code and report a failed mutation as success.
|
|
170
|
-
- To branch in code, key off the exit code or the JSON ack on stdout — never a hardcoded label.
|
|
171
|
-
|
|
172
|
-
## Platform & debugging notes
|
|
173
|
-
|
|
174
|
-
- Pass code and SQL via \`--file\`/\`--sql-file\`, not inline: shells (especially PowerShell) mangle quotes, \`$\`, and newlines in multi-line snippets.
|
|
175
|
-
- On a non-UTF-8 console (e.g. Windows cp1252), don't print non-ASCII results; write them to a UTF-8 file and read that, or use \`--output PATH\`.
|
|
176
|
-
- Build failures: \`dss job log <id> --errors-only\` surfaces just error/traceback lines, and \`--output PATH\` saves the full log to a file. Logs are one long line with JVM noise; the \`Error in Python process: At line <N>\` marker maps \`<N>\` straight to your recipe payload's source line.
|
|
177
|
-
- Schema changes aren't automatic for code recipes: after changing a python/SQL/prepare recipe's output columns run \`dss dataset refresh-schema\` (or rebuild) before downstream reads, and \`dss dataset validate-build\` to catch file-backed misconfig before launching a build. Exception: \`dss recipe create --type sync\` copies the input schema onto a schemaless dataset output at create time (see \`syncOutputSchemaPropagated\` in the result), so a fresh sync output builds populated without a manual refresh.
|
|
178
|
-
- \`dss dataset download\` is capped (default 100k rows) and returns \`{ path, rows, truncated, limit }\`: check \`truncated\` and raise \`--limit N\` when you need more — treat it as a sample, not a guaranteed full export. Without \`--output\` the file lands in the current working directory (and a \`dataset_download_default_location\` warning names that path); pass \`--output PATH\` to control the destination. For very large tables, aggregate in SQL or read inside a recipe instead.
|
|
179
|
-
- \`dss api-service list-packages SERVICE\` first verifies the parent service through its settings. A \`not_found\` error means the service is missing (verify the service ID and project key); an empty array means the service exists but has no deployable packages. Do not reinterpret \`not_found\` as an empty package list or retry the lower-level packages route.
|
|
180
|
-
- \`dss notebook clear-jupyter-outputs NAME --dry-run\` returns the full \`current\` and \`next\` notebook states. Applying it clears \`outputs\` and resets \`execution_count\` to \`0\` only on code cells, preserving markdown and raw cells unchanged.
|
|
181
|
-
- \`dss sql query ... --dataset PROJECT.NAME\` first queries through the dataset. If DSS rejects that dataset connection as neither SQL nor HDFS, the CLI reads dataset metadata and retries with \`params.connection\` (using its schema or catalog as the database when available). A readable dataset with no usable connection exits with \`validation_failed\` and advises \`--connection\`; dataset metadata \`404\` and \`403\` errors propagate as \`not_found\` and \`permission_denied\`.
|
|
182
|
-
|
|
183
|
-
## Error envelope
|
|
184
|
-
|
|
185
|
-
For dispatch/runtime failures, parse stderr as JSONL; the final line is an error event:
|
|
186
|
-
|
|
187
|
-
\`\`\`json
|
|
188
|
-
{
|
|
189
|
-
"type": "error",
|
|
190
|
-
"ok": false,
|
|
191
|
-
"error": "Missing API key.",
|
|
192
|
-
"code": "usage_error",
|
|
193
|
-
"category": "usage",
|
|
194
|
-
"exitCode": 1,
|
|
195
|
-
"resource": "dataset",
|
|
196
|
-
"action": "list"
|
|
197
|
-
}
|
|
198
|
-
\`\`\`
|
|
199
|
-
|
|
200
|
-
\`doctor\`, \`batch\`, and \`cleanup\` report command-level failures on stdout and leave stderr empty. Use the exit code before choosing a stream.
|
|
201
|
-
Use \`code\`, \`category\`, \`exitCode\`, \`retryable\`, \`status\`, and \`details\` for recovery logic. Do not scrape message text when a structured field is available.
|
|
202
|
-
Treat \`details.body\` as sanitized metadata only: it contains at most \`requestId\`/\`request_id\`/\`errorId\`/\`elapsedMs\` plus locally trusted target/timing fields, not the arbitrary DSS response body. \`details.statusText\` is canonical text derived from the numeric status, not a remote reason phrase. Base recovery on top-level \`code\`, \`category\`, \`status\`, \`retryable\`, \`requestId\`, \`hint\`, and \`details.dssCategory\`; never parse or expose assumed server-body fields.
|
|
203
|
-
`;
|
|
204
|
-
const SKILL_FRONTMATTER = `---
|
|
205
|
-
name: dataiku-dss
|
|
206
|
-
description: >-
|
|
207
|
-
Agent-only JSON CLI for Dataiku DSS. Use to inspect or mutate DSS projects,
|
|
208
|
-
datasets, recipes, jobs, scenarios, folders, notebooks, SQL, variables,
|
|
209
|
-
code envs, and connections. Discover scoped command metadata with
|
|
210
|
-
dss commands run --fields RESOURCE.ACTION --json.
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
`;
|
|
5
|
+
const SKILL_URLS = [
|
|
6
|
+
new URL("../skills/dataiku-dss/SKILL.md", import.meta.url),
|
|
7
|
+
new URL("../../skills/dataiku-dss/SKILL.md", import.meta.url),
|
|
8
|
+
];
|
|
214
9
|
function skillContent() {
|
|
215
|
-
|
|
10
|
+
for (const skillUrl of SKILL_URLS) {
|
|
11
|
+
if (fs.existsSync(skillUrl))
|
|
12
|
+
return fs.readFileSync(skillUrl, "utf-8");
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`Bundled Dataiku skill not found. Checked: ${SKILL_URLS.map((url) => url.pathname).join(", ")}`);
|
|
216
15
|
}
|
|
217
16
|
export const AGENTS = {
|
|
218
17
|
claude: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dataiku-sdk",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.7",
|
|
4
4
|
"description": "Dataiku DSS SDK and CLI for programmatic access to DSS REST APIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist/",
|
|
16
|
+
"plugin.json",
|
|
17
|
+
"skills/",
|
|
16
18
|
"bin/dss.js",
|
|
17
19
|
"packages/types/dist/"
|
|
18
20
|
],
|
|
@@ -22,9 +24,10 @@
|
|
|
22
24
|
},
|
|
23
25
|
"scripts": {
|
|
24
26
|
"build": "tsc && tsc -p packages/types/tsconfig.json",
|
|
25
|
-
"prepack": "tsc && tsc -p packages/types/tsconfig.json",
|
|
26
|
-
"prepublishOnly": "tsc && tsc -p packages/types/tsconfig.json",
|
|
27
|
-
"check": "tsc --noEmit",
|
|
27
|
+
"prepack": "node scripts/sync-plugin-version.mjs --check && tsc && tsc -p packages/types/tsconfig.json",
|
|
28
|
+
"prepublishOnly": "node scripts/sync-plugin-version.mjs --check && tsc && tsc -p packages/types/tsconfig.json",
|
|
29
|
+
"check": "tsc --noEmit && node scripts/sync-plugin-version.mjs --check",
|
|
30
|
+
"version": "node scripts/sync-plugin-version.mjs && git add plugin.json",
|
|
28
31
|
"lint": "oxlint src/",
|
|
29
32
|
"lint:fix": "oxlint --fix src/",
|
|
30
33
|
"format": "dprint fmt",
|
|
@@ -43,7 +46,9 @@
|
|
|
43
46
|
"dprint": "^0.55.2",
|
|
44
47
|
"js-tiktoken": "1.0.21",
|
|
45
48
|
"oxlint": "^1.78.0",
|
|
46
|
-
"typescript": "^7.0.2"
|
|
49
|
+
"typescript": "^7.0.2",
|
|
50
|
+
"typescript-language-server": "5.3.0",
|
|
51
|
+
"typescript-language-service": "npm:typescript@5.9.3"
|
|
47
52
|
},
|
|
48
53
|
"dependencies": {
|
|
49
54
|
"@sinclair/typebox": "^0.34.52"
|
package/plugin.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "dataiku-sdk",
|
|
4
|
+
"version": "0.12.7",
|
|
5
|
+
"description": "Dataiku DSS SDK and agent-oriented CLI for programmatic access to DSS REST APIs.",
|
|
6
|
+
"homepage": "https://github.com/clssck/dataiku-sdk",
|
|
7
|
+
"repository": "https://github.com/clssck/dataiku-sdk",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"dataiku",
|
|
11
|
+
"dss",
|
|
12
|
+
"cli",
|
|
13
|
+
"agent-skills"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dataiku-dss
|
|
3
|
+
description: >-
|
|
4
|
+
Agent-only JSON CLI for Dataiku DSS. Use to inspect or mutate DSS projects,
|
|
5
|
+
datasets, recipes, jobs, scenarios, folders, notebooks, SQL, variables,
|
|
6
|
+
code envs, and connections. Discover scoped command metadata with
|
|
7
|
+
dss commands run --fields RESOURCE.ACTION --json.
|
|
8
|
+
license: MIT
|
|
9
|
+
compatibility: >-
|
|
10
|
+
Requires the dss CLI, network access to a Dataiku DSS instance, and configured
|
|
11
|
+
DATAIKU_URL and DATAIKU_API_KEY credentials. Source-checkout fallback requires Bun.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Dataiku DSS agent CLI
|
|
15
|
+
|
|
16
|
+
Use `dss` when an agent needs to inspect or change Dataiku DSS resources: projects, datasets, recipes, jobs, scenarios, folders, notebooks, SQL, variables, code envs, and connections.
|
|
17
|
+
If the installed `dss` binary is unavailable but the repository checkout is the current workspace, prefer `bun --no-env-file src/cli.ts ...` or `bun --no-env-file ./bin/dss.js ...`. Node users can invoke the same cross-runtime launcher as `node ./bin/dss.js ...`; when `dist/` has not been built, it delegates to Bun. From another working directory, pass the checkout's absolute `bin/dss.js` path to Bun or Node.
|
|
18
|
+
`--no-env-file` disables Bun's automatic preloading only; the CLI still applies its documented `.env` handling unless `DATAIKU_DISABLE_ENV=1` is set.
|
|
19
|
+
|
|
20
|
+
## Contract
|
|
21
|
+
|
|
22
|
+
- Command results write exactly one JSON value to stdout, including `doctor`, `batch`, and `cleanup` failure reports; use the exit code and result fields.
|
|
23
|
+
- Dispatch/runtime failures write one JSONL error event to stderr with `type:"error"`, `ok:false`, `error`, `code`, and `exitCode`, while stdout stays empty.
|
|
24
|
+
- Non-fatal warnings and `--verbose` HTTP traces are JSONL stderr events (`type:"warning"` / `type:"trace"`), never prose.
|
|
25
|
+
- No prompts, help screens, tables, banners, or prose output are part of the contract.
|
|
26
|
+
- Exit codes: 0 success, 1 usage/configuration error, 2 DSS or internal error, 3 transient/retryable DSS error, 4 a failed long-running result or synchronous assertion.
|
|
27
|
+
- Pass `--json` for compact single-line JSON on stdout; use it for agent discovery and other JSON results to minimize tokens. Without it, success JSON is pretty-printed.
|
|
28
|
+
- `--raw` is the only stdout escape hatch: recipe payload commands emit raw bytes to stdout unless `--output PATH` is also set; with `--output`, stdout is the JSON string equal to `PATH` and the file receives exact raw bytes.
|
|
29
|
+
- `--fields a,b,c` projects those fields from object or array-of-objects results; dotted paths (`a.b.c`) drill into nested objects, and missing fields become `null`; string and scalar results pass through unchanged.
|
|
30
|
+
|
|
31
|
+
## Discover commands
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
dss agent contract --fields protocol,agentContractVersion,cli,stdio,planning,compatibility --json
|
|
35
|
+
dss commands run --fields dataset.create --json
|
|
36
|
+
dss commands run --fields dataset.create.usage,dataset.create.description,dataset.create.flags,dataset.create.examples --json
|
|
37
|
+
dss commands run --fields dataset --json
|
|
38
|
+
dss agent contract --fields commands.actions --json
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Bootstrap with the scoped `agent contract` call above: `protocol,agentContractVersion,cli,stdio,planning,compatibility` cover protocol/schema compatibility, stderr event semantics, preferred discovery commands, non-JSON escape hatches, planning rules, and compatibility guarantees in ~250 tokens. Request `schemas` (the JSON schemas) or `commands` (the contract's discovery section) only when you actually need them.
|
|
42
|
+
|
|
43
|
+
Before choosing command syntax, look the action up in the command registry: `--fields RESOURCE.ACTION` returns one complete entry, and appended `.FIELD` paths return only the metadata you need. Prefer the four-field projection `usage,description,flags,examples` by default — it is the smallest self-sufficient starting point for constructing an invocation. The registry entry is the canonical schema for flags, positional arguments, side effects, auth requirements, output shape, idempotency, dry-run support, structured examples, payload schemas, unsafe outputs, cleanup hints, and exit codes.
|
|
44
|
+
|
|
45
|
+
`--fields` takes a comma-separated list, so request several actions in one call. Each key is echoed exactly as requested (`--fields dataset.create` returns `{"dataset.create": {...}}`). Enumerate every resource and action with `dss agent contract --fields commands.actions --json` (~1k tokens); the hundreds-of-thousands-token unscoped registry is compatibility-only. An unknown resource or action exits with code 1 and a JSON error envelope on stderr containing the valid options.
|
|
46
|
+
Credential lookup order is flags first, then `DATAIKU_*` environment variables, then saved credentials.
|
|
47
|
+
Set `DATAIKU_DISABLE_ENV=1` when a test must ignore both `.env` files and `DATAIKU_*` environment variables.
|
|
48
|
+
When `.env` loading is enabled, the CLI reads `.env` from the command's current working directory first and then the CLI build/root directory; the invocation directory wins on conflicting keys. Put test-specific `.env` files in the directory where you invoke `dss`.
|
|
49
|
+
For disposable agent tests, set `DSS_CONFIG_DIR` to a temporary directory so saved credentials never touch the real profile.
|
|
50
|
+
|
|
51
|
+
## Planning, safety, and generic inputs
|
|
52
|
+
|
|
53
|
+
- Before any registry entry with `sideEffect:"write"`, run the exact argv with `--plan` first. Planning is local: it returns the derived operation without resolving credentials or calling DSS. Check `destructive`, `idempotency`, `async`, `unsafeOutputs`, and `exitCodes` before execution.
|
|
54
|
+
- Use `--dry-run` only when that action's registry entry has `dryRun:true`. `--plan` explains the operation; `--dry-run` exercises the action-specific simulation path. Never add an unsupported flag.
|
|
55
|
+
- When a create/upload action advertises `--record-cleanup`, pass `--record-cleanup cleanup.jsonl`. `dss cleanup --file cleanup.jsonl` previews the recorded cleanup steps in reverse order and does not mutate DSS; add `--apply` only after checking the preview.
|
|
56
|
+
- For JSON payload actions, follow `inputContract`, `requiredFlags`, and `requiredOneOf`. Prefer `--data-file PATH` or `--stdin` when advertised instead of inline `--data`; this preserves exact JSON across shells and keeps large or sensitive payloads out of process arguments.
|
|
57
|
+
- Authenticated actions advertise `--request-timeout MS` and `--retries N`; `--retries` applies to idempotent GET requests. Long-running actions separately advertise controls such as `--timeout MS`, `--poll-interval MS`, and log limits; use only the flags in that action's registry entry.
|
|
58
|
+
- Before live mutation tests, use `dss fixtures --json` to discover compatible test resources instead of guessing project objects.
|
|
59
|
+
|
|
60
|
+
## Authentication
|
|
61
|
+
|
|
62
|
+
Prefer environment variables for ephemeral agent runs. Use the syntax for the active shell:
|
|
63
|
+
|
|
64
|
+
POSIX shell:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
export DATAIKU_URL=https://dss.example.com
|
|
68
|
+
export DATAIKU_API_KEY=your-api-key
|
|
69
|
+
export DATAIKU_PROJECT_KEY=MYPROJ
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
PowerShell:
|
|
73
|
+
|
|
74
|
+
```powershell
|
|
75
|
+
$env:DATAIKU_URL = "https://dss.example.com"
|
|
76
|
+
$env:DATAIKU_API_KEY = "your-api-key"
|
|
77
|
+
$env:DATAIKU_PROJECT_KEY = "MYPROJ"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Windows Command Prompt:
|
|
81
|
+
|
|
82
|
+
```bat
|
|
83
|
+
set "DATAIKU_URL=https://dss.example.com"
|
|
84
|
+
set "DATAIKU_API_KEY=your-api-key"
|
|
85
|
+
set "DATAIKU_PROJECT_KEY=MYPROJ"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
To persist credentials for later invocations:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
dss auth login --url https://dss.example.com --api-key YOUR_KEY --project-key MYPROJ
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The command saves credentials and returns `{"saved":true,"path":"..."}`. `DSS_CONFIG_DIR` wins when set. Otherwise credentials use `XDG_CONFIG_HOME/dataiku/credentials.json`, the `dataiku/credentials.json` directory under `APPDATA` on Windows, or `~/.config/dataiku/credentials.json`.
|
|
95
|
+
`auth login` validates by listing accessible projects before saving credentials, so the API key must be allowed to call DSS project-list APIs.
|
|
96
|
+
|
|
97
|
+
TLS flags: `--insecure` disables certificate verification; `--ca-cert PATH` adds a PEM CA bundle. Environment equivalents: `NODE_TLS_REJECT_UNAUTHORIZED`, `NODE_EXTRA_CA_CERTS`.
|
|
98
|
+
|
|
99
|
+
## Common workflows
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
dss version
|
|
103
|
+
dss project list
|
|
104
|
+
dss dataset list --project-key MYPROJ
|
|
105
|
+
dss recipe get-payload compute_orders --raw --output code.py --project-key MYPROJ
|
|
106
|
+
dss recipe diff compute_orders --file code.py --project-key MYPROJ
|
|
107
|
+
dss recipe set-payload compute_orders --file code.py --project-key MYPROJ
|
|
108
|
+
dss job build-and-wait orders --include-logs --project-key MYPROJ
|
|
109
|
+
dss sql query --connection analytics --sql "select 1" --project-key MYPROJ
|
|
110
|
+
dss batch --data-file steps.json
|
|
111
|
+
```
|
|
112
|
+
For fake-DSS smoke tests, return project lists as JSON arrays such as `[{"projectKey":"MYPROJ","name":"My Project"}]` from `/public/api/projects/`; recipe payload commands read `/public/api/projects/<PROJECT>/recipes/<NAME>?includePayload=true` and expect a JSON object shaped like `{"recipe":{"name":"<NAME>","type":"python"},"payload":"..."}`.
|
|
113
|
+
|
|
114
|
+
## Application release safety
|
|
115
|
+
|
|
116
|
+
Treat app release as explicit validate, compare, version, successor, verify, and permission gates.
|
|
117
|
+
The public app-manifest `version` field is raw metadata: writing it is NOT a publish transaction.
|
|
118
|
+
New instances inherit the template's raw `version`; existing instances are never upgraded in
|
|
119
|
+
place — create an additive successor (the old instance is preserved), verify it, then retire the
|
|
120
|
+
old one as a separate guarded step. Never infer private publish/recreate/rename,
|
|
121
|
+
recipient-sharing, or UI-click operations that the public DSS API does not expose.
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
dss app validate-manifest --project-key APP_TEMPLATE
|
|
125
|
+
dss app compare-manifest APP_ID --project-key RELEASE_INSTANCE
|
|
126
|
+
dss app manifest-version --project-key APP_TEMPLATE
|
|
127
|
+
dss app set-manifest-version --manifest-version 1.4.0 --project-key APP_TEMPLATE
|
|
128
|
+
# targetProjectKey in instance.json must be confirmed absent
|
|
129
|
+
dss app create-instance APP_ID --data-file instance.json --wait --record-cleanup cleanup.jsonl
|
|
130
|
+
dss app create-successor-instance APP_ID --from RELEASE_INSTANCE --to RELEASE_INSTANCE_V2 --copy-permissions --record-cleanup cleanup.jsonl
|
|
131
|
+
dss app verify-instance APP_ID --project-key RELEASE_INSTANCE_V2 --expect-version 1.4.0
|
|
132
|
+
dss cleanup --file cleanup.jsonl
|
|
133
|
+
dss app permissions-snapshot --project-key RELEASE_INSTANCE --output permissions.json
|
|
134
|
+
dss app permissions-diff --project-key RELEASE_INSTANCE --file permissions.json
|
|
135
|
+
dss app permissions-restore --project-key RELEASE_INSTANCE --file permissions.json --dry-run
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
An invalid manifest exits non-zero with the validation result in the structured error details.
|
|
139
|
+
Review the cleanup preview before `dss cleanup --file cleanup.jsonl --apply`. Every new entry is
|
|
140
|
+
bound to the canonical DSS URL. App cleanup records a `creationTag` hash observed after the DSS
|
|
141
|
+
future identifies its target key; unconfirmed creation stops unresolved. The future target and
|
|
142
|
+
later `creationTag` are independent, non-atomic observations because the public API exposes
|
|
143
|
+
neither an immutable project ID joined to the future nor a conditional DELETE. Cleanup rechecks
|
|
144
|
+
type and `creationTag` immediately before deletion, rejecting detected key reuse but unable to
|
|
145
|
+
eliminate replacement in the remaining check-to-DELETE gap. Apply validates the full ledger before
|
|
146
|
+
any request and rejects legacy, mixed-server, mismatched-server, or unbound app cleanup entries.
|
|
147
|
+
The successor's cleanup ledger entry targets only the new project key;
|
|
148
|
+
the predecessor is never targeted. Some DSS deployments return 403 for an unknown target project:
|
|
149
|
+
instance creation requires confirmed target absence before POST, so an inaccessible target absent
|
|
150
|
+
from the visible project list is still rejected as unconfirmed rather than risking cleanup against
|
|
151
|
+
a pre-existing project. A definitive create rejection never produces a cleanup entry.
|
|
152
|
+
|
|
153
|
+
`app set-manifest-version` reports
|
|
154
|
+
`concurrencyControl:"client-side-non-atomic-stale-read-check"`. `--expect-hash SHA256` refuses the
|
|
155
|
+
write when the manifest already changed, but the PUT itself stays unconditional: this command can
|
|
156
|
+
overwrite a writer that commits inside the read-then-write window, and the post-write read cannot
|
|
157
|
+
detect that lost update when this command's payload wins. Never treat the hash as a serializing lock.
|
|
158
|
+
An ambiguous manifest PUT or verification read exits non-zero with `persisted:null`,
|
|
159
|
+
`after:null`, and `outcome:"indeterminate"`; it never claims success or rejection.
|
|
160
|
+
|
|
161
|
+
The API key authenticates public REST only. `app verify-instance` reports `apiReady:true`,
|
|
162
|
+
`status:"API_VERIFIED_UI_PENDING"`, and `uiPublicationVerified:false`. Its `visual-ui` gate
|
|
163
|
+
requires a pre-authenticated SSO browser session or dedicated UI test identity and evidence from
|
|
164
|
+
exercising the affected tiles, forms, and actions; the CLI never marks that external gate complete.
|
|
165
|
+
Permission snapshots have an integrity hash bound to the canonical DSS URL, project key, and
|
|
166
|
+
observed concrete project incarnation from `creationTag`; diff and restore reject detected server,
|
|
167
|
+
key, or incarnation mismatches. DSS exposes no conditional permission PUT, so these client-side checks
|
|
168
|
+
narrow and detect key-reuse races but cannot serialize the final check with the write. Snapshots
|
|
169
|
+
contain access-control identities; keep them mode `0600` and commit them only when repository
|
|
170
|
+
policy permits.
|
|
171
|
+
|
|
172
|
+
## Confirming mutations
|
|
173
|
+
|
|
174
|
+
Mutations print a small JSON ack to stdout and exit 0 on success (e.g. `{"updated":"NAME","resource":"recipe"}`); on failure they print the error envelope to stderr and exit non-zero. The exit code is the source of truth.
|
|
175
|
+
|
|
176
|
+
- For portable multi-step writes, prefer `dss batch` (payload: a JSON array of argv arrays). It runs fail-fast, returns one envelope with per-step `ok`/`result`/`error`, and exits non-zero if any step fails.
|
|
177
|
+
- If separate processes are required, inspect each exit code and stop before launching the next step. Do not rely on shell chaining: syntax differs between POSIX shells, Windows PowerShell 5.1, PowerShell 7, and Command Prompt.
|
|
178
|
+
- Never pipe a mutation into a command that prints a fixed string or merges stderr: a pipeline may return the helper's exit code and report a failed mutation as success.
|
|
179
|
+
- To branch in code, key off the exit code or the JSON ack on stdout — never a hardcoded label.
|
|
180
|
+
|
|
181
|
+
## Platform & debugging notes
|
|
182
|
+
|
|
183
|
+
- Pass code and SQL via `--file`/`--sql-file`, not inline: shells (especially PowerShell) mangle quotes, `$`, and newlines in multi-line snippets.
|
|
184
|
+
- On a non-UTF-8 console (e.g. Windows cp1252), don't print non-ASCII results; write them to a UTF-8 file and read that, or use `--output PATH`.
|
|
185
|
+
- Build failures: `dss job log <id> --errors-only` surfaces just error/traceback lines, and `--output PATH` saves the full log to a file. Logs are one long line with JVM noise; the `Error in Python process: At line <N>` marker maps `<N>` straight to your recipe payload's source line.
|
|
186
|
+
- Schema changes aren't automatic for code recipes: after changing a python/SQL/prepare recipe's output columns run `dss dataset refresh-schema` (or rebuild) before downstream reads, and `dss dataset validate-build` to catch file-backed misconfig before launching a build. Exception: `dss recipe create --type sync` copies the input schema onto a schemaless dataset output at create time (see `syncOutputSchemaPropagated` in the result), so a fresh sync output builds populated without a manual refresh.
|
|
187
|
+
- `dss dataset download` is capped (default 100k rows) and returns `{ path, rows, truncated, limit }`: check `truncated` and raise `--limit N` when you need more — treat it as a sample, not a guaranteed full export. Without `--output` the file lands in the current working directory (and a `dataset_download_default_location` warning names that path); pass `--output PATH` to control the destination. For very large tables, aggregate in SQL or read inside a recipe instead.
|
|
188
|
+
- `dss dashboard create` and `dss dashboard update` validate every `INSIGHT` tile before mutation: `insightId` and `targetInsightId` must agree and resolve, and any `datasetSmartName` must resolve. Missing or stale references exit with `validation_failed` before POST/PUT. Cross-project `PROJECT.DATASET` references require the API key to read that dataset project; a `403` blocks the save rather than silently accepting an unverifiable dashboard.
|
|
189
|
+
- `dss api-service list-packages SERVICE` first verifies the parent service through its settings. A `not_found` error means the service is missing (verify the service ID and project key); an empty array means the service exists but has no deployable packages. Do not reinterpret `not_found` as an empty package list or retry the lower-level packages route.
|
|
190
|
+
- `dss notebook clear-jupyter-outputs NAME --dry-run` returns the full `current` and `next` notebook states. Applying it clears `outputs` and resets `execution_count` to `0` only on code cells, preserving markdown and raw cells unchanged.
|
|
191
|
+
- `dss sql query ... --dataset PROJECT.NAME` first queries through the dataset. If DSS rejects that dataset connection as neither SQL nor HDFS, the CLI reads dataset metadata and retries with `params.connection` (using its schema or catalog as the database when available). A readable dataset with no usable connection exits with `validation_failed` and advises `--connection`; dataset metadata `404` and `403` errors propagate as `not_found` and `permission_denied`.
|
|
192
|
+
- `dss sql query ... --start-retries N` opts the query-start POST into transient retries with exponential backoff. A lost response can cause DSS to execute the SQL more than once, so use it only for SQL whose repetition is safe; ordinary `--retries N` remains GET-only.
|
|
193
|
+
|
|
194
|
+
## Error envelope
|
|
195
|
+
|
|
196
|
+
For dispatch/runtime failures, parse stderr as JSONL; the final line is an error event:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"type": "error",
|
|
201
|
+
"ok": false,
|
|
202
|
+
"error": "Missing API key.",
|
|
203
|
+
"code": "usage_error",
|
|
204
|
+
"category": "usage",
|
|
205
|
+
"exitCode": 1,
|
|
206
|
+
"resource": "dataset",
|
|
207
|
+
"action": "list"
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`doctor`, `batch`, and `cleanup` report command-level failures on stdout and leave stderr empty. Use the exit code before choosing a stream.
|
|
212
|
+
Use `code`, `category`, `exitCode`, `retryable`, `status`, and `details` for recovery logic. Do not scrape message text when a structured field is available.
|
|
213
|
+
Treat `details.body` as sanitized metadata only: it contains at most `requestId`/`request_id`/`errorId`/`elapsedMs` plus locally trusted target/timing fields, not the arbitrary DSS response body. `details.statusText` is canonical text derived from the numeric status, not a remote reason phrase. Base recovery on top-level `code`, `category`, `status`, `retryable`, `requestId`, `hint`, and `details.dssCategory`; never parse or expose assumed server-body fields.
|