@kweaver-ai/kweaver-sdk 0.7.3 → 0.7.4

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 (43) hide show
  1. package/README.md +29 -0
  2. package/README.zh.md +26 -0
  3. package/bin/kweaver.js +12 -11
  4. package/dist/api/bkn-backend.d.ts +1 -0
  5. package/dist/api/bkn-backend.js +1 -1
  6. package/dist/api/bkn-metrics.d.ts +59 -0
  7. package/dist/api/bkn-metrics.js +129 -0
  8. package/dist/api/conversations.d.ts +47 -2
  9. package/dist/api/conversations.js +113 -17
  10. package/dist/api/datasources.js +43 -6
  11. package/dist/api/model-invocation.d.ts +58 -0
  12. package/dist/api/model-invocation.js +203 -0
  13. package/dist/api/models.d.ts +79 -0
  14. package/dist/api/models.js +183 -0
  15. package/dist/api/ontology-query-metrics.d.ts +14 -0
  16. package/dist/api/ontology-query-metrics.js +30 -0
  17. package/dist/bundled-model-templates.d.ts +17 -0
  18. package/dist/bundled-model-templates.js +24 -0
  19. package/dist/cli.js +10 -0
  20. package/dist/client.d.ts +3 -0
  21. package/dist/client.js +5 -0
  22. package/dist/commands/agent.d.ts +7 -1
  23. package/dist/commands/agent.js +75 -21
  24. package/dist/commands/bkn-metric.d.ts +1 -0
  25. package/dist/commands/bkn-metric.js +406 -0
  26. package/dist/commands/bkn-ops.js +17 -11
  27. package/dist/commands/bkn-utils.d.ts +29 -0
  28. package/dist/commands/bkn-utils.js +37 -0
  29. package/dist/commands/bkn.js +4 -0
  30. package/dist/commands/ds.js +7 -1
  31. package/dist/commands/explore-chat.js +2 -2
  32. package/dist/commands/model.d.ts +72 -0
  33. package/dist/commands/model.js +1315 -0
  34. package/dist/index.d.ts +9 -0
  35. package/dist/index.js +5 -0
  36. package/dist/resources/models.d.ts +40 -0
  37. package/dist/resources/models.js +88 -0
  38. package/dist/templates/model/llm-basic.json +13 -0
  39. package/dist/templates/model/manifest.json +16 -0
  40. package/dist/templates/model/small-basic.json +6 -0
  41. package/dist/utils/trace-views.d.ts +44 -0
  42. package/dist/utils/trace-views.js +425 -0
  43. package/package.json +3 -3
package/dist/client.js CHANGED
@@ -14,6 +14,7 @@ import { BknResource } from "./resources/bkn.js";
14
14
  import { SkillsResource } from "./resources/skills.js";
15
15
  import { ToolboxesResource } from "./resources/toolboxes.js";
16
16
  import { VegaResource } from "./resources/vega.js";
17
+ import { ModelsResource } from "./resources/models.js";
17
18
  // ── KWeaverClient ─────────────────────────────────────────────────────────────
18
19
  /**
19
20
  * Main entry point for the KWeaver TypeScript SDK.
@@ -67,6 +68,8 @@ export class KWeaverClient {
67
68
  skills;
68
69
  /** Toolbox / tool management plus execute & debug invocation. */
69
70
  toolboxes;
