@kweaver-ai/kweaver-sdk 0.6.3 → 0.6.5

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.
package/dist/cli.js CHANGED
@@ -12,6 +12,8 @@ import { runExploreCommand } from "./commands/explore.js";
12
12
  import { runDataviewCommand } from "./commands/dataview.js";
13
13
  import { runSkillCommand } from "./commands/skill.js";
14
14
  import { runTokenCommand } from "./commands/token.js";
15
+ import { runToolboxCommand } from "./commands/toolbox.js";
16
+ import { runToolCommand } from "./commands/tool.js";
15
17
  import { runVegaCommand } from "./commands/vega.js";
16
18
  function printHelp() {
17
19
  console.log(`kweaver
@@ -21,7 +23,7 @@ Usage:
21
23
  kweaver --version | -V
22
24
  kweaver --help | -h
23
25
 
24
- kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
26
+ kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--playwright] [--insecure|-k]
25
27
  kweaver auth login <platform-url> (alias for auth <url>)
26
28
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (run on host without browser)
27
29
  kweaver auth whoami [platform-url|alias] [--json]
@@ -99,6 +101,15 @@ Usage:
99
101
  kweaver skill read-file <skill-id> <rel-path> [--raw] [--output file]
100
102
  kweaver skill download|install <skill-id> [path] [options]
101
103
 
104
+ kweaver toolbox create --name <n> --service-url <url> [--description <d>] [-bd value]
105
+ kweaver toolbox list [--keyword X] [--limit N] [--offset N] [-bd value]
106
+ kweaver toolbox publish|unpublish <box-id> [-bd value]
107
+ kweaver toolbox delete <box-id> [-y] [-bd value]
108
+
109
+ kweaver tool upload --toolbox <box-id> <openapi-spec-path> [--metadata-type openapi]
110
+ kweaver tool list --toolbox <box-id> [-bd value]
111
+ kweaver tool enable|disable --toolbox <box-id> <tool-id>... [-bd value]
112
+
102
113
  kweaver vega health|stats|inspect
103
114
  kweaver vega catalog list|get|health|test-connection|discover|resources [options]
104
115
  kweaver vega resource list|get|query [options]
@@ -129,6 +140,8 @@ Commands:
129
140
  object-type, relation-type, subgraph, action-type, action-execution, action-log)
130
141
  config Per-platform configuration (business domain)
131
142
  skill Skill registry and market (register, search, progressive read, download/install)
143
+ toolbox Agent toolbox lifecycle (create, list, publish, delete)
144
+ tool Tools inside a toolbox (upload OpenAPI spec, list, enable/disable)
132
145
  vega Vega observability (catalog, resource, query/sql, connector-type, health/stats/inspect)
133
146
  context-loader Context-loader MCP (config, tools, resources, prompts, kn-search, query-*, etc.)
134
147
  help Show this message`);
@@ -195,6 +208,12 @@ export async function run(argv) {
195
208
  if (command === "skill") {
196
209
  return runSkillCommand(rest);
197
210
  }
211
+ if (command === "toolbox") {
212
+ return runToolboxCommand(rest);
213
+ }
214
+ if (command === "tool") {
215
+ return runToolCommand(rest);
216
+ }
198
217
  if (command === "context-loader" || command === "context") {
199
218
  return runContextLoaderCommand(rest);
200
219
  }
@@ -1,14 +1,25 @@
1
1
  import { isNoAuth } from "../config/no-auth.js";
2
2
  import { autoSelectBusinessDomain, clearPlatformSession, deletePlatform, deleteUser, getActiveUser, getConfigDir, getCurrentPlatform, getPlatformAlias, hasPlatform, listPlatforms, listUserProfiles, loadClientConfig, loadTokenConfig, resolveBusinessDomain, resolvePlatformIdentifier, resolveUserId, saveNoAuthPlatform, setActiveUser, setCurrentPlatform, setPlatformAlias, } from "../config/store.js";
3
3
  import { decodeJwtPayload } from "../config/jwt.js";
4
- import { buildCopyCommand, formatHttpError, normalizeBaseUrl, oauth2Login, playwrightLogin, refreshTokenLogin, } from "../auth/oauth.js";
4
+ import { buildCopyCommand, formatHttpError, isStudiowebShellUnavailableError, normalizeBaseUrl, oauth2Login, oauth2PasswordSigninLogin, playwrightLogin, refreshTokenLogin, } from "../auth/oauth.js";
5
+ /** True when the `playwright` npm package can be imported (browser binaries may still need `npx playwright install`). */
6
+ async function isPlaywrightPackageResolvable() {
7
+ try {
8
+ const modName = "playwright";
9
+ await import(/* webpackIgnore: true */ modName);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
5
16
  export async function runAuthCommand(args) {
6
17
  const target = args[0];
7
18
  const rest = args.slice(1);
8
19
  if (!target || target === "--help" || target === "-h") {
9
20
  console.log(`kweaver auth login <url> [options] Login to a platform (browser OAuth2 by default)
10
21
  kweaver auth <url> Login (shorthand; same options as login)
11
- kweaver auth whoami [url|alias] Show current user identity (from id_token)
22
+ kweaver auth whoami [url|alias] [--json] Show current user identity (from id_token)
12
23
  kweaver auth export [url|alias] [--json] Export credentials; run printed command on a headless host
13
24
  kweaver auth status [url|alias] Show current auth status
14
25
  kweaver auth list List all platforms and users (tree view)
@@ -30,16 +41,17 @@ Login options:
30
41
  --port <n> Local callback port (default: 9010). Use when 9010 is occupied.
31
42
  --no-browser Do not open a browser; print the auth URL and prompt for the callback URL or code (stdin).
32
43
  Use on headless servers or when automatic browser launch fails.
33
- -u, --username Username (with -p triggers Playwright headless login)
34
- -p, --password Password
35
- --playwright Force Playwright browser login even without -u/-p
44
+ -u, --username Username (with -p: tries HTTP /oauth2/signin first when the Studio web shell is available)
45
+ -p, --password Password (with -u: falls back to Playwright headless only when studioweb is unavailable and Playwright is installed)
46
+ --http-signin With -u/-p: HTTP POST /oauth2/signin only (no Playwright fallback). Uses the built-in RSA public key.
47
+ --playwright With -u/-p: force Playwright (skip HTTP sign-in). Without -u/-p: open Playwright for manual login.
36
48
  --insecure, -k Skip TLS certificate verification (self-signed / dev HTTPS only)
37
49
  --no-auth Save platform without OAuth (servers with no authentication). Same as detecting OAuth 404 during login.`);
38
50
  return 0;
39
51
  }
40
52
  if (target === "login") {
41
53
  if (rest[0] === "--help" || rest[0] === "-h") {
42
- console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
54
+ console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
43
55
  return 0;
44
56
  }
45
57
  const url = rest[0];
@@ -69,6 +81,9 @@ Login options:
69
81
  const username = readOption(args, "--username") ?? readOption(args, "-u");
70
82
  const password = readOption(args, "--password") ?? readOption(args, "-p");
71
83
  const usePlaywright = args.includes("--playwright");
84
+ const httpSignin = args.includes("--http-signin");
85
+ const oauthProduct = readOption(args, "--oauth-product");
86
+ const signinPublicKeyFile = readOption(args, "--signin-public-key-file");
72
87
  const clientId = readOption(args, "--client-id");
73
88
  const clientSecret = readOption(args, "--client-secret");
74
89
  const refreshToken = readOption(args, "--refresh-token");
@@ -83,11 +98,16 @@ Login options:
83
98
  const KNOWN_LOGIN_FLAGS = new Set([
84
99
  "--alias", "--client-id", "--client-secret", "--refresh-token",
85
100
  "--port", "--no-browser", "--username", "-u", "--password", "-p",
101
+ "--http-signin",
102
+ "--oauth-product",
103
+ "--signin-public-key-file",
86
104
  "--playwright", "--insecure", "-k", "--no-auth", "--redirect-uri",
87
105
  ]);
88
106
  const KNOWN_VALUE_FLAGS = new Set([
89
107
  "--alias", "--client-id", "--client-secret", "--refresh-token",
90
108
  "--port", "--username", "-u", "--password", "-p", "--redirect-uri",
109
+ "--oauth-product",
110
+ "--signin-public-key-file",
91
111
  ]);
92
112
  for (let i = 0; i < args.length; i++) {
93
113
  const a = args[i];
@@ -110,12 +130,24 @@ Login options:
110
130
  if (noAuth && noBrowser) {
111
131
  console.error("--no-auth does not require a browser; --no-browser is ignored.");
112
132
  }
113
- if (noAuth && (username || password || usePlaywright)) {
114
- console.error("--no-auth cannot be used with Playwright login or -u/-p.");
133
+ if (noAuth && (username || password || usePlaywright || httpSignin)) {
134
+ console.error("--no-auth cannot be used with Playwright login, HTTP sign-in, or -u/-p.");
135
+ return 1;
136
+ }
137
+ if (noBrowser && (username || password || usePlaywright || httpSignin)) {
138
+ console.error("--no-browser cannot be used with Playwright login, HTTP sign-in, or -u/-p.");
115
139
  return 1;
116
140
  }
117
- if (noBrowser && (username || password || usePlaywright)) {
118
- console.error("--no-browser cannot be used with Playwright login or -u/-p.");
141
+ if (httpSignin && usePlaywright) {
142
+ console.error("--http-signin cannot be used with --playwright.");
143
+ return 1;
144
+ }
145
+ if (httpSignin && refreshToken) {
146
+ console.error("--http-signin cannot be used with --refresh-token.");
147
+ return 1;
148
+ }
149
+ if (httpSignin && (!username || !password)) {
150
+ console.error("--http-signin requires -u/--username and -p/--password.");
119
151
  return 1;
120
152
  }
121
153
  if (noBrowser && refreshToken) {
@@ -137,12 +169,67 @@ Login options:
137
169
  clientId, clientSecret, refreshToken, tlsInsecure,
138
170
  });
139
171
  }
140
- else if (username && password) {
141
- console.log("Logging in (headless)...");
172
+ else if (username && password && httpSignin) {
173
+ console.log("Logging in (HTTP /oauth2/signin)...");
174
+ token = await oauth2PasswordSigninLogin(normalizedTarget, {
175
+ username,
176
+ password,
177
+ tlsInsecure,
178
+ port: customPort,
179
+ clientId: clientId ?? undefined,
180
+ clientSecret: clientSecret ?? undefined,
181
+ oauthProduct: oauthProduct ?? undefined,
182
+ signinPublicKeyPemPath: signinPublicKeyFile ?? undefined,
183
+ });
184
+ }
185
+ else if (username && password && usePlaywright) {
186
+ console.log("Logging in (headless, Playwright)...");
142
187
  token = await playwrightLogin(normalizedTarget, {
143
- username, password, tlsInsecure, port: customPort,
188
+ username,
189
+ password,
190
+ tlsInsecure,
191
+ port: customPort,
144
192
  });
145
193
  }
194
+ else if (username && password) {
195
+ const signinOpts = {
196
+ username,
197
+ password,
198
+ tlsInsecure,
199
+ port: customPort,
200
+ clientId: clientId ?? undefined,
201
+ clientSecret: clientSecret ?? undefined,
202
+ oauthProduct: oauthProduct ?? undefined,
203
+ signinPublicKeyPemPath: signinPublicKeyFile ?? undefined,
204
+ };
205
+ console.log("Logging in (HTTP /oauth2/signin)...");
206
+ try {
207
+ token = await oauth2PasswordSigninLogin(normalizedTarget, signinOpts);
208
+ }
209
+ catch (err) {
210
+ if (!isStudiowebShellUnavailableError(err)) {
211
+ throw err;
212
+ }
213
+ const playwrightOk = await isPlaywrightPackageResolvable();
214
+ if (playwrightOk) {
215
+ process.stderr.write("Studio web sign-in shell is not available; falling back to Playwright headless login.\n");
216
+ console.log("Logging in (headless, Playwright)...");
217
+ token = await playwrightLogin(normalizedTarget, {
218
+ username,
219
+ password,
220
+ tlsInsecure,
221
+ port: customPort,
222
+ });
223
+ }
224
+ else {
225
+ console.error("Studio web sign-in shell is not available on this platform, and the Playwright package is not installed.");
226
+ console.error("Install Playwright for headless browser login: npm install playwright && npx playwright install chromium");
227
+ console.error("Alternatively, use OAuth without credentials:");
228
+ console.error(` kweaver auth login ${normalizedTarget} --no-browser`);
229
+ throw err;
230
+ }
231
+ }
232
+ }
146
233
  else if (usePlaywright) {
147
234
  console.log("Opening browser for login (Playwright)...");
148
235
  token = await playwrightLogin(normalizedTarget, {
@@ -217,8 +304,19 @@ Login options:
217
304
  const statusTarget = resolvedTarget && /^https?:\/\//.test(resolvedTarget) ? normalizeBaseUrl(resolvedTarget) : resolvedTarget ?? undefined;
218
305
  const platform = statusTarget ?? getCurrentPlatform();
219
306
  if (!platform) {
220
- console.error("No active platform. Run `kweaver auth login <platform-url>` first.");
221
- return 1;
307
+ const envRaw = process.env.KWEAVER_BASE_URL?.trim();
308
+ const envUrl = envRaw ? normalizeBaseUrl(envRaw) : undefined;
309
+ const envToken = process.env.KWEAVER_TOKEN?.trim();
310
+ if (!envUrl || !envToken) {
311
+ console.error("No active platform. Run `kweaver auth login <platform-url>` first.\n" +
312
+ " Tip: set KWEAVER_BASE_URL and KWEAVER_TOKEN to use this command without a saved login.");
313
+ return 1;
314
+ }
315
+ console.log(`Config directory: ${getConfigDir()}`);
316
+ console.log(`Platform: ${envUrl} (KWEAVER_BASE_URL)`);
317
+ console.log(`Token present: yes (KWEAVER_TOKEN)`);
318
+ console.log(`Refresh token: n/a (env)`);
319
+ return 0;
222
320
  }
223
321
  const token = loadTokenConfig(platform);
224
322
  if (!token) {
@@ -359,7 +457,7 @@ Login options:
359
457
  return 0;
360
458
  }
361
459
  console.error("Usage: kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass] [--playwright]");
362
- console.error(" kweaver auth whoami [platform-url|alias]");
460
+ console.error(" kweaver auth whoami [platform-url|alias] [--json]");
363
461
  console.error(" kweaver auth export [platform-url|alias] [--json]");
364
462
  console.error(" kweaver auth status [platform-url|alias]");
365
463
  console.error(" kweaver auth list");
@@ -456,8 +554,37 @@ Options:
456
554
  const resolved = positional ? resolvePlatformIdentifier(positional) : null;
457
555
  const platform = resolved && /^https?:\/\//.test(resolved) ? normalizeBaseUrl(resolved) : resolved ?? getCurrentPlatform();
458
556
  if (!platform) {
459
- console.error("No active platform. Run `kweaver auth login <platform-url>` first.");
460
- return 1;
557
+ const envRaw = process.env.KWEAVER_BASE_URL?.trim();
558
+ const envUrl = envRaw ? normalizeBaseUrl(envRaw) : undefined;
559
+ const envToken = process.env.KWEAVER_TOKEN?.trim();
560
+ if (!envUrl || !envToken) {
561
+ console.error("No active platform. Run `kweaver auth login <platform-url>` first.");
562
+ return 1;
563
+ }
564
+ const accessToken = envToken.replace(/^Bearer\s+/i, "");
565
+ const payload = decodeJwtPayload(accessToken);
566
+ if (jsonOutput) {
567
+ console.log(JSON.stringify({ platform: envUrl, source: "env", ...(payload ?? {}) }, null, 2));
568
+ return 0;
569
+ }
570
+ console.log(`Platform: ${envUrl}`);
571
+ console.log(`Source: env (KWEAVER_TOKEN)`);
572
+ if (payload) {
573
+ const uname = payload.preferred_username ?? payload.name;
574
+ if (uname)
575
+ console.log(`Username: ${uname}`);
576
+ console.log(`User ID: ${payload.sub ?? "(unknown)"}`);
577
+ console.log(`Issuer: ${payload.iss ?? "(unknown)"}`);
578
+ if (payload.iat)
579
+ console.log(`Issued: ${new Date(payload.iat * 1000).toISOString()}`);
580
+ if (payload.exp)
581
+ console.log(`Expires: ${new Date(payload.exp * 1000).toISOString()}`);
582
+ }
583
+ else {
584
+ console.log(`User info unavailable: opaque access token.`);
585
+ console.log(`Hint: run \`kweaver auth login ${envUrl}\` to obtain a full session.`);
586
+ }
587
+ return 0;
461
588
  }
462
589
  const token = loadTokenConfig(platform);
463
590
  if (!token) {
@@ -49,6 +49,7 @@ export declare function parseKnCreateFromCsvArgs(args: string[]): {
49
49
  batchSize: number;
50
50
  tables: string[];
51
51
  build: boolean;
52
+ recreate: boolean;
52
53
  timeout: number;
53
54
  businessDomain: string;
54
55
  };
@@ -706,6 +706,7 @@ Options:
706
706
  --tables <a,b> Tables to include in KN (default: all imported)
707
707
  --build (default) Build after creation
708
708
  --no-build Skip build
709
+ --recreate Use "insert" mode on first batch (only effective for new tables)
709
710
  --timeout <n> Build timeout in seconds (default: 300)
710
711
  -bd, --biz-domain Business domain (default: bd_public)`;
711
712
  export function parseKnCreateFromCsvArgs(args) {
@@ -716,6 +717,7 @@ export function parseKnCreateFromCsvArgs(args) {
716
717
  let batchSize = 500;
717
718
  let tablesStr = "";
718
719
  let build = true;
720
+ let recreate = false;
719
721
  let timeout = 300;
720
722
  let businessDomain = "";
721
723
  for (let i = 0; i < args.length; i += 1) {
@@ -752,6 +754,10 @@ export function parseKnCreateFromCsvArgs(args) {
752
754
  build = false;
753
755
  continue;
754
756
  }
757
+ if (arg === "--recreate") {
758
+ recreate = true;
759
+ continue;
760
+ }
755
761
  if (arg === "--timeout" && args[i + 1]) {
756
762
  timeout = parseInt(args[++i], 10);
757
763
  if (Number.isNaN(timeout) || timeout < 1)
@@ -772,7 +778,7 @@ export function parseKnCreateFromCsvArgs(args) {
772
778
  }
773
779
  if (!businessDomain)
774
780
  businessDomain = resolveBusinessDomain();
775
- return { dsId, files, name, tablePrefix, batchSize, tables, build, timeout, businessDomain };
781
+ return { dsId, files, name, tablePrefix, batchSize, tables, build, recreate, timeout, businessDomain };
776
782
  }
777
783
  export async function runKnCreateFromCsvCommand(args) {
778
784
  let options;
@@ -795,6 +801,7 @@ export async function runKnCreateFromCsvCommand(args) {
795
801
  "--table-prefix", options.tablePrefix,
796
802
  "--batch-size", String(options.batchSize),
797
803
  "-bd", options.businessDomain,
804
+ ...(options.recreate ? ["--recreate"] : []),
798
805
  ];
799
806
  const importResult = await runDsImportCsv(importArgs);
800
807
  if (importResult.code !== 0) {
@@ -1,8 +1,18 @@
1
+ export type FormField = {
2
+ name: string;
3
+ kind: "string";
4
+ value: string;
5
+ } | {
6
+ name: string;
7
+ kind: "file";
8
+ path: string;
9
+ };
1
10
  export interface CallInvocation {
2
11
  url: string;
3
12
  method: string;
4
13
  headers: Headers;
5
14
  body?: string;
15
+ formFields?: FormField[];
6
16
  pretty: boolean;
7
17
  verbose: boolean;
8
18
  businessDomain: string;
@@ -1,3 +1,5 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename } from "node:path";
1
3
  import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
2
4
  import { isNoAuth } from "../config/no-auth.js";
3
5
  import { HttpError } from "../utils/http.js";
@@ -6,6 +8,7 @@ export function parseCallArgs(args) {
6
8
  const headers = new Headers();
7
9
  let method = "GET";
8
10
  let body;
11
+ const formFields = [];
9
12
  let url;
10
13
  let pretty = true;
11
14
  let verbose = false;
@@ -40,6 +43,26 @@ export function parseCallArgs(args) {
40
43
  index += 1;
41
44
  continue;
42
45
  }
46
+ if (arg === "-F" || arg === "--form") {
47
+ const raw = args[index + 1];
48
+ if (!raw)
49
+ throw new Error("Missing value for -F flag");
50
+ const eq = raw.indexOf("=");
51
+ if (eq === -1)
52
+ throw new Error(`Invalid -F format: ${raw} (expected key=value or key=@path)`);
53
+ const name = raw.slice(0, eq);
54
+ const rhs = raw.slice(eq + 1);
55
+ if (rhs.startsWith("@")) {
56
+ formFields.push({ name, kind: "file", path: rhs.slice(1) });
57
+ }
58
+ else {
59
+ formFields.push({ name, kind: "string", value: rhs });
60
+ }
61
+ if (method === "GET")
62
+ method = "POST";
63
+ index += 1;
64
+ continue;
65
+ }
43
66
  if (arg === "--pretty") {
44
67
  pretty = true;
45
68
  continue;
@@ -70,9 +93,21 @@ export function parseCallArgs(args) {
70
93
  if (!url) {
71
94
  throw new Error("Missing request URL");
72
95
  }
96
+ if (formFields.length > 0 && body !== undefined) {
97
+ throw new Error("-F and -d are mutually exclusive");
98
+ }
73
99
  if (!businessDomain)
74
100
  businessDomain = resolveBusinessDomain();
75
- return { url, method, headers, body, pretty, verbose, businessDomain };
101
+ return {
102
+ url,
103
+ method,
104
+ headers,
105
+ body,
106
+ formFields: formFields.length > 0 ? formFields : undefined,
107
+ pretty,
108
+ verbose,
109
+ businessDomain,
110
+ };
76
111
  }
77
112
  function injectAuthHeaders(headers, accessToken, businessDomain) {
78
113
  if (!isNoAuth(accessToken)) {
@@ -115,12 +150,17 @@ export function formatVerboseRequest(invocation) {
115
150
  for (const [name, value] of entries) {
116
151
  lines.push(` ${name}: ${value}`);
117
152
  }
118
- lines.push(`Body: ${invocation.body ? "present" : "empty"}`);
153
+ const bodyDesc = invocation.formFields && invocation.formFields.length > 0
154
+ ? `multipart (${invocation.formFields.length} field${invocation.formFields.length > 1 ? "s" : ""})`
155
+ : invocation.body
156
+ ? "present"
157
+ : "empty";
158
+ lines.push(`Body: ${bodyDesc}`);
119
159
  return lines;
120
160
  }
121
161
  export async function runCallCommand(args) {
122
162
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
123
- console.log(`kweaver call <url> [-X METHOD] [-H "Name: value"] [-d BODY] [--pretty] [--verbose] [-bd value]
163
+ console.log(`kweaver call <url> [-X METHOD] [-H "Name: value"] [-d BODY] [-F key=value] [--pretty] [--verbose] [-bd value]
124
164
 
125
165
  Call an API with curl-style flags and auto-injected token headers.
126
166
 
@@ -129,6 +169,7 @@ Options:
129
169
  -X, --request HTTP method (default: GET)
130
170
  -H, --header Extra header (repeatable)
131
171
  -d, --data, --data-raw JSON request body (sets Content-Type: application/json if not set)
172
+ -F, --form Multipart form field. -F key=value or -F key=@/path/to/file. Repeatable. Mutually exclusive with -d.
132
173
  -bd, --biz-domain Override x-business-domain (default: bd_public)
133
174
  -v, --verbose Print request info to stderr
134
175
  --pretty Pretty-print JSON output (default)`);
@@ -150,7 +191,22 @@ Options:
150
191
  : invocation.url;
151
192
  const headers = new Headers(invocation.headers);
152
193
  injectAuthHeaders(headers, token.accessToken, invocation.businessDomain);
153
- if (invocation.body !== undefined &&
194
+ let requestBody = invocation.body;
195
+ if (invocation.formFields && invocation.formFields.length > 0) {
196
+ const form = new FormData();
197
+ for (const field of invocation.formFields) {
198
+ if (field.kind === "string") {
199
+ form.append(field.name, field.value);
200
+ }
201
+ else {
202
+ const buf = await readFile(field.path);
203
+ form.append(field.name, new Blob([buf]), basename(field.path));
204
+ }
205
+ }
206
+ requestBody = form;
207
+ // do not set content-type — fetch sets multipart boundary
208
+ }
209
+ else if (invocation.body !== undefined &&
154
210
  invocation.body.length > 0 &&
155
211
  !headers.has("content-type") &&
156
212
  !headers.has("Content-Type")) {
@@ -164,7 +220,7 @@ Options:
164
220
  const response = await fetch(url, {
165
221
  method: invocation.method,
166
222
  headers,
167
- body: invocation.body,
223
+ body: requestBody,
168
224
  });
169
225
  const rawText = await response.text();
170
226
  const text = stripSseDoneMarker(rawText, response.headers.get("content-type"));
@@ -1,5 +1,14 @@
1
1
  import { listBusinessDomains } from "../api/business-domains.js";
2
- import { withTokenRetry } from "../auth/oauth.js";
2
+ import { normalizeBaseUrl, withTokenRetry } from "../auth/oauth.js";
3
+ // Resolve platform URL: saved current platform > KWEAVER_BASE_URL (normalized to
4
+ // match what `auth login` writes, so env users share the same platforms/<key>/ dir).
5
+ function resolvePlatformUrl() {
6
+ const saved = getCurrentPlatform();
7
+ if (saved)
8
+ return saved;
9
+ const env = process.env.KWEAVER_BASE_URL?.trim();
10
+ return env ? normalizeBaseUrl(env) : undefined;
11
+ }
3
12
  import { getCurrentPlatform, loadPlatformBusinessDomain, resolveBusinessDomain, savePlatformBusinessDomain, } from "../config/store.js";
4
13
  const HELP = `kweaver config
5
14
 
@@ -20,9 +29,9 @@ export async function runConfigCommand(args) {
20
29
  return 0;
21
30
  }
22
31
  if (sub === "show") {
23
- const platform = getCurrentPlatform();
32
+ const platform = resolvePlatformUrl();
24
33
  if (!platform) {
25
- console.error("No active platform. Run `kweaver auth login <url>` first.");
34
+ console.error("No active platform. Run `kweaver auth login <url>` first.\n Tip: set KWEAVER_BASE_URL to use this command without a saved login.");
26
35
  return 1;
27
36
  }
28
37
  const bd = resolveBusinessDomain(platform);
@@ -31,7 +40,8 @@ export async function runConfigCommand(args) {
31
40
  : loadPlatformBusinessDomain(platform)
32
41
  ? "config"
33
42
  : "default";
34
- console.log(`Platform: ${platform}`);
43
+ const platformSource = getCurrentPlatform() ? "" : " (KWEAVER_BASE_URL)";
44
+ console.log(`Platform: ${platform}${platformSource}`);
35
45
  console.log(`Business Domain: ${bd} (${source})`);
36
46
  return 0;
37
47
  }
@@ -41,19 +51,19 @@ export async function runConfigCommand(args) {
41
51
  console.error("Usage: kweaver config set-bd <value>");
42
52
  return 1;
43
53
  }
44
- const platform = getCurrentPlatform();
54
+ const platform = resolvePlatformUrl();
45
55
  if (!platform) {
46
- console.error("No active platform. Run `kweaver auth login <url>` first.");
56
+ console.error("No active platform. Run `kweaver auth login <url>` first.\n Tip: set KWEAVER_BASE_URL to write the business domain for that platform.");
47
57
  return 1;
48
58
  }
49
59
  savePlatformBusinessDomain(platform, value);
50
- console.log(`Business domain set to: ${value}`);
60
+ console.log(`Business domain set to: ${value} (${getCurrentPlatform() ? platform : `${platform} via KWEAVER_BASE_URL`})`);
51
61
  return 0;
52
62
  }
53
63
  if (sub === "list-bd") {
54
- const platform = getCurrentPlatform();
64
+ const platform = resolvePlatformUrl();
55
65
  if (!platform) {
56
- console.error("No active platform. Run `kweaver auth login <url>` first.");
66
+ console.error("No active platform. Run `kweaver auth login <url>` first.\n Tip: set KWEAVER_BASE_URL and KWEAVER_TOKEN to use this command without a saved login.");
57
67
  return 1;
58
68
  }
59
69
  try {
@@ -281,20 +281,26 @@ async function runGetPrompt(options, args, pretty) {
281
281
  async function runKnSearch(options, args, pretty) {
282
282
  let query;
283
283
  let onlySchema = false;
284
+ let knIdOverride;
284
285
  for (let i = 0; i < args.length; i += 1) {
285
286
  const arg = args[i];
286
287
  if (arg === "--only-schema") {
287
288
  onlySchema = true;
288
289
  }
290
+ else if ((arg === "--kn-id" || arg === "-k") && args[i + 1]) {
291
+ knIdOverride = args[i + 1];
292
+ i += 1;
293
+ }
289
294
  else if (!arg.startsWith("-") && !query) {
290
295
  query = arg;
291
296
  }
292
297
  }
293
298
  if (!query) {
294
- console.error("Usage: kweaver context-loader kn-search <query> [--only-schema]");
299
+ console.error("Usage: kweaver context-loader kn-search <query> [--kn-id <id>] [--only-schema]");
295
300
  return 1;
296
301
  }
297
- const result = await knSearch(options, { query, only_schema: onlySchema });
302
+ const effectiveOptions = knIdOverride ? { ...options, knId: knIdOverride } : options;
303
+ const result = await knSearch(effectiveOptions, { query, only_schema: onlySchema });
298
304
  console.log(formatOutput(result, pretty));
299
305
  return 0;
300
306
  }
@@ -17,6 +17,7 @@ export declare function resolveFiles(pattern: string): Promise<string[]>;
17
17
  export interface ImportCsvResult {
18
18
  code: number;
19
19
  tables: string[];
20
+ failed: string[];
20
21
  tableColumns: Record<string, string[]>;
21
22
  sampleRows: Record<string, Array<Record<string, string | null>>>;
22
23
  }
@@ -384,17 +384,17 @@ export async function runDsImportCsv(args) {
384
384
  catch (error) {
385
385
  if (error instanceof Error && error.message === "help") {
386
386
  console.log(IMPORT_CSV_HELP);
387
- return { code: 0, tables: [], tableColumns: {}, sampleRows: {} };
387
+ return { code: 0, tables: [], failed: [], tableColumns: {}, sampleRows: {} };
388
388
  }
389
389
  throw error;
390
390
  }
391
391
  if (!options.datasourceId) {
392
392
  console.error("Usage: kweaver ds import-csv <ds-id> --files <glob_or_list> [options]");
393
- return { code: 1, tables: [], tableColumns: {}, sampleRows: {} };
393
+ return { code: 1, tables: [], failed: [], tableColumns: {}, sampleRows: {} };
394
394
  }
395
395
  if (!options.files) {
396
396
  console.error("Error: --files is required");
397
- return { code: 1, tables: [], tableColumns: {}, sampleRows: {} };
397
+ return { code: 1, tables: [], failed: [], tableColumns: {}, sampleRows: {} };
398
398
  }
399
399
  // 1. Get credentials
400
400
  const token = await ensureValidToken();
@@ -429,7 +429,7 @@ export async function runDsImportCsv(args) {
429
429
  }
430
430
  if (parsed.length === 0) {
431
431
  console.error("All files were skipped — nothing to import");
432
- return { code: 1, tables: [], tableColumns: {}, sampleRows: {} };
432
+ return { code: 1, tables: [], failed: [], tableColumns: {}, sampleRows: {} };
433
433
  }
434
434
  // Phase 2: Import each file in batches
435
435
  const succeeded = [];
@@ -466,7 +466,7 @@ export async function runDsImportCsv(args) {
466
466
  process.stderr.write(`${elapsed}s\n`);
467
467
  }
468
468
  catch (err) {
469
- const msg = err instanceof Error ? err.message : String(err);
469
+ const msg = formatHttpError(err);
470
470
  process.stderr.write(`FAILED\n`);
471
471
  console.error(`[${tableName}] batch ${batchLabel} error: ${msg}`);
472
472
  batchFailed = true;
@@ -487,14 +487,14 @@ export async function runDsImportCsv(args) {
487
487
  if (failed.length > 0) {
488
488
  console.error(`Failed tables: ${failed.join(", ")}`);
489
489
  }
490
- console.log(JSON.stringify({
491
- tables: succeeded,
492
- failed,
493
- summary: { succeeded: succeeded.length, failed: failed.length },
494
- }, null, 2));
495
- return { code: failed.length > 0 ? 1 : 0, tables: succeeded, tableColumns, sampleRows };
490
+ return { code: failed.length > 0 ? 1 : 0, tables: succeeded, failed, tableColumns, sampleRows };
496
491
  }
497
492
  export async function runDsImportCsvCommand(args) {
498
493
  const result = await runDsImportCsv(args);
494
+ console.log(JSON.stringify({
495
+ tables: result.tables,
496
+ failed: result.failed,
497
+ summary: { succeeded: result.tables.length, failed: result.failed.length },
498
+ }, null, 2));
499
499
  return result.code;
500
500
  }