dataiku-sdk 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +124 -12
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/src/auth.js +1 -1
  4. package/dist/src/cli/coerce.js +11 -2
  5. package/dist/src/cli/commands/analysis.js +4 -3
  6. package/dist/src/cli/commands/api-service.js +5 -3
  7. package/dist/src/cli/commands/app.js +4 -3
  8. package/dist/src/cli/commands/bundle.js +2 -1
  9. package/dist/src/cli/commands/code-env.js +10 -9
  10. package/dist/src/cli/commands/dashboard.js +7 -6
  11. package/dist/src/cli/commands/data-quality.js +9 -8
  12. package/dist/src/cli/commands/dataset.js +9 -8
  13. package/dist/src/cli/commands/doctor.js +4 -2
  14. package/dist/src/cli/commands/flow-zone.js +9 -8
  15. package/dist/src/cli/commands/folder.js +9 -8
  16. package/dist/src/cli/commands/future.js +2 -1
  17. package/dist/src/cli/commands/insight.js +8 -7
  18. package/dist/src/cli/commands/job.js +4 -3
  19. package/dist/src/cli/commands/meaning.js +5 -4
  20. package/dist/src/cli/commands/ml-task.js +3 -2
  21. package/dist/src/cli/commands/model-evaluation-store.js +4 -3
  22. package/dist/src/cli/commands/notebook.js +11 -10
  23. package/dist/src/cli/commands/project-library.js +7 -6
  24. package/dist/src/cli/commands/project.js +3 -2
  25. package/dist/src/cli/commands/recipe.js +14 -13
  26. package/dist/src/cli/commands/saved-model.js +4 -3
  27. package/dist/src/cli/commands/scenario.js +9 -8
  28. package/dist/src/cli/commands/sql.js +3 -2
  29. package/dist/src/cli/commands/statistics.js +7 -6
  30. package/dist/src/cli/commands/variable.js +2 -1
  31. package/dist/src/cli/commands/webapp.js +2 -1
  32. package/dist/src/cli/commands/wiki.js +7 -6
  33. package/dist/src/cli/contract.d.ts +1 -1
  34. package/dist/src/cli/contract.js +100 -82
  35. package/dist/src/cli/flags.d.ts +4 -0
  36. package/dist/src/cli/flags.js +20 -1
  37. package/dist/src/cli.js +92 -68
  38. package/dist/src/client.d.ts +3 -1
  39. package/dist/src/client.js +78 -21
  40. package/dist/src/errors.js +2 -0
  41. package/dist/src/resources/folders.js +2 -1
  42. package/dist/src/resources/projects.d.ts +0 -2
  43. package/dist/src/resources/projects.js +3 -17
  44. package/dist/src/resources/sql.js +1 -1
  45. package/dist/src/skill.d.ts +13 -4
  46. package/dist/src/skill.js +75 -24
  47. package/dist/src/utils/response-file.d.ts +2 -0
  48. package/dist/src/utils/response-file.js +34 -0
  49. package/package.json +6 -4
  50. package/plugin.json +1 -1
  51. package/skills/dataiku-dss/SKILL.md +44 -224
  52. package/skills/dataiku-dss/references/app-releases.md +27 -0
  53. package/skills/dataiku-dss/references/authentication.md +39 -0
  54. package/skills/dataiku-dss/references/coding.md +8 -0
  55. package/skills/dataiku-dss/references/discovery.md +30 -0
  56. package/skills/dataiku-dss/references/flow-maps.md +12 -0
  57. package/skills/dataiku-dss/references/mutations.md +33 -0
  58. package/skills/dataiku-dss/references/troubleshooting.md +29 -0
@@ -1,5 +1,6 @@
1
1
  import { scenarioUpdatePreview, } from "../../resources/scenarios.js";
2
2
  import { jsonInput, num, } from "../coerce.js";
3
+ import { executionMode, } from "../flags.js";
3
4
  import { encodedProjectEndpoint, readIfExists, skipResult, } from "../output.js";
4
5
  import { requireArgs, UsageError, } from "../usage.js";
