dataiku-sdk 4.0.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.
Files changed (38) hide show
  1. package/README.md +1 -0
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +3 -1
  4. package/dist/packages/types/src/index.js +3 -1
  5. package/dist/src/cli/command-syntax.json +216 -0
  6. package/dist/src/cli/commands/code-env.js +5 -3
  7. package/dist/src/cli/commands/dataset.js +6 -4
  8. package/dist/src/cli/commands/job.js +12 -9
  9. package/dist/src/cli/commands/recipe.js +23 -2
  10. package/dist/src/cli/commands/scenario.js +76 -8
  11. package/dist/src/cli/contract.js +3 -2
  12. package/dist/src/cli/env.d.ts +6 -0
  13. package/dist/src/cli/env.js +8 -0
  14. package/dist/src/cli/output.d.ts +2 -0
  15. package/dist/src/cli/output.js +24 -4
  16. package/dist/src/cli/read-shapes.js +90 -0
  17. package/dist/src/cli/result-shapes.d.ts +19 -1
  18. package/dist/src/cli/result-shapes.js +227 -4
  19. package/dist/src/cli/selector-suggestions.d.ts +14 -0
  20. package/dist/src/cli/selector-suggestions.js +56 -0
  21. package/dist/src/cli/usage.d.ts +1 -1
  22. package/dist/src/cli/usage.js +29 -4
  23. package/dist/src/cli/version.d.ts +2 -2
  24. package/dist/src/cli/version.js +2 -2
  25. package/dist/src/cli.js +62 -13
  26. package/dist/src/client.d.ts +5 -4
  27. package/dist/src/client.js +24 -7
  28. package/dist/src/errors.d.ts +2 -0
  29. package/dist/src/errors.js +12 -2
  30. package/dist/src/generated/action-output-schemas.json +214 -0
  31. package/dist/src/resources/recipe-create.d.ts +4 -1
  32. package/dist/src/resources/recipe-create.js +11 -2
  33. package/dist/src/resources/sql.js +3 -3
  34. package/package.json +2 -1
  35. package/plugin.json +1 -1
  36. package/skills/dataiku-dss/SKILL.md +14 -16
  37. package/skills/dataiku-dss/references/discovery.md +2 -2
  38. package/skills/dataiku-dss/references/troubleshooting.md +1 -0
