@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.
Files changed (57) hide show
  1. package/README.md +19 -1
  2. package/README.zh.md +19 -1
  3. package/dist/api/agent-chat.d.ts +7 -1
  4. package/dist/api/agent-chat.js +146 -40
  5. package/dist/api/agent-list.js +13 -13
  6. package/dist/api/business-domains.js +9 -5
  7. package/dist/api/context-loader.js +4 -1
  8. package/dist/api/conversations.js +4 -9
  9. package/dist/api/dataflow2.d.ts +95 -0
  10. package/dist/api/dataflow2.js +80 -0
  11. package/dist/api/headers.d.ts +2 -0
  12. package/dist/api/headers.js +7 -2
  13. package/dist/api/skills.js +2 -10
  14. package/dist/api/vega.d.ts +0 -16
  15. package/dist/api/vega.js +0 -33
  16. package/dist/auth/oauth.d.ts +7 -6
  17. package/dist/auth/oauth.js +170 -80
  18. package/dist/cli.js +21 -1
  19. package/dist/client.d.ts +9 -0
  20. package/dist/client.js +48 -8
  21. package/dist/commands/auth.js +103 -42
  22. package/dist/commands/bkn-schema.js +22 -0
  23. package/dist/commands/call.js +8 -5
  24. package/dist/commands/dataflow.d.ts +1 -0
  25. package/dist/commands/dataflow.js +251 -0
  26. package/dist/commands/explore-bkn.d.ts +79 -0
  27. package/dist/commands/explore-bkn.js +273 -0
  28. package/dist/commands/explore-chat.d.ts +3 -0
  29. package/dist/commands/explore-chat.js +193 -0
  30. package/dist/commands/explore-vega.d.ts +3 -0
  31. package/dist/commands/explore-vega.js +71 -0
  32. package/dist/commands/explore.d.ts +9 -0
  33. package/dist/commands/explore.js +258 -0
  34. package/dist/commands/vega.js +2 -104
  35. package/dist/config/no-auth.d.ts +3 -0
  36. package/dist/config/no-auth.js +5 -0
  37. package/dist/config/store.d.ts +8 -0
  38. package/dist/config/store.js +22 -0
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.js +1 -1
  41. package/dist/kweaver.d.ts +5 -0
  42. package/dist/kweaver.js +32 -2
  43. package/dist/resources/bkn.js +2 -3
  44. package/dist/resources/knowledge-networks.js +3 -8
  45. package/dist/resources/vega.d.ts +0 -6
  46. package/dist/resources/vega.js +1 -10
  47. package/dist/templates/explorer/app.js +136 -0
  48. package/dist/templates/explorer/bkn.js +747 -0
  49. package/dist/templates/explorer/chat.js +980 -0
  50. package/dist/templates/explorer/dashboard.js +82 -0
  51. package/dist/templates/explorer/index.html +35 -0
  52. package/dist/templates/explorer/style.css +2440 -0
  53. package/dist/templates/explorer/vega.js +291 -0
  54. package/dist/utils/browser.js +33 -10
  55. package/dist/utils/http.d.ts +3 -0
  56. package/dist/utils/http.js +37 -1
  57. package/package.json +9 -5
@@ -1,4 +1,5 @@
1
- import { autoSelectBusinessDomain, clearPlatformSession, deletePlatform, deleteUser, getActiveUser, getConfigDir, getCurrentPlatform, getPlatformAlias, hasPlatform, listPlatforms, listUserProfiles, loadClientConfig, loadTokenConfig, resolvePlatformIdentifier, resolveUserId, setActiveUser, setCurrentPlatform, setPlatformAlias, } from "../config/store.js";
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
- --redirect-uri <uri> Full OAuth2 redirect URI override. Localhost URIs start a local server;
31
- non-localhost URIs use a manual paste-the-callback-URL flow.
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 (refreshToken) {
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 (clientId) {
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
- console.log(`Access token saved: yes`);
142
- if (token.refreshToken) {
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 = await autoSelectBusinessDomain(normalizedTarget, token.accessToken, {
152
- tlsInsecure: token.tlsInsecure,
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
- const statusActiveUser = getActiveUser(platform);
182
- if (statusActiveUser) {
183
- const statusDisplayName = token.displayName;
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
- if (token.expiresAt) {
193
- const expiry = new Date(token.expiresAt);
194
- const remainingMs = expiry.getTime() - Date.now();
195
- if (remainingMs > 0) {
196
- const remainingMin = Math.ceil(remainingMs / 60_000);
197
- lines.push(`Token status: active (expires in ${remainingMin} min)`);
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
- else if (token.refreshToken) {
200
- lines.push(`Token status: expired (will auto-refresh on next command)`);
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
- else {
203
- lines.push(`Token status: expired (run \`kweaver auth login ${token.baseUrl}\` again)`);
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
- console.log(`${marker} ${platform.baseUrl}${aliasPart}`);
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
  }
@@ -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 (!headers.has("authorization")) {
78
- headers.set("authorization", `Bearer ${accessToken}`);
79
- }
80
- if (!headers.has("token")) {
81
- headers.set("token", accessToken);
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>;