dataiku-sdk 0.1.2 → 0.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/dist/src/auth.d.ts +4 -0
- package/dist/src/auth.js +20 -0
- package/dist/src/cli.js +347 -30
- package/dist/src/config.d.ts +11 -0
- package/dist/src/config.js +64 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +3 -0
- package/dist/src/resources/recipes.d.ts +11 -3
- package/dist/src/resources/recipes.js +27 -3
- package/dist/src/skill.d.ts +33 -0
- package/dist/src/skill.js +229 -0
- package/package.json +1 -1
package/dist/src/auth.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DataikuClient, } from "./client.js";
|
|
2
|
+
import { DataikuError, } from "./errors.js";
|
|
3
|
+
export async function validateCredentials(url, apiKey) {
|
|
4
|
+
try {
|
|
5
|
+
const client = new DataikuClient({
|
|
6
|
+
url,
|
|
7
|
+
apiKey,
|
|
8
|
+
requestTimeoutMs: 10_000,
|
|
9
|
+
retryMaxAttempts: 1,
|
|
10
|
+
});
|
|
11
|
+
await client.projects.list();
|
|
12
|
+
return { valid: true, };
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
if (err instanceof DataikuError) {
|
|
16
|
+
return { valid: false, error: err.message, };
|
|
17
|
+
}
|
|
18
|
+
return { valid: false, error: err instanceof Error ? err.message : String(err), };
|
|
19
|
+
}
|
|
20
|
+
}
|
package/dist/src/cli.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync, } from "node:fs";
|
|
3
|
+
import { writeFile, } from "node:fs/promises";
|
|
3
4
|
import { dirname, resolve, } from "node:path";
|
|
5
|
+
import { createInterface, } from "node:readline";
|
|
6
|
+
import { Writable, } from "node:stream";
|
|
4
7
|
import { fileURLToPath, } from "node:url";
|
|
8
|
+
import { validateCredentials, } from "./auth.js";
|
|
5
9
|
import { DataikuClient, } from "./client.js";
|
|
10
|
+
import { deleteCredentials, getCredentialsPath, loadCredentials, maskApiKey, saveCredentials, } from "./config.js";
|
|
6
11
|
import { DataikuError, } from "./errors.js";
|
|
12
|
+
import { AGENTS, detectAgents, installSkill, } from "./skill.js";
|
|
7
13
|
// ---------------------------------------------------------------------------
|
|
8
14
|
// Utility helpers
|
|
9
15
|
// ---------------------------------------------------------------------------
|
|
16
|
+
const CLI_VERSION = (() => {
|
|
17
|
+
try {
|
|
18
|
+
const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
19
|
+
return JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return "unknown";
|
|
23
|
+
}
|
|
24
|
+
})();
|
|
10
25
|
function num(v) {
|
|
11
26
|
if (typeof v !== "string")
|
|
12
27
|
return undefined;
|
|
@@ -63,9 +78,29 @@ function formatLineDiff(remoteName, localPath, remoteContent, localContent) {
|
|
|
63
78
|
function parseOutputFormat(v) {
|
|
64
79
|
if (v === undefined)
|
|
65
80
|
return "json";
|
|
66
|
-
if (v === "json" || v === "quiet" || v === "tsv")
|
|
81
|
+
if (v === "json" || v === "quiet" || v === "table" || v === "tsv")
|
|
67
82
|
return v;
|
|
68
|
-
throw new UsageError(`Invalid --format value: ${String(v)}. Use json, tsv, or quiet.`);
|
|
83
|
+
throw new UsageError(`Invalid --format value: ${String(v)}. Use json, tsv, table, or quiet.`);
|
|
84
|
+
}
|
|
85
|
+
function writeTable(items) {
|
|
86
|
+
if (items.length === 0)
|
|
87
|
+
return;
|
|
88
|
+
const keys = Object.keys(items[0]);
|
|
89
|
+
const maxWidths = keys.map((k) => {
|
|
90
|
+
const values = items.map((item) => String(item[k] ?? ""));
|
|
91
|
+
return Math.min(40, Math.max(k.length, ...values.map((v) => v.length)));
|
|
92
|
+
});
|
|
93
|
+
process.stdout.write(`${keys.map((k, i) => k.padEnd(maxWidths[i])).join(" ")}\n`);
|
|
94
|
+
process.stdout.write(`${maxWidths.map((w) => "-".repeat(w)).join(" ")}\n`);
|
|
95
|
+
for (const item of items) {
|
|
96
|
+
const row = keys.map((k, i) => {
|
|
97
|
+
const val = String(item[k] ?? "");
|
|
98
|
+
return (val.length > maxWidths[i]
|
|
99
|
+
? `${val.slice(0, maxWidths[i] - 1)}\u2026`
|
|
100
|
+
: val).padEnd(maxWidths[i]);
|
|
101
|
+
});
|
|
102
|
+
process.stdout.write(`${row.join(" ")}\n`);
|
|
103
|
+
}
|
|
69
104
|
}
|
|
70
105
|
function writeCommandResult(result, format) {
|
|
71
106
|
if (result === undefined || result === null) {
|
|
@@ -84,9 +119,9 @@ function writeCommandResult(result, format) {
|
|
|
84
119
|
}
|
|
85
120
|
if (format === "quiet")
|
|
86
121
|
return;
|
|
87
|
-
|
|
88
|
-
&& Array.isArray(
|
|
89
|
-
|
|
122
|
+
const isArrayOfObjects = Array.isArray(result)
|
|
123
|
+
&& result.every((item) => item !== null && typeof item === "object" && !Array.isArray(item));
|
|
124
|
+
if (format === "tsv" && isArrayOfObjects) {
|
|
90
125
|
const items = result;
|
|
91
126
|
if (items.length === 0)
|
|
92
127
|
return;
|
|
@@ -97,8 +132,23 @@ function writeCommandResult(result, format) {
|
|
|
97
132
|
}
|
|
98
133
|
return;
|
|
99
134
|
}
|
|
135
|
+
if (format === "table" && isArrayOfObjects) {
|
|
136
|
+
writeTable(result);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
100
139
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
101
140
|
}
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Arg parsing
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
const BOOLEAN_FLAGS = new Set(["help", "verbose", "version", "stdin", "global", "list-agents",]);
|
|
145
|
+
const SHORT_FLAGS = {
|
|
146
|
+
h: "help",
|
|
147
|
+
v: "verbose",
|
|
148
|
+
V: "version",
|
|
149
|
+
f: "format",
|
|
150
|
+
o: "output",
|
|
151
|
+
};
|
|
102
152
|
function parseArgs(argv) {
|
|
103
153
|
const positional = [];
|
|
104
154
|
const flags = {};
|
|
@@ -115,16 +165,43 @@ function parseArgs(argv) {
|
|
|
115
165
|
flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
|
|
116
166
|
}
|
|
117
167
|
else {
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
flags[
|
|
121
|
-
i++;
|
|
168
|
+
const flagName = arg.slice(2);
|
|
169
|
+
if (BOOLEAN_FLAGS.has(flagName)) {
|
|
170
|
+
flags[flagName] = true;
|
|
122
171
|
}
|
|
123
172
|
else {
|
|
124
|
-
|
|
173
|
+
const next = argv[i + 1];
|
|
174
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
175
|
+
flags[flagName] = next;
|
|
176
|
+
i++;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
flags[flagName] = true;
|
|
180
|
+
}
|
|
125
181
|
}
|
|
126
182
|
}
|
|
127
183
|
}
|
|
184
|
+
else if (arg.length === 2 && arg[0] === "-" && arg[1] !== "-") {
|
|
185
|
+
const long = SHORT_FLAGS[arg[1]];
|
|
186
|
+
if (long) {
|
|
187
|
+
if (BOOLEAN_FLAGS.has(long)) {
|
|
188
|
+
flags[long] = true;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const next = argv[i + 1];
|
|
192
|
+
if (next !== undefined && !next.startsWith("-")) {
|
|
193
|
+
flags[long] = next;
|
|
194
|
+
i++;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
flags[long] = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
positional.push(arg);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
128
205
|
else {
|
|
129
206
|
positional.push(arg);
|
|
130
207
|
}
|
|
@@ -325,6 +402,33 @@ const commands = {
|
|
|
325
402
|
},
|
|
326
403
|
usage: "dss recipe update <name> [--data '{...}' | --data-file PATH | --stdin] [--project-key KEY]",
|
|
327
404
|
},
|
|
405
|
+
"get-payload": {
|
|
406
|
+
handler: async (c, a, f) => {
|
|
407
|
+
requireArgs(a, 1, "dss recipe get-payload <name>");
|
|
408
|
+
const payload = await c.recipes.getPayload(a[0], {
|
|
409
|
+
projectKey: f["project-key"],
|
|
410
|
+
});
|
|
411
|
+
if (typeof f["output"] === "string") {
|
|
412
|
+
await writeFile(f["output"], payload, "utf-8");
|
|
413
|
+
return f["output"];
|
|
414
|
+
}
|
|
415
|
+
return payload;
|
|
416
|
+
},
|
|
417
|
+
usage: "dss recipe get-payload <name> [--output PATH] [--project-key KEY]",
|
|
418
|
+
},
|
|
419
|
+
"set-payload": {
|
|
420
|
+
handler: async (c, a, f) => {
|
|
421
|
+
requireArgs(a, 1, "dss recipe set-payload <name> --file PATH");
|
|
422
|
+
const filePath = f["file"];
|
|
423
|
+
if (!filePath)
|
|
424
|
+
throw new UsageError("--file is required.");
|
|
425
|
+
const content = readFileSync(filePath, "utf-8");
|
|
426
|
+
await c.recipes.setPayload(a[0], content, {
|
|
427
|
+
projectKey: f["project-key"],
|
|
428
|
+
});
|
|
429
|
+
},
|
|
430
|
+
usage: "dss recipe set-payload <name> --file PATH [--project-key KEY]",
|
|
431
|
+
},
|
|
328
432
|
},
|
|
329
433
|
job: {
|
|
330
434
|
list: {
|
|
@@ -648,21 +752,35 @@ const commands = {
|
|
|
648
752
|
// ---------------------------------------------------------------------------
|
|
649
753
|
// Help
|
|
650
754
|
// ---------------------------------------------------------------------------
|
|
651
|
-
const RESOURCE_NAMES = Object.keys(commands).sort();
|
|
755
|
+
const RESOURCE_NAMES = [...Object.keys(commands), "auth", "install-skill",].sort();
|
|
652
756
|
function printTopLevelHelp() {
|
|
653
757
|
const lines = [
|
|
654
758
|
"Usage: dss <resource> <action> [args...] [--flags]",
|
|
655
759
|
"",
|
|
656
760
|
"Global flags:",
|
|
657
|
-
" --
|
|
658
|
-
"
|
|
659
|
-
"
|
|
660
|
-
" --format FORMAT Output format: json|tsv|quiet",
|
|
661
|
-
" --
|
|
662
|
-
"
|
|
761
|
+
" -h, --help Show help",
|
|
762
|
+
" -v, --verbose Log HTTP requests to stderr",
|
|
763
|
+
" -V, --version Show version",
|
|
764
|
+
" -f, --format FORMAT Output format: json|tsv|table|quiet",
|
|
765
|
+
" -o, --output PATH Write output to file (recipe get-payload)",
|
|
766
|
+
" --url URL Dataiku DSS base URL (env: DATAIKU_URL)",
|
|
767
|
+
" --api-key KEY API key (env: DATAIKU_API_KEY)",
|
|
768
|
+
" --project-key KEY Default project key (env: DATAIKU_PROJECT_KEY)",
|
|
769
|
+
" --timeout MS Request timeout in ms (default: 30000)",
|
|
663
770
|
"",
|
|
664
771
|
"Resources:",
|
|
665
772
|
...RESOURCE_NAMES.map((r) => ` ${r}`),
|
|
773
|
+
"",
|
|
774
|
+
"Quick start:",
|
|
775
|
+
" dss auth login Save DSS credentials",
|
|
776
|
+
" dss auth status Verify connection",
|
|
777
|
+
" dss project list List accessible projects",
|
|
778
|
+
" dss dataset list List datasets in default project",
|
|
779
|
+
" dss dataset preview <name> Preview dataset rows as CSV",
|
|
780
|
+
" dss recipe get-payload <name> Print recipe code to stdout",
|
|
781
|
+
" dss recipe download-code <name> Download recipe code to a file",
|
|
782
|
+
" dss job log <id> View job log output",
|
|
783
|
+
" dss install-skill Install agent skill for coding agents",
|
|
666
784
|
];
|
|
667
785
|
process.stderr.write(`${lines.join("\n")}\n`);
|
|
668
786
|
}
|
|
@@ -728,11 +846,133 @@ function loadEnvFile() {
|
|
|
728
846
|
}
|
|
729
847
|
}
|
|
730
848
|
// ---------------------------------------------------------------------------
|
|
849
|
+
// Auth commands (run before client creation)
|
|
850
|
+
// ---------------------------------------------------------------------------
|
|
851
|
+
const AUTH_ACTIONS = {
|
|
852
|
+
login: {
|
|
853
|
+
handler: async (flags) => {
|
|
854
|
+
let { url, apiKey, projectKey, } = resolveCredentials(flags);
|
|
855
|
+
if (!url || !apiKey) {
|
|
856
|
+
if (!process.stdin.isTTY) {
|
|
857
|
+
throw new UsageError("Missing --url and/or --api-key. Provide them as flags or run interactively.");
|
|
858
|
+
}
|
|
859
|
+
if (!url)
|
|
860
|
+
url = await promptLine("DSS URL: ");
|
|
861
|
+
if (!apiKey)
|
|
862
|
+
apiKey = await promptSecret("API key: ");
|
|
863
|
+
if (!projectKey)
|
|
864
|
+
projectKey = (await promptLine("Project key (optional): ")) || undefined;
|
|
865
|
+
}
|
|
866
|
+
if (!url)
|
|
867
|
+
throw new UsageError("URL is required.");
|
|
868
|
+
if (!apiKey)
|
|
869
|
+
throw new UsageError("API key is required.");
|
|
870
|
+
process.stderr.write("Validating credentials... ");
|
|
871
|
+
const result = await validateCredentials(url, apiKey);
|
|
872
|
+
if (!result.valid) {
|
|
873
|
+
process.stderr.write(`✗ Failed\n`);
|
|
874
|
+
throw new DataikuError(0, "Authentication Failed", result.error ?? "Credential validation failed");
|
|
875
|
+
}
|
|
876
|
+
process.stderr.write("\u2713 Connected\n");
|
|
877
|
+
saveCredentials({ url, apiKey, projectKey, });
|
|
878
|
+
process.stderr.write(`Credentials saved to ${getCredentialsPath()}\n`);
|
|
879
|
+
},
|
|
880
|
+
usage: "dss auth login [--url URL] [--api-key KEY] [--project-key KEY]",
|
|
881
|
+
},
|
|
882
|
+
status: {
|
|
883
|
+
handler: async (_flags) => {
|
|
884
|
+
const creds = loadCredentials();
|
|
885
|
+
if (!creds) {
|
|
886
|
+
process.stderr.write("No saved credentials. Run: dss auth login\n");
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
const lines = [
|
|
890
|
+
`URL: ${creds.url}`,
|
|
891
|
+
`API key: ${maskApiKey(creds.apiKey)}`,
|
|
892
|
+
`Project key: ${creds.projectKey ?? "(not set)"}`,
|
|
893
|
+
];
|
|
894
|
+
for (const line of lines)
|
|
895
|
+
process.stderr.write(`${line}\n`);
|
|
896
|
+
const result = await validateCredentials(creds.url, creds.apiKey);
|
|
897
|
+
if (result.valid) {
|
|
898
|
+
process.stderr.write("Connection: \u2713 Valid\n");
|
|
899
|
+
}
|
|
900
|
+
else {
|
|
901
|
+
process.stderr.write(`Connection: \u2717 Failed (${result.error ?? "unknown error"})\n`);
|
|
902
|
+
}
|
|
903
|
+
process.stderr.write(`Config: ${getCredentialsPath()}\n`);
|
|
904
|
+
},
|
|
905
|
+
usage: "dss auth status",
|
|
906
|
+
},
|
|
907
|
+
logout: {
|
|
908
|
+
handler: async (_flags) => {
|
|
909
|
+
deleteCredentials();
|
|
910
|
+
process.stderr.write("Credentials removed.\n");
|
|
911
|
+
},
|
|
912
|
+
usage: "dss auth logout",
|
|
913
|
+
},
|
|
914
|
+
};
|
|
915
|
+
// ---------------------------------------------------------------------------
|
|
916
|
+
// Interactive prompts
|
|
917
|
+
// ---------------------------------------------------------------------------
|
|
918
|
+
function promptLine(label) {
|
|
919
|
+
return new Promise((res, rej) => {
|
|
920
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr, });
|
|
921
|
+
rl.on("close", () => rej(new UsageError("Input closed before a value was provided.")));
|
|
922
|
+
rl.question(label, (answer) => {
|
|
923
|
+
rl.close();
|
|
924
|
+
res(answer.trim());
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
function promptSecret(label) {
|
|
929
|
+
return new Promise((res, rej) => {
|
|
930
|
+
const muted = new Writable({
|
|
931
|
+
write(_chunk, _encoding, cb) {
|
|
932
|
+
cb();
|
|
933
|
+
},
|
|
934
|
+
});
|
|
935
|
+
const rl = createInterface({ input: process.stdin, output: muted, terminal: true, });
|
|
936
|
+
rl.on("close", () => rej(new UsageError("Input closed before a value was provided.")));
|
|
937
|
+
process.stderr.write(label);
|
|
938
|
+
rl.question("", (answer) => {
|
|
939
|
+
rl.close();
|
|
940
|
+
process.stderr.write("\n");
|
|
941
|
+
res(answer.trim());
|
|
942
|
+
});
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
// ---------------------------------------------------------------------------
|
|
946
|
+
// Credential resolution
|
|
947
|
+
// ---------------------------------------------------------------------------
|
|
948
|
+
function resolveCredentials(flags) {
|
|
949
|
+
let url = flags["url"];
|
|
950
|
+
let apiKey = flags["api-key"];
|
|
951
|
+
let projectKey = flags["project-key"];
|
|
952
|
+
url ??= process.env.DATAIKU_URL;
|
|
953
|
+
apiKey ??= process.env.DATAIKU_API_KEY;
|
|
954
|
+
projectKey ??= process.env.DATAIKU_PROJECT_KEY;
|
|
955
|
+
if (!url || !apiKey) {
|
|
956
|
+
const saved = loadCredentials();
|
|
957
|
+
if (saved) {
|
|
958
|
+
url ??= saved.url;
|
|
959
|
+
apiKey ??= saved.apiKey;
|
|
960
|
+
projectKey ??= saved.projectKey;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return { url: url ?? "", apiKey: apiKey ?? "", projectKey, };
|
|
964
|
+
}
|
|
965
|
+
// ---------------------------------------------------------------------------
|
|
731
966
|
// Main
|
|
732
967
|
// ---------------------------------------------------------------------------
|
|
733
968
|
async function main() {
|
|
734
969
|
loadEnvFile();
|
|
735
970
|
const { positional, flags, } = parseArgs(process.argv.slice(2));
|
|
971
|
+
// --version
|
|
972
|
+
if (flags["version"] === true) {
|
|
973
|
+
process.stdout.write(`${CLI_VERSION}\n`);
|
|
974
|
+
process.exit(0);
|
|
975
|
+
}
|
|
736
976
|
// Top-level help
|
|
737
977
|
if (positional.length === 0 || (positional.length === 0 && flags["help"])) {
|
|
738
978
|
printTopLevelHelp();
|
|
@@ -741,13 +981,91 @@ async function main() {
|
|
|
741
981
|
process.exit(1);
|
|
742
982
|
}
|
|
743
983
|
const resource = positional[0];
|
|
984
|
+
// Auth commands — dispatched before client creation
|
|
985
|
+
if (resource === "auth") {
|
|
986
|
+
const action = positional[1];
|
|
987
|
+
if (!action || flags["help"] === true) {
|
|
988
|
+
const lines = [
|
|
989
|
+
"Usage: dss auth <action> [--flags]",
|
|
990
|
+
"",
|
|
991
|
+
"Actions:",
|
|
992
|
+
...Object.entries(AUTH_ACTIONS).map(([name, meta,]) => ` ${name} \u2192 ${meta.usage}`),
|
|
993
|
+
];
|
|
994
|
+
process.stderr.write(`${lines.join("\n")}\n`);
|
|
995
|
+
process.exit(flags["help"] === true ? 0 : 1);
|
|
996
|
+
}
|
|
997
|
+
const authMeta = AUTH_ACTIONS[action];
|
|
998
|
+
if (!authMeta) {
|
|
999
|
+
process.stderr.write(`Unknown action: auth ${action}\nAvailable: ${Object.keys(AUTH_ACTIONS).join(", ")}\n`);
|
|
1000
|
+
process.exit(1);
|
|
1001
|
+
}
|
|
1002
|
+
await authMeta.handler(flags);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
// install-skill — dispatched before client creation
|
|
1006
|
+
if (resource === "install-skill") {
|
|
1007
|
+
if (flags["help"] === true) {
|
|
1008
|
+
const lines = [
|
|
1009
|
+
"Usage: dss install-skill [--global] [--agent NAME] [--list-agents]",
|
|
1010
|
+
"",
|
|
1011
|
+
"Install the dataiku-dss agent skill for detected coding agents.",
|
|
1012
|
+
"",
|
|
1013
|
+
"Flags:",
|
|
1014
|
+
" --global Install to user-level global scope (default: project)",
|
|
1015
|
+
" --agent NAME Target a specific agent: claude, codex, cursor, pi, omp",
|
|
1016
|
+
" --list-agents Print detected agents and exit",
|
|
1017
|
+
];
|
|
1018
|
+
process.stderr.write(`${lines.join("\n")}\n`);
|
|
1019
|
+
process.exit(0);
|
|
1020
|
+
}
|
|
1021
|
+
const listOnly = flags["list-agents"] === true;
|
|
1022
|
+
const agentFilter = typeof flags["agent"] === "string" ? flags["agent"] : undefined;
|
|
1023
|
+
const isGlobal = flags["global"] === true;
|
|
1024
|
+
// Resolve target agents
|
|
1025
|
+
let targets;
|
|
1026
|
+
if (agentFilter) {
|
|
1027
|
+
const def = AGENTS[agentFilter];
|
|
1028
|
+
if (!def) {
|
|
1029
|
+
throw new UsageError(`Unknown agent: ${agentFilter}. Available: ${Object.keys(AGENTS).join(", ")}`);
|
|
1030
|
+
}
|
|
1031
|
+
targets = [{ id: agentFilter, def, via: "flag", },];
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
targets = detectAgents();
|
|
1035
|
+
}
|
|
1036
|
+
if (listOnly) {
|
|
1037
|
+
if (targets.length === 0) {
|
|
1038
|
+
process.stderr.write("No coding agents detected.\n");
|
|
1039
|
+
}
|
|
1040
|
+
else {
|
|
1041
|
+
process.stderr.write("Detected agents:\n");
|
|
1042
|
+
for (const t of targets) {
|
|
1043
|
+
process.stderr.write(` ${t.id} (${t.def.name}, via ${t.via})\n`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
process.exit(0);
|
|
1047
|
+
}
|
|
1048
|
+
if (targets.length === 0) {
|
|
1049
|
+
throw new UsageError("No coding agents detected. Install one (claude, codex, cursor, pi, omp) or use --agent NAME.");
|
|
1050
|
+
}
|
|
1051
|
+
const scope = isGlobal ? "global" : "project";
|
|
1052
|
+
process.stderr.write(`Installing dataiku-dss skill (${scope} scope):\n`);
|
|
1053
|
+
const results = installSkill(targets, { global: isGlobal, cwd: process.cwd(), });
|
|
1054
|
+
for (const r of results) {
|
|
1055
|
+
process.stderr.write(` ${r.agent} \u2192 ${r.path}\n`);
|
|
1056
|
+
}
|
|
1057
|
+
if (results.length > 0) {
|
|
1058
|
+
process.stderr.write(`\nDone. ${results.length} skill(s) installed.\n`);
|
|
1059
|
+
}
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
744
1062
|
// Unknown resource
|
|
745
1063
|
if (!commands[resource]) {
|
|
746
1064
|
if (flags["help"]) {
|
|
747
1065
|
printTopLevelHelp();
|
|
748
1066
|
process.exit(0);
|
|
749
1067
|
}
|
|
750
|
-
process.stderr.write(`Unknown resource: ${resource}\nAvailable: ${RESOURCE_NAMES.join(", ")}\n`);
|
|
1068
|
+
process.stderr.write(`Unknown resource: ${resource} \nAvailable: ${RESOURCE_NAMES.join(", ")} \n`);
|
|
751
1069
|
process.exit(1);
|
|
752
1070
|
}
|
|
753
1071
|
// Resource-level help
|
|
@@ -763,7 +1081,7 @@ async function main() {
|
|
|
763
1081
|
const actionMeta = commands[resource][action];
|
|
764
1082
|
// Unknown action
|
|
765
1083
|
if (!actionMeta) {
|
|
766
|
-
process.stderr.write(`Unknown action: ${resource} ${action}\nAvailable actions for ${resource}: ${Object.keys(commands[resource]).join(", ")}\n`);
|
|
1084
|
+
process.stderr.write(`Unknown action: ${resource} ${action} \nAvailable actions for ${resource}: ${Object.keys(commands[resource]).join(", ")} \n`);
|
|
767
1085
|
process.exit(1);
|
|
768
1086
|
}
|
|
769
1087
|
// Action-level help
|
|
@@ -771,22 +1089,21 @@ async function main() {
|
|
|
771
1089
|
printActionHelp(resource, action);
|
|
772
1090
|
process.exit(0);
|
|
773
1091
|
}
|
|
774
|
-
//
|
|
775
|
-
const url
|
|
776
|
-
const apiKey = flags["api-key"] ?? process.env.DATAIKU_API_KEY ?? "";
|
|
1092
|
+
// Resolve credentials: flags > env > saved > .env
|
|
1093
|
+
const { url, apiKey, projectKey, } = resolveCredentials(flags);
|
|
777
1094
|
if (!url) {
|
|
778
|
-
|
|
779
|
-
process.exit(1);
|
|
1095
|
+
throw new UsageError("Missing Dataiku URL. Set DATAIKU_URL, pass --url, or run: dss auth login");
|
|
780
1096
|
}
|
|
781
1097
|
if (!apiKey) {
|
|
782
|
-
|
|
783
|
-
process.exit(1);
|
|
1098
|
+
throw new UsageError("Missing API key. Set DATAIKU_API_KEY, pass --api-key, or run: dss auth login");
|
|
784
1099
|
}
|
|
1100
|
+
const requestTimeoutMs = num(flags["timeout"]) ?? undefined;
|
|
785
1101
|
const client = new DataikuClient({
|
|
786
1102
|
url,
|
|
787
1103
|
apiKey,
|
|
788
|
-
projectKey
|
|
1104
|
+
projectKey,
|
|
789
1105
|
verbose: flags["verbose"] === true,
|
|
1106
|
+
requestTimeoutMs,
|
|
790
1107
|
});
|
|
791
1108
|
const args = positional.slice(2);
|
|
792
1109
|
const format = parseOutputFormat(flags["format"]);
|
|
@@ -795,7 +1112,7 @@ async function main() {
|
|
|
795
1112
|
}
|
|
796
1113
|
main().catch((err) => {
|
|
797
1114
|
if (err instanceof UsageError) {
|
|
798
|
-
process.stderr.write(`${err.message}\n`);
|
|
1115
|
+
process.stderr.write(`${err.message} \n`);
|
|
799
1116
|
process.exit(1);
|
|
800
1117
|
}
|
|
801
1118
|
if (err instanceof DataikuError) {
|
|
@@ -806,10 +1123,10 @@ main().catch((err) => {
|
|
|
806
1123
|
};
|
|
807
1124
|
if (err.retryHint)
|
|
808
1125
|
payload.retryHint = err.retryHint;
|
|
809
|
-
process.stderr.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
1126
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)} \n`);
|
|
810
1127
|
process.exit(err.category === "transient" ? 3 : 2);
|
|
811
1128
|
}
|
|
812
1129
|
const message = err instanceof Error ? err.message : String(err);
|
|
813
|
-
process.stderr.write(`${JSON.stringify({ error: message, }, null, 2)}\n`);
|
|
1130
|
+
process.stderr.write(`${JSON.stringify({ error: message, }, null, 2)} \n`);
|
|
814
1131
|
process.exit(1);
|
|
815
1132
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface DssCredentials {
|
|
2
|
+
url: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
projectKey?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getConfigDir(): string;
|
|
7
|
+
export declare function getCredentialsPath(): string;
|
|
8
|
+
export declare function loadCredentials(): DssCredentials | null;
|
|
9
|
+
export declare function saveCredentials(creds: DssCredentials): void;
|
|
10
|
+
export declare function deleteCredentials(): void;
|
|
11
|
+
export declare function maskApiKey(apiKey: string): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { homedir, } from "node:os";
|
|
3
|
+
import { dirname, join, resolve, } from "node:path";
|
|
4
|
+
export function getConfigDir() {
|
|
5
|
+
if (process.env.DSS_CONFIG_DIR)
|
|
6
|
+
return process.env.DSS_CONFIG_DIR;
|
|
7
|
+
if (process.env.XDG_CONFIG_HOME)
|
|
8
|
+
return resolve(process.env.XDG_CONFIG_HOME, "dataiku");
|
|
9
|
+
if (process.platform === "win32" && process.env.APPDATA) {
|
|
10
|
+
return resolve(process.env.APPDATA, "dataiku");
|
|
11
|
+
}
|
|
12
|
+
return resolve(homedir(), ".config", "dataiku");
|
|
13
|
+
}
|
|
14
|
+
export function getCredentialsPath() {
|
|
15
|
+
return join(getConfigDir(), "credentials.json");
|
|
16
|
+
}
|
|
17
|
+
export function loadCredentials() {
|
|
18
|
+
try {
|
|
19
|
+
const raw = readFileSync(getCredentialsPath(), "utf-8");
|
|
20
|
+
const parsed = JSON.parse(raw);
|
|
21
|
+
if (!parsed
|
|
22
|
+
|| typeof parsed !== "object"
|
|
23
|
+
|| Array.isArray(parsed)
|
|
24
|
+
|| typeof parsed.url !== "string"
|
|
25
|
+
|| typeof parsed.apiKey !== "string") {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const obj = parsed;
|
|
29
|
+
return {
|
|
30
|
+
url: obj.url,
|
|
31
|
+
apiKey: obj.apiKey,
|
|
32
|
+
projectKey: typeof obj.projectKey === "string" ? obj.projectKey : undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err.code === "ENOENT")
|
|
37
|
+
return null;
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function saveCredentials(creds) {
|
|
42
|
+
const path = getCredentialsPath();
|
|
43
|
+
mkdirSync(dirname(path), { recursive: true, });
|
|
44
|
+
const data = { url: creds.url, apiKey: creds.apiKey, };
|
|
45
|
+
if (creds.projectKey)
|
|
46
|
+
data.projectKey = creds.projectKey;
|
|
47
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
48
|
+
chmodSync(path, 0o600);
|
|
49
|
+
}
|
|
50
|
+
export function deleteCredentials() {
|
|
51
|
+
try {
|
|
52
|
+
unlinkSync(getCredentialsPath());
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (err.code === "ENOENT")
|
|
56
|
+
return;
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export function maskApiKey(apiKey) {
|
|
61
|
+
if (apiKey.length <= 12)
|
|
62
|
+
return "***";
|
|
63
|
+
return `${apiKey.slice(0, 6)}...${apiKey.slice(-6)}`;
|
|
64
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { DataikuClient, type DataikuClientConfig, } from "./client.js";
|
|
2
|
+
export { validateCredentials, } from "./auth.js";
|
|
3
|
+
export { deleteCredentials, type DssCredentials, getConfigDir, getCredentialsPath, loadCredentials, maskApiKey, saveCredentials, } from "./config.js";
|
|
2
4
|
export { DataikuError, type DataikuErrorCategory, type DataikuErrorTaxonomy, type DataikuRetryMetadata, } from "./errors.js";
|
|
3
5
|
export { CodeEnvsResource, } from "./resources/code-envs.js";
|
|
4
6
|
export { ConnectionsResource, } from "./resources/connections.js";
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Client
|
|
2
2
|
export { DataikuClient, } from "./client.js";
|
|
3
|
+
// Auth & Config
|
|
4
|
+
export { validateCredentials, } from "./auth.js";
|
|
5
|
+
export { deleteCredentials, getConfigDir, getCredentialsPath, loadCredentials, maskApiKey, saveCredentials, } from "./config.js";
|
|
3
6
|
// Errors
|
|
4
7
|
export { DataikuError, } from "./errors.js";
|
|
5
8
|
// Resources (for advanced use / extension)
|
|
@@ -23,14 +23,22 @@ export declare class RecipesResource extends BaseResource {
|
|
|
23
23
|
*/
|
|
24
24
|
update(recipeName: string, data: Record<string, unknown>, projectKey?: string): Promise<void>;
|
|
25
25
|
/**
|
|
26
|
-
|
|
26
|
+
* Download a recipe code payload to a local file.
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
* Returns the path to the written file.
|
|
29
|
+
*/
|
|
30
30
|
downloadCode(recipeName: string, opts?: {
|
|
31
31
|
outputPath?: string;
|
|
32
32
|
projectKey?: string;
|
|
33
33
|
}): Promise<string>;
|
|
34
|
+
/** Get only the code payload of a recipe as a raw string. */
|
|
35
|
+
getPayload(recipeName: string, opts?: {
|
|
36
|
+
projectKey?: string;
|
|
37
|
+
}): Promise<string>;
|
|
38
|
+
/** Replace only the code payload of a recipe. */
|
|
39
|
+
setPayload(recipeName: string, payload: string, opts?: {
|
|
40
|
+
projectKey?: string;
|
|
41
|
+
}): Promise<void>;
|
|
34
42
|
/** Delete a recipe. */
|
|
35
43
|
delete(recipeName: string, projectKey?: string): Promise<void>;
|
|
36
44
|
/**
|
|
@@ -279,10 +279,10 @@ export class RecipesResource extends BaseResource {
|
|
|
279
279
|
await this.client.put(`/public/api/projects/${enc}/recipes/${rnEnc}`, merged);
|
|
280
280
|
}
|
|
281
281
|
/**
|
|
282
|
-
|
|
282
|
+
* Download a recipe code payload to a local file.
|
|
283
283
|
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
* Returns the path to the written file.
|
|
285
|
+
*/
|
|
286
286
|
async downloadCode(recipeName, opts) {
|
|
287
287
|
const result = await this.get(recipeName, {
|
|
288
288
|
includePayload: true,
|
|
@@ -296,6 +296,30 @@ export class RecipesResource extends BaseResource {
|
|
|
296
296
|
await writeFile(filePath, result.payload, "utf-8");
|
|
297
297
|
return filePath;
|
|
298
298
|
}
|
|
299
|
+
/** Get only the code payload of a recipe as a raw string. */
|
|
300
|
+
async getPayload(recipeName, opts) {
|
|
301
|
+
const result = await this.get(recipeName, {
|
|
302
|
+
includePayload: true,
|
|
303
|
+
projectKey: opts?.projectKey,
|
|
304
|
+
});
|
|
305
|
+
if (!result.payload) {
|
|
306
|
+
throw new Error(`Recipe "${recipeName}" has no code payload.`);
|
|
307
|
+
}
|
|
308
|
+
return result.payload;
|
|
309
|
+
}
|
|
310
|
+
/** Replace only the code payload of a recipe. */
|
|
311
|
+
async setPayload(recipeName, payload, opts) {
|
|
312
|
+
const current = await this.get(recipeName, {
|
|
313
|
+
includePayload: true,
|
|
314
|
+
projectKey: opts?.projectKey,
|
|
315
|
+
});
|
|
316
|
+
const enc = this.enc(opts?.projectKey);
|
|
317
|
+
const rnEnc = encodeURIComponent(recipeName);
|
|
318
|
+
await this.client.put(`/public/api/projects/${enc}/recipes/${rnEnc}`, {
|
|
319
|
+
...current,
|
|
320
|
+
payload,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
299
323
|
/** Delete a recipe. */
|
|
300
324
|
async delete(recipeName, projectKey) {
|
|
301
325
|
const enc = this.enc(projectKey);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface AgentDef {
|
|
2
|
+
/** Display name */
|
|
3
|
+
name: string;
|
|
4
|
+
/** CLI binary name (for `which` detection) */
|
|
5
|
+
binary: string;
|
|
6
|
+
/** Config directory relative to HOME (for fallback detection) */
|
|
7
|
+
configDir: string;
|
|
8
|
+
/** Require config dir to exist even when binary is found (disambiguates shared binary names) */
|
|
9
|
+
configDirRequired?: boolean;
|
|
10
|
+
/** Skill path relative to HOME (global install) */
|
|
11
|
+
globalPath: (home: string) => string;
|
|
12
|
+
/** Skill path relative to CWD (project install). null = not supported. */
|
|
13
|
+
projectPath: string | null;
|
|
14
|
+
/** File to write inside the skill directory */
|
|
15
|
+
filename: string;
|
|
16
|
+
/** Content generator: standard SKILL.md or Cursor MDC */
|
|
17
|
+
content: () => string;
|
|
18
|
+
}
|
|
19
|
+
export declare const AGENTS: Record<string, AgentDef>;
|
|
20
|
+
export interface DetectedAgent {
|
|
21
|
+
id: string;
|
|
22
|
+
def: AgentDef;
|
|
23
|
+
via: "binary" | "config-dir" | "flag";
|
|
24
|
+
}
|
|
25
|
+
export declare function detectAgents(): DetectedAgent[];
|
|
26
|
+
export interface InstallResult {
|
|
27
|
+
agent: string;
|
|
28
|
+
path: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function installSkill(agents: DetectedAgent[], opts: {
|
|
31
|
+
global: boolean;
|
|
32
|
+
cwd: string;
|
|
33
|
+
}): InstallResult[];
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { execFileSync, } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { homedir, } from "node:os";
|
|
4
|
+
import { join, } from "node:path";
|
|
5
|
+
const SKILL_BODY = `# Dataiku DSS CLI
|
|
6
|
+
|
|
7
|
+
The \`dss\` CLI (npm: dataiku-sdk) manages Dataiku DSS resources from the terminal.
|
|
8
|
+
|
|
9
|
+
## When to use
|
|
10
|
+
|
|
11
|
+
- Query, create, or modify DSS projects, datasets, recipes, jobs, or scenarios.
|
|
12
|
+
- Build datasets or run scenarios and wait for completion.
|
|
13
|
+
- Download or upload recipe code, dataset data, or managed folder files.
|
|
14
|
+
- Run SQL queries against DSS connections.
|
|
15
|
+
- Inspect project flows, job logs, or dataset schemas.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Requires [Bun](https://bun.sh) runtime.
|
|
20
|
+
|
|
21
|
+
\`\`\`bash
|
|
22
|
+
bun add -g dataiku-sdk # global install \u2014 provides the \`dss\` command
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
Or run without installing:
|
|
26
|
+
|
|
27
|
+
\`\`\`bash
|
|
28
|
+
bunx dataiku-sdk <command> # e.g. bunx dataiku-sdk auth login
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
## Authentication
|
|
32
|
+
|
|
33
|
+
\`\`\`bash
|
|
34
|
+
dss auth login # interactive: prompts for URL, API key, project key
|
|
35
|
+
dss auth login --url https://dss.example.com --api-key YOUR_KEY
|
|
36
|
+
dss auth status # verify connection
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
Credentials are saved to \`~/.dss/credentials.json\`. Alternatively set environment variables:
|
|
40
|
+
|
|
41
|
+
\`\`\`bash
|
|
42
|
+
export DATAIKU_URL=https://dss.example.com
|
|
43
|
+
export DATAIKU_API_KEY=your-api-key
|
|
44
|
+
export DATAIKU_PROJECT_KEY=MYPROJ # optional default project
|
|
45
|
+
\`\`\`
|
|
46
|
+
|
|
47
|
+
## Workflows
|
|
48
|
+
|
|
49
|
+
### Inspect a project
|
|
50
|
+
|
|
51
|
+
\`\`\`bash
|
|
52
|
+
dss project list # find the project key
|
|
53
|
+
dss dataset list --project-key MYPROJ # list its datasets
|
|
54
|
+
dss dataset preview orders --max-rows 10 # peek at data
|
|
55
|
+
dss dataset schema orders # inspect columns
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
### Edit recipe code
|
|
59
|
+
|
|
60
|
+
\`\`\`bash
|
|
61
|
+
dss recipe download-code my-recipe -o code.py # download
|
|
62
|
+
# ... edit code.py ...
|
|
63
|
+
dss recipe diff my-recipe --file code.py # review changes
|
|
64
|
+
dss recipe set-payload my-recipe --file code.py # upload
|
|
65
|
+
\`\`\`
|
|
66
|
+
|
|
67
|
+
### Build and monitor
|
|
68
|
+
|
|
69
|
+
\`\`\`bash
|
|
70
|
+
dss job build-and-wait my-dataset --include-logs # build + wait + stream logs
|
|
71
|
+
dss job list # recent jobs
|
|
72
|
+
dss job log <job-id> # full log output
|
|
73
|
+
\`\`\`
|
|
74
|
+
|
|
75
|
+
### Run a scenario
|
|
76
|
+
|
|
77
|
+
\`\`\`bash
|
|
78
|
+
dss scenario run my-scenario
|
|
79
|
+
dss scenario status my-scenario # check if finished
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
## Command reference
|
|
83
|
+
|
|
84
|
+
\`\`\`
|
|
85
|
+
dss <resource> <action> [args...] [--flags]
|
|
86
|
+
|
|
87
|
+
Resources: project, dataset, recipe, job, scenario, folder, notebook,
|
|
88
|
+
variable, code-env, connection, sql, auth, install-skill
|
|
89
|
+
\`\`\`
|
|
90
|
+
|
|
91
|
+
Use \`dss <resource> --help\` to see all actions and flags for any resource.
|
|
92
|
+
|
|
93
|
+
## Key flags
|
|
94
|
+
|
|
95
|
+
\`\`\`
|
|
96
|
+
-f, --format FORMAT json (default) | tsv | table | quiet
|
|
97
|
+
-o, --output PATH write output to file instead of stdout
|
|
98
|
+
-v, --verbose log HTTP requests to stderr
|
|
99
|
+
--project-key KEY override default project for any command
|
|
100
|
+
--timeout MS request timeout (default: 30000)
|
|
101
|
+
--stdin read JSON input from stdin
|
|
102
|
+
\`\`\`
|
|
103
|
+
|
|
104
|
+
## Gotchas
|
|
105
|
+
|
|
106
|
+
- **Most commands need a project key.** Set it once via \`dss auth login\` or \`DATAIKU_PROJECT_KEY\` to avoid passing \`--project-key\` on every call.
|
|
107
|
+
- **Output is JSON by default.** Use \`-f table\` when showing results to a user; use \`-f tsv\` when piping to scripts.
|
|
108
|
+
- **\`dss job build\` returns immediately.** Use \`dss job build-and-wait\` to block until the build finishes. Add \`--include-logs\` to stream log output.
|
|
109
|
+
- **Folder commands accept names or IDs.** If a folder name contains spaces, quote it. The CLI resolves names to IDs automatically.
|
|
110
|
+
- **Recipe set-payload overwrites the entire payload.** Always download first, edit, diff, then upload.
|
|
111
|
+
- **Transient errors exit code 3, API errors exit code 2, usage errors exit code 1.** Check exit codes to distinguish retriable failures.
|
|
112
|
+
`;
|
|
113
|
+
const SKILL_FRONTMATTER = `---
|
|
114
|
+
name: dataiku-dss
|
|
115
|
+
description: >-
|
|
116
|
+
Interact with Dataiku DSS from the command line \u2014 list projects, query datasets,
|
|
117
|
+
download and upload recipe code, build datasets, run scenarios, and manage jobs.
|
|
118
|
+
Use when the user wants to work with Dataiku DSS resources, inspect a DSS project,
|
|
119
|
+
modify recipes, trigger builds, check job logs, or run SQL against DSS connections,
|
|
120
|
+
even if they don't explicitly mention the dss CLI.
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
`;
|
|
124
|
+
function skillContent() {
|
|
125
|
+
return SKILL_FRONTMATTER + SKILL_BODY;
|
|
126
|
+
}
|
|
127
|
+
export const AGENTS = {
|
|
128
|
+
claude: {
|
|
129
|
+
name: "Claude Code",
|
|
130
|
+
binary: "claude",
|
|
131
|
+
configDir: ".claude",
|
|
132
|
+
globalPath: (home) => join(home, ".claude", "skills", "dataiku-dss"),
|
|
133
|
+
projectPath: ".claude/skills/dataiku-dss",
|
|
134
|
+
filename: "SKILL.md",
|
|
135
|
+
content: skillContent,
|
|
136
|
+
},
|
|
137
|
+
codex: {
|
|
138
|
+
name: "Codex",
|
|
139
|
+
binary: "codex",
|
|
140
|
+
configDir: ".codex",
|
|
141
|
+
globalPath: (home) => join(home, ".codex", "skills", "dataiku-dss"),
|
|
142
|
+
projectPath: ".codex/skills/dataiku-dss",
|
|
143
|
+
filename: "SKILL.md",
|
|
144
|
+
content: skillContent,
|
|
145
|
+
},
|
|
146
|
+
cursor: {
|
|
147
|
+
name: "Cursor",
|
|
148
|
+
binary: "cursor",
|
|
149
|
+
configDir: ".cursor",
|
|
150
|
+
globalPath: (home) => join(home, ".cursor", "skills", "dataiku-dss"),
|
|
151
|
+
projectPath: ".cursor/skills/dataiku-dss",
|
|
152
|
+
filename: "SKILL.md",
|
|
153
|
+
content: skillContent,
|
|
154
|
+
},
|
|
155
|
+
pi: {
|
|
156
|
+
name: "Pi",
|
|
157
|
+
binary: "pi",
|
|
158
|
+
configDir: ".pi",
|
|
159
|
+
globalPath: (home) => join(home, ".pi", "agent", "skills", "dataiku-dss"),
|
|
160
|
+
projectPath: ".pi/skills/dataiku-dss",
|
|
161
|
+
filename: "SKILL.md",
|
|
162
|
+
content: skillContent,
|
|
163
|
+
},
|
|
164
|
+
omp: {
|
|
165
|
+
name: "OhMyPi",
|
|
166
|
+
binary: "omp",
|
|
167
|
+
configDir: join(".omp", "agent"),
|
|
168
|
+
configDirRequired: true,
|
|
169
|
+
globalPath: (home) => join(home, ".omp", "agent", "skills", "dataiku-dss"),
|
|
170
|
+
projectPath: ".omp/skills/dataiku-dss",
|
|
171
|
+
filename: "SKILL.md",
|
|
172
|
+
content: skillContent,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Agent detection
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
function binaryExists(name) {
|
|
179
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
180
|
+
try {
|
|
181
|
+
execFileSync(cmd, [name,], { stdio: "pipe", });
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
export function detectAgents() {
|
|
189
|
+
const home = homedir();
|
|
190
|
+
const found = [];
|
|
191
|
+
for (const [id, def,] of Object.entries(AGENTS)) {
|
|
192
|
+
const hasBinary = binaryExists(def.binary);
|
|
193
|
+
const hasConfigDir = existsSync(join(home, def.configDir));
|
|
194
|
+
if (hasBinary && (!def.configDirRequired || hasConfigDir)) {
|
|
195
|
+
found.push({ id, def, via: "binary", });
|
|
196
|
+
}
|
|
197
|
+
else if (hasConfigDir) {
|
|
198
|
+
found.push({ id, def, via: "config-dir", });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return found;
|
|
202
|
+
}
|
|
203
|
+
export function installSkill(agents, opts) {
|
|
204
|
+
const home = homedir();
|
|
205
|
+
const results = [];
|
|
206
|
+
for (const { id, def, } of agents) {
|
|
207
|
+
let dir;
|
|
208
|
+
if (opts.global) {
|
|
209
|
+
const globalDir = def.globalPath(home);
|
|
210
|
+
if (!globalDir) {
|
|
211
|
+
process.stderr.write(` ${def.name}: skipped (no global path available)\n`);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
dir = globalDir;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
if (!def.projectPath) {
|
|
218
|
+
process.stderr.write(` ${def.name}: skipped (no project path available)\n`);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
dir = join(opts.cwd, def.projectPath);
|
|
222
|
+
}
|
|
223
|
+
mkdirSync(dir, { recursive: true, });
|
|
224
|
+
const filePath = join(dir, def.filename);
|
|
225
|
+
writeFileSync(filePath, def.content(), "utf-8");
|
|
226
|
+
results.push({ agent: id, path: filePath, });
|
|
227
|
+
}
|
|
228
|
+
return results;
|
|
229
|
+
}
|