@kweaver-ai/kweaver-sdk 0.5.2 → 0.6.1
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 +19 -1
- package/README.zh.md +19 -1
- package/dist/api/agent-chat.d.ts +7 -1
- package/dist/api/agent-chat.js +146 -40
- package/dist/api/agent-list.js +13 -13
- package/dist/api/business-domains.js +9 -5
- package/dist/api/context-loader.js +4 -1
- package/dist/api/conversations.js +4 -9
- package/dist/api/dataflow2.d.ts +95 -0
- package/dist/api/dataflow2.js +80 -0
- package/dist/api/headers.d.ts +2 -0
- package/dist/api/headers.js +7 -2
- package/dist/api/skills.js +2 -10
- package/dist/api/vega.d.ts +0 -16
- package/dist/api/vega.js +0 -33
- package/dist/auth/oauth.d.ts +7 -6
- package/dist/auth/oauth.js +170 -80
- package/dist/cli.js +21 -1
- package/dist/client.d.ts +9 -0
- package/dist/client.js +48 -8
- package/dist/commands/auth.js +103 -42
- package/dist/commands/bkn-schema.js +22 -0
- package/dist/commands/call.js +8 -5
- package/dist/commands/dataflow.d.ts +1 -0
- package/dist/commands/dataflow.js +251 -0
- package/dist/commands/explore-bkn.d.ts +79 -0
- package/dist/commands/explore-bkn.js +273 -0
- package/dist/commands/explore-chat.d.ts +3 -0
- package/dist/commands/explore-chat.js +193 -0
- package/dist/commands/explore-vega.d.ts +3 -0
- package/dist/commands/explore-vega.js +71 -0
- package/dist/commands/explore.d.ts +9 -0
- package/dist/commands/explore.js +258 -0
- package/dist/commands/vega.js +2 -104
- package/dist/config/no-auth.d.ts +3 -0
- package/dist/config/no-auth.js +5 -0
- package/dist/config/store.d.ts +8 -0
- package/dist/config/store.js +22 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/kweaver.d.ts +5 -0
- package/dist/kweaver.js +32 -2
- package/dist/resources/bkn.js +2 -3
- package/dist/resources/knowledge-networks.js +3 -8
- package/dist/resources/vega.d.ts +0 -6
- package/dist/resources/vega.js +1 -10
- package/dist/templates/explorer/app.js +136 -0
- package/dist/templates/explorer/bkn.js +747 -0
- package/dist/templates/explorer/chat.js +980 -0
- package/dist/templates/explorer/dashboard.js +82 -0
- package/dist/templates/explorer/index.html +35 -0
- package/dist/templates/explorer/style.css +2440 -0
- package/dist/templates/explorer/vega.js +291 -0
- package/dist/utils/browser.js +33 -10
- package/dist/utils/http.d.ts +3 -0
- package/dist/utils/http.js +37 -1
- package/package.json +9 -5
package/dist/commands/auth.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isNoAuth } from "../config/no-auth.js";
|
|
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";
|
|
2
3
|
import { decodeJwtPayload } from "../config/jwt.js";
|
|
3
4
|
import { buildCopyCommand, formatHttpError, normalizeBaseUrl, oauth2Login, playwrightLogin, refreshTokenLogin, } from "../auth/oauth.js";
|
|
4
5
|
export async function runAuthCommand(args) {
|
|
@@ -27,18 +28,18 @@ Login options:
|
|
|
27
28
|
Requires --client-id and --client-secret.
|
|
28
29
|
Get these from the callback page after browser login or \`auth export\`.
|
|
29
30
|
--port <n> Local callback port (default: 9010). Use when 9010 is occupied.
|
|
30
|
-
--
|
|
31
|
-
|
|
32
|
-
Overrides --port. Example: http://127.0.0.1:8080/callback
|
|
31
|
+
--no-browser Do not open a browser; print the auth URL and prompt for the callback URL or code (stdin).
|
|
32
|
+
Use on headless servers or when automatic browser launch fails.
|
|
33
33
|
-u, --username Username (with -p triggers Playwright headless login)
|
|
34
34
|
-p, --password Password
|
|
35
35
|
--playwright Force Playwright browser login even without -u/-p
|
|
36
|
-
--insecure, -k Skip TLS certificate verification (self-signed / dev HTTPS only)
|
|
36
|
+
--insecure, -k Skip TLS certificate verification (self-signed / dev HTTPS only)
|
|
37
|
+
--no-auth Save platform without OAuth (servers with no authentication). Same as detecting OAuth 404 during login.`);
|
|
37
38
|
return 0;
|
|
38
39
|
}
|
|
39
40
|
if (target === "login") {
|
|
40
41
|
if (rest[0] === "--help" || rest[0] === "-h") {
|
|
41
|
-
console.log(`kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
|
|
42
|
+
console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
|
|
42
43
|
return 0;
|
|
43
44
|
}
|
|
44
45
|
const url = rest[0];
|
|
@@ -71,16 +72,61 @@ Login options:
|
|
|
71
72
|
const clientId = readOption(args, "--client-id");
|
|
72
73
|
const clientSecret = readOption(args, "--client-secret");
|
|
73
74
|
const refreshToken = readOption(args, "--refresh-token");
|
|
74
|
-
const customRedirectUri = readOption(args, "--redirect-uri");
|
|
75
75
|
const customPortStr = readOption(args, "--port");
|
|
76
76
|
const customPort = customPortStr ? parseInt(customPortStr, 10) : undefined;
|
|
77
77
|
const tlsInsecure = args.includes("--insecure") || args.includes("-k");
|
|
78
|
+
const noAuth = args.includes("--no-auth");
|
|
79
|
+
const noBrowser = args.includes("--no-browser");
|
|
80
|
+
if (args.includes("--redirect-uri")) {
|
|
81
|
+
console.error("Warning: --redirect-uri is deprecated and ignored. The redirect URI is always http://127.0.0.1:<port>/callback.");
|
|
82
|
+
}
|
|
83
|
+
const KNOWN_LOGIN_FLAGS = new Set([
|
|
84
|
+
"--alias", "--client-id", "--client-secret", "--refresh-token",
|
|
85
|
+
"--port", "--no-browser", "--username", "-u", "--password", "-p",
|
|
86
|
+
"--playwright", "--insecure", "-k", "--no-auth", "--redirect-uri",
|
|
87
|
+
]);
|
|
88
|
+
const KNOWN_VALUE_FLAGS = new Set([
|
|
89
|
+
"--alias", "--client-id", "--client-secret", "--refresh-token",
|
|
90
|
+
"--port", "--username", "-u", "--password", "-p", "--redirect-uri",
|
|
91
|
+
]);
|
|
92
|
+
for (let i = 0; i < args.length; i++) {
|
|
93
|
+
const a = args[i];
|
|
94
|
+
if (a.startsWith("-") && a !== target && !KNOWN_LOGIN_FLAGS.has(a)) {
|
|
95
|
+
console.error(`Unknown option: ${a}`);
|
|
96
|
+
console.error("Run 'kweaver auth --help' to see available options.");
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
if (KNOWN_VALUE_FLAGS.has(a))
|
|
100
|
+
i++;
|
|
101
|
+
}
|
|
78
102
|
if (customPort !== undefined && (Number.isNaN(customPort) || customPort < 1 || customPort > 65535)) {
|
|
79
103
|
console.error("Invalid --port value. Expected a number between 1 and 65535.");
|
|
80
104
|
return 1;
|
|
81
105
|
}
|
|
106
|
+
if (noAuth && refreshToken) {
|
|
107
|
+
console.error("--no-auth cannot be used with --refresh-token.");
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
if (noAuth && noBrowser) {
|
|
111
|
+
console.error("--no-auth does not require a browser; --no-browser is ignored.");
|
|
112
|
+
}
|
|
113
|
+
if (noAuth && (username || password || usePlaywright)) {
|
|
114
|
+
console.error("--no-auth cannot be used with Playwright login or -u/-p.");
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
if (noBrowser && (username || password || usePlaywright)) {
|
|
118
|
+
console.error("--no-browser cannot be used with Playwright login or -u/-p.");
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
if (noBrowser && refreshToken) {
|
|
122
|
+
console.error("--no-browser cannot be used with --refresh-token.");
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
82
125
|
let token;
|
|
83
|
-
if (
|
|
126
|
+
if (noAuth) {
|
|
127
|
+
token = saveNoAuthPlatform(normalizedTarget, { tlsInsecure });
|
|
128
|
+
}
|
|
129
|
+
else if (refreshToken) {
|
|
84
130
|
if (!clientId || !clientSecret) {
|
|
85
131
|
console.error("--refresh-token requires --client-id and --client-secret.\n");
|
|
86
132
|
console.error("Get these values from the callback page after a browser login or `kweaver auth export`.");
|
|
@@ -94,19 +140,20 @@ Login options:
|
|
|
94
140
|
else if (username && password) {
|
|
95
141
|
console.log("Logging in (headless)...");
|
|
96
142
|
token = await playwrightLogin(normalizedTarget, {
|
|
97
|
-
username, password, tlsInsecure,
|
|
98
|
-
port: customPort, redirectUri: customRedirectUri ?? undefined,
|
|
143
|
+
username, password, tlsInsecure, port: customPort,
|
|
99
144
|
});
|
|
100
145
|
}
|
|
101
146
|
else if (usePlaywright) {
|
|
102
147
|
console.log("Opening browser for login (Playwright)...");
|
|
103
148
|
token = await playwrightLogin(normalizedTarget, {
|
|
104
|
-
tlsInsecure,
|
|
105
|
-
port: customPort, redirectUri: customRedirectUri ?? undefined,
|
|
149
|
+
tlsInsecure, port: customPort,
|
|
106
150
|
});
|
|
107
151
|
}
|
|
108
152
|
else {
|
|
109
|
-
if (
|
|
153
|
+
if (noBrowser) {
|
|
154
|
+
console.log("OAuth2 login (no browser — open the URL on any device, then paste the callback URL or code)...");
|
|
155
|
+
}
|
|
156
|
+
else if (clientId) {
|
|
110
157
|
console.log(`Opening browser for OAuth2 login (client: ${clientId})...`);
|
|
111
158
|
}
|
|
112
159
|
else {
|
|
@@ -115,8 +162,7 @@ Login options:
|
|
|
115
162
|
token = await oauth2Login(normalizedTarget, {
|
|
116
163
|
clientId: clientId ?? undefined,
|
|
117
164
|
clientSecret: clientSecret ?? undefined,
|
|
118
|
-
tlsInsecure,
|
|
119
|
-
port: customPort, redirectUri: customRedirectUri ?? undefined,
|
|
165
|
+
tlsInsecure, port: customPort, noBrowser,
|
|
120
166
|
});
|
|
121
167
|
}
|
|
122
168
|
if (alias) {
|
|
@@ -138,19 +184,26 @@ Login options:
|
|
|
138
184
|
const userLabel = token.displayName ? `${token.displayName} (${activeUser})` : activeUser;
|
|
139
185
|
console.log(`User: ${userLabel}`);
|
|
140
186
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
console.log(`Refresh token: yes (auto-refresh enabled)`);
|
|
187
|
+
if (isNoAuth(token.accessToken)) {
|
|
188
|
+
console.log(`Authentication: none (no-auth mode)`);
|
|
144
189
|
}
|
|
145
190
|
else {
|
|
191
|
+
console.log(`Access token saved: yes`);
|
|
192
|
+
}
|
|
193
|
+
if (!isNoAuth(token.accessToken) && token.refreshToken) {
|
|
194
|
+
console.log(`Refresh token: yes (auto-refresh enabled)`);
|
|
195
|
+
}
|
|
196
|
+
else if (!isNoAuth(token.accessToken)) {
|
|
146
197
|
console.log(`Refresh token: no (token will expire in 1 hour)`);
|
|
147
198
|
}
|
|
148
199
|
if (token.expiresAt) {
|
|
149
200
|
console.log(`Token expires at: ${token.expiresAt}`);
|
|
150
201
|
}
|
|
151
|
-
const selectedBd =
|
|
152
|
-
|
|
153
|
-
|
|
202
|
+
const selectedBd = isNoAuth(token.accessToken)
|
|
203
|
+
? resolveBusinessDomain(normalizedTarget)
|
|
204
|
+
: await autoSelectBusinessDomain(normalizedTarget, token.accessToken, {
|
|
205
|
+
tlsInsecure: token.tlsInsecure,
|
|
206
|
+
});
|
|
154
207
|
console.log(`Business domain: ${selectedBd}`);
|
|
155
208
|
return 0;
|
|
156
209
|
}
|
|
@@ -178,29 +231,35 @@ Login options:
|
|
|
178
231
|
`Platform: ${token.baseUrl}`,
|
|
179
232
|
`Current platform: ${token.baseUrl === currentPlatform ? "yes" : "no"}`,
|
|
180
233
|
];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const userLabel = statusDisplayName ? `${statusDisplayName} (${statusActiveUser})` : statusActiveUser;
|
|
185
|
-
lines.push(`User: ${userLabel}`);
|
|
186
|
-
}
|
|
187
|
-
lines.push(`Token present: yes`);
|
|
188
|
-
lines.push(`Refresh token: ${token.refreshToken ? "yes (auto-refresh enabled)" : "no"}`);
|
|
189
|
-
if (token.tlsInsecure) {
|
|
190
|
-
lines.push(`TLS: certificate verification disabled (saved; dev only)`);
|
|
234
|
+
if (isNoAuth(token.accessToken)) {
|
|
235
|
+
lines.push(`Authentication: none (no-auth mode)`);
|
|
236
|
+
lines.push(`User: default (built-in profile for no-auth platforms)`);
|
|
191
237
|
}
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
lines.push(`
|
|
238
|
+
else {
|
|
239
|
+
const statusActiveUser = getActiveUser(platform);
|
|
240
|
+
if (statusActiveUser) {
|
|
241
|
+
const statusDisplayName = token.displayName;
|
|
242
|
+
const userLabel = statusDisplayName ? `${statusDisplayName} (${statusActiveUser})` : statusActiveUser;
|
|
243
|
+
lines.push(`User: ${userLabel}`);
|
|
198
244
|
}
|
|
199
|
-
|
|
200
|
-
|
|
245
|
+
lines.push(`Token present: yes`);
|
|
246
|
+
lines.push(`Refresh token: ${token.refreshToken ? "yes (auto-refresh enabled)" : "no"}`);
|
|
247
|
+
if (token.tlsInsecure) {
|
|
248
|
+
lines.push(`TLS: certificate verification disabled (saved; dev only)`);
|
|
201
249
|
}
|
|
202
|
-
|
|
203
|
-
|
|
250
|
+
if (token.expiresAt) {
|
|
251
|
+
const expiry = new Date(token.expiresAt);
|
|
252
|
+
const remainingMs = expiry.getTime() - Date.now();
|
|
253
|
+
if (remainingMs > 0) {
|
|
254
|
+
const remainingMin = Math.ceil(remainingMs / 60_000);
|
|
255
|
+
lines.push(`Token status: active (expires in ${remainingMin} min)`);
|
|
256
|
+
}
|
|
257
|
+
else if (token.refreshToken) {
|
|
258
|
+
lines.push(`Token status: expired (will auto-refresh on next command)`);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
lines.push(`Token status: expired (run \`kweaver auth login ${token.baseUrl}\` again)`);
|
|
262
|
+
}
|
|
204
263
|
}
|
|
205
264
|
}
|
|
206
265
|
for (const line of lines) {
|
|
@@ -219,7 +278,9 @@ Login options:
|
|
|
219
278
|
for (const platform of platforms) {
|
|
220
279
|
const marker = platform.baseUrl === currentPlatform ? "*" : "-";
|
|
221
280
|
const aliasPart = platform.alias ? ` (${platform.alias})` : "";
|
|
222
|
-
|
|
281
|
+
const tok = loadTokenConfig(platform.baseUrl);
|
|
282
|
+
const noAuthPart = tok && isNoAuth(tok.accessToken) ? " (no-auth)" : "";
|
|
283
|
+
console.log(`${marker} ${platform.baseUrl}${aliasPart}${noAuthPart}`);
|
|
223
284
|
const profiles = listUserProfiles(platform.baseUrl);
|
|
224
285
|
const activeUser = getActiveUser(platform.baseUrl);
|
|
225
286
|
for (let i = 0; i < profiles.length; i++) {
|
|
@@ -109,6 +109,28 @@ export function parseKnObjectTypeQueryArgs(args) {
|
|
|
109
109
|
throw new Error("Usage: kweaver bkn object-type query <kn-id> <ot-id> ['<json>'] [--limit <n>] [--search-after '<json-array>'] [--pretty] [-bd value]");
|
|
110
110
|
}
|
|
111
111
|
const body = parseJsonObject(bodyText, "object-type query body must be a JSON object.");
|
|
112
|
+
// Detect likely misplaced filter fields in query body (#49)
|
|
113
|
+
// Instead of a brittle whitelist, detect the pattern: no "condition" key present,
|
|
114
|
+
// but there are keys with primitive values (string/number/boolean) — these are
|
|
115
|
+
// almost certainly field=value filters that belong inside a condition structure.
|
|
116
|
+
if (!("condition" in body)) {
|
|
117
|
+
const suspectKeys = Object.keys(body).filter((k) => {
|
|
118
|
+
const v = body[k];
|
|
119
|
+
return typeof v === "string" || typeof v === "number" || typeof v === "boolean";
|
|
120
|
+
});
|
|
121
|
+
// Exclude keys that are well-known query parameters with primitive values
|
|
122
|
+
const PRIMITIVE_QUERY_KEYS = new Set(["limit"]);
|
|
123
|
+
const misplacedKeys = suspectKeys.filter((k) => !PRIMITIVE_QUERY_KEYS.has(k));
|
|
124
|
+
if (misplacedKeys.length > 0) {
|
|
125
|
+
const keyList = misplacedKeys.map((k) => `"${k}"`).join(", ");
|
|
126
|
+
const hint = misplacedKeys.length === 1
|
|
127
|
+
? `Example: {"limit":20,"condition":{"field":${JSON.stringify(misplacedKeys[0])},"operation":"==","value":"<your-value>"}}`
|
|
128
|
+
: `Example: {"limit":20,"condition":{"operation":"and","sub_conditions":[${misplacedKeys.map((k) => `{"field":${JSON.stringify(k)},"operation":"==","value":"<value>"}`).join(",")}]}}`;
|
|
129
|
+
throw new Error(`Likely misplaced filter field(s) ${keyList} in query body.\n` +
|
|
130
|
+
`Filter conditions must be wrapped in a "condition" structure.\n` +
|
|
131
|
+
hint);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
112
134
|
if (limit !== undefined) {
|
|
113
135
|
body.limit = limit;
|
|
114
136
|
}
|
package/dist/commands/call.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
|
|
2
|
+
import { isNoAuth } from "../config/no-auth.js";
|
|
2
3
|
import { HttpError } from "../utils/http.js";
|
|
3
4
|
import { resolveBusinessDomain } from "../config/store.js";
|
|
4
5
|
export function parseCallArgs(args) {
|
|
@@ -74,11 +75,13 @@ export function parseCallArgs(args) {
|
|
|
74
75
|
return { url, method, headers, body, pretty, verbose, businessDomain };
|
|
75
76
|
}
|
|
76
77
|
function injectAuthHeaders(headers, accessToken, businessDomain) {
|
|
77
|
-
if (!
|
|
78
|
-
headers.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
headers.
|
|
78
|
+
if (!isNoAuth(accessToken)) {
|
|
79
|
+
if (!headers.has("authorization")) {
|
|
80
|
+
headers.set("authorization", `Bearer ${accessToken}`);
|
|
81
|
+
}
|
|
82
|
+
if (!headers.has("token")) {
|
|
83
|
+
headers.set("token", accessToken);
|
|
84
|
+
}
|
|
82
85
|
}
|
|
83
86
|
if (!headers.has("x-business-domain")) {
|
|
84
87
|
headers.set("x-business-domain", businessDomain);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runDataflowCommand(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { access, readFile } from "node:fs/promises";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import columnify from "columnify";
|
|
4
|
+
import stringWidth from "string-width";
|
|
5
|
+
import yargs from "yargs";
|
|
6
|
+
import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
|
|
7
|
+
import { resolveBusinessDomain } from "../config/store.js";
|
|
8
|
+
import { getDataflowLogsPage, listDataflowRuns, listDataflows, runDataflowWithFile, runDataflowWithRemoteUrl, } from "../api/dataflow2.js";
|
|
9
|
+
function renderTable(rows) {
|
|
10
|
+
if (rows.length === 0)
|
|
11
|
+
return "";
|
|
12
|
+
return columnify(rows, {
|
|
13
|
+
showHeaders: true,
|
|
14
|
+
preserveNewLines: true,
|
|
15
|
+
stringLength: stringWidth,
|
|
16
|
+
headingTransform: (heading) => heading,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function buildListTableRows(items) {
|
|
20
|
+
return items.map((item) => ({
|
|
21
|
+
"ID": item.id,
|
|
22
|
+
"Title": item.title ?? "",
|
|
23
|
+
"Status": item.status ?? "",
|
|
24
|
+
"Trigger": item.trigger ?? "",
|
|
25
|
+
"Creator": item.creator ?? "",
|
|
26
|
+
"Updated At": item.updated_at != null ? String(item.updated_at) : "",
|
|
27
|
+
"Version ID": item.version_id ?? "",
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
function buildRunTableRows(items) {
|
|
31
|
+
return items.map((item) => ({
|
|
32
|
+
"ID": item.id,
|
|
33
|
+
"Status": item.status ?? "",
|
|
34
|
+
"Started At": item.started_at != null ? String(item.started_at) : "",
|
|
35
|
+
"Ended At": item.ended_at != null ? String(item.ended_at) : "",
|
|
36
|
+
"Source Name": item.source?.name != null ? String(item.source.name) : "",
|
|
37
|
+
"Content Type": item.source?.content_type != null ? String(item.source.content_type) : "",
|
|
38
|
+
"Size": item.source?.size != null ? String(item.source.size) : "",
|
|
39
|
+
"Reason": item.reason ?? "",
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
function parseSinceToLocalDayRange(value) {
|
|
43
|
+
// 只支持 YYYY-MM-DD 格式,解析为本地时区的一整天范围
|
|
44
|
+
const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
45
|
+
if (!match)
|
|
46
|
+
return null;
|
|
47
|
+
const year = parseInt(match[1], 10);
|
|
48
|
+
const month = parseInt(match[2], 10) - 1; // Date month is 0-indexed
|
|
49
|
+
const day = parseInt(match[3], 10);
|
|
50
|
+
const start = new Date(year, month, day, 0, 0, 0);
|
|
51
|
+
const end = new Date(year, month, day, 23, 59, 59);
|
|
52
|
+
return {
|
|
53
|
+
startTime: Math.floor(start.getTime() / 1000),
|
|
54
|
+
endTime: Math.floor(end.getTime() / 1000),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function formatDataflowLogSummary(item) {
|
|
58
|
+
const duration = item.metadata?.duration ?? "-";
|
|
59
|
+
return [
|
|
60
|
+
`[${item.id}] ${item.taskId ?? ""} ${item.operator ?? ""}`,
|
|
61
|
+
`Status: ${item.status ?? ""}`,
|
|
62
|
+
`Started At: ${item.started_at ?? ""}`,
|
|
63
|
+
`Updated At: ${item.updated_at ?? ""}`,
|
|
64
|
+
`Duration: ${duration}`
|
|
65
|
+
].join("\n");
|
|
66
|
+
}
|
|
67
|
+
function formatIndentedJsonBlock(label, value) {
|
|
68
|
+
const pretty = JSON.stringify(value ?? {}, null, 4) ?? "{}";
|
|
69
|
+
const indented = pretty
|
|
70
|
+
.split("\n")
|
|
71
|
+
.map((line) => ` ${line}`)
|
|
72
|
+
.join("\n");
|
|
73
|
+
return ` ${label}:\n${indented}`;
|
|
74
|
+
}
|
|
75
|
+
function formatDataflowLogOutput(item, detail) {
|
|
76
|
+
const parts = [formatDataflowLogSummary(item)];
|
|
77
|
+
if (detail) {
|
|
78
|
+
parts.push("");
|
|
79
|
+
parts.push(formatIndentedJsonBlock("input", item.inputs ?? {}));
|
|
80
|
+
parts.push("");
|
|
81
|
+
parts.push(formatIndentedJsonBlock("output", item.outputs ?? {}));
|
|
82
|
+
}
|
|
83
|
+
return parts.join("\n");
|
|
84
|
+
}
|
|
85
|
+
async function requireTokenAndBusinessDomain(businessDomain) {
|
|
86
|
+
const token = await ensureValidToken();
|
|
87
|
+
return {
|
|
88
|
+
baseUrl: token.baseUrl,
|
|
89
|
+
accessToken: token.accessToken,
|
|
90
|
+
businessDomain: businessDomain || resolveBusinessDomain(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export async function runDataflowCommand(args) {
|
|
94
|
+
let exitCode = 0;
|
|
95
|
+
const parser = yargs(args)
|
|
96
|
+
.scriptName("kweaver dataflow")
|
|
97
|
+
.exitProcess(false)
|
|
98
|
+
.help()
|
|
99
|
+
.version(false)
|
|
100
|
+
.strict()
|
|
101
|
+
.fail((message, error) => {
|
|
102
|
+
throw error ?? new Error(message);
|
|
103
|
+
})
|
|
104
|
+
.command("list", "List all dataflows", (command) => command.option("biz-domain", {
|
|
105
|
+
alias: "bd",
|
|
106
|
+
type: "string",
|
|
107
|
+
}), async (argv) => {
|
|
108
|
+
exitCode = await with401RefreshRetry(async () => {
|
|
109
|
+
const base = await requireTokenAndBusinessDomain(argv.bizDomain);
|
|
110
|
+
const body = await listDataflows(base);
|
|
111
|
+
const table = renderTable(buildListTableRows(body.dags));
|
|
112
|
+
if (table) {
|
|
113
|
+
console.log(table);
|
|
114
|
+
}
|
|
115
|
+
return 0;
|
|
116
|
+
});
|
|
117
|
+
})
|
|
118
|
+
.command("run <dagId>", "Trigger one dataflow run", (command) => command
|
|
119
|
+
.positional("dagId", { type: "string" })
|
|
120
|
+
.option("file", { type: "string" })
|
|
121
|
+
.option("url", { type: "string" })
|
|
122
|
+
.option("name", { type: "string" })
|
|
123
|
+
.option("biz-domain", { alias: "bd", type: "string" })
|
|
124
|
+
.check((argv) => {
|
|
125
|
+
const hasFile = typeof argv.file === "string";
|
|
126
|
+
const hasUrl = typeof argv.url === "string";
|
|
127
|
+
if (hasFile === hasUrl) {
|
|
128
|
+
throw new Error("Exactly one of --file or --url is required.");
|
|
129
|
+
}
|
|
130
|
+
if (hasUrl && typeof argv.name !== "string") {
|
|
131
|
+
throw new Error("--url requires --name.");
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}), async (argv) => {
|
|
135
|
+
exitCode = await with401RefreshRetry(async () => {
|
|
136
|
+
const base = await requireTokenAndBusinessDomain(argv.bizDomain);
|
|
137
|
+
if (typeof argv.file === "string") {
|
|
138
|
+
await access(argv.file, constants.R_OK);
|
|
139
|
+
const fileBytes = await readFile(argv.file);
|
|
140
|
+
const fileName = argv.file.split(/[\\/]/).pop() || "upload.bin";
|
|
141
|
+
const body = await runDataflowWithFile({
|
|
142
|
+
...base,
|
|
143
|
+
dagId: argv.dagId,
|
|
144
|
+
fileName,
|
|
145
|
+
fileBytes,
|
|
146
|
+
});
|
|
147
|
+
console.log(body.dag_instance_id);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
const body = await runDataflowWithRemoteUrl({
|
|
151
|
+
...base,
|
|
152
|
+
dagId: argv.dagId,
|
|
153
|
+
url: String(argv.url),
|
|
154
|
+
name: String(argv.name),
|
|
155
|
+
});
|
|
156
|
+
console.log(body.dag_instance_id);
|
|
157
|
+
return 0;
|
|
158
|
+
});
|
|
159
|
+
})
|
|
160
|
+
.command("runs <dagId>", "List run records for one dataflow", (command) => command
|
|
161
|
+
.positional("dagId", { type: "string" })
|
|
162
|
+
.option("since", { type: "string" })
|
|
163
|
+
.option("biz-domain", { alias: "bd", type: "string" }), async (argv) => {
|
|
164
|
+
exitCode = await with401RefreshRetry(async () => {
|
|
165
|
+
const base = await requireTokenAndBusinessDomain(argv.bizDomain);
|
|
166
|
+
const dayRange = typeof argv.since === "string" ? parseSinceToLocalDayRange(argv.since) : null;
|
|
167
|
+
let results = [];
|
|
168
|
+
if (!dayRange) {
|
|
169
|
+
const body = await listDataflowRuns({
|
|
170
|
+
...base,
|
|
171
|
+
dagId: argv.dagId,
|
|
172
|
+
page: 0,
|
|
173
|
+
limit: 20,
|
|
174
|
+
sortBy: "started_at",
|
|
175
|
+
order: "desc",
|
|
176
|
+
});
|
|
177
|
+
results = body.results;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const first = await listDataflowRuns({
|
|
181
|
+
...base,
|
|
182
|
+
dagId: argv.dagId,
|
|
183
|
+
page: 0,
|
|
184
|
+
limit: 20,
|
|
185
|
+
sortBy: "started_at",
|
|
186
|
+
order: "desc",
|
|
187
|
+
startTime: dayRange.startTime,
|
|
188
|
+
endTime: dayRange.endTime,
|
|
189
|
+
});
|
|
190
|
+
results = [...first.results];
|
|
191
|
+
const total = first.total ?? first.results.length;
|
|
192
|
+
for (let page = 1; page * 20 < total; page += 1) {
|
|
193
|
+
const next = await listDataflowRuns({
|
|
194
|
+
...base,
|
|
195
|
+
dagId: argv.dagId,
|
|
196
|
+
page,
|
|
197
|
+
limit: 20,
|
|
198
|
+
sortBy: "started_at",
|
|
199
|
+
order: "desc",
|
|
200
|
+
startTime: dayRange.startTime,
|
|
201
|
+
endTime: dayRange.endTime,
|
|
202
|
+
});
|
|
203
|
+
results = results.concat(next.results);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const table = renderTable(buildRunTableRows(results));
|
|
207
|
+
if (table) {
|
|
208
|
+
console.log(table);
|
|
209
|
+
}
|
|
210
|
+
return 0;
|
|
211
|
+
});
|
|
212
|
+
})
|
|
213
|
+
.command("logs <dagId> <instanceId>", "Show logs for one run in summary or detail mode", (command) => command
|
|
214
|
+
.positional("dagId", { type: "string" })
|
|
215
|
+
.positional("instanceId", { type: "string" })
|
|
216
|
+
.option("detail", { type: "boolean", default: false })
|
|
217
|
+
.option("biz-domain", { alias: "bd", type: "string" }), async (argv) => {
|
|
218
|
+
exitCode = await with401RefreshRetry(async () => {
|
|
219
|
+
const base = await requireTokenAndBusinessDomain(argv.bizDomain);
|
|
220
|
+
let seen = 0;
|
|
221
|
+
for (let page = 0;; page += 1) {
|
|
222
|
+
const body = await getDataflowLogsPage({
|
|
223
|
+
...base,
|
|
224
|
+
dagId: argv.dagId,
|
|
225
|
+
instanceId: argv.instanceId,
|
|
226
|
+
page,
|
|
227
|
+
limit: 100,
|
|
228
|
+
});
|
|
229
|
+
if (body.results.length === 0)
|
|
230
|
+
break;
|
|
231
|
+
for (const item of body.results) {
|
|
232
|
+
console.log(formatDataflowLogOutput(item, argv.detail === true));
|
|
233
|
+
console.log("");
|
|
234
|
+
}
|
|
235
|
+
seen += body.results.length;
|
|
236
|
+
if ((body.total ?? 0) > 0 && seen >= (body.total ?? 0))
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
return 0;
|
|
240
|
+
});
|
|
241
|
+
})
|
|
242
|
+
.demandCommand(1);
|
|
243
|
+
try {
|
|
244
|
+
await parser.parseAsync();
|
|
245
|
+
return exitCode;
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
console.error(formatHttpError(error));
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
export interface ExploreMeta {
|
|
3
|
+
bkn: {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
statistics: {
|
|
8
|
+
object_count: number;
|
|
9
|
+
relation_count: number;
|
|
10
|
+
};
|
|
11
|
+
objectTypes: Array<{
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
displayKey: string;
|
|
15
|
+
propertyCount: number;
|
|
16
|
+
properties: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
type?: string;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
21
|
+
relationTypes: Array<{
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
sourceOtId: string;
|
|
25
|
+
targetOtId: string;
|
|
26
|
+
sourceOtName: string;
|
|
27
|
+
targetOtName: string;
|
|
28
|
+
}>;
|
|
29
|
+
actionTypes: Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
export interface ExploreOt {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
displayKey: string;
|
|
38
|
+
propertyCount: number;
|
|
39
|
+
properties: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
type?: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
export interface ExploreRt {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
sourceOtId: string;
|
|
48
|
+
targetOtId: string;
|
|
49
|
+
sourceOtName: string;
|
|
50
|
+
targetOtName: string;
|
|
51
|
+
}
|
|
52
|
+
export interface ExploreAt {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
}
|
|
56
|
+
export interface ExploreBkn {
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
}
|
|
60
|
+
export interface ExploreStats {
|
|
61
|
+
object_count: number;
|
|
62
|
+
relation_count: number;
|
|
63
|
+
}
|
|
64
|
+
export declare const EXPLORE_BOOTSTRAP_RETRY_DELAY_MS = 300;
|
|
65
|
+
export declare const EXPLORE_BOOTSTRAP_MAX_ATTEMPTS = 2;
|
|
66
|
+
export declare function buildMeta(knRaw: string, otRaw: string, rtRaw: string, atRaw: string): ExploreMeta;
|
|
67
|
+
export declare function isRetryableExploreBootstrapError(error: unknown): boolean;
|
|
68
|
+
export declare function loadExploreMetaWithRetry(token: {
|
|
69
|
+
baseUrl: string;
|
|
70
|
+
accessToken: string;
|
|
71
|
+
}, knId: string, businessDomain: string): Promise<ExploreMeta>;
|
|
72
|
+
export declare function readBody(req: IncomingMessage): Promise<string>;
|
|
73
|
+
export declare function jsonResponse(res: ServerResponse, status: number, data: unknown): void;
|
|
74
|
+
export declare function handleApiError(res: ServerResponse, error: unknown): void;
|
|
75
|
+
export type TokenProvider = () => Promise<{
|
|
76
|
+
baseUrl: string;
|
|
77
|
+
accessToken: string;
|
|
78
|
+
}>;
|
|
79
|
+
export declare function registerBknRoutes(meta: ExploreMeta, getToken: TokenProvider, businessDomain: string): Map<string, (req: IncomingMessage, res: ServerResponse) => void>;
|