package/README.md CHANGED
@@ -291,6 +291,7 @@ Use environment variables for ephemeral runs. For disposable agent tests, set `D
291
291
  Credential precedence is flags first, then `DATAIKU_*` environment variables, then saved credentials in `DSS_CONFIG_DIR` or the platform config directory.
292
292
  Set `DATAIKU_DISABLE_ENV=1` when a test must ignore both `.env` files and `DATAIKU_*` environment variables.
293
293
  When `.env` loading is enabled, the CLI reads `.env` from the CLI build/root directory and from the command's current working directory; put test-specific `.env` files in the directory where you invoke `dss`. Only `DATAIKU_*`, `NODE_TLS_REJECT_UNAUTHORIZED`, and `NODE_EXTRA_CA_CERTS` are read from `.env`; other keys are ignored.
294
+ Set `DSS_PRETTY=1` to indent stdout JSON (still one JSON value). Use it when the shell or agent harness cuts long lines (omp's bash tool cuts each line at 768 bytes), so a one-line result is not truncated. It is read from the process environment only, not from `.env`.
294
295
 
295
296
  POSIX shell:
296
297
 
@@ -1 +1 @@
1
- {"format":1,"buildRevision":"ab176cb1de853bfbdc58627b38a131ce1fc1681d"}
1
+ {"format":1,"buildRevision":"88fd13012a34de8687737c530fbe86469d8e3f5d"}
@@ -286,8 +286,10 @@ export declare const JobWaitResultSchema: import("@sinclair/typebox").TObject<{
286
286
  timedOut: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
287
287
  /** Present on a wait timeout: the wait never cancels the job, so this is `false`. */
288
288
  cancelled: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
289
- /** CLI only, on a wait timeout: the command that resumes waiting for the still-running job. */
289
+ /** CLI only, on a wait timeout: POSIX display form of the command that resumes waiting for the still-running job. */
290
290
  resume: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
291
+ /** CLI only, on a wait timeout: the resume command as argv, runnable without a shell on any OS. */
292
+ resumeArgv: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
291
293
  progress: import("@sinclair/typebox").TObject<{
292
294
  done: import("@sinclair/typebox").TNumber;
293
295
  failed: import("@sinclair/typebox").TNumber;
@@ -310,8 +310,10 @@ export const JobWaitResultSchema = Type.Object({
310
310
  timedOut: Type.Optional(Type.Boolean()),
311
311
  /** Present on a wait timeout: the wait never cancels the job, so this is `false`. */
312
312
  cancelled: Type.Optional(Type.Boolean()),
313
- /** CLI only, on a wait timeout: the command that resumes waiting for the still-running job. */
313
+ /** CLI only, on a wait timeout: POSIX display form of the command that resumes waiting for the still-running job. */
314
314
  resume: Type.Optional(Type.String()),
315
+ /** CLI only, on a wait timeout: the resume command as argv, runnable without a shell on any OS. */
316
+ resumeArgv: Type.Optional(Type.Array(Type.String())),
315
317
  progress: Type.Object({
316
318
  done: Type.Number(),
317
319
  failed: Type.Number(),
@@ -87,6 +87,15 @@
87
87
  {
88
88
  "arg": "analysisId"
89
89
  },
90
+ {
91
+ "optional": [
92
+ [
93
+ {
94
+ "flag": "full"
95
+ }
96
+ ]
97
+ ]
98
+ },
90
99
  {
91
100
  "optional": [
92
101
  [
@@ -1699,6 +1708,15 @@
1699
1708
  "get",
1700
1709
  {
1701
1710
  "arg": "id"
1711
+ },
1712
+ {
1713
+ "optional": [
1714
+ [
1715
+ {
1716
+ "flag": "full"
1717
+ }
1718
+ ]
1719
+ ]
1702
1720
  }
1703
1721
  ],
1704
1722
  "business-app.install-from-archive": [
@@ -1956,6 +1974,15 @@
1956
1974
  },
1957
1975
  {
1958
1976
  "arg": "name"
1977
+ },
1978
+ {
1979
+ "optional": [
1980
+ [
1981
+ {
1982
+ "flag": "full"
1983
+ }
1984
+ ]
1985
+ ]
1959
1986
  }
1960
1987
  ],
1961
1988
  "code-env.get-definition": [
@@ -3106,6 +3133,15 @@
3106
3133
  {
3107
3134
  "arg": "id"
3108
3135
  },
3136
+ {
3137
+ "optional": [
3138
+ [
3139
+ {
3140
+ "flag": "full"
3141
+ }
3142
+ ]
3143
+ ]
3144
+ },
3109
3145
  {
3110
3146
  "optional": [
3111
3147
  [
@@ -3320,6 +3356,15 @@
3320
3356
  "get",
3321
3357
  {
3322
3358
  "arg": "dataCollectionId"
3359
+ },
3360
+ {
3361
+ "optional": [
3362
+ [
3363
+ {
3364
+ "flag": "full"
3365
+ }
3366
+ ]
3367
+ ]
3323
3368
  }
3324
3369
  ],
3325
3370
  "data-collection.list": [
@@ -5033,6 +5078,15 @@
5033
5078
  {
5034
5079
  "arg": "discussionId"
5035
5080
  },
5081
+ {
5082
+ "optional": [
5083
+ [
5084
+ {
5085
+ "flag": "full"
5086
+ }
5087
+ ]
5088
+ ]
5089
+ },
5036
5090
  {
5037
5091
  "optional": [
5038
5092
  [
@@ -5373,6 +5427,15 @@
5373
5427
  {
5374
5428
  "arg": "id"
5375
5429
  },
5430
+ {
5431
+ "optional": [
5432
+ [
5433
+ {
5434
+ "flag": "full"
5435
+ }
5436
+ ]
5437
+ ]
5438
+ },
5376
5439
  {
5377
5440
  "optional": [
5378
5441
  [
@@ -6092,6 +6155,15 @@
6092
6155
  {
6093
6156
  "arg": "name-or-id"
6094
6157
  },
6158
+ {
6159
+ "optional": [
6160
+ [
6161
+ {
6162
+ "flag": "full"
6163
+ }
6164
+ ]
6165
+ ]
6166
+ },
6095
6167
  {
6096
6168
  "optional": [
6097
6169
  [
@@ -6409,6 +6481,15 @@
6409
6481
  "get",
6410
6482
  {
6411
6483
  "arg": "name"
6484
+ },
6485
+ {
6486
+ "optional": [
6487
+ [
6488
+ {
6489
+ "flag": "full"
6490
+ }
6491
+ ]
6492
+ ]
6412
6493
  }
6413
6494
  ],
6414
6495
  "group.list": [
@@ -6634,6 +6715,15 @@
6634
6715
  {
6635
6716
  "arg": "id"
6636
6717
  },
6718
+ {
6719
+ "optional": [
6720
+ [
6721
+ {
6722
+ "flag": "full"
6723
+ }
6724
+ ]
6725
+ ]
6726
+ },
6637
6727
  {
6638
6728
  "optional": [
6639
6729
  [
@@ -7900,6 +7990,15 @@
7900
7990
  {
7901
7991
  "arg": "macro_id"
7902
7992
  },
7993
+ {
7994
+ "optional": [
7995
+ [
7996
+ {
7997
+ "flag": "full"
7998
+ }
7999
+ ]
8000
+ ]
8001
+ },
7903
8002
  {
7904
8003
  "optional": [
7905
8004
  [
@@ -8209,6 +8308,15 @@
8209
8308
  "get",
8210
8309
  {
8211
8310
  "arg": "meaningId"
8311
+ },
8312
+ {
8313
+ "optional": [
8314
+ [
8315
+ {
8316
+ "flag": "full"
8317
+ }
8318
+ ]
8319
+ ]
8212
8320
  }
8213
8321
  ],
8214
8322
  "meaning.list": [
@@ -9758,6 +9866,15 @@
9758
9866
  {
9759
9867
  "arg": "storeId"
9760
9868
  },
9869
+ {
9870
+ "optional": [
9871
+ [
9872
+ {
9873
+ "flag": "full"
9874
+ }
9875
+ ]
9876
+ ]
9877
+ },
9761
9878
  {
9762
9879
  "optional": [
9763
9880
  [
@@ -11627,6 +11744,15 @@
11627
11744
  "get",
11628
11745
  {
11629
11746
  "arg": "folderId"
11747
+ },
11748
+ {
11749
+ "optional": [
11750
+ [
11751
+ {
11752
+ "flag": "full"
11753
+ }
11754
+ ]
11755
+ ]
11630
11756
  }
11631
11757
  ],
11632
11758
  "project-folder.move": [
@@ -13940,6 +14066,15 @@
13940
14066
  ]
13941
14067
  ]
13942
14068
  },
14069
+ {
14070
+ "optional": [
14071
+ [
14072
+ {
14073
+ "flag": "full"
14074
+ }
14075
+ ]
14076
+ ]
14077
+ },
13943
14078
  {
13944
14079
  "optional": [
13945
14080
  [
@@ -15058,6 +15193,15 @@
15058
15193
  {
15059
15194
  "arg": "modelId"
15060
15195
  },
15196
+ {
15197
+ "optional": [
15198
+ [
15199
+ {
15200
+ "flag": "full"
15201
+ }
15202
+ ]
15203
+ ]
15204
+ },
15061
15205
  {
15062
15206
  "optional": [
15063
15207
  [
@@ -15612,6 +15756,15 @@
15612
15756
  {
15613
15757
  "arg": "id"
15614
15758
  },
15759
+ {
15760
+ "optional": [
15761
+ [
15762
+ {
15763
+ "flag": "full"
15764
+ }
15765
+ ]
15766
+ ]
15767
+ },
15615
15768
  {
15616
15769
  "optional": [
15617
15770
  [
@@ -15633,6 +15786,15 @@
15633
15786
  {
15634
15787
  "arg": "runId"
15635
15788
  },
15789
+ {
15790
+ "optional": [
15791
+ [
15792
+ {
15793
+ "flag": "full"
15794
+ }
15795
+ ]
15796
+ ]
15797
+ },
15636
15798
  {
15637
15799
  "optional": [
15638
15800
  [
@@ -15661,6 +15823,15 @@
15661
15823
  ]
15662
15824
  ]
15663
15825
  },
15826
+ {
15827
+ "optional": [
15828
+ [
15829
+ {
15830
+ "flag": "full"
15831
+ }
15832
+ ]
15833
+ ]
15834
+ },
15664
15835
  {
15665
15836
  "optional": [
15666
15837
  [
@@ -16525,6 +16696,15 @@
16525
16696
  {
16526
16697
  "arg": "id"
16527
16698
  },
16699
+ {
16700
+ "optional": [
16701
+ [
16702
+ {
16703
+ "flag": "full"
16704
+ }
16705
+ ]
16706
+ ]
16707
+ },
16528
16708
  {
16529
16709
  "optional": [
16530
16710
  [
@@ -16784,6 +16964,15 @@
16784
16964
  "get",
16785
16965
  {
16786
16966
  "arg": "login"
16967
+ },
16968
+ {
16969
+ "optional": [
16970
+ [
16971
+ {
16972
+ "flag": "full"
16973
+ }
16974
+ ]
16975
+ ]
16787
16976
  }
16788
16977
  ],
16789
16978
  "user.list": [
@@ -16941,6 +17130,15 @@
16941
17130
  "dss",
16942
17131
  "variable",
16943
17132
  "get",
17133
+ {
17134
+ "optional": [
17135
+ [
17136
+ {
17137
+ "flag": "full"
17138
+ }
17139
+ ]
17140
+ ]
17141
+ },
16944
17142
  {
16945
17143
  "optional": [
16946
17144
  [
@@ -17446,6 +17644,15 @@
17446
17644
  {
17447
17645
  "arg": "id-or-name"
17448
17646
  },
17647
+ {
17648
+ "optional": [
17649
+ [
17650
+ {
17651
+ "flag": "full"
17652
+ }
17653
+ ]
17654
+ ]
17655
+ },
17449
17656
  {
17450
17657
  "optional": [
17451
17658
  [
@@ -17697,6 +17904,15 @@
17697
17904
  "get",
17698
17905
  {
17699
17906
  "arg": "workspaceKey"
17907
+ },
17908
+ {
17909
+ "optional": [
17910
+ [
17911
+ {
17912
+ "flag": "full"
17913
+ }
17914
+ ]
17915
+ ]
17700
17916
  }
17701
17917
  ],
17702
17918
  "workspace.list": [
@@ -79,13 +79,15 @@ export const codeEnvCommands = withUsage("code-env", {
79
79
  examples: ["dss code-env list", "dss code-env list --lang PYTHON",],
80
80
  },
81
81
  get: {
82
- handler: (c, a) => {
82
+ handler: (c, a, f) => {
83
83
  const usage = commandUsage("code-env", "get");
84
84
  requireArgs(a, 2, usage);
85
85
  const envLang = requireEnvLang(a[0], usage);
86
- return c.codeEnvs.get(envLang, a[1]);
86
+ return f["full"] === true
87
+ ? c.codeEnvs.getDefinition(envLang, a[1])
88
+ : c.codeEnvs.get(envLang, a[1]);
87
89
  },
88
- description: "Get code environment details: requested vs installed packages, Python interpreter, deployment mode, and the stable definition hash (definitionHash) used by set-definition --expect-hash.",
90
+ description: "Get code environment details: requested vs installed packages, Python interpreter, deployment mode, and the stable definition hash (definitionHash) used by set-definition --expect-hash. --full returns the raw DSS definition (same as get-definition).",
89
91
  examples: ["dss code-env get PYTHON my_env",],
90
92
  },
91
93
  "get-definition": {
@@ -225,11 +225,13 @@ export const datasetCommands = withUsage("dataset", {
225
225
  handler: async (c, a, f) => {
226
226
  const previewUsage = commandUsage("dataset", "preview");
227
227
  requireArgs(a, 1, previewUsage);
228
- const maxRows = num(f["max-rows"], "--max-rows");
229
- if (maxRows !== undefined && (!Number.isSafeInteger(maxRows) || maxRows <= 0 || maxRows > 500)) {
230
- throw new UsageError(`--max-rows must be a positive integer no greater than 500 (got "${String(f["max-rows"])}"). `
231
- + `Usage: ${previewUsage}`, "validation_failed");
228
+ const requestedRows = num(f["max-rows"], "--max-rows");
229
+ if (requestedRows !== undefined && (!Number.isSafeInteger(requestedRows) || requestedRows <= 0)) {
230
+ throw new UsageError(`--max-rows must be a positive integer (got "${String(f["max-rows"])}"). Usage: ${previewUsage}`, "validation_failed");
232
231
  }
232
+ // Agents ask for "all rows" with a large number; serve the 500-row public
233
+ // maximum instead of failing, and let the truncation warning say if more exist.
234
+ const maxRows = requestedRows === undefined ? undefined : Math.min(requestedRows, 500);
233
235
  const result = await c.datasets.preview(a[0], {
234
236
  maxRows,
235
237
  projectKey: f["project-key"],
@@ -1,5 +1,5 @@
1
1
  import { resolve, } from "node:path";
2
- import { ClientValidationError, DataikuError, dataikuErrorCode, } from "../../errors.js";
2
+ import { ClientValidationError, DataikuError, dataikuErrorCode, RESPONSE_TOO_LARGE_STATUS_TEXT, } from "../../errors.js";
3
3
  import { asRecord, } from "../../utils/records.js";
4
4
  import { jobBuildTargetTypeFromFlags, jobLogFilterFromFlag, maxLogLinesFromFlags, num, } from "../coerce.js";
5
5
  import { executionMode, } from "../flags.js";
@@ -34,23 +34,26 @@ function tailJobLogForStdout(log, limit) {
34
34
  const tail = lines.slice(-shown).join("\n");
35
35
  return hasTrailingLineBreak ? `${tail}\n` : tail;
36
36
  }
37
- const RESPONSE_TOO_LARGE_STATUS_TEXT = "Response Too Large";
38
- /** POSIX-shell quoting for a resume command argument. */
37
+ /** POSIX-shell quoting for the display form of a resume command. */
39
38
  function shellArg(value) {
40
39
  return /^[\w.:@%+=,/-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
41
40
  }
42
- /** Command that resumes waiting for a job whose wait timed out. */
43
- function jobWaitResumeCommand(jobId, projectKey) {
44
- return `dss job wait ${shellArg(jobId)}${projectKey ? ` --project-key ${shellArg(projectKey)}` : ""}`;
45
- }
46
41
  /**
47
42
  * A wait that timed out only stopped polling: the job is still running in DSS.
48
- * Attach the command that resumes waiting; other results pass through.
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.
49
45
  */
50
46
  function withWaitResume(result, projectKey) {
51
47
  if (result.timedOut !== true)
52
48
  return result;
53
- return { ...result, resume: jobWaitResumeCommand(result.jobId, projectKey), };
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, };
54
57
  }
55
58
  /**
56
59
  * With an empty output schema DSS ends a dataset build DONE after writing no
@@ -412,9 +412,30 @@ export const recipeCommands = withUsage("recipe", {
412
412
  hint: `Fix the recipe settings, then run dss recipe update-schema ${createdName}.`,
413
413
  });
414
414
  }
415
- return { created: createdName, resource: "recipe", ...created, ...moved, };
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
+ };
416
437
  },
417
- description: "Create a recipe with optional inputs and a dataset or managed-folder output. Missing outputs are created on --output-connection as DSS managed datasets. Join, grouping (--group-by/--aggregate) and prepare (--rename/--formula/--filter/...) payloads come from flags. Visual recipe outputs get the DSS-computed schema (outputSchemaUpdated).",
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.",
418
439
  examples: [
419
440
  "dss recipe create --type python --input raw_orders,lookup --output orders_clean",
420
441
  "dss recipe create --type grouping --input orders --output orders_by_cat --group-by category --aggregate amount:sum+avg,name:first --order-by day",
@@ -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.scenarios.runAndWait(a[0], { ...options, projectKey: pk, });
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.scenarios.runAndWait(a[0], { ...options, projectKey: pk, });
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, outcome).",
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
- return c.scenarios.getRunDetails(a[0], a[1], {
219
- projectKey: f["project-key"],
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 the details of a specific scenario run, including per-step outcomes.",
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: {
@@ -779,6 +779,7 @@ const STRING_OUTPUT_ACTIONS = new Set([
779
779
  */
780
780
  const SCOPED_OUTPUT_SHAPES = {
781
781
  "folder.contents": "array",
782
+ "scenario.last-runs": "array",
782
783
  "plugin.usages": "object",
783
784
  "plugin.download": "object",
784
785
  "dataset.download": "object",
@@ -1518,7 +1519,7 @@ export function buildAgentContract() {
1518
1519
  "dss commands run --fields dataset",
1519
1520
  "dss commands run --fields dataset.create",
1520
1521
  ],
1521
- scopedDiscoveryHint: "Default: resource/action summary. --contains TEXT: top 5 actions by concept. --fields RESOURCE: all resource entries; RESOURCE.ACTION: one entry keyed by that path; append .FIELD for nested metadata. Comma-separate paths. --output PATH exports the full registry.",
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).",
1522
1523
  // Accepted by every command and absent from per-entry `flags`; the
1523
1524
  // connection flags matter only for commands that call DSS.
1524
1525
  globalFlags: [...GLOBAL_AGENT_FLAGS, ...AUTHENTICATED_AGENT_FLAGS,].map((name) => {
@@ -1545,7 +1546,7 @@ export function buildAgentContract() {
1545
1546
  failure: "structured-error-object",
1546
1547
  failureResultDetailLimitBytes: 65_536,
1547
1548
  fieldProjection: "Missing --fields paths: null on stdout; field_projection_missing on stderr.",
1548
- 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).",
1549
1550
  richFailureResults: "doctor/batch/cleanup failures: own compact {ok:false,...} result on stdout and command exit code; no wrapper.",
1550
1551
  },
1551
1552
  stderr: {