@kweaver-ai/kweaver-sdk 0.5.0 → 0.5.2

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 (66) hide show
  1. package/README.md +6 -1
  2. package/README.zh.md +5 -0
  3. package/dist/api/agent-chat.d.ts +1 -1
  4. package/dist/api/agent-chat.js +4 -4
  5. package/dist/api/agent-list.d.ts +35 -0
  6. package/dist/api/agent-list.js +86 -12
  7. package/dist/api/bkn-backend.d.ts +60 -0
  8. package/dist/api/bkn-backend.js +103 -10
  9. package/dist/api/conversations.d.ts +6 -3
  10. package/dist/api/conversations.js +26 -27
  11. package/dist/api/dataflow.js +1 -10
  12. package/dist/api/datasources.js +1 -10
  13. package/dist/api/dataviews.js +1 -10
  14. package/dist/api/headers.d.ts +9 -0
  15. package/dist/api/headers.js +25 -0
  16. package/dist/api/knowledge-networks.d.ts +41 -0
  17. package/dist/api/knowledge-networks.js +69 -22
  18. package/dist/api/ontology-query.d.ts +14 -1
  19. package/dist/api/ontology-query.js +63 -49
  20. package/dist/api/semantic-search.js +2 -12
  21. package/dist/api/skills.d.ts +141 -0
  22. package/dist/api/skills.js +216 -0
  23. package/dist/api/vega.d.ts +63 -0
  24. package/dist/api/vega.js +131 -10
  25. package/dist/auth/oauth.d.ts +5 -1
  26. package/dist/auth/oauth.js +293 -94
  27. package/dist/cli.js +29 -4
  28. package/dist/client.d.ts +3 -0
  29. package/dist/client.js +4 -0
  30. package/dist/commands/agent.d.ts +33 -1
  31. package/dist/commands/agent.js +721 -49
  32. package/dist/commands/auth.js +211 -21
  33. package/dist/commands/bkn-ops.d.ts +77 -0
  34. package/dist/commands/bkn-ops.js +1056 -0
  35. package/dist/commands/bkn-query.d.ts +14 -0
  36. package/dist/commands/bkn-query.js +370 -0
  37. package/dist/commands/bkn-schema.d.ts +135 -0
  38. package/dist/commands/bkn-schema.js +1461 -0
  39. package/dist/commands/bkn-utils.d.ts +36 -0
  40. package/dist/commands/bkn-utils.js +102 -0
  41. package/dist/commands/bkn.d.ts +7 -113
  42. package/dist/commands/bkn.js +175 -2429
  43. package/dist/commands/dataview.d.ts +7 -0
  44. package/dist/commands/dataview.js +38 -2
  45. package/dist/commands/ds.d.ts +1 -0
  46. package/dist/commands/ds.js +8 -1
  47. package/dist/commands/import-csv.d.ts +2 -0
  48. package/dist/commands/import-csv.js +3 -2
  49. package/dist/commands/skill.d.ts +26 -0
  50. package/dist/commands/skill.js +524 -0
  51. package/dist/commands/vega.js +371 -14
  52. package/dist/config/jwt.d.ts +6 -0
  53. package/dist/config/jwt.js +21 -0
  54. package/dist/config/store.d.ts +37 -5
  55. package/dist/config/store.js +363 -30
  56. package/dist/index.d.ts +6 -1
  57. package/dist/index.js +5 -1
  58. package/dist/resources/bkn.d.ts +4 -0
  59. package/dist/resources/bkn.js +4 -0
  60. package/dist/resources/conversations.d.ts +5 -2
  61. package/dist/resources/conversations.js +17 -3
  62. package/dist/resources/skills.d.ts +47 -0
  63. package/dist/resources/skills.js +47 -0
  64. package/dist/resources/vega.d.ts +11 -0
  65. package/dist/resources/vega.js +37 -1
  66. package/package.json +1 -1
