@kweaver-ai/kweaver-sdk 0.7.1 → 0.7.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.
@@ -4,14 +4,36 @@ import { resolve } from "node:path";
4
4
  import { loadNetwork, allObjects, allRelations, allActions, generateChecksum, validateNetwork } from "@kweaver-ai/bkn";
5
5
  import { prepareBknDirectoryForImport, stripBknEncodingCliArgs, } from "../utils/bkn-encoding.js";
6
6
  import { ensureValidToken, formatHttpError } from "../auth/oauth.js";
7
- import { createKnowledgeNetwork, createObjectTypes, buildKnowledgeNetwork, getBuildStatus, } from "../api/knowledge-networks.js";
7
+ import { createKnowledgeNetwork, createObjectTypes, deleteKnowledgeNetwork, buildKnowledgeNetwork, getBuildStatus, } from "../api/knowledge-networks.js";
8
8
  import { listTablesWithColumns, scanMetadata, getDatasource } from "../api/datasources.js";
9
9
  import { createDataView, findDataView } from "../api/dataviews.js";
10
+ import { resolveFiles } from "./ds.js";
11
+ import { buildTableName } from "./import-csv.js";
10
12
  import { downloadBkn, uploadBkn, listActionSchedules, getActionSchedule, createActionSchedule, updateActionSchedule, setActionScheduleStatus, deleteActionSchedules, listJobs, getJob, getJobTasks, deleteJobs, } from "../api/bkn-backend.js";
11
13
  import { formatCallOutput } from "./call.js";
12
14
  import { resolveBusinessDomain } from "../config/store.js";
13
15
  import { runDsImportCsv } from "./ds.js";
14
16
  import { pollWithBackoff, detectPrimaryKey, detectDisplayKey, confirmYes, } from "./bkn-utils.js";
17
+ // ── BKN object name validation ──────────────────────────────────────────────
18
+ // Mirrors bkn-backend OBJECT_NAME_MAX_LENGTH (interfaces/common.go:28) and
19
+ // validateObjectName (driveradapters/validate.go:85). 40 utf-8 codepoints,
20
+ // non-empty. Backend rejects the whole batch on first violation, so we surface
21
+ // every offender locally before any side-effecting call.
22
+ export const BKN_OBJECT_NAME_MAX_LENGTH = 40;
23
+ export function assertValidBknObjectNames(names, context) {
24
+ const offenders = [];
25
+ for (const name of names) {
26
+ const len = [...name].length;
27
+ if (len === 0 || len > BKN_OBJECT_NAME_MAX_LENGTH) {
28
+ offenders.push({ name, length: len });
29
+ }
30
+ }
31
+ if (offenders.length === 0)
32
+ return;
33
+ const lines = offenders.map((o) => ` - ${o.name} (${o.length} chars)`);
34
+ throw new Error(`${context}: ${offenders.length} name(s) violate BKN object-name limit ` +
35
+ `(1..${BKN_OBJECT_NAME_MAX_LENGTH} utf-8 chars):\n${lines.join("\n")}`);
36
+ }
15
37
  // ── Build ───────────────────────────────────────────────────────────────────
16
38
  const KN_BUILD_HELP = `kweaver bkn build <kn-id> [options]
17
39
 
@@ -461,6 +483,7 @@ Options:
461
483
  --build (default) Build after creation
462
484
  --no-build Skip build after creation
463
485
  --timeout <n> Build timeout in seconds (default: 300)
486
+ --no-rollback Keep partially-created KN on failure (debug; default: rollback)
464
487
  -bd, --biz-domain Business domain (default: bd_public)
465
488
  --pretty Pretty-print output (default)`;
