dataiku-sdk 2.0.0 → 3.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 (80) hide show
  1. package/LICENSE +107 -17
  2. package/README.md +144 -17
  3. package/dist/build-metadata.json +1 -1
  4. package/dist/packages/types/src/index.d.ts +39 -4
  5. package/dist/packages/types/src/index.js +31 -2
  6. package/dist/src/cli/commands/api-service.js +62 -16
  7. package/dist/src/cli/commands/bundle.js +49 -15
  8. package/dist/src/cli/commands/code-env.js +210 -49
  9. package/dist/src/cli/commands/code.d.ts +19 -0
  10. package/dist/src/cli/commands/code.js +87 -15
  11. package/dist/src/cli/commands/dataset.js +8 -3
  12. package/dist/src/cli/commands/flow-zone.js +33 -5
  13. package/dist/src/cli/commands/notebook.js +72 -41
  14. package/dist/src/cli/commands/project-git.js +8 -5
  15. package/dist/src/cli/commands/project-library.d.ts +21 -0
  16. package/dist/src/cli/commands/project-library.js +121 -50
  17. package/dist/src/cli/commands/project.js +16 -2
  18. package/dist/src/cli/commands/recipe.js +37 -12
  19. package/dist/src/cli/commands/sql.d.ts +10 -0
  20. package/dist/src/cli/commands/sql.js +39 -17
  21. package/dist/src/cli/commands/webapp.js +67 -13
  22. package/dist/src/cli/contract.d.ts +15 -0
  23. package/dist/src/cli/contract.js +870 -107
  24. package/dist/src/cli/env.d.ts +9 -0
  25. package/dist/src/cli/env.js +20 -2
  26. package/dist/src/cli/flags.js +9 -0
  27. package/dist/src/cli/helpers/cleanup.js +104 -3
  28. package/dist/src/cli/helpers/flow-zone.d.ts +3 -0
  29. package/dist/src/cli/helpers/flow-zone.js +33 -3
  30. package/dist/src/cli/helpers/recipe.d.ts +20 -0
  31. package/dist/src/cli/helpers/recipe.js +158 -3
  32. package/dist/src/cli/output.d.ts +2 -0
  33. package/dist/src/cli/output.js +33 -6
  34. package/dist/src/cli/runtime.d.ts +35 -1
  35. package/dist/src/cli/runtime.js +125 -18
  36. package/dist/src/cli/types.d.ts +7 -0
  37. package/dist/src/cli.js +135 -28
  38. package/dist/src/client.d.ts +60 -0
  39. package/dist/src/client.js +237 -42
  40. package/dist/src/errors.d.ts +8 -2
  41. package/dist/src/errors.js +40 -0
  42. package/dist/src/index.d.ts +4 -3
  43. package/dist/src/index.js +2 -1
  44. package/dist/src/resources/api-services.d.ts +48 -6
  45. package/dist/src/resources/api-services.js +54 -8
  46. package/dist/src/resources/bundles.d.ts +51 -6
  47. package/dist/src/resources/bundles.js +39 -9
  48. package/dist/src/resources/code-envs.d.ts +25 -3
  49. package/dist/src/resources/code-envs.js +104 -2
  50. package/dist/src/resources/datasets.d.ts +20 -0
  51. package/dist/src/resources/datasets.js +232 -52
  52. package/dist/src/resources/jobs.d.ts +35 -10
  53. package/dist/src/resources/jobs.js +92 -17
  54. package/dist/src/resources/notebooks.d.ts +77 -7
  55. package/dist/src/resources/notebooks.js +143 -19
  56. package/dist/src/resources/project-git.d.ts +12 -0
  57. package/dist/src/resources/project-git.js +39 -8
  58. package/dist/src/resources/project-library.d.ts +122 -7
  59. package/dist/src/resources/project-library.js +494 -25
  60. package/dist/src/resources/projects.d.ts +5 -2
  61. package/dist/src/resources/projects.js +18 -2
  62. package/dist/src/resources/scenarios.d.ts +30 -0
  63. package/dist/src/resources/scenarios.js +73 -27
  64. package/dist/src/resources/sql.d.ts +6 -0
  65. package/dist/src/resources/sql.js +22 -2
  66. package/dist/src/resources/webapps.d.ts +35 -4
  67. package/dist/src/resources/webapps.js +54 -5
  68. package/dist/src/schemas.d.ts +2 -2
  69. package/dist/src/schemas.js +1 -1
  70. package/dist/src/skill.d.ts +9 -0
  71. package/dist/src/skill.js +37 -4
  72. package/dist/src/utils/flow-analysis.d.ts +53 -0
  73. package/dist/src/utils/flow-analysis.js +382 -0
  74. package/dist/src/utils/project-archive.d.ts +41 -2
  75. package/dist/src/utils/project-archive.js +159 -16
  76. package/package.json +5 -5
  77. package/packages/types/dist/index.d.ts +39 -4
  78. package/packages/types/dist/index.js +31 -2
  79. package/plugin.json +2 -2
  80. package/skills/dataiku-dss/SKILL.md +75 -48
@@ -31,6 +31,25 @@ export interface ScenarioUpdateResult extends ScenarioUpdatePreview {
31
31
  verified: true;
32
32
  mismatches: [];
33
33
  }
