dataiku-sdk 0.6.2 → 0.7.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/README.md +85 -0
- package/bin/dss.js +39 -13
- package/dist/src/cli.js +800 -504
- package/dist/src/client.d.ts +18 -17
- package/dist/src/client.js +49 -36
- package/dist/src/config.d.ts +0 -2
- package/dist/src/config.js +1 -16
- package/dist/src/errors.d.ts +2 -1
- package/dist/src/errors.js +3 -1
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.js +2 -2
- package/dist/src/resources/datasets.d.ts +21 -7
- package/dist/src/resources/datasets.js +62 -69
- package/dist/src/resources/jobs.d.ts +1 -0
- package/dist/src/resources/jobs.js +1 -1
- package/dist/src/resources/recipes.js +20 -4
- package/dist/src/resources/scenarios.d.ts +24 -0
- package/dist/src/resources/scenarios.js +161 -0
- package/dist/src/skill.d.ts +5 -0
- package/dist/src/skill.js +93 -100
- package/dist/src/utils/cleanup-ledger.js +22 -1
- package/package.json +2 -1
package/dist/src/cli.js
CHANGED
|
@@ -3,17 +3,15 @@ import { createHash, } from "node:crypto";
|
|
|
3
3
|
import { readFileSync, } from "node:fs";
|
|
4
4
|
import { mkdir, writeFile, } from "node:fs/promises";
|
|
5
5
|
import { dirname, join, resolve, } from "node:path";
|
|
6
|
-
import { createInterface, } from "node:readline";
|
|
7
|
-
import { Writable, } from "node:stream";
|
|
8
6
|
import { fileURLToPath, } from "node:url";
|
|
9
7
|
import { validateCredentials, } from "./auth.js";
|
|
10
8
|
import { DataikuClient, } from "./client.js";
|
|
11
|
-
import {
|
|
9
|
+
import { getCredentialsPath, loadCredentials, saveCredentials, } from "./config.js";
|
|
12
10
|
import { DataikuError, dataikuErrorCode, } from "./errors.js";
|
|
13
11
|
import { buildDatasetCloneSettings, } from "./resources/datasets.js";
|
|
14
12
|
import { parseJobLogProgress, } from "./resources/jobs.js";
|
|
15
13
|
import { scenarioUpdatePreview, } from "./resources/scenarios.js";
|
|
16
|
-
import { AGENTS, detectAgents, findWorkspaceRoot, installSkill, } from "./skill.js";
|
|
14
|
+
import { AGENTS, detectAgents, findWorkspaceRoot, installSkill, planSkillInstalls, } from "./skill.js";
|
|
17
15
|
import { appendCleanupLedgerEntry, readCleanupLedger, } from "./utils/cleanup-ledger.js";
|
|
18
16
|
import { deepMerge, } from "./utils/deep-merge.js";
|
|
19
17
|
import { sanitizeFileName, } from "./utils/sanitize.js";
|
|
@@ -73,10 +71,10 @@ function gitRevision(packageRoot) {
|
|
|
73
71
|
}
|
|
74
72
|
const PACKAGE_ROOT = findPackageRoot();
|
|
75
73
|
const CLI_VERSION = packageVersion(PACKAGE_ROOT);
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
return
|
|
79
|
-
}
|
|
74
|
+
const CLI_GIT_REVISION = gitRevision(PACKAGE_ROOT);
|
|
75
|
+
function cliVersionResult() {
|
|
76
|
+
return { version: CLI_VERSION, gitRevision: CLI_GIT_REVISION ?? null, };
|
|
77
|
+
}
|
|
80
78
|
function num(v) {
|
|
81
79
|
if (typeof v !== "string")
|
|
82
80
|
return undefined;
|
|
@@ -883,7 +881,24 @@ function json(v, source = "JSON flag") {
|
|
|
883
881
|
return undefined;
|
|
884
882
|
return parseJsonObject(v, source);
|
|
885
883
|
}
|
|
886
|
-
const SQL_QUERY_USAGE = "dss sql query [SQL | --sql QUERY | --sql-file PATH | --sql - | --stdin] (--connection CONN | --dataset FULL_NAME) [--database DB] [--output PATH|--output-file PATH] [--request-timeout MS] [--project-key KEY]";
|
|
884
|
+
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]";
|
|
885
|
+
const DEFAULT_SQL_PREVIEW_ROWS = 5;
|
|
886
|
+
/**
|
|
887
|
+
* Parse `--preview N` into a non-negative row count. Rejects non-integers,
|
|
888
|
+
* negatives, and empty values loudly so a bad flag never silently degrades to a
|
|
889
|
+
* default. `--preview 0` is valid and yields an empty preview (explicit opt-out).
|
|
890
|
+
*/
|
|
891
|
+
function parseSqlPreviewCount(value) {
|
|
892
|
+
if (typeof value !== "string") {
|
|
893
|
+
throw new UsageError(`--preview requires an integer value. Usage: ${SQL_QUERY_USAGE}`, "validation_failed");
|
|
894
|
+
}
|
|
895
|
+
const trimmed = value.trim();
|
|
896
|
+
const parsed = Number(trimmed);
|
|
897
|
+
if (trimmed.length === 0 || !Number.isInteger(parsed) || parsed < 0) {
|
|
898
|
+
throw new UsageError(`--preview must be a non-negative integer (got "${value}"). Usage: ${SQL_QUERY_USAGE}`, "validation_failed");
|
|
899
|
+
}
|
|
900
|
+
return parsed;
|
|
901
|
+
}
|
|
887
902
|
function readStdinText() {
|
|
888
903
|
return readFileSync(0, "utf-8");
|
|
889
904
|
}
|
|
@@ -1049,6 +1064,30 @@ function resolveSqlInput(args, flags) {
|
|
|
1049
1064
|
}
|
|
1050
1065
|
return query;
|
|
1051
1066
|
}
|
|
1067
|
+
const CODE_RUN_USAGE = "dss code run (--file PATH | --stdin) [--env ENV] [--timeout MS] [--keep] [--full-log] [--project-key KEY]";
|
|
1068
|
+
function resolveCodeInput(args, flags) {
|
|
1069
|
+
if (args.length > 0) {
|
|
1070
|
+
throw new UsageError(`code run takes no positional arguments; pass the script via --file PATH or --stdin. Usage: ${CODE_RUN_USAGE}`);
|
|
1071
|
+
}
|
|
1072
|
+
const sources = [];
|
|
1073
|
+
if (typeof flags["file"] === "string") {
|
|
1074
|
+
sources.push({ label: "--file", read: () => readFileSync(flags["file"], "utf-8"), });
|
|
1075
|
+
}
|
|
1076
|
+
if (flags["stdin"] === true) {
|
|
1077
|
+
sources.push({ label: "--stdin", read: readStdinText, });
|
|
1078
|
+
}
|
|
1079
|
+
if (sources.length === 0) {
|
|
1080
|
+
throw new UsageError(`Python source is required: pass --file PATH or --stdin. Usage: ${CODE_RUN_USAGE}`);
|
|
1081
|
+
}
|
|
1082
|
+
if (sources.length > 1) {
|
|
1083
|
+
throw new UsageError(`Choose exactly one Python source: --file or --stdin. Usage: ${CODE_RUN_USAGE}`);
|
|
1084
|
+
}
|
|
1085
|
+
const script = stripUtf8Bom(sources[0].read());
|
|
1086
|
+
if (script.trim().length === 0) {
|
|
1087
|
+
throw new UsageError(`Python source from ${sources[0].label} must not be empty. Usage: ${CODE_RUN_USAGE}`);
|
|
1088
|
+
}
|
|
1089
|
+
return script;
|
|
1090
|
+
}
|
|
1052
1091
|
async function resolveFolderId(client, nameOrId, flags) {
|
|
1053
1092
|
return client.folders.resolveId(nameOrId, flags["project-key"]);
|
|
1054
1093
|
}
|
|
@@ -1079,8 +1118,40 @@ function formatLineDiff(remoteName, localPath, remoteContent, localContent) {
|
|
|
1079
1118
|
}
|
|
1080
1119
|
return lines.join("\n");
|
|
1081
1120
|
}
|
|
1121
|
+
let outputFieldProjection;
|
|
1122
|
+
function resolveFieldPath(source, field) {
|
|
1123
|
+
let current = source;
|
|
1124
|
+
for (const segment of field.split(".")) {
|
|
1125
|
+
if (current === null || typeof current !== "object" || Array.isArray(current))
|
|
1126
|
+
return null;
|
|
1127
|
+
current = current[segment];
|
|
1128
|
+
}
|
|
1129
|
+
return current ?? null;
|
|
1130
|
+
}
|
|
1131
|
+
function pickResultFields(item, fields) {
|
|
1132
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
1133
|
+
return item;
|
|
1134
|
+
const source = item;
|
|
1135
|
+
const picked = {};
|
|
1136
|
+
for (const field of fields)
|
|
1137
|
+
picked[field] = resolveFieldPath(source, field);
|
|
1138
|
+
return picked;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Project the top-level fields callers asked for via --fields. Arrays are mapped
|
|
1142
|
+
* element-wise; scalars and string results pass through untouched. Requested keys
|
|
1143
|
+
* that are absent become null so every row keeps a stable, predictable shape.
|
|
1144
|
+
*/
|
|
1145
|
+
function projectResultFields(result, fields) {
|
|
1146
|
+
if (Array.isArray(result))
|
|
1147
|
+
return result.map((item) => pickResultFields(item, fields));
|
|
1148
|
+
return pickResultFields(result, fields);
|
|
1149
|
+
}
|
|
1082
1150
|
function writeCommandResult(result) {
|
|
1083
|
-
|
|
1151
|
+
const projected = outputFieldProjection
|
|
1152
|
+
? projectResultFields(result, outputFieldProjection)
|
|
1153
|
+
: result;
|
|
1154
|
+
process.stdout.write(`${JSON.stringify(projected ?? { ok: true, }, null, 2)}\n`);
|
|
1084
1155
|
}
|
|
1085
1156
|
function transientBodyWithTargetContext(body, target, elapsedMs) {
|
|
1086
1157
|
try {
|
|
@@ -1100,7 +1171,7 @@ function transientBodyWithTargetContext(body, target, elapsedMs) {
|
|
|
1100
1171
|
}
|
|
1101
1172
|
function addTransientTargetContext(error, target, elapsedMs) {
|
|
1102
1173
|
if (error instanceof DataikuError && error.category === "transient") {
|
|
1103
|
-
throw new DataikuError(error.status, error.statusText, transientBodyWithTargetContext(error.body, target, elapsedMs), error.retry);
|
|
1174
|
+
throw new DataikuError(error.status, error.statusText, transientBodyWithTargetContext(error.body, target, elapsedMs), error.retry, error.requestId);
|
|
1104
1175
|
}
|
|
1105
1176
|
throw error;
|
|
1106
1177
|
}
|
|
@@ -1120,6 +1191,27 @@ function commandFailureExitCode(result) {
|
|
|
1120
1191
|
return 4;
|
|
1121
1192
|
return undefined;
|
|
1122
1193
|
}
|
|
1194
|
+
class CommandResultFailure extends Error {
|
|
1195
|
+
result;
|
|
1196
|
+
exitCode;
|
|
1197
|
+
constructor(result, exitCode) {
|
|
1198
|
+
super(commandFailureMessage(result));
|
|
1199
|
+
this.name = "CommandResultFailure";
|
|
1200
|
+
this.result = result;
|
|
1201
|
+
this.exitCode = exitCode;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
function commandFailureMessage(result) {
|
|
1205
|
+
if (isFailedWaitResult(result)) {
|
|
1206
|
+
const record = result;
|
|
1207
|
+
const state = typeof record.state === "string" ? record.state : record.outcome;
|
|
1208
|
+
return `Command completed with failed long-running result${state ? `: ${state}` : ""}.`;
|
|
1209
|
+
}
|
|
1210
|
+
if (result && typeof result === "object" && result.unchanged === false) {
|
|
1211
|
+
return "Command completed with failed assertion result.";
|
|
1212
|
+
}
|
|
1213
|
+
return "Command completed with failed result.";
|
|
1214
|
+
}
|
|
1123
1215
|
function isNotFoundError(error) {
|
|
1124
1216
|
if (error instanceof DataikuError)
|
|
1125
1217
|
return error.category === "not_found";
|
|
@@ -1140,6 +1232,22 @@ async function readIfExists(reader) {
|
|
|
1140
1232
|
function skipResult(resource, id, reason, extra = {}) {
|
|
1141
1233
|
return { skipped: id, reason, resource, ...extra, };
|
|
1142
1234
|
}
|
|
1235
|
+
function recipeRoleInputItems(recipe, role) {
|
|
1236
|
+
const inputs = recipe["inputs"];
|
|
1237
|
+
if (!inputs || typeof inputs !== "object")
|
|
1238
|
+
return [];
|
|
1239
|
+
const roleEntry = inputs[role];
|
|
1240
|
+
if (!roleEntry || typeof roleEntry !== "object")
|
|
1241
|
+
return [];
|
|
1242
|
+
const items = roleEntry["items"];
|
|
1243
|
+
return Array.isArray(items) ? items : [];
|
|
1244
|
+
}
|
|
1245
|
+
function recipeInputItemRef(item) {
|
|
1246
|
+
if (!item || typeof item !== "object")
|
|
1247
|
+
return undefined;
|
|
1248
|
+
const ref = item["ref"];
|
|
1249
|
+
return typeof ref === "string" && ref.length > 0 ? ref : undefined;
|
|
1250
|
+
}
|
|
1143
1251
|
function planResult(resource, action, options) {
|
|
1144
1252
|
return {
|
|
1145
1253
|
plan: true,
|
|
@@ -1337,7 +1445,6 @@ function cleanupLedgerEntry(resource, action, args, flags, result, projectKey) {
|
|
|
1337
1445
|
// Arg parsing
|
|
1338
1446
|
// ---------------------------------------------------------------------------
|
|
1339
1447
|
const BOOLEAN_FLAGS = new Set([
|
|
1340
|
-
"help",
|
|
1341
1448
|
"verbose",
|
|
1342
1449
|
"version",
|
|
1343
1450
|
"stdin",
|
|
@@ -1361,7 +1468,6 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
1361
1468
|
"if-not-exists",
|
|
1362
1469
|
"if-exists",
|
|
1363
1470
|
"json",
|
|
1364
|
-
"report-json",
|
|
1365
1471
|
"no-wait",
|
|
1366
1472
|
"force-rebuild",
|
|
1367
1473
|
"latest",
|
|
@@ -1372,9 +1478,11 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
1372
1478
|
"allow-same-path",
|
|
1373
1479
|
"sync",
|
|
1374
1480
|
"validate-objects",
|
|
1481
|
+
"errors-only",
|
|
1482
|
+
"keep",
|
|
1483
|
+
"full-log",
|
|
1375
1484
|
]);
|
|
1376
1485
|
const SHORT_FLAGS = {
|
|
1377
|
-
h: "help",
|
|
1378
1486
|
v: "verbose",
|
|
1379
1487
|
V: "version",
|
|
1380
1488
|
o: "output",
|
|
@@ -1389,6 +1497,7 @@ const FLAG_ALIASES = {
|
|
|
1389
1497
|
"zone-name": "zone",
|
|
1390
1498
|
};
|
|
1391
1499
|
const VALUE_FLAGS = new Set([
|
|
1500
|
+
"fields",
|
|
1392
1501
|
"activity",
|
|
1393
1502
|
"agent",
|
|
1394
1503
|
"api-key",
|
|
@@ -1412,6 +1521,7 @@ const VALUE_FLAGS = new Set([
|
|
|
1412
1521
|
"database",
|
|
1413
1522
|
"dataset",
|
|
1414
1523
|
"file",
|
|
1524
|
+
"env",
|
|
1415
1525
|
"install-core-packages",
|
|
1416
1526
|
"folder",
|
|
1417
1527
|
"input",
|
|
@@ -1447,6 +1557,7 @@ const VALUE_FLAGS = new Set([
|
|
|
1447
1557
|
"partition",
|
|
1448
1558
|
"parent",
|
|
1449
1559
|
"path",
|
|
1560
|
+
"preview",
|
|
1450
1561
|
"project-key",
|
|
1451
1562
|
"recipe",
|
|
1452
1563
|
"request-timeout",
|
|
@@ -1454,6 +1565,7 @@ const VALUE_FLAGS = new Set([
|
|
|
1454
1565
|
"results-per-page",
|
|
1455
1566
|
"record-cleanup",
|
|
1456
1567
|
"rule-id",
|
|
1568
|
+
"role",
|
|
1457
1569
|
"retries",
|
|
1458
1570
|
"poll-interval",
|
|
1459
1571
|
"python-interpreter",
|
|
@@ -1497,6 +1609,8 @@ const KNOWN_LONG_FLAGS = new Set([
|
|
|
1497
1609
|
...Object.values(FLAG_ALIASES),
|
|
1498
1610
|
]);
|
|
1499
1611
|
function normalizeLongFlag(rawFlagName) {
|
|
1612
|
+
if (rawFlagName === "help")
|
|
1613
|
+
throw unsupportedHelpFlag();
|
|
1500
1614
|
const flagName = FLAG_ALIASES[rawFlagName] ?? rawFlagName;
|
|
1501
1615
|
if (!KNOWN_LONG_FLAGS.has(rawFlagName) && !KNOWN_LONG_FLAGS.has(flagName)) {
|
|
1502
1616
|
throw new UsageError(`Unknown flag: --${rawFlagName}`, "unknown_flag");
|
|
@@ -1563,6 +1677,8 @@ function parseArgs(argv) {
|
|
|
1563
1677
|
}
|
|
1564
1678
|
}
|
|
1565
1679
|
else {
|
|
1680
|
+
if (arg[1] === "h")
|
|
1681
|
+
throw unsupportedHelpFlag();
|
|
1566
1682
|
throw new UsageError(`Unknown flag: -${arg[1]}`, "unknown_flag");
|
|
1567
1683
|
}
|
|
1568
1684
|
}
|
|
@@ -2640,10 +2756,11 @@ const commands = {
|
|
|
2640
2756
|
return c.datasets.download(a[0], {
|
|
2641
2757
|
outputPath: f["output"],
|
|
2642
2758
|
projectKey: f["project-key"],
|
|
2759
|
+
limit: num(f["limit"]),
|
|
2643
2760
|
});
|
|
2644
2761
|
},
|
|
2645
|
-
usage: "dss dataset download <name> [--output PATH] [--project-key KEY]",
|
|
2646
|
-
description: "Download
|
|
2762
|
+
usage: "dss dataset download <name> [--output PATH] [--limit N] [--project-key KEY]",
|
|
2763
|
+
description: "Download up to --limit rows (default 100k) as CSV; returns { path, rows, truncated, limit } so truncation is visible.",
|
|
2647
2764
|
examples: ["dss dataset download orders", "dss dataset download orders --output ./data/",],
|
|
2648
2765
|
},
|
|
2649
2766
|
create: {
|
|
@@ -3096,6 +3213,87 @@ const commands = {
|
|
|
3096
3213
|
"cat settings.json | dss recipe update compute_orders --stdin",
|
|
3097
3214
|
],
|
|
3098
3215
|
},
|
|
3216
|
+
"add-input": {
|
|
3217
|
+
handler: async (c, a, f) => {
|
|
3218
|
+
requireArgs(a, 2, "dss recipe add-input <recipe> <dataset> [--role ROLE] [--if-not-exists] [--dry-run] [--project-key KEY]");
|
|
3219
|
+
const role = f["role"] ?? "main";
|
|
3220
|
+
const pk = f["project-key"];
|
|
3221
|
+
const { recipe, } = await c.recipes.get(a[0], { projectKey: pk, });
|
|
3222
|
+
const items = recipeRoleInputItems(recipe, role);
|
|
3223
|
+
const present = items.some((item) => recipeInputItemRef(item) === a[1]);
|
|
3224
|
+
if (present) {
|
|
3225
|
+
if (f["if-not-exists"] === true) {
|
|
3226
|
+
return skipResult("recipe", a[0], "exists", { dataset: a[1], role, });
|
|
3227
|
+
}
|
|
3228
|
+
throw new UsageError(`Dataset "${a[1]}" is already a "${role}" input of recipe "${a[0]}".`, "validation_failed");
|
|
3229
|
+
}
|
|
3230
|
+
const nextItems = [...items, { ref: a[1], deps: [], },];
|
|
3231
|
+
const inputs = nextItems.map(recipeInputItemRef).filter((ref) => Boolean(ref));
|
|
3232
|
+
if (f["dry-run"] === true) {
|
|
3233
|
+
return {
|
|
3234
|
+
dryRun: true,
|
|
3235
|
+
action: "add-input",
|
|
3236
|
+
resource: "recipe",
|
|
3237
|
+
recipe: a[0],
|
|
3238
|
+
dataset: a[1],
|
|
3239
|
+
role,
|
|
3240
|
+
inputs,
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
await c.recipes.update(a[0], { recipe: { inputs: { [role]: { items: nextItems, }, }, }, }, pk);
|
|
3244
|
+
return { updated: a[0], resource: "recipe", action: "add-input", role, dataset: a[1], inputs, };
|
|
3245
|
+
},
|
|
3246
|
+
usage: "dss recipe add-input <recipe> <dataset> [--role ROLE] [--if-not-exists] [--dry-run] [--project-key KEY]",
|
|
3247
|
+
description: "Add a dataset as a recipe input by appending one item to the current inputs (no need to resend the whole list).",
|
|
3248
|
+
examples: [
|
|
3249
|
+
"dss recipe add-input compute_orders extra_lookup",
|
|
3250
|
+
"dss recipe add-input compute_orders extra_lookup --if-not-exists --dry-run",
|
|
3251
|
+
],
|
|
3252
|
+
},
|
|
3253
|
+
"remove-input": {
|
|
3254
|
+
handler: async (c, a, f) => {
|
|
3255
|
+
requireArgs(a, 2, "dss recipe remove-input <recipe> <dataset> [--role ROLE] [--if-exists] [--dry-run] [--project-key KEY]");
|
|
3256
|
+
const role = f["role"] ?? "main";
|
|
3257
|
+
const pk = f["project-key"];
|
|
3258
|
+
const { recipe, } = await c.recipes.get(a[0], { projectKey: pk, });
|
|
3259
|
+
const items = recipeRoleInputItems(recipe, role);
|
|
3260
|
+
const present = items.some((item) => recipeInputItemRef(item) === a[1]);
|
|
3261
|
+
if (!present) {
|
|
3262
|
+
if (f["if-exists"] === true) {
|
|
3263
|
+
return skipResult("recipe", a[0], "missing", { dataset: a[1], role, });
|
|
3264
|
+
}
|
|
3265
|
+
throw new UsageError(`Dataset "${a[1]}" is not a "${role}" input of recipe "${a[0]}".`, "validation_failed");
|
|
3266
|
+
}
|
|
3267
|
+
const nextItems = items.filter((item) => recipeInputItemRef(item) !== a[1]);
|
|
3268
|
+
const inputs = nextItems.map(recipeInputItemRef).filter((ref) => Boolean(ref));
|
|
3269
|
+
if (f["dry-run"] === true) {
|
|
3270
|
+
return {
|
|
3271
|
+
dryRun: true,
|
|
3272
|
+
action: "remove-input",
|
|
3273
|
+
resource: "recipe",
|
|
3274
|
+
recipe: a[0],
|
|
3275
|
+
dataset: a[1],
|
|
3276
|
+
role,
|
|
3277
|
+
inputs,
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
await c.recipes.update(a[0], { recipe: { inputs: { [role]: { items: nextItems, }, }, }, }, pk);
|
|
3281
|
+
return {
|
|
3282
|
+
updated: a[0],
|
|
3283
|
+
resource: "recipe",
|
|
3284
|
+
action: "remove-input",
|
|
3285
|
+
role,
|
|
3286
|
+
dataset: a[1],
|
|
3287
|
+
inputs,
|
|
3288
|
+
};
|
|
3289
|
+
},
|
|
3290
|
+
usage: "dss recipe remove-input <recipe> <dataset> [--role ROLE] [--if-exists] [--dry-run] [--project-key KEY]",
|
|
3291
|
+
description: "Remove a dataset from a recipe's inputs by dropping one item from the current inputs.",
|
|
3292
|
+
examples: [
|
|
3293
|
+
"dss recipe remove-input compute_orders stale_lookup",
|
|
3294
|
+
"dss recipe remove-input compute_orders stale_lookup --if-exists --dry-run",
|
|
3295
|
+
],
|
|
3296
|
+
},
|
|
3099
3297
|
"get-payload": {
|
|
3100
3298
|
handler: async (c, a, f) => {
|
|
3101
3299
|
requireArgs(a, 1, "dss recipe get-payload <name>");
|
|
@@ -3109,7 +3307,7 @@ const commands = {
|
|
|
3109
3307
|
return payload;
|
|
3110
3308
|
},
|
|
3111
3309
|
usage: "dss recipe get-payload <name> [--raw] [--output PATH] [--project-key KEY]",
|
|
3112
|
-
description: "Print the recipe code payload
|
|
3310
|
+
description: "Print the recipe code payload as JSON; use --raw for raw bytes, not JSON.",
|
|
3113
3311
|
examples: [
|
|
3114
3312
|
"dss recipe get-payload compute_orders --raw",
|
|
3115
3313
|
"dss recipe get-payload compute_orders -o code.py",
|
|
@@ -3123,7 +3321,7 @@ const commands = {
|
|
|
3123
3321
|
});
|
|
3124
3322
|
},
|
|
3125
3323
|
usage: "dss recipe cat <name> [--raw] [--project-key KEY]",
|
|
3126
|
-
description: "Print a recipe code payload;
|
|
3324
|
+
description: "Print a recipe code payload as JSON; use --raw for raw bytes, not JSON.",
|
|
3127
3325
|
examples: ["dss recipe cat compute_orders --raw",],
|
|
3128
3326
|
},
|
|
3129
3327
|
"set-payload": {
|
|
@@ -3306,17 +3504,29 @@ const commands = {
|
|
|
3306
3504
|
examples: ["dss job summary JOB_ID --max-log-lines 200",],
|
|
3307
3505
|
},
|
|
3308
3506
|
log: {
|
|
3309
|
-
handler: (c, a, f) => {
|
|
3507
|
+
handler: async (c, a, f) => {
|
|
3310
3508
|
requireArgs(a, 1, "dss job log <id>");
|
|
3311
|
-
|
|
3509
|
+
const logFilter = f["errors-only"] === true
|
|
3510
|
+
? "errors"
|
|
3511
|
+
: jobLogFilterFromFlag(f["log-filter"]);
|
|
3512
|
+
const log = await c.jobs.log(a[0], {
|
|
3312
3513
|
activity: f["activity"],
|
|
3313
3514
|
logId: f["log-id"],
|
|
3515
|
+
logFilter,
|
|
3314
3516
|
maxLogLines: maxLogLinesFromFlags(f),
|
|
3315
3517
|
projectKey: f["project-key"],
|
|
3316
3518
|
});
|
|
3519
|
+
const outputFile = f["output"]
|
|
3520
|
+
?? f["output-file"];
|
|
3521
|
+
if (!outputFile)
|
|
3522
|
+
return log;
|
|
3523
|
+
const outputPath = resolve(outputFile);
|
|
3524
|
+
await mkdir(dirname(outputPath), { recursive: true, });
|
|
3525
|
+
await writeFile(outputPath, log.endsWith("\n") ? log : `${log}\n`, "utf-8");
|
|
3526
|
+
return outputPath;
|
|
3317
3527
|
},
|
|
3318
|
-
usage: "dss job log <id> [--activity ACTIVITY_ID] [--log-id LOG_ID] [--max-lines N|--max-log-lines N] [--project-key KEY]",
|
|
3319
|
-
description: "Get public API job log output. --log-id is accepted for UI parity but DSS API-key auth cannot select browser-only cat-activity-log files.",
|
|
3528
|
+
usage: "dss job log <id> [--activity ACTIVITY_ID] [--log-id LOG_ID] [--log-filter stdout|stderr|user|errors] [--errors-only] [--max-lines N|--max-log-lines N] [--output PATH] [--project-key KEY]",
|
|
3529
|
+
description: "Get public API job log output. Use --errors-only (or --log-filter errors) to surface just error/traceback lines, and --output PATH to write the log to a file (stdout returns the path). --log-id is accepted for UI parity but DSS API-key auth cannot select browser-only cat-activity-log files.",
|
|
3320
3530
|
examples: [
|
|
3321
3531
|
"dss job log JOB_ID",
|
|
3322
3532
|
"dss job log JOB_ID --activity main --max-log-lines 200",
|
|
@@ -4185,12 +4395,21 @@ const commands = {
|
|
|
4185
4395
|
sql: {
|
|
4186
4396
|
query: {
|
|
4187
4397
|
handler: async (c, a, f) => {
|
|
4188
|
-
const query = resolveSqlInput(a, f);
|
|
4189
4398
|
const connection = f["connection"];
|
|
4190
4399
|
const datasetFullName = f["dataset"];
|
|
4191
4400
|
if ((connection ? 1 : 0) + (datasetFullName ? 1 : 0) !== 1) {
|
|
4192
4401
|
throw new UsageError(`Pass exactly one of --connection or --dataset. Usage: ${SQL_QUERY_USAGE}`);
|
|
4193
4402
|
}
|
|
4403
|
+
const outputFile = f["output"]
|
|
4404
|
+
?? f["output-file"];
|
|
4405
|
+
const previewProvided = f["preview"] !== undefined;
|
|
4406
|
+
if (previewProvided && !outputFile) {
|
|
4407
|
+
throw new UsageError(`--preview requires --output or --output-file. Usage: ${SQL_QUERY_USAGE}`, "validation_failed");
|
|
4408
|
+
}
|
|
4409
|
+
const previewCount = previewProvided
|
|
4410
|
+
? parseSqlPreviewCount(f["preview"])
|
|
4411
|
+
: DEFAULT_SQL_PREVIEW_ROWS;
|
|
4412
|
+
const query = resolveSqlInput(a, f);
|
|
4194
4413
|
const result = await c.sql.query({
|
|
4195
4414
|
query,
|
|
4196
4415
|
connection,
|
|
@@ -4198,8 +4417,6 @@ const commands = {
|
|
|
4198
4417
|
database: f["database"],
|
|
4199
4418
|
projectKey: f["project-key"],
|
|
4200
4419
|
});
|
|
4201
|
-
const outputFile = f["output"]
|
|
4202
|
-
?? f["output-file"];
|
|
4203
4420
|
if (!outputFile)
|
|
4204
4421
|
return result;
|
|
4205
4422
|
const outputPath = resolve(outputFile);
|
|
@@ -4210,6 +4427,7 @@ const commands = {
|
|
|
4210
4427
|
schema: result.schema,
|
|
4211
4428
|
columns: result.columns ?? result.schema,
|
|
4212
4429
|
rowCount: result.rows.length,
|
|
4430
|
+
preview: result.rows.slice(0, previewCount),
|
|
4213
4431
|
outputPath,
|
|
4214
4432
|
written: outputPath,
|
|
4215
4433
|
};
|
|
@@ -4221,6 +4439,40 @@ const commands = {
|
|
|
4221
4439
|
"dss sql query --sql-file query.sql --connection my_pg",
|
|
4222
4440
|
"echo 'SELECT 1' | dss sql query --stdin --dataset MYPROJ.orders",
|
|
4223
4441
|
"dss sql query --sql-file query.sql --connection my_pg --output results.json --request-timeout 120000",
|
|
4442
|
+
"dss sql query --sql-file query.sql --connection my_pg --output results.json --preview 10",
|
|
4443
|
+
],
|
|
4444
|
+
},
|
|
4445
|
+
},
|
|
4446
|
+
code: {
|
|
4447
|
+
run: {
|
|
4448
|
+
handler: async (c, a, f) => {
|
|
4449
|
+
const script = resolveCodeInput(a, f);
|
|
4450
|
+
const run = await c.scenarios.runScript(script, {
|
|
4451
|
+
envName: f["env"],
|
|
4452
|
+
projectKey: f["project-key"],
|
|
4453
|
+
timeoutMs: num(f["timeout"]),
|
|
4454
|
+
keepScenario: f["keep"] === true,
|
|
4455
|
+
});
|
|
4456
|
+
const result = {
|
|
4457
|
+
outcome: run.outcome,
|
|
4458
|
+
success: run.success,
|
|
4459
|
+
runId: run.runId,
|
|
4460
|
+
elapsedMs: run.elapsedMs,
|
|
4461
|
+
pollCount: run.pollCount,
|
|
4462
|
+
output: run.output ?? "",
|
|
4463
|
+
};
|
|
4464
|
+
if (f["full-log"] === true || run.output === undefined) {
|
|
4465
|
+
result.log = run.log;
|
|
4466
|
+
}
|
|
4467
|
+
return result;
|
|
4468
|
+
},
|
|
4469
|
+
usage: CODE_RUN_USAGE,
|
|
4470
|
+
description: "Run one-off Python in a DSS code env via a throwaway custom-python scenario; returns the script's captured output (stdout+stderr) plus outcome/success. Pass --full-log for the raw DSS run log. Exits 4 on a non-SUCCESS outcome.",
|
|
4471
|
+
examples: [
|
|
4472
|
+
"dss code run --file inspect.py",
|
|
4473
|
+
"dss code run --file inspect.py --env py39_pandas",
|
|
4474
|
+
"cat snippet.py | dss code run --stdin",
|
|
4475
|
+
"dss code run --file inspect.py --full-log",
|
|
4224
4476
|
],
|
|
4225
4477
|
},
|
|
4226
4478
|
},
|
|
@@ -4452,7 +4704,7 @@ const commands = {
|
|
|
4452
4704
|
},
|
|
4453
4705
|
};
|
|
4454
4706
|
// ---------------------------------------------------------------------------
|
|
4455
|
-
//
|
|
4707
|
+
// Agent-facing command inventory
|
|
4456
4708
|
// ---------------------------------------------------------------------------
|
|
4457
4709
|
const RESOURCE_NAMES = [
|
|
4458
4710
|
...Object.keys(commands),
|
|
@@ -4463,87 +4715,37 @@ const RESOURCE_NAMES = [
|
|
|
4463
4715
|
"install-skill",
|
|
4464
4716
|
]
|
|
4465
4717
|
.sort();
|
|
4466
|
-
function printTopLevelHelp() {
|
|
4467
|
-
const lines = [
|
|
4468
|
-
"Usage: dss <resource> <action> [args...] [--flags]",
|
|
4469
|
-
"",
|
|
4470
|
-
"Global flags:",
|
|
4471
|
-
" -h, --help Show help",
|
|
4472
|
-
" -v, --verbose Log HTTP requests to stderr",
|
|
4473
|
-
" -V, --version Show version",
|
|
4474
|
-
" --json Emit JSON output (default)",
|
|
4475
|
-
" -o, --output PATH Write output to file (recipe get-payload)",
|
|
4476
|
-
" --url URL Dataiku DSS base URL (env: DATAIKU_URL)",
|
|
4477
|
-
" --api-key KEY API key (env: DATAIKU_API_KEY)",
|
|
4478
|
-
" --project-key KEY Default project key (env: DATAIKU_PROJECT_KEY)",
|
|
4479
|
-
" --timeout MS Operation timeout (build-and-wait, run-and-wait, recipe run)",
|
|
4480
|
-
" --request-timeout MS HTTP request timeout in ms (default: 30000)",
|
|
4481
|
-
" --dry-run Preview destructive actions without executing",
|
|
4482
|
-
" --if-not-exists Skip create if resource already exists",
|
|
4483
|
-
" --if-exists Skip delete if resource is already missing",
|
|
4484
|
-
" --insecure Disable TLS certificate verification",
|
|
4485
|
-
" --ca-cert PATH Extra PEM CA bundle (env: NODE_EXTRA_CA_CERTS)",
|
|
4486
|
-
"",
|
|
4487
|
-
"Resources:",
|
|
4488
|
-
...RESOURCE_NAMES.map((r) => ` ${r}`),
|
|
4489
|
-
"",
|
|
4490
|
-
"Quick start:",
|
|
4491
|
-
" dss auth login Save DSS credentials",
|
|
4492
|
-
" dss auth status Verify connection",
|
|
4493
|
-
" dss doctor Run JSON connectivity diagnostics",
|
|
4494
|
-
" dss project list List accessible projects",
|
|
4495
|
-
" dss dataset list List datasets in default project",
|
|
4496
|
-
" dss dataset preview <name> Preview dataset rows as CSV",
|
|
4497
|
-
" dss recipe get-payload <name> Print recipe code to stdout",
|
|
4498
|
-
" dss recipe download-code <name> Download recipe code to a file",
|
|
4499
|
-
" dss job log <id> View job log output",
|
|
4500
|
-
" dss install-skill Install agent skill for coding agents",
|
|
4501
|
-
];
|
|
4502
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
4503
|
-
}
|
|
4504
|
-
function printResourceHelp(resource) {
|
|
4505
|
-
const actions = commands[resource];
|
|
4506
|
-
if (!actions)
|
|
4507
|
-
return;
|
|
4508
|
-
const maxName = Math.max(...Object.keys(actions).map((n) => n.length));
|
|
4509
|
-
const lines = [
|
|
4510
|
-
`Usage: dss ${resource} <action> [args...] [--flags]`,
|
|
4511
|
-
"",
|
|
4512
|
-
"Actions:",
|
|
4513
|
-
...Object.entries(actions).map(([name, meta,]) => ` ${name.padEnd(maxName + 2)}${meta.description ?? meta.usage}`),
|
|
4514
|
-
"",
|
|
4515
|
-
`Run 'dss ${resource} <action> --help' for details and examples.`,
|
|
4516
|
-
];
|
|
4517
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
4518
|
-
}
|
|
4519
|
-
function printActionHelp(resource, action) {
|
|
4520
|
-
const meta = commands[resource]?.[action];
|
|
4521
|
-
if (!meta)
|
|
4522
|
-
return;
|
|
4523
|
-
const lines = [];
|
|
4524
|
-
if (meta.description)
|
|
4525
|
-
lines.push(meta.description, "");
|
|
4526
|
-
lines.push(`Usage: ${meta.usage}`);
|
|
4527
|
-
if (meta.examples && meta.examples.length > 0) {
|
|
4528
|
-
lines.push("", "Examples:");
|
|
4529
|
-
for (const ex of meta.examples)
|
|
4530
|
-
lines.push(` ${ex}`);
|
|
4531
|
-
}
|
|
4532
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
4533
|
-
}
|
|
4534
4718
|
// ---------------------------------------------------------------------------
|
|
4535
4719
|
// Validation
|
|
4536
4720
|
// ---------------------------------------------------------------------------
|
|
4537
4721
|
class UsageError extends Error {
|
|
4538
4722
|
code;
|
|
4539
4723
|
hint;
|
|
4540
|
-
|
|
4724
|
+
details;
|
|
4725
|
+
constructor(message, code = "usage_error", hint, details) {
|
|
4541
4726
|
super(message);
|
|
4542
4727
|
this.name = "UsageError";
|
|
4543
4728
|
this.code = code;
|
|
4544
4729
|
this.hint = hint;
|
|
4730
|
+
this.details = details;
|
|
4545
4731
|
}
|
|
4546
4732
|
}
|
|
4733
|
+
const COMMANDS_RUN_HINT = "Use `dss commands run` for machine-readable command discovery.";
|
|
4734
|
+
function unsupportedHelpFlag() {
|
|
4735
|
+
return new UsageError("Help screens are not supported.", "usage_error", COMMANDS_RUN_HINT, { command: "dss commands run", });
|
|
4736
|
+
}
|
|
4737
|
+
function noCommandError() {
|
|
4738
|
+
return new UsageError("No command provided.", "usage_error", COMMANDS_RUN_HINT, { command: "dss commands run", resources: RESOURCE_NAMES, });
|
|
4739
|
+
}
|
|
4740
|
+
function missingActionError(resource, validActions, usage) {
|
|
4741
|
+
return new UsageError(`Missing action for ${resource}.`, "usage_error", usage ?? COMMANDS_RUN_HINT, { resource, validActions, });
|
|
4742
|
+
}
|
|
4743
|
+
function unknownResourceError(resource) {
|
|
4744
|
+
return new UsageError(`Unknown resource: ${resource}.`, "usage_error", COMMANDS_RUN_HINT, { resource, validResources: RESOURCE_NAMES, });
|
|
4745
|
+
}
|
|
4746
|
+
function unknownActionError(resource, action, validActions) {
|
|
4747
|
+
return new UsageError(`Unknown action: ${resource} ${action ?? ""}`.trim(), "usage_error", COMMANDS_RUN_HINT, { resource, action, validActions, });
|
|
4748
|
+
}
|
|
4547
4749
|
function requireArgs(args, count, usage) {
|
|
4548
4750
|
if (args.length < count) {
|
|
4549
4751
|
throw new UsageError(`Expected ${count} argument(s), got ${args.length}.\nUsage: ${usage}`, "missing_required_arg");
|
|
@@ -4552,12 +4754,18 @@ function requireArgs(args, count, usage) {
|
|
|
4552
4754
|
// ---------------------------------------------------------------------------
|
|
4553
4755
|
// .env auto-loading
|
|
4554
4756
|
// ---------------------------------------------------------------------------
|
|
4757
|
+
function dataikuEnvironmentEnabled() {
|
|
4758
|
+
return process.env.DATAIKU_DISABLE_ENV !== "1";
|
|
4759
|
+
}
|
|
4555
4760
|
function loadEnvFile() {
|
|
4556
|
-
if (
|
|
4761
|
+
if (!dataikuEnvironmentEnabled())
|
|
4557
4762
|
return;
|
|
4763
|
+
// The invocation cwd takes precedence over the CLI install/root directory, so a
|
|
4764
|
+
// project-local .env where `dss` is invoked overrides defaults shipped beside the
|
|
4765
|
+
// CLI. First writer wins below, so cwd must be listed first.
|
|
4558
4766
|
const dirs = [
|
|
4559
|
-
resolve(dirname(fileURLToPath(import.meta.url)), ".."),
|
|
4560
4767
|
process.cwd(),
|
|
4768
|
+
resolve(dirname(fileURLToPath(import.meta.url)), ".."),
|
|
4561
4769
|
];
|
|
4562
4770
|
for (const dir of dirs) {
|
|
4563
4771
|
try {
|
|
@@ -4587,81 +4795,42 @@ const AUTH_ACTIONS = {
|
|
|
4587
4795
|
login: {
|
|
4588
4796
|
handler: async (flags) => {
|
|
4589
4797
|
const tlsSettings = resolveTlsSettings(flags);
|
|
4590
|
-
|
|
4798
|
+
const useEnv = dataikuEnvironmentEnabled();
|
|
4799
|
+
const url = typeof flags["url"] === "string"
|
|
4800
|
+
? flags["url"]
|
|
4801
|
+
: useEnv
|
|
4802
|
+
? process.env.DATAIKU_URL ?? ""
|
|
4803
|
+
: "";
|
|
4804
|
+
const apiKey = typeof flags["api-key"] === "string"
|
|
4805
|
+
? flags["api-key"]
|
|
4806
|
+
: useEnv
|
|
4807
|
+
? process.env.DATAIKU_API_KEY ?? ""
|
|
4808
|
+
: "";
|
|
4809
|
+
const projectKey = typeof flags["project-key"] === "string"
|
|
4810
|
+
? flags["project-key"]
|
|
4811
|
+
: useEnv
|
|
4812
|
+
? process.env.DATAIKU_PROJECT_KEY
|
|
4813
|
+
: undefined;
|
|
4591
4814
|
if (!url || !apiKey) {
|
|
4592
|
-
|
|
4593
|
-
throw new UsageError("Missing --url and/or --api-key. Provide them as flags or run interactively.");
|
|
4594
|
-
}
|
|
4595
|
-
if (!url)
|
|
4596
|
-
url = await promptLine("DSS URL: ");
|
|
4597
|
-
if (!apiKey)
|
|
4598
|
-
apiKey = await promptSecret("API key: ");
|
|
4599
|
-
if (!projectKey)
|
|
4600
|
-
projectKey = (await promptLine("Project key (optional): ")) || undefined;
|
|
4815
|
+
throw new UsageError("Missing --url and/or --api-key for auth login.", "missing_required_flag", "Pass --url and --api-key, or set DATAIKU_URL and DATAIKU_API_KEY.", { requiredFlags: ["url", "api-key",], env: ["DATAIKU_URL", "DATAIKU_API_KEY",], });
|
|
4601
4816
|
}
|
|
4602
|
-
if (!url)
|
|
4603
|
-
throw new UsageError("URL is required.");
|
|
4604
|
-
if (!apiKey)
|
|
4605
|
-
throw new UsageError("API key is required.");
|
|
4606
|
-
process.stderr.write("Validating credentials... ");
|
|
4607
4817
|
const result = await validateCredentials(url, apiKey, tlsSettings);
|
|
4608
4818
|
if (!result.valid) {
|
|
4609
|
-
process.stderr.write("Failed\n");
|
|
4610
4819
|
if (result.dataikuError)
|
|
4611
4820
|
throw result.dataikuError;
|
|
4612
4821
|
throw new DataikuError(0, "Authentication Failed", result.error ?? "Credential validation failed");
|
|
4613
4822
|
}
|
|
4614
|
-
|
|
4823
|
+
const path = getCredentialsPath();
|
|
4615
4824
|
saveCredentials({ url, apiKey, projectKey, ...tlsSettings, });
|
|
4616
|
-
|
|
4825
|
+
return { saved: true, path, };
|
|
4617
4826
|
},
|
|
4618
|
-
usage: "dss auth login
|
|
4619
|
-
description: "
|
|
4827
|
+
usage: "dss auth login --url URL --api-key KEY [--project-key KEY] [--insecure] [--ca-cert PATH]",
|
|
4828
|
+
description: "Validate and save DSS credentials from flags or environment variables.",
|
|
4620
4829
|
examples: [
|
|
4621
4830
|
"dss auth login --url https://dss.example.com --api-key YOUR_KEY",
|
|
4622
4831
|
"dss auth login --url https://dss.example.com --api-key YOUR_KEY --project-key MYPROJ",
|
|
4623
4832
|
],
|
|
4624
|
-
|
|
4625
|
-
status: {
|
|
4626
|
-
handler: async (flags) => {
|
|
4627
|
-
const creds = loadCredentials();
|
|
4628
|
-
if (!creds) {
|
|
4629
|
-
process.stderr.write("No saved credentials. Run: dss auth login\n");
|
|
4630
|
-
process.exit(1);
|
|
4631
|
-
}
|
|
4632
|
-
const tlsSettings = resolveTlsSettings(flags, creds);
|
|
4633
|
-
const lines = [
|
|
4634
|
-
`URL: ${creds.url}`,
|
|
4635
|
-
`API key: ${maskApiKey(creds.apiKey)}`,
|
|
4636
|
-
`Project key: ${creds.projectKey ?? "(not set)"}`,
|
|
4637
|
-
`TLS verify: ${tlsSettings.tlsRejectUnauthorized === false ? "disabled" : "strict"}`,
|
|
4638
|
-
`CA cert: ${tlsSettings.caCertPath ?? "(default trust store)"}`,
|
|
4639
|
-
];
|
|
4640
|
-
for (const line of lines)
|
|
4641
|
-
process.stderr.write(`${line}\n`);
|
|
4642
|
-
const result = await validateCredentials(creds.url, creds.apiKey, tlsSettings);
|
|
4643
|
-
if (result.valid) {
|
|
4644
|
-
process.stderr.write("Connection: valid\n");
|
|
4645
|
-
}
|
|
4646
|
-
else {
|
|
4647
|
-
process.stderr.write(`Connection: failed (${result.error ?? "unknown error"})\n`);
|
|
4648
|
-
process.stderr.write(`Config: ${getCredentialsPath()}\n`);
|
|
4649
|
-
process.exit(1);
|
|
4650
|
-
}
|
|
4651
|
-
process.stderr.write(`Config: ${getCredentialsPath()}\n`);
|
|
4652
|
-
},
|
|
4653
|
-
usage: "dss auth status [--insecure] [--ca-cert PATH]",
|
|
4654
|
-
description: "Show saved credentials and verify the connection.",
|
|
4655
|
-
examples: ["dss auth status",],
|
|
4656
|
-
},
|
|
4657
|
-
logout: {
|
|
4658
|
-
handler: async (_flags) => {
|
|
4659
|
-
deleteCredentials();
|
|
4660
|
-
process.stderr.write("Credentials removed.\n");
|
|
4661
|
-
},
|
|
4662
|
-
usage: "dss auth logout",
|
|
4663
|
-
description: "Remove saved credentials.",
|
|
4664
|
-
examples: ["dss auth logout",],
|
|
4833
|
+
requiredFlags: ["url", "api-key",],
|
|
4665
4834
|
},
|
|
4666
4835
|
};
|
|
4667
4836
|
function errorDetails(error) {
|
|
@@ -4913,7 +5082,7 @@ async function runDoctor(flags) {
|
|
|
4913
5082
|
ok: credentialsOk,
|
|
4914
5083
|
message: credentialsOk
|
|
4915
5084
|
? "Dataiku URL and API key are configured."
|
|
4916
|
-
: "Missing Dataiku URL and/or API key. Set DATAIKU_URL/DATAIKU_API_KEY
|
|
5085
|
+
: "Missing Dataiku URL and/or API key. Set DATAIKU_URL/DATAIKU_API_KEY or pass --url/--api-key.",
|
|
4917
5086
|
});
|
|
4918
5087
|
let accessibleProjects;
|
|
4919
5088
|
if (credentialsOk) {
|
|
@@ -4992,13 +5161,13 @@ async function runDoctor(flags) {
|
|
|
4992
5161
|
async function runFixtures(flags) {
|
|
4993
5162
|
const { url, apiKey, projectKey, tlsRejectUnauthorized, caCertPath, } = resolveCredentials(flags);
|
|
4994
5163
|
if (!url) {
|
|
4995
|
-
throw new UsageError("Missing Dataiku URL. Set DATAIKU_URL
|
|
5164
|
+
throw new UsageError("Missing Dataiku URL. Set DATAIKU_URL or pass --url.", "missing_required_flag");
|
|
4996
5165
|
}
|
|
4997
5166
|
if (!apiKey) {
|
|
4998
|
-
throw new UsageError("Missing API key. Set DATAIKU_API_KEY
|
|
5167
|
+
throw new UsageError("Missing API key. Set DATAIKU_API_KEY or pass --api-key.", "missing_required_flag");
|
|
4999
5168
|
}
|
|
5000
5169
|
if (!projectKey) {
|
|
5001
|
-
throw new UsageError("Missing project key. Set DATAIKU_PROJECT_KEY
|
|
5170
|
+
throw new UsageError("Missing project key. Set DATAIKU_PROJECT_KEY or pass --project-key.", "missing_required_flag");
|
|
5002
5171
|
}
|
|
5003
5172
|
currentCommandContext.projectKey = projectKey;
|
|
5004
5173
|
const requestTimeoutMs = num(flags["request-timeout"]);
|
|
@@ -5072,7 +5241,7 @@ const PROJECT_SCOPED_RESOURCES = new Set([
|
|
|
5072
5241
|
"variable",
|
|
5073
5242
|
"wiki",
|
|
5074
5243
|
]);
|
|
5075
|
-
const GLOBAL_AGENT_FLAGS = ["
|
|
5244
|
+
const GLOBAL_AGENT_FLAGS = ["json", "verbose", "fields",];
|
|
5076
5245
|
const AUTHENTICATED_AGENT_FLAGS = [
|
|
5077
5246
|
"url",
|
|
5078
5247
|
"api-key",
|
|
@@ -5081,9 +5250,12 @@ const AUTHENTICATED_AGENT_FLAGS = [
|
|
|
5081
5250
|
"insecure",
|
|
5082
5251
|
"ca-cert",
|
|
5083
5252
|
];
|
|
5084
|
-
const COMMANDS_USAGE = "dss commands [--json]";
|
|
5253
|
+
const COMMANDS_USAGE = "dss commands run [--json]";
|
|
5085
5254
|
const COMMANDS_DESCRIPTION = "Print the machine-readable command registry for agent planning.";
|
|
5086
|
-
const COMMANDS_EXAMPLES = ["dss commands", "dss commands --json",];
|
|
5255
|
+
const COMMANDS_EXAMPLES = ["dss commands run", "dss commands run --json",];
|
|
5256
|
+
const VERSION_USAGE = "dss version";
|
|
5257
|
+
const VERSION_DESCRIPTION = "Print the CLI version and git revision as JSON.";
|
|
5258
|
+
const VERSION_EXAMPLES = ["dss version", "dss --version",];
|
|
5087
5259
|
const INSTALL_SKILL_USAGE = "dss install-skill [--global] [--agent NAME] [--target PATH] [--list-agents] [--dry-run] [--plan]";
|
|
5088
5260
|
const INSTALL_SKILL_DESCRIPTION = "Install the dataiku-dss agent skill for detected coding agents.";
|
|
5089
5261
|
const INSTALL_SKILL_EXAMPLES = [
|
|
@@ -5102,6 +5274,22 @@ const FIXTURES_EXAMPLES = [
|
|
|
5102
5274
|
"dss fixtures --json",
|
|
5103
5275
|
"dss fixtures --json --allow-types Filesystem,Inline",
|
|
5104
5276
|
];
|
|
5277
|
+
const ALLOWED_CLEANUP_ACTIONS = new Set([
|
|
5278
|
+
// Must mirror every cleanup.argv shape emitted by cleanupLedgerEntry().
|
|
5279
|
+
"dataset delete",
|
|
5280
|
+
"recipe delete",
|
|
5281
|
+
"scenario delete",
|
|
5282
|
+
"flow-zone delete",
|
|
5283
|
+
"wiki delete",
|
|
5284
|
+
"dashboard delete",
|
|
5285
|
+
"insight delete",
|
|
5286
|
+
"data-quality delete-rule",
|
|
5287
|
+
"code-env delete",
|
|
5288
|
+
"folder delete-file",
|
|
5289
|
+
]);
|
|
5290
|
+
function isAllowedCleanupAction(resource, action) {
|
|
5291
|
+
return ALLOWED_CLEANUP_ACTIONS.has(`${resource} ${action}`);
|
|
5292
|
+
}
|
|
5105
5293
|
function uniqueStrings(values) {
|
|
5106
5294
|
return [...new Set(values),];
|
|
5107
5295
|
}
|
|
@@ -5166,26 +5354,33 @@ function extractPositionals(usage) {
|
|
|
5166
5354
|
function inferSideEffect(resource, action) {
|
|
5167
5355
|
if (resource === "auth")
|
|
5168
5356
|
return "auth";
|
|
5169
|
-
if (resource === "doctor" || resource === "commands" || resource === "fixtures"
|
|
5357
|
+
if (resource === "doctor" || resource === "commands" || resource === "fixtures"
|
|
5358
|
+
|| resource === "version") {
|
|
5170
5359
|
return "read";
|
|
5360
|
+
}
|
|
5171
5361
|
if (resource === "install-skill")
|
|
5172
5362
|
return "write";
|
|
5173
5363
|
if (resource === "data-quality" && action === "compute")
|
|
5174
5364
|
return "write";
|
|
5175
5365
|
if (READ_ACTIONS.has(action))
|
|
5176
5366
|
return "read";
|
|
5177
|
-
if (/^(create|clone|restore|update|delete|set|save|upload|run|build|abort|move|refresh|clear|unload|install|login|logout)/
|
|
5367
|
+
if (/^(create|clone|restore|update|delete|set|save|upload|run|build|abort|move|refresh|clear|unload|install|login|logout|add|remove)/
|
|
5178
5368
|
.test(action)) {
|
|
5179
5369
|
return "write";
|
|
5180
5370
|
}
|
|
5181
5371
|
return "read";
|
|
5182
5372
|
}
|
|
5183
5373
|
function inferRequiresAuth(resource) {
|
|
5184
|
-
return resource !== "auth"
|
|
5374
|
+
return resource !== "auth"
|
|
5375
|
+
&& resource !== "commands"
|
|
5376
|
+
&& resource !== "install-skill"
|
|
5377
|
+
&& resource !== "version";
|
|
5185
5378
|
}
|
|
5186
5379
|
function inferRequiresProject(resource, action, usage) {
|
|
5187
|
-
if (resource === "
|
|
5380
|
+
if (resource === "auth" || resource === "doctor" || resource === "commands"
|
|
5381
|
+
|| resource === "install-skill" || resource === "version") {
|
|
5188
5382
|
return false;
|
|
5383
|
+
}
|
|
5189
5384
|
if (PROJECT_SCOPED_RESOURCES.has(resource))
|
|
5190
5385
|
return true;
|
|
5191
5386
|
if (resource === "project" && action !== "list")
|
|
@@ -5213,13 +5408,16 @@ const STRING_OUTPUT_ACTIONS = new Set([
|
|
|
5213
5408
|
"cat",
|
|
5214
5409
|
"log",
|
|
5215
5410
|
"log-url",
|
|
5216
|
-
"preview",
|
|
5217
5411
|
]);
|
|
5218
5412
|
function inferOutputShape(resource, action) {
|
|
5219
|
-
if (resource === "auth" || resource === "install-skill"
|
|
5220
|
-
|
|
5413
|
+
if (resource === "auth" || resource === "commands" || resource === "install-skill"
|
|
5414
|
+
|| resource === "version") {
|
|
5415
|
+
return "object";
|
|
5416
|
+
}
|
|
5221
5417
|
if (ARRAY_OUTPUT_ACTIONS.has(action))
|
|
5222
5418
|
return "array";
|
|
5419
|
+
if (resource === "dataset" && action === "download")
|
|
5420
|
+
return "object";
|
|
5223
5421
|
if (STRING_OUTPUT_ACTIONS.has(action))
|
|
5224
5422
|
return "string";
|
|
5225
5423
|
return "object";
|
|
@@ -5234,8 +5432,107 @@ function inferInputContract(usage) {
|
|
|
5234
5432
|
function stripOptionalUsageGroups(usage) {
|
|
5235
5433
|
return usage.replace(/\[[^\]]*\]/g, " ");
|
|
5236
5434
|
}
|
|
5237
|
-
function
|
|
5238
|
-
return
|
|
5435
|
+
function stripAllUsageGroups(usage) {
|
|
5436
|
+
return usage.replace(/\[[^\]]*\]/g, " ").replace(/\([^)]*\)/g, " ");
|
|
5437
|
+
}
|
|
5438
|
+
function topLevelParenGroups(usage) {
|
|
5439
|
+
const groups = [];
|
|
5440
|
+
let depth = 0;
|
|
5441
|
+
let current = "";
|
|
5442
|
+
for (const char of usage) {
|
|
5443
|
+
if (char === "(") {
|
|
5444
|
+
if (depth > 0)
|
|
5445
|
+
current += char;
|
|
5446
|
+
else
|
|
5447
|
+
current = "";
|
|
5448
|
+
depth++;
|
|
5449
|
+
}
|
|
5450
|
+
else if (char === ")") {
|
|
5451
|
+
depth--;
|
|
5452
|
+
if (depth === 0)
|
|
5453
|
+
groups.push(current);
|
|
5454
|
+
else
|
|
5455
|
+
current += char;
|
|
5456
|
+
}
|
|
5457
|
+
else if (depth > 0) {
|
|
5458
|
+
current += char;
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
return groups;
|
|
5462
|
+
}
|
|
5463
|
+
function splitTopLevelChoices(group) {
|
|
5464
|
+
const parts = [];
|
|
5465
|
+
let depth = 0;
|
|
5466
|
+
let current = "";
|
|
5467
|
+
for (const char of group) {
|
|
5468
|
+
if (char === "[" || char === "(")
|
|
5469
|
+
depth++;
|
|
5470
|
+
else if (char === "]" || char === ")")
|
|
5471
|
+
depth--;
|
|
5472
|
+
if (char === "|" && depth === 0) {
|
|
5473
|
+
parts.push(current);
|
|
5474
|
+
current = "";
|
|
5475
|
+
}
|
|
5476
|
+
else {
|
|
5477
|
+
current += char;
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5480
|
+
parts.push(current);
|
|
5481
|
+
return parts;
|
|
5482
|
+
}
|
|
5483
|
+
function flagsInUsageFragment(fragment) {
|
|
5484
|
+
return extractUsageFlags(fragment.replace(/\[[^\]]*\]/g, " "));
|
|
5485
|
+
}
|
|
5486
|
+
/**
|
|
5487
|
+
* Split required usage flags into unconditional flags and mutually-exclusive
|
|
5488
|
+
* choice groups. A required `(--a X | --b Y)` group becomes a requiredOneOf entry
|
|
5489
|
+
* (pick exactly one alternative; an alternative listing several flags must be
|
|
5490
|
+
* supplied together) instead of marking every flag as unconditionally required.
|
|
5491
|
+
*/
|
|
5492
|
+
function deriveRequiredUsage(usage) {
|
|
5493
|
+
const requiredFlags = extractUsageFlags(stripAllUsageGroups(usage));
|
|
5494
|
+
const requiredOneOf = [];
|
|
5495
|
+
for (const group of topLevelParenGroups(usage)) {
|
|
5496
|
+
const alternatives = splitTopLevelChoices(group);
|
|
5497
|
+
if (alternatives.length <= 1) {
|
|
5498
|
+
requiredFlags.push(...flagsInUsageFragment(group));
|
|
5499
|
+
continue;
|
|
5500
|
+
}
|
|
5501
|
+
const oneOf = alternatives
|
|
5502
|
+
.map((alternative) => flagsInUsageFragment(alternative))
|
|
5503
|
+
.filter((alternativeFlags) => alternativeFlags.length > 0);
|
|
5504
|
+
if (oneOf.length > 1)
|
|
5505
|
+
requiredOneOf.push({ oneOf, });
|
|
5506
|
+
else if (oneOf.length === 1)
|
|
5507
|
+
requiredFlags.push(...oneOf[0]);
|
|
5508
|
+
}
|
|
5509
|
+
return { requiredFlags: uniqueStrings(requiredFlags), requiredOneOf, };
|
|
5510
|
+
}
|
|
5511
|
+
const GLOBAL_FLAG_VALUE_HINTS = {
|
|
5512
|
+
url: { valueType: "URL", },
|
|
5513
|
+
fields: { valueType: "CSV", },
|
|
5514
|
+
"api-key": { valueType: "KEY", },
|
|
5515
|
+
"request-timeout": { valueType: "MS", },
|
|
5516
|
+
retries: { valueType: "N", },
|
|
5517
|
+
"ca-cert": { valueType: "PATH", },
|
|
5518
|
+
"project-key": { valueType: "KEY", },
|
|
5519
|
+
"record-cleanup": { valueType: "PATH", },
|
|
5520
|
+
};
|
|
5521
|
+
/** Derive a value placeholder (and enum members) for each value flag from its usage token. */
|
|
5522
|
+
function extractFlagValueHints(usage) {
|
|
5523
|
+
const hints = new Map();
|
|
5524
|
+
for (const match of usage.matchAll(/--([a-z0-9-]+)\s+([a-z]+(?:\|[a-z]+)+)/g)) {
|
|
5525
|
+
const flag = FLAG_ALIASES[match[1]] ?? match[1];
|
|
5526
|
+
if (!hints.has(flag)) {
|
|
5527
|
+
hints.set(flag, { valueType: "enum", enumValues: match[2].split("|"), });
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
for (const match of usage.matchAll(/--([a-z0-9-]+)\s+(<[^>]+>|[A-Z][A-Za-z0-9_]*)/g)) {
|
|
5531
|
+
const flag = FLAG_ALIASES[match[1]] ?? match[1];
|
|
5532
|
+
if (!hints.has(flag))
|
|
5533
|
+
hints.set(flag, { valueType: match[2], });
|
|
5534
|
+
}
|
|
5535
|
+
return hints;
|
|
5239
5536
|
}
|
|
5240
5537
|
function inferPayloadSchema(inputContract) {
|
|
5241
5538
|
if (!inputContract.stdin && !inputContract.dataFlag && !inputContract.dataFileFlag) {
|
|
@@ -5288,6 +5585,8 @@ function inferAsyncKind(resource, action) {
|
|
|
5288
5585
|
}
|
|
5289
5586
|
if (resource === "data-quality" && action === "compute")
|
|
5290
5587
|
return "future";
|
|
5588
|
+
if (resource === "code" && action === "run")
|
|
5589
|
+
return "future";
|
|
5291
5590
|
return "none";
|
|
5292
5591
|
}
|
|
5293
5592
|
function inferIdempotency(sideEffect, action, usage) {
|
|
@@ -5297,6 +5596,8 @@ function inferIdempotency(sideEffect, action, usage) {
|
|
|
5297
5596
|
return "if-not-exists";
|
|
5298
5597
|
if (action.startsWith("delete") && usage.includes("--if-exists"))
|
|
5299
5598
|
return "if-exists";
|
|
5599
|
+
if (/^(clear|refresh|set|save)/.test(action))
|
|
5600
|
+
return "convergent";
|
|
5300
5601
|
return "none";
|
|
5301
5602
|
}
|
|
5302
5603
|
function inferCleanupHint(resource, action) {
|
|
@@ -5327,12 +5628,18 @@ function buildRegistryEntry(resource, action, meta) {
|
|
|
5327
5628
|
...(requiresAuth ? AUTHENTICATED_AGENT_FLAGS : []),
|
|
5328
5629
|
...(requiresProject ? ["project-key",] : []),
|
|
5329
5630
|
]);
|
|
5631
|
+
const derivedRequired = deriveRequiredUsage(meta.usage);
|
|
5330
5632
|
const requiredFlags = meta.requiredFlags
|
|
5331
5633
|
?? EXPLICIT_REGISTRY_OVERRIDES[registryKey(resource, action)]?.requiredFlags
|
|
5332
|
-
??
|
|
5634
|
+
?? derivedRequired.requiredFlags;
|
|
5635
|
+
const requiredOneOf = meta.requiredOneOf
|
|
5636
|
+
?? EXPLICIT_REGISTRY_OVERRIDES[registryKey(resource, action)]?.requiredOneOf
|
|
5637
|
+
?? derivedRequired.requiredOneOf;
|
|
5638
|
+
const oneOfFlags = new Set(requiredOneOf.flatMap((choice) => choice.oneOf.flat()));
|
|
5333
5639
|
const optionalFlags = meta.optionalFlags
|
|
5334
5640
|
?? EXPLICIT_REGISTRY_OVERRIDES[registryKey(resource, action)]?.optionalFlags
|
|
5335
|
-
?? flags.filter((flag) => !requiredFlags.includes(flag));
|
|
5641
|
+
?? flags.filter((flag) => !requiredFlags.includes(flag) && !oneOfFlags.has(flag));
|
|
5642
|
+
const valueHints = extractFlagValueHints(meta.usage);
|
|
5336
5643
|
const inputContract = inferInputContract(meta.usage);
|
|
5337
5644
|
const cleanupHint = inferCleanupHint(resource, action);
|
|
5338
5645
|
const payloadSchema = meta.payloadSchema
|
|
@@ -5349,7 +5656,20 @@ function buildRegistryEntry(resource, action, meta) {
|
|
|
5349
5656
|
usage: meta.usage,
|
|
5350
5657
|
description: meta.description,
|
|
5351
5658
|
examples: meta.examples,
|
|
5352
|
-
flags: flags.map((name) =>
|
|
5659
|
+
flags: flags.map((name) => {
|
|
5660
|
+
const kind = flagKind(name);
|
|
5661
|
+
if (kind === "boolean")
|
|
5662
|
+
return { name, kind, };
|
|
5663
|
+
const hint = valueHints.get(name) ?? GLOBAL_FLAG_VALUE_HINTS[name];
|
|
5664
|
+
if (!hint)
|
|
5665
|
+
return { name, kind, };
|
|
5666
|
+
return {
|
|
5667
|
+
name,
|
|
5668
|
+
kind,
|
|
5669
|
+
valueType: hint.valueType,
|
|
5670
|
+
...(hint.enumValues ? { enumValues: hint.enumValues, } : {}),
|
|
5671
|
+
};
|
|
5672
|
+
}),
|
|
5353
5673
|
positionals: extractPositionals(meta.usage),
|
|
5354
5674
|
sideEffect,
|
|
5355
5675
|
requiresAuth,
|
|
@@ -5365,6 +5685,7 @@ function buildRegistryEntry(resource, action, meta) {
|
|
|
5365
5685
|
dryRun: meta.usage.includes("--dry-run"),
|
|
5366
5686
|
requiredFlags: uniqueStrings(requiredFlags),
|
|
5367
5687
|
optionalFlags: uniqueStrings(optionalFlags),
|
|
5688
|
+
...(requiredOneOf.length > 0 ? { requiredOneOf, } : {}),
|
|
5368
5689
|
...(payloadSchema ? { payloadSchema, } : {}),
|
|
5369
5690
|
...(examplePayload !== undefined ? { examplePayload, } : {}),
|
|
5370
5691
|
...(cleanupCommand ? { cleanupCommand, } : {}),
|
|
@@ -5388,6 +5709,14 @@ function buildCommandRegistry() {
|
|
|
5388
5709
|
examples: COMMANDS_EXAMPLES,
|
|
5389
5710
|
}),
|
|
5390
5711
|
};
|
|
5712
|
+
registry.version = {
|
|
5713
|
+
run: buildRegistryEntry("version", "run", {
|
|
5714
|
+
handler: async () => undefined,
|
|
5715
|
+
usage: VERSION_USAGE,
|
|
5716
|
+
description: VERSION_DESCRIPTION,
|
|
5717
|
+
examples: VERSION_EXAMPLES,
|
|
5718
|
+
}),
|
|
5719
|
+
};
|
|
5391
5720
|
registry["install-skill"] = {
|
|
5392
5721
|
run: buildRegistryEntry("install-skill", "run", {
|
|
5393
5722
|
handler: async () => undefined,
|
|
@@ -5412,6 +5741,16 @@ function buildCommandRegistry() {
|
|
|
5412
5741
|
examples: FIXTURES_EXAMPLES,
|
|
5413
5742
|
}),
|
|
5414
5743
|
};
|
|
5744
|
+
registry.batch = {
|
|
5745
|
+
run: buildRegistryEntry("batch", "run", {
|
|
5746
|
+
handler: async () => undefined,
|
|
5747
|
+
usage: BATCH_USAGE,
|
|
5748
|
+
description: BATCH_DESCRIPTION,
|
|
5749
|
+
examples: BATCH_EXAMPLES,
|
|
5750
|
+
examplePayload: BATCH_EXAMPLE_PAYLOAD,
|
|
5751
|
+
payloadSchema: { stdin: true, dataFlag: true, dataFileFlag: true, jsonShape: "array", },
|
|
5752
|
+
}),
|
|
5753
|
+
};
|
|
5415
5754
|
registry.auth = {};
|
|
5416
5755
|
for (const [action, meta,] of Object.entries(AUTH_ACTIONS)) {
|
|
5417
5756
|
registry.auth[action] = buildRegistryEntry("auth", action, {
|
|
@@ -5419,6 +5758,7 @@ function buildCommandRegistry() {
|
|
|
5419
5758
|
usage: meta.usage,
|
|
5420
5759
|
description: meta.description,
|
|
5421
5760
|
examples: meta.examples,
|
|
5761
|
+
requiredFlags: meta.requiredFlags,
|
|
5422
5762
|
});
|
|
5423
5763
|
}
|
|
5424
5764
|
return registry;
|
|
@@ -6041,10 +6381,10 @@ async function runCleanup(flags) {
|
|
|
6041
6381
|
}
|
|
6042
6382
|
const { url, apiKey, projectKey, tlsRejectUnauthorized, caCertPath, } = resolveCredentials(flags);
|
|
6043
6383
|
if (!url) {
|
|
6044
|
-
throw new UsageError("Missing Dataiku URL. Set DATAIKU_URL
|
|
6384
|
+
throw new UsageError("Missing Dataiku URL. Set DATAIKU_URL or pass --url.", "missing_required_flag");
|
|
6045
6385
|
}
|
|
6046
6386
|
if (!apiKey) {
|
|
6047
|
-
throw new UsageError("Missing API key. Set DATAIKU_API_KEY
|
|
6387
|
+
throw new UsageError("Missing API key. Set DATAIKU_API_KEY or pass --api-key.", "missing_required_flag");
|
|
6048
6388
|
}
|
|
6049
6389
|
const requestTimeoutMs = num(flags["request-timeout"]);
|
|
6050
6390
|
const retryMaxAttempts = num(flags["retries"]);
|
|
@@ -6064,7 +6404,8 @@ async function runCleanup(flags) {
|
|
|
6064
6404
|
try {
|
|
6065
6405
|
const parsed = parseArgs(entry.cleanup.argv);
|
|
6066
6406
|
const [resource, action, ...args] = parsed.positional;
|
|
6067
|
-
if (!resource || !action || !
|
|
6407
|
+
if (!resource || !action || !isAllowedCleanupAction(resource, action)
|
|
6408
|
+
|| !commands[resource]?.[action]) {
|
|
6068
6409
|
throw new UsageError(`Invalid cleanup argv: ${entry.cleanup.argv.join(" ")}`);
|
|
6069
6410
|
}
|
|
6070
6411
|
const result = await commands[resource][action].handler(client, args, parsed.flags);
|
|
@@ -6090,35 +6431,128 @@ async function runCleanup(flags) {
|
|
|
6090
6431
|
exitCode: failures.length > 0 ? 2 : 0,
|
|
6091
6432
|
};
|
|
6092
6433
|
}
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6434
|
+
const BATCH_USAGE = "dss batch (--data JSON|--data-file PATH|--stdin) [--continue-on-error] [--dry-run]";
|
|
6435
|
+
const BATCH_DESCRIPTION = "Run a sequence of dss commands from a JSON array of argv arrays. Fail-fast by default; returns one envelope with a per-step ok/result/error and exits non-zero if any step failed.";
|
|
6436
|
+
const BATCH_HINT = 'Pass a JSON array of argv arrays, e.g. [["dataset","list"],["recipe","update","r","--data-file","p.json"]].';
|
|
6437
|
+
const BATCH_EXAMPLE_PAYLOAD = [
|
|
6438
|
+
["recipe", "set-payload", "compute_orders", "--file", "code.py", "--no-backup",],
|
|
6439
|
+
["recipe", "update", "compute_orders", "--data-file", "env.json",],
|
|
6440
|
+
["dataset", "update", "orders", "--data-file", "ds.json",],
|
|
6441
|
+
];
|
|
6442
|
+
const BATCH_EXAMPLES = [
|
|
6443
|
+
"dss batch --data-file steps.json",
|
|
6444
|
+
"dss batch --stdin --continue-on-error",
|
|
6445
|
+
];
|
|
6446
|
+
function parseBatchSteps(payload) {
|
|
6447
|
+
if (!Array.isArray(payload)) {
|
|
6448
|
+
throw new UsageError("Batch payload must be a JSON array of command-argument arrays.", "validation_failed", BATCH_HINT, { example: BATCH_EXAMPLE_PAYLOAD, });
|
|
6449
|
+
}
|
|
6450
|
+
return payload.map((step, index) => {
|
|
6451
|
+
if (!Array.isArray(step) || !step.every((token) => typeof token === "string")) {
|
|
6452
|
+
throw new UsageError(`Batch step ${index} must be an array of string arguments.`, "validation_failed", BATCH_HINT);
|
|
6453
|
+
}
|
|
6454
|
+
return step;
|
|
6104
6455
|
});
|
|
6105
6456
|
}
|
|
6106
|
-
function
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6457
|
+
async function runBatch(flags) {
|
|
6458
|
+
const payload = unknownJsonInput(flags);
|
|
6459
|
+
if (payload === undefined) {
|
|
6460
|
+
throw new UsageError(`Provide steps via --data, --data-file, or --stdin. Usage: ${BATCH_USAGE}`, "missing_required_flag", BATCH_HINT);
|
|
6461
|
+
}
|
|
6462
|
+
const steps = parseBatchSteps(payload);
|
|
6463
|
+
if (flags["dry-run"] === true) {
|
|
6464
|
+
const planned = steps.map((argv, index) => {
|
|
6465
|
+
const { positional, } = parseArgs(argv);
|
|
6466
|
+
const resource = positional[0];
|
|
6467
|
+
const action = positional[1];
|
|
6468
|
+
const runnable = Boolean(resource && action && commands[resource]?.[action]);
|
|
6469
|
+
return { index, args: argv, resource, action, runnable, };
|
|
6112
6470
|
});
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6471
|
+
return {
|
|
6472
|
+
result: { dryRun: true, total: steps.length, steps: planned, },
|
|
6473
|
+
exitCode: planned.every((step) => step.runnable) ? 0 : 1,
|
|
6474
|
+
};
|
|
6475
|
+
}
|
|
6476
|
+
const { url, apiKey, projectKey, tlsRejectUnauthorized, caCertPath, } = resolveCredentials(flags);
|
|
6477
|
+
if (!url) {
|
|
6478
|
+
throw new UsageError("Missing Dataiku URL.", "missing_required_flag", "Set DATAIKU_URL or pass --url.", {
|
|
6479
|
+
requiredFlags: ["url",],
|
|
6480
|
+
env: ["DATAIKU_URL",],
|
|
6120
6481
|
});
|
|
6482
|
+
}
|
|
6483
|
+
if (!apiKey) {
|
|
6484
|
+
throw new UsageError("Missing API key.", "missing_required_flag", "Set DATAIKU_API_KEY or pass --api-key.", {
|
|
6485
|
+
requiredFlags: ["api-key",],
|
|
6486
|
+
env: ["DATAIKU_API_KEY",],
|
|
6487
|
+
});
|
|
6488
|
+
}
|
|
6489
|
+
const client = new DataikuClient({
|
|
6490
|
+
url,
|
|
6491
|
+
apiKey,
|
|
6492
|
+
projectKey,
|
|
6493
|
+
verbose: flags["verbose"] === true,
|
|
6494
|
+
requestTimeoutMs: num(flags["request-timeout"]),
|
|
6495
|
+
retryMaxAttempts: num(flags["retries"]),
|
|
6496
|
+
tlsRejectUnauthorized,
|
|
6497
|
+
caCertPath,
|
|
6121
6498
|
});
|
|
6499
|
+
const continueOnError = flags["continue-on-error"] === true;
|
|
6500
|
+
const results = [];
|
|
6501
|
+
let firstFailureExit;
|
|
6502
|
+
for (let index = 0; index < steps.length; index++) {
|
|
6503
|
+
const argv = steps[index];
|
|
6504
|
+
const { positional, flags: stepFlags, } = parseArgs(argv);
|
|
6505
|
+
const resource = positional[0];
|
|
6506
|
+
const action = positional[1];
|
|
6507
|
+
if (firstFailureExit !== undefined && !continueOnError) {
|
|
6508
|
+
results.push({ index, args: argv, resource, action, ok: null, skipped: true, });
|
|
6509
|
+
continue;
|
|
6510
|
+
}
|
|
6511
|
+
currentCommandContext = {
|
|
6512
|
+
resource,
|
|
6513
|
+
action,
|
|
6514
|
+
projectKey: typeof stepFlags["project-key"] === "string" ? stepFlags["project-key"] : projectKey,
|
|
6515
|
+
};
|
|
6516
|
+
try {
|
|
6517
|
+
if (!resource)
|
|
6518
|
+
throw noCommandError();
|
|
6519
|
+
const resourceActions = commands[resource];
|
|
6520
|
+
if (!resourceActions)
|
|
6521
|
+
throw unknownResourceError(resource);
|
|
6522
|
+
if (!action) {
|
|
6523
|
+
throw missingActionError(resource, Object.keys(resourceActions), `dss ${resource} <action>`);
|
|
6524
|
+
}
|
|
6525
|
+
const meta = resourceActions[action];
|
|
6526
|
+
if (!meta)
|
|
6527
|
+
throw unknownActionError(resource, action, Object.keys(resourceActions));
|
|
6528
|
+
const result = await meta.handler(client, positional.slice(2), stepFlags);
|
|
6529
|
+
const failureExitCode = commandFailureExitCode(result);
|
|
6530
|
+
if (failureExitCode !== undefined)
|
|
6531
|
+
throw new CommandResultFailure(result, failureExitCode);
|
|
6532
|
+
const stepFieldsFlag = stepFlags["fields"];
|
|
6533
|
+
const stepFields = typeof stepFieldsFlag === "string"
|
|
6534
|
+
? stepFieldsFlag.split(",").map((field) => field.trim()).filter((field) => field.length > 0)
|
|
6535
|
+
: [];
|
|
6536
|
+
const stepResult = stepFields.length > 0 ? projectResultFields(result, stepFields) : result;
|
|
6537
|
+
results.push({ index, args: argv, resource, action, ok: true, result: stepResult, });
|
|
6538
|
+
}
|
|
6539
|
+
catch (error) {
|
|
6540
|
+
const envelope = buildErrorReport(error);
|
|
6541
|
+
results.push({ index, args: argv, resource, action, ok: false, error: envelope, });
|
|
6542
|
+
if (firstFailureExit === undefined)
|
|
6543
|
+
firstFailureExit = envelope.exitCode;
|
|
6544
|
+
}
|
|
6545
|
+
}
|
|
6546
|
+
const ok = firstFailureExit === undefined;
|
|
6547
|
+
return {
|
|
6548
|
+
result: {
|
|
6549
|
+
ok,
|
|
6550
|
+
total: steps.length,
|
|
6551
|
+
completed: results.filter((step) => step.ok !== null).length,
|
|
6552
|
+
steps: results,
|
|
6553
|
+
},
|
|
6554
|
+
exitCode: ok ? 0 : firstFailureExit ?? 2,
|
|
6555
|
+
};
|
|
6122
6556
|
}
|
|
6123
6557
|
// ---------------------------------------------------------------------------
|
|
6124
6558
|
// Credential resolution
|
|
@@ -6131,12 +6565,15 @@ function resolveCredentials(flags) {
|
|
|
6131
6565
|
let apiKey = hasApiKeyFlag ? flags["api-key"] : undefined;
|
|
6132
6566
|
let projectKey = hasProjectKeyFlag ? flags["project-key"] : undefined;
|
|
6133
6567
|
const saved = loadCredentials();
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6568
|
+
const useEnv = dataikuEnvironmentEnabled();
|
|
6569
|
+
if (useEnv) {
|
|
6570
|
+
if (!hasUrlFlag)
|
|
6571
|
+
url ??= process.env.DATAIKU_URL;
|
|
6572
|
+
if (!hasApiKeyFlag)
|
|
6573
|
+
apiKey ??= process.env.DATAIKU_API_KEY;
|
|
6574
|
+
if (!hasProjectKeyFlag)
|
|
6575
|
+
projectKey ??= process.env.DATAIKU_PROJECT_KEY;
|
|
6576
|
+
}
|
|
6140
6577
|
if (saved) {
|
|
6141
6578
|
if (!hasUrlFlag)
|
|
6142
6579
|
url ??= saved.url;
|
|
@@ -6153,10 +6590,6 @@ function resolveCredentials(flags) {
|
|
|
6153
6590
|
};
|
|
6154
6591
|
}
|
|
6155
6592
|
let currentCommandContext = {};
|
|
6156
|
-
function isReportJsonRequested() {
|
|
6157
|
-
return process.env.DSS_REPORT_JSON === "1"
|
|
6158
|
-
|| process.argv.slice(2).some((arg) => arg === "--report-json" || arg.startsWith("--report-json="));
|
|
6159
|
-
}
|
|
6160
6593
|
function rawFlagValue(argv, flagName) {
|
|
6161
6594
|
const longFlag = `--${flagName}`;
|
|
6162
6595
|
for (let index = 0; index < argv.length; index++) {
|
|
@@ -6170,6 +6603,12 @@ function rawFlagValue(argv, flagName) {
|
|
|
6170
6603
|
}
|
|
6171
6604
|
return undefined;
|
|
6172
6605
|
}
|
|
6606
|
+
function commandIsProjectScoped(resource, action) {
|
|
6607
|
+
if (!resource)
|
|
6608
|
+
return false;
|
|
6609
|
+
const usage = commands[resource]?.[action ?? ""]?.usage ?? "";
|
|
6610
|
+
return inferRequiresProject(resource, action ?? "", usage);
|
|
6611
|
+
}
|
|
6173
6612
|
function rawCommandContext() {
|
|
6174
6613
|
const argv = process.argv.slice(2);
|
|
6175
6614
|
const positionals = [];
|
|
@@ -6194,13 +6633,19 @@ function rawCommandContext() {
|
|
|
6194
6633
|
}
|
|
6195
6634
|
positionals.push(arg);
|
|
6196
6635
|
}
|
|
6636
|
+
const resource = currentCommandContext.resource ?? positionals[0];
|
|
6637
|
+
const action = currentCommandContext.action ?? positionals[1];
|
|
6638
|
+
const explicitProjectKey = rawFlagValue(argv, "project-key") ?? rawFlagValue(argv, "project");
|
|
6639
|
+
const ambientProjectKey = dataikuEnvironmentEnabled()
|
|
6640
|
+
? process.env.DATAIKU_PROJECT_KEY
|
|
6641
|
+
: undefined;
|
|
6197
6642
|
return {
|
|
6198
|
-
resource
|
|
6199
|
-
action
|
|
6200
|
-
projectKey:
|
|
6201
|
-
??
|
|
6202
|
-
|
|
6203
|
-
|
|
6643
|
+
resource,
|
|
6644
|
+
action,
|
|
6645
|
+
projectKey: explicitProjectKey
|
|
6646
|
+
?? (commandIsProjectScoped(resource, action)
|
|
6647
|
+
? currentCommandContext.projectKey ?? ambientProjectKey
|
|
6648
|
+
: undefined),
|
|
6204
6649
|
};
|
|
6205
6650
|
}
|
|
6206
6651
|
function requestIdFromBody(body) {
|
|
@@ -6213,26 +6658,52 @@ function requestIdFromBody(body) {
|
|
|
6213
6658
|
return undefined;
|
|
6214
6659
|
}
|
|
6215
6660
|
}
|
|
6661
|
+
function errorExitCode(err) {
|
|
6662
|
+
if (err instanceof CommandResultFailure)
|
|
6663
|
+
return err.exitCode;
|
|
6664
|
+
if (err instanceof UsageError)
|
|
6665
|
+
return 1;
|
|
6666
|
+
if (err instanceof DataikuError)
|
|
6667
|
+
return err.category === "transient" ? 3 : 2;
|
|
6668
|
+
return 2;
|
|
6669
|
+
}
|
|
6216
6670
|
function buildErrorReport(err) {
|
|
6217
6671
|
const context = rawCommandContext();
|
|
6672
|
+
const exitCode = errorExitCode(err);
|
|
6218
6673
|
if (err instanceof UsageError) {
|
|
6219
6674
|
return {
|
|
6675
|
+
ok: false,
|
|
6676
|
+
error: err.message,
|
|
6220
6677
|
code: err.code,
|
|
6221
6678
|
category: "usage",
|
|
6222
|
-
|
|
6679
|
+
exitCode,
|
|
6223
6680
|
...(err.hint ? { hint: err.hint, } : {}),
|
|
6681
|
+
...(err.details ? { details: err.details, } : {}),
|
|
6682
|
+
...context,
|
|
6683
|
+
};
|
|
6684
|
+
}
|
|
6685
|
+
if (err instanceof CommandResultFailure) {
|
|
6686
|
+
return {
|
|
6687
|
+
ok: false,
|
|
6688
|
+
error: err.message,
|
|
6689
|
+
code: "long_running_failure",
|
|
6690
|
+
category: "dss",
|
|
6691
|
+
exitCode: err.exitCode,
|
|
6692
|
+
details: { result: err.result, },
|
|
6224
6693
|
...context,
|
|
6225
6694
|
};
|
|
6226
6695
|
}
|
|
6227
6696
|
if (err instanceof DataikuError) {
|
|
6228
6697
|
return {
|
|
6698
|
+
ok: false,
|
|
6699
|
+
error: err.message,
|
|
6229
6700
|
code: dataikuErrorCode(err.category),
|
|
6230
6701
|
category: "dss",
|
|
6231
|
-
|
|
6702
|
+
exitCode,
|
|
6232
6703
|
hint: err.retryHint,
|
|
6233
6704
|
status: err.status,
|
|
6234
6705
|
retryable: err.retryable,
|
|
6235
|
-
requestId: requestIdFromBody(err.body),
|
|
6706
|
+
requestId: err.requestId ?? requestIdFromBody(err.body),
|
|
6236
6707
|
details: {
|
|
6237
6708
|
dssCategory: err.category,
|
|
6238
6709
|
statusText: err.statusText,
|
|
@@ -6244,190 +6715,109 @@ function buildErrorReport(err) {
|
|
|
6244
6715
|
}
|
|
6245
6716
|
const message = err instanceof Error ? err.message : String(err);
|
|
6246
6717
|
return {
|
|
6718
|
+
ok: false,
|
|
6719
|
+
error: message,
|
|
6247
6720
|
code: "internal_error",
|
|
6248
6721
|
category: "internal",
|
|
6249
|
-
|
|
6722
|
+
exitCode,
|
|
6250
6723
|
...context,
|
|
6251
6724
|
};
|
|
6252
6725
|
}
|
|
6253
6726
|
function writeErrorReport(err) {
|
|
6254
6727
|
process.stderr.write(`${JSON.stringify(buildErrorReport(err), null, 2)}\n`);
|
|
6255
6728
|
}
|
|
6256
|
-
function commandRegistryEntry(resource, action) {
|
|
6257
|
-
return buildCommandRegistry()[resource]?.[action];
|
|
6258
|
-
}
|
|
6259
|
-
function writeReportHelp(resource, action) {
|
|
6260
|
-
const entry = commandRegistryEntry(resource, action);
|
|
6261
|
-
if (entry) {
|
|
6262
|
-
process.stderr.write(`${JSON.stringify(entry, null, 2)}\n`);
|
|
6263
|
-
return;
|
|
6264
|
-
}
|
|
6265
|
-
process.stderr.write(`${JSON.stringify({
|
|
6266
|
-
code: "usage_error",
|
|
6267
|
-
category: "usage",
|
|
6268
|
-
message: `No registry entry for ${resource} ${action}.`,
|
|
6269
|
-
resource,
|
|
6270
|
-
action,
|
|
6271
|
-
}, null, 2)}\n`);
|
|
6272
|
-
}
|
|
6273
6729
|
// ---------------------------------------------------------------------------
|
|
6274
6730
|
// Main
|
|
6275
6731
|
// ---------------------------------------------------------------------------
|
|
6276
6732
|
async function main() {
|
|
6277
6733
|
loadEnvFile();
|
|
6278
6734
|
const { positional, flags, } = parseArgs(process.argv.slice(2));
|
|
6279
|
-
|
|
6280
|
-
if (
|
|
6281
|
-
|
|
6282
|
-
|
|
6735
|
+
const fieldsFlag = flags["fields"];
|
|
6736
|
+
if (typeof fieldsFlag === "string") {
|
|
6737
|
+
const selected = fieldsFlag.split(",").map((field) => field.trim()).filter((field) => field.length > 0);
|
|
6738
|
+
if (selected.length > 0)
|
|
6739
|
+
outputFieldProjection = selected;
|
|
6283
6740
|
}
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
if (flags["help"])
|
|
6288
|
-
process.exit(0);
|
|
6289
|
-
process.exit(1);
|
|
6741
|
+
if (flags["version"] === true) {
|
|
6742
|
+
writeCommandResult(cliVersionResult());
|
|
6743
|
+
return;
|
|
6290
6744
|
}
|
|
6745
|
+
if (positional.length === 0)
|
|
6746
|
+
throw noCommandError();
|
|
6291
6747
|
const resource = positional[0];
|
|
6292
6748
|
currentCommandContext = {
|
|
6293
6749
|
resource,
|
|
6294
6750
|
action: positional[1],
|
|
6295
6751
|
projectKey: typeof flags["project-key"] === "string"
|
|
6296
6752
|
? flags["project-key"]
|
|
6297
|
-
:
|
|
6753
|
+
: dataikuEnvironmentEnabled()
|
|
6754
|
+
? process.env.DATAIKU_PROJECT_KEY
|
|
6755
|
+
: undefined,
|
|
6298
6756
|
};
|
|
6299
6757
|
if (resource === "doctor") {
|
|
6300
6758
|
const action = positional[1];
|
|
6301
|
-
|
|
6302
|
-
if (flags["report-json"] === true)
|
|
6303
|
-
writeReportHelp("doctor", "run");
|
|
6304
|
-
else
|
|
6305
|
-
printActionHelp("doctor", "run");
|
|
6306
|
-
process.exit(0);
|
|
6307
|
-
}
|
|
6759
|
+
currentCommandContext.action = action ?? "run";
|
|
6308
6760
|
if (action !== undefined && action !== "run") {
|
|
6309
|
-
throw
|
|
6761
|
+
throw unknownActionError("doctor", action, ["run",]);
|
|
6310
6762
|
}
|
|
6311
6763
|
const { result, exitCode, } = await runDoctor(flags);
|
|
6312
6764
|
writeCommandResult(result);
|
|
6313
|
-
|
|
6765
|
+
if (exitCode !== 0)
|
|
6766
|
+
process.exit(exitCode);
|
|
6767
|
+
return;
|
|
6314
6768
|
}
|
|
6315
|
-
// Auth commands — dispatched before client creation
|
|
6316
6769
|
if (resource === "auth") {
|
|
6317
6770
|
const action = positional[1];
|
|
6771
|
+
const validActions = Object.keys(AUTH_ACTIONS);
|
|
6318
6772
|
if (!action) {
|
|
6319
|
-
|
|
6320
|
-
const lines = [
|
|
6321
|
-
"Usage: dss auth <action> [--flags]",
|
|
6322
|
-
"",
|
|
6323
|
-
"Actions:",
|
|
6324
|
-
...Object.entries(AUTH_ACTIONS).map(([name, meta,]) => ` ${name.padEnd(maxName + 2)}${meta.description ?? meta.usage}`),
|
|
6325
|
-
"",
|
|
6326
|
-
"Run 'dss auth <action> --help' for details and examples.",
|
|
6327
|
-
];
|
|
6328
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
6329
|
-
process.exit(flags["help"] === true ? 0 : 1);
|
|
6773
|
+
throw missingActionError("auth", validActions, "dss auth login --url URL --api-key KEY");
|
|
6330
6774
|
}
|
|
6775
|
+
currentCommandContext.action = action;
|
|
6331
6776
|
const authMeta = AUTH_ACTIONS[action];
|
|
6332
|
-
if (!authMeta)
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
process.stderr.write(`Unknown action: auth ${action}\nAvailable: ${Object.keys(AUTH_ACTIONS).join(", ")}\n`);
|
|
6337
|
-
process.exit(1);
|
|
6338
|
-
}
|
|
6339
|
-
if (flags["help"] === true) {
|
|
6340
|
-
if (flags["report-json"] === true) {
|
|
6341
|
-
writeReportHelp("auth", action);
|
|
6342
|
-
}
|
|
6343
|
-
else {
|
|
6344
|
-
const lines = [];
|
|
6345
|
-
if (authMeta.description)
|
|
6346
|
-
lines.push(authMeta.description, "");
|
|
6347
|
-
lines.push(`Usage: ${authMeta.usage}`);
|
|
6348
|
-
if (authMeta.examples && authMeta.examples.length > 0) {
|
|
6349
|
-
lines.push("", "Examples:");
|
|
6350
|
-
for (const ex of authMeta.examples)
|
|
6351
|
-
lines.push(` ${ex}`);
|
|
6352
|
-
}
|
|
6353
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
6354
|
-
}
|
|
6355
|
-
process.exit(0);
|
|
6356
|
-
}
|
|
6357
|
-
await authMeta.handler(flags);
|
|
6777
|
+
if (!authMeta)
|
|
6778
|
+
throw unknownActionError("auth", action, validActions);
|
|
6779
|
+
const result = await authMeta.handler(flags);
|
|
6780
|
+
writeCommandResult(result);
|
|
6358
6781
|
return;
|
|
6359
6782
|
}
|
|
6360
|
-
// install-skill — dispatched before client creation
|
|
6361
6783
|
if (resource === "install-skill") {
|
|
6362
|
-
const
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6366
|
-
}
|
|
6367
|
-
else {
|
|
6368
|
-
const lines = [
|
|
6369
|
-
`Usage: ${INSTALL_SKILL_USAGE}`,
|
|
6370
|
-
"",
|
|
6371
|
-
INSTALL_SKILL_DESCRIPTION,
|
|
6372
|
-
"",
|
|
6373
|
-
"Flags:",
|
|
6374
|
-
" --global Install to user-level global scope (default: project)",
|
|
6375
|
-
" --agent NAME Target a specific agent: claude, codex, cursor, pi, omp",
|
|
6376
|
-
" --target PATH Project directory to install into (default: workspace root)",
|
|
6377
|
-
" --list-agents Print detected agents and exit",
|
|
6378
|
-
" --dry-run Print planned skill installs without writing files",
|
|
6379
|
-
" --plan Print planned skill installs without writing files",
|
|
6380
|
-
];
|
|
6381
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
6382
|
-
}
|
|
6383
|
-
process.exit(0);
|
|
6384
|
-
}
|
|
6385
|
-
if (installSkillAction !== undefined && installSkillAction !== "run") {
|
|
6386
|
-
throw new UsageError(`Usage: ${INSTALL_SKILL_USAGE}`);
|
|
6784
|
+
const action = positional[1];
|
|
6785
|
+
currentCommandContext.action = action ?? "run";
|
|
6786
|
+
if (action !== undefined && action !== "run") {
|
|
6787
|
+
throw unknownActionError("install-skill", action, ["run",]);
|
|
6387
6788
|
}
|
|
6388
|
-
const listOnly = flags["list-agents"] === true;
|
|
6389
6789
|
const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
|
|
6390
6790
|
const isGlobal = flags["global"] === true;
|
|
6391
6791
|
const targetDir = typeof flags["target"] === "string" ? flags["target"] : undefined;
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6792
|
+
const targets = (() => {
|
|
6793
|
+
if (!agentFilter)
|
|
6794
|
+
return detectAgents();
|
|
6395
6795
|
const def = AGENTS[agentFilter];
|
|
6396
6796
|
if (!def) {
|
|
6397
|
-
throw new UsageError(`Unknown agent: ${agentFilter}
|
|
6797
|
+
throw new UsageError(`Unknown agent: ${agentFilter}.`, "usage_error", COMMANDS_RUN_HINT, { agent: agentFilter, validAgents: Object.keys(AGENTS), });
|
|
6398
6798
|
}
|
|
6399
|
-
|
|
6400
|
-
}
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
for (const t of targets) {
|
|
6411
|
-
process.stderr.write(` ${t.id} (${t.def.name}, via ${t.via})\n`);
|
|
6412
|
-
}
|
|
6413
|
-
}
|
|
6414
|
-
process.exit(0);
|
|
6799
|
+
return [{ id: agentFilter, def, via: "flag", },];
|
|
6800
|
+
})();
|
|
6801
|
+
if (flags["list-agents"] === true) {
|
|
6802
|
+
writeCommandResult({
|
|
6803
|
+
agents: targets.map((target) => ({
|
|
6804
|
+
id: target.id,
|
|
6805
|
+
name: target.def.name,
|
|
6806
|
+
via: target.via,
|
|
6807
|
+
})),
|
|
6808
|
+
});
|
|
6809
|
+
return;
|
|
6415
6810
|
}
|
|
6416
6811
|
if (targets.length === 0) {
|
|
6417
|
-
throw new UsageError("No coding agents detected.
|
|
6812
|
+
throw new UsageError("No coding agents detected.", "usage_error", "Use --agent NAME to choose one of the supported agents.", { validAgents: Object.keys(AGENTS), });
|
|
6418
6813
|
}
|
|
6419
6814
|
const scope = isGlobal ? "global" : "project";
|
|
6420
6815
|
const cwd = targetDir ?? (isGlobal ? process.cwd() : findWorkspaceRoot(process.cwd()));
|
|
6816
|
+
const installed = planSkillInstalls(targets, { global: isGlobal, cwd, });
|
|
6421
6817
|
if (flags["plan"] === true) {
|
|
6422
6818
|
writeCommandResult(planResult("install-skill", "run", {
|
|
6423
6819
|
identifiers: { scope, target: cwd, },
|
|
6424
|
-
payload: {
|
|
6425
|
-
agents: targets.map((target) => ({
|
|
6426
|
-
id: target.id,
|
|
6427
|
-
name: target.def.name,
|
|
6428
|
-
via: target.via,
|
|
6429
|
-
})),
|
|
6430
|
-
},
|
|
6820
|
+
payload: { installed, },
|
|
6431
6821
|
idempotency: "none",
|
|
6432
6822
|
asyncKind: "none",
|
|
6433
6823
|
exitCodesOnFailure: { usage: 1, error: 2, transient: 3, },
|
|
@@ -6435,163 +6825,93 @@ async function main() {
|
|
|
6435
6825
|
}));
|
|
6436
6826
|
return;
|
|
6437
6827
|
}
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
id: target.id,
|
|
6447
|
-
name: target.def.name,
|
|
6448
|
-
via: target.via,
|
|
6449
|
-
})),
|
|
6450
|
-
});
|
|
6451
|
-
return;
|
|
6452
|
-
}
|
|
6453
|
-
process.stderr.write(`Installing dataiku-dss skill (${scope} scope):\n`);
|
|
6454
|
-
const results = installSkill(targets, { global: isGlobal, cwd, });
|
|
6455
|
-
for (const r of results) {
|
|
6456
|
-
process.stderr.write(` ${r.agent} -> ${r.path}\n`);
|
|
6457
|
-
}
|
|
6458
|
-
if (results.length > 0) {
|
|
6459
|
-
process.stderr.write(`\nDone. ${results.length} skill(s) installed.\n`);
|
|
6460
|
-
}
|
|
6828
|
+
writeCommandResult({
|
|
6829
|
+
scope,
|
|
6830
|
+
target: cwd,
|
|
6831
|
+
installed: flags["dry-run"] === true
|
|
6832
|
+
? installed
|
|
6833
|
+
: installSkill(targets, { global: isGlobal, cwd, }),
|
|
6834
|
+
...(flags["dry-run"] === true ? { dryRun: true, } : {}),
|
|
6835
|
+
});
|
|
6461
6836
|
return;
|
|
6462
6837
|
}
|
|
6463
|
-
// commands — machine-readable introspection (no auth needed)
|
|
6464
6838
|
if (resource === "commands") {
|
|
6465
6839
|
const action = positional[1];
|
|
6466
|
-
if (
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
...COMMANDS_EXAMPLES.map((example) => ` ${example}`),
|
|
6478
|
-
];
|
|
6479
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
6480
|
-
}
|
|
6481
|
-
process.exit(0);
|
|
6482
|
-
}
|
|
6840
|
+
if (!action)
|
|
6841
|
+
throw missingActionError("commands", ["run",], COMMANDS_USAGE);
|
|
6842
|
+
currentCommandContext.action = action;
|
|
6843
|
+
if (action !== "run")
|
|
6844
|
+
throw unknownActionError("commands", action, ["run",]);
|
|
6845
|
+
writeCommandResult(buildCommandRegistry());
|
|
6846
|
+
return;
|
|
6847
|
+
}
|
|
6848
|
+
if (resource === "version") {
|
|
6849
|
+
const action = positional[1];
|
|
6850
|
+
currentCommandContext.action = action ?? "run";
|
|
6483
6851
|
if (action !== undefined && action !== "run") {
|
|
6484
|
-
throw
|
|
6852
|
+
throw unknownActionError("version", action, ["run",]);
|
|
6485
6853
|
}
|
|
6486
|
-
writeCommandResult(
|
|
6854
|
+
writeCommandResult(cliVersionResult());
|
|
6487
6855
|
return;
|
|
6488
6856
|
}
|
|
6489
6857
|
if (resource === "cleanup") {
|
|
6490
6858
|
const action = positional[1];
|
|
6491
|
-
|
|
6492
|
-
if (flags["report-json"] === true) {
|
|
6493
|
-
writeReportHelp("cleanup", "run");
|
|
6494
|
-
}
|
|
6495
|
-
else {
|
|
6496
|
-
const lines = [
|
|
6497
|
-
`Usage: ${CLEANUP_USAGE}`,
|
|
6498
|
-
"",
|
|
6499
|
-
CLEANUP_DESCRIPTION,
|
|
6500
|
-
"",
|
|
6501
|
-
"Examples:",
|
|
6502
|
-
...CLEANUP_EXAMPLES.map((example) => ` ${example}`),
|
|
6503
|
-
];
|
|
6504
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
6505
|
-
}
|
|
6506
|
-
process.exit(0);
|
|
6507
|
-
}
|
|
6859
|
+
currentCommandContext.action = action ?? "run";
|
|
6508
6860
|
if (action !== undefined && action !== "run") {
|
|
6509
|
-
throw
|
|
6861
|
+
throw unknownActionError("cleanup", action, ["run",]);
|
|
6510
6862
|
}
|
|
6511
6863
|
const { result, exitCode, } = await runCleanup(flags);
|
|
6512
6864
|
writeCommandResult(result);
|
|
6513
|
-
|
|
6865
|
+
if (exitCode !== 0)
|
|
6866
|
+
process.exit(exitCode);
|
|
6867
|
+
return;
|
|
6514
6868
|
}
|
|
6515
6869
|
if (resource === "fixtures") {
|
|
6516
6870
|
const action = positional[1];
|
|
6517
|
-
currentCommandContext.action = "run";
|
|
6518
|
-
if (flags["help"] === true) {
|
|
6519
|
-
if (flags["report-json"] === true) {
|
|
6520
|
-
writeReportHelp("fixtures", "run");
|
|
6521
|
-
}
|
|
6522
|
-
else {
|
|
6523
|
-
const lines = [
|
|
6524
|
-
`Usage: ${FIXTURES_USAGE}`,
|
|
6525
|
-
"",
|
|
6526
|
-
FIXTURES_DESCRIPTION,
|
|
6527
|
-
"",
|
|
6528
|
-
"Examples:",
|
|
6529
|
-
...FIXTURES_EXAMPLES.map((example) => ` ${example}`),
|
|
6530
|
-
];
|
|
6531
|
-
process.stderr.write(`${lines.join("\n")}\n`);
|
|
6532
|
-
}
|
|
6533
|
-
process.exit(0);
|
|
6534
|
-
}
|
|
6871
|
+
currentCommandContext.action = action ?? "run";
|
|
6535
6872
|
if (action !== undefined && action !== "run") {
|
|
6536
|
-
throw
|
|
6873
|
+
throw unknownActionError("fixtures", action, ["run",]);
|
|
6537
6874
|
}
|
|
6538
6875
|
const result = await runFixtures(flags);
|
|
6539
6876
|
writeCommandResult(result);
|
|
6540
6877
|
return;
|
|
6541
6878
|
}
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
}
|
|
6548
|
-
if (flags["report-json"] === true) {
|
|
6549
|
-
throw new UsageError(`Unknown resource: ${resource}. Available: ${RESOURCE_NAMES.join(", ")}`);
|
|
6550
|
-
}
|
|
6551
|
-
process.stderr.write(`Unknown resource: ${resource} \nAvailable: ${RESOURCE_NAMES.join(", ")} \n`);
|
|
6552
|
-
process.exit(1);
|
|
6553
|
-
}
|
|
6554
|
-
// Resource-level help
|
|
6555
|
-
if (positional.length === 1 || flags["help"] === true) {
|
|
6556
|
-
if (positional.length === 1) {
|
|
6557
|
-
printResourceHelp(resource);
|
|
6558
|
-
if (flags["help"])
|
|
6559
|
-
process.exit(0);
|
|
6560
|
-
process.exit(1);
|
|
6561
|
-
}
|
|
6562
|
-
}
|
|
6563
|
-
const action = positional[1];
|
|
6564
|
-
const actionMeta = commands[resource][action];
|
|
6565
|
-
// Unknown action
|
|
6566
|
-
if (!actionMeta) {
|
|
6567
|
-
if (flags["report-json"] === true) {
|
|
6568
|
-
throw new UsageError(`Unknown action: ${resource} ${action}. Available actions for ${resource}: ${Object.keys(commands[resource]).join(", ")}`);
|
|
6879
|
+
if (resource === "batch") {
|
|
6880
|
+
const action = positional[1];
|
|
6881
|
+
currentCommandContext.action = action ?? "run";
|
|
6882
|
+
if (action !== undefined && action !== "run") {
|
|
6883
|
+
throw unknownActionError("batch", action, ["run",]);
|
|
6569
6884
|
}
|
|
6570
|
-
|
|
6571
|
-
|
|
6885
|
+
const { result, exitCode, } = await runBatch(flags);
|
|
6886
|
+
writeCommandResult(result);
|
|
6887
|
+
if (exitCode !== 0)
|
|
6888
|
+
process.exit(exitCode);
|
|
6889
|
+
return;
|
|
6572
6890
|
}
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
printActionHelp(resource, action);
|
|
6579
|
-
process.exit(0);
|
|
6891
|
+
if (!commands[resource])
|
|
6892
|
+
throw unknownResourceError(resource);
|
|
6893
|
+
const resourceActions = commands[resource];
|
|
6894
|
+
if (positional.length === 1) {
|
|
6895
|
+
throw missingActionError(resource, Object.keys(resourceActions), `dss ${resource} <action> [args...]`);
|
|
6580
6896
|
}
|
|
6897
|
+
const action = positional[1];
|
|
6898
|
+
currentCommandContext.action = action;
|
|
6899
|
+
const actionMeta = resourceActions[action];
|
|
6900
|
+
if (!actionMeta)
|
|
6901
|
+
throw unknownActionError(resource, action, Object.keys(resourceActions));
|
|
6581
6902
|
const args = positional.slice(2);
|
|
6582
6903
|
if (flags["plan"] === true) {
|
|
6583
6904
|
const plan = buildMutationPlan(resource, action, actionMeta, args, flags);
|
|
6584
6905
|
writeCommandResult(plan);
|
|
6585
6906
|
return;
|
|
6586
6907
|
}
|
|
6587
|
-
// Resolve credentials: flags > env > saved > .env
|
|
6588
6908
|
const { url, apiKey, projectKey, tlsRejectUnauthorized, caCertPath, } = resolveCredentials(flags);
|
|
6589
6909
|
currentCommandContext.projectKey = projectKey;
|
|
6590
6910
|
if (!url) {
|
|
6591
|
-
throw new UsageError("Missing Dataiku URL. Set DATAIKU_URL
|
|
6911
|
+
throw new UsageError("Missing Dataiku URL.", "missing_required_flag", "Set DATAIKU_URL or pass --url.", { requiredFlags: ["url",], env: ["DATAIKU_URL",], });
|
|
6592
6912
|
}
|
|
6593
6913
|
if (!apiKey) {
|
|
6594
|
-
throw new UsageError("Missing API key. Set DATAIKU_API_KEY
|
|
6914
|
+
throw new UsageError("Missing API key.", "missing_required_flag", "Set DATAIKU_API_KEY or pass --api-key.", { requiredFlags: ["api-key",], env: ["DATAIKU_API_KEY",], });
|
|
6595
6915
|
}
|
|
6596
6916
|
const requestTimeoutMs = num(flags["request-timeout"]);
|
|
6597
6917
|
const retryMaxAttempts = num(flags["retries"]);
|
|
@@ -6616,41 +6936,17 @@ async function main() {
|
|
|
6616
6936
|
if (entry)
|
|
6617
6937
|
await appendCleanupLedgerEntry(flags["record-cleanup"], entry);
|
|
6618
6938
|
}
|
|
6619
|
-
|
|
6939
|
+
const failureExitCode = commandFailureExitCode(result);
|
|
6940
|
+
if (failureExitCode !== undefined)
|
|
6941
|
+
throw new CommandResultFailure(result, failureExitCode);
|
|
6942
|
+
if (flags["raw"] === true && typeof result === "string" && typeof flags["output"] !== "string") {
|
|
6620
6943
|
process.stdout.write(result);
|
|
6621
6944
|
}
|
|
6622
6945
|
else {
|
|
6623
6946
|
writeCommandResult(result);
|
|
6624
6947
|
}
|
|
6625
|
-
const failureExitCode = commandFailureExitCode(result);
|
|
6626
|
-
if (failureExitCode !== undefined)
|
|
6627
|
-
process.exit(failureExitCode);
|
|
6628
6948
|
}
|
|
6629
6949
|
main().catch((err) => {
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
if (err instanceof UsageError)
|
|
6633
|
-
process.exit(1);
|
|
6634
|
-
if (err instanceof DataikuError)
|
|
6635
|
-
process.exit(err.category === "transient" ? 3 : 2);
|
|
6636
|
-
process.exit(2);
|
|
6637
|
-
}
|
|
6638
|
-
if (err instanceof UsageError) {
|
|
6639
|
-
process.stderr.write(`${JSON.stringify({ error: err.message, code: "usage", }, null, 2)}\n`);
|
|
6640
|
-
process.exit(1);
|
|
6641
|
-
}
|
|
6642
|
-
if (err instanceof DataikuError) {
|
|
6643
|
-
const payload = {
|
|
6644
|
-
error: err.message,
|
|
6645
|
-
category: err.category,
|
|
6646
|
-
retryable: err.retryable,
|
|
6647
|
-
};
|
|
6648
|
-
if (err.retryHint)
|
|
6649
|
-
payload.retryHint = err.retryHint;
|
|
6650
|
-
process.stderr.write(`${JSON.stringify(payload, null, 2)} \n`);
|
|
6651
|
-
process.exit(err.category === "transient" ? 3 : 2);
|
|
6652
|
-
}
|
|
6653
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
6654
|
-
process.stderr.write(`${JSON.stringify({ error: message, }, null, 2)} \n`);
|
|
6655
|
-
process.exit(1);
|
|
6950
|
+
writeErrorReport(err);
|
|
6951
|
+
process.exit(errorExitCode(err));
|
|
6656
6952
|
});
|