@kweaver-ai/kweaver-sdk 0.7.2 → 0.7.3

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 (34) hide show
  1. package/README.md +6 -1
  2. package/dist/api/datasources.d.ts +7 -0
  3. package/dist/api/datasources.js +8 -0
  4. package/dist/api/toolboxes.d.ts +2 -0
  5. package/dist/api/toolboxes.js +2 -1
  6. package/dist/cli.js +18 -2
  7. package/dist/commands/auth.js +42 -7
  8. package/dist/commands/bkn-ops.d.ts +2 -1
  9. package/dist/commands/bkn-ops.js +69 -34
  10. package/dist/commands/bkn-utils.d.ts +26 -2
  11. package/dist/commands/bkn-utils.js +66 -9
  12. package/dist/commands/dataflow.js +194 -20
  13. package/dist/commands/ds.d.ts +0 -1
  14. package/dist/commands/ds.js +19 -9
  15. package/dist/commands/import-csv.d.ts +0 -2
  16. package/dist/commands/import-csv.js +2 -4
  17. package/dist/commands/tool.d.ts +1 -0
  18. package/dist/commands/tool.js +12 -0
  19. package/dist/config/store.d.ts +1 -0
  20. package/dist/config/store.js +17 -0
  21. package/dist/resources/toolboxes.d.ts +2 -0
  22. package/dist/templates/bkn/document/manifest.json +12 -0
  23. package/dist/templates/bkn/document/template.json +757 -0
  24. package/dist/templates/dataflow/unstructured/manifest.json +11 -0
  25. package/dist/templates/dataflow/unstructured/template.json +63 -0
  26. package/dist/templates/dataset/document/manifest.json +10 -0
  27. package/dist/templates/dataset/document/template.json +23 -0
  28. package/dist/templates/dataset/document-content/manifest.json +10 -0
  29. package/dist/templates/dataset/document-content/template.json +29 -0
  30. package/dist/templates/dataset/document-element/manifest.json +10 -0
  31. package/dist/templates/dataset/document-element/template.json +21 -0
  32. package/dist/utils/template-loader.d.ts +40 -0
  33. package/dist/utils/template-loader.js +129 -0
  34. package/package.json +1 -1
@@ -7,6 +7,9 @@ import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/
7
7
  import { resolveBusinessDomain } from "../config/store.js";
8
8
  import { getDataflowLogsPage, listDataflowRuns, listDataflows, runDataflowWithFile, runDataflowWithRemoteUrl, } from "../api/dataflow2.js";
9
9
  import { createDataflow } from "../api/dataflow.js";