34
+ export interface ScenarioScriptRunCleanup {
35
+ status: "deleted" | "kept" | "failed";
36
+ error?: string;
37
+ }
38
+ /** Cleanup failure carried on errors thrown from {@link ScenariosResource.runScript}. */
39
+ export interface ScenarioScriptRunCleanupFailure {
40
+ scenarioId: string;
41
+ error: string;
42
+ }
43
+ /**
44
+ * Wraps a primary code-run failure so the original error (and its taxonomy:
45
+ * category, exit-code semantics) stays intact while the concurrent throwaway
46
+ * cleanup failure remains observable via {@link cleanupFailure}.
47
+ */
48
+ export declare class ScenarioScriptRunWithCleanupFailureError extends Error {
49
+ readonly cause: unknown;
50
+ readonly cleanupFailure: ScenarioScriptRunCleanupFailure;
51
+ constructor(cause: unknown, cleanupFailure: ScenarioScriptRunCleanupFailure);
52
+ }
34
53
  export interface ScenarioScriptRunResult {
35
54
  scenarioId: string;
36
55
  runId: string;
@@ -43,6 +62,10 @@ export interface ScenarioScriptRunResult {
43
62
  logTruncated: boolean;
44
63
  maxLogBytes: number;
45
64
  envName?: string;
65
+ timedOut?: boolean;
66
+ timeoutMs?: number;
67
+ /** Observable throwaway-scenario cleanup outcome; never silently swallowed here. */
68
+ cleanup: ScenarioScriptRunCleanup;
46
69
  }
47
70
  export declare function normalizeScenarioUpdateData(data: Record<string, unknown>): {
48
71
  normalizedData: Record<string, unknown>;
@@ -95,4 +118,11 @@ export declare class ScenariosResource extends BaseResource {
95
118
  keepScenario?: boolean;
96
119
  maxLogBytes?: number;
97
120
  }): Promise<ScenarioScriptRunResult>;
121
+ /**
122
+ * Delete the throwaway scenario at the end of a code run, or report the
123
+ * explicit keep outcome. Never throws: delete failures are surfaced in the
124
+ * returned cleanup outcome instead of being swallowed. A 404 is idempotent
125
+ * success — the scenario is already gone.
126
+ */
127
+ private removeThrowawayScenario;
98
128
  }
@@ -12,6 +12,21 @@ export const SCENARIO_CANONICAL_EDITABLE_FIELDS = [
12
12
  "active",
13
13
  "name",
14
14
  ];
15
+ /**
16
+ * Wraps a primary code-run failure so the original error (and its taxonomy:
17
+ * category, exit-code semantics) stays intact while the concurrent throwaway
18
+ * cleanup failure remains observable via {@link cleanupFailure}.
19
+ */
20
+ export class ScenarioScriptRunWithCleanupFailureError extends Error {
21
+ cause;
22
+ cleanupFailure;
23
+ constructor(cause, cleanupFailure) {
24
+ super("Code run failed and its throwaway scenario cleanup also failed.");
25
+ this.cause = cause;
26
+ this.cleanupFailure = cleanupFailure;
27
+ this.name = "ScenarioScriptRunWithCleanupFailureError";
28
+ }
29
+ }
15
30
  const DEFAULT_CODE_RUN_MAX_LOG_BYTES = 1_048_576;
16
31
  const CODE_RUN_OUTPUT_START = "<<<DSS_CODE_RUN_OUTPUT_b7e3a1>>>";
17
32
  const CODE_RUN_OUTPUT_END = "<<<DSS_CODE_RUN_OUTPUT_END_b7e3a1>>>";
