dataiku-sdk 0.1.1 → 0.2.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/bin/dss.js +3 -1
- package/dist/packages/types/src/index.d.ts +4 -4
- package/dist/packages/types/src/index.js +2 -2
- package/dist/src/auth.d.ts +4 -0
- package/dist/src/auth.js +20 -0
- package/dist/src/cli.js +515 -72
- package/dist/src/client.d.ts +5 -1
- package/dist/src/client.js +14 -1
- package/dist/src/config.d.ts +11 -0
- package/dist/src/config.js +64 -0
- package/dist/src/errors.js +12 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +3 -0
- package/dist/src/resources/connections.d.ts +1 -1
- package/dist/src/resources/connections.js +7 -4
- package/dist/src/resources/datasets.js +8 -2
- package/dist/src/resources/folders.d.ts +1 -0
- package/dist/src/resources/folders.js +8 -0
- package/dist/src/resources/jobs.d.ts +2 -1
- package/dist/src/resources/jobs.js +7 -3
- package/dist/src/resources/notebooks.d.ts +18 -3
- package/dist/src/resources/notebooks.js +25 -5
- package/dist/src/resources/recipes.d.ts +18 -1
- package/dist/src/resources/recipes.js +68 -7
- package/dist/src/resources/sql.js +32 -5
- package/dist/src/resources/variables.d.ts +1 -0
- package/dist/src/resources/variables.js +9 -1
- package/dist/src/skill.d.ts +33 -0
- package/dist/src/skill.js +229 -0
- package/package.json +1 -1
- package/packages/types/dist/index.d.ts +4 -4
- package/packages/types/dist/index.js +2 -2
package/dist/src/cli.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync, } from "node:fs";
|
|
3
|
+
import { writeFile, } from "node:fs/promises";
|
|
3
4
|
import { dirname, resolve, } from "node:path";
|
|
5
|
+
import { createInterface, } from "node:readline";
|
|
6
|
+
import { Writable, } from "node:stream";
|
|
4
7
|
import { fileURLToPath, } from "node:url";
|
|
8
|
+
import { validateCredentials, } from "./auth.js";
|
|
5
9
|
import { DataikuClient, } from "./client.js";
|
|
10
|
+
import { deleteCredentials, getCredentialsPath, loadCredentials, maskApiKey, saveCredentials, } from "./config.js";
|
|
6
11
|
import { DataikuError, } from "./errors.js";
|
|
12
|
+
import { AGENTS, detectAgents, installSkill, } from "./skill.js";
|
|
7
13
|
// ---------------------------------------------------------------------------
|
|
8
14
|
// Utility helpers
|
|
9
15
|
// ---------------------------------------------------------------------------
|
|
16
|
+
const CLI_VERSION = (() => {
|
|
17
|
+
try {
|
|
18
|
+
const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
19
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return "unknown";
|
|
23
|
+
}
|
|
24
|
+
})();
|
|
10
25
|
function num(v) {
|
|
11
26
|
if (typeof v !== "string")
|
|
12
27
|
return undefined;
|
|
@@ -18,6 +33,122 @@ function json(v) {
|
|
|
18
33
|
return undefined;
|
|
19
34
|
return JSON.parse(v);
|
|
20
35
|
}
|
|
36
|
+
function jsonInput(flags) {
|
|
37
|
+
if (flags["stdin"] === true) {
|
|
38
|
+
return JSON.parse(readFileSync(0, "utf-8"));
|
|
39
|
+
}
|
|
40
|
+
if (typeof flags["data-file"] === "string") {
|
|
41
|
+
return JSON.parse(readFileSync(flags["data-file"], "utf-8"));
|
|
42
|
+
}
|
|
43
|
+
if (typeof flags["data"] === "string") {
|
|
44
|
+
return JSON.parse(flags["data"]);
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
async function resolveFolderId(client, nameOrId, flags) {
|
|
49
|
+
return client.folders.resolveId(nameOrId, flags["project-key"]);
|
|
50
|
+
}
|
|
51
|
+
function formatLineDiff(remoteName, localPath, remoteContent, localContent) {
|
|
52
|
+
if (localContent === remoteContent) {
|
|
53
|
+
return "No differences.";
|
|
54
|
+
}
|
|
55
|
+
const localLines = localContent.split("\n");
|
|
56
|
+
const remoteLines = remoteContent.split("\n");
|
|
57
|
+
const lines = [`--- remote:${remoteName}`, `+++ local:${localPath}`, "",];
|
|
58
|
+
const maxLen = Math.max(localLines.length, remoteLines.length);
|
|
59
|
+
for (let i = 0; i < maxLen; i++) {
|
|
60
|
+
const remoteLine = remoteLines[i];
|
|
61
|
+
const localLine = localLines[i];
|
|
62
|
+
if (remoteLine === localLine)
|
|
63
|
+
continue;
|
|
64
|
+
if (remoteLine !== undefined && localLine !== undefined) {
|
|
65
|
+
lines.push(`@@ line ${String(i + 1)} @@`);
|
|
66
|
+
lines.push(`- ${remoteLine}`);
|
|
67
|
+
lines.push(`+ ${localLine}`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (remoteLine !== undefined) {
|
|
71
|
+
lines.push(`- ${remoteLine}`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
lines.push(`+ ${localLine}`);
|
|
75
|
+
}
|
|
76
|
+
return lines.join("\n");
|
|
77
|
+
}
|
|
78
|
+
function parseOutputFormat(v) {
|
|
79
|
+
if (v === undefined)
|
|
80
|
+
return "json";
|
|
81
|
+
if (v === "json" || v === "quiet" || v === "table" || v === "tsv")
|
|
82
|
+
return v;
|
|
83
|
+
throw new UsageError(`Invalid --format value: ${String(v)}. Use json, tsv, table, or quiet.`);
|
|
84
|
+
}
|
|
85
|
+
function writeTable(items) {
|
|
86
|
+
if (items.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
const keys = Object.keys(items[0]);
|
|
89
|
+
const maxWidths = keys.map((k) => {
|
|
90
|
+
const values = items.map((item) => String(item[k] ?? ""));
|
|
91
|
+
return Math.min(40, Math.max(k.length, ...values.map((v) => v.length)));
|
|
92
|
+
});
|
|
93
|
+
process.stdout.write(`${keys.map((k, i) => k.padEnd(maxWidths[i])).join(" ")}\n`);
|
|
94
|
+
process.stdout.write(`${maxWidths.map((w) => "-".repeat(w)).join(" ")}\n`);
|
|
95
|
+
for (const item of items) {
|
|
96
|
+
const row = keys.map((k, i) => {
|
|
97
|
+
const val = String(item[k] ?? "");
|
|
98
|
+
return (val.length > maxWidths[i]
|
|
99
|
+
? `${val.slice(0, maxWidths[i] - 1)}\u2026`
|
|
100
|
+
: val).padEnd(maxWidths[i]);
|
|
101
|
+
});
|
|
102
|
+
process.stdout.write(`${row.join(" ")}\n`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function writeCommandResult(result, format) {
|
|
106
|
+
if (result === undefined || result === null) {
|
|
107
|
+
if (format !== "quiet") {
|
|
108
|
+
process.stdout.write(`${JSON.stringify({ ok: true, }, null, 2)}\n`);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (typeof result === "string") {
|
|
113
|
+
if (format !== "quiet") {
|
|
114
|
+
process.stdout.write(result);
|
|
115
|
+
if (!result.endsWith("\n"))
|
|
116
|
+
process.stdout.write("\n");
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (format === "quiet")
|
|
121
|
+
return;
|
|
122
|
+
const isArrayOfObjects = Array.isArray(result)
|
|
123
|
+
&& result.every((item) => item !== null && typeof item === "object" && !Array.isArray(item));
|
|
124
|
+
if (format === "tsv" && isArrayOfObjects) {
|
|
125
|
+
const items = result;
|
|
126
|
+
if (items.length === 0)
|
|
127
|
+
return;
|
|
128
|
+
const keys = Object.keys(items[0]);
|
|
129
|
+
process.stdout.write(`${keys.join("\t")}\n`);
|
|
130
|
+
for (const item of items) {
|
|
131
|
+
process.stdout.write(`${keys.map((key) => String(item[key] ?? "")).join("\t")}\n`);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (format === "table" && isArrayOfObjects) {
|
|
136
|
+
writeTable(result);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
140
|
+
}
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Arg parsing
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
const BOOLEAN_FLAGS = new Set(["help", "verbose", "version", "stdin", "global", "list-agents",]);
|
|
145
|
+
const SHORT_FLAGS = {
|
|
146
|
+
h: "help",
|
|
147
|
+
v: "verbose",
|
|
148
|
+
V: "version",
|
|
149
|
+
f: "format",
|
|
150
|
+
o: "output",
|
|
151
|
+
};
|
|
21
152
|
function parseArgs(argv) {
|
|
22
153
|
const positional = [];
|
|
23
154
|
const flags = {};
|
|
@@ -34,16 +165,43 @@ function parseArgs(argv) {
|
|
|
34
165
|
flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
|
|
35
166
|
}
|
|
36
167
|
else {
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
39
|
-
flags[
|
|
40
|
-
i++;
|
|
168
|
+
const flagName = arg.slice(2);
|
|
169
|
+
if (BOOLEAN_FLAGS.has(flagName)) {
|
|
170
|
+
flags[flagName] = true;
|
|
41
171
|
}
|
|
42
172
|
else {
|
|
43
|
-
|
|
173
|
+
const next = argv[i + 1];
|
|
174
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
175
|
+
flags[flagName] = next;
|
|
176
|
+
i++;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
flags[flagName] = true;
|
|
180
|
+
}
|
|
44
181
|
}
|
|
45
182
|
}
|
|
46
183
|
}
|
|
184
|
+
else if (arg.length === 2 && arg[0] === "-" && arg[1] !== "-") {
|
|
185
|
+
const long = SHORT_FLAGS[arg[1]];
|
|
186
|
+
if (long) {
|
|
187
|
+
if (BOOLEAN_FLAGS.has(long)) {
|
|
188
|
+
flags[long] = true;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const next = argv[i + 1];
|
|
192
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
193
|
+
flags[long] = next;
|
|
194
|
+
i++;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
flags[long] = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
positional.push(arg);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
47
205
|
else {
|
|
48
206
|
positional.push(arg);
|
|
49
207
|
}
|
|
@@ -142,14 +300,14 @@ const commands = {
|
|
|
142
300
|
},
|
|
143
301
|
update: {
|
|
144
302
|
handler: (c, a, f) => {
|
|
145
|
-
requireArgs(a, 1, "dss dataset update <name> --data '{...}'");
|
|
146
|
-
const data =
|
|
303
|
+
requireArgs(a, 1, "dss dataset update <name> [--data '{...}' | --data-file PATH | --stdin]");
|
|
304
|
+
const data = jsonInput(f);
|
|
147
305
|
if (!data) {
|
|
148
|
-
throw new UsageError("--data is required. Usage: dss dataset update <name> --data '{...}'");
|
|
306
|
+
throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss dataset update <name> [--data '{...}' | --data-file PATH | --stdin]");
|
|
149
307
|
}
|
|
150
308
|
return c.datasets.update(a[0], data, f["project-key"]);
|
|
151
309
|
},
|
|
152
|
-
usage: "dss dataset update <name> --data '{...}' [--project-key KEY]",
|
|
310
|
+
usage: "dss dataset update <name> [--data '{...}' | --data-file PATH | --stdin] [--project-key KEY]",
|
|
153
311
|
},
|
|
154
312
|
},
|
|
155
313
|
recipe: {
|
|
@@ -178,37 +336,98 @@ const commands = {
|
|
|
178
336
|
requireArgs(a, 1, "dss recipe download <name>");
|
|
179
337
|
return c.recipes.download(a[0], {
|
|
180
338
|
outputPath: f["output"],
|
|
339
|
+
projectKey: f["project-key"],
|
|
181
340
|
});
|
|
182
341
|
},
|
|
183
|
-
usage: "dss recipe download <name> [--output PATH]",
|
|
342
|
+
usage: "dss recipe download <name> [--output PATH] [--project-key KEY]",
|
|
343
|
+
},
|
|
344
|
+
"download-code": {
|
|
345
|
+
handler: (c, a, f) => {
|
|
346
|
+
requireArgs(a, 1, "dss recipe download-code <name>");
|
|
347
|
+
return c.recipes.downloadCode(a[0], {
|
|
348
|
+
outputPath: f["output"],
|
|
349
|
+
projectKey: f["project-key"],
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
usage: "dss recipe download-code <name> [--output PATH] [--project-key KEY]",
|
|
184
353
|
},
|
|
185
354
|
create: {
|
|
186
355
|
handler: (c, _a, f) => {
|
|
187
356
|
const type = f["type"];
|
|
188
357
|
if (!type) {
|
|
189
|
-
throw new UsageError("--type is required. Usage: dss recipe create --type TYPE --input DS");
|
|
358
|
+
throw new UsageError("--type is required. Usage: dss recipe create --type TYPE --input DS --output DS");
|
|
359
|
+
}
|
|
360
|
+
const outputDataset = f["output"];
|
|
361
|
+
if (!outputDataset) {
|
|
362
|
+
throw new UsageError("--output is required. Usage: dss recipe create --type TYPE --input DS --output DS");
|
|
190
363
|
}
|
|
191
364
|
return c.recipes.create({
|
|
192
365
|
type,
|
|
193
366
|
name: f["name"],
|
|
194
367
|
inputDatasets: f["input"] ? [f["input"],] : undefined,
|
|
195
|
-
outputDataset
|
|
368
|
+
outputDataset,
|
|
196
369
|
outputConnection: f["output-connection"],
|
|
197
370
|
projectKey: f["project-key"],
|
|
198
371
|
});
|
|
199
372
|
},
|
|
200
|
-
usage: "dss recipe create --type TYPE --input DS
|
|
373
|
+
usage: "dss recipe create --type TYPE --input DS --output DS [--output-connection CONN] [--project-key KEY]",
|
|
374
|
+
},
|
|
375
|
+
diff: {
|
|
376
|
+
handler: async (c, a, f) => {
|
|
377
|
+
requireArgs(a, 1, "dss recipe diff <name> --file PATH");
|
|
378
|
+
const filePath = f["file"];
|
|
379
|
+
if (!filePath) {
|
|
380
|
+
throw new UsageError("--file is required. Usage: dss recipe diff <name> --file PATH");
|
|
381
|
+
}
|
|
382
|
+
const result = await c.recipes.get(a[0], {
|
|
383
|
+
includePayload: true,
|
|
384
|
+
projectKey: f["project-key"],
|
|
385
|
+
});
|
|
386
|
+
if (!result.payload) {
|
|
387
|
+
throw new Error(`Recipe "${a[0]}" has no code payload to diff.`);
|
|
388
|
+
}
|
|
389
|
+
const localContent = readFileSync(filePath, "utf-8");
|
|
390
|
+
return formatLineDiff(a[0], filePath, result.payload, localContent);
|
|
391
|
+
},
|
|
392
|
+
usage: "dss recipe diff <name> --file PATH [--project-key KEY]",
|
|
201
393
|
},
|
|
202
394
|
update: {
|
|
203
395
|
handler: (c, a, f) => {
|
|
204
|
-
requireArgs(a, 1, "dss recipe update <name> --data '{...}'");
|
|
205
|
-
const data =
|
|
396
|
+
requireArgs(a, 1, "dss recipe update <name> [--data '{...}' | --data-file PATH | --stdin]");
|
|
397
|
+
const data = jsonInput(f);
|
|
206
398
|
if (!data) {
|
|
207
|
-
throw new UsageError("--data is required. Usage: dss recipe update <name> --data '{...}'");
|
|
399
|
+
throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss recipe update <name> [--data '{...}' | --data-file PATH | --stdin]");
|
|
208
400
|
}
|
|
209
401
|
return c.recipes.update(a[0], data, f["project-key"]);
|
|
210
402
|
},
|
|
211
|
-
usage: "dss recipe update <name> --data '{...}' [--project-key KEY]",
|
|
403
|
+
usage: "dss recipe update <name> [--data '{...}' | --data-file PATH | --stdin] [--project-key KEY]",
|
|
404
|
+
},
|
|
405
|
+
"get-payload": {
|
|
406
|
+
handler: async (c, a, f) => {
|
|
407
|
+
requireArgs(a, 1, "dss recipe get-payload <name>");
|
|
408
|
+
const payload = await c.recipes.getPayload(a[0], {
|
|
409
|
+
projectKey: f["project-key"],
|
|
410
|
+
});
|
|
411
|
+
if (typeof f["output"] === "string") {
|
|
412
|
+
await writeFile(f["output"], payload, "utf-8");
|
|
413
|
+
return f["output"];
|
|
414
|
+
}
|
|
415
|
+
return payload;
|
|
416
|
+
},
|
|
417
|
+
usage: "dss recipe get-payload <name> [--output PATH] [--project-key KEY]",
|
|
418
|
+
},
|
|
419
|
+
"set-payload": {
|
|
420
|
+
handler: async (c, a, f) => {
|
|
421
|
+
requireArgs(a, 1, "dss recipe set-payload <name> --file PATH");
|
|
422
|
+
const filePath = f["file"];
|
|
423
|
+
if (!filePath)
|
|
424
|
+
throw new UsageError("--file is required.");
|
|
425
|
+
const content = readFileSync(filePath, "utf-8");
|
|
426
|
+
await c.recipes.setPayload(a[0], content, {
|
|
427
|
+
projectKey: f["project-key"],
|
|
428
|
+
});
|
|
429
|
+
},
|
|
430
|
+
usage: "dss recipe set-payload <name> --file PATH [--project-key KEY]",
|
|
212
431
|
},
|
|
213
432
|
},
|
|
214
433
|
job: {
|
|
@@ -316,14 +535,14 @@ const commands = {
|
|
|
316
535
|
},
|
|
317
536
|
update: {
|
|
318
537
|
handler: (c, a, f) => {
|
|
319
|
-
requireArgs(a, 1, "dss scenario update <id> --data '{...}'");
|
|
320
|
-
const data =
|
|
538
|
+
requireArgs(a, 1, "dss scenario update <id> [--data '{...}' | --data-file PATH | --stdin]");
|
|
539
|
+
const data = jsonInput(f);
|
|
321
540
|
if (!data) {
|
|
322
|
-
throw new UsageError("--data is required. Usage: dss scenario update <id> --data '{...}'");
|
|
541
|
+
throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss scenario update <id> [--data '{...}' | --data-file PATH | --stdin]");
|
|
323
542
|
}
|
|
324
543
|
return c.scenarios.update(a[0], data, f["project-key"]);
|
|
325
544
|
},
|
|
326
|
-
usage: "dss scenario update <id> --data '{...}' [--project-key KEY]",
|
|
545
|
+
usage: "dss scenario update <id> [--data '{...}' | --data-file PATH | --stdin] [--project-key KEY]",
|
|
327
546
|
},
|
|
328
547
|
},
|
|
329
548
|
folder: {
|
|
@@ -332,41 +551,44 @@ const commands = {
|
|
|
332
551
|
usage: "dss folder list [--project-key KEY]",
|
|
333
552
|
},
|
|
334
553
|
get: {
|
|
335
|
-
handler: (c, a, f) => {
|
|
336
|
-
requireArgs(a, 1, "dss folder get <id>");
|
|
337
|
-
return c.folders.get(a[0], f["project-key"]);
|
|
554
|
+
handler: async (c, a, f) => {
|
|
555
|
+
requireArgs(a, 1, "dss folder get <name-or-id>");
|
|
556
|
+
return c.folders.get(await resolveFolderId(c, a[0], f), f["project-key"]);
|
|
338
557
|
},
|
|
339
|
-
usage: "dss folder get <id> [--project-key KEY]",
|
|
558
|
+
usage: "dss folder get <name-or-id> [--project-key KEY]",
|
|
340
559
|
},
|
|
341
560
|
contents: {
|
|
342
|
-
handler: (c, a,
|
|
343
|
-
requireArgs(a, 1, "dss folder contents <id>");
|
|
344
|
-
return c.folders.contents(a[0])
|
|
561
|
+
handler: async (c, a, f) => {
|
|
562
|
+
requireArgs(a, 1, "dss folder contents <name-or-id>");
|
|
563
|
+
return c.folders.contents(await resolveFolderId(c, a[0], f), {
|
|
564
|
+
projectKey: f["project-key"],
|
|
565
|
+
});
|
|
345
566
|
},
|
|
346
|
-
usage: "dss folder contents <id>",
|
|
567
|
+
usage: "dss folder contents <name-or-id> [--project-key KEY]",
|
|
347
568
|
},
|
|
348
569
|
download: {
|
|
349
|
-
handler: (c, a, f) => {
|
|
350
|
-
requireArgs(a, 2, "dss folder download <id> <path>");
|
|
351
|
-
return c.folders.download(a[0], a[1], {
|
|
570
|
+
handler: async (c, a, f) => {
|
|
571
|
+
requireArgs(a, 2, "dss folder download <name-or-id> <path>");
|
|
572
|
+
return c.folders.download(await resolveFolderId(c, a[0], f), a[1], {
|
|
352
573
|
localPath: f["output"],
|
|
574
|
+
projectKey: f["project-key"],
|
|
353
575
|
});
|
|
354
576
|
},
|
|
355
|
-
usage: "dss folder download <id> <path> [--output PATH]",
|
|
577
|
+
usage: "dss folder download <name-or-id> <path> [--output PATH] [--project-key KEY]",
|
|
356
578
|
},
|
|
357
579
|
upload: {
|
|
358
|
-
handler: (c, a, f) => {
|
|
359
|
-
requireArgs(a, 3, "dss folder upload <id> <path> <localPath>");
|
|
360
|
-
return c.folders.upload(a[0], a[1], a[2], f["project-key"]);
|
|
580
|
+
handler: async (c, a, f) => {
|
|
581
|
+
requireArgs(a, 3, "dss folder upload <name-or-id> <path> <localPath>");
|
|
582
|
+
return c.folders.upload(await resolveFolderId(c, a[0], f), a[1], a[2], f["project-key"]);
|
|
361
583
|
},
|
|
362
|
-
usage: "dss folder upload <id> <path> <localPath> [--project-key KEY]",
|
|
584
|
+
usage: "dss folder upload <name-or-id> <path> <localPath> [--project-key KEY]",
|
|
363
585
|
},
|
|
364
586
|
"delete-file": {
|
|
365
|
-
handler: (c, a, f) => {
|
|
366
|
-
requireArgs(a, 2, "dss folder delete-file <id> <path>");
|
|
367
|
-
return c.folders.deleteFile(a[0], a[1], f["project-key"]);
|
|
587
|
+
handler: async (c, a, f) => {
|
|
588
|
+
requireArgs(a, 2, "dss folder delete-file <name-or-id> <path>");
|
|
589
|
+
return c.folders.deleteFile(await resolveFolderId(c, a[0], f), a[1], f["project-key"]);
|
|
368
590
|
},
|
|
369
|
-
usage: "dss folder delete-file <id> <path> [--project-key KEY]",
|
|
591
|
+
usage: "dss folder delete-file <name-or-id> <path> [--project-key KEY]",
|
|
370
592
|
},
|
|
371
593
|
},
|
|
372
594
|
variable: {
|
|
@@ -378,8 +600,10 @@ const commands = {
|
|
|
378
600
|
handler: (c, _a, f) => c.variables.set({
|
|
379
601
|
standard: json(f["standard"]),
|
|
380
602
|
local: json(f["local"]),
|
|
603
|
+
replace: f["replace"] === true,
|
|
604
|
+
projectKey: f["project-key"],
|
|
381
605
|
}),
|
|
382
|
-
usage: 'dss variable set --standard \'{"k":"v"}\' --local \'{"k":"v"}\'',
|
|
606
|
+
usage: 'dss variable set --standard \'{"k":"v"}\' --local \'{"k":"v"}\' [--replace] [--project-key KEY]',
|
|
383
607
|
},
|
|
384
608
|
},
|
|
385
609
|
connection: {
|
|
@@ -492,23 +716,25 @@ const commands = {
|
|
|
492
716
|
},
|
|
493
717
|
"save-jupyter": {
|
|
494
718
|
handler: (c, a, f) => {
|
|
495
|
-
requireArgs(a, 1, "dss notebook save-jupyter <name> --data '{...}'");
|
|
496
|
-
const data =
|
|
497
|
-
if (!data)
|
|
498
|
-
throw new UsageError("--data is required (notebook JSON content)");
|
|
719
|
+
requireArgs(a, 1, "dss notebook save-jupyter <name> [--data '{...}' | --data-file PATH | --stdin]");
|
|
720
|
+
const data = jsonInput(f);
|
|
721
|
+
if (!data) {
|
|
722
|
+
throw new UsageError("--data, --data-file, or --stdin is required (notebook JSON content).");
|
|
723
|
+
}
|
|
499
724
|
return c.notebooks.saveJupyter(a[0], data, f["project-key"]);
|
|
500
725
|
},
|
|
501
|
-
usage: "dss notebook save-jupyter <name> --data '{...}' [--project-key KEY]",
|
|
726
|
+
usage: "dss notebook save-jupyter <name> [--data '{...}' | --data-file PATH | --stdin] [--project-key KEY]",
|
|
502
727
|
},
|
|
503
728
|
"save-sql": {
|
|
504
729
|
handler: (c, a, f) => {
|
|
505
|
-
requireArgs(a, 1, "dss notebook save-sql <id> --data '{...}'");
|
|
506
|
-
const data =
|
|
507
|
-
if (!data)
|
|
508
|
-
throw new UsageError("--data is required (SQL notebook content JSON)");
|
|
730
|
+
requireArgs(a, 1, "dss notebook save-sql <id> [--data '{...}' | --data-file PATH | --stdin]");
|
|
731
|
+
const data = jsonInput(f);
|
|
732
|
+
if (!data) {
|
|
733
|
+
throw new UsageError("--data, --data-file, or --stdin is required (SQL notebook content JSON).");
|
|
734
|
+
}
|
|
509
735
|
return c.notebooks.saveSql(a[0], data, f["project-key"]);
|
|
510
736
|
},
|
|
511
|
-
usage: "dss notebook save-sql <id> --data '{...}' [--project-key KEY]",
|
|
737
|
+
usage: "dss notebook save-sql <id> [--data '{...}' | --data-file PATH | --stdin] [--project-key KEY]",
|
|
512
738
|
},
|
|
513
739
|
"clear-sql-history": {
|
|
514
740
|
handler: (c, a, f) => {
|
|
@@ -526,19 +752,35 @@ const commands = {
|
|
|
526
752
|
// ---------------------------------------------------------------------------
|
|
527
753
|
// Help
|
|
528
754
|
// ---------------------------------------------------------------------------
|
|
529
|
-
const RESOURCE_NAMES = Object.keys(commands).sort();
|
|
755
|
+
const RESOURCE_NAMES = [...Object.keys(commands), "auth", "install-skill",].sort();
|
|
530
756
|
function printTopLevelHelp() {
|
|
531
757
|
const lines = [
|
|
532
758
|
"Usage: dss <resource> <action> [args...] [--flags]",
|
|
533
759
|
"",
|
|
534
760
|
"Global flags:",
|
|
535
|
-
" --
|
|
536
|
-
"
|
|
537
|
-
"
|
|
538
|
-
" --
|
|
761
|
+
" -h, --help Show help",
|
|
762
|
+
" -v, --verbose Log HTTP requests to stderr",
|
|
763
|
+
" -V, --version Show version",
|
|
764
|
+
" -f, --format FORMAT Output format: json|tsv|table|quiet",
|
|
765
|
+
" -o, --output PATH Write output to file (recipe get-payload)",
|
|
766
|
+
" --url URL Dataiku DSS base URL (env: DATAIKU_URL)",
|
|
767
|
+
" --api-key KEY API key (env: DATAIKU_API_KEY)",
|
|
768
|
+
" --project-key KEY Default project key (env: DATAIKU_PROJECT_KEY)",
|
|
769
|
+
" --timeout MS Request timeout in ms (default: 30000)",
|
|
539
770
|
"",
|
|
540
771
|
"Resources:",
|
|
541
772
|
...RESOURCE_NAMES.map((r) => ` ${r}`),
|
|
773
|
+
"",
|
|
774
|
+
"Quick start:",
|
|
775
|
+
" dss auth login Save DSS credentials",
|
|
776
|
+
" dss auth status Verify connection",
|
|
777
|
+
" dss project list List accessible projects",
|
|
778
|
+
" dss dataset list List datasets in default project",
|
|
779
|
+
" dss dataset preview <name> Preview dataset rows as CSV",
|
|
780
|
+
" dss recipe get-payload <name> Print recipe code to stdout",
|
|
781
|
+
" dss recipe download-code <name> Download recipe code to a file",
|
|
782
|
+
" dss job log <id> View job log output",
|
|
783
|
+
" dss install-skill Install agent skill for coding agents",
|
|
542
784
|
];
|
|
543
785
|
process.stderr.write(`${lines.join("\n")}\n`);
|
|
544
786
|
}
|
|
@@ -604,11 +846,133 @@ function loadEnvFile() {
|
|
|
604
846
|
}
|
|
605
847
|
}
|
|
606
848
|
// ---------------------------------------------------------------------------
|
|
849
|
+
// Auth commands (run before client creation)
|
|
850
|
+
// ---------------------------------------------------------------------------
|
|
851
|
+
const AUTH_ACTIONS = {
|
|
852
|
+
login: {
|
|
853
|
+
handler: async (flags) => {
|
|
854
|
+
let { url, apiKey, projectKey, } = resolveCredentials(flags);
|
|
855
|
+
if (!url || !apiKey) {
|
|
856
|
+
if (!process.stdin.isTTY) {
|
|
857
|
+
throw new UsageError("Missing --url and/or --api-key. Provide them as flags or run interactively.");
|
|
858
|
+
}
|
|
859
|
+
if (!url)
|
|
860
|
+
url = await promptLine("DSS URL: ");
|
|
861
|
+
if (!apiKey)
|
|
862
|
+
apiKey = await promptSecret("API key: ");
|
|
863
|
+
if (!projectKey)
|
|
864
|
+
projectKey = (await promptLine("Project key (optional): ")) || undefined;
|
|
865
|
+
}
|
|
866
|
+
if (!url)
|
|
867
|
+
throw new UsageError("URL is required.");
|
|
868
|
+
if (!apiKey)
|
|
869
|
+
throw new UsageError("API key is required.");
|
|
870
|
+
process.stderr.write("Validating credentials... ");
|
|
871
|
+
const result = await validateCredentials(url, apiKey);
|
|
872
|
+
if (!result.valid) {
|
|
873
|
+
process.stderr.write(`✗ Failed\n`);
|
|
874
|
+
throw new DataikuError(0, "Authentication Failed", result.error ?? "Credential validation failed");
|
|
875
|
+
}
|
|
876
|
+
process.stderr.write("\u2713 Connected\n");
|
|
877
|
+
saveCredentials({ url, apiKey, projectKey, });
|
|
878
|
+
process.stderr.write(`Credentials saved to ${getCredentialsPath()}\n`);
|
|
879
|
+
},
|
|
880
|
+
usage: "dss auth login [--url URL] [--api-key KEY] [--project-key KEY]",
|
|
881
|
+
},
|
|
882
|
+
status: {
|
|
883
|
+
handler: async (_flags) => {
|
|
884
|
+
const creds = loadCredentials();
|
|
885
|
+
if (!creds) {
|
|
886
|
+
process.stderr.write("No saved credentials. Run: dss auth login\n");
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
const lines = [
|
|
890
|
+
`URL: ${creds.url}`,
|
|
891
|
+
`API key: ${maskApiKey(creds.apiKey)}`,
|
|
892
|
+
`Project key: ${creds.projectKey ?? "(not set)"}`,
|
|
893
|
+
];
|
|
894
|
+
for (const line of lines)
|
|
895
|
+
process.stderr.write(`${line}\n`);
|
|
896
|
+
const result = await validateCredentials(creds.url, creds.apiKey);
|
|
897
|
+
if (result.valid) {
|
|
898
|
+
process.stderr.write("Connection: \u2713 Valid\n");
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
process.stderr.write(`Connection: \u2717 Failed (${result.error ?? "unknown error"})\n`);
|
|
902
|
+
}
|
|
903
|
+
process.stderr.write(`Config: ${getCredentialsPath()}\n`);
|
|
904
|
+
},
|
|
905
|
+
usage: "dss auth status",
|
|
906
|
+
},
|
|
907
|
+
logout: {
|
|
908
|
+
handler: async (_flags) => {
|
|
909
|
+
deleteCredentials();
|
|
910
|
+
process.stderr.write("Credentials removed.\n");
|
|
911
|
+
},
|
|
912
|
+
usage: "dss auth logout",
|
|
913
|
+
},
|
|
914
|
+
};
|
|
915
|
+
// ---------------------------------------------------------------------------
|
|
916
|
+
// Interactive prompts
|
|
917
|
+
// ---------------------------------------------------------------------------
|
|
918
|
+
function promptLine(label) {
|
|
919
|
+
return new Promise((res, rej) => {
|
|
920
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr, });
|
|
921
|
+
rl.on("close", () => rej(new UsageError("Input closed before a value was provided.")));
|
|
922
|
+
rl.question(label, (answer) => {
|
|
923
|
+
rl.close();
|
|
924
|
+
res(answer.trim());
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
function promptSecret(label) {
|
|
929
|
+
return new Promise((res, rej) => {
|
|
930
|
+
const muted = new Writable({
|
|
931
|
+
write(_chunk, _encoding, cb) {
|
|
932
|
+
cb();
|
|
933
|
+
},
|
|
934
|
+
});
|
|
935
|
+
const rl = createInterface({ input: process.stdin, output: muted, terminal: true, });
|
|
936
|
+
rl.on("close", () => rej(new UsageError("Input closed before a value was provided.")));
|
|
937
|
+
process.stderr.write(label);
|
|
938
|
+
rl.question("", (answer) => {
|
|
939
|
+
rl.close();
|
|
940
|
+
process.stderr.write("\n");
|
|
941
|
+
res(answer.trim());
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
// ---------------------------------------------------------------------------
|
|
946
|
+
// Credential resolution
|
|
947
|
+
// ---------------------------------------------------------------------------
|
|
948
|
+
function resolveCredentials(flags) {
|
|
949
|
+
let url = flags["url"];
|
|
950
|
+
let apiKey = flags["api-key"];
|
|
951
|
+
let projectKey = flags["project-key"];
|
|
952
|
+
url ??= process.env.DATAIKU_URL;
|
|
953
|
+
apiKey ??= process.env.DATAIKU_API_KEY;
|
|
954
|
+
projectKey ??= process.env.DATAIKU_PROJECT_KEY;
|
|
955
|
+
if (!url || !apiKey) {
|
|
956
|
+
const saved = loadCredentials();
|
|
957
|
+
if (saved) {
|
|
958
|
+
url ??= saved.url;
|
|
959
|
+
apiKey ??= saved.apiKey;
|
|
960
|
+
projectKey ??= saved.projectKey;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return { url: url ?? "", apiKey: apiKey ?? "", projectKey, };
|
|
964
|
+
}
|
|
965
|
+
// ---------------------------------------------------------------------------
|
|
607
966
|
// Main
|
|
608
967
|
// ---------------------------------------------------------------------------
|
|
609
968
|
async function main() {
|
|
610
969
|
loadEnvFile();
|
|
611
970
|
const { positional, flags, } = parseArgs(process.argv.slice(2));
|
|
971
|
+
// --version
|
|
972
|
+
if (flags["version"] === true) {
|
|
973
|
+
process.stdout.write(`${CLI_VERSION}\n`);
|
|
974
|
+
process.exit(0);
|
|
975
|
+
}
|
|
612
976
|
// Top-level help
|
|
613
977
|
if (positional.length === 0 || (positional.length === 0 && flags["help"])) {
|
|
614
978
|
printTopLevelHelp();
|
|
@@ -617,13 +981,91 @@ async function main() {
|
|
|
617
981
|
process.exit(1);
|
|
618
982
|
}
|
|
619
983
|
const resource = positional[0];
|
|
984
|
+
// Auth commands — dispatched before client creation
|
|
985
|
+
if (resource === "auth") {
|
|
986
|
+
const action = positional[1];
|
|
987
|
+
if (!action || flags["help"] === true) {
|
|
988
|
+
const lines = [
|
|
989
|
+
"Usage: dss auth <action> [--flags]",
|
|
990
|
+
"",
|
|
991
|
+
"Actions:",
|
|
992
|
+
...Object.entries(AUTH_ACTIONS).map(([name, meta,]) => ` ${name} \u2192 ${meta.usage}`),
|
|
993
|
+
];
|
|
994
|
+
process.stderr.write(`${lines.join("\n")}\n`);
|
|
995
|
+
process.exit(flags["help"] === true ? 0 : 1);
|
|
996
|
+
}
|
|
997
|
+
const authMeta = AUTH_ACTIONS[action];
|
|
998
|
+
if (!authMeta) {
|
|
999
|
+
process.stderr.write(`Unknown action: auth ${action}\nAvailable: ${Object.keys(AUTH_ACTIONS).join(", ")}\n`);
|
|
1000
|
+
process.exit(1);
|
|
1001
|
+
}
|
|
1002
|
+
await authMeta.handler(flags);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
// install-skill — dispatched before client creation
|
|
1006
|
+
if (resource === "install-skill") {
|
|
1007
|
+
if (flags["help"] === true) {
|
|
1008
|
+
const lines = [
|
|
1009
|
+
"Usage: dss install-skill [--global] [--agent NAME] [--list-agents]",
|
|
1010
|
+
"",
|
|
1011
|
+
"Install the dataiku-dss agent skill for detected coding agents.",
|
|
1012
|
+
"",
|
|
1013
|
+
"Flags:",
|
|
1014
|
+
" --global Install to user-level global scope (default: project)",
|
|
1015
|
+
" --agent NAME Target a specific agent: claude, codex, cursor, pi, omp",
|
|
1016
|
+
" --list-agents Print detected agents and exit",
|
|
1017
|
+
];
|
|
1018
|
+
process.stderr.write(`${lines.join("\n")}\n`);
|
|
1019
|
+
process.exit(0);
|
|
1020
|
+
}
|
|
1021
|
+
const listOnly = flags["list-agents"] === true;
|
|
1022
|
+
const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
|
|
1023
|
+
const isGlobal = flags["global"] === true;
|
|
1024
|
+
// Resolve target agents
|
|
1025
|
+
let targets;
|
|
1026
|
+
if (agentFilter) {
|
|
1027
|
+
const def = AGENTS[agentFilter];
|
|
1028
|
+
if (!def) {
|
|
1029
|
+
throw new UsageError(`Unknown agent: ${agentFilter}. Available: ${Object.keys(AGENTS).join(", ")}`);
|
|
1030
|
+
}
|
|
1031
|
+
targets = [{ id: agentFilter, def, via: "flag", },];
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
targets = detectAgents();
|
|
1035
|
+
}
|
|
1036
|
+
if (listOnly) {
|
|
1037
|
+
if (targets.length === 0) {
|
|
1038
|
+
process.stderr.write("No coding agents detected.\n");
|
|
1039
|
+
}
|
|
1040
|
+
else {
|
|
1041
|
+
process.stderr.write("Detected agents:\n");
|
|
1042
|
+
for (const t of targets) {
|
|
1043
|
+
process.stderr.write(` ${t.id} (${t.def.name}, via ${t.via})\n`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
process.exit(0);
|
|
1047
|
+
}
|
|
1048
|
+
if (targets.length === 0) {
|
|
1049
|
+
throw new UsageError("No coding agents detected. Install one (claude, codex, cursor, pi, omp) or use --agent NAME.");
|
|
1050
|
+
}
|
|
1051
|
+
const scope = isGlobal ? "global" : "project";
|
|
1052
|
+
process.stderr.write(`Installing dataiku-dss skill (${scope} scope):\n`);
|
|
1053
|
+
const results = installSkill(targets, { global: isGlobal, cwd: process.cwd(), });
|
|
1054
|
+
for (const r of results) {
|
|
1055
|
+
process.stderr.write(` ${r.agent} \u2192 ${r.path}\n`);
|
|
1056
|
+
}
|
|
1057
|
+
if (results.length > 0) {
|
|
1058
|
+
process.stderr.write(`\nDone. ${results.length} skill(s) installed.\n`);
|
|
1059
|
+
}
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
620
1062
|
// Unknown resource
|
|
621
1063
|
if (!commands[resource]) {
|
|
622
1064
|
if (flags["help"]) {
|
|
623
1065
|
printTopLevelHelp();
|
|
624
1066
|
process.exit(0);
|
|
625
1067
|
}
|
|
626
|
-
process.stderr.write(`Unknown resource: ${resource}\nAvailable: ${RESOURCE_NAMES.join(", ")}\n`);
|
|
1068
|
+
process.stderr.write(`Unknown resource: ${resource} \nAvailable: ${RESOURCE_NAMES.join(", ")} \n`);
|
|
627
1069
|
process.exit(1);
|
|
628
1070
|
}
|
|
629
1071
|
// Resource-level help
|
|
@@ -639,7 +1081,7 @@ async function main() {
|
|
|
639
1081
|
const actionMeta = commands[resource][action];
|
|
640
1082
|
// Unknown action
|
|
641
1083
|
if (!actionMeta) {
|
|
642
|
-
process.stderr.write(`Unknown action: ${resource} ${action}\nAvailable actions for ${resource}: ${Object.keys(commands[resource]).join(", ")}\n`);
|
|
1084
|
+
process.stderr.write(`Unknown action: ${resource} ${action} \nAvailable actions for ${resource}: ${Object.keys(commands[resource]).join(", ")} \n`);
|
|
643
1085
|
process.exit(1);
|
|
644
1086
|
}
|
|
645
1087
|
// Action-level help
|
|
@@ -647,29 +1089,30 @@ async function main() {
|
|
|
647
1089
|
printActionHelp(resource, action);
|
|
648
1090
|
process.exit(0);
|
|
649
1091
|
}
|
|
650
|
-
//
|
|
651
|
-
const url
|
|
652
|
-
const apiKey = flags["api-key"] ?? process.env.DATAIKU_API_KEY ?? "";
|
|
1092
|
+
// Resolve credentials: flags > env > saved > .env
|
|
1093
|
+
const { url, apiKey, projectKey, } = resolveCredentials(flags);
|
|
653
1094
|
if (!url) {
|
|
654
|
-
|
|
655
|
-
process.exit(1);
|
|
1095
|
+
throw new UsageError("Missing Dataiku URL. Set DATAIKU_URL, pass --url, or run: dss auth login");
|
|
656
1096
|
}
|
|
657
1097
|
if (!apiKey) {
|
|
658
|
-
|
|
659
|
-
process.exit(1);
|
|
1098
|
+
throw new UsageError("Missing API key. Set DATAIKU_API_KEY, pass --api-key, or run: dss auth login");
|
|
660
1099
|
}
|
|
1100
|
+
const requestTimeoutMs = num(flags["timeout"]) ?? undefined;
|
|
661
1101
|
const client = new DataikuClient({
|
|
662
1102
|
url,
|
|
663
1103
|
apiKey,
|
|
664
|
-
projectKey
|
|
1104
|
+
projectKey,
|
|
1105
|
+
verbose: flags["verbose"] === true,
|
|
1106
|
+
requestTimeoutMs,
|
|
665
1107
|
});
|
|
666
1108
|
const args = positional.slice(2);
|
|
1109
|
+
const format = parseOutputFormat(flags["format"]);
|
|
667
1110
|
const result = await actionMeta.handler(client, args, flags);
|
|
668
|
-
|
|
1111
|
+
writeCommandResult(result, format);
|
|
669
1112
|
}
|
|
670
1113
|
main().catch((err) => {
|
|
671
1114
|
if (err instanceof UsageError) {
|
|
672
|
-
process.stderr.write(`${err.message}\n`);
|
|
1115
|
+
process.stderr.write(`${err.message} \n`);
|
|
673
1116
|
process.exit(1);
|
|
674
1117
|
}
|
|
675
1118
|
if (err instanceof DataikuError) {
|
|
@@ -680,10 +1123,10 @@ main().catch((err) => {
|
|
|
680
1123
|
};
|
|
681
1124
|
if (err.retryHint)
|
|
682
1125
|
payload.retryHint = err.retryHint;
|
|
683
|
-
process.stderr.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
684
|
-
process.exit(
|
|
1126
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)} \n`);
|
|
1127
|
+
process.exit(err.category === "transient" ? 3 : 2);
|
|
685
1128
|
}
|
|
686
1129
|
const message = err instanceof Error ? err.message : String(err);
|
|
687
|
-
process.stderr.write(`${JSON.stringify({ error: message, }, null, 2)}\n`);
|
|
1130
|
+
process.stderr.write(`${JSON.stringify({ error: message, }, null, 2)} \n`);
|
|
688
1131
|
process.exit(1);
|
|
689
1132
|
});
|