@kweaver-ai/kweaver-sdk 0.4.0 → 0.4.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.
@@ -1,9 +1,11 @@
1
1
  import { createInterface } from "node:readline";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { ensureValidToken, formatHttpError } from "../auth/oauth.js";
4
- import { listKnowledgeNetworks, getKnowledgeNetwork, createKnowledgeNetwork, updateKnowledgeNetwork, deleteKnowledgeNetwork, listObjectTypes, listRelationTypes, listActionTypes, } from "../api/knowledge-networks.js";
4
+ import { listKnowledgeNetworks, getKnowledgeNetwork, createKnowledgeNetwork, updateKnowledgeNetwork, deleteKnowledgeNetwork, listObjectTypes, listRelationTypes, listActionTypes, getObjectType, createObjectTypes, updateObjectType, deleteObjectTypes, getRelationType, createRelationTypes, updateRelationType, deleteRelationTypes, buildKnowledgeNetwork, getBuildStatus, } from "../api/knowledge-networks.js";
5
5
  import { objectTypeQuery, objectTypeProperties, subgraph, actionTypeQuery, actionTypeExecute, actionExecutionGet, actionLogsList, actionLogGet, actionLogCancel, } from "../api/ontology-query.js";
6
6
  import { semanticSearch } from "../api/semantic-search.js";
7
+ import { listTablesWithColumns } from "../api/datasources.js";
8
+ import { createDataView } from "../api/dataviews.js";
7
9
  import { formatCallOutput } from "./call.js";