@@ -421,6 +436,13 @@ export class ScenariosResource extends BaseResource {
421
436
  const adaptivePolling = opts?.pollIntervalMs === undefined;
422
437
  const timeout = Math.max(baseIntervalMs, opts?.timeoutMs ?? 120_000);
423
438
  const maxLogBytes = Math.max(0, Math.floor(opts?.maxLogBytes ?? DEFAULT_CODE_RUN_MAX_LOG_BYTES));
439
+ let outcome = "UNKNOWN";
440
+ let runId = "";
441
+ let pollCount = 0;
442
+ let timedOut = false;
443
+ let log = "";
444
+ let logTruncated = false;
445
+ let primaryError;
424
446
  try {
425
447
  await this.client.post(`/public/api/projects/${pkEnc}/scenarios/`, {
426
448
  id: scenarioId,
@@ -438,12 +460,10 @@ export class ScenariosResource extends BaseResource {
438
460
  throw new DataikuError(500, "Scenario run not started", `Scenario "${scenarioId}" run trigger returned no run id; cannot track the run.`);
439
461
  }
440
462
  const trigQuery = `triggerId=${encodeURIComponent(triggerId)}&triggerRunId=${encodeURIComponent(triggerRunId)}`;
441
- let runId = "";
442
- let outcome = "UNKNOWN";
443
- let pollCount = 0;
444
463
  while (true) {
445
464
  if (Date.now() - startedAt >= timeout) {
446
465
  outcome = "TIMEOUT";
466
+ timedOut = true;
447
467
  break;
448
468
  }
449
469
  pollCount += 1;
@@ -465,37 +485,63 @@ export class ScenariosResource extends BaseResource {
465
485
  });
466
486
  await new Promise((r) => setTimeout(r, Math.min(nextDelayMs, Math.max(1, timeout - (Date.now() - startedAt)))));
467
487
  }
468
- let log = "";
469
- let logTruncated = false;
470
488
  if (runId && outcome !== "TIMEOUT") {
471
489
  const limitedLog = await this.client.getTextLimited(`${base}/${encodeURIComponent(runId)}/log`, maxLogBytes);
472
490
  log = limitedLog.text;
473
491
  logTruncated = limitedLog.truncated;
474
492
  }
475
- const output = extractCodeRunOutput(log);
476
- return {
477
- scenarioId,
478
- runId,
479
- outcome,
480
- success: outcome === "SUCCESS",
481
- elapsedMs: Date.now() - startedAt,
482
- pollCount,
483
- output,
484
- log,
485
- logTruncated,
486
- maxLogBytes,
487
- ...(opts?.envName ? { envName: opts.envName, } : {}),
488
- };
489
493
  }
490
- finally {
491
- if (opts?.keepScenario !== true) {
492
- try {
493
- await this.client.del(base);
494
- }
495
- catch {
496
- // Best-effort cleanup of the throwaway scenario.
497
- }
494
+ catch (error) {
495
+ primaryError = error;
496
+ }
497
+ const cleanup = await this.removeThrowawayScenario(base, opts?.keepScenario === true);
498
+ if (primaryError !== undefined) {
499
+ if (cleanup.status === "failed") {
500
+ throw new ScenarioScriptRunWithCleanupFailureError(primaryError, {
501
+ scenarioId,
502
+ error: cleanup.error ?? "unknown cleanup error",
503
+ });
498
504
  }
505
+ throw primaryError;
506
+ }
507
+ const output = extractCodeRunOutput(log);
508
+ return {
509
+ scenarioId,
510
+ runId,
511
+ outcome,
512
+ success: outcome === "SUCCESS",
513
+ elapsedMs: Date.now() - startedAt,
514
+ pollCount,
515
+ output,
516
+ log,
517
+ logTruncated,
518
+ maxLogBytes,
519
+ ...(opts?.envName ? { envName: opts.envName, } : {}),
520
+ ...(timedOut ? { timedOut: true, timeoutMs: timeout, } : {}),
521
+ cleanup,
522
+ };
523
+ }
524
+ /**
525
+ * Delete the throwaway scenario at the end of a code run, or report the
526
+ * explicit keep outcome. Never throws: delete failures are surfaced in the
527
+ * returned cleanup outcome instead of being swallowed. A 404 is idempotent
528
+ * success — the scenario is already gone.
529
+ */
530
+ async removeThrowawayScenario(base, keep) {
531
+ if (keep)
532
+ return { status: "kept", };
533
+ try {
534
+ await this.client.del(base);
535
+ return { status: "deleted", };
536
+ }
537
+ catch (error) {
538
+ if (error instanceof DataikuError && error.category === "not_found") {
539
+ return { status: "deleted", };
540
+ }
541
+ return {
542
+ status: "failed",
543
+ error: error instanceof Error ? error.message : String(error),
544
+ };
499
545
  }
500
546
  }
501
547
  }
@@ -25,6 +25,12 @@ export declare class SqlResource extends BaseResource {
25
25
  startQuery(opts: SqlQueryOptions): Promise<SqlQueryResult>;
26
26
  /**
27
27
  * Stream results of a started query as parsed JSON (array of arrays).
28
+ *
29
+ * The result body is buffered under the client's response body limit
30
+ * (default 50 MiB, `maxResponseBodyBytes`) and the request deadline. A
31
+ * response exceeding the limit is cancelled and rejected instead of being
32
+ * parsed — otherwise a collaborator-controlled result set (or a stalled
33
+ * stream) could exhaust client memory or hang unbounded.
28
34
  */
29
35
  streamResults(queryId: string): Promise<unknown[][]>;
30
36
  /**
@@ -1,6 +1,12 @@
1
1
  import { ClientValidationError, DataikuError, } from "../errors.js";
2
2
  import { BaseResource, } from "./base.js";
3
3
  const UNSUPPORTED_SQL_DATASET_CONNECTION_DETAIL = "neither of sql nor hdfs type";
4
+ /**
5
+ * Cap on the finish-streaming status text behind the client's response body
6
+ * limit: the payload is a short status message, so any larger body is a
7
+ * misbehaving endpoint and must not be buffered further.
8
+ */
9
+ const MAX_SQL_FINISH_TEXT_BYTES = 1024 * 1024;
4
10
  function isUnsupportedSqlDatasetConnectionError(error) {
5
11
  if (!(error instanceof DataikuError))
6
12
  return false;
@@ -135,10 +141,20 @@ export class SqlResource extends BaseResource {
135
141
  }
136
142
  /**
137
143
  * Stream results of a started query as parsed JSON (array of arrays).
144
+ *
145
+ * The result body is buffered under the client's response body limit
146
+ * (default 50 MiB, `maxResponseBodyBytes`) and the request deadline. A
147
+ * response exceeding the limit is cancelled and rejected instead of being
148
+ * parsed — otherwise a collaborator-controlled result set (or a stalled
149
+ * stream) could exhaust client memory or hang unbounded.
138
150
  */
139
151
  async streamResults(queryId) {
140
152
  const id = encodeURIComponent(queryId);
141
- const text = await this.client.getText(`/public/api/sql/queries/${id}/stream?format=json`);
153
+ const maxBytes = this.client.getMaxResponseBodyBytes();
154
+ const { text, truncated, } = await this.client.getTextLimited(`/public/api/sql/queries/${id}/stream?format=json`, maxBytes);
155
+ if (truncated) {
156
+ throw new DataikuError(0, "Response Too Large", `SQL stream results exceeded the ${maxBytes}-byte response limit and were not parsed.`);
157
+ }
142
158
  return JSON.parse(text);
143
159
  }
144
160
  /**
@@ -147,7 +163,11 @@ export class SqlResource extends BaseResource {
147
163
  */
148
164
  async finishStreaming(queryId) {
149
165
  const id = encodeURIComponent(queryId);
150
- const text = await this.client.getText(`/public/api/sql/queries/${id}/finish-streaming`);
166
+ const maxBytes = Math.min(this.client.getMaxResponseBodyBytes(), MAX_SQL_FINISH_TEXT_BYTES);
167
+ const { text, truncated, } = await this.client.getTextLimited(`/public/api/sql/queries/${id}/finish-streaming`, maxBytes);
168
+ if (truncated) {
169
+ throw new DataikuError(0, "Response Too Large", `SQL finish-streaming response exceeded the ${maxBytes}-byte response limit.`);
170
+ }
151
171
  if (text.length > 0) {
152
172
  throw new Error(`SQL query ${queryId} failed: ${text}`);
153
173
  }
@@ -1,3 +1,4 @@
1
+ import type { FutureWaitResult } from "../schemas.js";
1
2
  import { BaseResource } from "./base.js";
2
3
  export interface WebappListItem {
3
4
  id?: string;
@@ -9,6 +10,20 @@ export interface WebappListItem {
9
10
  export interface WebappCreateResult {
10
11
  webAppId: string;
11
12
  }
13
+ /**
14
+ * 64-hex SHA-256 hash of the webapp object as last fetched, used as a
15
+ * non-atomic stale-read guard: the write is refused when the object already
16
+ * differs at the verification read. DSS exposes no conditional write (no
17
+ * ETag, no If-Match), so a writer committing between that read and the PUT
18
+ * is still not detected.
19
+ */
20
+ export interface WebappUpdateOptions {
21
+ expectHash?: string;
22
+ }
23
+ export interface WebappWaitOptions {
24
+ pollIntervalMs?: number;
25
+ timeoutMs?: number;
26
+ }
12
27
  export declare class WebappsResource extends BaseResource {
13
28
  /** List all webapps in a project. */
14
29
  list(projectKey?: string): Promise<WebappListItem[]>;
@@ -16,12 +31,28 @@ export declare class WebappsResource extends BaseResource {
16
31
  getSettings(webappId: string, projectKey?: string): Promise<Record<string, unknown>>;
17
32
  /** Create a webapp. */
18
33
  create(body: Record<string, unknown>, projectKey?: string): Promise<WebappCreateResult>;
19
- /** Update one webapp's settings. */
20
- updateSettings(webappId: string, body: Record<string, unknown>, projectKey?: string): Promise<Record<string, unknown>>;
34
+ /**
35
+ * Update one webapp's settings via GET-merge-PUT. The public API requires
36
+ * the object passed to a PUT call to have been obtained from a GET call at
37
+ * the same URL, so the current object is fetched first, the supplied
38
+ * fields are merged over it (fields absent from `body` are preserved), and
39
+ * the whole merged object is PUT back.
40
+ *
41
+ * With `expectHash` armed, the fresh GET doubles as the stale-read guard:
42
+ * the stable hash of the fetched object must match, or the write is
43
+ * refused before any PUT.
44
+ */
45
+ updateSettings(webappId: string, body: Record<string, unknown>, projectKey?: string, options?: WebappUpdateOptions): Promise<Record<string, unknown>>;
21
46
  /** Stop a webapp backend. */
22
47
  stopBackend(webappId: string, projectKey?: string): Promise<void>;
23
- /** Start or restart a webapp backend. */
24
- startOrRestartBackend(webappId: string, projectKey?: string): Promise<void>;
48
+ /**
49
+ * Start or restart a webapp backend. The documented endpoint returns a
50
+ * reference to a future, which is surfaced as-is; settle it with
51
+ * {@link restartBackendAndWait} or `client.futures.wait`.
52
+ */
53
+ startOrRestartBackend(webappId: string, projectKey?: string): Promise<Record<string, unknown>>;
54
+ /** Restart a webapp backend and wait for the returned future to settle. */
55
+ restartBackendAndWait(webappId: string, projectKey?: string, options?: WebappWaitOptions): Promise<FutureWaitResult>;
25
56
  /** Get a webapp backend's state. */
26
57
  getBackendState(webappId: string, projectKey?: string): Promise<Record<string, unknown>>;
27
58
  }
@@ -1,4 +1,22 @@
1
+ import { ClientValidationError, } from "../errors.js";
2
+ import { deepMerge, } from "../utils/deep-merge.js";
3
+ import { stableHash, } from "../utils/stable-hash.js";
1
4
  import { BaseResource, } from "./base.js";
5
+ const EXPECT_HASH_PATTERN = /^[0-9a-fA-F]{64}$/;
6
+ function assertExpectedHash(id, expectHash, current, projectKey) {
7
+ if (!EXPECT_HASH_PATTERN.test(expectHash)) {
8
+ throw new ClientValidationError("Expected webapp hash must be a 64-character SHA-256 hex digest.", "validation_failed", "Use the hash value returned by a previous read.", { projectKey, id, });
9
+ }
10
+ const actual = stableHash(current);
11
+ if (actual !== expectHash.toLowerCase()) {
12
+ throw new ClientValidationError(`The webapp ${JSON.stringify(id)} changed since it was read.`, "validation_failed", "Re-read the webapp settings and retry with the current hash value.", {
13
+ projectKey,
14
+ id,
15
+ expectedHash: expectHash.toLowerCase(),
16
+ actualHash: actual,
17
+ });
18
+ }
19
+ }
2
20
  export class WebappsResource extends BaseResource {
3
21
  /** List all webapps in a project. */
4
22
  async list(projectKey) {
@@ -13,20 +31,51 @@ export class WebappsResource extends BaseResource {
13
31
  async create(body, projectKey) {
14
32
  return this.client.post(`/public/api/projects/${this.enc(projectKey)}/webapps/`, body);
15
33
  }
16
- /** Update one webapp's settings. */
17
- async updateSettings(webappId, body, projectKey) {
34
+ /**
35
+ * Update one webapp's settings via GET-merge-PUT. The public API requires
36
+ * the object passed to a PUT call to have been obtained from a GET call at
37
+ * the same URL, so the current object is fetched first, the supplied
38
+ * fields are merged over it (fields absent from `body` are preserved), and
39
+ * the whole merged object is PUT back.
40
+ *
41
+ * With `expectHash` armed, the fresh GET doubles as the stale-read guard:
42
+ * the stable hash of the fetched object must match, or the write is
43
+ * refused before any PUT.
44
+ */
45
+ async updateSettings(webappId, body, projectKey, options = {}) {
46
+ const current = await this.getSettings(webappId, projectKey);
47
+ if (options.expectHash !== undefined) {
48
+ assertExpectedHash(webappId, options.expectHash, current, projectKey);
49
+ }
50
+ const next = deepMerge(current, body);
18
51
  const id = encodeURIComponent(webappId);
19
- return this.client.put(`/public/api/projects/${this.enc(projectKey)}/webapps/${id}`, body);
52
+ return this.client.put(`/public/api/projects/${this.enc(projectKey)}/webapps/${id}`, next);
20
53
  }
21
54
  /** Stop a webapp backend. */
22
55
  async stopBackend(webappId, projectKey) {
23
56
  const id = encodeURIComponent(webappId);
24
57
  await this.client.putVoid(`/public/api/projects/${this.enc(projectKey)}/webapps/${id}/backend/actions/stop`, {});
25
58
  }
26
- /** Start or restart a webapp backend. */
59
+ /**
60
+ * Start or restart a webapp backend. The documented endpoint returns a
61
+ * reference to a future, which is surfaced as-is; settle it with
62
+ * {@link restartBackendAndWait} or `client.futures.wait`.
63
+ */
27
64
  async startOrRestartBackend(webappId, projectKey) {
28
65
  const id = encodeURIComponent(webappId);
29
- await this.client.putVoid(`/public/api/projects/${this.enc(projectKey)}/webapps/${id}/backend/actions/restart`, {});
66
+ return this.client.put(`/public/api/projects/${this.enc(projectKey)}/webapps/${id}/backend/actions/restart`, {});
67
+ }
68
+ /** Restart a webapp backend and wait for the returned future to settle. */
69
+ async restartBackendAndWait(webappId, projectKey, options = {}) {
70
+ const future = await this.startOrRestartBackend(webappId, projectKey);
71
+ const jobId = future.jobId;
72
+ if (typeof jobId !== "string" || jobId.length === 0) {
73
+ throw new Error("Webapp backend restart did not return a future jobId.");
74
+ }
75
+ return this.client.futures.wait(jobId, {
76
+ pollIntervalMs: options.pollIntervalMs,
77
+ timeoutMs: options.timeoutMs,
78
+ });
30
79
  }
31
80
  /** Get a webapp backend's state. */
32
81
  async getBackendState(webappId, projectKey) {
@@ -3,5 +3,5 @@
3
3
  * The types package (packages/types/) owns the TypeBox schema definitions.
4
4
  * SDK consumers get everything through this re-export.
5
5
  */
6
- export { BuildModeSchema, CodeEnvActionResultSchema, CodeEnvCreateOptionsSchema, CodeEnvDetailsSchema, CodeEnvPackageListSchema, CodeEnvSetPackagesOptionsSchema, CodeEnvSummaryArraySchema, CodeEnvSummarySchema, CodeEnvUpdatePackagesOptionsSchema, CodeEnvUsageArraySchema, CodeEnvWaitOptionsSchema, ConnectionSummarySchema, DashboardDetailsSchema, DashboardSummaryArraySchema, DashboardSummarySchema, DataQualityComputeResultSchema, DataQualityProjectStatusSchema, DataQualityRuleArraySchema, DataQualityRuleResultArraySchema, DataQualityRuleResultSchema, DataQualityRuleSchema, DataQualityRulesSchema, DataQualityStatusByPartitionSchema, DataQualityStatusSchema, DataQualityTimelineEntrySchema, DataQualityTimelineSchema, DatasetCreateOptionsSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, DatasetSummarySchema, FlowMapOptionsSchema, FlowZoneArraySchema, FlowZoneCreateOptionsSchema, FlowZoneItemSchema, FlowZoneObjectTypeSchema, FlowZonePositionSchema, FlowZoneSchema, FlowZoneUpdateOptionsSchema, FolderCreateOptionsSchema, FolderDetailsSchema, FolderItemArraySchema, FolderItemSchema, FolderSummaryArraySchema, FolderSummarySchema, FutureStateSchema, FutureWaitResultSchema, InsightDetailsSchema, InsightSummaryArraySchema, InsightSummarySchema, JobSummaryArraySchema, JobSummarySchema, JobWaitResultSchema, JupyterCellSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, JupyterNotebookSummarySchema, NotebookSessionArraySchema, NotebookSessionSchema, parseSchema, ProjectDetailsSchema, ProjectGitActionResultSchema, ProjectGitDiffResultSchema, ProjectGitFutureResponseSchema, ProjectGitFutureStateSchema, ProjectGitLibrariesSchema, ProjectGitLibrarySchema, ProjectGitLogResultSchema, ProjectGitRemoteSchema, ProjectGitStatusSchema, ProjectGitTagSchema, ProjectGitTagsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, ProjectSummarySchema, ProjectVariablesSchema, RecipeCreateOptionsSchema, RecipeCreateResultSchema, RecipeDetailsSchema, RecipeSummaryArraySchema, RecipeSummarySchema, safeParseSchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioStepRunSchema, ScenarioSummaryArraySchema, ScenarioSummarySchema, ScenarioWaitResultSchema, SqlNotebookCellSchema, SqlNotebookContentSchema, SqlNotebookSummaryArraySchema, SqlNotebookSummarySchema, SqlQueryResponseSchema, SqlQueryResultSchema, SqlQuerySchemaSchema, WikiArticleDataArraySchema, WikiArticleDataSchema, WikiArticleMetadataSchema, WikiSettingsSchema, WikiTaxonomyNodeSchema, } from "../packages/types/src/index.js";
7
- export type { BuildMode, CodeEnvActionResult, CodeEnvCreateOptions, CodeEnvDetails, CodeEnvPackageList, CodeEnvSetPackagesOptions, CodeEnvSummary, CodeEnvUpdatePackagesOptions, CodeEnvUsage, CodeEnvWaitOptions, ConnectionSummary, DashboardDetails, DashboardSummary, DataQualityComputeResult, DataQualityProjectStatus, DataQualityRule, DataQualityRuleResult, DataQualityRules, DataQualityStatus, DataQualityStatusByPartition, DataQualityTimeline, DataQualityTimelineEntry, DatasetCreateOptions, DatasetDetails, DatasetSchema, DatasetSummary, FlowMapOptions, FlowZone, FlowZoneCreateOptions, FlowZoneItem, FlowZoneObjectType, FlowZonePosition, FlowZoneUpdateOptions, FolderCreateOptions, FolderDetails, FolderItem, FolderSummary, FutureState, FutureWaitResult, InsightDetails, InsightSummary, JobSummary, JobWaitResult, JupyterCell, JupyterNotebookContent, JupyterNotebookSummary, NotebookSession, ProjectDetails, ProjectGitActionResult, ProjectGitDiffResult, ProjectGitFutureResponse, ProjectGitFutureState, ProjectGitLibraries, ProjectGitLibrary, ProjectGitLogResult, ProjectGitRemote, ProjectGitStatus, ProjectGitTag, ProjectGitTags, ProjectMetadata, ProjectSummary, ProjectVariables, RecipeCreateOptions, RecipeCreateResult, RecipeDetails, RecipeSummary, SafeParseResult, ScenarioDetails, ScenarioStatus, ScenarioStepRun, ScenarioSummary, ScenarioWaitResult, SqlNotebookCell, SqlNotebookContent, SqlNotebookSummary, SqlQueryResponse, SqlQueryResult, SqlQuerySchema, WikiArticleData, WikiArticleMetadata, WikiSettings, WikiTaxonomyNode, } from "../packages/types/src/index.js";
6
+ export { BuildModeSchema, CodeEnvActionResultSchema, CodeEnvCreateOptionsSchema, CodeEnvDetailsSchema, CodeEnvGetLogOptionsSchema, CodeEnvLogResultSchema, CodeEnvLogSummaryArraySchema, CodeEnvLogSummarySchema, CodeEnvPackageListSchema, CodeEnvSetPackagesOptionsSchema, CodeEnvSummaryArraySchema, CodeEnvSummarySchema, CodeEnvUpdateImagesOptionsSchema, CodeEnvUpdatePackagesOptionsSchema, CodeEnvUsageArraySchema, CodeEnvVersionForProjectSchema, CodeEnvWaitOptionsSchema, ConnectionSummarySchema, DashboardDetailsSchema, DashboardSummaryArraySchema, DashboardSummarySchema, DataQualityComputeResultSchema, DataQualityProjectStatusSchema, DataQualityRuleArraySchema, DataQualityRuleResultArraySchema, DataQualityRuleResultSchema, DataQualityRuleSchema, DataQualityRulesSchema, DataQualityStatusByPartitionSchema, DataQualityStatusSchema, DataQualityTimelineEntrySchema, DataQualityTimelineSchema, DatasetCreateOptionsSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, DatasetSummarySchema, FlowMapOptionsSchema, FlowZoneArraySchema, FlowZoneCreateOptionsSchema, FlowZoneItemSchema, FlowZoneObjectTypeSchema, FlowZonePositionSchema, FlowZoneSchema, FlowZoneUpdateOptionsSchema, FolderCreateOptionsSchema, FolderDetailsSchema, FolderItemArraySchema, FolderItemSchema, FolderSummaryArraySchema, FolderSummarySchema, FutureStateSchema, FutureWaitResultSchema, InsightDetailsSchema, InsightSummaryArraySchema, InsightSummarySchema, JobSummaryArraySchema, JobSummarySchema, JobWaitResultSchema, JupyterCellSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, JupyterNotebookSummarySchema, NotebookSessionArraySchema, NotebookSessionSchema, parseSchema, ProjectDetailsSchema, ProjectGitActionResultSchema, ProjectGitDiffResultSchema, ProjectGitFutureResponseSchema, ProjectGitFutureStateSchema, ProjectGitLibrariesSchema, ProjectGitLibrarySchema, ProjectGitLogResultSchema, ProjectGitRemoteSchema, ProjectGitStatusSchema, ProjectGitTagSchema, ProjectGitTagsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, ProjectSummarySchema, ProjectVariablesSchema, RecipeCreateOptionsSchema, RecipeCreateResultSchema, RecipeDetailsSchema, RecipeSummaryArraySchema, RecipeSummarySchema, safeParseSchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioStepRunSchema, ScenarioSummaryArraySchema, ScenarioSummarySchema, ScenarioWaitResultSchema, SqlNotebookCellSchema, SqlNotebookContentSchema, SqlNotebookHistorySchema, SqlNotebookSummaryArraySchema, SqlNotebookSummarySchema, SqlQueryResponseSchema, SqlQueryResultSchema, SqlQuerySchemaSchema, WikiArticleDataArraySchema, WikiArticleDataSchema, WikiArticleMetadataSchema, WikiSettingsSchema, WikiTaxonomyNodeSchema, } from "../packages/types/src/index.js";
7
+ export type { BuildMode, CodeEnvActionResult, CodeEnvCreateOptions, CodeEnvDetails, CodeEnvGetLogOptions, CodeEnvLogResult, CodeEnvLogSummary, CodeEnvPackageList, CodeEnvSetPackagesOptions, CodeEnvSummary, CodeEnvUpdateImagesOptions, CodeEnvUpdatePackagesOptions, CodeEnvUsage, CodeEnvVersionForProject, CodeEnvWaitOptions, ConnectionSummary, DashboardDetails, DashboardSummary, DataQualityComputeResult, DataQualityProjectStatus, DataQualityRule, DataQualityRuleResult, DataQualityRules, DataQualityStatus, DataQualityStatusByPartition, DataQualityTimeline, DataQualityTimelineEntry, DatasetCreateOptions, DatasetDetails, DatasetSchema, DatasetSummary, FlowMapOptions, FlowZone, FlowZoneCreateOptions, FlowZoneItem, FlowZoneObjectType, FlowZonePosition, FlowZoneUpdateOptions, FolderCreateOptions, FolderDetails, FolderItem, FolderSummary, FutureState, FutureWaitResult, InsightDetails, InsightSummary, JobSummary, JobWaitResult, JupyterCell, JupyterNotebookContent, JupyterNotebookSummary, NotebookSession, ProjectDetails, ProjectGitActionResult, ProjectGitDiffResult, ProjectGitFutureResponse, ProjectGitFutureState, ProjectGitLibraries, ProjectGitLibrary, ProjectGitLogResult, ProjectGitRemote, ProjectGitStatus, ProjectGitTag, ProjectGitTags, ProjectMetadata, ProjectSummary, ProjectVariables, RecipeCreateOptions, RecipeCreateResult, RecipeDetails, RecipeSummary, SafeParseResult, ScenarioDetails, ScenarioStatus, ScenarioStepRun, ScenarioSummary, ScenarioWaitResult, SqlNotebookCell, SqlNotebookContent, SqlNotebookHistory, SqlNotebookSummary, SqlQueryResponse, SqlQueryResult, SqlQuerySchema, WikiArticleData, WikiArticleMetadata, WikiSettings, WikiTaxonomyNode, } from "../packages/types/src/index.js";
@@ -3,4 +3,4 @@
3
3
  * The types package (packages/types/) owns the TypeBox schema definitions.
4
4
  * SDK consumers get everything through this re-export.
5
5
  */
6
- export { BuildModeSchema, CodeEnvActionResultSchema, CodeEnvCreateOptionsSchema, CodeEnvDetailsSchema, CodeEnvPackageListSchema, CodeEnvSetPackagesOptionsSchema, CodeEnvSummaryArraySchema, CodeEnvSummarySchema, CodeEnvUpdatePackagesOptionsSchema, CodeEnvUsageArraySchema, CodeEnvWaitOptionsSchema, ConnectionSummarySchema, DashboardDetailsSchema, DashboardSummaryArraySchema, DashboardSummarySchema, DataQualityComputeResultSchema, DataQualityProjectStatusSchema, DataQualityRuleArraySchema, DataQualityRuleResultArraySchema, DataQualityRuleResultSchema, DataQualityRuleSchema, DataQualityRulesSchema, DataQualityStatusByPartitionSchema, DataQualityStatusSchema, DataQualityTimelineEntrySchema, DataQualityTimelineSchema, DatasetCreateOptionsSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, DatasetSummarySchema, FlowMapOptionsSchema, FlowZoneArraySchema, FlowZoneCreateOptionsSchema, FlowZoneItemSchema, FlowZoneObjectTypeSchema, FlowZonePositionSchema, FlowZoneSchema, FlowZoneUpdateOptionsSchema, FolderCreateOptionsSchema, FolderDetailsSchema, FolderItemArraySchema, FolderItemSchema, FolderSummaryArraySchema, FolderSummarySchema, FutureStateSchema, FutureWaitResultSchema, InsightDetailsSchema, InsightSummaryArraySchema, InsightSummarySchema, JobSummaryArraySchema, JobSummarySchema, JobWaitResultSchema, JupyterCellSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, JupyterNotebookSummarySchema, NotebookSessionArraySchema, NotebookSessionSchema, parseSchema, ProjectDetailsSchema, ProjectGitActionResultSchema, ProjectGitDiffResultSchema, ProjectGitFutureResponseSchema, ProjectGitFutureStateSchema, ProjectGitLibrariesSchema, ProjectGitLibrarySchema, ProjectGitLogResultSchema, ProjectGitRemoteSchema, ProjectGitStatusSchema, ProjectGitTagSchema, ProjectGitTagsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, ProjectSummarySchema, ProjectVariablesSchema, RecipeCreateOptionsSchema, RecipeCreateResultSchema, RecipeDetailsSchema, RecipeSummaryArraySchema, RecipeSummarySchema, safeParseSchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioStepRunSchema, ScenarioSummaryArraySchema, ScenarioSummarySchema, ScenarioWaitResultSchema, SqlNotebookCellSchema, SqlNotebookContentSchema, SqlNotebookSummaryArraySchema, SqlNotebookSummarySchema, SqlQueryResponseSchema, SqlQueryResultSchema, SqlQuerySchemaSchema, WikiArticleDataArraySchema, WikiArticleDataSchema, WikiArticleMetadataSchema, WikiSettingsSchema, WikiTaxonomyNodeSchema, } from "../packages/types/src/index.js";
6
+ export { BuildModeSchema, CodeEnvActionResultSchema, CodeEnvCreateOptionsSchema, CodeEnvDetailsSchema, CodeEnvGetLogOptionsSchema, CodeEnvLogResultSchema, CodeEnvLogSummaryArraySchema, CodeEnvLogSummarySchema, CodeEnvPackageListSchema, CodeEnvSetPackagesOptionsSchema, CodeEnvSummaryArraySchema, CodeEnvSummarySchema, CodeEnvUpdateImagesOptionsSchema, CodeEnvUpdatePackagesOptionsSchema, CodeEnvUsageArraySchema, CodeEnvVersionForProjectSchema, CodeEnvWaitOptionsSchema, ConnectionSummarySchema, DashboardDetailsSchema, DashboardSummaryArraySchema, DashboardSummarySchema, DataQualityComputeResultSchema, DataQualityProjectStatusSchema, DataQualityRuleArraySchema, DataQualityRuleResultArraySchema, DataQualityRuleResultSchema, DataQualityRuleSchema, DataQualityRulesSchema, DataQualityStatusByPartitionSchema, DataQualityStatusSchema, DataQualityTimelineEntrySchema, DataQualityTimelineSchema, DatasetCreateOptionsSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, DatasetSummarySchema, FlowMapOptionsSchema, FlowZoneArraySchema, FlowZoneCreateOptionsSchema, FlowZoneItemSchema, FlowZoneObjectTypeSchema, FlowZonePositionSchema, FlowZoneSchema, FlowZoneUpdateOptionsSchema, FolderCreateOptionsSchema, FolderDetailsSchema, FolderItemArraySchema, FolderItemSchema, FolderSummaryArraySchema, FolderSummarySchema, FutureStateSchema, FutureWaitResultSchema, InsightDetailsSchema, InsightSummaryArraySchema, InsightSummarySchema, JobSummaryArraySchema, JobSummarySchema, JobWaitResultSchema, JupyterCellSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, JupyterNotebookSummarySchema, NotebookSessionArraySchema, NotebookSessionSchema, parseSchema, ProjectDetailsSchema, ProjectGitActionResultSchema, ProjectGitDiffResultSchema, ProjectGitFutureResponseSchema, ProjectGitFutureStateSchema, ProjectGitLibrariesSchema, ProjectGitLibrarySchema, ProjectGitLogResultSchema, ProjectGitRemoteSchema, ProjectGitStatusSchema, ProjectGitTagSchema, ProjectGitTagsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, ProjectSummarySchema, ProjectVariablesSchema, RecipeCreateOptionsSchema, RecipeCreateResultSchema, RecipeDetailsSchema, RecipeSummaryArraySchema, RecipeSummarySchema, safeParseSchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioStepRunSchema, ScenarioSummaryArraySchema, ScenarioSummarySchema, ScenarioWaitResultSchema, SqlNotebookCellSchema, SqlNotebookContentSchema, SqlNotebookHistorySchema, SqlNotebookSummaryArraySchema, SqlNotebookSummarySchema, SqlQueryResponseSchema, SqlQueryResultSchema, SqlQuerySchemaSchema, WikiArticleDataArraySchema, WikiArticleDataSchema, WikiArticleMetadataSchema, WikiSettingsSchema, WikiTaxonomyNodeSchema, } from "../packages/types/src/index.js";
@@ -28,10 +28,19 @@ export declare function detectAgents(): DetectedAgent[];
28
28
  * Agent config directories are install targets, not workspace roots.
29
29
  */
30
30
  export declare function findWorkspaceRoot(startDir: string): string;
31
+ export type SkillStatus = "missing" | "stale" | "current";
31
32
  export interface InstallResult {
32
33
  agent: string;
33
34
  path: string;
34
35
  via: DetectedAgent["via"];
36
+ /** Deterministic state of the destination file relative to the canonical skill content. */
37
+ status: SkillStatus;
38
+ /** Whether an install run would (or did) write the destination file. */
39
+ changed: boolean;
40
+ /** SHA-256 hex of the canonical skill bytes this installation writes. */
41
+ expectedSha256: string;
42
+ /** SHA-256 hex of the destination file when it exists; absent when status is "missing". */
43
+ actualSha256?: string;
35
44
  }
36
45
  export declare function planSkillInstalls(agents: DetectedAgent[], opts: {
37
46
  global: boolean;
package/dist/src/skill.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createHash, } from "node:crypto";
1
2
  import * as fs from "node:fs";
2
3
  import * as os from "node:os";
3
4
  import * as path from "node:path";
@@ -97,6 +98,18 @@ export function findWorkspaceRoot(startDir) {
97
98
  }
98
99
  return startDir;
99
100
  }
101
+ function sha256Hex(value) {
102
+ return createHash("sha256").update(value).digest("hex");
103
+ }
104
+ function skillState(target, expectedSha256) {
105
+ if (!fs.existsSync(target))
106
+ return { status: "missing", };
107
+ const actualSha256 = sha256Hex(fs.readFileSync(target));
108
+ return {
109
+ status: actualSha256 === expectedSha256 ? "current" : "stale",
110
+ actualSha256,
111
+ };
112
+ }
100
113
  export function planSkillInstalls(agents, opts) {
101
114
  const home = os.homedir();
102
115
  const results = [];
@@ -108,7 +121,17 @@ export function planSkillInstalls(agents, opts) {
108
121
  : undefined;
109
122
  if (!dir)
110
123
  continue;
111
- results.push({ agent: id, path: path.join(dir, def.filename), via, });
124
+ const target = path.join(dir, def.filename);
125
+ const expectedSha256 = sha256Hex(def.content());
126
+ const state = skillState(target, expectedSha256);
127
+ results.push({
128
+ agent: id,
129
+ path: target,
130
+ via,
131
+ ...state,
132
+ changed: state.status !== "current",
133
+ expectedSha256,
134
+ });
112
135
  }
113
136
  return results;
114
137
  }
@@ -116,10 +139,20 @@ export function installSkill(agents, opts) {
116
139
  const results = planSkillInstalls(agents, opts);
117
140
  for (const result of results) {
118
141
  const def = AGENTS[result.agent];
119
- if (!def)
142
+ if (!def || !result.changed)
120
143
  continue;
121
- fs.mkdirSync(path.dirname(result.path), { recursive: true, });
122
- fs.writeFileSync(result.path, def.content(), "utf-8");
144
+ const content = def.content();
145
+ const dir = path.dirname(result.path);
146
+ fs.mkdirSync(dir, { recursive: true, });
147
+ const tmpPath = path.join(dir, `.${path.basename(result.path)}.tmp-${process.pid}-${Date.now().toString(36)}`);
148
+ fs.writeFileSync(tmpPath, content, "utf-8");
149
+ try {
150
+ fs.renameSync(tmpPath, result.path);
151
+ }
152
+ catch (error) {
153
+ fs.rmSync(tmpPath, { force: true, });
154
+ throw error;
155
+ }
123
156
  }
124
157
  return results;
125
158
  }
@@ -0,0 +1,53 @@
1
+ import type { FlowZone } from "../schemas.js";
2
+ import type { NormalizedFlowMap, NormalizedFlowNode } from "./flow-map.js";
3
+ export type FlowRenderFormat = "ascii" | "mermaid";
4
+ export type FlowDiagnosticSeverity = "info" | "warning";
5
+ export interface AnalyzedFlowNode extends NormalizedFlowNode {
6
+ componentId: string;
7
+ layer: number;
8
+ zoneId: string;
9
+ zoneName: string;
10
+ }
11
+ export interface FlowMapZone {
12
+ id: string;
13
+ name: string;
14
+ color?: string;
15
+ position?: {
16
+ x: number;
17
+ y: number;
18
+ };
19
+ itemCount: number;
20
+ nodeIds: string[];
21
+ }
22
+ export interface FlowMapComponent {
23
+ id: string;
24
+ nodeIds: string[];
25
+ rootIds: string[];
26
+ leafIds: string[];
27
+ cyclic: boolean;
28
+ }
29
+ export interface FlowMapDiagnostic {
30
+ code: string;
31
+ severity: FlowDiagnosticSeverity;
32
+ message: string;
33
+ nodeIds?: string[];
34
+ componentIds?: string[];
35
+ edgeCount?: number;
36
+ }
37
+ export interface AnalyzedFlowMap extends Omit<NormalizedFlowMap, "nodes"> {
38
+ nodes: AnalyzedFlowNode[];
39
+ zones: FlowMapZone[];
40
+ components: FlowMapComponent[];
41
+ diagnostics: FlowMapDiagnostic[];
42
+ topologyFingerprint: string;
43
+ }
44
+ export interface FlowMapRendering {
45
+ format: FlowRenderFormat;
46
+ content: string;
47
+ }
48
+ export declare function flowTopologyFingerprint(map: NormalizedFlowMap): string;
49
+ export declare function analyzeFlowMap(map: NormalizedFlowMap, flowZones: FlowZone[], options?: {
50
+ topologyFingerprint?: string;
51
+ truncated?: boolean;
52
+ }): AnalyzedFlowMap;
53
+ export declare function renderFlowMap(map: AnalyzedFlowMap, format: FlowRenderFormat): FlowMapRendering;