5
6
  export const scenarioCommands = {
@@ -26,7 +27,7 @@ export const scenarioCommands = {
26
27
  pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
27
28
  timeoutMs: num(f["timeout"], "--timeout"),
28
29
  };
29
- if (f["dry-run"] === true) {
30
+ if (executionMode(f).dryRun) {
30
31
  return {
31
32
  dryRun: true,
32
33
  action: "run",
@@ -54,7 +55,7 @@ export const scenarioCommands = {
54
55
  pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
55
56
  timeoutMs: num(f["timeout"], "--timeout"),
56
57
  };
57
- if (f["dry-run"] === true) {
58
+ if (executionMode(f).dryRun) {
58
59
  return {
59
60
  dryRun: true,
60
61
  action: "run-and-wait",
@@ -87,11 +88,11 @@ export const scenarioCommands = {
87
88
  handler: async (c, a, f) => {
88
89
  requireArgs(a, 1, "dss scenario delete <id>");
89
90
  const pk = f["project-key"];
90
- if (f["dry-run"] === true || f["if-exists"] === true) {
91
+ if (executionMode(f).dryRun || f["if-exists"] === true) {
91
92
  const current = await readIfExists(() => c.scenarios.get(a[0], { projectKey: pk, }));
92
93
  if (!current)
93
94
  return skipResult("scenario", a[0], "missing");
94
- if (f["dry-run"] === true) {
95
+ if (executionMode(f).dryRun) {
95
96
  return { dryRun: true, action: "delete", resource: "scenario", id: a[0], current, };
96
97
  }
97
98
  }
@@ -112,13 +113,13 @@ export const scenarioCommands = {
112
113
  scenarioType: f["type"],
113
114
  projectKey: pk,
114
115
  };
115
- if (f["if-not-exists"] === true || f["dry-run"] === true) {
116
+ if (f["if-not-exists"] === true || executionMode(f).dryRun) {
116
117
  const list = await c.scenarios.list(pk);
117
118
  const existing = list.find((s) => s.id === a[0]);
118
- if (existing && f["if-not-exists"] === true && f["dry-run"] !== true) {
119
+ if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
119
120
  return skipResult("scenario", a[0], "exists", { current: existing, });
120
121
  }
121
- if (f["dry-run"] === true) {
122
+ if (executionMode(f).dryRun) {
122
123
  return {
123
124
  dryRun: true,
124
125
  action: "create",
@@ -150,7 +151,7 @@ export const scenarioCommands = {
150
151
  throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss scenario update <id> [--data '{...}' | --data-file PATH | --stdin]");
151
152
  }
152
153
  const pk = f["project-key"];
153
- if (f["dry-run"] === true) {
154
+ if (executionMode(f).dryRun) {
154
155
  const current = await c.scenarios.get(a[0], { projectKey: pk, });
155
156
  const preview = scenarioUpdatePreview(current, data);
156
157
  return {
@@ -1,6 +1,7 @@
1
1
  import { readFileSync, } from "node:fs";
2
- import { mkdir, writeFile, } from "node:fs/promises";
2
+ import { mkdir, } from "node:fs/promises";
3
3
  import { dirname, resolve, } from "node:path";
4
+ import { writeResponseToFile, } from "../../utils/response-file.js";
4
5
  import { readStdinText, stripUtf8Bom, } from "../coerce.js";
5
6
  import { enqueueCliWarning, } from "../output.js";
6
7
  import { UsageError, } from "../usage.js";
@@ -121,7 +122,7 @@ export const sqlCommands = {
121
122
  }
122
123
  const outputPath = resolve(outputFile);
123
124
  await mkdir(dirname(outputPath), { recursive: true, });
124
- await writeFile(outputPath, `${JSON.stringify(result, null, 2)}\n`, "utf-8");
125
+ await writeResponseToFile(outputPath, new Response(`${JSON.stringify(result, null, 2)}\n`), "preserve");
125
126
  return {
126
127
  queryId: result.queryId,
127
128
  schema: result.schema,
@@ -1,4 +1,5 @@
1
1
  import { requiredJsonInput, } from "../coerce.js";
2
+ import { executionMode, } from "../flags.js";
2
3
  import { encodedProjectEndpoint, planResult, } from "../output.js";
3
4
  import { requireArgs, } from "../usage.js";
4
5
  const STATISTICS_PLAN_EXIT_CODES = { usage: 1, error: 2, transient: 3, };
@@ -51,7 +52,7 @@ export const statisticsCommands = {
51
52
  requireArgs(a, 1, "dss statistics create-worksheet <dataset> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]");
52
53
  const body = requiredJsonInput(f, "--data, --data-file, or --stdin is required (worksheet definition).");
53
54
  const projectKey = f["project-key"];
54
- if (f["dry-run"] === true) {
55
+ if (executionMode(f).dryRun) {
55
56
  return statisticsPlan("create-worksheet", {
56
57
  method: "POST",
57
58
  endpoint: statisticsWorksheetsEndpoint(c, a[0], projectKey),
@@ -72,7 +73,7 @@ export const statisticsCommands = {
72
73
  requireArgs(a, 2, "dss statistics update-worksheet <dataset> <worksheetId> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]");
73
74
  const body = requiredJsonInput(f, "--data, --data-file, or --stdin is required (worksheet definition).");
74
75
  const projectKey = f["project-key"];
75
- if (f["dry-run"] === true) {
76
+ if (executionMode(f).dryRun) {
76
77
  return statisticsPlan("update-worksheet", {
77
78
  method: "PUT",
78
79
  endpoint: statisticsWorksheetEndpoint(c, a[0], a[1], projectKey),
@@ -90,7 +91,7 @@ export const statisticsCommands = {
90
91
  handler: async (c, a, f) => {
91
92
  requireArgs(a, 2, "dss statistics delete-worksheet <dataset> <worksheetId> [--dry-run] [--project-key KEY]");
92
93
  const projectKey = f["project-key"];
93
- if (f["dry-run"] === true) {
94
+ if (executionMode(f).dryRun) {
94
95
  return statisticsPlan("delete-worksheet", {
95
96
  method: "DELETE",
96
97
  endpoint: statisticsWorksheetEndpoint(c, a[0], a[1], projectKey),
@@ -108,7 +109,7 @@ export const statisticsCommands = {
108
109
  handler: async (c, a, f) => {
109
110
  requireArgs(a, 2, "dss statistics run-worksheet <dataset> <worksheetId> [--dry-run] [--project-key KEY]");
110
111
  const projectKey = f["project-key"];
111
- if (f["dry-run"] === true) {
112
+ if (executionMode(f).dryRun) {
112
113
  return statisticsPlan("run-worksheet", {
113
114
  method: "POST",
114
115
  endpoint: `${statisticsWorksheetEndpoint(c, a[0], a[1], projectKey)}/actions/run-card`,
@@ -127,7 +128,7 @@ export const statisticsCommands = {
127
128
  requireArgs(a, 2, "dss statistics run-card <dataset> <worksheetId> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]");
128
129
  const card = requiredJsonInput(f, "--data, --data-file, or --stdin is required (card settings).");
129
130
  const projectKey = f["project-key"];
130
- if (f["dry-run"] === true) {
131
+ if (executionMode(f).dryRun) {
131
132
  return statisticsPlan("run-card", {
132
133
  method: "POST",
133
134
  endpoint: `${statisticsWorksheetEndpoint(c, a[0], a[1], projectKey)}/actions/run-card`,
@@ -147,7 +148,7 @@ export const statisticsCommands = {
147
148
  requireArgs(a, 2, "dss statistics run-computation <dataset> <worksheetId> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]");
148
149
  const computation = requiredJsonInput(f, "--data, --data-file, or --stdin is required (computation settings).");
149
150
  const projectKey = f["project-key"];
150
- if (f["dry-run"] === true) {
151
+ if (executionMode(f).dryRun) {
151
152
  return statisticsPlan("run-computation", {
152
153
  method: "POST",
153
154
  endpoint: `${statisticsWorksheetEndpoint(c, a[0], a[1], projectKey)}/actions/run-computation`,
@@ -1,4 +1,5 @@
1
1
  import { json, } from "../coerce.js";
2
+ import { executionMode, } from "../flags.js";
2
3
  import { UsageError, } from "../usage.js";
3
4
  export const variableCommands = {
4
5
  get: {
@@ -15,7 +16,7 @@ export const variableCommands = {
15
16
  if (standard === undefined && local === undefined) {
16
17
  throw new UsageError("--standard and/or --local is required.");
17
18
  }
18
- if (f["dry-run"] === true) {
19
+ if (executionMode(f).dryRun) {
19
20
  const current = await c.variables.get(pk);
20
21
  const next = f["replace"] === true
21
22
  ? { standard: standard ?? {}, local: local ?? {}, }
@@ -2,6 +2,7 @@ import { ClientValidationError, } from "../../errors.js";
2
2
  import { deepMerge, } from "../../utils/deep-merge.js";
3
3
  import { stableHash, } from "../../utils/stable-hash.js";
4
4
  import { num, requiredJsonInput, } from "../coerce.js";
5
+ import { executionMode, } from "../flags.js";
5
6
  import { requireArgs, } from "../usage.js";
6
7
  const UPDATE_SETTINGS_USAGE = "dss webapp update-settings <webappId> (--data JSON|--data-file PATH|--stdin) [--expect-hash SHA256] [--dry-run] [--project-key KEY]";
7
8
  const RESTART_USAGE = "dss webapp restart-backend <webappId> [--wait] [--timeout MS] [--poll-interval MS] [--project-key KEY]";
@@ -44,7 +45,7 @@ export const webappCommands = {
44
45
  const body = requiredJsonInput(f, "--data, --data-file, or --stdin is required (webapp settings).");
45
46
  const projectKey = f["project-key"];
46
47
  const expectHash = validateExpectHash(f["expect-hash"]);
47
- if (f["dry-run"] === true) {
48
+ if (executionMode(f).dryRun) {
48
49
  const current = await c.webapps.getSettings(a[0], projectKey);
49
50
  const currentHash = stableHash(current);
50
51
  if (expectHash !== undefined && currentHash !== expectHash.toLowerCase()) {
@@ -1,5 +1,6 @@
1
1
  import { deepMerge, } from "../../utils/deep-merge.js";
2
2
  import { jsonInput, textInput, } from "../coerce.js";
3
+ import { executionMode, } from "../flags.js";
3
4
  import { readIfExists, skipResult, } from "../output.js";
4
5
  import { requireArgs, UsageError, } from "../usage.js";
5
6
  export const wikiCommands = {
@@ -31,12 +32,12 @@ export const wikiCommands = {
31
32
  throw new UsageError("--name is required. Usage: dss wiki create --name NAME");
32
33
  const content = textInput(f);
33
34
  const pk = f["project-key"];
34
- if (f["if-not-exists"] === true || f["dry-run"] === true) {
35
+ if (f["if-not-exists"] === true || executionMode(f).dryRun) {
35
36
  const existing = (await c.wiki.list(pk)).find((article) => article.article.name === name);
36
- if (existing && f["if-not-exists"] === true && f["dry-run"] !== true) {
37
+ if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
37
38
  return skipResult("wiki", existing.article.id, "exists", { current: existing, });
38
39
  }
39
- if (f["dry-run"] === true) {
40
+ if (executionMode(f).dryRun) {
40
41
  return {
41
42
  dryRun: true,
42
43
  action: "create",
@@ -75,7 +76,7 @@ export const wikiCommands = {
75
76
  if (!data && content === undefined && name === undefined) {
76
77
  throw new UsageError("--name, --content, --file, --data, --data-file, or --stdin is required.");
77
78
  }
78
- if (f["dry-run"] === true) {
79
+ if (executionMode(f).dryRun) {
79
80
  const current = await c.wiki.get(a[0], f["project-key"]);
80
81
  const next = deepMerge(current, data ?? {});
81
82
  if (name !== undefined) {
@@ -105,11 +106,11 @@ export const wikiCommands = {
105
106
  handler: async (c, a, f) => {
106
107
  requireArgs(a, 1, "dss wiki delete <id-or-name>");
107
108
  const pk = f["project-key"];
108
- if (f["dry-run"] === true || f["if-exists"] === true) {
109
+ if (executionMode(f).dryRun || f["if-exists"] === true) {
109
110
  const current = await readIfExists(() => c.wiki.get(a[0], pk));
110
111
  if (!current)
111
112
  return skipResult("wiki", a[0], "missing");
112
- if (f["dry-run"] === true) {
113
+ if (executionMode(f).dryRun) {
113
114
  return { dryRun: true, action: "delete", resource: "wiki", article: a[0], current, };
114
115
  }
115
116
  }
@@ -103,7 +103,7 @@ export declare function inferRequiresProject(resource: string, action: string, u
103
103
  export declare function inferInputContract(usage: string): CommandInputContract;
104
104
  export declare function supportsCleanupLedger(resource: string, action: string): boolean;
105
105
  export declare function inferCleanupHint(resource: string, action: string): string | undefined;
106
- export declare function buildCommandRegistry(): Record<string, Record<string, CommandRegistryEntry>>;
106
+ export declare function buildCommandRegistry(resourceFilter?: string): Record<string, Record<string, CommandRegistryEntry>>;
107
107
  export declare function agentContractJsonSchema(): Record<string, unknown>;
108
108
  export declare function commandActionSummary(registry: Record<string, Record<string, CommandRegistryEntry>>): Record<string, string[]>;
109
109
  export declare function buildAgentContract(): Record<string, unknown>;
@@ -13,7 +13,7 @@ import { commands, } from "./commands/index.js";
13
13
  import { projectLibraryPutPayload, } from "./commands/project-library.js";
14
14
  import { resolveSqlQueryInvocation, } from "./commands/sql.js";
15
15
  import { dataikuEnvironmentEnabled, } from "./env.js";
16
- import { BOOLEAN_FLAGS, FLAG_ALIASES, KNOWN_LONG_FLAGS, SHORT_FLAGS, } from "./flags.js";
16
+ import { BOOLEAN_FLAGS, executionMode, FLAG_ALIASES, KNOWN_LONG_FLAGS, SHORT_FLAGS, } from "./flags.js";
17
17
  import { flowZoneColor, flowZoneMoveItems, flowZoneName, } from "./helpers/flow-zone.js";
18
18
  import { recipeBackupPath, recipeRunShouldWait, } from "./helpers/recipe.js";
19
19
  import { encodedProjectEndpointForPlan, planResult, } from "./output.js";
@@ -749,11 +749,11 @@ function unsafeOutputs(resource, action, producesLocalFile) {
749
749
  condition: "--output or --output-file",
750
750
  kind: "local-file",
751
751
  detail: sensitivePermissions
752
- ? "Command writes access-control identities and permissions to an owner-only local file."
753
- : "Command writes bytes to a local path and returns JSON metadata on stdout.",
752
+ ? "Writes access-control identities and permissions to an owner-only local file."
753
+ : "Writes a local file; stdout contains JSON metadata.",
754
754
  ...(sensitivePermissions
755
755
  ? {
756
- safeAlternative: "Keep the file owner-only and outside version control unless repository policy explicitly permits committing access-control data.",
756
+ safeAlternative: "Keep owner-only and outside version control unless repository policy explicitly permits committing access-control data.",
757
757
  }
758
758
  : {}),
759
759
  });
@@ -1293,10 +1293,10 @@ export function inferCleanupHint(resource, action) {
1293
1293
  return undefined;
1294
1294
  const key = `${resource}.${action}`;
1295
1295
  if (key === "notebook.save-jupyter") {
1296
- return "When the save reports created:true, delete with `dss notebook delete-jupyter <name> --if-exists`.";
1296
+ return "If created:true, delete with `dss notebook delete-jupyter <name> --if-exists`.";
1297
1297
  }
1298
1298
  if (key === "notebook.save-sql") {
1299
- return "When the save reports created:true, delete with `dss notebook delete-sql <id> --if-exists`.";
1299
+ return "If created:true, delete with `dss notebook delete-sql <id> --if-exists`.";
1300
1300
  }
1301
1301
  if (!(action.startsWith("create") || action === "clone"))
1302
1302
  return undefined;
@@ -1317,7 +1317,7 @@ export function inferCleanupHint(resource, action) {
1317
1317
  if (resource === "data-quality") {
1318
1318
  return `Delete with \`dss data-quality delete-rule <dataset> <rule-id>${ifExists}\`.`;
1319
1319
  }
1320
- return `Delete with \`dss ${resource} delete <id>${ifExists}\` when the created object is disposable.`;
1320
+ return `For disposable creates: \`dss ${resource} delete <id>${ifExists}\`.`;
1321
1321
  }
1322
1322
  function buildRegistryEntry(resource, action, meta) {
1323
1323
  const requiresAuth = meta.localHandler === undefined && inferRequiresAuth(resource);
@@ -1427,81 +1427,99 @@ function buildRegistryEntry(resource, action, meta) {
1427
1427
  agentContractVersion: AGENT_CONTRACT_VERSION,
1428
1428
  };
1429
1429
  }
1430
- export function buildCommandRegistry() {
1430
+ export function buildCommandRegistry(resourceFilter) {
1431
1431
  const registry = {};
1432
1432
  for (const [resource, actions,] of Object.entries(commands)) {
1433
+ if (resourceFilter !== undefined && resource !== resourceFilter)
1434
+ continue;
1433
1435
  registry[resource] = {};
1434
1436
  for (const [action, meta,] of Object.entries(actions)) {
1435
1437
  registry[resource][action] = buildRegistryEntry(resource, action, meta);
1436
1438
  }
1437
1439
  }
1438
- registry.commands = {
1439
- run: buildRegistryEntry("commands", "run", {
1440
- handler: async () => undefined,
1441
- usage: COMMANDS_USAGE,
1442
- description: COMMANDS_DESCRIPTION,
1443
- examples: COMMANDS_EXAMPLES,
1444
- }),
1445
- };
1446
- registry.agent = {
1447
- contract: buildRegistryEntry("agent", "contract", {
1448
- handler: async () => undefined,
1449
- usage: AGENT_CONTRACT_USAGE,
1450
- description: AGENT_CONTRACT_DESCRIPTION,
1451
- examples: AGENT_CONTRACT_EXAMPLES,
1452
- }),
1453
- };
1454
- registry.version = {
1455
- run: buildRegistryEntry("version", "run", {
1456
- handler: async () => undefined,
1457
- usage: VERSION_USAGE,
1458
- description: VERSION_DESCRIPTION,
1459
- examples: VERSION_EXAMPLES,
1460
- }),
1461
- };
1462
- registry["install-skill"] = {
1463
- run: buildRegistryEntry("install-skill", "run", {
1464
- handler: async () => undefined,
1465
- usage: INSTALL_SKILL_USAGE,
1466
- description: INSTALL_SKILL_DESCRIPTION,
1467
- examples: INSTALL_SKILL_EXAMPLES,
1468
- }),
1469
- };
1470
- registry.cleanup = {
1471
- run: buildRegistryEntry("cleanup", "run", {
1472
- handler: async () => undefined,
1473
- usage: CLEANUP_USAGE,
1474
- description: CLEANUP_DESCRIPTION,
1475
- examples: CLEANUP_EXAMPLES,
1476
- }),
1477
- };
1478
- registry.fixtures = {
1479
- run: buildRegistryEntry("fixtures", "run", {
1480
- handler: async () => undefined,
1481
- usage: FIXTURES_USAGE,
1482
- description: FIXTURES_DESCRIPTION,
1483
- examples: FIXTURES_EXAMPLES,
1484
- }),
1485
- };
1486
- registry.batch = {
1487
- run: buildRegistryEntry("batch", "run", {
1488
- handler: async () => undefined,
1489
- usage: BATCH_USAGE,
1490
- description: BATCH_DESCRIPTION,
1491
- examples: BATCH_EXAMPLES,
1492
- examplePayload: BATCH_EXAMPLE_PAYLOAD,
1493
- payloadSchema: { stdin: true, dataFlag: true, dataFileFlag: true, jsonShape: "array", },
1494
- }),
1495
- };
1496
- registry.auth = {};
1497
- for (const [action, meta,] of Object.entries(AUTH_ACTIONS)) {
1498
- registry.auth[action] = buildRegistryEntry("auth", action, {
1499
- handler: async () => undefined,
1500
- usage: meta.usage,
1501
- description: meta.description,
1502
- examples: meta.examples,
1503
- requiredFlags: meta.requiredFlags,
1504
- });
1440
+ if (resourceFilter === undefined || resourceFilter === "commands") {
1441
+ registry.commands = {
1442
+ run: buildRegistryEntry("commands", "run", {
1443
+ handler: async () => undefined,
1444
+ usage: COMMANDS_USAGE,
1445
+ description: COMMANDS_DESCRIPTION,
1446
+ examples: COMMANDS_EXAMPLES,
1447
+ }),
1448
+ };
1449
+ }
1450
+ if (resourceFilter === undefined || resourceFilter === "agent") {
1451
+ registry.agent = {
1452
+ contract: buildRegistryEntry("agent", "contract", {
1453
+ handler: async () => undefined,
1454
+ usage: AGENT_CONTRACT_USAGE,
1455
+ description: AGENT_CONTRACT_DESCRIPTION,
1456
+ examples: AGENT_CONTRACT_EXAMPLES,
1457
+ }),
1458
+ };
1459
+ }
1460
+ if (resourceFilter === undefined || resourceFilter === "version") {
1461
+ registry.version = {
1462
+ run: buildRegistryEntry("version", "run", {
1463
+ handler: async () => undefined,
1464
+ usage: VERSION_USAGE,
1465
+ description: VERSION_DESCRIPTION,
1466
+ examples: VERSION_EXAMPLES,
1467
+ }),
1468
+ };
1469
+ }
1470
+ if (resourceFilter === undefined || resourceFilter === "install-skill") {
1471
+ registry["install-skill"] = {
1472
+ run: buildRegistryEntry("install-skill", "run", {
1473
+ handler: async () => undefined,
1474
+ usage: INSTALL_SKILL_USAGE,
1475
+ description: INSTALL_SKILL_DESCRIPTION,
1476
+ examples: INSTALL_SKILL_EXAMPLES,
1477
+ }),
1478
+ };
1479
+ }
1480
+ if (resourceFilter === undefined || resourceFilter === "cleanup") {
1481
+ registry.cleanup = {
1482
+ run: buildRegistryEntry("cleanup", "run", {
1483
+ handler: async () => undefined,
1484
+ usage: CLEANUP_USAGE,
1485
+ description: CLEANUP_DESCRIPTION,
1486
+ examples: CLEANUP_EXAMPLES,
1487
+ }),
1488
+ };
1489
+ }
1490
+ if (resourceFilter === undefined || resourceFilter === "fixtures") {
1491
+ registry.fixtures = {
1492
+ run: buildRegistryEntry("fixtures", "run", {
1493
+ handler: async () => undefined,
1494
+ usage: FIXTURES_USAGE,
1495
+ description: FIXTURES_DESCRIPTION,
1496
+ examples: FIXTURES_EXAMPLES,
1497
+ }),
1498
+ };
1499
+ }
1500
+ if (resourceFilter === undefined || resourceFilter === "batch") {
1501
+ registry.batch = {
1502
+ run: buildRegistryEntry("batch", "run", {
1503
+ handler: async () => undefined,
1504
+ usage: BATCH_USAGE,
1505
+ description: BATCH_DESCRIPTION,
1506
+ examples: BATCH_EXAMPLES,
1507
+ examplePayload: BATCH_EXAMPLE_PAYLOAD,
1508
+ payloadSchema: { stdin: true, dataFlag: true, dataFileFlag: true, jsonShape: "array", },
1509
+ }),
1510
+ };
1511
+ }
1512
+ if (resourceFilter === undefined || resourceFilter === "auth") {
1513
+ registry.auth = {};
1514
+ for (const [action, meta,] of Object.entries(AUTH_ACTIONS)) {
1515
+ registry.auth[action] = buildRegistryEntry("auth", action, {
1516
+ handler: async () => undefined,
1517
+ usage: meta.usage,
1518
+ description: meta.description,
1519
+ examples: meta.examples,
1520
+ requiredFlags: meta.requiredFlags,
1521
+ });
1522
+ }
1505
1523
  }
1506
1524
  return registry;
1507
1525
  }
@@ -1680,7 +1698,7 @@ export function buildAgentContract() {
1680
1698
  "dss commands run --fields dataset",
1681
1699
  "dss commands run --fields dataset.create",
1682
1700
  ],
1683
- scopedDiscoveryHint: "`dss commands run` returns the resource/action summary. --fields RESOURCE returns every action of one resource; --fields RESOURCE.ACTION returns a single registry entry keyed by the dotted path; append .FIELD paths to project nested metadata. Comma-separate paths to select several. Use --output PATH to export the full registry.",
1701
+ scopedDiscoveryHint: "Default: resource/action summary. --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.",
1684
1702
  actions: commandActionSummary(registry),
1685
1703
  },
1686
1704
  schemas: {
@@ -1697,8 +1715,8 @@ export function buildAgentContract() {
1697
1715
  success: "single-json-value",
1698
1716
  failure: "structured-error-object",
1699
1717
  failureResultDetailLimitBytes: 65_536,
1700
- fieldProjection: "Missing --fields paths stay null on stdout and emit field_projection_missing on stderr.",
1701
- richFailureResults: "doctor/batch/cleanup nonzero outcomes are their own compact result on stdout ({ok:false,...}) with the command's exit code; not re-wrapped.",
1718
+ fieldProjection: "Missing --fields paths: null on stdout; field_projection_missing on stderr.",
1719
+ richFailureResults: "doctor/batch/cleanup failures: own compact {ok:false,...} result on stdout and command exit code; no wrapper.",
1702
1720
  },
1703
1721
  stderr: {
1704
1722
  format: "jsonl",
@@ -1715,7 +1733,7 @@ export function buildAgentContract() {
1715
1733
  },
1716
1734
  compatibility: {
1717
1735
  fieldsAreAdditiveWithinMajor: true,
1718
- failFastWhenUnsupported: "Check agentContractVersion before planning commands.",
1736
+ failFastWhenUnsupported: "Check agentContractVersion before planning.",
1719
1737
  },
1720
1738
  };
1721
1739
  }
@@ -3654,7 +3672,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3654
3672
  wait: true,
3655
3673
  })}`;
3656
3674
  const guarded = flags["if-exists"] === true
3657
- || flags["dry-run"] === true
3675
+ || executionMode(flags).dryRun
3658
3676
  || expectedProjectIncarnation !== undefined;
3659
3677
  if (!guarded) {
3660
3678
  return {
@@ -3671,7 +3689,7 @@ export function commandPlanShape(resource, action, args, flags, entry, projectKe
3671
3689
  provided: true,
3672
3690
  expectedHash: expectedProjectIncarnation,
3673
3691
  };
3674
- if (flags["dry-run"] === true) {
3692
+ if (executionMode(flags).dryRun) {
3675
3693
  return {
3676
3694
  method: "GET",
3677
3695
  endpoint: projectProbe,
@@ -4080,7 +4098,7 @@ export function buildMutationPlan(resource, action, meta, args, flags) {
4080
4098
  asyncKind: entry.async,
4081
4099
  exitCodesOnFailure: exitCodesOnFailure(entry),
4082
4100
  idempotency: entry.idempotency,
4083
- plannedAndDryRun: flags["dry-run"] === true,
4101
+ plannedAndDryRun: executionMode(flags).dryRun,
4084
4102
  });
4085
4103
  }
4086
4104
  /**
@@ -1,3 +1,7 @@
1
+ export declare function executionMode(flags: Record<string, string | boolean>): {
2
+ readonly plan: boolean;
3
+ readonly dryRun: boolean;
4
+ };
1
5
  export declare const BOOLEAN_FLAGS: Set<string>;
2
6
  export declare const SHORT_FLAGS: Record<string, string>;
3
7
  /** Long-flag aliases: these are normalized to the canonical name in parseArgs. */
@@ -1,4 +1,17 @@
1
+ import { parseBooleanOption, } from "./coerce.js";
1
2
  import { unsupportedHelpFlag, UsageError, } from "./usage.js";
3
+ /** Planning wins dispatch; dryRun remains set for combined plan metadata. */
4
+ const EXECUTION_MODES = [
5
+ Object.freeze({ plan: false, dryRun: false, }),
6
+ Object.freeze({ plan: false, dryRun: true, }),
7
+ Object.freeze({ plan: true, dryRun: false, }),
8
+ Object.freeze({ plan: true, dryRun: true, }),
9
+ ];
10
+ export function executionMode(flags) {
11
+ const plan = parseBooleanOption(flags["plan"], "--plan") ?? false;
12
+ const dryRun = parseBooleanOption(flags["dry-run"], "--dry-run") ?? false;
13
+ return EXECUTION_MODES[(plan ? 2 : 0) + (dryRun ? 1 : 0)];
14
+ }
2
15
  export const BOOLEAN_FLAGS = new Set([
3
16
  "verbose",
4
17
  "version",
@@ -273,7 +286,13 @@ export function parseArgs(argv) {
273
286
  if (eqIdx !== -1) {
274
287
  const raw = arg.slice(2, eqIdx);
275
288
  const flagName = normalizeLongFlag(raw);
276
- setParsedFlagValue(flags, flagName, arg.slice(eqIdx + 1));
289
+ const value = arg.slice(eqIdx + 1);
290
+ if (BOOLEAN_FLAGS.has(flagName)) {
291
+ flags[flagName] = parseBooleanOption(value, `--${flagName}`);
292
+ }
293
+ else {
294
+ setParsedFlagValue(flags, flagName, value);
295
+ }
277
296
  }
278
297
  else {
279
298
  const rawFlagName = arg.slice(2);