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.
- package/README.md +124 -12
- package/dist/build-metadata.json +1 -1
- package/dist/src/auth.js +1 -1
- package/dist/src/cli/coerce.js +11 -2
- package/dist/src/cli/commands/analysis.js +4 -3
- package/dist/src/cli/commands/api-service.js +5 -3
- package/dist/src/cli/commands/app.js +4 -3
- package/dist/src/cli/commands/bundle.js +2 -1
- package/dist/src/cli/commands/code-env.js +10 -9
- package/dist/src/cli/commands/dashboard.js +7 -6
- package/dist/src/cli/commands/data-quality.js +9 -8
- package/dist/src/cli/commands/dataset.js +9 -8
- package/dist/src/cli/commands/doctor.js +4 -2
- package/dist/src/cli/commands/flow-zone.js +9 -8
- package/dist/src/cli/commands/folder.js +9 -8
- package/dist/src/cli/commands/future.js +2 -1
- package/dist/src/cli/commands/insight.js +8 -7
- package/dist/src/cli/commands/job.js +4 -3
- package/dist/src/cli/commands/meaning.js +5 -4
- package/dist/src/cli/commands/ml-task.js +3 -2
- package/dist/src/cli/commands/model-evaluation-store.js +4 -3
- package/dist/src/cli/commands/notebook.js +11 -10
- package/dist/src/cli/commands/project-library.js +7 -6
- package/dist/src/cli/commands/project.js +3 -2
- package/dist/src/cli/commands/recipe.js +14 -13
- package/dist/src/cli/commands/saved-model.js +4 -3
- package/dist/src/cli/commands/scenario.js +9 -8
- package/dist/src/cli/commands/sql.js +3 -2
- package/dist/src/cli/commands/statistics.js +7 -6
- package/dist/src/cli/commands/variable.js +2 -1
- package/dist/src/cli/commands/webapp.js +2 -1
- package/dist/src/cli/commands/wiki.js +7 -6
- package/dist/src/cli/contract.d.ts +1 -1
- package/dist/src/cli/contract.js +100 -82
- package/dist/src/cli/flags.d.ts +4 -0
- package/dist/src/cli/flags.js +20 -1
- package/dist/src/cli.js +92 -68
- package/dist/src/client.d.ts +3 -1
- package/dist/src/client.js +78 -21
- package/dist/src/errors.js +2 -0
- package/dist/src/resources/folders.js +2 -1
- package/dist/src/resources/projects.d.ts +0 -2
- package/dist/src/resources/projects.js +3 -17
- package/dist/src/resources/sql.js +1 -1
- package/dist/src/skill.d.ts +13 -4
- package/dist/src/skill.js +75 -24
- package/dist/src/utils/response-file.d.ts +2 -0
- package/dist/src/utils/response-file.js +34 -0
- package/package.json +6 -4
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +44 -224
- package/skills/dataiku-dss/references/app-releases.md +27 -0
- package/skills/dataiku-dss/references/authentication.md +39 -0
- package/skills/dataiku-dss/references/coding.md +8 -0
- package/skills/dataiku-dss/references/discovery.md +30 -0
- package/skills/dataiku-dss/references/flow-maps.md +12 -0
- package/skills/dataiku-dss/references/mutations.md +33 -0
- package/skills/dataiku-dss/references/troubleshooting.md +29 -0
package/dist/src/client.js
CHANGED
|
@@ -76,15 +76,6 @@ function isTransientError(status, body) {
|
|
|
76
76
|
function shouldRetryMethod(method) {
|
|
77
77
|
return method.toUpperCase() === "GET";
|
|
78
78
|
}
|
|
79
|
-
function concatBytes(chunks, total) {
|
|
80
|
-
const out = new Uint8Array(total);
|
|
81
|
-
let offset = 0;
|
|
82
|
-
for (const chunk of chunks) {
|
|
83
|
-
out.set(chunk, offset);
|
|
84
|
-
offset += chunk.byteLength;
|
|
85
|
-
}
|
|
86
|
-
return out;
|
|
87
|
-
}
|
|
88
79
|
/**
|
|
89
80
|
* Drop a trailing incomplete UTF-8 multibyte sequence so the bytes end on a
|
|
90
81
|
* character boundary. Guarantees the decoded string re-encodes to no more bytes
|
|
@@ -133,7 +124,7 @@ function buildBodyReadTimeoutError(timeoutMs) {
|
|
|
133
124
|
* Read the next chunk of a response body under a hard deadline.
|
|
134
125
|
* When `remainingMs` elapses, the stream is cancelled and the caller's
|
|
135
126
|
* promise rejects with a DataikuError, so a stalled body cannot hang the
|
|
136
|
-
*
|
|
127
|
+
* consumers after headers already arrived.
|
|
137
128
|
*/
|
|
138
129
|
async function readChunkWithDeadline(reader, remainingMs, timeoutMs) {
|
|
139
130
|
const { promise, resolve, reject, } = Promise.withResolvers();
|
|
@@ -506,11 +497,12 @@ export class DataikuClient {
|
|
|
506
497
|
return text;
|
|
507
498
|
}
|
|
508
499
|
async postStream(path, body) {
|
|
509
|
-
|
|
500
|
+
const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
510
501
|
method: "POST",
|
|
511
502
|
headers: { ...this.getAnyHeaders(), "Content-Type": "application/json", },
|
|
512
503
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
513
504
|
});
|
|
505
|
+
return this.withBodyDeadline(res);
|
|
514
506
|
}
|
|
515
507
|
async put(path, body) {
|
|
516
508
|
const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
@@ -551,10 +543,51 @@ export class DataikuClient {
|
|
|
551
543
|
return this.parseJsonResponse(response);
|
|
552
544
|
}
|
|
553
545
|
async stream(path) {
|
|
554
|
-
|
|
546
|
+
const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
555
547
|
method: "GET",
|
|
556
548
|
headers: this.getAnyHeaders(),
|
|
557
549
|
});
|
|
550
|
+
return this.withBodyDeadline(res);
|
|
551
|
+
}
|
|
552
|
+
/** Bound each pending read, not total transfer time; preserve large downloads and backpressure. */
|
|
553
|
+
withBodyDeadline(res) {
|
|
554
|
+
if (!res.body)
|
|
555
|
+
return res;
|
|
556
|
+
const reader = res.body.getReader();
|
|
557
|
+
const timeoutMs = this.requestTimeoutMs;
|
|
558
|
+
const body = new ReadableStream({
|
|
559
|
+
async pull(controller) {
|
|
560
|
+
try {
|
|
561
|
+
const { done, value, } = await readChunkWithDeadline(reader, timeoutMs, timeoutMs);
|
|
562
|
+
if (done) {
|
|
563
|
+
controller.close();
|
|
564
|
+
reader.releaseLock();
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
controller.enqueue(value);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
catch (error) {
|
|
571
|
+
controller.error(error);
|
|
572
|
+
void reader.cancel(error).catch(() => { });
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
cancel(reason) {
|
|
576
|
+
void reader.cancel(reason).catch(() => { });
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
const response = new Response(body, {
|
|
580
|
+
status: res.status,
|
|
581
|
+
statusText: res.statusText,
|
|
582
|
+
headers: res.headers,
|
|
583
|
+
});
|
|
584
|
+
// ResponseInit has no fields for these fetch response properties.
|
|
585
|
+
Object.defineProperties(response, {
|
|
586
|
+
url: { value: res.url, },
|
|
587
|
+
redirected: { value: res.redirected, },
|
|
588
|
+
type: { value: res.type, },
|
|
589
|
+
});
|
|
590
|
+
return response;
|
|
558
591
|
}
|
|
559
592
|
/* ---- private: headers ---- */
|
|
560
593
|
getHeaders() {
|
|
@@ -658,7 +691,34 @@ export class DataikuClient {
|
|
|
658
691
|
if (!res.body)
|
|
659
692
|
return { text: "", truncated: false, };
|
|
660
693
|
const reader = res.body.getReader();
|
|
661
|
-
const
|
|
694
|
+
const decoder = new TextDecoder();
|
|
695
|
+
const parts = [];
|
|
696
|
+
// Keep the final UTF-8 sequence undecoded until truncation is known.
|
|
697
|
+
// Four bytes also cover malformed sequences handled by trimToUtf8Boundary.
|
|
698
|
+
const tail = new Uint8Array(4);
|
|
699
|
+
let tailLength = 0;
|
|
700
|
+
const append = (chunk) => {
|
|
701
|
+
if (chunk.byteLength >= tail.length) {
|
|
702
|
+
if (tailLength > 0) {
|
|
703
|
+
parts.push(decoder.decode(tail.subarray(0, tailLength), { stream: true, }));
|
|
704
|
+
}
|
|
705
|
+
const end = chunk.byteLength - tail.length;
|
|
706
|
+
if (end > 0)
|
|
707
|
+
parts.push(decoder.decode(chunk.subarray(0, end), { stream: true, }));
|
|
708
|
+
tail.set(chunk.subarray(end));
|
|
709
|
+
tailLength = tail.length;
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
const excess = Math.max(0, tailLength + chunk.byteLength - tail.length);
|
|
713
|
+
if (excess > 0) {
|
|
714
|
+
parts.push(decoder.decode(tail.subarray(0, excess), { stream: true, }));
|
|
715
|
+
tail.copyWithin(0, excess, tailLength);
|
|
716
|
+
tailLength -= excess;
|
|
717
|
+
}
|
|
718
|
+
tail.set(chunk, tailLength);
|
|
719
|
+
tailLength += chunk.byteLength;
|
|
720
|
+
}
|
|
721
|
+
};
|
|
662
722
|
let bytesRead = 0;
|
|
663
723
|
let truncated = false;
|
|
664
724
|
const startedAt = Date.now();
|
|
@@ -672,12 +732,12 @@ export class DataikuClient {
|
|
|
672
732
|
break;
|
|
673
733
|
const room = limit - bytesRead;
|
|
674
734
|
if (value.byteLength > room) {
|
|
675
|
-
|
|
735
|
+
append(value.subarray(0, room));
|
|
676
736
|
bytesRead += room;
|
|
677
737
|
truncated = true;
|
|
678
738
|
break;
|
|
679
739
|
}
|
|
680
|
-
|
|
740
|
+
append(value);
|
|
681
741
|
bytesRead += value.byteLength;
|
|
682
742
|
}
|
|
683
743
|
if (!truncated && bytesRead >= limit) {
|
|
@@ -693,12 +753,9 @@ export class DataikuClient {
|
|
|
693
753
|
finally {
|
|
694
754
|
void reader.cancel().catch(() => { });
|
|
695
755
|
}
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
// emit a 3-byte replacement char for a split multibyte sequence).
|
|
700
|
-
const usable = truncated ? trimToUtf8Boundary(collected) : collected;
|
|
701
|
-
return { text: new TextDecoder().decode(usable), truncated, };
|
|
756
|
+
const remaining = tail.subarray(0, tailLength);
|
|
757
|
+
parts.push(decoder.decode(truncated ? trimToUtf8Boundary(remaining) : remaining));
|
|
758
|
+
return { text: parts.join(""), truncated, };
|
|
702
759
|
}
|
|
703
760
|
/**
|
|
704
761
|
* Read a 2xx response body keeping only the last `maxBytes` bytes.
|
package/dist/src/errors.js
CHANGED
|
@@ -240,6 +240,8 @@ export function classifyDataikuError(status, body) {
|
|
|
240
240
|
&& (lowerBody.includes("column_not_found")
|
|
241
241
|
|| lowerBody.includes("table_not_found")
|
|
242
242
|
|| lowerBody.includes("no_such_table")
|
|
243
|
+
|| lowerBody.includes("no such column")
|
|
244
|
+
|| lowerBody.includes("no such table")
|
|
243
245
|
|| lowerBody.includes("column does not exist"));
|
|
244
246
|
if (isSqlEngineValidation) {
|
|
245
247
|
return {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as nodePath from "node:path";
|
|
2
2
|
import { FolderDetailsSchema, FolderItemArraySchema, FolderSummaryArraySchema, } from "../schemas.js";
|
|
3
3
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
4
|
+
import { writeResponseToFile, } from "../utils/response-file.js";
|
|
4
5
|
import { sanitizeFileName, } from "../utils/sanitize.js";
|
|
5
6
|
import { BaseResource, } from "./base.js";
|
|
6
7
|
import { resolveAdminManagedStorageConnection, } from "./connections.js";
|
|
@@ -73,7 +74,7 @@ export class FoldersResource extends BaseResource {
|
|
|
73
74
|
throw new Error("folders.download response did not include a body");
|
|
74
75
|
const dest = opts?.localPath
|
|
75
76
|
?? nodePath.resolve(process.cwd(), inferDownloadFileName(normalizedPath));
|
|
76
|
-
await
|
|
77
|
+
await writeResponseToFile(dest, res);
|
|
77
78
|
return dest;
|
|
78
79
|
}
|
|
79
80
|
upload(folderId, path, localPath, projectKey) {
|
|
@@ -87,8 +87,6 @@ export interface ProjectImportResult extends ProjectImportProcessResult {
|
|
|
87
87
|
projectIncarnationHash?: string;
|
|
88
88
|
}
|
|
89
89
|
export declare class ProjectsResource extends BaseResource {
|
|
90
|
-
private httpInternals;
|
|
91
|
-
private postStream;
|
|
92
90
|
/** Create a new project. */
|
|
93
91
|
createProject(projectKey: string, name: string, ownerLogin: string, settings?: ProjectLifecycleSettings | null): Promise<Record<string, unknown>>;
|
|
94
92
|
/** Delete a project using the lifecycle endpoint. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
2
|
+
import { writeResponseToFile, } from "../utils/response-file.js";
|
|
2
3
|
import { ProjectDetailsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, } from "../schemas.js";
|
|
3
4
|
import { analyzeFlowMap, flowTopologyFingerprint, renderFlowMap, } from "../utils/flow-analysis.js";
|
|
4
5
|
import { normalizeFlowGraph, } from "../utils/flow-map.js";
|
|
@@ -104,21 +105,6 @@ const DEFAULT_MAX_EDGES = 600;
|
|
|
104
105
|
const DEFAULT_METADATA_TIMEOUT_MS = 1_500;
|
|
105
106
|
const PROJECT_IMPORT_AMBIGUOUS_REMEDIATION = "Import processing left DSS state indeterminate. Inspect the archive with `dss project inspect-archive <file>` and the live projects with `dss project list` before retrying the import.";
|
|
106
107
|
export class ProjectsResource extends BaseResource {
|
|
107
|
-
httpInternals() {
|
|
108
|
-
return this.client;
|
|
109
|
-
}
|
|
110
|
-
async postStream(path, body) {
|
|
111
|
-
const http = this.httpInternals();
|
|
112
|
-
return http.fetchWithRetry(`${http.baseUrl}${path}`, {
|
|
113
|
-
method: "POST",
|
|
114
|
-
headers: {
|
|
115
|
-
Authorization: `Bearer ${http.apiKey}`,
|
|
116
|
-
Accept: "*/*",
|
|
117
|
-
"Content-Type": "application/json",
|
|
118
|
-
},
|
|
119
|
-
body: JSON.stringify(body),
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
108
|
/** Create a new project. */
|
|
123
109
|
async createProject(projectKey, name, ownerLogin, settings) {
|
|
124
110
|
return this.client.post("/public/api/projects/", {
|
|
@@ -155,10 +141,10 @@ export class ProjectsResource extends BaseResource {
|
|
|
155
141
|
}
|
|
156
142
|
/** Export a project archive to a local file. */
|
|
157
143
|
async exportArchive(projectKey, filePath, options) {
|
|
158
|
-
const res = await this.postStream(`/public/api/projects/${encodeURIComponent(projectKey)}/export`, options ?? {});
|
|
144
|
+
const res = await this.client.postStream(`/public/api/projects/${encodeURIComponent(projectKey)}/export`, options ?? {});
|
|
159
145
|
if (!res.body)
|
|
160
146
|
throw new Error("projects.exportArchive response did not include a body");
|
|
161
|
-
await
|
|
147
|
+
await writeResponseToFile(filePath, res);
|
|
162
148
|
}
|
|
163
149
|
/** Upload a project archive and return its temporary import handle. */
|
|
164
150
|
async prepareProjectImport(filePath) {
|
|
@@ -101,7 +101,7 @@ function withSqlErrorContext(error) {
|
|
|
101
101
|
catch {
|
|
102
102
|
body = `SQL query failed: ${detail}\n${error.body}`;
|
|
103
103
|
}
|
|
104
|
-
throw new DataikuError(error.status, error.statusText, body, error.retry);
|
|
104
|
+
throw new DataikuError(error.status, error.statusText, body, error.retry, error.requestId, { target: error.trustedTarget, elapsedMs: error.trustedElapsedMs, });
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
throw error;
|
package/dist/src/skill.d.ts
CHANGED
|
@@ -29,18 +29,27 @@ export declare function detectAgents(): DetectedAgent[];
|
|
|
29
29
|
*/
|
|
30
30
|
export declare function findWorkspaceRoot(startDir: string): string;
|
|
31
31
|
export type SkillStatus = "missing" | "stale" | "current";
|
|
32
|
+
export interface InstalledSkillFile {
|
|
33
|
+
relativePath: string;
|
|
34
|
+
path: string;
|
|
35
|
+
status: SkillStatus;
|
|
36
|
+
changed: boolean;
|
|
37
|
+
expectedSha256: string;
|
|
38
|
+
actualSha256?: string;
|
|
39
|
+
}
|
|
32
40
|
export interface InstallResult {
|
|
33
41
|
agent: string;
|
|
34
42
|
path: string;
|
|
35
43
|
via: DetectedAgent["via"];
|
|
36
|
-
/**
|
|
44
|
+
/** Missing entrypoint, incomplete/different bundle, or entirely current bundle. */
|
|
37
45
|
status: SkillStatus;
|
|
38
|
-
/** Whether
|
|
46
|
+
/** Whether any bundled file would (or did) change. Unmanaged files are preserved. */
|
|
39
47
|
changed: boolean;
|
|
40
|
-
/** SHA-256
|
|
48
|
+
/** SHA-256 of the canonical SKILL.md entrypoint, not the whole bundle. */
|
|
41
49
|
expectedSha256: string;
|
|
42
|
-
/**
|
|
50
|
+
/** Hash of the existing entrypoint; absent when that file is missing. */
|
|
43
51
|
actualSha256?: string;
|
|
52
|
+
files: InstalledSkillFile[];
|
|
44
53
|
}
|
|
45
54
|
export declare function planSkillInstalls(agents: DetectedAgent[], opts: {
|
|
46
55
|
global: boolean;
|
package/dist/src/skill.js
CHANGED
|
@@ -2,17 +2,21 @@ import { createHash, } from "node:crypto";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
+
import { fileURLToPath, } from "node:url";
|
|
5
6
|
const SKILL_URLS = [
|
|
6
7
|
new URL("../skills/dataiku-dss/SKILL.md", import.meta.url),
|
|
7
8
|
new URL("../../skills/dataiku-dss/SKILL.md", import.meta.url),
|
|
8
9
|
];
|
|
9
|
-
function
|
|
10
|
+
function skillDirectory() {
|
|
10
11
|
for (const skillUrl of SKILL_URLS) {
|
|
11
12
|
if (fs.existsSync(skillUrl))
|
|
12
|
-
return
|
|
13
|
+
return new URL("./", skillUrl);
|
|
13
14
|
}
|
|
14
15
|
throw new Error(`Bundled Dataiku skill not found. Checked: ${SKILL_URLS.map((url) => url.pathname).join(", ")}`);
|
|
15
16
|
}
|
|
17
|
+
function skillContent() {
|
|
18
|
+
return fs.readFileSync(new URL("SKILL.md", skillDirectory()), "utf-8");
|
|
19
|
+
}
|
|
16
20
|
export const AGENTS = {
|
|
17
21
|
claude: {
|
|
18
22
|
name: "Claude Code",
|
|
@@ -110,7 +114,27 @@ function skillState(target, expectedSha256) {
|
|
|
110
114
|
actualSha256,
|
|
111
115
|
};
|
|
112
116
|
}
|
|
113
|
-
|
|
117
|
+
/** Load the packaged tree, keeping portable relative paths in the report. */
|
|
118
|
+
function skillFiles() {
|
|
119
|
+
const root = fileURLToPath(skillDirectory());
|
|
120
|
+
const files = new Map();
|
|
121
|
+
function visit(relativeDir) {
|
|
122
|
+
const dir = path.join(root, relativeDir);
|
|
123
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true, }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
124
|
+
const relativePath = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
125
|
+
if (entry.isDirectory())
|
|
126
|
+
visit(relativePath);
|
|
127
|
+
else if (entry.isFile()) {
|
|
128
|
+
files.set(relativePath, fs.readFileSync(path.join(root, relativePath)));
|
|
129
|
+
}
|
|
130
|
+
else
|
|
131
|
+
throw new Error(`Unsupported bundled skill entry: ${relativePath}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
visit("");
|
|
135
|
+
return files;
|
|
136
|
+
}
|
|
137
|
+
function skillInstallPlan(agents, opts, bundle) {
|
|
114
138
|
const home = os.homedir();
|
|
115
139
|
const results = [];
|
|
116
140
|
for (const { id, def, via, } of agents) {
|
|
@@ -121,38 +145,65 @@ export function planSkillInstalls(agents, opts) {
|
|
|
121
145
|
: undefined;
|
|
122
146
|
if (!dir)
|
|
123
147
|
continue;
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
148
|
+
const files = [];
|
|
149
|
+
for (const [sourcePath, source,] of bundle) {
|
|
150
|
+
const relativePath = sourcePath === "SKILL.md" ? def.filename : sourcePath;
|
|
151
|
+
const target = path.join(dir, relativePath);
|
|
152
|
+
const expectedSha256 = sha256Hex(sourcePath === "SKILL.md" ? def.content() : source);
|
|
153
|
+
const state = skillState(target, expectedSha256);
|
|
154
|
+
files.push({
|
|
155
|
+
relativePath,
|
|
156
|
+
path: target,
|
|
157
|
+
...state,
|
|
158
|
+
changed: state.status !== "current",
|
|
159
|
+
expectedSha256,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
const entrypoint = files.find((file) => file.relativePath === def.filename);
|
|
163
|
+
const changed = files.some((file) => file.changed);
|
|
127
164
|
results.push({
|
|
128
165
|
agent: id,
|
|
129
|
-
path:
|
|
166
|
+
path: entrypoint.path,
|
|
130
167
|
via,
|
|
131
|
-
|
|
132
|
-
changed
|
|
133
|
-
expectedSha256,
|
|
168
|
+
status: entrypoint.status === "missing" ? "missing" : changed ? "stale" : "current",
|
|
169
|
+
changed,
|
|
170
|
+
expectedSha256: entrypoint.expectedSha256,
|
|
171
|
+
...(entrypoint.actualSha256 !== undefined ? { actualSha256: entrypoint.actualSha256, } : {}),
|
|
172
|
+
files,
|
|
134
173
|
});
|
|
135
174
|
}
|
|
136
175
|
return results;
|
|
137
176
|
}
|
|
177
|
+
export function planSkillInstalls(agents, opts) {
|
|
178
|
+
return skillInstallPlan(agents, opts, skillFiles());
|
|
179
|
+
}
|
|
180
|
+
function writeSkillFile(file, content) {
|
|
181
|
+
if (!file.changed)
|
|
182
|
+
return;
|
|
183
|
+
const dir = path.dirname(file.path);
|
|
184
|
+
fs.mkdirSync(dir, { recursive: true, });
|
|
185
|
+
const tmpPath = path.join(dir, `.${path.basename(file.path)}.tmp-${process.pid}-${Date.now().toString(36)}`);
|
|
186
|
+
try {
|
|
187
|
+
fs.writeFileSync(tmpPath, content);
|
|
188
|
+
fs.renameSync(tmpPath, file.path);
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
fs.rmSync(tmpPath, { force: true, });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
138
194
|
export function installSkill(agents, opts) {
|
|
139
|
-
const
|
|
195
|
+
const bundle = skillFiles();
|
|
196
|
+
const results = skillInstallPlan(agents, opts, bundle);
|
|
140
197
|
for (const result of results) {
|
|
141
|
-
|
|
142
|
-
if (!def || !result.changed)
|
|
198
|
+
if (!result.changed)
|
|
143
199
|
continue;
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
fs.renameSync(tmpPath, result.path);
|
|
151
|
-
}
|
|
152
|
-
catch (error) {
|
|
153
|
-
fs.rmSync(tmpPath, { force: true, });
|
|
154
|
-
throw error;
|
|
200
|
+
const def = agents.find((agent) => agent.id === result.agent).def;
|
|
201
|
+
// Publish referenced files first; never expose a new entrypoint with missing references.
|
|
202
|
+
for (const file of result.files) {
|
|
203
|
+
if (file.relativePath !== def.filename)
|
|
204
|
+
writeSkillFile(file, bundle.get(file.relativePath));
|
|
155
205
|
}
|
|
206
|
+
writeSkillFile(result.files.find((file) => file.relativePath === def.filename), def.content());
|
|
156
207
|
}
|
|
157
208
|
return results;
|
|
158
209
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { randomUUID, } from "node:crypto";
|
|
2
|
+
import { createWriteStream, } from "node:fs";
|
|
3
|
+
import { chmod, rename, rm, stat, } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join, } from "node:path";
|
|
5
|
+
import { Readable, } from "node:stream";
|
|
6
|
+
import { pipeline, } from "node:stream/promises";
|
|
7
|
+
/** Stream with backpressure; publish only a complete download, preserving existing output on failure. */
|
|
8
|
+
export async function writeResponseToFile(path, response, permissions = "private") {
|
|
9
|
+
let mode;
|
|
10
|
+
if (permissions === "preserve") {
|
|
11
|
+
try {
|
|
12
|
+
mode = (await stat(path)).mode & 0o777;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error.code !== "ENOENT")
|
|
16
|
+
throw error;
|
|
17
|
+
mode = 0o666 & ~process.umask();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const input = Readable.fromWeb(response.body);
|
|
21
|
+
const temporaryPath = join(dirname(path), `.${basename(path)}.tmp-${randomUUID()}`);
|
|
22
|
+
const output = createWriteStream(temporaryPath, { flags: "wx", mode: 0o600, });
|
|
23
|
+
try {
|
|
24
|
+
await pipeline(input, output);
|
|
25
|
+
// Keep partial contents private; restore publication permissions only after completion.
|
|
26
|
+
if (mode !== undefined)
|
|
27
|
+
await chmod(temporaryPath, mode);
|
|
28
|
+
await rename(temporaryPath, path);
|
|
29
|
+
return output.bytesWritten;
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
await rm(temporaryPath, { force: true, });
|
|
33
|
+
}
|
|
34
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dataiku-sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Dataiku DSS SDK and CLI for programmatic access to DSS REST APIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -25,13 +25,15 @@
|
|
|
25
25
|
"build": "tsc && bun scripts/generate-build-metadata.mjs && tsc -p packages/types/tsconfig.json",
|
|
26
26
|
"prepack": "bun scripts/sync-plugin-version.mjs --check && tsc && bun scripts/generate-build-metadata.mjs && tsc -p packages/types/tsconfig.json",
|
|
27
27
|
"prepublishOnly": "bun scripts/sync-plugin-version.mjs --check && tsc && bun scripts/generate-build-metadata.mjs && tsc -p packages/types/tsconfig.json",
|
|
28
|
-
"check": "tsc --noEmit && bun scripts/sync-plugin-version.mjs --check",
|
|
28
|
+
"check": "tsc --noEmit && bun run check:live && bun scripts/sync-plugin-version.mjs --check",
|
|
29
29
|
"version": "bun scripts/sync-plugin-version.mjs && git add plugin.json",
|
|
30
|
-
"lint": "oxlint src/",
|
|
31
|
-
"lint:fix": "oxlint --fix src/",
|
|
30
|
+
"lint": "oxlint src/ scripts/live-suite.ts tests/live-*.ts",
|
|
31
|
+
"lint:fix": "oxlint --fix src/ scripts/live-suite.ts tests/live-*.ts",
|
|
32
32
|
"format": "dprint fmt",
|
|
33
33
|
"format:check": "dprint check",
|
|
34
34
|
"test": "bun test",
|
|
35
|
+
"test:live": "bun --no-env-file scripts/live-suite.ts",
|
|
36
|
+
"check:live": "tsc --noEmit -p tsconfig.live.json",
|
|
35
37
|
"test:tokens": "bun test tests/cli/agent-token-budget.test.ts",
|
|
36
38
|
"test:platform": "bun scripts/platform-smoke.mjs",
|
|
37
39
|
"test:integration": "cross-env RUN_DATAIKU_INTEGRATION=1 bun test tests/integration-playground.test.ts",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "dataiku-sdk",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.2.0",
|
|
5
5
|
"description": "Dataiku DSS SDK and agent-oriented CLI for programmatic access to DSS REST APIs.",
|
|
6
6
|
"homepage": "https://github.com/clssck/dataiku-sdk",
|
|
7
7
|
"repository": "https://github.com/clssck/dataiku-sdk",
|