71
+ /** Model factory (mf-model-manager CRUD + mf-model-api chat / embedding / rerank). */
72
+ models;
70
73
  constructor(opts = {}) {
71
74
  const envDomain = process.env.KWEAVER_BUSINESS_DOMAIN;
72
75
  if (opts.auth === false && opts.config) {
@@ -101,6 +104,7 @@ export class KWeaverClient {
101
104
  this.vega = new VegaResource(this);
102
105
  this.skills = new SkillsResource(this);
103
106
  this.toolboxes = new ToolboxesResource(this);
107
+ this.models = new ModelsResource(this);
104
108
  return;
105
109
  }
106
110
  if (opts.config) {
@@ -156,6 +160,7 @@ export class KWeaverClient {
156
160
  this.vega = new VegaResource(this);
157
161
  this.skills = new SkillsResource(this);
158
162
  this.toolboxes = new ToolboxesResource(this);
163
+ this.models = new ModelsResource(this);
159
164
  }
160
165
  /**
161
166
  * Async factory that auto-refreshes expired or revoked tokens.
@@ -65,8 +65,14 @@ export interface AgentHistoryOptions {
65
65
  }
66
66
  export declare function parseAgentHistoryArgs(args: string[]): AgentHistoryOptions;
67
67
  export interface AgentTraceOptions {
68
- agentId: string;
68
+ /** Optional. Retained as positional arg for backward compatibility; trace-ai keys by conversation_id only. */
69
+ agentId?: string;
69
70
  conversationId: string;
71
+ view: "tree" | "perf" | "evidence" | "reasoning" | "all";
72
+ /** When true, emit raw JSON of the TracesByConversationResult instead of a rendered view. */
73
+ json: boolean;
74
+ /** Disable per-message truncation in the reasoning view. */
75
+ full: boolean;
70
76
  pretty: boolean;
71
77
  }
72
78
  export declare function parseAgentTraceArgs(args: string[]): AgentTraceOptions;
@@ -468,21 +468,43 @@ export function parseAgentHistoryArgs(args) {
468
468
  const finalConversationId = optionStartIndex === 2 ? args[1] : args[0];
469
469
  return { agentId: finalAgentId, conversationId: finalConversationId, businessDomain, pretty, limit };
470
470
  }
471
+ const TRACE_VIEWS = new Set(["tree", "perf", "evidence", "reasoning", "all"]);
471
472
  export function parseAgentTraceArgs(args) {
472
- const agentId = args[0];
473
- if (!agentId || agentId.startsWith("-")) {
474
- throw new Error("Missing agent_id");
475
- }
476
- const conversationId = args[1];
477
- if (!conversationId || conversationId.startsWith("-")) {
478
- throw new Error("Missing conversation_id");
479
- }
473
+ const positional = [];
474
+ let view = "tree";
475
+ let json = false;
476
+ let full = false;
480
477
  let pretty = true;
481
- for (let i = 2; i < args.length; i += 1) {
478
+ for (let i = 0; i < args.length; i += 1) {
482
479
  const arg = args[i];
483
480
  if (arg === "--help" || arg === "-h") {
484
481
  throw new Error("help");
485
482
  }
483
+ if (arg === "--view") {
484
+ const next = args[i + 1];
485
+ if (!next || !TRACE_VIEWS.has(next)) {
486
+ throw new Error(`--view requires one of: tree, perf, evidence, reasoning, all`);
487
+ }
488
+ view = next;
489
+ i += 1;
490
+ continue;
491
+ }
492
+ if (arg.startsWith("--view=")) {
493
+ const value = arg.slice("--view=".length);
494
+ if (!TRACE_VIEWS.has(value)) {
495
+ throw new Error(`--view requires one of: tree, perf, evidence, reasoning, all`);
496
+ }
497
+ view = value;
498
+ continue;
499
+ }
500
+ if (arg === "--json") {
501
+ json = true;
502
+ continue;
503
+ }
504
+ if (arg === "--full") {
505
+ full = true;
506
+ continue;
507
+ }
486
508
  if (arg === "--pretty") {
487
509
  pretty = true;
488
510
  continue;
@@ -491,9 +513,26 @@ export function parseAgentTraceArgs(args) {
491
513
  pretty = false;
492
514
  continue;
493
515
  }
494
- throw new Error(`Unsupported agent trace argument: ${arg}`);
516
+ if (arg.startsWith("-")) {
517
+ throw new Error(`Unsupported agent trace argument: ${arg}`);
518
+ }
519
+ positional.push(arg);
520
+ }
521
+ // Backward-compat: legacy form is `trace <agent_id> <conversation_id>`.
522
+ // New form: `trace <conversation_id>`. We disambiguate by argument count.
523
+ let agentId;
524
+ let conversationId;
525
+ if (positional.length === 0) {
526
+ throw new Error("Missing conversation_id");
527
+ }
528
+ else if (positional.length === 1) {
529
+ conversationId = positional[0];
495
530
  }
496
- return { agentId, conversationId, pretty };
531
+ else {
532
+ agentId = positional[0];
533
+ conversationId = positional[1];
534
+ }
535
+ return { agentId, conversationId, view, json, full, pretty };
497
536
  }
498
537
  export async function runAgentCommand(args) {
499
538
  const [subcommand, ...rest] = args;
@@ -520,7 +559,8 @@ Subcommands:
520
559
  skill <verb> ... Manage skills attached to an agent (add/remove/list)
521
560
  sessions <agent_id> List all conversations for an agent
522
561
  history <agent_id> <conversation_id> Show message history for a conversation
523
- trace <agent_id> <conversation_id> Get trace data for a conversation`);
562
+ trace <conversation_id> [--view tree|perf|evidence|reasoning|all] [--json]
563
+ Get trace data for a conversation`);
524
564
  return Promise.resolve(0);
525
565
  }
526
566
  const dispatch = async () => {
@@ -724,13 +764,16 @@ Options:
724
764
  }
725
765
  if (subcommand === "trace") {
726
766
  if (rest.length === 1 && (rest[0] === "--help" || rest[0] === "-h")) {
727
- console.log(`kweaver agent trace <agent_id> <conversation_id> [options]
767
+ console.log(`kweaver agent trace <conversation_id> [options]
768
+ kweaver agent trace <agent_id> <conversation_id> [options] (legacy)
728
769
 
729
770
  Get trace data for a conversation.
730
771
 
731
772
  Options:
732
- --pretty Pretty-print JSON output (default)
733
- --compact Compact JSON output`);
773
+ --view tree|perf|evidence|reasoning|all Render style (default: tree)
774
+ --json Emit raw TracesByConversationResult JSON
775
+ --pretty Pretty-print JSON output (default)
776
+ --compact Compact JSON output`);
734
777
  return 0;
735
778
  }
736
779
  }
@@ -1031,13 +1074,18 @@ async function runAgentTraceCommand(args) {
1031
1074
  }
1032
1075
  catch (error) {
1033
1076
  if (error instanceof Error && error.message === "help") {
1034
- console.log(`kweaver agent trace <agent_id> <conversation_id> [options]
1077
+ console.log(`kweaver agent trace <conversation_id> [options]
1078
+ kweaver agent trace <agent_id> <conversation_id> [options] (legacy)
1035
1079
 
1036
- Get trace data for a conversation.
1080
+ Get trace data for a conversation. Spans are fetched from trace-ai via a 2-jump
1081
+ lookup that recovers pipeline spans (HTTP entry, internal RPCs, prompt-build)
1082
+ which the simpler /by-conversation endpoint omits.
1037
1083
 
1038
1084
  Options:
1039
- --pretty Pretty-print JSON output (default)
1040
- --compact Compact JSON output`);
1085
+ --view tree|perf|evidence|reasoning|all Render style (default: tree)
1086
+ --json Emit raw TracesByConversationResult JSON
1087
+ --pretty Pretty-print JSON output (default)
1088
+ --compact Compact JSON output`);
1041
1089
  return 0;
1042
1090
  }
1043
1091
  console.error(formatHttpError(error));
@@ -1045,13 +1093,19 @@ Options:
1045
1093
  }
1046
1094
  try {
1047
1095
  const token = await ensureValidToken();
1048
- const body = await getTracesByConversation({
1096
+ const result = await getTracesByConversation({
1049
1097
  baseUrl: token.baseUrl,
1050
1098
  accessToken: token.accessToken,
1051
1099
  agentId: options.agentId,
1052
1100
  conversationId: options.conversationId,
1053
1101
  });
1054
- console.log(formatCallOutput(body, options.pretty));
1102
+ if (options.json) {
1103
+ console.log(formatCallOutput(JSON.stringify(result), options.pretty));
1104
+ }
1105
+ else {
1106
+ const { formatTraceResult } = await import("../utils/trace-views.js");
1107
+ console.log(formatTraceResult(result, options.view, { full: options.full }));
1108
+ }
1055
1109
  return 0;
1056
1110
  }
1057
1111
  catch (error) {
@@ -0,0 +1 @@
1
+ export declare function runKnMetricCommand(args: string[]): Promise<number>;
@@ -0,0 +1,406 @@
1
+ import { ensureValidToken, formatHttpError } from "../auth/oauth.js";
2
+ import { listMetrics, createMetrics, searchMetrics, validateMetrics, getMetric, updateMetric, deleteMetric, getMetrics, deleteMetrics, } from "../api/bkn-metrics.js";
3
+ import { metricQueryData, metricDryRun } from "../api/ontology-query-metrics.js";
4
+ import { formatCallOutput } from "./call.js";
5
+ import { resolveBusinessDomain } from "../config/store.js";
6
+ import { parseJsonObject, parseSearchAfterArray, confirmYes } from "./bkn-utils.js";
7
+ function parseCommaSeparatedIds(raw) {
8
+ return raw
9
+ .split(",")
10
+ .map((s) => s.trim())
11
+ .filter((s) => s.length > 0);
12
+ }
13
+ const METRIC_HELP = `kweaver bkn metric <action> [args] [--pretty] [-bd <domain>]
14
+
15
+ Management (bkn-backend):
16
+ list <kn-id> [--limit <n>] [--branch <b>] [--name-pattern <p>] [--sort update_time|name] [--direction asc|desc] [--offset <n>] [--tag <t>] [--group-id <id>]
17
+ get <kn-id> <metric-id(s)> [--branch <b>] (comma-separated for multiple)
18
+ create <kn-id> '<json>' [--branch] [--strict-mode true|false]
19
+ search <kn-id> '<json>' [--branch] [--strict-mode] [--limit <n>] [--search-after '<json>']
20
+ validate <kn-id> '<json>' [--branch] [--strict-mode] [--import-mode normal|ignore|overwrite]
21
+ update <kn-id> <metric-id> '<json>' [--branch] [--strict-mode]
22
+ delete <kn-id> <metric-id(s)> [-y] (comma-separated for multiple)
23
+
24
+ Query (ontology-query):
25
+ query <kn-id> <metric-id> ['<json-body>'] [--branch] [--fill-null]
26
+ dry-run <kn-id> '<json>' [--branch] [--fill-null]
27
+
28
+ list: default --limit 30. search/query JSON: default limit 50 in body when not set.`;
29
+ function parseListArgs(args) {
30
+ let pretty = true;
31
+ let businessDomain = "";
32
+ let limit = 30;
33
+ let branch;
34
+ let namePattern;
35
+ let sort;
36
+ let direction;
37
+ let offset;
38
+ let tag;
39
+ let groupId;
40
+ const pos = [];
41
+ for (let i = 0; i < args.length; i += 1) {
42
+ const a = args[i];
43
+ if (a === "--help" || a === "-h")
44
+ throw new Error("help");
45
+ if (a === "--pretty") {
46
+ pretty = true;
47
+ continue;
48
+ }
49
+ if ((a === "-bd" || a === "--biz-domain") && args[i + 1]) {
50
+ businessDomain = args[i + 1];
51
+ i += 1;
52
+ continue;
53
+ }
54
+ if (a === "--limit" && args[i + 1]) {
55
+ const n = parseInt(args[i + 1], 10);
56
+ if (Number.isNaN(n) || n < 1)
57
+ throw new Error("Invalid --limit");
58
+ limit = n;
59
+ i += 1;
60
+ continue;
61
+ }
62
+ if (a === "--branch" && args[i + 1]) {
63
+ branch = args[i + 1];
64
+ i += 1;
65
+ continue;
66
+ }
67
+ if (a === "--name-pattern" && args[i + 1]) {
68
+ namePattern = args[i + 1];
69
+ i += 1;
70
+ continue;
71
+ }
72
+ if (a === "--sort" && args[i + 1]) {
73
+ const s = args[i + 1];
74
+ if (s !== "update_time" && s !== "name")
75
+ throw new Error("--sort must be update_time|name");
76
+ sort = s;
77
+ i += 1;
78
+ continue;
79
+ }
80
+ if (a === "--direction" && args[i + 1]) {
81
+ const d = args[i + 1];
82
+ if (d !== "asc" && d !== "desc")
83
+ throw new Error("--direction must be asc|desc");
84
+ direction = d;
85
+ i += 1;
86
+ continue;
87
+ }
88
+ if (a === "--offset" && args[i + 1]) {
89
+ offset = parseInt(args[i + 1], 10);
90
+ i += 1;
91
+ continue;
92
+ }
93
+ if (a === "--tag" && args[i + 1]) {
94
+ tag = args[i + 1];
95
+ i += 1;
96
+ continue;
97
+ }
98
+ if (a === "--group-id" && args[i + 1]) {
99
+ groupId = args[i + 1];
100
+ i += 1;
101
+ continue;
102
+ }
103
+ pos.push(a);
104
+ }
105
+ if (!businessDomain)
106
+ businessDomain = resolveBusinessDomain();
107
+ const [knId] = pos;
108
+ if (!knId)
109
+ throw new Error("Usage: kweaver bkn metric list <kn-id> [options]");
110
+ return {
111
+ knId,
112
+ limit,
113
+ pretty,
114
+ businessDomain,
115
+ branch,
116
+ namePattern,
117
+ sort,
118
+ direction,
119
+ offset,
120
+ tag,
121
+ groupId,
122
+ };
123
+ }
124
+ function parseCommonKnFlags(rest, withYes) {
125
+ let pretty = true;
126
+ let businessDomain = "";
127
+ let branch;
128
+ let strictMode;
129
+ let importMode;
130
+ let fillNull;
131
+ let yes = false;
132
+ const out = [];
133
+ for (let i = 0; i < rest.length; i += 1) {
134
+ const a = rest[i];
135
+ if (a === "--help" || a === "-h")
136
+ throw new Error("help");
137
+ if (a === "--pretty") {
138
+ pretty = true;
139
+ continue;
140
+ }
141
+ if (withYes && (a === "-y" || a === "--yes")) {
142
+ yes = true;
143
+ continue;
144
+ }
145
+ if ((a === "-bd" || a === "--biz-domain") && rest[i + 1]) {
146
+ businessDomain = rest[i + 1];
147
+ i += 1;
148
+ continue;
149
+ }
150
+ if (a === "--branch" && rest[i + 1]) {
151
+ branch = rest[i + 1];
152
+ i += 1;
153
+ continue;
154
+ }
155
+ if (a === "--strict-mode" && rest[i + 1]) {
156
+ strictMode = rest[i + 1] === "true" || rest[i + 1] === "1";
157
+ i += 1;
158
+ continue;
159
+ }
160
+ if (a === "--import-mode" && rest[i + 1]) {
161
+ const m = rest[i + 1];
162
+ if (m !== "normal" && m !== "ignore" && m !== "overwrite") {
163
+ throw new Error("--import-mode must be normal|ignore|overwrite");
164
+ }
165
+ importMode = m;
166
+ i += 1;
167
+ continue;
168
+ }
169
+ if (a === "--fill-null") {
170
+ fillNull = true;
171
+ continue;
172
+ }
173
+ out.push(a);
174
+ }
175
+ if (!businessDomain)
176
+ businessDomain = resolveBusinessDomain();
177
+ return { filtered: out, pretty, businessDomain, branch, strictMode, importMode, fillNull, yes };
178
+ }
179
+ export async function runKnMetricCommand(args) {
180
+ const [action, ...rest] = args;
181
+ if (!action || action === "--help" || action === "-h") {
182
+ console.log(METRIC_HELP);
183
+ return 0;
184
+ }
185
+ try {
186
+ const token = await ensureValidToken();
187
+ const b = { baseUrl: token.baseUrl, accessToken: token.accessToken };
188
+ if (action === "list") {
189
+ const p = parseListArgs(rest);
190
+ const out = await listMetrics({
191
+ ...b,
192
+ knId: p.knId,
193
+ businessDomain: p.businessDomain,
194
+ limit: p.limit,
195
+ branch: p.branch,
196
+ namePattern: p.namePattern,
197
+ sort: p.sort,
198
+ direction: p.direction,
199
+ offset: p.offset,
200
+ tag: p.tag,
201
+ groupId: p.groupId,
202
+ });
203
+ console.log(formatCallOutput(out, p.pretty));
204
+ return 0;
205
+ }
206
+ if (action === "get") {
207
+ const o = parseCommonKnFlags(rest, false);
208
+ const [knId, metricIdArg] = o.filtered;
209
+ if (!knId || !metricIdArg) {
210
+ console.error("Usage: kweaver bkn metric get <kn-id> <metric-id(s)> [options]");
211
+ return 1;
212
+ }
213
+ const ids = parseCommaSeparatedIds(metricIdArg);
214
+ if (ids.length === 0) {
215
+ console.error("metric-id(s): need at least one id");
216
+ return 1;
217
+ }
218
+ const out = ids.length === 1
219
+ ? await getMetric({ ...b, knId, businessDomain: o.businessDomain, metricId: ids[0], branch: o.branch })
220
+ : await getMetrics({ ...b, knId, businessDomain: o.businessDomain, metricIds: ids.join(","), branch: o.branch });
221
+ console.log(formatCallOutput(out, o.pretty));
222
+ return 0;
223
+ }
224
+ if (action === "create") {
225
+ const o = parseCommonKnFlags(rest, false);
226
+ const [knId, bodyJson] = o.filtered;
227
+ if (!knId || !bodyJson) {
228
+ console.error("Usage: kweaver bkn metric create <kn-id> '<json>' [options]");
229
+ return 1;
230
+ }
231
+ const out = await createMetrics({
232
+ ...b,
233
+ knId,
234
+ businessDomain: o.businessDomain,
235
+ body: bodyJson,
236
+ branch: o.branch,
237
+ strictMode: o.strictMode,
238
+ });
239
+ console.log(formatCallOutput(out, o.pretty));
240
+ return 0;
241
+ }
242
+ if (action === "search") {
243
+ let limit;
244
+ let searchAfter;
245
+ const r = [];
246
+ for (let i = 0; i < rest.length; i += 1) {
247
+ const a = rest[i];
248
+ if (a === "--limit" && rest[i + 1]) {
249
+ limit = parseInt(rest[i + 1], 10);
250
+ if (Number.isNaN(limit) || limit < 1)
251
+ throw new Error("Invalid --limit");
252
+ i += 1;
253
+ continue;
254
+ }
255
+ if (a === "--search-after" && rest[i + 1]) {
256
+ searchAfter = parseSearchAfterArray(rest[i + 1]);
257
+ i += 1;
258
+ continue;
259
+ }
260
+ r.push(a);
261
+ }
262
+ const o = parseCommonKnFlags(r, false);
263
+ const [knId, bodyText] = o.filtered;
264
+ if (!knId || !bodyText) {
265
+ console.error("Usage: kweaver bkn metric search <kn-id> '<json>' [options]");
266
+ return 1;
267
+ }
268
+ const obj = parseJsonObject(bodyText, "search body must be a JSON object.");
269
+ if (limit !== undefined)
270
+ obj.limit = limit;
271
+ if (searchAfter !== undefined)
272
+ obj.search_after = searchAfter;
273
+ if (typeof obj.limit !== "number" || !Number.isFinite(obj.limit)) {
274
+ obj.limit = 50;
275
+ }
276
+ const out = await searchMetrics({
277
+ ...b,
278
+ knId,
279
+ businessDomain: o.businessDomain,
280
+ body: JSON.stringify(obj),
281
+ branch: o.branch,
282
+ strictMode: o.strictMode,
283
+ });
284
+ console.log(formatCallOutput(out, o.pretty));
285
+ return 0;
286
+ }
287
+ if (action === "validate") {
288
+ const o = parseCommonKnFlags(rest, false);
289
+ const [knId, bodyJson] = o.filtered;
290
+ if (!knId || !bodyJson) {
291
+ console.error("Usage: kweaver bkn metric validate <kn-id> '<json>' [options]");
292
+ return 1;
293
+ }
294
+ const out = await validateMetrics({
295
+ ...b,
296
+ knId,
297
+ businessDomain: o.businessDomain,
298
+ body: bodyJson,
299
+ branch: o.branch,
300
+ strictMode: o.strictMode,
301
+ importMode: o.importMode,
302
+ });
303
+ console.log(formatCallOutput(out, o.pretty));
304
+ return 0;
305
+ }
306
+ if (action === "update") {
307
+ const o = parseCommonKnFlags(rest, false);
308
+ const [knId, metricId, bodyJson] = o.filtered;
309
+ if (!knId || !metricId || !bodyJson) {
310
+ console.error("Usage: kweaver bkn metric update <kn-id> <metric-id> '<json>' [options]");
311
+ return 1;
312
+ }
313
+ const out = await updateMetric({
314
+ ...b,
315
+ knId,
316
+ businessDomain: o.businessDomain,
317
+ metricId,
318
+ body: bodyJson,
319
+ branch: o.branch,
320
+ strictMode: o.strictMode,
321
+ });
322
+ console.log(formatCallOutput(out, o.pretty));
323
+ return 0;
324
+ }
325
+ if (action === "delete") {
326
+ const o = parseCommonKnFlags(rest, true);
327
+ const [knId, metricIdArg] = o.filtered;
328
+ if (!knId || !metricIdArg) {
329
+ console.error("Usage: kweaver bkn metric delete <kn-id> <metric-id(s)> [-y]");
330
+ return 1;
331
+ }
332
+ const ids = parseCommaSeparatedIds(metricIdArg);
333
+ if (ids.length === 0) {
334
+ console.error("metric-id(s): need at least one id");
335
+ return 1;
336
+ }
337
+ if (!o.yes) {
338
+ const label = ids.length === 1 ? ids[0] : ids.join(",");
339
+ const ok = await confirmYes(`Delete metric(s) ${label}?`);
340
+ if (!ok) {
341
+ console.log("Cancelled.");
342
+ return 0;
343
+ }
344
+ }
345
+ const out = ids.length === 1
346
+ ? await deleteMetric({ ...b, knId, businessDomain: o.businessDomain, metricId: ids[0], branch: o.branch })
347
+ : await deleteMetrics({ ...b, knId, businessDomain: o.businessDomain, metricIds: ids.join(","), branch: o.branch });
348
+ console.log(formatCallOutput(out, o.pretty));
349
+ return 0;
350
+ }
351
+ if (action === "query") {
352
+ const o = parseCommonKnFlags(rest, false);
353
+ const { fillNull, branch, filtered, pretty, businessDomain } = o;
354
+ const [knId, metricId, bodyText = "{}"] = filtered;
355
+ if (!knId || !metricId) {
356
+ console.error("Usage: kweaver bkn metric query <kn-id> <metric-id> ['<json>'] [--branch] [--fill-null]");
357
+ return 1;
358
+ }
359
+ const body = parseJsonObject(bodyText, "metric query body must be a JSON object.");
360
+ if (typeof body.limit !== "number" || !Number.isFinite(body.limit)) {
361
+ body.limit = 50;
362
+ }
363
+ const out = await metricQueryData({
364
+ ...b,
365
+ knId,
366
+ businessDomain,
367
+ metricId,
368
+ body: JSON.stringify(body),
369
+ branch,
370
+ fillNull,
371
+ });
372
+ console.log(formatCallOutput(out, pretty));
373
+ return 0;
374
+ }
375
+ if (action === "dry-run") {
376
+ const o = parseCommonKnFlags(rest, false);
377
+ const { fillNull, branch, filtered, pretty, businessDomain } = o;
378
+ const [knId, bodyText] = filtered;
379
+ if (!knId || !bodyText) {
380
+ console.error("Usage: kweaver bkn metric dry-run <kn-id> '<json>' [--branch] [--fill-null]");
381
+ return 1;
382
+ }
383
+ parseJsonObject(bodyText, "dry-run body must be a JSON object.");
384
+ const out = await metricDryRun({
385
+ ...b,
386
+ knId,
387
+ businessDomain,
388
+ body: bodyText,
389
+ branch,
390
+ fillNull,
391
+ });
392
+ console.log(formatCallOutput(out, pretty));
393
+ return 0;
394
+ }
395
+ console.error(`Unknown bkn metric action: ${action}. Use --help.`);
396
+ return 1;
397
+ }
398
+ catch (error) {
399
+ if (error instanceof Error && error.message === "help") {
400
+ console.log(METRIC_HELP);
401
+ return 0;
402
+ }
403
+ console.error(formatHttpError(error));
404
+ return 1;
405
+ }
406
+ }
@@ -13,7 +13,7 @@ import { downloadBkn, uploadBkn, listActionSchedules, getActionSchedule, createA
13
13
  import { formatCallOutput } from "./call.js";
14
14
  import { resolveBusinessDomain } from "../config/store.js";
15
15
  import { runDsImportCsv } from "./ds.js";
16
- import { pollWithBackoff, detectPrimaryKey, detectDisplayKey, formatPkDetectionError, parsePkMap, confirmYes, } from "./bkn-utils.js";
16
+ import { pollWithBackoff, detectDisplayKey, formatPkDetectionError, parsePkMap, resolvePrimaryKey, confirmYes, } from "./bkn-utils.js";
17
17
  // ── BKN object name validation ──────────────────────────────────────────────
18
18
  // Mirrors bkn-backend OBJECT_NAME_MAX_LENGTH (interfaces/common.go:28) and
19
19
  // validateObjectName (driveradapters/validate.go:85). 40 utf-8 codepoints,
@@ -644,19 +644,25 @@ export async function runKnCreateFromDsCommand(args, sampleRows) {
644
644
  }
645
645
  for (const t of targetTables) {
646
646
  const override = options.pkMap[t.name];
647
- if (override) {
648
- if (!t.columns.some((c) => c.name === override)) {
649
- throw new Error(`--pk-map specifies '${override}' for table '${t.name}', but no such column. ` +
650
- `Columns: ${t.columns.map((c) => c.name).join(", ")}`);
651
- }
652
- tablePks[t.name] = override;
647
+ if (override && !t.columns.some((c) => c.name === override)) {
648
+ throw new Error(`--pk-map specifies '${override}' for table '${t.name}', but no such column. ` +
649
+ `Columns: ${t.columns.map((c) => c.name).join(", ")}`);
650
+ }
651
+ const resolution = resolvePrimaryKey(t, sampleRows?.[t.name], override);
652
+ if (resolution.pk) {
653
+ tablePks[t.name] = resolution.pk;
653
654
  continue;
654
655
  }
655
- const result = detectPrimaryKey(t, sampleRows?.[t.name]);
656
- if (!result.pk) {
657
- throw new Error(formatPkDetectionError(t.name, result));
656
+ if (resolution.source === "ambiguous") {
657
+ const cols = (resolution.ambiguous ?? []).join(", ");
658
+ throw new Error(`Table '${t.name}' has a composite PRIMARY KEY (${cols}). ` +
659
+ `BKN object types take a single primary key — pick one with --pk-map ${t.name}:<column>.`);
658
660
  }
659
- tablePks[t.name] = result.pk;
661
+ throw new Error(formatPkDetectionError(t.name, {
662
+ pk: null,
663
+ candidates: resolution.candidates ?? [],
664
+ sampleSize: resolution.sampleSize ?? 0,
665
+ }));
660
666
  }
661
667
  // Phase 1: Create DataViews for each table. findDataView is idempotent;
662
668
  // not tracked for rollback so a retry can reuse what's already there.