@kweaver-ai/kweaver-sdk 0.4.11 → 0.4.13

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,11 +1,12 @@
1
- import { clearPlatformSession, deletePlatform, getConfigDir, getCurrentPlatform, getPlatformAlias, hasPlatform, listPlatforms, loadTokenConfig, resolvePlatformIdentifier, setCurrentPlatform, setPlatformAlias, } from "../config/store.js";
2
- import { formatHttpError, normalizeBaseUrl, oauth2Login, playwrightLogin, } from "../auth/oauth.js";
1
+ import { clearPlatformSession, deletePlatform, getConfigDir, getCurrentPlatform, getPlatformAlias, hasPlatform, listPlatforms, loadClientConfig, loadTokenConfig, resolvePlatformIdentifier, setCurrentPlatform, setPlatformAlias, } from "../config/store.js";
2
+ import { buildCopyCommand, formatHttpError, normalizeBaseUrl, oauth2Login, playwrightLogin, refreshTokenLogin, } from "../auth/oauth.js";
3
3
  export async function runAuthCommand(args) {
4
4
  const target = args[0];
5
5
  const rest = args.slice(1);
6
6
  if (!target || target === "--help" || target === "-h") {
7
7
  console.log(`kweaver auth login <url> [options] Login to a platform (browser OAuth2 by default)
8
8
  kweaver auth <url> Login (shorthand; same options as login)
9
+ kweaver auth export [url|alias] [--json] Export credentials; run printed command on a headless host
9
10
  kweaver auth status [url|alias] Show current auth status
10
11
  kweaver auth list List saved platforms
11
12
  kweaver auth use <url|alias> Switch active platform
@@ -18,6 +19,9 @@ Login options:
18
19
  Use the platform's web app client ID to get the same permissions
19
20
  as the browser. Find it in DevTools: /oauth2/auth?client_id=<id>
20
21
  --client-secret <s> Client secret (omit for public/PKCE clients)
22
+ --refresh-token <t> Use on a machine without a browser: exchange refresh token for access token.
23
+ Requires --client-id and --client-secret.
24
+ Get these from the callback page after browser login or \`auth export\`.
21
25
  -u, --username Username (with -p triggers Playwright headless login)
22
26
  -p, --password Password
23
27
  --playwright Force Playwright browser login even without -u/-p
@@ -26,7 +30,7 @@ Login options:
26
30
  }
27
31
  if (target === "login") {
28
32
  if (rest[0] === "--help" || rest[0] === "-h") {
29
- console.log(`kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass] [--playwright]`);
33
+ console.log(`kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
30
34
  return 0;
31
35
  }
32
36
  const url = rest[0];
@@ -36,7 +40,11 @@ Login options:
36
40
  }
37
41
  return runAuthCommand([url, ...rest.slice(1)]);
38
42
  }