10
+ import { createVegaResource } from "../api/vega.js";
11
+ import { createKnowledgeNetwork } from "../api/knowledge-networks.js";
12
+ import { loadTemplate, listTemplates, renderTemplate, generateSourceIdentifier, getTemplatesDir, } from "../utils/template-loader.js";
10
13
  function renderTable(rows) {
11
14
  if (rows.length === 0)
12
15
  return "";
@@ -101,26 +104,6 @@ export async function runDataflowCommand(args) {
101
104
  .strict()
102
105
  .fail((message, error) => {
103
106
  throw error ?? new Error(message);
104
- })
105
- .command("create <json>", "Create a new dataflow (DAG) from a JSON definition", (command) => command
106
- .positional("json", {
107
- type: "string",
108
- describe: "JSON body string or @file-path to read from file",
109
- })
110
- .option("biz-domain", { alias: "bd", type: "string" }), async (argv) => {
111
- exitCode = await with401RefreshRetry(async () => {
112
- const base = await requireTokenAndBusinessDomain(argv.bizDomain);
113
- let raw = argv.json;
114
- if (raw.startsWith("@")) {
115
- const filePath = raw.slice(1);
116
- await access(filePath, constants.R_OK);
117
- raw = (await readFile(filePath, "utf8")).toString();
118
- }
119
- const body = JSON.parse(raw);
120
- const dagId = await createDataflow({ ...base, body });
121
- console.log(JSON.stringify({ id: dagId }, null, 2));
122
- return 0;
123
- });
124
107
  })
125
108
  .command("list", "List all dataflows", (command) => command
126
109
  .option("biz-domain", {
@@ -280,6 +263,197 @@ export async function runDataflowCommand(args) {
280
263
  }
281
264
  return 0;
282
265
  });
266
+ })
267
+ .command("templates", "List all available templates", {
268
+ json: { type: "boolean", default: false, describe: "Output as JSON" },
269
+ }, (argv) => {
270
+ const templatesDir = getTemplatesDir();
271
+ return Promise.all([
272
+ listTemplates("dataset", templatesDir),
273
+ listTemplates("bkn", templatesDir),
274
+ listTemplates("dataflow", templatesDir),
275
+ ]).then(([datasetTemplates, bknTemplates, dataflowTemplates]) => {
276
+ if (argv.json) {
277
+ console.log(JSON.stringify({
278
+ dataset: datasetTemplates,
279
+ bkn: bknTemplates,
280
+ dataflow: dataflowTemplates,
281
+ }, null, 2));
282
+ }
283
+ else {
284
+ console.log("Dataset Templates:");
285
+ for (const t of datasetTemplates) {
286
+ console.log(` - ${t.name.padEnd(18)} ${t.description}`);
287
+ }
288
+ console.log("");
289
+ console.log("BKN Templates:");
290
+ for (const t of bknTemplates) {
291
+ console.log(` - ${t.name.padEnd(18)} ${t.description}`);
292
+ }
293
+ console.log("");
294
+ console.log("Dataflow Templates:");
295
+ for (const t of dataflowTemplates) {
296
+ console.log(` - ${t.name.padEnd(18)} ${t.description}`);
297
+ }
298
+ }
299
+ });
300
+ })
301
+ .command("create-dataset", "Create a dataset from a template", (command) => command
302
+ .option("template", { type: "string", demandOption: true, describe: "Template name" })
303
+ .option("set", { type: "array", string: true, describe: "Set parameter (key=value), can be used multiple times" })
304
+ .option("json", { type: "boolean", default: false, describe: "Output as JSON" })
305
+ .option("biz-domain", { alias: "bd", type: "string" }), async (argv) => {
306
+ exitCode = await with401RefreshRetry(async () => {
307
+ const base = await requireTokenAndBusinessDomain(argv.bizDomain);
308
+ const templatesDir = getTemplatesDir();
309
+ // Parse --set arguments
310
+ const args = {};
311
+ if (argv.set) {
312
+ for (const item of argv.set) {
313
+ const eqIdx = item.indexOf("=");
314
+ if (eqIdx > 0) {
315
+ const key = item.slice(0, eqIdx);
316
+ const value = item.slice(eqIdx + 1);
317
+ args[key] = value;
318
+ }
319
+ }
320
+ }
321
+ // Load template
322
+ const loaded = await loadTemplate(argv.template, "dataset", templatesDir);
323
+ if (!loaded) {
324
+ console.error(`Template not found: ${argv.template}`);
325
+ return 1;
326
+ }
327
+ // Auto-generate source_identifier if not provided
328
+ if (!args["source_identifier"]) {
329
+ const prefixMap = {
330
+ "document": "dataflow_document",
331
+ "document-content": "dataflow_content",
332
+ "document-element": "dataflow_element",
333
+ };
334
+ const prefix = prefixMap[loaded.manifest.name] || "dataflow";
335
+ args["source_identifier"] = generateSourceIdentifier(prefix);
336
+ }
337
+ // Render template
338
+ const rendered = renderTemplate(loaded.template, loaded.manifest, args);
339
+ // Create dataset via API
340
+ const response = await createVegaResource({
341
+ ...base,
342
+ body: JSON.stringify(rendered),
343
+ });
344
+ const result = JSON.parse(response);
345
+ if (argv.json) {
346
+ console.log(JSON.stringify({ success: true, id: result.id, name: args.name }, null, 2));
347
+ }
348
+ else {
349
+ console.log(`dataset created: id=${result.id}`);
350
+ }
351
+ return 0;
352
+ });
353
+ })
354
+ .command("create-bkn", "Create a BKN (knowledge network) from a template", (command) => command
355
+ .option("template", { type: "string", demandOption: true, describe: "Template name" })
356
+ .option("set", { type: "array", string: true, describe: "Set parameter (key=value), can be used multiple times" })
357
+ .option("json", { type: "boolean", default: false, describe: "Output as JSON" })
358
+ .option("biz-domain", { alias: "bd", type: "string" }), async (argv) => {
359
+ exitCode = await with401RefreshRetry(async () => {
360
+ const base = await requireTokenAndBusinessDomain(argv.bizDomain);
361
+ const templatesDir = getTemplatesDir();
362
+ // Parse --set arguments
363
+ const args = {};
364
+ if (argv.set) {
365
+ for (const item of argv.set) {
366
+ const eqIdx = item.indexOf("=");
367
+ if (eqIdx > 0) {
368
+ const key = item.slice(0, eqIdx);
369
+ const value = item.slice(eqIdx + 1);
370
+ args[key] = value;
371
+ }
372
+ }
373
+ }
374
+ // Load template
375
+ const loaded = await loadTemplate(argv.template, "bkn", templatesDir);
376
+ if (!loaded) {
377
+ console.error(`Template not found: ${argv.template}`);
378
+ return 1;
379
+ }
380
+ // Render template
381
+ const rendered = renderTemplate(loaded.template, loaded.manifest, args);
382
+ rendered.business_domain = base.businessDomain;
383
+ // Create BKN via API
384
+ const response = await createKnowledgeNetwork({
385
+ ...base,
386
+ body: JSON.stringify(rendered),
387
+ validate_dependency: false,
388
+ });
389
+ const result = JSON.parse(response);
390
+ if (argv.json) {
391
+ console.log(JSON.stringify({ success: true, id: result.id, name: args.name }, null, 2));
392
+ }
393
+ else {
394
+ console.log(`bkn created: id=${result.id}`);
395
+ }
396
+ return 0;
397
+ });
398
+ })
399
+ .command("create [json]", "Create a new dataflow (DAG) from a JSON definition or template", (command) => command
400
+ .positional("json", {
401
+ type: "string",
402
+ describe: "JSON body string or @file-path to read from file",
403
+ })
404
+ .option("template", { type: "string", describe: "Template name (use instead of json)" })
405
+ .option("set", { type: "array", string: true, describe: "Set parameter (key=value), can be used multiple times" })
406
+ .option("biz-domain", { alias: "bd", type: "string" })
407
+ .check((argv) => {
408
+ const hasJson = typeof argv.json === "string";
409
+ const hasTemplate = typeof argv.template === "string";
410
+ if (hasJson && hasTemplate) {
411
+ throw new Error("Cannot use both json and --template");
412
+ }
413
+ if (!hasJson && !hasTemplate) {
414
+ throw new Error("Either json or --template is required");
415
+ }
416
+ return true;
417
+ }), async (argv) => {
418
+ exitCode = await with401RefreshRetry(async () => {
419
+ const base = await requireTokenAndBusinessDomain(argv.bizDomain);
420
+ let body;
421
+ if (argv.template) {
422
+ // Use template
423
+ const templatesDir = getTemplatesDir();
424
+ // Parse --set arguments
425
+ const args = {};
426
+ if (argv.set) {
427
+ for (const item of argv.set) {
428
+ const eqIdx = item.indexOf("=");
429
+ if (eqIdx > 0) {
430
+ const key = item.slice(0, eqIdx);
431
+ const value = item.slice(eqIdx + 1);
432
+ args[key] = value;
433
+ }
434
+ }
435
+ }
436
+ const loaded = await loadTemplate(argv.template, "dataflow", templatesDir);
437
+ if (!loaded) {
438
+ console.error(`Template not found: ${argv.template}`);
439
+ return 1;
440
+ }
441
+ body = renderTemplate(loaded.template, loaded.manifest, args);
442
+ }
443
+ else {
444
+ // Use JSON
445
+ let raw = argv.json;
446
+ if (raw.startsWith("@")) {
447
+ const filePath = raw.slice(1);
448
+ await access(filePath, constants.R_OK);
449
+ raw = (await readFile(filePath, "utf8")).toString();
450
+ }
451
+ body = JSON.parse(raw);
452
+ }
453
+ const dagId = await createDataflow({ ...base, body });
454
+ console.log(JSON.stringify({ id: dagId }, null, 2));
455
+ return 0;
456
+ });
283
457
  })
284
458
  .demandCommand(1);
285
459
  try {
@@ -34,7 +34,6 @@ export declare function parseImportCsvArgs(args: string[]): {
34
34
  tablePrefix: string;
35
35
  batchSize: number;
36
36
  businessDomain: string;
37
- recreate: boolean;
38
37
  };
39
38
  export declare function resolveFiles(pattern: string): Promise<string[]>;
40
39
  export interface ImportCsvResult {
@@ -3,7 +3,7 @@ import { statSync } from "node:fs";
3
3
  import { glob } from "node:fs/promises";
4
4
  import { resolve as resolvePath } from "node:path";
5
5
  import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
6
- import { testDatasource, createDatasource, listDatasources, getDatasource, deleteDatasource, listTablesWithColumns, } from "../api/datasources.js";
6
+ import { testDatasource, createDatasource, listDatasources, getDatasource, deleteDatasource, listTablesWithColumns, scanMetadata, } from "../api/datasources.js";
7
7
  import { formatCallOutput } from "./call.js";
8
8
  import { resolveBusinessDomain } from "../config/store.js";
9
9
  import { parseCsvFile, buildTableName, splitBatches, buildFieldMappings, buildDagBody, } from "./import-csv.js";
@@ -404,7 +404,6 @@ Options:
404
404
  --files <s> CSV file paths (comma-separated or glob pattern, required)
405
405
  --table-prefix <s> Table name prefix (default: none)
406
406
  --batch-size <n> Rows per batch (default: 500, range: 1-10000)
407
- --recreate First batch uses overwrite (drop/recreate table) then append; use when schema changed
408
407
  -bd, --biz-domain Business domain (default: bd_public)`;
409
408
  export function parseImportCsvArgs(args) {
410
409
  let datasourceId = "";
@@ -412,7 +411,6 @@ export function parseImportCsvArgs(args) {
412
411
  let tablePrefix = "";
413
412
  let batchSize = 500;
414
413
  let businessDomain = "";
415
- let recreate = false;
416
414
  for (let i = 0; i < args.length; i += 1) {
417
415
  const arg = args[i];
418
416
  if (arg === "--help" || arg === "-h")
@@ -421,10 +419,6 @@ export function parseImportCsvArgs(args) {
421
419
  files = args[++i];
422
420
  continue;
423
421
  }
424
- if (arg === "--recreate") {
425
- recreate = true;
426
- continue;
427
- }
428
422
  if (arg === "--table-prefix" && args[i + 1]) {
429
423
  tablePrefix = args[++i];
430
424
  continue;
@@ -447,7 +441,7 @@ export function parseImportCsvArgs(args) {
447
441
  }
448
442
  if (!businessDomain)
449
443
  businessDomain = resolveBusinessDomain();
450
- return { datasourceId, files, tablePrefix, batchSize, businessDomain, recreate };
444
+ return { datasourceId, files, tablePrefix, batchSize, businessDomain };
451
445
  }
452
446
  export async function resolveFiles(pattern) {
453
447
  const parts = pattern.split(",").map((p) => p.trim()).filter(Boolean);
@@ -550,7 +544,6 @@ export async function runDsImportCsv(args) {
550
544
  tableExist,
551
545
  data: batch,
552
546
  fieldMappings,
553
- recreate: options.recreate,
554
547
  });
555
548
  const t0 = Date.now();
556
549
  process.stderr.write(`[${tableName}] batch ${batchLabel} (${rowCount} rows)... `);
@@ -585,6 +578,23 @@ export async function runDsImportCsv(args) {
585
578
  if (failed.length > 0) {
586
579
  console.error(`Failed tables: ${failed.join(", ")}`);
587
580
  }
581
+ // Refresh the platform metadata catalog so the freshly imported tables
582
+ // are visible to ds tables / bkn create-from-ds without manual scan.
583
+ // Best-effort: scan failures shouldn't mask a successful import.
584
+ if (succeeded.length > 0) {
585
+ process.stderr.write("Scanning datasource metadata ...\n");
586
+ try {
587
+ await scanMetadata({
588
+ ...base,
589
+ id: options.datasourceId,
590
+ dsType: datasourceType,
591
+ businessDomain: options.businessDomain,
592
+ });
593
+ }
594
+ catch (err) {
595
+ console.error(`Scan warning (continuing): ${formatHttpError(err)}`);
596
+ }
597
+ }
588
598
  return { code: failed.length > 0 ? 1 : 0, tables: succeeded, failed, tableColumns, sampleRows };
589
599
  }
590
600
  export async function runDsImportCsvCommand(args) {
@@ -19,8 +19,6 @@ export interface DagBodyOptions {
19
19
  tableExist: boolean;
20
20
  data: Array<Record<string, string | null>>;
21
21
  fieldMappings: FieldMapping[];
22
- /** When true on the first batch (`tableExist` false), use "insert" to force table recreation. */
23
- recreate?: boolean;
24
22
  }
25
23
  /**
26
24
  * Read a CSV file and return its headers and rows.
@@ -80,11 +80,9 @@ export function buildFieldMappings(headers) {
80
80
  * The DAG has two steps: a manual trigger and the database write.
81
81
  */
82
82
  export function buildDagBody(options) {
83
- const { datasourceId, datasourceType, tableName, tableExist, data, fieldMappings, recreate } = options;
83
+ const { datasourceId, datasourceType, tableName, tableExist, data, fieldMappings } = options;
84
84
  const ts = Date.now();
85
- // "insert" creates/replaces the table; "append" adds rows to an existing table.
86
- // With --recreate, use "insert" on first batch to force table recreation when schema changed.
87
- const operateType = tableExist ? "append" : recreate ? "insert" : "append";
85
+ const operateType = "append";
88
86
  const triggerStep = {
89
87
  id: "step-trigger",
90
88
  title: "Trigger",
@@ -19,6 +19,7 @@ export interface ToolInvokeOptions {
19
19
  toolId: string;
20
20
  header?: Record<string, unknown>;
21
21
  query?: Record<string, unknown>;
22
+ path?: Record<string, unknown>;
22
23
  body?: unknown;
23
24
  bodyFile?: string;
24
25
  timeout?: number;
@@ -12,8 +12,10 @@ Subcommands:
12
12
  enable --toolbox <box-id> <tool-id>... Enable one or more tools
13
13
  disable --toolbox <box-id> <tool-id>... Disable one or more tools
14
14
  execute --toolbox <box-id> <tool-id> [--body '<json>'|--body-file <path>]
15
+ [--header|--query|--path '<json>']
15
16
  Invoke a published+enabled tool
16
17
  debug --toolbox <box-id> <tool-id> [--body '<json>'|--body-file <path>]
18
+ [--header|--query|--path '<json>']
17
19
  Invoke a tool (works on draft/disabled too)
18
20
 
19
21
  Options for execute/debug:
@@ -21,6 +23,9 @@ Options for execute/debug:
21
23
  (Authorization is auto-injected from current session
22
24
  when --header omits it; pass {} to send none)
23
25
  --query '<json>' Query params map forwarded to the downstream tool
26
+ --path '<json>' Path parameter map for OpenAPI path placeholders (e.g. {id})
27
+ (JSON object: quote id and UUID, e.g. key id for get_dataview_detail /
28
+ query_dataview_sql)
24
29
  --timeout <seconds> Per-call timeout (backend default applies when omitted)
25
30
 
26
31
  Common options:
@@ -241,6 +246,7 @@ export function parseToolInvokeArgs(args) {
241
246
  let pretty = true;
242
247
  let header;
243
248
  let query;
249
+ let path;
244
250
  let body;
245
251
  let bodyProvided = false;
246
252
  let bodyFile;
@@ -259,6 +265,10 @@ export function parseToolInvokeArgs(args) {
259
265
  query = parseJsonOption("--query", args[++i]);
260
266
  continue;
261
267
  }
268
+ if (a === "--path" && args[i + 1]) {
269
+ path = parseJsonOption("--path", args[++i]);
270
+ continue;
271
+ }
262
272
  if (a === "--body" && args[i + 1]) {
263
273
  const raw = args[++i];
264
274
  try {
@@ -312,6 +322,7 @@ export function parseToolInvokeArgs(args) {
312
322
  toolId,
313
323
  header,
314
324
  query,
325
+ path,
315
326
  body: bodyProvided ? body : undefined,
316
327
  bodyFile,
317
328
  timeout,
@@ -370,6 +381,7 @@ async function runToolInvoke(args, action) {
370
381
  toolId: opts.toolId,
371
382
  header,
372
383
  query: opts.query,
384
+ path: opts.path,
373
385
  body,
374
386
  timeout: opts.timeout,
375
387
  });
@@ -53,6 +53,7 @@ export interface PlatformSummary {
53
53
  /** Human-readable name persisted from /oauth2/userinfo at login time. */
54
54
  displayName?: string;
55
55
  }
56
+ export declare function getProfileName(): string | null;
56
57
  /** Extract userId from a TokenConfig (try idToken, then accessToken, fallback "default"). */
57
58
  export declare function extractUserId(token: TokenConfig): string;
58
59
  /** Get the active userId for a platform. */
@@ -29,6 +29,19 @@ const MCP_PATH = "/api/agent-retrieval/v1/mcp";
29
29
  function buildMcpUrl(baseUrl) {
30
30
  return baseUrl.replace(/\/+$/, "") + MCP_PATH;
31
31
  }
32
+ const PROFILE_NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;
33
+ export function getProfileName() {
34
+ const raw = process.env.KWEAVER_PROFILE;
35
+ if (!raw)
36
+ return null;
37
+ const trimmed = raw.trim();
38
+ if (!trimmed)
39
+ return null;
40
+ if (!PROFILE_NAME_RE.test(trimmed)) {
41
+ throw new Error(`KWEAVER_PROFILE='${raw}' is invalid. Use 1-64 chars from [A-Za-z0-9_-].`);
42
+ }
43
+ return trimmed;
44
+ }
32
45
  function getConfigDirPath() {
33
46
  return process.env.KWEAVERC_CONFIG_DIR || join(homedir(), ".kweaver");
34
47
  }
@@ -36,6 +49,10 @@ function getPlatformsDirPath() {
36
49
  return join(getConfigDirPath(), "platforms");
37
50
  }
38
51
  function getStateFilePath() {
52
+ const profile = getProfileName();
53
+ if (profile) {
54
+ return join(getConfigDirPath(), "profiles", profile, "state.json");
55
+ }
39
56
  return join(getConfigDirPath(), "state.json");
40
57
  }
41
58
  function getLegacyClientFilePath() {
@@ -6,6 +6,8 @@ export interface InvokeToolArgs {
6
6
  * send no headers. */
7
7
  header?: Record<string, unknown>;
8
8
  query?: Record<string, unknown>;
9
+ /** Path parameters for OpenAPI `{name}` placeholders (e.g. `{ id: "<uuid>" }`). */
10
+ path?: Record<string, unknown>;
9
11
  body?: unknown;
10
12
  /** Per-call timeout in seconds (backend default applies when omitted). */
11
13
  timeout?: number;
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "document",
3
+ "type": "bkn",
4
+ "description": "文档知识网络",
5
+ "arguments": [
6
+ { "name": "name", "required": true, "description": "BKN 名称", "type": "string" },
7
+ { "name": "embedding_model_id", "required": true, "description": "向量化模型 ID", "type": "string" },
8
+ { "name": "content_dataset_id", "required": true, "description": "内容数据集 ID", "type": "string" },
9
+ { "name": "document_dataset_id", "required": true, "description": "文档数据集 ID", "type": "string" },
10
+ { "name": "element_dataset_id", "required": true, "description": "元素数据集 ID", "type": "string" }
11
+ ]
12
+ }