466
489
  export function parseKnCreateFromDsArgs(args) {
@@ -471,6 +494,7 @@ export function parseKnCreateFromDsArgs(args) {
471
494
  let timeout = 300;
472
495
  let businessDomain = "";
473
496
  let pretty = true;
497
+ let noRollback = false;
474
498
  for (let i = 0; i < args.length; i += 1) {
475
499
  const arg = args[i];
476
500
  if (arg === "--help" || arg === "-h")
@@ -491,6 +515,10 @@ export function parseKnCreateFromDsArgs(args) {
491
515
  build = false;
492
516
  continue;
493
517
  }
518
+ if (arg === "--no-rollback") {
519
+ noRollback = true;
520
+ continue;
521
+ }
494
522
  if (arg === "--timeout" && args[i + 1]) {
495
523
  timeout = parseInt(args[++i], 10);
496
524
  if (Number.isNaN(timeout) || timeout < 1)
@@ -515,7 +543,7 @@ export function parseKnCreateFromDsArgs(args) {
515
543
  }
516
544
  if (!businessDomain)
517
545
  businessDomain = resolveBusinessDomain();
518
- return { dsId, name, tables, build, timeout, businessDomain, pretty };
546
+ return { dsId, name, tables, build, timeout, businessDomain, pretty, noRollback };
519
547
  }
520
548
  /** Sanitize a table name into a BKN-safe ID (alphanumeric + underscore). */
521
549
  function sanitizeBknId(name) {
@@ -576,7 +604,12 @@ export async function runKnCreateFromDsCommand(args, sampleRows) {
576
604
  console.error("No tables available");
577
605
  return 1;
578
606
  }
579
- // Phase 1: Create DataViews for each table
607
+ // Pre-flight: catch every offending OT name before any side effect.
608
+ // Backend rejects the whole batch on first violation (validate.go:90),
609
+ // so retroactive rollback is wasted work if we can fail fast here.
610
+ assertValidBknObjectNames(targetTables.map((t) => t.name), "Object type names derived from table names");
611
+ // Phase 1: Create DataViews for each table. findDataView is idempotent;
612
+ // not tracked for rollback so a retry can reuse what's already there.
580
613
  console.error(`Creating data views for ${targetTables.length} table(s) ...`);
581
614
  const viewMap = {};
582
615
  for (const t of targetTables) {
@@ -597,7 +630,8 @@ export async function runKnCreateFromDsCommand(args, sampleRows) {
597
630
  }));
598
631
  viewMap[t.name] = dvId;
599
632
  }
600
- // Phase 2: Create the KN record
633
+ // Phase 2: Create the KN. If any subsequent step fails we DELETE this
634
+ // KN — backend cascades to OTs (knowledge_network_service.go:917-969).
601
635
  const knBody = JSON.stringify({
602
636
  name: options.name,
603
637
  branch: "main",
@@ -611,72 +645,91 @@ export async function runKnCreateFromDsCommand(args, sampleRows) {
611
645
  const knItem = Array.isArray(knParsed) ? knParsed[0] : knParsed;
612
646
  const knId = String(knItem?.id ?? "");
613
647
  console.error(`Knowledge network created: ${knId}`);
614
- // Phase 3: Create object types via REST API
615
- console.error(`Creating ${targetTables.length} object type(s) ...`);
648
+ let createdKnId = knId;
616
649
  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] });
650
+ let statusStr = "skipped";
651
+ try {
652
+ // Phase 3: Single batched POST. Backend wraps all entries in one tx
653
+ // (object_type_service.go:213-355) all-or-nothing.
654
+ console.error(`Creating ${targetTables.length} object type(s) ...`);
655
+ const entries = targetTables.map((t) => {
656
+ const pk = detectPrimaryKey(t, sampleRows?.[t.name]);
657
+ const dk = detectDisplayKey(t, pk);
658
+ return {
659
+ branch: "main",
660
+ name: t.name,
661
+ data_source: { type: "data_view", id: viewMap[t.name] },
662
+ primary_keys: [pk],
663
+ display_key: dk,
664
+ data_properties: t.columns.map((c) => ({
665
+ name: c.name,
666
+ display_name: c.name,
667
+ type: "string",
668
+ mapped_field: { name: c.name, type: c.type || "varchar" },
669
+ })),
670
+ _meta: { pk, dk },
671
+ };
672
+ });
673
+ const wireEntries = entries.map(({ _meta: _, ...rest }) => rest);
674
+ const otBody = JSON.stringify({ entries: wireEntries });
635
675
  const otResponse = await createObjectTypes({
636
676
  ...base,
637
677
  knId,
638
678
  body: otBody,
639
679
  });
640
680
  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,
681
+ const otItems = Array.isArray(otParsed) ? otParsed : (otParsed.entries ?? []);
682
+ for (let i = 0; i < entries.length; i += 1) {
683
+ const t = targetTables[i];
684
+ const meta = entries[i]._meta;
685
+ otResults.push({
686
+ name: t.name,
687
+ id: otItems[i]?.id ?? "",
688
+ field_count: t.columns.length,
676
689
  });
690
+ console.error(` Created: ${t.name} (${t.columns.length} fields, pk=${meta.pk}, dk=${meta.dk})`);
677
691
  }
678
- catch {
679
- // timeout — statusStr remains "skipped"
692
+ if (options.build) {
693
+ console.error("Building ...");
694
+ await buildKnowledgeNetwork({ ...base, knId });
695
+ const TERMINAL = ["completed", "failed", "success"];
696
+ try {
697
+ statusStr = await pollWithBackoff({
698
+ fn: async () => {
699
+ const statusBody = await getBuildStatus({ ...base, knId });
700
+ const statusParsed = JSON.parse(statusBody);
701
+ const jobs = Array.isArray(statusParsed) ? statusParsed : (statusParsed.entries ?? []);
702
+ const state = (jobs[0]?.state ?? "running").toLowerCase();
703
+ if (TERMINAL.includes(state))
704
+ return { done: true, value: state };
705
+ return { done: false, value: "running" };
706
+ },
707
+ interval: 2000,
708
+ timeout: options.timeout * 1000,
709
+ });
710
+ }
711
+ catch {
712
+ // build timeout — KN itself is fine, just mark skipped
713
+ }
714
+ }
715
+ // Reached the end without throwing — clear the rollback handle.
716
+ createdKnId = undefined;
717
+ }
718
+ finally {
719
+ if (createdKnId !== undefined) {
720
+ if (options.noRollback) {
721
+ console.error(`Leaving partial KN ${createdKnId} in place (--no-rollback)`);
722
+ }
723
+ else {
724
+ console.error(`Rolling back KN ${createdKnId} ...`);
725
+ try {
726
+ await deleteKnowledgeNetwork({ ...base, knId: createdKnId });
727
+ console.error(`Rolled back KN ${createdKnId}`);
728
+ }
729
+ catch (rollbackErr) {
730
+ console.error(`Rollback failed for KN ${createdKnId}: ${formatHttpError(rollbackErr)}`);
731
+ }
732
+ }
680
733
  }
681
734
  }
682
735
  const output = {
@@ -708,6 +761,7 @@ Options:
708
761
  --no-build Skip build
709
762
  --recreate Use "insert" mode on first batch (only effective for new tables)
710
763
  --timeout <n> Build timeout in seconds (default: 300)
764
+ --no-rollback Keep partially-created KN on failure (debug; default: rollback)
711
765
  -bd, --biz-domain Business domain (default: bd_public)`;
712
766
  export function parseKnCreateFromCsvArgs(args) {
713
767
  let dsId = "";
@@ -720,6 +774,7 @@ export function parseKnCreateFromCsvArgs(args) {
720
774
  let recreate = false;
721
775
  let timeout = 300;
722
776
  let businessDomain = "";
777
+ let noRollback = false;
723
778
  for (let i = 0; i < args.length; i += 1) {
724
779
  const arg = args[i];
725
780
  if (arg === "--help" || arg === "-h")
@@ -758,6 +813,10 @@ export function parseKnCreateFromCsvArgs(args) {
758
813
  recreate = true;
759
814
  continue;
760
815
  }
816
+ if (arg === "--no-rollback") {
817
+ noRollback = true;
818
+ continue;
819
+ }
761
820
  if (arg === "--timeout" && args[i + 1]) {
762
821
  timeout = parseInt(args[++i], 10);
763
822
  if (Number.isNaN(timeout) || timeout < 1)
@@ -778,7 +837,7 @@ export function parseKnCreateFromCsvArgs(args) {
778
837
  }
779
838
  if (!businessDomain)
780
839
  businessDomain = resolveBusinessDomain();
781
- return { dsId, files, name, tablePrefix, batchSize, tables, build, recreate, timeout, businessDomain };
840
+ return { dsId, files, name, tablePrefix, batchSize, tables, build, recreate, timeout, businessDomain, noRollback };
782
841
  }
783
842
  export async function runKnCreateFromCsvCommand(args) {
784
843
  let options;
@@ -793,6 +852,20 @@ export async function runKnCreateFromCsvCommand(args) {
793
852
  console.error(formatHttpError(error));
794
853
  return 1;
795
854
  }
855
+ // Pre-flight: predict OT names from (table-prefix + csv basename) and
856
+ // reject before any CSV is imported. CSV import is expensive; failing
857
+ // here saves the user a multi-minute round trip.
858
+ try {
859
+ const filePaths = await resolveFiles(options.files);
860
+ const predictedNames = options.tables.length > 0
861
+ ? options.tables
862
+ : filePaths.map((p) => buildTableName(p, options.tablePrefix));
863
+ assertValidBknObjectNames(predictedNames, "Object type names derived from CSV file names");
864
+ }
865
+ catch (error) {
866
+ console.error(formatHttpError(error));
867
+ return 1;
868
+ }
796
869
  // Phase 1: Import CSVs
797
870
  console.error("Phase 1: Importing CSVs ...");
798
871
  const importArgs = [
@@ -844,6 +917,7 @@ export async function runKnCreateFromCsvCommand(args) {
844
917
  options.build ? "--build" : "--no-build",
845
918
  "--timeout", String(options.timeout),
846
919
  "-bd", options.businessDomain,
920
+ ...(options.noRollback ? ["--no-rollback"] : []),
847
921
  ];
848
922
  return runKnCreateFromDsCommand(knArgs, importResult.sampleRows);
849
923
  }
@@ -2,6 +2,7 @@ import { listBusinessDomains } from "../api/business-domains.js";
2
2
  import { fetchEacpUserInfo, resolveActivePlatform, withTokenRetry } from "../auth/oauth.js";
3
3
  import { HttpError } from "../utils/http.js";
4
4
  import { loadPlatformBusinessDomain, resolveBusinessDomain, savePlatformBusinessDomain, } from "../config/store.js";
5
+ import { assertNotStatelessForWrite } from "../config/stateless.js";
5
6
  const HELP = `kweaver config
6
7
 
7
8
  Subcommands:
@@ -50,6 +51,13 @@ export async function runConfigCommand(args) {
50
51
  return 1;
51
52
  }
52
53
  const platform = active.url;
54
+ try {
55
+ assertNotStatelessForWrite("config set-bd");
56
+ }
57
+ catch (err) {
58
+ console.error(err instanceof Error ? err.message : String(err));
59
+ return 1;
60
+ }
53
61
  savePlatformBusinessDomain(platform, value);
54
62
  const provenance = active.source === "env" ? `${platform} via KWEAVER_BASE_URL` : platform;
55
63
  console.log(`Business domain set to: ${value} (${provenance})`);
@@ -2,12 +2,28 @@ import { ensureValidToken, formatHttpError, resolveActivePlatform, with401Refres
2
2
  import { callTool, searchSchema, queryObjectInstance, queryInstanceSubgraph, getLogicPropertiesValues, getActionInfo, findSkills, listTools, listResources, readResource, listResourceTemplates, listPrompts, getPrompt, } from "../api/context-loader.js";
3
3
  import { knSearchHttp, semanticSearch } from "../api/semantic-search.js";
4
4
  import { addContextLoaderEntry, getCurrentContextLoaderKn, loadContextLoaderConfig, removeContextLoaderEntry, resolveBusinessDomain, setCurrentContextLoader, } from "../config/store.js";
5
+ import { assertNotStatelessForWrite } from "../config/stateless.js";
6
+ const CONTEXT_LOADER_CONFIG_DEPRECATION = "[deprecated] `kweaver context-loader config ...` will be removed in a future release. " +
7
+ "Pass <kn-id> as the first positional to runtime subcommands instead, e.g. " +
8
+ "`kweaver context-loader tools <kn-id>` (or use the `--kn-id <id>` flag).";
5
9
  const MCP_NOT_CONFIGURED = "Context-loader MCP is not configured. Run: kweaver context-loader config set --kn-id <kn-id>";
6
- function ensureContextLoaderConfig() {
10
+ const MCP_PATH = "/api/agent-retrieval/v1/mcp";
11
+ function ensureContextLoaderConfig(knIdOverride) {
7
12
  const active = resolveActivePlatform();
8
13
  if (!active) {
9
14
  throw new Error("No platform selected. Set KWEAVER_BASE_URL or run: kweaver auth <platform-url>");
10
15
  }
16
+ // Override path (positional <kn-id> or --kn-id flag): derive MCP URL from
17
+ // the active platform; do not touch the deprecated saved config.
18
+ if (knIdOverride) {
19
+ return {
20
+ baseUrl: active.url,
21
+ mcpUrl: active.url.replace(/\/+$/, "") + MCP_PATH,
22
+ knId: knIdOverride,
23
+ accessToken: "",
24
+ businessDomain: resolveBusinessDomain(active.url),
25
+ };
26
+ }
11
27
  const kn = getCurrentContextLoaderKn();
12
28
  if (!kn) {
13
29
  throw new Error(MCP_NOT_CONFIGURED);
@@ -20,6 +36,50 @@ function ensureContextLoaderConfig() {
20
36
  businessDomain: resolveBusinessDomain(active.url),
21
37
  };
22
38
  }
39
+ // Subcommands that consult `ensureContextLoaderConfig`. The number is the
40
+ // minimum non-flag positional count expected by the handler itself (after
41
+ // kn-id is extracted). When the leading non-flag positional count exceeds
42
+ // this minimum, the first one is treated as <kn-id>.
43
+ const RUNTIME_MIN_POSITIONALS = {
44
+ tools: 0,
45
+ resources: 0,
46
+ templates: 0,
47
+ prompts: 0,
48
+ prompt: 1,
49
+ resource: 1,
50
+ "search-schema": 1,
51
+ "tool-call": 1,
52
+ "kn-search": 1,
53
+ "kn-schema-search": 1,
54
+ "query-object-instance": 1,
55
+ "query-instance-subgraph": 1,
56
+ "get-logic-properties": 1,
57
+ "get-action-info": 1,
58
+ "find-skills": 1,
59
+ };
60
+ function extractKnIdOverride(subcommand, rest) {
61
+ // 1) Explicit flag wins. `--kn-id <id>` / `-k <id>` is allowed for every
62
+ // runtime subcommand and is consumed before the handler sees `rest`.
63
+ for (let i = 0; i < rest.length; i += 1) {
64
+ if ((rest[i] === "--kn-id" || rest[i] === "-k") && rest[i + 1]) {
65
+ const id = rest[i + 1];
66
+ rest.splice(i, 2);
67
+ return id;
68
+ }
69
+ }
70
+ // 2) Positional <kn-id> as the first non-flag arg, when leading non-flag
71
+ // positional count exceeds what the handler itself requires.
72
+ const min = RUNTIME_MIN_POSITIONALS[subcommand];
73
+ if (min === undefined)
74
+ return undefined;
75
+ let cut = 0;
76
+ while (cut < rest.length && !rest[cut].startsWith("-"))
77
+ cut += 1;
78
+ if (cut > min) {
79
+ return rest.shift();
80
+ }
81
+ return undefined;
82
+ }
23
83
  function formatOutput(value, pretty) {
24
84
  const json = JSON.stringify(value, null, pretty ? 2 : 0);
25
85
  return json;
@@ -29,34 +89,38 @@ export async function runContextLoaderCommand(args) {
29
89
  if (!subcommand || subcommand === "--help" || subcommand === "-h") {
30
90
  console.log(`kweaver context-loader
31
91
 
92
+ KN selection (for runtime subcommands below):
93
+ Pass <kn-id> as the FIRST positional, e.g. \`kweaver context-loader tools <kn-id>\`,
94
+ or use the global \`--kn-id <id>\` / \`-k <id>\` flag. When omitted, falls back to
95
+ the deprecated saved config managed by \`kweaver context-loader config\`.
96
+
32
97
  Subcommands:
33
- config set --kn-id <id> [--name n] Add or update kn config (MCP URL derived from platform)
34
- config use <name> Switch current config
35
- config list List all configs and current
36
- config remove <name> Remove a config
37
- config show Show current config (knId + mcpUrl)
38
- tools tools/list - list available tools
39
- resources resources/list - list resources
40
- resource <uri> resources/read - read resource by URI
41
- templates resources/templates/list - list resource templates
42
- prompts prompts/list - list prompts
43
- prompt <name> [--args json] prompts/get - get prompt by name
44
- search-schema <query> [options] MCP search_schema (object/relation/action/metric types)
45
- tool-call <name> --args '<json>' MCP tools/call for any server tool
46
- kn-search <query> [--only-schema] Compatibility: HTTP kn_search
47
- kn-schema-search <query> [--max N] Compatibility: HTTP semantic-search
48
- query-object-instance <json> Layer 2: Query instances (args as JSON)
49
- query-instance-subgraph <json> Layer 2: Query subgraph (args as JSON)
50
- get-logic-properties <json> Layer 3: Get logic property values (args as JSON)
51
- get-action-info <json> Layer 3: Get action info (args as JSON)
52
- find-skills <ot_id> [options] Layer 3: Recall skills for an object type
98
+ config set --kn-id <id> [--name n] [deprecated] Add or update kn config
99
+ config use <name> [deprecated] Switch current config
100
+ config list [deprecated] List all configs and current
101
+ config remove <name> [deprecated] Remove a config
102
+ config show [deprecated] Show current config (knId + mcpUrl)
103
+ tools <kn-id> tools/list - list available tools
104
+ resources <kn-id> resources/list - list resources
105
+ resource <kn-id> <uri> resources/read - read resource by URI
106
+ templates <kn-id> resources/templates/list - list resource templates
107
+ prompts <kn-id> prompts/list - list prompts
108
+ prompt <kn-id> <name> [--args json] prompts/get - get prompt by name
109
+ search-schema <kn-id> <query> [opts] MCP search_schema (object/relation/action/metric)
110
+ tool-call <kn-id> <name> --args '<json>' MCP tools/call for any server tool
111
+ kn-search <kn-id> <query> [--only-schema] Compatibility: HTTP kn_search
112
+ kn-schema-search <kn-id> <query> [--max N] Compatibility: HTTP semantic-search
113
+ query-object-instance <kn-id> <json> Layer 2: Query instances
114
+ query-instance-subgraph <kn-id> <json> Layer 2: Query subgraph
115
+ get-logic-properties <kn-id> <json> Layer 3: Get logic property values
116
+ get-action-info <kn-id> <json> Layer 3: Get action info
117
+ find-skills <kn-id> <ot_id> [options] Layer 3: Recall skills for an object type
53
118
 
54
119
  Examples:
55
- kweaver context-loader config set --kn-id d5iv6c9818p72mpje8pg
56
- kweaver context-loader config set --kn-id xyz123 --name project-a
57
- kweaver context-loader search-schema "利润率" --scope object,metric --max 5
58
- kweaver context-loader tool-call search_schema --args '{"query":"利润率"}'
59
- kweaver context-loader kn-search "高血压 治疗 药品" --only-schema --pretty`);
120
+ kweaver context-loader tools d5iv6c9818p72mpje8pg
121
+ kweaver context-loader search-schema d5iv6c9818p72mpje8pg "利润率" --scope object,metric --max 5
122
+ kweaver context-loader tool-call d5iv6c9818p72mpje8pg search_schema --args '{"query":"利润率"}'
123
+ kweaver context-loader kn-search d5iv6c9818p72mpje8pg "高血压 治疗 药品" --only-schema --pretty`);
60
124
  return 0;
61
125
  }
62
126
  if (subcommand === "config") {
@@ -68,9 +132,12 @@ Examples:
68
132
  pretty = true;
69
133
  rest.splice(prettyIdx, 1);
70
134
  }
135
+ // Extract `<kn-id>` (positional or --kn-id/-k flag) before per-subcommand
136
+ // arg parsing. When provided it bypasses the deprecated saved config.
137
+ const knIdOverride = extractKnIdOverride(subcommand, rest);
71
138
  const dispatch = async () => {
72
139
  const token = await ensureValidToken();
73
- const base = ensureContextLoaderConfig();
140
+ const base = ensureContextLoaderConfig(knIdOverride);
74
141
  const options = { ...base, accessToken: token.accessToken };
75
142
  if (subcommand === "tools")
76
143
  return runListTools(options, rest, pretty);
@@ -122,16 +189,30 @@ Examples:
122
189
  async function runConfigCommand(args) {
123
190
  const [action, ...rest] = args;
124
191
  if (!action || action === "--help" || action === "-h") {
125
- console.log(`kweaver context-loader config
192
+ console.log(`kweaver context-loader config [deprecated]
126
193
 
127
194
  Subcommands:
128
195
  set --kn-id <id> [--name <name>] Add or update kn config (default name: default)
129
196
  use <name> Switch current config
130
197
  list List all configs and current
131
198
  remove <name> Remove a config
132
- show Show current config (knId + mcpUrl)`);
199
+ show Show current config (knId + mcpUrl)
200
+
201
+ Note: this command group is deprecated and will be removed in a future release.
202
+ It is disabled entirely in stateless mode (\`--token\`).`);
133
203
  return 0;
134
204
  }
205
+ // Stateless mode (`--token`) does not support any context-loader config
206
+ // operations; the saved config lives under `~/.kweaver/` and is foreign
207
+ // to the stateless paradigm.
208
+ try {
209
+ assertNotStatelessForWrite(`context-loader config ${action}`);
210
+ }
211
+ catch (err) {
212
+ console.error(err instanceof Error ? err.message : String(err));
213
+ return 1;
214
+ }
215
+ console.warn(CONTEXT_LOADER_CONFIG_DEPRECATION);
135
216
  const active = resolveActivePlatform();
136
217
  if (!active) {
137
218
  console.error("No platform selected. Set KWEAVER_BASE_URL or run: kweaver auth <platform-url>");
@@ -435,29 +516,24 @@ async function runToolCall(options, args, pretty) {
435
516
  async function runKnSearch(options, args, pretty) {
436
517
  let query;
437
518
  let onlySchema = false;
438
- let knIdOverride;
439
519
  for (let i = 0; i < args.length; i += 1) {
440
520
  const arg = args[i];
441
521
  if (arg === "--only-schema") {
442
522
  onlySchema = true;
443
523
  }
444
- else if ((arg === "--kn-id" || arg === "-k") && args[i + 1]) {
445
- knIdOverride = args[i + 1];
446
- i += 1;
447
- }
448
524
  else if (!arg.startsWith("-") && !query) {
449
525
  query = arg;
450
526
  }
451
527
  }
452
528
  if (!query) {
453
- console.error("Usage: kweaver context-loader kn-search <query> [--kn-id <id>] [--only-schema]");
529
+ console.error("Usage: kweaver context-loader kn-search <kn-id> <query> [--only-schema]");
454
530
  return 1;
455
531
  }
456
532
  const raw = await knSearchHttp({
457
533
  baseUrl: options.baseUrl,
458
534
  accessToken: options.accessToken,
459
535
  businessDomain: options.businessDomain,
460
- knId: knIdOverride ?? options.knId,
536
+ knId: options.knId,
461
537
  query,
462
538
  onlySchema,
463
539
  });
@@ -5,6 +5,29 @@ export declare function parseDsListArgs(args: string[]): {
5
5
  businessDomain: string;
6
6
  pretty: boolean;
7
7
  };
8
+ /**
9
+ * Match candidate signature against a list response (the kind returned by
10
+ * `listDatasources`). Connection metadata lives under `bin_data` — host,
11
+ * port, account, plus type-specific fields (`database_name` for MySQL etc.).
12
+ *
13
+ * Exported for unit testing.
14
+ */
15
+ export interface DsMatchSignature {
16
+ type: string;
17
+ host: string;
18
+ port: number;
19
+ database: string;
20
+ account: string;
21
+ name?: string;
22
+ }
23
+ export interface DsMatchHit {
24
+ id: string;
25
+ name: string;
26
+ matchedByName: boolean;
27
+ matchedByTuple: boolean;
28
+ }
29
+ export declare function findExistingDatasource(listBody: string, sig: DsMatchSignature): DsMatchHit | undefined;
30
+ export declare function findDatasourceIdByName(listBody: string, name: string): string | undefined;
8
31
  export declare function parseImportCsvArgs(args: string[]): {
9
32
  datasourceId: string;
10
33
  files: string;