@@ -0,0 +1,1056 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { mkdirSync, readdirSync, statSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { loadNetwork, allObjects, allRelations, allActions, generateChecksum, validateNetwork } from "@kweaver-ai/bkn";
5
+ import { prepareBknDirectoryForImport, stripBknEncodingCliArgs, } from "../utils/bkn-encoding.js";
6
+ import { ensureValidToken, formatHttpError } from "../auth/oauth.js";
7
+ import { createKnowledgeNetwork, createObjectTypes, buildKnowledgeNetwork, getBuildStatus, } from "../api/knowledge-networks.js";
8
+ import { listTablesWithColumns, scanMetadata, getDatasource } from "../api/datasources.js";
9
+ import { createDataView, findDataView } from "../api/dataviews.js";
10
+ import { downloadBkn, uploadBkn, listActionSchedules, getActionSchedule, createActionSchedule, updateActionSchedule, setActionScheduleStatus, deleteActionSchedules, listJobs, getJob, getJobTasks, deleteJobs, } from "../api/bkn-backend.js";
11
+ import { formatCallOutput } from "./call.js";
12
+ import { resolveBusinessDomain } from "../config/store.js";
13
+ import { runDsImportCsv } from "./ds.js";
14
+ import { pollWithBackoff, detectPrimaryKey, detectDisplayKey, confirmYes, } from "./bkn-utils.js";
15
+ // ── Build ───────────────────────────────────────────────────────────────────
16
+ const KN_BUILD_HELP = `kweaver bkn build <kn-id> [options]
17
+
18
+ Trigger a full build for a knowledge network.
19
+
20
+ Options:
21
+ --wait (default) Poll until build completes
22
+ --no-wait Return immediately after triggering
23
+ --timeout <seconds> Max wait time when --wait (default: 300)
24
+ -bd, --biz-domain Business domain (default: bd_public)`;
25
+ export function parseKnBuildArgs(args) {
26
+ let knId = "";
27
+ let wait = true;
28
+ let timeout = 300;
29
+ let businessDomain = "";
30
+ for (let i = 0; i < args.length; i += 1) {
31
+ const arg = args[i];
32
+ if (arg === "--help" || arg === "-h")
33
+ throw new Error("help");
34
+ if (arg === "--wait") {
35
+ wait = true;
36
+ continue;
37
+ }
38
+ if (arg === "--no-wait") {
39
+ wait = false;
40
+ continue;
41
+ }
42
+ if (arg === "--timeout" && args[i + 1]) {
43
+ timeout = parseInt(args[i + 1], 10);
44
+ if (Number.isNaN(timeout) || timeout < 1)
45
+ timeout = 300;
46
+ i += 1;
47
+ continue;
48
+ }
49
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
50
+ businessDomain = args[i + 1];
51
+ i += 1;
52
+ continue;
53
+ }
54
+ if (!arg.startsWith("-") && !knId) {
55
+ knId = arg;
56
+ }
57
+ }
58
+ if (!knId) {
59
+ throw new Error("Missing kn-id. Usage: kweaver bkn build <kn-id> [options]");
60
+ }
61
+ if (!businessDomain)
62
+ businessDomain = resolveBusinessDomain();
63
+ return { knId, wait, timeout, businessDomain };
64
+ }
65
+ export async function runKnBuildCommand(args) {
66
+ let options;
67
+ try {
68
+ options = parseKnBuildArgs(args);
69
+ }
70
+ catch (error) {
71
+ if (error instanceof Error && error.message === "help") {
72
+ console.log(KN_BUILD_HELP);
73
+ return 0;
74
+ }
75
+ console.error(formatHttpError(error));
76
+ return 1;
77
+ }
78
+ const TERMINAL_STATES = ["completed", "failed", "success"];
79
+ try {
80
+ const token = await ensureValidToken();
81
+ await buildKnowledgeNetwork({
82
+ baseUrl: token.baseUrl,
83
+ accessToken: token.accessToken,
84
+ knId: options.knId,
85
+ businessDomain: options.businessDomain,
86
+ });
87
+ console.error(`Build started for ${options.knId}`);
88
+ if (!options.wait) {
89
+ console.error("Build triggered (not waiting).");
90
+ return 0;
91
+ }
92
+ console.error("Waiting for build to complete ...");
93
+ let lastBuildState = "running";
94
+ let lastBuildDetail;
95
+ try {
96
+ const { state, detail } = await pollWithBackoff({
97
+ fn: async () => {
98
+ const body = await getBuildStatus({
99
+ baseUrl: token.baseUrl,
100
+ accessToken: token.accessToken,
101
+ knId: options.knId,
102
+ businessDomain: options.businessDomain,
103
+ });
104
+ const parsed = JSON.parse(body);
105
+ const jobs = Array.isArray(parsed) ? parsed : (parsed.entries ?? parsed.data ?? []);
106
+ const job = jobs[0];
107
+ const st = (job?.state ?? "running").toLowerCase();
108
+ const dt = job?.state_detail;
109
+ lastBuildState = st;
110
+ lastBuildDetail = dt;
111
+ if (TERMINAL_STATES.includes(st))
112
+ return { done: true, value: { state: st, detail: dt } };
113
+ return { done: false, value: { state: st } };
114
+ },
115
+ interval: 2000,
116
+ timeout: options.timeout * 1000,
117
+ });
118
+ console.log(state);
119
+ if (detail) {
120
+ console.log(`Detail: ${detail}`);
121
+ }
122
+ return state === "failed" ? 1 : 0;
123
+ }
124
+ catch {
125
+ console.error(`Build did not complete within ${options.timeout}s.`);
126
+ console.error(`Current status: ${lastBuildState}${lastBuildDetail ? ` (${lastBuildDetail})` : ""}`);
127
+ console.error(`Run \`kweaver bkn stats ${options.knId}\` to check progress.`);
128
+ return 1;
129
+ }
130
+ }
131
+ catch (error) {
132
+ console.error(formatHttpError(error));
133
+ return 1;
134
+ }
135
+ }
136
+ // ── Validate ────────────────────────────────────────────────────────────────
137
+ export async function runKnValidateCommand(args) {
138
+ if (args.includes("--help") || args.includes("-h")) {
139
+ console.log("Usage: kweaver bkn validate <directory> [options]\n\n" +
140
+ "Validate a local BKN directory without uploading.\n\n" +
141
+ "Options:\n" +
142
+ " --detect-encoding Detect .bkn encoding and normalize to UTF-8 (default: on)\n" +
143
+ " --no-detect-encoding Require UTF-8 .bkn files\n" +
144
+ " --source-encoding <n> Decode all .bkn with this encoding (e.g. gb18030)");
145
+ return 0;
146
+ }
147
+ let encodingOptions;
148
+ let restArgs;
149
+ try {
150
+ const stripped = stripBknEncodingCliArgs(args);
151
+ encodingOptions = stripped.options;
152
+ restArgs = stripped.rest;
153
+ }
154
+ catch (e) {
155
+ console.error(e instanceof Error ? e.message : String(e));
156
+ return 1;
157
+ }
158
+ const directory = restArgs.find((a) => !a.startsWith("-"));
159
+ if (!directory) {
160
+ console.error("Missing directory. Usage: kweaver bkn validate <directory> [options]");
161
+ return 1;
162
+ }
163
+ const absDir = resolve(directory);
164
+ try {
165
+ const stat = statSync(absDir);
166
+ if (!stat.isDirectory()) {
167
+ console.error(`Not a directory: ${directory}`);
168
+ return 1;
169
+ }
170
+ }
171
+ catch (err) {
172
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
173
+ console.error(`Directory not found: ${directory}`);
174
+ return 1;
175
+ }
176
+ throw err;
177
+ }
178
+ const prepared = prepareBknDirectoryForImport(absDir, encodingOptions);
179
+ try {
180
+ const network = await loadNetwork(prepared.dir);
181
+ const result = validateNetwork(network);
182
+ if (!result.ok) {
183
+ for (const e of result.errors)
184
+ console.error(` - ${e}`);
185
+ console.error(`BKN validation failed: ${result.errors.length} error(s)`);
186
+ return 1;
187
+ }
188
+ const objs = allObjects(network);
189
+ const rels = allRelations(network);
190
+ const acts = allActions(network);
191
+ console.log(`Valid: ${objs.length} object types, ${rels.length} relation types, ${acts.length} action types`);
192
+ return 0;
193
+ }
194
+ catch (error) {
195
+ console.error(`BKN validation failed: ${error instanceof Error ? error.message : String(error)}`);
196
+ return 1;
197
+ }
198
+ finally {
199
+ prepared.cleanup();
200
+ }
201
+ }
202
+ export function parseKnPushArgs(args) {
203
+ const { rest, options: encodingOptions } = stripBknEncodingCliArgs(args);
204
+ let directory = "";
205
+ let branch = "main";
206
+ let businessDomain = "";
207
+ let pretty = true;
208
+ for (let i = 0; i < rest.length; i += 1) {
209
+ const arg = rest[i];
210
+ if (arg === "--help" || arg === "-h") {
211
+ throw new Error("help");
212
+ }
213
+ if (arg === "--branch") {
214
+ branch = args[i + 1] ?? "main";
215
+ if (!branch || branch.startsWith("-")) {
216
+ throw new Error("Missing value for --branch");
217
+ }
218
+ i += 1;
219
+ continue;
220
+ }
221
+ if (arg === "-bd" || arg === "--biz-domain") {
222
+ businessDomain = args[i + 1] ?? "bd_public";
223
+ if (!businessDomain || businessDomain.startsWith("-")) {
224
+ throw new Error("Missing value for biz-domain flag");
225
+ }
226
+ i += 1;
227
+ continue;
228
+ }
229
+ if (arg === "--pretty") {
230
+ pretty = true;
231
+ continue;
232
+ }
233
+ if (!arg.startsWith("-") && !directory) {
234
+ directory = arg;
235
+ continue;
236
+ }
237
+ throw new Error(`Unsupported bkn push argument: ${arg}`);
238
+ }
239
+ if (!directory) {
240
+ throw new Error("Missing directory. Usage: kweaver bkn push <directory> [--branch main] [-bd value]");
241
+ }
242
+ if (!businessDomain)
243
+ businessDomain = resolveBusinessDomain();
244
+ return { directory, branch, businessDomain, pretty, encodingOptions };
245
+ }
246
+ export function parseKnPullArgs(args) {
247
+ let knId = "";
248
+ let directory = "";
249
+ let branch = "main";
250
+ let businessDomain = "";
251
+ for (let i = 0; i < args.length; i += 1) {
252
+ const arg = args[i];
253
+ if (arg === "--help" || arg === "-h") {
254
+ throw new Error("help");
255
+ }
256
+ if (arg === "--branch") {
257
+ branch = args[i + 1] ?? "main";
258
+ if (!branch || branch.startsWith("-")) {
259
+ throw new Error("Missing value for --branch");
260
+ }
261
+ i += 1;
262
+ continue;
263
+ }
264
+ if (arg === "-bd" || arg === "--biz-domain") {
265
+ businessDomain = args[i + 1] ?? "bd_public";
266
+ if (!businessDomain || businessDomain.startsWith("-")) {
267
+ throw new Error("Missing value for biz-domain flag");
268
+ }
269
+ i += 1;
270
+ continue;
271
+ }
272
+ if (!arg.startsWith("-")) {
273
+ if (!knId) {
274
+ knId = arg;
275
+ }
276
+ else if (!directory) {
277
+ directory = arg;
278
+ }
279
+ else {
280
+ throw new Error(`Unexpected positional argument: ${arg}`);
281
+ }
282
+ continue;
283
+ }
284
+ throw new Error(`Unsupported bkn pull argument: ${arg}`);
285
+ }
286
+ if (!knId) {
287
+ throw new Error("Missing kn-id. Usage: kweaver bkn pull <kn-id> [<directory>] [--branch main] [-bd value]");
288
+ }
289
+ if (!businessDomain)
290
+ businessDomain = resolveBusinessDomain();
291
+ return { knId, directory: directory || knId, branch, businessDomain };
292
+ }
293
+ export function packDirectoryToTar(dirPath) {
294
+ const absPath = resolve(dirPath);
295
+ const entries = readdirSync(absPath);
296
+ const args = ["cf", "-", "-C", absPath, ...entries];
297
+ const result = spawnSync("tar", args, {
298
+ encoding: "buffer",
299
+ env: { ...process.env, COPYFILE_DISABLE: "1" },
300
+ });
301
+ if (result.error) {
302
+ if ("code" in result.error && result.error.code === "ENOENT") {
303
+ throw new Error("tar executable not found. On Windows, ensure tar.exe is in PATH " +
304
+ "(ships with Windows 10 1803+) or install GNU tar via Git for Windows / scoop.");
305
+ }
306
+ throw result.error;
307
+ }
308
+ if (result.status !== 0) {
309
+ throw new Error(`tar pack failed: ${result.stderr?.toString() ?? result.status}`);
310
+ }
311
+ return result.stdout;
312
+ }
313
+ export function extractTarToDirectory(tarBuffer, dirPath) {
314
+ const absPath = resolve(dirPath);
315
+ mkdirSync(absPath, { recursive: true });
316
+ const result = spawnSync("tar", ["xf", "-", "-C", absPath], {
317
+ input: tarBuffer,
318
+ });
319
+ if (result.error) {
320
+ if ("code" in result.error && result.error.code === "ENOENT") {
321
+ throw new Error("tar executable not found. On Windows, ensure tar.exe is in PATH " +
322
+ "(ships with Windows 10 1803+) or install GNU tar via Git for Windows / scoop.");
323
+ }
324
+ throw result.error;
325
+ }
326
+ if (result.status !== 0) {
327
+ throw new Error(`tar extract failed: ${result.stderr?.toString() ?? result.status}`);
328
+ }
329
+ }
330
+ const KN_PUSH_HELP = `kweaver bkn push <directory> [options]
331
+
332
+ Pack a BKN directory into a tar and upload to import as a knowledge network.
333
+
334
+ Options:
335
+ --branch <s> Branch name (default: main)
336
+ -bd, --biz-domain Business domain (default: bd_public)
337
+ --pretty Pretty-print JSON output
338
+ --detect-encoding Detect .bkn encoding and normalize to UTF-8 (default: on)
339
+ --no-detect-encoding Do not detect; require UTF-8 .bkn files
340
+ --source-encoding <name> Decode all .bkn files with this encoding (e.g. gb18030); overrides detection`;
341
+ const KN_PULL_HELP = `kweaver bkn pull <kn-id> [<directory>] [options]
342
+
343
+ Download a BKN tar from a knowledge network and extract to a local directory.
344
+
345
+ Options:
346
+ <directory> Output directory (default: <kn-id>)
347
+ --branch <s> Branch name (default: main)
348
+ -bd, --biz-domain Business domain (default: bd_public)`;
349
+ export async function runKnPushCommand(args) {
350
+ let options;
351
+ try {
352
+ options = parseKnPushArgs(args);
353
+ }
354
+ catch (error) {
355
+ if (error instanceof Error && error.message === "help") {
356
+ console.log(KN_PUSH_HELP);
357
+ return 0;
358
+ }
359
+ console.error(formatHttpError(error));
360
+ return 1;
361
+ }
362
+ const absDir = resolve(options.directory);
363
+ try {
364
+ const stat = statSync(absDir);
365
+ if (!stat.isDirectory()) {
366
+ console.error(`Not a directory: ${options.directory}`);
367
+ return 1;
368
+ }
369
+ }
370
+ catch (err) {
371
+ if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
372
+ console.error(`Directory not found: ${options.directory}`);
373
+ return 1;
374
+ }
375
+ throw err;
376
+ }
377
+ const prepared = prepareBknDirectoryForImport(absDir, options.encodingOptions);
378
+ const workDir = prepared.dir;
379
+ try {
380
+ try {
381
+ const network = await loadNetwork(workDir);
382
+ const objs = allObjects(network);
383
+ const rels = allRelations(network);
384
+ const acts = allActions(network);
385
+ console.error(`Validated: ${objs.length} object types, ${rels.length} relation types, ${acts.length} action types`);
386
+ }
387
+ catch (error) {
388
+ console.error(`BKN validation failed: ${error instanceof Error ? error.message : String(error)}`);
389
+ return 1;
390
+ }
391
+ try {
392
+ await generateChecksum(workDir);
393
+ console.error("Checksum generated");
394
+ }
395
+ catch (error) {
396
+ console.error(`Checksum generation failed: ${error instanceof Error ? error.message : String(error)}`);
397
+ return 1;
398
+ }
399
+ try {
400
+ const tarBuffer = packDirectoryToTar(workDir);
401
+ const token = await ensureValidToken();
402
+ const body = await uploadBkn({
403
+ baseUrl: token.baseUrl,
404
+ accessToken: token.accessToken,
405
+ tarBuffer,
406
+ businessDomain: options.businessDomain,
407
+ branch: options.branch,
408
+ });
409
+ console.log(formatCallOutput(body, options.pretty));
410
+ return 0;
411
+ }
412
+ catch (error) {
413
+ console.error(formatHttpError(error));
414
+ return 1;
415
+ }
416
+ }
417
+ finally {
418
+ prepared.cleanup();
419
+ }
420
+ }
421
+ export async function runKnPullCommand(args) {
422
+ let options;
423
+ try {
424
+ options = parseKnPullArgs(args);
425
+ }
426
+ catch (error) {
427
+ if (error instanceof Error && error.message === "help") {
428
+ console.log(KN_PULL_HELP);
429
+ return 0;
430
+ }
431
+ console.error(formatHttpError(error));
432
+ return 1;
433
+ }
434
+ try {
435
+ const token = await ensureValidToken();
436
+ const tarBuffer = await downloadBkn({
437
+ baseUrl: token.baseUrl,
438
+ accessToken: token.accessToken,
439
+ knId: options.knId,
440
+ businessDomain: options.businessDomain,
441
+ branch: options.branch,
442
+ });
443
+ const absDir = resolve(options.directory);
444
+ extractTarToDirectory(tarBuffer, absDir);
445
+ console.log(`Extracted to ${absDir}`);
446
+ return 0;
447
+ }
448
+ catch (error) {
449
+ console.error(formatHttpError(error));
450
+ return 1;
451
+ }
452
+ }
453
+ // ── Create from datasource ──────────────────────────────────────────────────
454
+ const KN_CREATE_FROM_DS_HELP = `kweaver bkn create-from-ds <ds-id> --name X [options]
455
+
456
+ Create a knowledge network from a datasource (dataviews + object types + optional build).
457
+
458
+ Options:
459
+ --name <s> Knowledge network name (required)
460
+ --tables <a,b> Comma-separated table names (default: all)
461
+ --build (default) Build after creation
462
+ --no-build Skip build after creation
463
+ --timeout <n> Build timeout in seconds (default: 300)
464
+ -bd, --biz-domain Business domain (default: bd_public)
465
+ --pretty Pretty-print output (default)`;
466
+ export function parseKnCreateFromDsArgs(args) {
467
+ let dsId = "";
468
+ let name = "";
469
+ let tablesStr = "";
470
+ let build = true;
471
+ let timeout = 300;
472
+ let businessDomain = "";
473
+ let pretty = true;
474
+ for (let i = 0; i < args.length; i += 1) {
475
+ const arg = args[i];
476
+ if (arg === "--help" || arg === "-h")
477
+ throw new Error("help");
478
+ if (arg === "--name" && args[i + 1]) {
479
+ name = args[++i];
480
+ continue;
481
+ }
482
+ if (arg === "--tables" && args[i + 1]) {
483
+ tablesStr = args[++i];
484
+ continue;
485
+ }
486
+ if (arg === "--build") {
487
+ build = true;
488
+ continue;
489
+ }
490
+ if (arg === "--no-build") {
491
+ build = false;
492
+ continue;
493
+ }
494
+ if (arg === "--timeout" && args[i + 1]) {
495
+ timeout = parseInt(args[++i], 10);
496
+ if (Number.isNaN(timeout) || timeout < 1)
497
+ timeout = 300;
498
+ continue;
499
+ }
500
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
501
+ businessDomain = args[++i];
502
+ continue;
503
+ }
504
+ if (arg === "--pretty") {
505
+ pretty = true;
506
+ continue;
507
+ }
508
+ if (!arg.startsWith("-") && !dsId) {
509
+ dsId = arg;
510
+ }
511
+ }
512
+ const tables = tablesStr ? tablesStr.split(",").map((s) => s.trim()).filter(Boolean) : [];
513
+ if (!dsId || !name) {
514
+ throw new Error("Usage: kweaver bkn create-from-ds <ds-id> --name X [options]");
515
+ }
516
+ if (!businessDomain)
517
+ businessDomain = resolveBusinessDomain();
518
+ return { dsId, name, tables, build, timeout, businessDomain, pretty };
519
+ }
520
+ /** Sanitize a table name into a BKN-safe ID (alphanumeric + underscore). */
521
+ function sanitizeBknId(name) {
522
+ return name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/^(\d)/, "_$1");
523
+ }
524
+ /** Generate a BKN ObjectType YAML markdown file for a table. */
525
+ export function generateObjectTypeBkn(tableName, dvId, pk, dk, columns) {
526
+ const safeId = sanitizeBknId(tableName);
527
+ const header = `## ObjectType: ${safeId}\n\n**${tableName}**\n`;
528
+ const dsTable = `### Data Source\n\n| Type | ID | Name |\n|------|-----|------|\n| data_view | ${dvId} | ${tableName} |\n`;
529
+ const dpHeader = `### Data Properties\n\n| Property | Display Name | Type | Primary Key | Display Key |\n|----------|-------------|------|-------------|-------------|\n`;
530
+ const dpRows = columns.map((c) => {
531
+ const isPk = c.name === pk ? "yes" : "no";
532
+ const isDk = c.name === dk ? "yes" : "no";
533
+ return `| ${c.name} | ${c.name} | string | ${isPk} | ${isDk} |`;
534
+ }).join("\n");
535
+ const frontmatter = `---\ntype: object_type\nid: ${safeId}\nname: ${tableName}\n---\n\n`;
536
+ return `${frontmatter}${header}\n${dsTable}\n${dpHeader}${dpRows}\n`;
537
+ }
538
+ export async function runKnCreateFromDsCommand(args, sampleRows) {
539
+ let options;
540
+ try {
541
+ options = parseKnCreateFromDsArgs(args);
542
+ }
543
+ catch (error) {
544
+ if (error instanceof Error && error.message === "help") {
545
+ console.log(KN_CREATE_FROM_DS_HELP);
546
+ return 0;
547
+ }
548
+ console.error(formatHttpError(error));
549
+ return 1;
550
+ }
551
+ try {
552
+ const token = await ensureValidToken();
553
+ const base = {
554
+ baseUrl: token.baseUrl,
555
+ accessToken: token.accessToken,
556
+ businessDomain: options.businessDomain,
557
+ };
558
+ const maxTableListAttempts = 3;
559
+ const tableRetryDelayMs = 4000;
560
+ let allTables = [];
561
+ let targetTables = [];
562
+ for (let attempt = 1; attempt <= maxTableListAttempts; attempt += 1) {
563
+ const tablesBody = await listTablesWithColumns({ ...base, id: options.dsId });
564
+ allTables = JSON.parse(tablesBody);
565
+ targetTables = options.tables.length > 0
566
+ ? allTables.filter((t) => options.tables.includes(t.name))
567
+ : allTables;
568
+ if (targetTables.length > 0)
569
+ break;
570
+ if (attempt < maxTableListAttempts) {
571
+ console.error(`No tables available (attempt ${attempt}/${maxTableListAttempts}); retrying in ${tableRetryDelayMs / 1000}s...`);
572
+ await new Promise((r) => setTimeout(r, tableRetryDelayMs));
573
+ }
574
+ }
575
+ if (targetTables.length === 0) {
576
+ console.error("No tables available");
577
+ return 1;
578
+ }
579
+ // Phase 1: Create DataViews for each table
580
+ console.error(`Creating data views for ${targetTables.length} table(s) ...`);
581
+ const viewMap = {};
582
+ for (const t of targetTables) {
583
+ const found = await findDataView({
584
+ ...base,
585
+ name: t.name,
586
+ datasourceId: options.dsId,
587
+ exact: true,
588
+ wait: true,
589
+ });
590
+ const dvId = found[0]?.id ??
591
+ (await createDataView({
592
+ ...base,
593
+ name: t.name,
594
+ datasourceId: options.dsId,
595
+ table: t.name,
596
+ fields: t.columns.map((c) => ({ name: c.name, type: c.type })),
597
+ }));
598
+ viewMap[t.name] = dvId;
599
+ }
600
+ // Phase 2: Create the KN record
601
+ const knBody = JSON.stringify({
602
+ name: options.name,
603
+ branch: "main",
604
+ base_branch: "",
605
+ });
606
+ const knResponse = await createKnowledgeNetwork({
607
+ ...base,
608
+ body: knBody,
609
+ });
610
+ const knParsed = JSON.parse(knResponse);
611
+ const knItem = Array.isArray(knParsed) ? knParsed[0] : knParsed;
612
+ const knId = String(knItem?.id ?? "");
613
+ console.error(`Knowledge network created: ${knId}`);
614
+ // Phase 3: Create object types via REST API
615
+ console.error(`Creating ${targetTables.length} object type(s) ...`);
616
+ const otResults = [];
617
+ for (const t of targetTables) {
618
+ const pk = detectPrimaryKey(t, sampleRows?.[t.name]);
619
+ const dk = detectDisplayKey(t, pk);
620
+ const uniqueProps = [pk, dk].filter((x, i, a) => a.indexOf(x) === i);
621
+ const entry = {
622
+ branch: "main",
623
+ name: t.name,
624
+ data_source: { type: "data_view", id: viewMap[t.name] },
625
+ primary_keys: [pk],
626
+ display_key: dk,
627
+ data_properties: t.columns.map((c) => ({
628
+ name: c.name,
629
+ display_name: c.name,
630
+ type: "string",
631
+ mapped_field: { name: c.name, type: c.type || "varchar" },
632
+ })),
633
+ };
634
+ const otBody = JSON.stringify({ entries: [entry] });
635
+ const otResponse = await createObjectTypes({
636
+ ...base,
637
+ knId,
638
+ body: otBody,
639
+ });
640
+ const otParsed = JSON.parse(otResponse);
641
+ const otItem = otParsed.entries?.[0];
642
+ otResults.push({
643
+ name: t.name,
644
+ id: otItem?.id ?? "",
645
+ field_count: t.columns.length,
646
+ });
647
+ console.error(` Created: ${t.name} (${t.columns.length} fields, pk=${pk}, dk=${dk})`);
648
+ }
649
+ if (otResults.length === 0) {
650
+ const errorOutput = {
651
+ kn_id: knId,
652
+ kn_name: options.name,
653
+ error: "No object types were created",
654
+ };
655
+ console.log(JSON.stringify(errorOutput, null, options.pretty ? 2 : 0));
656
+ return 1;
657
+ }
658
+ let statusStr = "skipped";
659
+ if (options.build) {
660
+ console.error("Building ...");
661
+ await buildKnowledgeNetwork({ ...base, knId });
662
+ const TERMINAL = ["completed", "failed", "success"];
663
+ try {
664
+ statusStr = await pollWithBackoff({
665
+ fn: async () => {
666
+ const statusBody = await getBuildStatus({ ...base, knId });
667
+ const statusParsed = JSON.parse(statusBody);
668
+ const jobs = Array.isArray(statusParsed) ? statusParsed : (statusParsed.entries ?? []);
669
+ const state = (jobs[0]?.state ?? "running").toLowerCase();
670
+ if (TERMINAL.includes(state))
671
+ return { done: true, value: state };
672
+ return { done: false, value: "running" };
673
+ },
674
+ interval: 2000,
675
+ timeout: options.timeout * 1000,
676
+ });
677
+ }
678
+ catch {
679
+ // timeout — statusStr remains "skipped"
680
+ }
681
+ }
682
+ const output = {
683
+ kn_id: knId,
684
+ kn_name: options.name,
685
+ object_types: otResults,
686
+ status: statusStr,
687
+ };
688
+ console.log(JSON.stringify(output, null, options.pretty ? 2 : 0));
689
+ return 0;
690
+ }
691
+ catch (error) {
692
+ console.error(formatHttpError(error));
693
+ return 1;
694
+ }
695
+ }
696
+ // ── Create from CSV ─────────────────────────────────────────────────────────
697
+ const KN_CREATE_FROM_CSV_HELP = `kweaver bkn create-from-csv <ds-id> --files <glob> --name X [options]
698
+
699
+ Import CSV files into datasource, then create a knowledge network.
700
+
701
+ Options:
702
+ --files <s> CSV file paths (comma-separated or glob, required)
703
+ --name <s> Knowledge network name (required)
704
+ --table-prefix <s> Table name prefix (default: none)
705
+ --batch-size <n> Rows per batch (default: 500)
706
+ --tables <a,b> Tables to include in KN (default: all imported)
707
+ --build (default) Build after creation
708
+ --no-build Skip build
709
+ --timeout <n> Build timeout in seconds (default: 300)
710
+ -bd, --biz-domain Business domain (default: bd_public)`;
711
+ export function parseKnCreateFromCsvArgs(args) {
712
+ let dsId = "";
713
+ let files = "";
714
+ let name = "";
715
+ let tablePrefix = "";
716
+ let batchSize = 500;
717
+ let tablesStr = "";
718
+ let build = true;
719
+ let timeout = 300;
720
+ let businessDomain = "";
721
+ for (let i = 0; i < args.length; i += 1) {
722
+ const arg = args[i];
723
+ if (arg === "--help" || arg === "-h")
724
+ throw new Error("help");
725
+ if (arg === "--files" && args[i + 1]) {
726
+ files = args[++i];
727
+ continue;
728
+ }
729
+ if (arg === "--name" && args[i + 1]) {
730
+ name = args[++i];
731
+ continue;
732
+ }
733
+ if (arg === "--table-prefix" && args[i + 1]) {
734
+ tablePrefix = args[++i];
735
+ continue;
736
+ }
737
+ if (arg === "--batch-size" && args[i + 1]) {
738
+ batchSize = parseInt(args[++i], 10);
739
+ if (Number.isNaN(batchSize) || batchSize < 1)
740
+ batchSize = 500;
741
+ continue;
742
+ }
743
+ if (arg === "--tables" && args[i + 1]) {
744
+ tablesStr = args[++i];
745
+ continue;
746
+ }
747
+ if (arg === "--build") {
748
+ build = true;
749
+ continue;
750
+ }
751
+ if (arg === "--no-build") {
752
+ build = false;
753
+ continue;
754
+ }
755
+ if (arg === "--timeout" && args[i + 1]) {
756
+ timeout = parseInt(args[++i], 10);
757
+ if (Number.isNaN(timeout) || timeout < 1)
758
+ timeout = 300;
759
+ continue;
760
+ }
761
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
762
+ businessDomain = args[++i];
763
+ continue;
764
+ }
765
+ if (!arg.startsWith("-") && !dsId) {
766
+ dsId = arg;
767
+ }
768
+ }
769
+ const tables = tablesStr ? tablesStr.split(",").map((s) => s.trim()).filter(Boolean) : [];
770
+ if (!dsId || !files || !name) {
771
+ throw new Error("Usage: kweaver bkn create-from-csv <ds-id> --files <glob> --name X [options]");
772
+ }
773
+ if (!businessDomain)
774
+ businessDomain = resolveBusinessDomain();
775
+ return { dsId, files, name, tablePrefix, batchSize, tables, build, timeout, businessDomain };
776
+ }
777
+ export async function runKnCreateFromCsvCommand(args) {
778
+ let options;
779
+ try {
780
+ options = parseKnCreateFromCsvArgs(args);
781
+ }
782
+ catch (error) {
783
+ if (error instanceof Error && error.message === "help") {
784
+ console.log(KN_CREATE_FROM_CSV_HELP);
785
+ return 0;
786
+ }
787
+ console.error(formatHttpError(error));
788
+ return 1;
789
+ }
790
+ // Phase 1: Import CSVs
791
+ console.error("Phase 1: Importing CSVs ...");
792
+ const importArgs = [
793
+ options.dsId,
794
+ "--files", options.files,
795
+ "--table-prefix", options.tablePrefix,
796
+ "--batch-size", String(options.batchSize),
797
+ "-bd", options.businessDomain,
798
+ ];
799
+ const importResult = await runDsImportCsv(importArgs);
800
+ if (importResult.code !== 0) {
801
+ console.error("CSV import failed — aborting KN creation");
802
+ return importResult.code;
803
+ }
804
+ // Phase 1.5: Scan datasource metadata so platform discovers newly imported tables
805
+ console.error("Scanning datasource metadata ...");
806
+ try {
807
+ const token = await ensureValidToken();
808
+ const dsBody = await getDatasource({
809
+ baseUrl: token.baseUrl,
810
+ accessToken: token.accessToken,
811
+ id: options.dsId,
812
+ businessDomain: options.businessDomain,
813
+ });
814
+ const dsParsed = JSON.parse(dsBody);
815
+ await scanMetadata({
816
+ baseUrl: token.baseUrl,
817
+ accessToken: token.accessToken,
818
+ id: options.dsId,
819
+ dsType: dsParsed.type ?? "mysql",
820
+ businessDomain: options.businessDomain,
821
+ });
822
+ }
823
+ catch (err) {
824
+ console.error(`Scan warning (continuing): ${String(err)}`);
825
+ }
826
+ // Phase 2: Create KN from datasource
827
+ console.error("Phase 2: Creating knowledge network ...");
828
+ const tableNames = options.tables.length > 0 ? options.tables : importResult.tables;
829
+ if (tableNames.length === 0) {
830
+ console.error("No tables available for KN creation — aborting");
831
+ return 1;
832
+ }
833
+ const knArgs = [
834
+ options.dsId,
835
+ "--name", options.name,
836
+ "--tables", tableNames.join(","),
837
+ options.build ? "--build" : "--no-build",
838
+ "--timeout", String(options.timeout),
839
+ "-bd", options.businessDomain,
840
+ ];
841
+ return runKnCreateFromDsCommand(knArgs, importResult.sampleRows);
842
+ }
843
+ export function parseActionScheduleArgs(args) {
844
+ const [action, ...rest] = args;
845
+ if (!action || action === "--help" || action === "-h")
846
+ throw new Error("help");
847
+ let pretty = true;
848
+ let businessDomain = "";
849
+ let yes = false;
850
+ const positional = [];
851
+ for (let i = 0; i < rest.length; i += 1) {
852
+ const arg = rest[i];
853
+ if (arg === "--help" || arg === "-h")
854
+ throw new Error("help");
855
+ if (arg === "--pretty") {
856
+ pretty = true;
857
+ continue;
858
+ }
859
+ if ((arg === "-bd" || arg === "--biz-domain") && rest[i + 1]) {
860
+ businessDomain = rest[++i];
861
+ continue;
862
+ }
863
+ if (arg === "-y" || arg === "--yes") {
864
+ yes = true;
865
+ continue;
866
+ }
867
+ positional.push(arg);
868
+ }
869
+ const [knId, itemId, extra] = positional;
870
+ if (!knId)
871
+ throw new Error("Missing kn-id. Usage: kweaver bkn action-schedule <action> <kn-id> ...");
872
+ if (!businessDomain)
873
+ businessDomain = resolveBusinessDomain();
874
+ return { action, knId, itemId: itemId || "", body: itemId || "", extra: extra || "", yes, pretty, businessDomain };
875
+ }
876
+ export async function runKnActionScheduleCommand(args) {
877
+ let parsed;
878
+ try {
879
+ parsed = parseActionScheduleArgs(args);
880
+ }
881
+ catch (error) {
882
+ if (error instanceof Error && error.message === "help") {
883
+ console.log(`kweaver bkn action-schedule <action> <kn-id> [args] [--pretty] [-bd value]
884
+
885
+ Actions:
886
+ list <kn-id> List action schedules
887
+ get <kn-id> <schedule-id> Get schedule details
888
+ create <kn-id> '<json>' Create schedule
889
+ update <kn-id> <schedule-id> '<json>' Update schedule
890
+ set-status <kn-id> <schedule-id> <status> Enable/disable schedule (enabled|disabled)
891
+ delete <kn-id> <schedule-ids> [-y] Delete schedule(s) (comma-separated)`);
892
+ return 0;
893
+ }
894
+ console.error(formatHttpError(error));
895
+ return 1;
896
+ }
897
+ const { action, knId, itemId, body, extra, yes, pretty, businessDomain } = parsed;
898
+ const token = await ensureValidToken();
899
+ const base = { baseUrl: token.baseUrl, accessToken: token.accessToken, businessDomain };
900
+ if (action === "list") {
901
+ const result = await listActionSchedules({ ...base, knId });
902
+ console.log(formatCallOutput(result, pretty));
903
+ return 0;
904
+ }
905
+ if (action === "get") {
906
+ if (!itemId) {
907
+ console.error("Missing schedule-id");
908
+ return 1;
909
+ }
910
+ const result = await getActionSchedule({ ...base, knId, scheduleId: itemId });
911
+ console.log(formatCallOutput(result, pretty));
912
+ return 0;
913
+ }
914
+ if (action === "create") {
915
+ if (!itemId) {
916
+ console.error("Missing JSON body");
917
+ return 1;
918
+ }
919
+ const result = await createActionSchedule({ ...base, knId, body });
920
+ console.log(formatCallOutput(result, pretty));
921
+ return 0;
922
+ }
923
+ if (action === "update") {
924
+ if (!itemId || !extra) {
925
+ console.error("Missing schedule-id or JSON body");
926
+ return 1;
927
+ }
928
+ const result = await updateActionSchedule({ ...base, knId, scheduleId: itemId, body: extra });
929
+ console.log(formatCallOutput(result, pretty));
930
+ return 0;
931
+ }
932
+ if (action === "set-status") {
933
+ if (!itemId || !extra) {
934
+ console.error("Missing schedule-id or status");
935
+ return 1;
936
+ }
937
+ const result = await setActionScheduleStatus({ ...base, knId, scheduleId: itemId, body: JSON.stringify({ status: extra }) });
938
+ console.log(formatCallOutput(result, pretty));
939
+ return 0;
940
+ }
941
+ if (action === "delete") {
942
+ if (!itemId) {
943
+ console.error("Missing schedule-ids");
944
+ return 1;
945
+ }
946
+ if (!yes) {
947
+ const confirmed = await confirmYes(`Delete action schedule(s) ${itemId}?`);
948
+ if (!confirmed) {
949
+ console.log("Cancelled.");
950
+ return 0;
951
+ }
952
+ }
953
+ const result = await deleteActionSchedules({ ...base, knId, scheduleIds: itemId });
954
+ console.log(formatCallOutput(result, pretty));
955
+ return 0;
956
+ }
957
+ console.error(`Unknown action-schedule action: ${action}`);
958
+ return 1;
959
+ }
960
+ export function parseJobArgs(args) {
961
+ const [action, ...rest] = args;
962
+ if (!action || action === "--help" || action === "-h")
963
+ throw new Error("help");
964
+ let pretty = true;
965
+ let businessDomain = "";
966
+ let yes = false;
967
+ const positional = [];
968
+ for (let i = 0; i < rest.length; i += 1) {
969
+ const arg = rest[i];
970
+ if (arg === "--help" || arg === "-h")
971
+ throw new Error("help");
972
+ if (arg === "--pretty") {
973
+ pretty = true;
974
+ continue;
975
+ }
976
+ if ((arg === "-bd" || arg === "--biz-domain") && rest[i + 1]) {
977
+ businessDomain = rest[++i];
978
+ continue;
979
+ }
980
+ if (arg === "-y" || arg === "--yes") {
981
+ yes = true;
982
+ continue;
983
+ }
984
+ positional.push(arg);
985
+ }
986
+ const [knId, itemId] = positional;
987
+ if (!knId)
988
+ throw new Error("Missing kn-id. Usage: kweaver bkn job <action> <kn-id> ...");
989
+ if (!businessDomain)
990
+ businessDomain = resolveBusinessDomain();
991
+ return { action, knId, itemId: itemId || "", yes, pretty, businessDomain };
992
+ }
993
+ export async function runKnJobCommand(args) {
994
+ let parsed;
995
+ try {
996
+ parsed = parseJobArgs(args);
997
+ }
998
+ catch (error) {
999
+ if (error instanceof Error && error.message === "help") {
1000
+ console.log(`kweaver bkn job <action> <kn-id> [args] [--pretty] [-bd value]
1001
+
1002
+ Actions:
1003
+ list <kn-id> List jobs
1004
+ get <kn-id> <job-id> Get job details
1005
+ tasks <kn-id> <job-id> List tasks within a job
1006
+ delete <kn-id> <job-ids> [-y] Delete job(s) (comma-separated)`);
1007
+ return 0;
1008
+ }
1009
+ console.error(formatHttpError(error));
1010
+ return 1;
1011
+ }
1012
+ const { action, knId, itemId, yes, pretty, businessDomain } = parsed;
1013
+ const token = await ensureValidToken();
1014
+ const base = { baseUrl: token.baseUrl, accessToken: token.accessToken, businessDomain };
1015
+ if (action === "list") {
1016
+ const result = await listJobs({ ...base, knId });
1017
+ console.log(formatCallOutput(result, pretty));
1018
+ return 0;
1019
+ }
1020
+ if (action === "get") {
1021
+ if (!itemId) {
1022
+ console.error("Missing job-id");
1023
+ return 1;
1024
+ }
1025
+ const result = await getJob({ ...base, knId, jobId: itemId });
1026
+ console.log(formatCallOutput(result, pretty));
1027
+ return 0;
1028
+ }
1029
+ if (action === "tasks") {
1030
+ if (!itemId) {
1031
+ console.error("Missing job-id");
1032
+ return 1;
1033
+ }
1034
+ const result = await getJobTasks({ ...base, knId, jobId: itemId });
1035
+ console.log(formatCallOutput(result, pretty));
1036
+ return 0;
1037
+ }
1038
+ if (action === "delete") {
1039
+ if (!itemId) {
1040
+ console.error("Missing job-ids");
1041
+ return 1;
1042
+ }
1043
+ if (!yes) {
1044
+ const confirmed = await confirmYes(`Delete job(s) ${itemId}?`);
1045
+ if (!confirmed) {
1046
+ console.log("Cancelled.");
1047
+ return 0;
1048
+ }
1049
+ }
1050
+ const result = await deleteJobs({ ...base, knId, jobIds: itemId });
1051
+ console.log(formatCallOutput(result, pretty));
1052
+ return 0;
1053
+ }
1054
+ console.error(`Unknown job action: ${action}`);
1055
+ return 1;
1056
+ }