39
- if (target && target !== "status" && target !== "list" && target !== "use" && target !== "delete" && target !== "logout") {
43
+ if (target === "export") {
44
+ return runAuthExportCommand(rest);
45
+ }
46
+ const LOGIN_SUBCOMMANDS = new Set(["status", "list", "use", "delete", "logout", "export"]);
47
+ if (target && !LOGIN_SUBCOMMANDS.has(target)) {
40
48
  try {
41
49
  const normalizedTarget = normalizeBaseUrl(target);
42
50
  const alias = readOption(args, "--alias");
@@ -45,9 +53,21 @@ Login options:
45
53
  const usePlaywright = args.includes("--playwright");
46
54
  const clientId = readOption(args, "--client-id");
47
55
  const clientSecret = readOption(args, "--client-secret");
56
+ const refreshToken = readOption(args, "--refresh-token");
48
57
  const tlsInsecure = args.includes("--insecure") || args.includes("-k");
49
58
  let token;
50
- if (username && password) {
59
+ if (refreshToken) {
60
+ if (!clientId || !clientSecret) {
61
+ console.error("--refresh-token requires --client-id and --client-secret.\n");
62
+ console.error("Get these values from the callback page after a browser login or `kweaver auth export`.");
63
+ return 1;
64
+ }
65
+ console.log("Logging in with refresh token (no browser)...");
66
+ token = await refreshTokenLogin(normalizedTarget, {
67
+ clientId, clientSecret, refreshToken, tlsInsecure,
68
+ });
69
+ }
70
+ else if (username && password) {
51
71
  // Headless Playwright login with credentials
52
72
  console.log("Logging in (headless)...");
53
73
  token = await playwrightLogin(normalizedTarget, { username, password, tlsInsecure });
@@ -213,7 +233,7 @@ Login options:
213
233
  return 0;
214
234
  }
215
235
  console.error("Usage: kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass] [--playwright]");
216
- console.error(" kweaver auth <platform-url> [--alias <name>] [-u user] [-p pass] [--playwright]");
236
+ console.error(" kweaver auth export [platform-url|alias] [--json]");
217
237
  console.error(" kweaver auth status [platform-url|alias]");
218
238
  console.error(" kweaver auth list");
219
239
  console.error(" kweaver auth use <platform-url|alias>");
@@ -221,6 +241,60 @@ Login options:
221
241
  console.error(" kweaver auth delete <platform-url|alias>");
222
242
  return 1;
223
243
  }
244
+ async function runAuthExportCommand(args) {
245
+ if (args[0] === "--help" || args[0] === "-h") {
246
+ console.log(`kweaver auth export [platform-url|alias] [--json]
247
+
248
+ Export OAuth2 credentials for copying to a headless host (no browser there).
249
+ Prints clientId, clientSecret, refreshToken, and a command to run on that machine.
250
+
251
+ Options:
252
+ --json Output as JSON (machine-readable)`);
253
+ return 0;
254
+ }
255
+ const jsonOutput = args.includes("--json");
256
+ const positional = args.find((a) => !a.startsWith("-"));
257
+ const resolved = positional ? resolvePlatformIdentifier(positional) : null;
258
+ const platform = resolved && /^https?:\/\//.test(resolved) ? normalizeBaseUrl(resolved) : resolved ?? getCurrentPlatform();
259
+ if (!platform) {
260
+ console.error("No active platform. Run `kweaver auth login <platform-url>` first.");
261
+ return 1;
262
+ }
263
+ const client = loadClientConfig(platform);
264
+ const token = loadTokenConfig(platform);
265
+ const clientId = client?.clientId ?? "";
266
+ const clientSecret = client?.clientSecret ?? "";
267
+ const refreshToken = token?.refreshToken ?? "";
268
+ const tlsInsecure = token?.tlsInsecure;
269
+ if (!clientId || !refreshToken) {
270
+ console.error(`Incomplete credentials for ${platform}.\n` +
271
+ (!clientId ? " Missing: client registration (client.json)\n" : "") +
272
+ (!refreshToken ? " Missing: refresh token (token.json)\n" : "") +
273
+ `Run \`kweaver auth login ${platform}\` first.`);
274
+ return 1;
275
+ }
276
+ if (jsonOutput) {
277
+ console.log(JSON.stringify({
278
+ baseUrl: platform,
279
+ clientId,
280
+ clientSecret,
281
+ refreshToken,
282
+ ...(tlsInsecure ? { tlsInsecure: true } : {}),
283
+ }));
284
+ return 0;
285
+ }
286
+ const cmd = buildCopyCommand(platform, clientId, clientSecret, refreshToken, tlsInsecure);
287
+ console.log(`Platform: ${platform}`);
288
+ console.log(`Client ID: ${clientId}`);
289
+ console.log(`Client Secret: ${clientSecret || "(none)"}`);
290
+ console.log(`Refresh Token: ${refreshToken}`);
291
+ console.log("");
292
+ console.log("On a machine without a browser, run:\n");
293
+ console.log(` ${cmd}`);
294
+ console.log("");
295
+ console.log("Keep these credentials secure.");
296
+ return 0;
297
+ }
224
298
  function readOption(args, name) {
225
299
  const index = args.findIndex((arg) => arg === name);
226
300
  if (index === -1) {
@@ -1,4 +1,15 @@
1
1
  import { type BknEncodingImportOptions } from "../utils/bkn-encoding.js";
2
+ export interface PollOptions<T> {
3
+ fn: () => Promise<{
4
+ done: boolean;
5
+ value: T;
6
+ }>;
7
+ interval: number;
8
+ timeout: number;
9
+ maxInterval?: number;
10
+ _sleep?: (ms: number) => Promise<void>;
11
+ }
12
+ export declare function pollWithBackoff<T>(opts: PollOptions<T>): Promise<T>;
2
13
  export interface KnListOptions {
3
14
  offset: number;
4
15
  limit: number;
@@ -9,11 +9,24 @@ import { listKnowledgeNetworks, getKnowledgeNetwork, createKnowledgeNetwork, upd
9
9
  import { objectTypeQuery, objectTypeProperties, subgraph, actionTypeQuery, actionTypeExecute, actionExecutionGet, actionLogsList, actionLogGet, actionLogCancel, } from "../api/ontology-query.js";
10
10
  import { semanticSearch } from "../api/semantic-search.js";
11
11
  import { listTablesWithColumns, scanMetadata, getDatasource } from "../api/datasources.js";
12
- import { createDataView } from "../api/dataviews.js"; // used by runKnCreateFromDsCommand
12
+ import { createDataView, findDataView } from "../api/dataviews.js"; // used by runKnCreateFromDsCommand
13
13
  import { downloadBkn, uploadBkn } from "../api/bkn-backend.js";
14
14
  import { formatCallOutput } from "./call.js";
15
15
  import { resolveBusinessDomain } from "../config/store.js";
16
16
  import { runDsImportCsv } from "./ds.js";
17
+ export async function pollWithBackoff(opts) {
18
+ const { fn, timeout, maxInterval = 15000, _sleep = (ms) => new Promise(r => setTimeout(r, ms)) } = opts;
19
+ let currentInterval = opts.interval;
20
+ const deadline = Date.now() + timeout;
21
+ while (Date.now() < deadline) {
22
+ const result = await fn();
23
+ if (result.done)
24
+ return result.value;
25
+ await _sleep(currentInterval);
26
+ currentInterval = Math.min(currentInterval * 2, maxInterval);
27
+ }
28
+ throw new Error(`Polling timed out after ${timeout}ms`);
29
+ }
17
30
  export function formatSimpleKnList(text, pretty, includeDetail = false) {
18
31
  const parsed = JSON.parse(text);
19
32
  const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
@@ -27,7 +40,7 @@ export function formatSimpleKnList(text, pretty, includeDetail = false) {
27
40
  }
28
41
  export function parseKnListArgs(args) {
29
42
  let offset = 0;
30
- let limit = 50;
43
+ let limit = 30;
31
44
  let sort = "update_time";
32
45
  let direction = "desc";
33
46
  let businessDomain = "";
@@ -49,9 +62,9 @@ export function parseKnListArgs(args) {
49
62
  continue;
50
63
  }
51
64
  if (arg === "--limit") {
52
- limit = parseInt(args[i + 1] ?? "50", 10);
65
+ limit = parseInt(args[i + 1] ?? "30", 10);
53
66
  if (Number.isNaN(limit) || limit < 1)
54
- limit = 50;
67
+ limit = 30;
55
68
  i += 1;
56
69
  continue;
57
70
  }
@@ -582,7 +595,7 @@ export function parseKnObjectTypeQueryArgs(args) {
582
595
  body.search_after = searchAfter;
583
596
  }
584
597
  if (typeof body.limit !== "number" || !Number.isFinite(body.limit) || body.limit < 1) {
585
- body.limit = 30;
598
+ body.limit = 50;
586
599
  }
587
600
  if (!businessDomain)
588
601
  businessDomain = resolveBusinessDomain();
@@ -1126,7 +1139,7 @@ kweaver bkn object-type properties <kn-id> <ot-id> '<json>' [--pretty] [-bd valu
1126
1139
  list: List object types (schema) from ontology-manager.
1127
1140
  get: Get single object type details.
1128
1141
  create/update/delete: Schema CRUD (create requires dataview-id). update: merge flags (--add-property / --update-property / --remove-property, etc.) GET-merge-PUT; or full JSON as third arg.
1129
- query: Query via ontology-query API. Default limit is 30 if not specified. Use --search-after for pagination.
1142
+ query: Query via ontology-query API. Default limit is 50 if not specified. Use --search-after for pagination.
1130
1143
  properties: Query instance properties by primary key.
1131
1144
 
1132
1145
  properties JSON format: {"_instance_identities":[{"<primary-key>":"<value>"}],"properties":["prop1","prop2"]}`);
@@ -1719,26 +1732,35 @@ query/execute: Query or execute actions. execute has side effects - only use whe
1719
1732
  console.log(formatCallOutput(result, options.pretty));
1720
1733
  return 0;
1721
1734
  }
1722
- const deadline = Date.now() + options.timeout * 1000;
1723
1735
  let lastBody = result;
1724
- while (Date.now() < deadline) {
1725
- const status = extractStatus(lastBody);
1726
- if (TERMINAL_STATUSES.includes(status.toUpperCase())) {
1727
- console.log(formatCallOutput(lastBody, options.pretty));
1728
- return status.toUpperCase() === "SUCCESS" ? 0 : 1;
1729
- }
1730
- await new Promise((r) => setTimeout(r, 2000));
1731
- lastBody = await actionExecutionGet({
1732
- baseUrl: token.baseUrl,
1733
- accessToken: token.accessToken,
1734
- knId: options.knId,
1735
- executionId,
1736
- businessDomain: options.businessDomain,
1736
+ try {
1737
+ lastBody = await pollWithBackoff({
1738
+ fn: async () => {
1739
+ const status = extractStatus(lastBody);
1740
+ if (TERMINAL_STATUSES.includes(status.toUpperCase())) {
1741
+ return { done: true, value: lastBody };
1742
+ }
1743
+ lastBody = await actionExecutionGet({
1744
+ baseUrl: token.baseUrl,
1745
+ accessToken: token.accessToken,
1746
+ knId: options.knId,
1747
+ executionId,
1748
+ businessDomain: options.businessDomain,
1749
+ });
1750
+ return { done: false, value: lastBody };
1751
+ },
1752
+ interval: 2000,
1753
+ timeout: options.timeout * 1000,
1737
1754
  });
1738
1755
  }
1739
- console.error(`Action execution did not complete within ${options.timeout}s`);
1756
+ catch {
1757
+ console.error(`Action execution did not complete within ${options.timeout}s`);
1758
+ console.log(formatCallOutput(lastBody, options.pretty));
1759
+ return 1;
1760
+ }
1761
+ const finalStatus = extractStatus(lastBody);
1740
1762
  console.log(formatCallOutput(lastBody, options.pretty));
1741
- return 1;
1763
+ return finalStatus.toUpperCase() === "SUCCESS" ? 0 : 1;
1742
1764
  }
1743
1765
  catch (error) {
1744
1766
  console.error(formatHttpError(error));
@@ -1802,7 +1824,7 @@ Options for list: --limit, --need-total, --action-type-id, --status, --trigger-t
1802
1824
  }
1803
1825
  let pretty = true;
1804
1826
  let businessDomain = "";
1805
- let limit;
1827
+ let limit = 30;
1806
1828
  let needTotal;
1807
1829
  let actionTypeId;
1808
1830
  let status;
@@ -1955,7 +1977,7 @@ List business knowledge networks from the ontology-manager API.
1955
1977
 
1956
1978
  Options:
1957
1979
  --offset <n> Offset (default: 0)
1958
- --limit <n> Limit (default: 50)
1980
+ --limit <n> Limit (default: 30)
1959
1981
  --sort <key> Sort field (default: update_time)
1960
1982
  --direction <asc|desc> Sort direction (default: desc)
1961
1983
  --name-pattern <s> Filter by name pattern
@@ -2195,13 +2217,21 @@ async function runKnCreateFromDsCommand(args, sampleRows) {
2195
2217
  console.error(`Creating data views for ${targetTables.length} table(s) ...`);
2196
2218
  const viewMap = {};
2197
2219
  for (const t of targetTables) {
2198
- const dvId = await createDataView({
2220
+ const found = await findDataView({
2199
2221
  ...base,
2200
2222
  name: t.name,
2201
2223
  datasourceId: options.dsId,
2202
- table: t.name,
2203
- fields: t.columns.map((c) => ({ name: c.name, type: c.type })),
2224
+ exact: true,
2225
+ wait: true,
2204
2226
  });
2227
+ const dvId = found[0]?.id ??
2228
+ (await createDataView({
2229
+ ...base,
2230
+ name: t.name,
2231
+ datasourceId: options.dsId,
2232
+ table: t.name,
2233
+ fields: t.columns.map((c) => ({ name: c.name, type: c.type })),
2234
+ }));
2205
2235
  viewMap[t.name] = dvId;
2206
2236
  }
2207
2237
  // Phase 2: Create the KN record
@@ -2266,18 +2296,24 @@ async function runKnCreateFromDsCommand(args, sampleRows) {
2266
2296
  if (options.build) {
2267
2297
  console.error("Building ...");
2268
2298
  await buildKnowledgeNetwork({ ...base, knId });
2269
- const deadline = Date.now() + options.timeout * 1000;
2270
2299
  const TERMINAL = ["completed", "failed", "success"];
2271
- while (Date.now() < deadline) {
2272
- await new Promise((r) => setTimeout(r, 2000));
2273
- const statusBody = await getBuildStatus({ ...base, knId });
2274
- const statusParsed = JSON.parse(statusBody);
2275
- const jobs = Array.isArray(statusParsed) ? statusParsed : (statusParsed.entries ?? []);
2276
- const state = (jobs[0]?.state ?? "running").toLowerCase();
2277
- if (TERMINAL.includes(state)) {
2278
- statusStr = state;
2279
- break;
2280
- }
2300
+ try {
2301
+ statusStr = await pollWithBackoff({
2302
+ fn: async () => {
2303
+ const statusBody = await getBuildStatus({ ...base, knId });
2304
+ const statusParsed = JSON.parse(statusBody);
2305
+ const jobs = Array.isArray(statusParsed) ? statusParsed : (statusParsed.entries ?? []);
2306
+ const state = (jobs[0]?.state ?? "running").toLowerCase();
2307
+ if (TERMINAL.includes(state))
2308
+ return { done: true, value: state };
2309
+ return { done: false, value: "running" };
2310
+ },
2311
+ interval: 2000,
2312
+ timeout: options.timeout * 1000,
2313
+ });
2314
+ }
2315
+ catch {
2316
+ // timeout — statusStr remains "skipped"
2281
2317
  }
2282
2318
  }
2283
2319
  const output = {
@@ -2448,30 +2484,37 @@ async function runKnBuildCommand(args) {
2448
2484
  return 0;
2449
2485
  }
2450
2486
  console.error("Waiting for build to complete ...");
2451
- const deadline = Date.now() + options.timeout * 1000;
2452
- while (Date.now() < deadline) {
2453
- await new Promise((r) => setTimeout(r, 2000));
2454
- const body = await getBuildStatus({
2455
- baseUrl: token.baseUrl,
2456
- accessToken: token.accessToken,
2457
- knId: options.knId,
2458
- businessDomain: options.businessDomain,
2487
+ try {
2488
+ const { state, detail } = await pollWithBackoff({
2489
+ fn: async () => {
2490
+ const body = await getBuildStatus({
2491
+ baseUrl: token.baseUrl,
2492
+ accessToken: token.accessToken,
2493
+ knId: options.knId,
2494
+ businessDomain: options.businessDomain,
2495
+ });
2496
+ const parsed = JSON.parse(body);
2497
+ const jobs = Array.isArray(parsed) ? parsed : (parsed.entries ?? parsed.data ?? []);
2498
+ const job = jobs[0];
2499
+ const st = (job?.state ?? "running").toLowerCase();
2500
+ const dt = job?.state_detail;
2501
+ if (TERMINAL_STATES.includes(st))
2502
+ return { done: true, value: { state: st, detail: dt } };
2503
+ return { done: false, value: { state: st } };
2504
+ },
2505
+ interval: 2000,
2506
+ timeout: options.timeout * 1000,
2459
2507
  });
2460
- const parsed = JSON.parse(body);
2461
- const jobs = Array.isArray(parsed) ? parsed : (parsed.entries ?? parsed.data ?? []);
2462
- const job = jobs[0];
2463
- const state = (job?.state ?? "running").toLowerCase();
2464
- const detail = job?.state_detail;
2465
- if (TERMINAL_STATES.includes(state)) {
2466
- console.log(state);
2467
- if (detail) {
2468
- console.log(`Detail: ${detail}`);
2469
- }
2470
- return state === "failed" ? 1 : 0;
2508
+ console.log(state);
2509
+ if (detail) {
2510
+ console.log(`Detail: ${detail}`);
2471
2511
  }
2512
+ return state === "failed" ? 1 : 0;
2513
+ }
2514
+ catch {
2515
+ console.error(`Build did not complete within ${options.timeout}s`);
2516
+ return 1;
2472
2517
  }
2473
- console.error(`Build did not complete within ${options.timeout}s`);
2474
- return 1;
2475
2518
  }
2476
2519
  catch (error) {
2477
2520
  console.error(formatHttpError(error));
@@ -0,0 +1 @@
1
+ export declare function runDataviewCommand(args: string[]): Promise<number>;