@kweaver-ai/kweaver-sdk 0.6.4 → 0.6.6
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 +24 -3
- package/README.zh.md +2 -2
- package/dist/api/dataflow.d.ts +1 -1
- package/dist/api/toolboxes.d.ts +47 -0
- package/dist/api/toolboxes.js +90 -0
- package/dist/auth/oauth.d.ts +52 -21
- package/dist/auth/oauth.js +178 -220
- package/dist/cli.js +20 -1
- package/dist/commands/auth.js +32 -81
- package/dist/commands/bkn-ops.d.ts +1 -0
- package/dist/commands/bkn-ops.js +8 -1
- package/dist/commands/call.d.ts +10 -0
- package/dist/commands/call.js +61 -5
- package/dist/commands/ds.js +1 -1
- package/dist/commands/import-csv.d.ts +1 -1
- package/dist/commands/import-csv.js +3 -1
- package/dist/commands/tool.d.ts +16 -0
- package/dist/commands/tool.js +208 -0
- package/dist/commands/toolbox.d.ts +14 -0
- package/dist/commands/toolbox.js +256 -0
- package/package.json +1 -11
package/dist/commands/auth.js
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import { isNoAuth } from "../config/no-auth.js";
|
|
2
2
|
import { autoSelectBusinessDomain, clearPlatformSession, deletePlatform, deleteUser, getActiveUser, getConfigDir, getCurrentPlatform, getPlatformAlias, hasPlatform, listPlatforms, listUserProfiles, loadClientConfig, loadTokenConfig, resolveBusinessDomain, resolvePlatformIdentifier, resolveUserId, saveNoAuthPlatform, setActiveUser, setCurrentPlatform, setPlatformAlias, } from "../config/store.js";
|
|
3
3
|
import { decodeJwtPayload } from "../config/jwt.js";
|
|
4
|
-
import { buildCopyCommand, formatHttpError,
|
|
5
|
-
/** True when the `playwright` npm package can be imported (browser binaries may still need `npx playwright install`). */
|
|
6
|
-
async function isPlaywrightPackageResolvable() {
|
|
7
|
-
try {
|
|
8
|
-
const modName = "playwright";
|
|
9
|
-
await import(/* webpackIgnore: true */ modName);
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
4
|
+
import { buildCopyCommand, formatHttpError, normalizeBaseUrl, oauth2Login, oauth2PasswordSigninLogin, promptForUsername, promptForPassword, refreshTokenLogin, } from "../auth/oauth.js";
|
|
16
5
|
export async function runAuthCommand(args) {
|
|
17
6
|
const target = args[0];
|
|
18
7
|
const rest = args.slice(1);
|
|
@@ -39,24 +28,24 @@ Login options:
|
|
|
39
28
|
Requires --client-id and --client-secret.
|
|
40
29
|
Get these from the callback page after browser login or \`auth export\`.
|
|
41
30
|
--port <n> Local callback port (default: 9010). Use when 9010 is occupied.
|
|
42
|
-
--no-browser Do not open a browser
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
--
|
|
31
|
+
--no-browser Do not open a browser. Without -u/-p: print the auth URL and prompt for the
|
|
32
|
+
callback URL or code (stdin). With -u and/or -p: route through HTTP sign-in
|
|
33
|
+
(any missing credential is prompted; password is hidden when stdin is a TTY).
|
|
34
|
+
-u, --username Username for HTTP /oauth2/signin (POST). If -p is omitted, password is prompted.
|
|
35
|
+
-p, --password Password for HTTP /oauth2/signin (POST). If -u is omitted, username is prompted.
|
|
36
|
+
--http-signin Force HTTP /oauth2/signin (no browser). Missing -u/-p are prompted from stdin.
|
|
48
37
|
--insecure, -k Skip TLS certificate verification (self-signed / dev HTTPS only)
|
|
49
38
|
--no-auth Save platform without OAuth (servers with no authentication). Same as detecting OAuth 404 during login.`);
|
|
50
39
|
return 0;
|
|
51
40
|
}
|
|
52
41
|
if (target === "login") {
|
|
53
42
|
if (rest[0] === "--help" || rest[0] === "-h") {
|
|
54
|
-
console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--
|
|
43
|
+
console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--refresh-token T --client-id ID --client-secret S]`);
|
|
55
44
|
return 0;
|
|
56
45
|
}
|
|
57
46
|
const url = rest[0];
|
|
58
47
|
if (!url || url.startsWith("-")) {
|
|
59
|
-
console.error("Usage: kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass]
|
|
48
|
+
console.error("Usage: kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass]");
|
|
60
49
|
return 1;
|
|
61
50
|
}
|
|
62
51
|
return runAuthCommand([url, ...rest.slice(1)]);
|
|
@@ -78,9 +67,8 @@ Login options:
|
|
|
78
67
|
try {
|
|
79
68
|
const normalizedTarget = normalizeBaseUrl(target);
|
|
80
69
|
const alias = readOption(args, "--alias");
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const usePlaywright = args.includes("--playwright");
|
|
70
|
+
let username = readOption(args, "--username") ?? readOption(args, "-u");
|
|
71
|
+
let password = readOption(args, "--password") ?? readOption(args, "-p");
|
|
84
72
|
const httpSignin = args.includes("--http-signin");
|
|
85
73
|
const oauthProduct = readOption(args, "--oauth-product");
|
|
86
74
|
const signinPublicKeyFile = readOption(args, "--signin-public-key-file");
|
|
@@ -101,7 +89,7 @@ Login options:
|
|
|
101
89
|
"--http-signin",
|
|
102
90
|
"--oauth-product",
|
|
103
91
|
"--signin-public-key-file",
|
|
104
|
-
"--
|
|
92
|
+
"--insecure", "-k", "--no-auth", "--redirect-uri",
|
|
105
93
|
]);
|
|
106
94
|
const KNOWN_VALUE_FLAGS = new Set([
|
|
107
95
|
"--alias", "--client-id", "--client-secret", "--refresh-token",
|
|
@@ -130,30 +118,34 @@ Login options:
|
|
|
130
118
|
if (noAuth && noBrowser) {
|
|
131
119
|
console.error("--no-auth does not require a browser; --no-browser is ignored.");
|
|
132
120
|
}
|
|
133
|
-
if (noAuth && (username || password ||
|
|
134
|
-
console.error("--no-auth cannot be used with
|
|
121
|
+
if (noAuth && (username || password || httpSignin)) {
|
|
122
|
+
console.error("--no-auth cannot be used with HTTP sign-in or -u/-p.");
|
|
135
123
|
return 1;
|
|
136
124
|
}
|
|
137
|
-
if (noBrowser &&
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
if (httpSignin && usePlaywright) {
|
|
142
|
-
console.error("--http-signin cannot be used with --playwright.");
|
|
143
|
-
return 1;
|
|
125
|
+
if (noBrowser && httpSignin) {
|
|
126
|
+
// HTTP sign-in already runs without a browser; --no-browser is a no-op signal here.
|
|
127
|
+
console.error("--http-signin already runs without a browser; --no-browser is redundant and ignored.");
|
|
144
128
|
}
|
|
145
129
|
if (httpSignin && refreshToken) {
|
|
146
130
|
console.error("--http-signin cannot be used with --refresh-token.");
|
|
147
131
|
return 1;
|
|
148
132
|
}
|
|
149
|
-
if (httpSignin && (!username || !password)) {
|
|
150
|
-
console.error("--http-signin requires -u/--username and -p/--password.");
|
|
151
|
-
return 1;
|
|
152
|
-
}
|
|
153
133
|
if (noBrowser && refreshToken) {
|
|
154
134
|
console.error("--no-browser cannot be used with --refresh-token.");
|
|
155
135
|
return 1;
|
|
156
136
|
}
|
|
137
|
+
// Headless credential login: if the user signalled HTTP sign-in (--http-signin,
|
|
138
|
+
// or partial -u/-p, or --no-browser combined with -u/-p) but didn't provide both
|
|
139
|
+
// credentials inline, prompt for the missing one(s) on stderr. Password is read
|
|
140
|
+
// without echo when stdin is a TTY.
|
|
141
|
+
const wantsCredentialLogin = !noAuth && !refreshToken &&
|
|
142
|
+
(httpSignin || (noBrowser && (username || password)) || (!!username !== !!password));
|
|
143
|
+
if (wantsCredentialLogin) {
|
|
144
|
+
if (!username)
|
|
145
|
+
username = await promptForUsername("Username");
|
|
146
|
+
if (!password)
|
|
147
|
+
password = await promptForPassword("Password");
|
|
148
|
+
}
|
|
157
149
|
let token;
|
|
158
150
|
if (noAuth) {
|
|
159
151
|
token = saveNoAuthPlatform(normalizedTarget, { tlsInsecure });
|
|
@@ -182,17 +174,9 @@ Login options:
|
|
|
182
174
|
signinPublicKeyPemPath: signinPublicKeyFile ?? undefined,
|
|
183
175
|
});
|
|
184
176
|
}
|
|
185
|
-
else if (username && password && usePlaywright) {
|
|
186
|
-
console.log("Logging in (headless, Playwright)...");
|
|
187
|
-
token = await playwrightLogin(normalizedTarget, {
|
|
188
|
-
username,
|
|
189
|
-
password,
|
|
190
|
-
tlsInsecure,
|
|
191
|
-
port: customPort,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
177
|
else if (username && password) {
|
|
195
|
-
|
|
178
|
+
console.log("Logging in (HTTP /oauth2/signin)...");
|
|
179
|
+
token = await oauth2PasswordSigninLogin(normalizedTarget, {
|
|
196
180
|
username,
|
|
197
181
|
password,
|
|
198
182
|
tlsInsecure,
|
|
@@ -201,39 +185,6 @@ Login options:
|
|
|
201
185
|
clientSecret: clientSecret ?? undefined,
|
|
202
186
|
oauthProduct: oauthProduct ?? undefined,
|
|
203
187
|
signinPublicKeyPemPath: signinPublicKeyFile ?? undefined,
|
|
204
|
-
};
|
|
205
|
-
console.log("Logging in (HTTP /oauth2/signin)...");
|
|
206
|
-
try {
|
|
207
|
-
token = await oauth2PasswordSigninLogin(normalizedTarget, signinOpts);
|
|
208
|
-
}
|
|
209
|
-
catch (err) {
|
|
210
|
-
if (!isStudiowebShellUnavailableError(err)) {
|
|
211
|
-
throw err;
|
|
212
|
-
}
|
|
213
|
-
const playwrightOk = await isPlaywrightPackageResolvable();
|
|
214
|
-
if (playwrightOk) {
|
|
215
|
-
process.stderr.write("Studio web sign-in shell is not available; falling back to Playwright headless login.\n");
|
|
216
|
-
console.log("Logging in (headless, Playwright)...");
|
|
217
|
-
token = await playwrightLogin(normalizedTarget, {
|
|
218
|
-
username,
|
|
219
|
-
password,
|
|
220
|
-
tlsInsecure,
|
|
221
|
-
port: customPort,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
console.error("Studio web sign-in shell is not available on this platform, and the Playwright package is not installed.");
|
|
226
|
-
console.error("Install Playwright for headless browser login: npm install playwright && npx playwright install chromium");
|
|
227
|
-
console.error("Alternatively, use OAuth without credentials:");
|
|
228
|
-
console.error(` kweaver auth login ${normalizedTarget} --no-browser`);
|
|
229
|
-
throw err;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
else if (usePlaywright) {
|
|
234
|
-
console.log("Opening browser for login (Playwright)...");
|
|
235
|
-
token = await playwrightLogin(normalizedTarget, {
|
|
236
|
-
tlsInsecure, port: customPort,
|
|
237
188
|
});
|
|
238
189
|
}
|
|
239
190
|
else {
|
|
@@ -456,7 +407,7 @@ Login options:
|
|
|
456
407
|
console.log(`Run \`kweaver auth login ${logoutTarget}\` to sign in again.`);
|
|
457
408
|
return 0;
|
|
458
409
|
}
|
|
459
|
-
console.error("Usage: kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass]
|
|
410
|
+
console.error("Usage: kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass]");
|
|
460
411
|
console.error(" kweaver auth whoami [platform-url|alias] [--json]");
|
|
461
412
|
console.error(" kweaver auth export [platform-url|alias] [--json]");
|
|
462
413
|
console.error(" kweaver auth status [platform-url|alias]");
|
package/dist/commands/bkn-ops.js
CHANGED
|
@@ -706,6 +706,7 @@ Options:
|
|
|
706
706
|
--tables <a,b> Tables to include in KN (default: all imported)
|
|
707
707
|
--build (default) Build after creation
|
|
708
708
|
--no-build Skip build
|
|
709
|
+
--recreate Use "insert" mode on first batch (only effective for new tables)
|
|
709
710
|
--timeout <n> Build timeout in seconds (default: 300)
|
|
710
711
|
-bd, --biz-domain Business domain (default: bd_public)`;
|
|
711
712
|
export function parseKnCreateFromCsvArgs(args) {
|
|
@@ -716,6 +717,7 @@ export function parseKnCreateFromCsvArgs(args) {
|
|
|
716
717
|
let batchSize = 500;
|
|
717
718
|
let tablesStr = "";
|
|
718
719
|
let build = true;
|
|
720
|
+
let recreate = false;
|
|
719
721
|
let timeout = 300;
|
|
720
722
|
let businessDomain = "";
|
|
721
723
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -752,6 +754,10 @@ export function parseKnCreateFromCsvArgs(args) {
|
|
|
752
754
|
build = false;
|
|
753
755
|
continue;
|
|
754
756
|
}
|
|
757
|
+
if (arg === "--recreate") {
|
|
758
|
+
recreate = true;
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
755
761
|
if (arg === "--timeout" && args[i + 1]) {
|
|
756
762
|
timeout = parseInt(args[++i], 10);
|
|
757
763
|
if (Number.isNaN(timeout) || timeout < 1)
|
|
@@ -772,7 +778,7 @@ export function parseKnCreateFromCsvArgs(args) {
|
|
|
772
778
|
}
|
|
773
779
|
if (!businessDomain)
|
|
774
780
|
businessDomain = resolveBusinessDomain();
|
|
775
|
-
return { dsId, files, name, tablePrefix, batchSize, tables, build, timeout, businessDomain };
|
|
781
|
+
return { dsId, files, name, tablePrefix, batchSize, tables, build, recreate, timeout, businessDomain };
|
|
776
782
|
}
|
|
777
783
|
export async function runKnCreateFromCsvCommand(args) {
|
|
778
784
|
let options;
|
|
@@ -795,6 +801,7 @@ export async function runKnCreateFromCsvCommand(args) {
|
|
|
795
801
|
"--table-prefix", options.tablePrefix,
|
|
796
802
|
"--batch-size", String(options.batchSize),
|
|
797
803
|
"-bd", options.businessDomain,
|
|
804
|
+
...(options.recreate ? ["--recreate"] : []),
|
|
798
805
|
];
|
|
799
806
|
const importResult = await runDsImportCsv(importArgs);
|
|
800
807
|
if (importResult.code !== 0) {
|
package/dist/commands/call.d.ts
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
|
+
export type FormField = {
|
|
2
|
+
name: string;
|
|
3
|
+
kind: "string";
|
|
4
|
+
value: string;
|
|
5
|
+
} | {
|
|
6
|
+
name: string;
|
|
7
|
+
kind: "file";
|
|
8
|
+
path: string;
|
|
9
|
+
};
|
|
1
10
|
export interface CallInvocation {
|
|
2
11
|
url: string;
|
|
3
12
|
method: string;
|
|
4
13
|
headers: Headers;
|
|
5
14
|
body?: string;
|
|
15
|
+
formFields?: FormField[];
|
|
6
16
|
pretty: boolean;
|
|
7
17
|
verbose: boolean;
|
|
8
18
|
businessDomain: string;
|
package/dist/commands/call.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { basename } from "node:path";
|
|
1
3
|
import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
|
|
2
4
|
import { isNoAuth } from "../config/no-auth.js";
|
|
3
5
|
import { HttpError } from "../utils/http.js";
|
|
@@ -6,6 +8,7 @@ export function parseCallArgs(args) {
|
|
|
6
8
|
const headers = new Headers();
|
|
7
9
|
let method = "GET";
|
|
8
10
|
let body;
|
|
11
|
+
const formFields = [];
|
|
9
12
|
let url;
|
|
10
13
|
let pretty = true;
|
|
11
14
|
let verbose = false;
|
|
@@ -40,6 +43,26 @@ export function parseCallArgs(args) {
|
|
|
40
43
|
index += 1;
|
|
41
44
|
continue;
|
|
42
45
|
}
|
|
46
|
+
if (arg === "-F" || arg === "--form") {
|
|
47
|
+
const raw = args[index + 1];
|
|
48
|
+
if (!raw)
|
|
49
|
+
throw new Error("Missing value for -F flag");
|
|
50
|
+
const eq = raw.indexOf("=");
|
|
51
|
+
if (eq === -1)
|
|
52
|
+
throw new Error(`Invalid -F format: ${raw} (expected key=value or key=@path)`);
|
|
53
|
+
const name = raw.slice(0, eq);
|
|
54
|
+
const rhs = raw.slice(eq + 1);
|
|
55
|
+
if (rhs.startsWith("@")) {
|
|
56
|
+
formFields.push({ name, kind: "file", path: rhs.slice(1) });
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
formFields.push({ name, kind: "string", value: rhs });
|
|
60
|
+
}
|
|
61
|
+
if (method === "GET")
|
|
62
|
+
method = "POST";
|
|
63
|
+
index += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
43
66
|
if (arg === "--pretty") {
|
|
44
67
|
pretty = true;
|
|
45
68
|
continue;
|
|
@@ -70,9 +93,21 @@ export function parseCallArgs(args) {
|
|
|
70
93
|
if (!url) {
|
|
71
94
|
throw new Error("Missing request URL");
|
|
72
95
|
}
|
|
96
|
+
if (formFields.length > 0 && body !== undefined) {
|
|
97
|
+
throw new Error("-F and -d are mutually exclusive");
|
|
98
|
+
}
|
|
73
99
|
if (!businessDomain)
|
|
74
100
|
businessDomain = resolveBusinessDomain();
|
|
75
|
-
return {
|
|
101
|
+
return {
|
|
102
|
+
url,
|
|
103
|
+
method,
|
|
104
|
+
headers,
|
|
105
|
+
body,
|
|
106
|
+
formFields: formFields.length > 0 ? formFields : undefined,
|
|
107
|
+
pretty,
|
|
108
|
+
verbose,
|
|
109
|
+
businessDomain,
|
|
110
|
+
};
|
|
76
111
|
}
|
|
77
112
|
function injectAuthHeaders(headers, accessToken, businessDomain) {
|
|
78
113
|
if (!isNoAuth(accessToken)) {
|
|
@@ -115,12 +150,17 @@ export function formatVerboseRequest(invocation) {
|
|
|
115
150
|
for (const [name, value] of entries) {
|
|
116
151
|
lines.push(` ${name}: ${value}`);
|
|
117
152
|
}
|
|
118
|
-
|
|
153
|
+
const bodyDesc = invocation.formFields && invocation.formFields.length > 0
|
|
154
|
+
? `multipart (${invocation.formFields.length} field${invocation.formFields.length > 1 ? "s" : ""})`
|
|
155
|
+
: invocation.body
|
|
156
|
+
? "present"
|
|
157
|
+
: "empty";
|
|
158
|
+
lines.push(`Body: ${bodyDesc}`);
|
|
119
159
|
return lines;
|
|
120
160
|
}
|
|
121
161
|
export async function runCallCommand(args) {
|
|
122
162
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
123
|
-
console.log(`kweaver call <url> [-X METHOD] [-H "Name: value"] [-d BODY] [--pretty] [--verbose] [-bd value]
|
|
163
|
+
console.log(`kweaver call <url> [-X METHOD] [-H "Name: value"] [-d BODY] [-F key=value] [--pretty] [--verbose] [-bd value]
|
|
124
164
|
|
|
125
165
|
Call an API with curl-style flags and auto-injected token headers.
|
|
126
166
|
|
|
@@ -129,6 +169,7 @@ Options:
|
|
|
129
169
|
-X, --request HTTP method (default: GET)
|
|
130
170
|
-H, --header Extra header (repeatable)
|
|
131
171
|
-d, --data, --data-raw JSON request body (sets Content-Type: application/json if not set)
|
|
172
|
+
-F, --form Multipart form field. -F key=value or -F key=@/path/to/file. Repeatable. Mutually exclusive with -d.
|
|
132
173
|
-bd, --biz-domain Override x-business-domain (default: bd_public)
|
|
133
174
|
-v, --verbose Print request info to stderr
|
|
134
175
|
--pretty Pretty-print JSON output (default)`);
|
|
@@ -150,7 +191,22 @@ Options:
|
|
|
150
191
|
: invocation.url;
|
|
151
192
|
const headers = new Headers(invocation.headers);
|
|
152
193
|
injectAuthHeaders(headers, token.accessToken, invocation.businessDomain);
|
|
153
|
-
|
|
194
|
+
let requestBody = invocation.body;
|
|
195
|
+
if (invocation.formFields && invocation.formFields.length > 0) {
|
|
196
|
+
const form = new FormData();
|
|
197
|
+
for (const field of invocation.formFields) {
|
|
198
|
+
if (field.kind === "string") {
|
|
199
|
+
form.append(field.name, field.value);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const buf = await readFile(field.path);
|
|
203
|
+
form.append(field.name, new Blob([buf]), basename(field.path));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
requestBody = form;
|
|
207
|
+
// do not set content-type — fetch sets multipart boundary
|
|
208
|
+
}
|
|
209
|
+
else if (invocation.body !== undefined &&
|
|
154
210
|
invocation.body.length > 0 &&
|
|
155
211
|
!headers.has("content-type") &&
|
|
156
212
|
!headers.has("Content-Type")) {
|
|
@@ -164,7 +220,7 @@ Options:
|
|
|
164
220
|
const response = await fetch(url, {
|
|
165
221
|
method: invocation.method,
|
|
166
222
|
headers,
|
|
167
|
-
body:
|
|
223
|
+
body: requestBody,
|
|
168
224
|
});
|
|
169
225
|
const rawText = await response.text();
|
|
170
226
|
const text = stripSseDoneMarker(rawText, response.headers.get("content-type"));
|
package/dist/commands/ds.js
CHANGED
|
@@ -466,7 +466,7 @@ export async function runDsImportCsv(args) {
|
|
|
466
466
|
process.stderr.write(`${elapsed}s\n`);
|
|
467
467
|
}
|
|
468
468
|
catch (err) {
|
|
469
|
-
const msg =
|
|
469
|
+
const msg = formatHttpError(err);
|
|
470
470
|
process.stderr.write(`FAILED\n`);
|
|
471
471
|
console.error(`[${tableName}] batch ${batchLabel} error: ${msg}`);
|
|
472
472
|
batchFailed = true;
|
|
@@ -19,7 +19,7 @@ export interface DagBodyOptions {
|
|
|
19
19
|
tableExist: boolean;
|
|
20
20
|
data: Array<Record<string, string | null>>;
|
|
21
21
|
fieldMappings: FieldMapping[];
|
|
22
|
-
/** When true on the first batch (`tableExist` false), use
|
|
22
|
+
/** When true on the first batch (`tableExist` false), use "insert" to force table recreation. */
|
|
23
23
|
recreate?: boolean;
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
@@ -82,7 +82,9 @@ export function buildFieldMappings(headers) {
|
|
|
82
82
|
export function buildDagBody(options) {
|
|
83
83
|
const { datasourceId, datasourceType, tableName, tableExist, data, fieldMappings, recreate } = options;
|
|
84
84
|
const ts = Date.now();
|
|
85
|
-
|
|
85
|
+
// "insert" creates/replaces the table; "append" adds rows to an existing table.
|
|
86
|
+
// With --recreate, use "insert" on first batch to force table recreation when schema changed.
|
|
87
|
+
const operateType = tableExist ? "append" : recreate ? "insert" : "append";
|
|
86
88
|
const triggerStep = {
|
|
87
89
|
id: "step-trigger",
|
|
88
90
|
title: "Trigger",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function runToolCommand(args: string[]): Promise<number>;
|
|
2
|
+
export interface ToolUploadOptions {
|
|
3
|
+
boxId: string;
|
|
4
|
+
filePath: string;
|
|
5
|
+
metadataType: "openapi";
|
|
6
|
+
businessDomain: string;
|
|
7
|
+
pretty: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseToolUploadArgs(args: string[]): ToolUploadOptions;
|
|
10
|
+
export interface ToolStatusOptions {
|
|
11
|
+
boxId: string;
|
|
12
|
+
toolIds: string[];
|
|
13
|
+
status: "enabled" | "disabled";
|
|
14
|
+
businessDomain: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function parseToolStatusArgs(args: string[], status: "enabled" | "disabled"): ToolStatusOptions;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
|
|
3
|
+
import { listTools, setToolStatuses, uploadTool } from "../api/toolboxes.js";
|
|
4
|
+
import { formatCallOutput } from "./call.js";
|
|
5
|
+
import { resolveBusinessDomain } from "../config/store.js";
|
|
6
|
+
const HELP = `kweaver tool
|
|
7
|
+
|
|
8
|
+
Subcommands:
|
|
9
|
+
upload --toolbox <box-id> <openapi-spec-path> [--metadata-type openapi]
|
|
10
|
+
Upload an OpenAPI spec file as a tool
|
|
11
|
+
list --toolbox <box-id> List tools in a toolbox
|
|
12
|
+
enable --toolbox <box-id> <tool-id>... Enable one or more tools
|
|
13
|
+
disable --toolbox <box-id> <tool-id>... Disable one or more tools
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
-bd, --biz-domain <s> Business domain (default: bd_public)
|
|
17
|
+
--pretty Pretty-print JSON (default)
|
|
18
|
+
--compact Single-line JSON (pipeline-friendly)`;
|
|
19
|
+
export async function runToolCommand(args) {
|
|
20
|
+
const [subcommand, ...rest] = args;
|
|
21
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
22
|
+
console.log(HELP);
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
const dispatch = () => {
|
|
26
|
+
if (subcommand === "upload")
|
|
27
|
+
return runToolUpload(rest);
|
|
28
|
+
if (subcommand === "list")
|
|
29
|
+
return runToolList(rest);
|
|
30
|
+
if (subcommand === "enable")
|
|
31
|
+
return runToolStatus(rest, "enabled");
|
|
32
|
+
if (subcommand === "disable")
|
|
33
|
+
return runToolStatus(rest, "disabled");
|
|
34
|
+
return Promise.resolve(-1);
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
return await with401RefreshRetry(async () => {
|
|
38
|
+
const code = await dispatch();
|
|
39
|
+
if (code === -1) {
|
|
40
|
+
console.error(`Unknown tool subcommand: ${subcommand}`);
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
return code;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error(formatHttpError(error));
|
|
48
|
+
return 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function parseToolUploadArgs(args) {
|
|
52
|
+
let boxId = "";
|
|
53
|
+
let filePath = "";
|
|
54
|
+
let metadataType = "openapi";
|
|
55
|
+
let businessDomain = "";
|
|
56
|
+
let pretty = true;
|
|
57
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
58
|
+
const a = args[i];
|
|
59
|
+
if (a === "--toolbox" && args[i + 1]) {
|
|
60
|
+
boxId = args[++i];
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (a === "--metadata-type" && args[i + 1]) {
|
|
64
|
+
const val = args[++i];
|
|
65
|
+
if (val !== "openapi") {
|
|
66
|
+
throw new Error(`Unsupported --metadata-type: ${val} (only "openapi" is supported)`);
|
|
67
|
+
}
|
|
68
|
+
metadataType = val;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
|
|
72
|
+
businessDomain = args[++i];
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (a === "--pretty") {
|
|
76
|
+
pretty = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (a === "--compact") {
|
|
80
|
+
pretty = false;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (!a.startsWith("-") && !filePath) {
|
|
84
|
+
filePath = a;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!boxId)
|
|
89
|
+
throw new Error("Missing required flag: --toolbox");
|
|
90
|
+
if (!filePath)
|
|
91
|
+
throw new Error("Missing required positional argument: <file-path>");
|
|
92
|
+
if (!businessDomain)
|
|
93
|
+
businessDomain = resolveBusinessDomain();
|
|
94
|
+
return { boxId, filePath, metadataType, businessDomain, pretty };
|
|
95
|
+
}
|
|
96
|
+
async function runToolUpload(args) {
|
|
97
|
+
let opts;
|
|
98
|
+
try {
|
|
99
|
+
opts = parseToolUploadArgs(args);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
await access(opts.filePath);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
console.error(`File not found: ${opts.filePath}`);
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
const token = await ensureValidToken();
|
|
113
|
+
const body = await uploadTool({
|
|
114
|
+
baseUrl: token.baseUrl,
|
|
115
|
+
accessToken: token.accessToken,
|
|
116
|
+
businessDomain: opts.businessDomain,
|
|
117
|
+
boxId: opts.boxId,
|
|
118
|
+
filePath: opts.filePath,
|
|
119
|
+
metadataType: opts.metadataType,
|
|
120
|
+
});
|
|
121
|
+
console.log(formatCallOutput(body, opts.pretty));
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
// ── list ──────────────────────────────────────────────────────────────────────
|
|
125
|
+
async function runToolList(args) {
|
|
126
|
+
let boxId = "";
|
|
127
|
+
let businessDomain = "";
|
|
128
|
+
let pretty = true;
|
|
129
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
130
|
+
const a = args[i];
|
|
131
|
+
if (a === "--toolbox" && args[i + 1]) {
|
|
132
|
+
boxId = args[++i];
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
|
|
136
|
+
businessDomain = args[++i];
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (a === "--pretty") {
|
|
140
|
+
pretty = true;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (a === "--compact") {
|
|
144
|
+
pretty = false;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (!boxId) {
|
|
149
|
+
console.error("Missing required flag: --toolbox");
|
|
150
|
+
return 1;
|
|
151
|
+
}
|
|
152
|
+
if (!businessDomain)
|
|
153
|
+
businessDomain = resolveBusinessDomain();
|
|
154
|
+
const token = await ensureValidToken();
|
|
155
|
+
const body = await listTools({
|
|
156
|
+
baseUrl: token.baseUrl,
|
|
157
|
+
accessToken: token.accessToken,
|
|
158
|
+
businessDomain,
|
|
159
|
+
boxId,
|
|
160
|
+
});
|
|
161
|
+
console.log(formatCallOutput(body, pretty));
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
export function parseToolStatusArgs(args, status) {
|
|
165
|
+
let boxId = "";
|
|
166
|
+
let businessDomain = "";
|
|
167
|
+
const toolIds = [];
|
|
168
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
169
|
+
const a = args[i];
|
|
170
|
+
if (a === "--toolbox" && args[i + 1]) {
|
|
171
|
+
boxId = args[++i];
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
|
|
175
|
+
businessDomain = args[++i];
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (!a.startsWith("-"))
|
|
179
|
+
toolIds.push(a);
|
|
180
|
+
}
|
|
181
|
+
if (!boxId)
|
|
182
|
+
throw new Error("Missing required flag: --toolbox");
|
|
183
|
+
if (toolIds.length === 0)
|
|
184
|
+
throw new Error("Missing tool id(s)");
|
|
185
|
+
if (!businessDomain)
|
|
186
|
+
businessDomain = resolveBusinessDomain();
|
|
187
|
+
return { boxId, toolIds, status, businessDomain };
|
|
188
|
+
}
|
|
189
|
+
async function runToolStatus(args, status) {
|
|
190
|
+
let opts;
|
|
191
|
+
try {
|
|
192
|
+
opts = parseToolStatusArgs(args, status);
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
const token = await ensureValidToken();
|
|
199
|
+
await setToolStatuses({
|
|
200
|
+
baseUrl: token.baseUrl,
|
|
201
|
+
accessToken: token.accessToken,
|
|
202
|
+
businessDomain: opts.businessDomain,
|
|
203
|
+
boxId: opts.boxId,
|
|
204
|
+
updates: opts.toolIds.map((toolId) => ({ toolId, status: opts.status })),
|
|
205
|
+
});
|
|
206
|
+
console.error(`${status === "enabled" ? "Enabled" : "Disabled"} ${opts.toolIds.length} tool(s) in toolbox ${opts.boxId}`);
|
|
207
|
+
return 0;
|
|
208
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function runToolboxCommand(args: string[]): Promise<number>;
|
|
2
|
+
export interface ToolboxCreateOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
serviceUrl: string;
|
|
5
|
+
description: string;
|
|
6
|
+
businessDomain: string;
|
|
7
|
+
pretty: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseToolboxCreateArgs(args: string[]): ToolboxCreateOptions;
|
|
10
|
+
export interface ToolboxSetStatusOptions {
|
|
11
|
+
boxId: string;
|
|
12
|
+
businessDomain: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function parseToolboxSetStatusArgs(args: string[]): ToolboxSetStatusOptions;
|