8
10
  export function formatSimpleKnList(text, pretty, includeDetail = false) {
9
11
  const parsed = JSON.parse(text);
@@ -436,16 +438,26 @@ const KN_HELP = `kweaver bkn
436
438
  Subcommands:
437
439
  list [options] List business knowledge networks
438
440
  get <kn-id> [options] Get knowledge network detail (use --stats or --export)
439
- create [options] Create a knowledge network
441
+ create [options] Create a knowledge network (empty or from --body-file)
442
+ create-from-ds <ds-id> --name X [--tables a,b] [--build] Create KN from datasource
440
443
  update <kn-id> [options] Update a knowledge network
441
444
  delete <kn-id> Delete a knowledge network
445
+ build <kn-id> [--wait|--no-wait] [--timeout n] Trigger full build
442
446
  export <kn-id> Export knowledge network (alias for get --export)
443
447
  stats <kn-id> Get statistics (alias for get --stats)
444
448
  search <kn-id> <query> [options] Semantic search within a knowledge network
445
449
  object-type list <kn-id> List object types (schema)
450
+ object-type get <kn-id> <ot-id> Get object type details
451
+ object-type create <kn-id> [options] Create object type (--name --dataview-id --primary-key --display-key)
452
+ object-type update <kn-id> <ot-id> [options] Update object type
453
+ object-type delete <kn-id> <ot-ids> [-y] Delete object type(s)
446
454
  object-type query <kn-id> <ot-id> ['<json>'] Query object instances (ontology-query; supports --limit/--search-after)
447
455
  object-type properties <kn-id> <ot-id> '<json>' Query object properties
448
456
  relation-type list <kn-id> List relation types (schema)
457
+ relation-type get <kn-id> <rt-id> Get relation type details
458
+ relation-type create <kn-id> [options] Create relation type (--name --source --target [--mapping src:tgt])
459
+ relation-type update <kn-id> <rt-id> [options] Update relation type
460
+ relation-type delete <kn-id> <rt-ids> [-y] Delete relation type(s)
449
461
  subgraph <kn-id> '<json>' Query subgraph
450
462
  action-type list <kn-id> List action types (schema)
451
463
  action-type query <kn-id> <at-id> '<json>' Query action info
@@ -471,12 +483,18 @@ export async function runKnCommand(args) {
471
483
  if (subcommand === "create") {
472
484
  return runKnCreateCommand(rest);
473
485
  }
486
+ if (subcommand === "create-from-ds") {
487
+ return runKnCreateFromDsCommand(rest);
488
+ }
474
489
  if (subcommand === "update") {
475
490
  return runKnUpdateCommand(rest);
476
491
  }
477
492
  if (subcommand === "delete") {
478
493
  return runKnDeleteCommand(rest);
479
494
  }
495
+ if (subcommand === "build") {
496
+ return runKnBuildCommand(rest);
497
+ }
480
498
  if (subcommand === "export") {
481
499
  return runKnGetCommand([...(rest[0] ? [rest[0]] : []), "--export", ...rest.slice(1)]);
482
500
  }
@@ -507,6 +525,150 @@ export async function runKnCommand(args) {
507
525
  console.error(`Unknown bkn subcommand: ${subcommand}`);
508
526
  return 1;
509
527
  }
528
+ /** Parse object-type create args: --name --dataview-id --primary-key --display-key [--property '<json>' ...] */
529
+ function parseObjectTypeCreateArgs(args) {
530
+ let name = "";
531
+ let dataviewId = "";
532
+ let primaryKey = "";
533
+ let displayKey = "";
534
+ let businessDomain = "bd_public";
535
+ let branch = "main";
536
+ let pretty = true;
537
+ const properties = [];
538
+ const positional = [];
539
+ for (let i = 0; i < args.length; i += 1) {
540
+ const arg = args[i];
541
+ if (arg === "--help" || arg === "-h")
542
+ throw new Error("help");
543
+ if (arg === "--name" && args[i + 1]) {
544
+ name = args[++i];
545
+ continue;
546
+ }
547
+ if (arg === "--dataview-id" && args[i + 1]) {
548
+ dataviewId = args[++i];
549
+ continue;
550
+ }
551
+ if (arg === "--primary-key" && args[i + 1]) {
552
+ primaryKey = args[++i];
553
+ continue;
554
+ }
555
+ if (arg === "--display-key" && args[i + 1]) {
556
+ displayKey = args[++i];
557
+ continue;
558
+ }
559
+ if (arg === "--property" && args[i + 1]) {
560
+ properties.push(args[++i]);
561
+ continue;
562
+ }
563
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
564
+ businessDomain = args[++i];
565
+ continue;
566
+ }
567
+ if (arg === "--branch" && args[i + 1]) {
568
+ branch = args[++i];
569
+ continue;
570
+ }
571
+ if (arg === "--pretty") {
572
+ pretty = true;
573
+ continue;
574
+ }
575
+ if (!arg.startsWith("-"))
576
+ positional.push(arg);
577
+ }
578
+ const knId = positional[0];
579
+ if (!knId || !name || !dataviewId || !primaryKey || !displayKey) {
580
+ throw new Error("Usage: kweaver bkn object-type create <kn-id> --name X --dataview-id Y --primary-key Z --display-key W");
581
+ }
582
+ const entry = {
583
+ name,
584
+ data_source: { type: "data_view", id: dataviewId },
585
+ primary_keys: [primaryKey],
586
+ display_key: displayKey,
587
+ };
588
+ if (properties.length > 0) {
589
+ entry.data_properties = properties.map((p) => JSON.parse(p));
590
+ }
591
+ else {
592
+ const autoProps = new Set([primaryKey, displayKey]);
593
+ entry.data_properties = Array.from(autoProps).map((n) => ({
594
+ name: n,
595
+ display_name: n,
596
+ type: "string",
597
+ }));
598
+ }
599
+ const body = JSON.stringify({ entries: [entry], branch });
600
+ return { knId, body, businessDomain, branch, pretty };
601
+ }
602
+ /** Parse object-type update args: --name X [--display-key Y] */
603
+ function parseObjectTypeUpdateArgs(args) {
604
+ let name;
605
+ let displayKey;
606
+ let businessDomain = "bd_public";
607
+ let pretty = true;
608
+ const positional = [];
609
+ for (let i = 0; i < args.length; i += 1) {
610
+ const arg = args[i];
611
+ if (arg === "--help" || arg === "-h")
612
+ throw new Error("help");
613
+ if (arg === "--name" && args[i + 1]) {
614
+ name = args[++i];
615
+ continue;
616
+ }
617
+ if (arg === "--display-key" && args[i + 1]) {
618
+ displayKey = args[++i];
619
+ continue;
620
+ }
621
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
622
+ businessDomain = args[++i];
623
+ continue;
624
+ }
625
+ if (arg === "--pretty") {
626
+ pretty = true;
627
+ continue;
628
+ }
629
+ if (!arg.startsWith("-"))
630
+ positional.push(arg);
631
+ }
632
+ const [knId, otId] = positional;
633
+ if (!knId || !otId) {
634
+ throw new Error("Usage: kweaver bkn object-type update <kn-id> <ot-id> [--name X] [--display-key Y]");
635
+ }
636
+ const payload = {};
637
+ if (name !== undefined)
638
+ payload.name = name;
639
+ if (displayKey !== undefined)
640
+ payload.display_key = displayKey;
641
+ if (Object.keys(payload).length === 0) {
642
+ throw new Error("No update fields. Use --name or --display-key.");
643
+ }
644
+ return { knId, otId, body: JSON.stringify(payload), businessDomain, pretty };
645
+ }
646
+ /** Parse object-type delete args: <kn-id> <ot-ids> [-y] */
647
+ function parseObjectTypeDeleteArgs(args) {
648
+ let businessDomain = "bd_public";
649
+ let yes = false;
650
+ const positional = [];
651
+ for (let i = 0; i < args.length; i += 1) {
652
+ const arg = args[i];
653
+ if (arg === "--help" || arg === "-h")
654
+ throw new Error("help");
655
+ if (arg === "--yes" || arg === "-y") {
656
+ yes = true;
657
+ continue;
658
+ }
659
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
660
+ businessDomain = args[++i];
661
+ continue;
662
+ }
663
+ if (!arg.startsWith("-"))
664
+ positional.push(arg);
665
+ }
666
+ const [knId, otIds] = positional;
667
+ if (!knId || !otIds) {
668
+ throw new Error("Usage: kweaver bkn object-type delete <kn-id> <ot-ids> [-y]");
669
+ }
670
+ return { knId, otIds, businessDomain, yes };
671
+ }
510
672
  /** Parse common flags for ontology-query subcommands; returns { filteredArgs, pretty, businessDomain } */
511
673
  function parseOntologyQueryFlags(args) {
512
674
  let pretty = true;
@@ -581,18 +743,124 @@ export function parseKnActionTypeExecuteArgs(args) {
581
743
  timeout,
582
744
  };
583
745
  }
746
+ const PK_CANDIDATES = new Set(["id", "pk", "key"]);
747
+ const PK_TYPES = new Set(["integer", "unsigned integer", "string", "varchar", "bigint", "int"]);
748
+ const DISPLAY_HINTS = ["name", "title", "label", "display_name", "description"];
749
+ function detectPrimaryKey(table) {
750
+ for (const col of table.columns) {
751
+ if (PK_CANDIDATES.has(col.name.toLowerCase()) && PK_TYPES.has(col.type.toLowerCase())) {
752
+ return col.name;
753
+ }
754
+ }
755
+ for (const col of table.columns) {
756
+ if (PK_TYPES.has(col.type.toLowerCase())) {
757
+ return col.name;
758
+ }
759
+ }
760
+ return table.columns[0]?.name ?? "id";
761
+ }
762
+ function detectDisplayKey(table, primaryKey) {
763
+ for (const col of table.columns) {
764
+ if (DISPLAY_HINTS.some((h) => col.name.toLowerCase().includes(h))) {
765
+ return col.name;
766
+ }
767
+ }
768
+ return primaryKey;
769
+ }
770
+ function confirmYes(prompt) {
771
+ return new Promise((resolve) => {
772
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
773
+ rl.question(`${prompt} [y/N] `, (answer) => {
774
+ rl.close();
775
+ const trimmed = answer.trim().toLowerCase();
776
+ resolve(trimmed === "y" || trimmed === "yes");
777
+ });
778
+ });
779
+ }
584
780
  async function runKnObjectTypeCommand(args) {
585
781
  const [action, ...rest] = args;
586
782
  if (!action || action === "--help" || action === "-h") {
587
783
  console.log(`kweaver bkn object-type list <kn-id> [--pretty] [-bd value]
784
+ kweaver bkn object-type get <kn-id> <ot-id> [--pretty] [-bd value]
785
+ kweaver bkn object-type create <kn-id> --name X --dataview-id Y --primary-key Z --display-key W [--property '<json>' ...]
786
+ kweaver bkn object-type update <kn-id> <ot-id> [--name X] [--display-key Y]
787
+ kweaver bkn object-type delete <kn-id> <ot-ids> [-y]
588
788
  kweaver bkn object-type query <kn-id> <ot-id> ['<json>'] [--limit <n>] [--search-after '<json-array>'] [--pretty] [-bd value]
589
789
  kweaver bkn object-type properties <kn-id> <ot-id> '<json>' [--pretty] [-bd value]
590
790
 
591
791
  list: List object types (schema) from ontology-manager.
792
+ get: Get single object type details.
793
+ create/update/delete: Schema CRUD (create requires dataview-id).
592
794
  query/properties: Query via ontology-query API. For query, --limit and --search-after are merged into the JSON body.`);
593
795
  return 0;
594
796
  }
595
797
  try {
798
+ if (action === "get") {
799
+ const parsed = parseOntologyQueryFlags(rest);
800
+ const [knId, otId] = parsed.filteredArgs;
801
+ if (!knId || !otId) {
802
+ console.error("Usage: kweaver bkn object-type get <kn-id> <ot-id> [options]");
803
+ return 1;
804
+ }
805
+ const token = await ensureValidToken();
806
+ const body = await getObjectType({
807
+ baseUrl: token.baseUrl,
808
+ accessToken: token.accessToken,
809
+ knId,
810
+ otId,
811
+ businessDomain: parsed.businessDomain,
812
+ });
813
+ console.log(formatCallOutput(body, parsed.pretty));
814
+ return 0;
815
+ }
816
+ if (action === "create") {
817
+ const opts = parseObjectTypeCreateArgs(rest);
818
+ const token = await ensureValidToken();
819
+ const body = await createObjectTypes({
820
+ baseUrl: token.baseUrl,
821
+ accessToken: token.accessToken,
822
+ knId: opts.knId,
823
+ body: opts.body,
824
+ businessDomain: opts.businessDomain,
825
+ branch: opts.branch,
826
+ });
827
+ console.log(formatCallOutput(body, opts.pretty));
828
+ return 0;
829
+ }
830
+ if (action === "update") {
831
+ const opts = parseObjectTypeUpdateArgs(rest);
832
+ const token = await ensureValidToken();
833
+ const body = await updateObjectType({
834
+ baseUrl: token.baseUrl,
835
+ accessToken: token.accessToken,
836
+ knId: opts.knId,
837
+ otId: opts.otId,
838
+ body: opts.body,
839
+ businessDomain: opts.businessDomain,
840
+ });
841
+ console.log(formatCallOutput(body, opts.pretty));
842
+ return 0;
843
+ }
844
+ if (action === "delete") {
845
+ const opts = parseObjectTypeDeleteArgs(rest);
846
+ if (!opts.yes) {
847
+ const confirmed = await confirmYes(`Delete object type(s) ${opts.otIds}?`);
848
+ if (!confirmed) {
849
+ console.error("Aborted.");
850
+ return 1;
851
+ }
852
+ }
853
+ const token = await ensureValidToken();
854
+ await deleteObjectTypes({
855
+ baseUrl: token.baseUrl,
856
+ accessToken: token.accessToken,
857
+ knId: opts.knId,
858
+ otIds: opts.otIds,
859
+ businessDomain: opts.businessDomain,
860
+ });
861
+ console.log(`Deleted ${opts.otIds}`);
862
+ return 0;
863
+ }
596
864
  if (action === "list") {
597
865
  const parsed = parseOntologyQueryFlags(rest);
598
866
  const [knId] = parsed.filteredArgs;
@@ -643,44 +911,255 @@ query/properties: Query via ontology-query API. For query, --limit and --search-
643
911
  console.log(formatCallOutput(result, parsed.pretty));
644
912
  return 0;
645
913
  }
646
- console.error(`Unknown object-type action: ${action}. Use list, query, or properties.`);
914
+ console.error(`Unknown object-type action: ${action}. Use list, get, create, update, delete, query, or properties.`);
647
915
  return 1;
648
916
  }
649
917
  catch (error) {
918
+ if (error instanceof Error && error.message === "help") {
919
+ console.log(`kweaver bkn object-type create <kn-id> --name X --dataview-id Y --primary-key Z --display-key W [--property '<json>' ...]
920
+ kweaver bkn object-type update <kn-id> <ot-id> [--name X] [--display-key Y]
921
+ kweaver bkn object-type delete <kn-id> <ot-ids> [-y]`);
922
+ return 0;
923
+ }
650
924
  console.error(formatHttpError(error));
651
925
  return 1;
652
926
  }
653
927
  }
928
+ /** Parse relation-type create args: --name --source --target [--mapping src:tgt ...] */
929
+ function parseRelationTypeCreateArgs(args) {
930
+ let name = "";
931
+ let source = "";
932
+ let target = "";
933
+ let businessDomain = "bd_public";
934
+ let branch = "main";
935
+ let pretty = true;
936
+ const mappings = [];
937
+ const positional = [];
938
+ for (let i = 0; i < args.length; i += 1) {
939
+ const arg = args[i];
940
+ if (arg === "--help" || arg === "-h")
941
+ throw new Error("help");
942
+ if (arg === "--name" && args[i + 1]) {
943
+ name = args[++i];
944
+ continue;
945
+ }
946
+ if (arg === "--source" && args[i + 1]) {
947
+ source = args[++i];
948
+ continue;
949
+ }
950
+ if (arg === "--target" && args[i + 1]) {
951
+ target = args[++i];
952
+ continue;
953
+ }
954
+ if (arg === "--mapping" && args[i + 1]) {
955
+ const m = args[++i];
956
+ if (!m.includes(":")) {
957
+ throw new Error(`Invalid mapping format '${m}'. Expected source_prop:target_prop.`);
958
+ }
959
+ const [s, t] = m.split(":", 2);
960
+ mappings.push([s, t]);
961
+ continue;
962
+ }
963
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
964
+ businessDomain = args[++i];
965
+ continue;
966
+ }
967
+ if (arg === "--branch" && args[i + 1]) {
968
+ branch = args[++i];
969
+ continue;
970
+ }
971
+ if (arg === "--pretty") {
972
+ pretty = true;
973
+ continue;
974
+ }
975
+ if (!arg.startsWith("-"))
976
+ positional.push(arg);
977
+ }
978
+ const knId = positional[0];
979
+ if (!knId || !name || !source || !target) {
980
+ throw new Error("Usage: kweaver bkn relation-type create <kn-id> --name X --source <ot-id> --target <ot-id> [--mapping src:tgt ...]");
981
+ }
982
+ const entry = {
983
+ name,
984
+ source_object_type_id: source,
985
+ target_object_type_id: target,
986
+ type: "direct",
987
+ mapping_rules: mappings.map(([s, t]) => ({
988
+ source_property: { name: s },
989
+ target_property: { name: t },
990
+ })),
991
+ };
992
+ const body = JSON.stringify({ entries: [entry], branch });
993
+ return { knId, body, businessDomain, branch, pretty };
994
+ }
995
+ /** Parse relation-type update args: [--name X] */
996
+ function parseRelationTypeUpdateArgs(args) {
997
+ let name;
998
+ let businessDomain = "bd_public";
999
+ let pretty = true;
1000
+ const positional = [];
1001
+ for (let i = 0; i < args.length; i += 1) {
1002
+ const arg = args[i];
1003
+ if (arg === "--help" || arg === "-h")
1004
+ throw new Error("help");
1005
+ if (arg === "--name" && args[i + 1]) {
1006
+ name = args[++i];
1007
+ continue;
1008
+ }
1009
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
1010
+ businessDomain = args[++i];
1011
+ continue;
1012
+ }
1013
+ if (arg === "--pretty") {
1014
+ pretty = true;
1015
+ continue;
1016
+ }
1017
+ if (!arg.startsWith("-"))
1018
+ positional.push(arg);
1019
+ }
1020
+ const [knId, rtId] = positional;
1021
+ if (!knId || !rtId) {
1022
+ throw new Error("Usage: kweaver bkn relation-type update <kn-id> <rt-id> [--name X]");
1023
+ }
1024
+ if (name === undefined) {
1025
+ throw new Error("No update fields. Use --name.");
1026
+ }
1027
+ return { knId, rtId, body: JSON.stringify({ name }), businessDomain, pretty };
1028
+ }
1029
+ /** Parse relation-type delete args: <kn-id> <rt-ids> [-y] */
1030
+ function parseRelationTypeDeleteArgs(args) {
1031
+ let businessDomain = "bd_public";
1032
+ let yes = false;
1033
+ const positional = [];
1034
+ for (let i = 0; i < args.length; i += 1) {
1035
+ const arg = args[i];
1036
+ if (arg === "--help" || arg === "-h")
1037
+ throw new Error("help");
1038
+ if (arg === "--yes" || arg === "-y") {
1039
+ yes = true;
1040
+ continue;
1041
+ }
1042
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
1043
+ businessDomain = args[++i];
1044
+ continue;
1045
+ }
1046
+ if (!arg.startsWith("-"))
1047
+ positional.push(arg);
1048
+ }
1049
+ const [knId, rtIds] = positional;
1050
+ if (!knId || !rtIds) {
1051
+ throw new Error("Usage: kweaver bkn relation-type delete <kn-id> <rt-ids> [-y]");
1052
+ }
1053
+ return { knId, rtIds, businessDomain, yes };
1054
+ }
654
1055
  async function runKnRelationTypeCommand(args) {
655
1056
  const [action, ...rest] = args;
656
1057
  if (!action || action === "--help" || action === "-h") {
657
1058
  console.log(`kweaver bkn relation-type list <kn-id> [--pretty] [-bd value]
1059
+ kweaver bkn relation-type get <kn-id> <rt-id> [--pretty] [-bd value]
1060
+ kweaver bkn relation-type create <kn-id> --name X --source <ot-id> --target <ot-id> [--mapping src:tgt ...]
1061
+ kweaver bkn relation-type update <kn-id> <rt-id> [--name X]
1062
+ kweaver bkn relation-type delete <kn-id> <rt-ids> [-y]
658
1063
 
659
- List relation types (schema) from ontology-manager.`);
1064
+ list: List relation types (schema) from ontology-manager.
1065
+ get: Get single relation type details.
1066
+ create/update/delete: Schema CRUD.`);
660
1067
  return 0;
