dataiku-sdk 3.9.0 → 4.1.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 +4 -3
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +6 -0
- package/dist/packages/types/src/index.js +6 -0
- package/dist/src/cli/coerce.d.ts +6 -0
- package/dist/src/cli/coerce.js +9 -0
- package/dist/src/cli/command-syntax.json +216 -0
- package/dist/src/cli/commands/app.js +2 -2
- package/dist/src/cli/commands/code-env.js +10 -9
- package/dist/src/cli/commands/code.js +16 -2
- package/dist/src/cli/commands/dashboard.js +1 -2
- package/dist/src/cli/commands/dataset.js +6 -4
- package/dist/src/cli/commands/flow-zone.js +1 -1
- package/dist/src/cli/commands/job.js +70 -16
- package/dist/src/cli/commands/plugin.js +7 -7
- package/dist/src/cli/commands/project-library.js +2 -2
- package/dist/src/cli/commands/project.js +21 -4
- package/dist/src/cli/commands/recipe.js +34 -21
- package/dist/src/cli/commands/scenario.js +78 -12
- package/dist/src/cli/contract.js +30 -7
- package/dist/src/cli/env.d.ts +6 -0
- package/dist/src/cli/env.js +10 -3
- package/dist/src/cli/helpers/app-permissions.d.ts +1 -1
- package/dist/src/cli/helpers/app-permissions.js +2 -2
- package/dist/src/cli/helpers/cleanup.d.ts +2 -0
- package/dist/src/cli/helpers/cleanup.js +122 -8
- package/dist/src/cli/helpers/flow-zone.d.ts +1 -1
- package/dist/src/cli/helpers/flow-zone.js +3 -4
- package/dist/src/cli/list-output.d.ts +5 -0
- package/dist/src/cli/list-output.js +11 -2
- package/dist/src/cli/list-shapes.d.ts +6 -0
- package/dist/src/cli/list-shapes.js +7 -1
- package/dist/src/cli/output-schemas.d.ts +1 -1
- package/dist/src/cli/output-schemas.js +1 -1
- package/dist/src/cli/output.d.ts +2 -0
- package/dist/src/cli/output.js +31 -4
- package/dist/src/cli/read-shapes.js +90 -0
- package/dist/src/cli/result-shapes.d.ts +19 -1
- package/dist/src/cli/result-shapes.js +227 -4
- package/dist/src/cli/selector-suggestions.d.ts +14 -0
- package/dist/src/cli/selector-suggestions.js +56 -0
- package/dist/src/cli/usage.d.ts +1 -1
- package/dist/src/cli/usage.js +29 -4
- package/dist/src/cli/version.d.ts +2 -2
- package/dist/src/cli/version.js +4 -5
- package/dist/src/cli.js +128 -30
- package/dist/src/client.d.ts +15 -2
- package/dist/src/client.js +36 -11
- package/dist/src/errors.d.ts +2 -0
- package/dist/src/errors.js +12 -2
- package/dist/src/generated/action-output-schemas.json +232 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/resources/datasets.d.ts +11 -1
- package/dist/src/resources/datasets.js +91 -30
- package/dist/src/resources/jobs.d.ts +35 -5
- package/dist/src/resources/jobs.js +68 -41
- package/dist/src/resources/projects.js +2 -2
- package/dist/src/resources/recipe-create.d.ts +4 -1
- package/dist/src/resources/recipe-create.js +11 -2
- package/dist/src/resources/recipes.d.ts +12 -1
- package/dist/src/resources/recipes.js +39 -12
- package/dist/src/resources/scenarios.d.ts +8 -1
- package/dist/src/resources/scenarios.js +51 -12
- package/dist/src/resources/sql.js +3 -3
- package/dist/src/resources/wiki.js +3 -7
- package/dist/src/skill.js +1 -2
- package/dist/src/utils/project-archive.d.ts +25 -0
- package/dist/src/utils/project-archive.js +216 -5
- package/dist/src/utils/response-file.js +15 -8
- package/package.json +12 -12
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +15 -17
- package/skills/dataiku-dss/references/discovery.md +2 -2
- package/skills/dataiku-dss/references/troubleshooting.md +1 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ClientValidationError, DataikuError, } from "../../errors.js";
|
|
1
|
+
import { resolve, } from "node:path";
|
|
2
|
+
import { ClientValidationError, DataikuError, dataikuErrorCode, RESPONSE_TOO_LARGE_STATUS_TEXT, } from "../../errors.js";
|
|
4
3
|
import { asRecord, } from "../../utils/records.js";
|
|
5
4
|
import { jobBuildTargetTypeFromFlags, jobLogFilterFromFlag, maxLogLinesFromFlags, num, } from "../coerce.js";
|
|
6
5
|
import { executionMode, } from "../flags.js";
|
|
6
|
+
import { compactValue, } from "../list-output.js";
|
|
7
|
+
import { COMPACT_LIST_FIELDS, } from "../list-shapes.js";
|
|
7
8
|
import { encodedProjectEndpoint, enqueueCliWarning, } from "../output.js";
|
|
8
9
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
9
10
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
@@ -33,6 +34,27 @@ function tailJobLogForStdout(log, limit) {
|
|
|
33
34
|
const tail = lines.slice(-shown).join("\n");
|
|
34
35
|
return hasTrailingLineBreak ? `${tail}\n` : tail;
|
|
35
36
|
}
|
|
37
|
+
/** POSIX-shell quoting for the display form of a resume command. */
|
|
38
|
+
function shellArg(value) {
|
|
39
|
+
return /^[\w.:@%+=,/-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A wait that timed out only stopped polling: the job is still running in DSS.
|
|
43
|
+
* Attach the command that resumes waiting: `resumeArgv` (run it without a
|
|
44
|
+
* shell, on any OS) and `resume` (POSIX display form); other results pass through.
|
|
45
|
+
*/
|
|
46
|
+
function withWaitResume(result, projectKey) {
|
|
47
|
+
if (result.timedOut !== true)
|
|
48
|
+
return result;
|
|
49
|
+
const resumeArgv = [
|
|
50
|
+
"dss",
|
|
51
|
+
"job",
|
|
52
|
+
"wait",
|
|
53
|
+
result.jobId,
|
|
54
|
+
...(projectKey ? ["--project-key", projectKey,] : []),
|
|
55
|
+
];
|
|
56
|
+
return { ...result, resume: resumeArgv.map(shellArg).join(" "), resumeArgv, };
|
|
57
|
+
}
|
|
36
58
|
/**
|
|
37
59
|
* With an empty output schema DSS ends a dataset build DONE after writing no
|
|
38
60
|
* columns and no rows, without an error. Flag that after a successful build so
|
|
@@ -120,8 +142,9 @@ function jobSummaryType(job) {
|
|
|
120
142
|
?? "unknown";
|
|
121
143
|
}
|
|
122
144
|
function jobSummaryState(job) {
|
|
145
|
+
const listState = compactValue(job, COMPACT_LIST_FIELDS["job"]["state"]);
|
|
123
146
|
return stringPath(job, ["baseStatus", "state",])
|
|
124
|
-
??
|
|
147
|
+
?? (typeof listState === "string" ? listState : undefined)
|
|
125
148
|
?? "unknown";
|
|
126
149
|
}
|
|
127
150
|
function filteredJobList(jobs, flags) {
|
|
@@ -202,7 +225,19 @@ function jobDurationMs(details) {
|
|
|
202
225
|
}
|
|
203
226
|
async function jobInspectionSummary(client, jobId, flags) {
|
|
204
227
|
const projectKey = flags["project-key"];
|
|
205
|
-
|
|
228
|
+
// A job-detail body over the buffered response cap must not sink the whole
|
|
229
|
+
// summary: keep the log part and report the details failure structurally.
|
|
230
|
+
let details = {};
|
|
231
|
+
let detailsError;
|
|
232
|
+
try {
|
|
233
|
+
details = await client.jobs.get(jobId, projectKey);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (!(error instanceof DataikuError && error.statusText === RESPONSE_TOO_LARGE_STATUS_TEXT)) {
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
detailsError = { code: dataikuErrorCode(error.category), message: error.message, };
|
|
240
|
+
}
|
|
206
241
|
let log;
|
|
207
242
|
let logError;
|
|
208
243
|
try {
|
|
@@ -233,6 +268,7 @@ async function jobInspectionSummary(client, jobId, flags) {
|
|
|
233
268
|
jobId: jobSummaryId(details, jobId),
|
|
234
269
|
state: jobSummaryState(details),
|
|
235
270
|
type: jobSummaryType(details),
|
|
271
|
+
...(detailsError ? { detailsError, } : {}),
|
|
236
272
|
...(durationMs !== undefined ? { durationMs, } : {}),
|
|
237
273
|
...(outputs !== undefined ? { outputs, } : {}),
|
|
238
274
|
warnings: jobWarningSummary(details, log),
|
|
@@ -253,7 +289,7 @@ async function jobInspectionSummary(client, jobId, flags) {
|
|
|
253
289
|
export const jobCommands = withUsage("job", {
|
|
254
290
|
list: {
|
|
255
291
|
handler: async (c, _a, f) => filteredJobList(await c.jobs.list(f["project-key"]), f),
|
|
256
|
-
description: "List recent jobs, optionally filtered for automation.",
|
|
292
|
+
description: "List recent jobs, optionally filtered for automation. In-progress jobs, which DSS lists without a state, show RUNNING; `job get` has the exact phase.",
|
|
257
293
|
examples: ["dss job list --state DONE --latest", "dss job list --contains WLM225S --limit 10",],
|
|
258
294
|
},
|
|
259
295
|
get: {
|
|
@@ -282,18 +318,34 @@ export const jobCommands = withUsage("job", {
|
|
|
282
318
|
?? f["output-file"];
|
|
283
319
|
const requestedLines = maxLogLinesFromFlags(f);
|
|
284
320
|
// Stdout is tailed here (not in the SDK) so the total can be reported.
|
|
285
|
-
const
|
|
321
|
+
const details = await c.jobs.logDetails(a[0], {
|
|
286
322
|
activity: f["activity"],
|
|
287
323
|
logId: f["log-id"],
|
|
288
324
|
logFilter,
|
|
289
325
|
maxLogLines: outputFile ? requestedLines : -1,
|
|
290
326
|
projectKey: f["project-key"],
|
|
291
327
|
});
|
|
328
|
+
const log = details.text;
|
|
329
|
+
if (details.truncated) {
|
|
330
|
+
enqueueCliWarning({
|
|
331
|
+
code: "log_truncated",
|
|
332
|
+
reason: "byte_cap",
|
|
333
|
+
earlyLinesDropped: true,
|
|
334
|
+
maxLogBytes: details.maxLogBytes,
|
|
335
|
+
hint: "Only the most recent part of the log was retrieved; early lines (and any earlier errors) were dropped. Use `dss job get` for job-level and per-activity errors.",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
if (logFilter === "errors" && log.length === 0) {
|
|
339
|
+
enqueueCliWarning({
|
|
340
|
+
code: "no_error_lines",
|
|
341
|
+
scannedLines: details.scannedLines,
|
|
342
|
+
hint: `No error lines matched in ${details.scannedLines} scanned log lines. Run \`dss job get ${a[0]}\` for the job-level and per-activity errors, or \`dss job log ${a[0]}\` without --errors-only for the full log.`,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
292
345
|
if (!outputFile)
|
|
293
346
|
return tailJobLogForStdout(log, requestedLines ?? DEFAULT_STDOUT_LOG_LINES);
|
|
294
347
|
const outputPath = resolve(outputFile);
|
|
295
|
-
await
|
|
296
|
-
await writeFile(outputPath, log.endsWith("\n") ? log : `${log}\n`, "utf-8");
|
|
348
|
+
await Bun.write(outputPath, log.endsWith("\n") ? log : `${log}\n`);
|
|
297
349
|
return outputPath;
|
|
298
350
|
},
|
|
299
351
|
description: "Get public API job log output (last 100 lines by default; --max-lines N, 0 = all); find why a job failed. Use --errors-only for error/traceback lines and --output PATH to write it to a file (stdout returns the path). --log-id is accepted for UI parity; API keys cannot read browser-only cat-activity-log files.",
|
|
@@ -349,7 +401,7 @@ export const jobCommands = withUsage("job", {
|
|
|
349
401
|
};
|
|
350
402
|
}
|
|
351
403
|
if (f["wait"] === true) {
|
|
352
|
-
const result = await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, });
|
|
404
|
+
const result = withWaitResume(await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, }), pk);
|
|
353
405
|
await warnIfBuiltDatasetHasNoColumns(c, a[0], options.targetType, result.success, pk);
|
|
354
406
|
return result;
|
|
355
407
|
}
|
|
@@ -389,7 +441,7 @@ export const jobCommands = withUsage("job", {
|
|
|
389
441
|
method: "POST",
|
|
390
442
|
};
|
|
391
443
|
}
|
|
392
|
-
const result = await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, });
|
|
444
|
+
const result = withWaitResume(await c.jobs.buildAndWait(a[0], { ...options, projectKey: pk, }), pk);
|
|
393
445
|
await warnIfBuiltDatasetHasNoColumns(c, a[0], options.targetType, result.success, pk);
|
|
394
446
|
return result;
|
|
395
447
|
},
|
|
@@ -402,17 +454,19 @@ export const jobCommands = withUsage("job", {
|
|
|
402
454
|
],
|
|
403
455
|
},
|
|
404
456
|
wait: {
|
|
405
|
-
handler: (c, a, f) => {
|
|
457
|
+
handler: async (c, a, f) => {
|
|
406
458
|
requireArgs(a, 1, commandUsage("job", "wait"));
|
|
407
|
-
|
|
459
|
+
const projectKey = f["project-key"];
|
|
460
|
+
const result = await c.jobs.wait(a[0], {
|
|
408
461
|
includeLogs: f["include-logs"] === true,
|
|
409
462
|
logFilter: jobLogFilterFromFlag(f["log-filter"]),
|
|
410
463
|
maxLogLines: maxLogLinesFromFlags(f),
|
|
411
464
|
pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
|
|
412
465
|
timeoutMs: num(f["timeout"], "--timeout"),
|
|
413
466
|
summary: f["summary"] === true,
|
|
414
|
-
projectKey
|
|
467
|
+
projectKey,
|
|
415
468
|
});
|
|
469
|
+
return withWaitResume(result, projectKey);
|
|
416
470
|
},
|
|
417
471
|
description: "Wait for an existing job to complete.",
|
|
418
472
|
examples: [
|
|
@@ -432,7 +486,7 @@ export const jobCommands = withUsage("job", {
|
|
|
432
486
|
summary: f["summary"] !== false,
|
|
433
487
|
projectKey: f["project-key"],
|
|
434
488
|
};
|
|
435
|
-
const jobs = await Promise.all(a.map((jobId) => c.jobs.wait(jobId, options)));
|
|
489
|
+
const jobs = await Promise.all(a.map(async (jobId) => withWaitResume(await c.jobs.wait(jobId, options), options.projectKey)));
|
|
436
490
|
return aggregateJobWaitResults(jobs, String(f["until"] ?? "all-done"));
|
|
437
491
|
},
|
|
438
492
|
description: "Monitor one or more existing jobs and summarize progress counters from logs.",
|
|
@@ -450,7 +504,7 @@ export const jobCommands = withUsage("job", {
|
|
|
450
504
|
summary: true,
|
|
451
505
|
projectKey: f["project-key"],
|
|
452
506
|
};
|
|
453
|
-
const jobs = await Promise.all(a.map((jobId) => c.jobs.wait(jobId, options)));
|
|
507
|
+
const jobs = await Promise.all(a.map(async (jobId) => withWaitResume(await c.jobs.wait(jobId, options), options.projectKey)));
|
|
454
508
|
return aggregateJobWaitResults(jobs, String(f["until"] ?? "all-done"));
|
|
455
509
|
},
|
|
456
510
|
description: "Watch one or more existing jobs with progress extraction enabled.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { readFile, } from "node:fs/promises";
|
|
2
2
|
import { validatedPluginGitUrl, validatePluginDestinationPath, validatePluginId, validatePluginName, validatePluginPath, } from "../../resources/plugins.js";
|
|
3
3
|
import { writeResponseToFile, } from "../../utils/response-file.js";
|
|
4
|
-
import { num, readStdinText, sha256Hex, } from "../coerce.js";
|
|
4
|
+
import { num, readStdinText, readTextFile, sha256Hex, } from "../coerce.js";
|
|
5
5
|
import { executionMode, } from "../flags.js";
|
|
6
6
|
import { planResult, } from "../output.js";
|
|
7
7
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
@@ -281,7 +281,7 @@ export const pluginCommands = withUsage("plugin", {
|
|
|
281
281
|
requireArgs(a, 1, SETTINGS_SET_USAGE);
|
|
282
282
|
const pluginId = validatePluginId(a[0]);
|
|
283
283
|
const mode = executionMode(f);
|
|
284
|
-
const settings = readSettingsInput(f);
|
|
284
|
+
const settings = await readSettingsInput(f);
|
|
285
285
|
if (mode.dryRun) {
|
|
286
286
|
return pluginPlan("settings-set", {
|
|
287
287
|
method: "POST",
|
|
@@ -330,7 +330,7 @@ export const pluginCommands = withUsage("plugin", {
|
|
|
330
330
|
},
|
|
331
331
|
description: "Create the code env of a plugin. Returns the DSS future (jobId); --wait polls it to completion.",
|
|
332
332
|
examples: [
|
|
333
|
-
"dss plugin code-env-create my-plugin --python-interpreter
|
|
333
|
+
"dss plugin code-env-create my-plugin --python-interpreter PYTHON311",
|
|
334
334
|
"dss plugin code-env-create my-plugin --conda --wait --timeout 300000",
|
|
335
335
|
],
|
|
336
336
|
},
|
|
@@ -629,7 +629,7 @@ export const pluginCommands = withUsage("plugin", {
|
|
|
629
629
|
});
|
|
630
630
|
}
|
|
631
631
|
const content = typeof f["file"] === "string"
|
|
632
|
-
?
|
|
632
|
+
? await readFile(f["file"])
|
|
633
633
|
: typeof f["content"] === "string"
|
|
634
634
|
? f["content"]
|
|
635
635
|
: await readStdinText();
|
|
@@ -738,10 +738,10 @@ function normalizeCreationMode(value) {
|
|
|
738
738
|
}
|
|
739
739
|
throw new UsageError("--creation-mode must be one of EMPTY, GIT_CLONE, GIT_EXPORT.", "invalid_enum");
|
|
740
740
|
}
|
|
741
|
-
function readSettingsInput(flags) {
|
|
741
|
+
async function readSettingsInput(flags) {
|
|
742
742
|
let text;
|
|
743
743
|
if (typeof flags["file"] === "string") {
|
|
744
|
-
text =
|
|
744
|
+
text = await readTextFile(flags["file"]);
|
|
745
745
|
}
|
|
746
746
|
else if (typeof flags["content"] === "string") {
|
|
747
747
|
text = flags["content"];
|
|
@@ -159,7 +159,7 @@ export const projectLibraryCommands = withUsage("project-library", {
|
|
|
159
159
|
const content = typeof f["content"] === "string"
|
|
160
160
|
? f["content"]
|
|
161
161
|
: typeof f["file"] === "string"
|
|
162
|
-
? fs.
|
|
162
|
+
? await fs.promises.readFile(f["file"])
|
|
163
163
|
: await readStdinText();
|
|
164
164
|
const result = await c.projectLibrary.addOrUpdateFile(a[0], content, pk, expectSha256 !== undefined ? { expectSha256, } : undefined);
|
|
165
165
|
return {
|
|
@@ -184,7 +184,7 @@ export const projectLibraryCommands = withUsage("project-library", {
|
|
|
184
184
|
const local = typeof f["content"] === "string"
|
|
185
185
|
? f["content"]
|
|
186
186
|
: typeof f["file"] === "string"
|
|
187
|
-
? fs.
|
|
187
|
+
? await fs.promises.readFile(f["file"])
|
|
188
188
|
: await readStdinText();
|
|
189
189
|
return c.projectLibrary.diffFile(a[0], local, pk, maxLines !== undefined ? { maxLines, } : {});
|
|
190
190
|
},
|
|
@@ -4,11 +4,13 @@ import { inspectProjectArchive, } from "../../utils/project-archive.js";
|
|
|
4
4
|
import { projectIncarnationHash, } from "../../utils/project-incarnation.js";
|
|
5
5
|
import { jsonInput, num, requiredJsonInput, requiredStringFlag, } from "../coerce.js";
|
|
6
6
|
import { executionMode, } from "../flags.js";
|
|
7
|
-
import { CommandResultFailure, } from "../output.js";
|
|
7
|
+
import { CommandResultFailure, enqueueCliWarning, } from "../output.js";
|
|
8
8
|
import { compactProjectMapOutput, } from "../project-map-output.js";
|
|
9
9
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
10
10
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
11
11
|
const INSPECT_ARCHIVE_USAGE = commandUsage("project", "inspect-archive");
|
|
12
|
+
/** Maximum credential inventory entries echoed in the export warning. */
|
|
13
|
+
const EXPORT_CREDENTIAL_INVENTORY_LIMIT = 100;
|
|
12
14
|
function flowRenderFormat(value) {
|
|
13
15
|
if (value === undefined)
|
|
14
16
|
return undefined;
|
|
@@ -162,9 +164,24 @@ export const projectCommands = withUsage("project", {
|
|
|
162
164
|
if (!out)
|
|
163
165
|
throw new UsageError("--output PATH is required.", "missing_required_flag");
|
|
164
166
|
await c.projects.exportArchive(a[0], out, jsonInput(f));
|
|
165
|
-
|
|
167
|
+
const inspection = await inspectProjectArchive(out);
|
|
168
|
+
const findings = inspection.credentialFindings;
|
|
169
|
+
if (findings.length > 0) {
|
|
170
|
+
enqueueCliWarning({
|
|
171
|
+
code: "export_contains_credentials",
|
|
172
|
+
message: `The exported archive '${out}' contains ${findings.length} credential-bearing structure(s) `
|
|
173
|
+
+ "(member paths and key names only; values are never printed). "
|
|
174
|
+
+ "DSS exports credentials, API keys and secret-named variables verbatim.",
|
|
175
|
+
hint: "Sanitize the archive before sharing or uploading it. Overwriting an object in versioned storage does not erase its retained versions: "
|
|
176
|
+
+ "once an unsanitized archive is uploaded, rotate the exposed credentials.",
|
|
177
|
+
total: findings.length,
|
|
178
|
+
shown: Math.min(findings.length, EXPORT_CREDENTIAL_INVENTORY_LIMIT),
|
|
179
|
+
findings: findings.slice(0, EXPORT_CREDENTIAL_INVENTORY_LIMIT),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
return { path: out, credentialFindings: findings.length, };
|
|
166
183
|
},
|
|
167
|
-
description: "Export a project to a local archive (.zip).",
|
|
184
|
+
description: "Export a project to a local archive (.zip). The written archive is inspected locally; credential-bearing structures (apikeys.json, secret-named variables, apiKey/token/password fields) raise an export_contains_credentials warning listing member paths and key names only, and the result reports credentialFindings. The archive is not redacted.",
|
|
168
185
|
examples: ["dss project export MY_PROJ --output ./my_proj.zip",],
|
|
169
186
|
},
|
|
170
187
|
import: {
|
|
@@ -202,7 +219,7 @@ export const projectCommands = withUsage("project", {
|
|
|
202
219
|
"inspect-archive": {
|
|
203
220
|
handler: async (_c, a) => inspectArchiveCommand(a),
|
|
204
221
|
localHandler: inspectArchiveCommand,
|
|
205
|
-
description: "Read-only local inspection of a project export archive: member names, sizes, manifest validity, archive issues, and the source project key. Never contacts DSS.",
|
|
222
|
+
description: "Read-only local inspection of a project export archive: member names, sizes, manifest validity, archive issues (including credential_structure and residue_scan_skipped warnings), a credentialFindings inventory of credential-bearing member paths and key names (values are never reported), and the source project key. Never contacts DSS.",
|
|
206
223
|
examples: ["dss project inspect-archive ./my_proj.zip",],
|
|
207
224
|
},
|
|
208
225
|
"permissions-set": {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { readFileSync, } from "node:fs";
|
|
2
|
-
import { writeFile, } from "node:fs/promises";
|
|
3
1
|
import { join, } from "node:path";
|
|
4
2
|
import { ClientValidationError, DataikuError, } from "../../errors.js";
|
|
5
3
|
import { recipeCreateWarnings, recipeOutputSchemaIsComputable, schemaUpdateComputableId, schemaUpdateIsPending, } from "../../resources/recipe-create.js";
|
|
6
|
-
import {
|
|
7
|
-
import { jobLogFilterFromFlag, jsonInput, maxLogLinesFromFlags, normalizeLineEndings, num, recipeInputDatasetsFromFlags, requiredJsonInput, requiredStringFlag, rewritePairsFromFlags, sha256Hex, splitCsvFlag, stableHash, } from "../coerce.js";
|
|
4
|
+
import { buildRecipeUpdate, } from "../../resources/recipes.js";
|
|
5
|
+
import { jobLogFilterFromFlag, jsonInput, maxLogLinesFromFlags, normalizeLineEndings, num, readTextFile, recipeInputDatasetsFromFlags, requiredJsonInput, requiredStringFlag, rewritePairsFromFlags, sha256Hex, splitCsvFlag, stableHash, } from "../coerce.js";
|
|
8
6
|
import { executionMode, } from "../flags.js";
|
|
9
7
|
import { moveCreatedItemsToZone, resolveFlowZoneIdFromFlags, } from "../helpers/flow-zone.js";
|
|
10
8
|
import { ensureRecipeBackupDir, readRecipeBackup, recipeBackupDocument, recipeBackupPath, recipeCodeEnv, recipeGraph, recipeInputItemRef, recipeRoleInputItems, recipeRunShouldWait, writeRecipeBackup, } from "../helpers/recipe.js";
|
|
@@ -12,9 +10,9 @@ import { encodedProjectEndpoint, enqueueCliWarning, readIfExists, skipResult, }
|
|
|
12
10
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
13
11
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
14
12
|
/** Read a local recipe file before any DSS request, mapping read failures to usage validation. */
|
|
15
|
-
function readRecipeFile(filePath, flagName) {
|
|
13
|
+
async function readRecipeFile(filePath, flagName) {
|
|
16
14
|
try {
|
|
17
|
-
return
|
|
15
|
+
return await readTextFile(filePath);
|
|
18
16
|
}
|
|
19
17
|
catch (error) {
|
|
20
18
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -414,9 +412,30 @@ export const recipeCommands = withUsage("recipe", {
|
|
|
414
412
|
hint: `Fix the recipe settings, then run dss recipe update-schema ${createdName}.`,
|
|
415
413
|
});
|
|
416
414
|
}
|
|
417
|
-
|
|
415
|
+
// A visual recipe whose payload came from flags is ready to run: point at the build
|
|
416
|
+
// (recursive, so outputs of recipes created just before are built too).
|
|
417
|
+
const output = f["output"];
|
|
418
|
+
const next = typeof output === "string" && (created.payloadConfigured || created.joinConfigured)
|
|
419
|
+
? [[
|
|
420
|
+
"dss",
|
|
421
|
+
"job",
|
|
422
|
+
"build-and-wait",
|
|
423
|
+
output,
|
|
424
|
+
"--build-mode",
|
|
425
|
+
"RECURSIVE_BUILD",
|
|
426
|
+
"--summary",
|
|
427
|
+
...(pk ? ["--project-key", pk,] : []),
|
|
428
|
+
],]
|
|
429
|
+
: undefined;
|
|
430
|
+
return {
|
|
431
|
+
created: createdName,
|
|
432
|
+
resource: "recipe",
|
|
433
|
+
...created,
|
|
434
|
+
...moved,
|
|
435
|
+
...(next ? { next, } : {}),
|
|
436
|
+
};
|
|
418
437
|
},
|
|
419
|
-
description: "Create a recipe
|
|
438
|
+
description: "Create a recipe; missing outputs become managed datasets on --output-connection. Join, grouping (--group-by/--aggregate: keys plus those aggregates, no implicit count) and prepare (--rename/--formula/--filter/...) payloads come from flags, then `next` is the build. Visual outputs get the DSS-computed schema.",
|
|
420
439
|
examples: [
|
|
421
440
|
"dss recipe create --type python --input raw_orders,lookup --output orders_clean",
|
|
422
441
|
"dss recipe create --type grouping --input orders --output orders_by_cat --group-by category --aggregate amount:sum+avg,name:first --order-by day",
|
|
@@ -507,7 +526,7 @@ export const recipeCommands = withUsage("recipe", {
|
|
|
507
526
|
throw new UsageError(`--file is required. Usage: ${commandUsage("recipe", "diff")}`);
|
|
508
527
|
}
|
|
509
528
|
// Read and validate the local file before any DSS request.
|
|
510
|
-
const localContent = readRecipeFile(filePath, "--file");
|
|
529
|
+
const localContent = await readRecipeFile(filePath, "--file");
|
|
511
530
|
const result = await c.recipes.get(a[0], {
|
|
512
531
|
includePayload: true,
|
|
513
532
|
projectKey: f["project-key"],
|
|
@@ -530,23 +549,17 @@ export const recipeCommands = withUsage("recipe", {
|
|
|
530
549
|
const pk = f["project-key"];
|
|
531
550
|
if (executionMode(f).dryRun) {
|
|
532
551
|
const current = await c.recipes.get(a[0], { projectKey: pk, includePayload: true, });
|
|
533
|
-
const
|
|
534
|
-
const next = {
|
|
535
|
-
...current,
|
|
536
|
-
...data,
|
|
537
|
-
recipe: deepMerge(currentRecipe, (data.recipe && typeof data.recipe === "object" && !Array.isArray(data.recipe))
|
|
538
|
-
? data.recipe
|
|
539
|
-
: {}),
|
|
540
|
-
};
|
|
552
|
+
const next = buildRecipeUpdate(current, data);
|
|
541
553
|
return { dryRun: true, action: "update", resource: "recipe", name: a[0], current, next, };
|
|
542
554
|
}
|
|
543
555
|
await c.recipes.update(a[0], data, pk);
|
|
544
556
|
return { updated: a[0], resource: "recipe", };
|
|
545
557
|
},
|
|
546
|
-
description: "Update recipe settings via JSON merge. Recipe definition fields must be nested under a top-level recipe key.",
|
|
558
|
+
description: "Update recipe settings via JSON merge. Recipe definition fields must be nested under a top-level recipe key. recipe.params.envSelection, when present, replaces the existing envSelection object (include envMode; no stale envName is kept) and is only accepted under recipe.params.",
|
|
547
559
|
examples: [
|
|
548
560
|
"dss recipe update compute_orders --data-file settings.json --dry-run",
|
|
549
|
-
'dss recipe update compute_orders --data \'{"recipe":{"params":{"envSelection":{"envMode":"EXPLICIT_ENV","envName":"
|
|
561
|
+
'dss recipe update compute_orders --data \'{"recipe":{"params":{"envSelection":{"envMode":"EXPLICIT_ENV","envName":"py311_env"}}}}\'',
|
|
562
|
+
'dss recipe update compute_orders --data \'{"recipe":{"params":{"envSelection":{"envMode":"INHERIT"}}}}\'',
|
|
550
563
|
"cat settings.json | dss recipe update compute_orders --stdin",
|
|
551
564
|
],
|
|
552
565
|
},
|
|
@@ -636,7 +649,7 @@ export const recipeCommands = withUsage("recipe", {
|
|
|
636
649
|
projectKey: f["project-key"],
|
|
637
650
|
});
|
|
638
651
|
if (typeof f["output"] === "string") {
|
|
639
|
-
await
|
|
652
|
+
await Bun.write(f["output"], payload);
|
|
640
653
|
return f["output"];
|
|
641
654
|
}
|
|
642
655
|
return payload;
|
|
@@ -663,7 +676,7 @@ export const recipeCommands = withUsage("recipe", {
|
|
|
663
676
|
const filePath = f["file"];
|
|
664
677
|
if (!filePath)
|
|
665
678
|
throw new UsageError("--file is required.");
|
|
666
|
-
const content = readRecipeFile(filePath, "--file");
|
|
679
|
+
const content = await readRecipeFile(filePath, "--file");
|
|
667
680
|
const pk = f["project-key"];
|
|
668
681
|
const shouldBackup = f["no-backup"] !== true;
|
|
669
682
|
const backupDir = shouldBackup
|
|
@@ -1,8 +1,69 @@
|
|
|
1
|
+
import { asRecord, } from "../../utils/records.js";
|
|
1
2
|
import { jsonInput, num, parseBooleanOption, } from "../coerce.js";
|
|
2
3
|
import { executionMode, } from "../flags.js";
|
|
3
4
|
import { encodedProjectEndpoint, readIfExists, skipResult, } from "../output.js";
|
|
5
|
+
import { scenarioRunDiagnosis, shapeCommandResult, } from "../result-shapes.js";
|
|
4
6
|
import { commandUsage, withUsage, } from "../syntax.js";
|
|
5
7
|
import { requireArgs, UsageError, } from "../usage.js";
|
|
8
|
+
/**
|
|
9
|
+
* The recipe a failed job was running and the datasets it read, from one
|
|
10
|
+
* `job get` (its FAILED activity). Undefined when the job cannot be read or
|
|
11
|
+
* names no failed recipe: the diagnosis then stays job-level.
|
|
12
|
+
*/
|
|
13
|
+
async function failedActivity(c, jobId, projectKey) {
|
|
14
|
+
if (typeof jobId !== "string")
|
|
15
|
+
return undefined;
|
|
16
|
+
try {
|
|
17
|
+
const job = asRecord(await c.jobs.get(jobId, projectKey));
|
|
18
|
+
const activities = asRecord(asRecord(job?.["baseStatus"])?.["activities"]) ?? {};
|
|
19
|
+
for (const value of Object.values(activities)) {
|
|
20
|
+
const activity = asRecord(value);
|
|
21
|
+
if (activity?.["state"] !== "FAILED" || typeof activity["recipeName"] !== "string")
|
|
22
|
+
continue;
|
|
23
|
+
const sources = Array.isArray(activity["sources"]) ? activity["sources"] : [];
|
|
24
|
+
const inputs = sources.flatMap((source) => {
|
|
25
|
+
const id = asRecord(source)?.["id"];
|
|
26
|
+
if (typeof id !== "string")
|
|
27
|
+
return [];
|
|
28
|
+
// Same-project inputs are named bare; foreign ones keep PROJECT.name.
|
|
29
|
+
const dot = id.indexOf(".");
|
|
30
|
+
return [dot > 0 && id.slice(0, dot) === projectKey ? id.slice(dot + 1) : id,];
|
|
31
|
+
});
|
|
32
|
+
return { recipe: activity["recipeName"], inputs, };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// A diagnosis aid only: the run report itself is already in hand.
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
/** Diagnosis of a raw run report, located at the failed recipe when its job names one. */
|
|
41
|
+
async function locatedDiagnosis(c, details, projectKey) {
|
|
42
|
+
const plain = scenarioRunDiagnosis(details);
|
|
43
|
+
const runProject = asRecord(asRecord(asRecord(details)?.["scenarioRun"])?.["trigger"])?.["projectKey"];
|
|
44
|
+
const pk = projectKey ?? (typeof runProject === "string" ? runProject : undefined);
|
|
45
|
+
const activity = await failedActivity(c, plain.failure?.["jobId"], pk);
|
|
46
|
+
return activity ? scenarioRunDiagnosis(details, activity) : plain;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Run a scenario and wait; a failed (not timed-out) run gains the first
|
|
50
|
+
* failure, the recipe that failed, and the next commands. Extra reads happen
|
|
51
|
+
* only on failure; a read error leaves the wait result untouched.
|
|
52
|
+
*/
|
|
53
|
+
async function runAndWaitWithFailure(c, scenarioId, options) {
|
|
54
|
+
const result = await c.scenarios.runAndWait(scenarioId, options);
|
|
55
|
+
if (result.success || result.timedOut === true || !result.runId)
|
|
56
|
+
return result;
|
|
57
|
+
try {
|
|
58
|
+
const details = await c.scenarios.getRunDetails(scenarioId, result.runId, {
|
|
59
|
+
projectKey: options.projectKey,
|
|
60
|
+
});
|
|
61
|
+
return { ...result, ...await locatedDiagnosis(c, details, options.projectKey), };
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
6
67
|
export const scenarioCommands = withUsage("scenario", {
|
|
7
68
|
list: {
|
|
8
69
|
handler: (c, _a, f) => c.scenarios.list(f["project-key"]),
|
|
@@ -37,7 +98,7 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
37
98
|
};
|
|
38
99
|
}
|
|
39
100
|
if (f["wait"] === true) {
|
|
40
|
-
return c
|
|
101
|
+
return runAndWaitWithFailure(c, a[0], { ...options, projectKey: pk, });
|
|
41
102
|
}
|
|
42
103
|
return c.scenarios.run(a[0], pk);
|
|
43
104
|
},
|
|
@@ -63,7 +124,7 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
63
124
|
method: "POST",
|
|
64
125
|
};
|
|
65
126
|
}
|
|
66
|
-
return c
|
|
127
|
+
return runAndWaitWithFailure(c, a[0], { ...options, projectKey: pk, });
|
|
67
128
|
},
|
|
68
129
|
description: "Run a scenario and wait for completion.",
|
|
69
130
|
examples: [
|
|
@@ -209,17 +270,24 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
209
270
|
projectKey: f["project-key"],
|
|
210
271
|
});
|
|
211
272
|
},
|
|
212
|
-
description: "List the most recent runs of a scenario (run id, start/end,
|
|
273
|
+
description: "List the most recent runs of a scenario (compact by default: run id, outcome, start/end, trigger, failure message; --full returns the DSS run objects).",
|
|
213
274
|
examples: ["dss scenario last-runs my_scenario --limit 10",],
|
|
214
275
|
},
|
|
215
276
|
"get-run": {
|
|
216
|
-
handler: (c, a, f) => {
|
|
277
|
+
handler: async (c, a, f) => {
|
|
217
278
|
requireArgs(a, 2, commandUsage("scenario", "get-run"));
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
279
|
+
const projectKey = f["project-key"];
|
|
280
|
+
const details = await c.scenarios.getRunDetails(a[0], a[1], { projectKey, });
|
|
281
|
+
if (f["full"] === true || typeof f["fields"] === "string")
|
|
282
|
+
return details;
|
|
283
|
+
// Compact view, located at the failed recipe (one extra job read, only on failure).
|
|
284
|
+
// The shaped object has no `scenarioRun`, so the dispatcher's shaping passes it through.
|
|
285
|
+
const shaped = asRecord(shapeCommandResult("scenario", "get-run", details, f));
|
|
286
|
+
if (!shaped?.["failure"])
|
|
287
|
+
return shaped ?? details;
|
|
288
|
+
return { ...shaped, ...await locatedDiagnosis(c, details, projectKey), };
|
|
221
289
|
},
|
|
222
|
-
description: "Get
|
|
290
|
+
description: "Get a scenario run: outcome, timing, trigger, per-step datasets and jobs, plus `failure` {stepId, jobId, dataset, recipe, inputs, message} and `next` argv (recipe code, input schemas, job errors) for the first failure (compact by default; --full returns the raw DSS run report).",
|
|
223
291
|
examples: ["dss scenario get-run my_scenario 2024-09-01-01-02-03-123",],
|
|
224
292
|
},
|
|
225
293
|
log: {
|
|
@@ -233,11 +301,9 @@ export const scenarioCommands = withUsage("scenario", {
|
|
|
233
301
|
const outputFile = f["output"]
|
|
234
302
|
?? f["output-file"];
|
|
235
303
|
if (outputFile) {
|
|
236
|
-
const {
|
|
237
|
-
const { dirname, resolve, } = await import("node:path");
|
|
304
|
+
const { resolve, } = await import("node:path");
|
|
238
305
|
const outputPath = resolve(outputFile);
|
|
239
|
-
await
|
|
240
|
-
await writeFile(outputPath, result.text, "utf-8");
|
|
306
|
+
await Bun.write(outputPath, result.text);
|
|
241
307
|
return { path: outputPath, truncated: result.truncated, };
|
|
242
308
|
}
|
|
243
309
|
return result.text;
|
package/dist/src/cli/contract.js
CHANGED
|
@@ -248,6 +248,13 @@ const ALLOWED_CLEANUP_ACTIONS = new Set([
|
|
|
248
248
|
"project-library delete",
|
|
249
249
|
"streaming-endpoint delete",
|
|
250
250
|
"workspace delete",
|
|
251
|
+
"saved-model delete",
|
|
252
|
+
"project-folder delete",
|
|
253
|
+
"data-collection delete",
|
|
254
|
+
"plugin delete",
|
|
255
|
+
"connection delete",
|
|
256
|
+
"user delete",
|
|
257
|
+
"group delete",
|
|
251
258
|
]);
|
|
252
259
|
export function isAllowedCleanupAction(resource, action) {
|
|
253
260
|
return ALLOWED_CLEANUP_ACTIONS.has(`${resource} ${action}`);
|
|
@@ -391,7 +398,7 @@ function structuredExamples(examples, examplePayload) {
|
|
|
391
398
|
* (`dss commands run`, `dss agent contract`) never loads the TypeBox graph.
|
|
392
399
|
* The content matches `typeBoxCommandOutputSchemas()` exactly; parity and
|
|
393
400
|
* freshness are enforced by tests/cli/discovery-output-schemas.test.ts and
|
|
394
|
-
* `bun run
|
|
401
|
+
* `bun run verify`.
|
|
395
402
|
*/
|
|
396
403
|
const actionOutputSchemas = actionOutputSchemasMetadata
|
|
397
404
|
.schemas;
|
|
@@ -764,6 +771,21 @@ const STRING_OUTPUT_ACTIONS = new Set([
|
|
|
764
771
|
"log",
|
|
765
772
|
"log-url",
|
|
766
773
|
]);
|
|
774
|
+
/**
|
|
775
|
+
* `resource.action` shapes that differ from the bare-action-name sets above:
|
|
776
|
+
* `contents`/`usages`/`project-timeline` are array-named elsewhere, and
|
|
777
|
+
* `download`/`diff` are string-named elsewhere, but these handlers return the
|
|
778
|
+
* opposite shape. Pinned by tests/cli/output-shapes.test.ts.
|
|
779
|
+
*/
|
|
780
|
+
const SCOPED_OUTPUT_SHAPES = {
|
|
781
|
+
"folder.contents": "array",
|
|
782
|
+
"scenario.last-runs": "array",
|
|
783
|
+
"plugin.usages": "object",
|
|
784
|
+
"plugin.download": "object",
|
|
785
|
+
"dataset.download": "object",
|
|
786
|
+
"project-library.get": "string",
|
|
787
|
+
"project-library.diff": "object",
|
|
788
|
+
};
|
|
767
789
|
function inferOutputShape(resource, action) {
|
|
768
790
|
if (resource === "project-git") {
|
|
769
791
|
if (action === "branches" || action === "tags" || action === "list-libraries")
|
|
@@ -777,14 +799,15 @@ function inferOutputShape(resource, action) {
|
|
|
777
799
|
|| resource === "version") {
|
|
778
800
|
return "object";
|
|
779
801
|
}
|
|
802
|
+
// Resource-scoped rules come first: the bare-name sets below classify by
|
|
803
|
+
// action alone and would otherwise misfile same-named actions elsewhere.
|
|
804
|
+
const scoped = SCOPED_OUTPUT_SHAPES[`${resource}.${action}`];
|
|
805
|
+
if (scoped !== undefined)
|
|
806
|
+
return scoped;
|
|
780
807
|
if (/^list(-|$)/.test(action))
|
|
781
808
|
return "array";
|
|
782
809
|
if (ARRAY_OUTPUT_ACTIONS.has(action))
|
|
783
810
|
return "array";
|
|
784
|
-
if (resource === "dataset" && action === "download")
|
|
785
|
-
return "object";
|
|
786
|
-
if (resource === "project-library" && action === "get")
|
|
787
|
-
return "string";
|
|
788
811
|
if (STRING_OUTPUT_ACTIONS.has(action))
|
|
789
812
|
return "string";
|
|
790
813
|
return "object";
|
|
@@ -1496,7 +1519,7 @@ export function buildAgentContract() {
|
|
|
1496
1519
|
"dss commands run --fields dataset",
|
|
1497
1520
|
"dss commands run --fields dataset.create",
|
|
1498
1521
|
],
|
|
1499
|
-
scopedDiscoveryHint: "Default: resource/action summary. --contains TEXT: top 5 actions by concept. --fields RESOURCE:
|
|
1522
|
+
scopedDiscoveryHint: "Default: resource/action summary. --contains TEXT: top 5 actions by concept. --fields RESOURCE: each action's usage, description, and side effect; RESOURCE.ACTION: one full entry keyed by that path; append .FIELD for nested metadata. Comma-separate paths. --output PATH exports full entries (the full registry without --fields).",
|
|
1500
1523
|
// Accepted by every command and absent from per-entry `flags`; the
|
|
1501
1524
|
// connection flags matter only for commands that call DSS.
|
|
1502
1525
|
globalFlags: [...GLOBAL_AGENT_FLAGS, ...AUTHENTICATED_AGENT_FLAGS,].map((name) => {
|
|
@@ -1523,7 +1546,7 @@ export function buildAgentContract() {
|
|
|
1523
1546
|
failure: "structured-error-object",
|
|
1524
1547
|
failureResultDetailLimitBytes: 65_536,
|
|
1525
1548
|
fieldProjection: "Missing --fields paths: null on stdout; field_projection_missing on stderr.",
|
|
1526
|
-
compactResults: "*.list, job get, dataset get/info, project get: compact; --full/--fields use DSS objects; lists take --contains/--limit (list_truncated warning).",
|
|
1549
|
+
compactResults: "*.list, job get, dataset get/info, project get, scenario get-run/last-runs: compact; --full/--fields use DSS objects; lists take --contains/--limit (list_truncated warning).",
|
|
1527
1550
|
richFailureResults: "doctor/batch/cleanup failures: own compact {ok:false,...} result on stdout and command exit code; no wrapper.",
|
|
1528
1551
|
},
|
|
1529
1552
|
stderr: {
|
package/dist/src/cli/env.d.ts
CHANGED
|
@@ -10,4 +10,10 @@ export declare function envFileProvenance(name: string): EnvFileProvenance | und
|
|
|
10
10
|
export declare function dataikuEnvironmentEnabled(): boolean;
|
|
11
11
|
/** DATAIKU_PROJECT_KEY from the environment, unless `DATAIKU_DISABLE_ENV=1`. */
|
|
12
12
|
export declare function ambientProjectKey(): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* `DSS_PRETTY=1` indents stdout JSON. Agent shells that cut long lines (omp at
|
|
15
|
+
* 768 bytes) truncate a one-line result; indented output keeps each line short.
|
|
16
|
+
* Read from the process environment only, never from a `.env` file.
|
|
17
|
+
*/
|
|
18
|
+
export declare function prettyOutputEnabled(): boolean;
|
|
13
19
|
export declare function loadEnvFile(): void;
|