661
1068
  }
662
- if (action !== "list") {
663
- console.error(`Unknown relation-type action: ${action}. Use list.`);
664
- return 1;
665
- }
666
1069
  try {
667
- const parsed = parseOntologyQueryFlags(rest);
668
- const [knId] = parsed.filteredArgs;
669
- if (!knId) {
670
- console.error("Usage: kweaver bkn relation-type list <kn-id> [options]");
671
- return 1;
1070
+ if (action === "get") {
1071
+ const parsed = parseOntologyQueryFlags(rest);
1072
+ const [knId, rtId] = parsed.filteredArgs;
1073
+ if (!knId || !rtId) {
1074
+ console.error("Usage: kweaver bkn relation-type get <kn-id> <rt-id> [options]");
1075
+ return 1;
1076
+ }
1077
+ const token = await ensureValidToken();
1078
+ const body = await getRelationType({
1079
+ baseUrl: token.baseUrl,
1080
+ accessToken: token.accessToken,
1081
+ knId,
1082
+ rtId,
1083
+ businessDomain: parsed.businessDomain,
1084
+ });
1085
+ console.log(formatCallOutput(body, parsed.pretty));
1086
+ return 0;
672
1087
  }
673
- const token = await ensureValidToken();
674
- const body = await listRelationTypes({
675
- baseUrl: token.baseUrl,
676
- accessToken: token.accessToken,
677
- knId,
678
- businessDomain: parsed.businessDomain,
679
- });
680
- console.log(formatCallOutput(body, parsed.pretty));
681
- return 0;
1088
+ if (action === "create") {
1089
+ const opts = parseRelationTypeCreateArgs(rest);
1090
+ const token = await ensureValidToken();
1091
+ const body = await createRelationTypes({
1092
+ baseUrl: token.baseUrl,
1093
+ accessToken: token.accessToken,
1094
+ knId: opts.knId,
1095
+ body: opts.body,
1096
+ businessDomain: opts.businessDomain,
1097
+ branch: opts.branch,
1098
+ });
1099
+ console.log(formatCallOutput(body, opts.pretty));
1100
+ return 0;
1101
+ }
1102
+ if (action === "update") {
1103
+ const opts = parseRelationTypeUpdateArgs(rest);
1104
+ const token = await ensureValidToken();
1105
+ const body = await updateRelationType({
1106
+ baseUrl: token.baseUrl,
1107
+ accessToken: token.accessToken,
1108
+ knId: opts.knId,
1109
+ rtId: opts.rtId,
1110
+ body: opts.body,
1111
+ businessDomain: opts.businessDomain,
1112
+ });
1113
+ console.log(formatCallOutput(body, opts.pretty));
1114
+ return 0;
1115
+ }
1116
+ if (action === "delete") {
1117
+ const opts = parseRelationTypeDeleteArgs(rest);
1118
+ if (!opts.yes) {
1119
+ const confirmed = await confirmYes(`Delete relation type(s) ${opts.rtIds}?`);
1120
+ if (!confirmed) {
1121
+ console.error("Aborted.");
1122
+ return 1;
1123
+ }
1124
+ }
1125
+ const token = await ensureValidToken();
1126
+ await deleteRelationTypes({
1127
+ baseUrl: token.baseUrl,
1128
+ accessToken: token.accessToken,
1129
+ knId: opts.knId,
1130
+ rtIds: opts.rtIds,
1131
+ businessDomain: opts.businessDomain,
1132
+ });
1133
+ console.log(`Deleted ${opts.rtIds}`);
1134
+ return 0;
1135
+ }
1136
+ if (action === "list") {
1137
+ const parsed = parseOntologyQueryFlags(rest);
1138
+ const [knId] = parsed.filteredArgs;
1139
+ if (!knId) {
1140
+ console.error("Usage: kweaver bkn relation-type list <kn-id> [options]");
1141
+ return 1;
1142
+ }
1143
+ const token = await ensureValidToken();
1144
+ const body = await listRelationTypes({
1145
+ baseUrl: token.baseUrl,
1146
+ accessToken: token.accessToken,
1147
+ knId,
1148
+ businessDomain: parsed.businessDomain,
1149
+ });
1150
+ console.log(formatCallOutput(body, parsed.pretty));
1151
+ return 0;
1152
+ }
1153
+ console.error(`Unknown relation-type action: ${action}. Use list, get, create, update, or delete.`);
1154
+ return 1;
682
1155
  }
683
1156
  catch (error) {
1157
+ if (error instanceof Error && error.message === "help") {
1158
+ console.log(`kweaver bkn relation-type create <kn-id> --name X --source <ot-id> --target <ot-id> [--mapping src:tgt ...]
1159
+ kweaver bkn relation-type update <kn-id> <rt-id> [--name X]
1160
+ kweaver bkn relation-type delete <kn-id> <rt-ids> [-y]`);
1161
+ return 0;
1162
+ }
684
1163
  console.error(formatHttpError(error));
685
1164
  return 1;
686
1165
  }
@@ -699,7 +1178,7 @@ async function runKnSubgraphCommand(args) {
699
1178
  if (error instanceof Error && error.message === "help") {
700
1179
  console.log(`kweaver bkn subgraph <kn-id> '<json>' [--pretty] [-bd value]
701
1180
 
702
- Query subgraph via ontology-query API. JSON body format see ref/ontology/ontology-query.yaml.`);
1181
+ Query subgraph via ontology-query API. JSON body format see references/json-formats.md#subgraph.`);
703
1182
  return 0;
704
1183
  }
705
1184
  throw error;
@@ -1210,6 +1689,183 @@ async function runKnCreateCommand(args) {
1210
1689
  return 1;
1211
1690
  }
1212
1691
  }
1692
+ const KN_CREATE_FROM_DS_HELP = `kweaver bkn create-from-ds <ds-id> --name X [options]
1693
+
1694
+ Create a knowledge network from a datasource (dataviews + object types + optional build).
1695
+
1696
+ Options:
1697
+ --name <s> Knowledge network name (required)
1698
+ --tables <a,b> Comma-separated table names (default: all)
1699
+ --build (default) Build after creation
1700
+ --no-build Skip build after creation
1701
+ --timeout <n> Build timeout in seconds (default: 300)
1702
+ -bd, --biz-domain Business domain (default: bd_public)
1703
+ --pretty Pretty-print output (default)`;
1704
+ function parseKnCreateFromDsArgs(args) {
1705
+ let dsId = "";
1706
+ let name = "";
1707
+ let tablesStr = "";
1708
+ let build = true;
1709
+ let timeout = 300;
1710
+ let businessDomain = "bd_public";
1711
+ let pretty = true;
1712
+ for (let i = 0; i < args.length; i += 1) {
1713
+ const arg = args[i];
1714
+ if (arg === "--help" || arg === "-h")
1715
+ throw new Error("help");
1716
+ if (arg === "--name" && args[i + 1]) {
1717
+ name = args[++i];
1718
+ continue;
1719
+ }
1720
+ if (arg === "--tables" && args[i + 1]) {
1721
+ tablesStr = args[++i];
1722
+ continue;
1723
+ }
1724
+ if (arg === "--build") {
1725
+ build = true;
1726
+ continue;
1727
+ }
1728
+ if (arg === "--no-build") {
1729
+ build = false;
1730
+ continue;
1731
+ }
1732
+ if (arg === "--timeout" && args[i + 1]) {
1733
+ timeout = parseInt(args[++i], 10);
1734
+ if (Number.isNaN(timeout) || timeout < 1)
1735
+ timeout = 300;
1736
+ continue;
1737
+ }
1738
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
1739
+ businessDomain = args[++i];
1740
+ continue;
1741
+ }
1742
+ if (arg === "--pretty") {
1743
+ pretty = true;
1744
+ continue;
1745
+ }
1746
+ if (!arg.startsWith("-") && !dsId) {
1747
+ dsId = arg;
1748
+ }
1749
+ }
1750
+ const tables = tablesStr ? tablesStr.split(",").map((s) => s.trim()).filter(Boolean) : [];
1751
+ if (!dsId || !name) {
1752
+ throw new Error("Usage: kweaver bkn create-from-ds <ds-id> --name X [options]");
1753
+ }
1754
+ return { dsId, name, tables, build, timeout, businessDomain, pretty };
1755
+ }
1756
+ async function runKnCreateFromDsCommand(args) {
1757
+ let options;
1758
+ try {
1759
+ options = parseKnCreateFromDsArgs(args);
1760
+ }
1761
+ catch (error) {
1762
+ if (error instanceof Error && error.message === "help") {
1763
+ console.log(KN_CREATE_FROM_DS_HELP);
1764
+ return 0;
1765
+ }
1766
+ console.error(formatHttpError(error));
1767
+ return 1;
1768
+ }
1769
+ try {
1770
+ const token = await ensureValidToken();
1771
+ const base = {
1772
+ baseUrl: token.baseUrl,
1773
+ accessToken: token.accessToken,
1774
+ businessDomain: options.businessDomain,
1775
+ };
1776
+ const tablesBody = await listTablesWithColumns({ ...base, id: options.dsId });
1777
+ const allTables = JSON.parse(tablesBody);
1778
+ const targetTables = options.tables.length > 0
1779
+ ? allTables.filter((t) => options.tables.includes(t.name))
1780
+ : allTables;
1781
+ if (targetTables.length === 0) {
1782
+ console.error("No tables available");
1783
+ return 1;
1784
+ }
1785
+ const viewMap = {};
1786
+ for (const t of targetTables) {
1787
+ const dvId = await createDataView({
1788
+ ...base,
1789
+ name: t.name,
1790
+ datasourceId: options.dsId,
1791
+ table: t.name,
1792
+ fields: t.columns.map((c) => ({ name: c.name, type: c.type })),
1793
+ });
1794
+ viewMap[t.name] = dvId;
1795
+ }
1796
+ const knBody = JSON.stringify({
1797
+ name: options.name,
1798
+ branch: "main",
1799
+ base_branch: "",
1800
+ });
1801
+ const knResponse = await createKnowledgeNetwork({
1802
+ ...base,
1803
+ body: knBody,
1804
+ });
1805
+ const knParsed = JSON.parse(knResponse);
1806
+ const knItem = Array.isArray(knParsed) ? knParsed[0] : knParsed;
1807
+ const knId = String(knItem?.id ?? "");
1808
+ const otResults = [];
1809
+ for (const t of targetTables) {
1810
+ const pk = detectPrimaryKey(t);
1811
+ const dk = detectDisplayKey(t, pk);
1812
+ const entry = {
1813
+ name: t.name,
1814
+ data_source: { type: "data_view", id: viewMap[t.name] },
1815
+ primary_keys: [pk],
1816
+ display_key: dk,
1817
+ data_properties: [pk, dk].filter((x, i, a) => a.indexOf(x) === i).map((n) => ({
1818
+ name: n,
1819
+ display_name: n,
1820
+ type: "string",
1821
+ })),
1822
+ };
1823
+ const otBody = JSON.stringify({ entries: [entry], branch: "main" });
1824
+ const otResponse = await createObjectTypes({
1825
+ ...base,
1826
+ knId,
1827
+ body: otBody,
1828
+ });
1829
+ const otParsed = JSON.parse(otResponse);
1830
+ const otItem = otParsed.entries?.[0];
1831
+ otResults.push({
1832
+ name: t.name,
1833
+ id: otItem?.id ?? "",
1834
+ field_count: t.columns.length,
1835
+ });
1836
+ }
1837
+ let statusStr = "skipped";
1838
+ if (options.build) {
1839
+ console.error("Building ...");
1840
+ await buildKnowledgeNetwork({ ...base, knId });
1841
+ const deadline = Date.now() + options.timeout * 1000;
1842
+ const TERMINAL = ["completed", "failed", "success"];
1843
+ while (Date.now() < deadline) {
1844
+ await new Promise((r) => setTimeout(r, 2000));
1845
+ const statusBody = await getBuildStatus({ ...base, knId });
1846
+ const statusParsed = JSON.parse(statusBody);
1847
+ const jobs = Array.isArray(statusParsed) ? statusParsed : (statusParsed.entries ?? []);
1848
+ const state = (jobs[0]?.state ?? "running").toLowerCase();
1849
+ if (TERMINAL.includes(state)) {
1850
+ statusStr = state;
1851
+ break;
1852
+ }
1853
+ }
1854
+ }
1855
+ const output = {
1856
+ kn_id: knId,
1857
+ kn_name: options.name,
1858
+ object_types: otResults,
1859
+ status: statusStr,
1860
+ };
1861
+ console.log(JSON.stringify(output, null, options.pretty ? 2 : 0));
1862
+ return 0;
1863
+ }
1864
+ catch (error) {
1865
+ console.error(formatHttpError(error));
1866
+ return 1;
1867
+ }
1868
+ }
1213
1869
  async function runKnUpdateCommand(args) {
1214
1870
  let options;
1215
1871
  try {
@@ -1287,6 +1943,111 @@ async function runKnDeleteCommand(args) {
1287
1943
  return 1;
1288
1944
  }
1289
1945
  }
1946
+ const KN_BUILD_HELP = `kweaver bkn build <kn-id> [options]
1947
+
1948
+ Trigger a full build for a knowledge network.
1949
+
1950
+ Options:
1951
+ --wait (default) Poll until build completes
1952
+ --no-wait Return immediately after triggering
1953
+ --timeout <seconds> Max wait time when --wait (default: 300)
1954
+ -bd, --biz-domain Business domain (default: bd_public)`;
1955
+ export function parseKnBuildArgs(args) {
1956
+ let knId = "";
1957
+ let wait = true;
1958
+ let timeout = 300;
1959
+ let businessDomain = "bd_public";
1960
+ for (let i = 0; i < args.length; i += 1) {
1961
+ const arg = args[i];
1962
+ if (arg === "--help" || arg === "-h")
1963
+ throw new Error("help");
1964
+ if (arg === "--wait") {
1965
+ wait = true;
1966
+ continue;
1967
+ }
1968
+ if (arg === "--no-wait") {
1969
+ wait = false;
1970
+ continue;
1971
+ }
1972
+ if (arg === "--timeout" && args[i + 1]) {
1973
+ timeout = parseInt(args[i + 1], 10);
1974
+ if (Number.isNaN(timeout) || timeout < 1)
1975
+ timeout = 300;
1976
+ i += 1;
1977
+ continue;
1978
+ }
1979
+ if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
1980
+ businessDomain = args[i + 1];
1981
+ i += 1;
1982
+ continue;
1983
+ }
1984
+ if (!arg.startsWith("-") && !knId) {
1985
+ knId = arg;
1986
+ }
1987
+ }
1988
+ if (!knId) {
1989
+ throw new Error("Missing kn-id. Usage: kweaver bkn build <kn-id> [options]");
1990
+ }
1991
+ return { knId, wait, timeout, businessDomain };
1992
+ }
1993
+ async function runKnBuildCommand(args) {
1994
+ let options;
1995
+ try {
1996
+ options = parseKnBuildArgs(args);
1997
+ }
1998
+ catch (error) {
1999
+ if (error instanceof Error && error.message === "help") {
2000
+ console.log(KN_BUILD_HELP);
2001
+ return 0;
2002
+ }
2003
+ console.error(formatHttpError(error));
2004
+ return 1;
2005
+ }
2006
+ const TERMINAL_STATES = ["completed", "failed", "success"];
2007
+ try {
2008
+ const token = await ensureValidToken();
2009
+ await buildKnowledgeNetwork({
2010
+ baseUrl: token.baseUrl,
2011
+ accessToken: token.accessToken,
2012
+ knId: options.knId,
2013
+ businessDomain: options.businessDomain,
2014
+ });
2015
+ console.error(`Build started for ${options.knId}`);
2016
+ if (!options.wait) {
2017
+ console.error("Build triggered (not waiting).");
2018
+ return 0;
2019
+ }
2020
+ console.error("Waiting for build to complete ...");
2021
+ const deadline = Date.now() + options.timeout * 1000;
2022
+ while (Date.now() < deadline) {
2023
+ await new Promise((r) => setTimeout(r, 2000));
2024
+ const body = await getBuildStatus({
2025
+ baseUrl: token.baseUrl,
2026
+ accessToken: token.accessToken,
2027
+ knId: options.knId,
2028
+ businessDomain: options.businessDomain,
2029
+ });
2030
+ const parsed = JSON.parse(body);
2031
+ const jobs = Array.isArray(parsed) ? parsed : (parsed.entries ?? parsed.data ?? []);
2032
+ const job = jobs[0];
2033
+ const state = (job?.state ?? "running").toLowerCase();
2034
+ const detail = job?.state_detail;
2035
+ if (TERMINAL_STATES.includes(state)) {
2036
+ console.log(state);
2037
+ if (detail) {
2038
+ console.log(`Detail: ${detail}`);
2039
+ }
2040
+ return state === "failed" ? 1 : 0;
2041
+ }
2042
+ }
2043
+ console.error(`Build did not complete within ${options.timeout}s`);
2044
+ return 1;
2045
+ }
2046
+ catch (error) {
2047
+ console.error(formatHttpError(error));
2048
+ return 1;
2049
+ }
2050
+ }
1290
2051
  // ── search ──────────────────────────────────────────────────────────────────
1291
2052
  const KN_SEARCH_HELP = `kweaver bkn search <kn-id> <query> [--max-concepts <n>] [--mode <mode>] [--pretty] [-bd value]
1292
2053