@kweaver-ai/kweaver-sdk 0.6.0 → 0.6.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.
- package/README.md +26 -3
- package/README.zh.md +29 -1
- package/dist/api/vega.d.ts +8 -0
- package/dist/api/vega.js +18 -0
- package/dist/auth/oauth.d.ts +7 -6
- package/dist/auth/oauth.js +106 -73
- package/dist/cli.js +3 -2
- package/dist/commands/auth.js +28 -15
- package/dist/commands/vega.js +101 -2
- package/dist/resources/vega.d.ts +2 -0
- package/dist/resources/vega.js +6 -1
- package/dist/utils/browser.js +33 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,9 +126,18 @@ const result = await client.dataflows.execute({
|
|
|
126
126
|
steps: [{ id: "s1", title: "load", operator: "csv_import", parameters: {} }],
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
// Vega observability
|
|
129
|
+
// Vega — observability and query
|
|
130
130
|
const catalogs = await client.vega.listCatalogs();
|
|
131
131
|
const health = await client.vega.health();
|
|
132
|
+
// Structured query — POST /api/vega-backend/v1/query/execute (JSON string body)
|
|
133
|
+
const structured = await client.vega.executeQuery(
|
|
134
|
+
JSON.stringify({ tables: [{ resource_id: "res-1" }], output_fields: ["*"], limit: 20 }),
|
|
135
|
+
);
|
|
136
|
+
// Direct SQL or OpenSearch DSL — POST /api/vega-backend/v1/resources/query
|
|
137
|
+
// Use {{resource_id}} placeholders so vega-backend routes to the correct catalog connector.
|
|
138
|
+
const rows = await client.vega.sqlQuery(
|
|
139
|
+
JSON.stringify({ query: "SELECT * FROM {{res-1}} LIMIT 5", resource_type: "mysql" }),
|
|
140
|
+
);
|
|
132
141
|
|
|
133
142
|
// Context Loader (semantic search over a BKN via MCP)
|
|
134
143
|
const cl = client.contextLoader(mcpUrl, "bkn-id");
|
|
@@ -142,7 +151,7 @@ const skillMd = await client.skills.fetchContent("skill-id");
|
|
|
142
151
|
## CLI Reference
|
|
143
152
|
|
|
144
153
|
```
|
|
145
|
-
kweaver auth login <url> [--alias name] [--no-auth] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
154
|
+
kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
146
155
|
kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (headless login)
|
|
147
156
|
kweaver auth export [url|alias] [--json] (export command to run on a headless host)
|
|
148
157
|
kweaver auth status/list/use/delete/logout
|
|
@@ -164,7 +173,7 @@ kweaver bkn action-execution get
|
|
|
164
173
|
kweaver bkn action-log list/get/cancel
|
|
165
174
|
kweaver agent list/get/create/update/delete/chat/sessions/history/publish/unpublish
|
|
166
175
|
kweaver skill list/market/get/register/status/delete/content/read-file/download/install
|
|
167
|
-
kweaver vega health/stats/inspect/catalog/resource/connector-type
|
|
176
|
+
kweaver vega health/stats/inspect/sql/catalog/resource/connector-type
|
|
168
177
|
kweaver context-loader config set/use/list/show
|
|
169
178
|
kweaver context-loader kn-search/query-object-instance/...
|
|
170
179
|
kweaver call <path> [-X METHOD] [-d BODY] [-H header]
|
|
@@ -184,6 +193,20 @@ kweaver dataflow logs <dagId> <instanceId> --detail
|
|
|
184
193
|
|
|
185
194
|
`kweaver dataflow runs --since` filters one local natural day. If the value cannot be parsed by `new Date(...)`, the CLI falls back to the most recent 20 runs. `kweaver dataflow logs` defaults to summary output; add `--detail` to print indented `input` and `output` payloads.
|
|
186
195
|
|
|
196
|
+
### Vega `sql` CLI examples
|
|
197
|
+
|
|
198
|
+
Direct SQL against catalog-backed resources (`POST /api/vega-backend/v1/resources/query`). In SQL, use **`{{<resource_id>}}`** or **`{{.<resource_id>}}`** (Vega resource id from `vega resource list` / `get`) so the backend resolves the physical table and connector. `--resource-type` accepts the connector type of the target data source (run `kweaver vega connector-type list` to see available types). In simple mode, **quote the entire `--query` value** so the shell does not treat `{` / `}` specially.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Simple mode (recommended): avoid JSON-escaping the query string
|
|
202
|
+
kweaver vega sql --resource-type mysql --query "SELECT * FROM {{res-1}} LIMIT 5"
|
|
203
|
+
|
|
204
|
+
# Advanced mode: full JSON body (optional fields like query_timeout, stream_size, OpenSearch DSL object)
|
|
205
|
+
kweaver vega sql -d '{"resource_type":"mysql","query":"SELECT * FROM {{res-1}} LIMIT 5"}'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
If both `-d` and `--query` / `--resource-type` are present, **only `-d` is used**.
|
|
209
|
+
|
|
187
210
|
**No-auth platforms:** If OAuth is not enabled, use `kweaver auth <url> --no-auth` (or run a normal `auth login`; a **404** on `POST /oauth2/clients` switches to no-auth automatically). Credentials are still saved under `~/.kweaver/` and work with `auth use` / `auth list`. Optional: `KWEAVER_NO_AUTH=1` with `KWEAVER_BASE_URL` when no token env is set. SDK: `new KWeaverClient({ baseUrl, auth: false })` or `kweaver.configure({ baseUrl, auth: false })`.
|
|
188
211
|
|
|
189
212
|
## Environment Variables
|
package/README.zh.md
CHANGED
|
@@ -119,6 +119,19 @@ const queryRows = await client.dataviews.query(viewId, {
|
|
|
119
119
|
needTotal: true,
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
// Vega — 可观测性与查询
|
|
123
|
+
const catalogs = await client.vega.listCatalogs();
|
|
124
|
+
const health = await client.vega.health();
|
|
125
|
+
// 结构化查询 — POST /api/vega-backend/v1/query/execute(body 为 JSON 字符串)
|
|
126
|
+
const structured = await client.vega.executeQuery(
|
|
127
|
+
JSON.stringify({ tables: [{ resource_id: "res-1" }], output_fields: ["*"], limit: 20 }),
|
|
128
|
+
);
|
|
129
|
+
// 直连 SQL 或 OpenSearch DSL — POST /api/vega-backend/v1/resources/query
|
|
130
|
+
// 使用 {{resource_id}} 占位符以路由到正确的 catalog connector
|
|
131
|
+
const rows = await client.vega.sqlQuery(
|
|
132
|
+
JSON.stringify({ query: "SELECT * FROM {{res-1}} LIMIT 5", resource_type: "mysql" }),
|
|
133
|
+
);
|
|
134
|
+
|
|
122
135
|
// Context Loader(通过 MCP 对 BKN 做语义搜索)
|
|
123
136
|
const cl = client.contextLoader(mcpUrl, "bkn-id");
|
|
124
137
|
const results = await cl.search({ query: "高血压 治疗" });
|
|
@@ -131,7 +144,7 @@ const skillMd = await client.skills.fetchContent("skill-id");
|
|
|
131
144
|
## 命令速查
|
|
132
145
|
|
|
133
146
|
```
|
|
134
|
-
kweaver auth login <url> [--alias name] [--no-auth] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
147
|
+
kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
135
148
|
kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (无浏览器登录)
|
|
136
149
|
kweaver auth export [url|alias] [--json] (导出在无浏览器机器上运行的命令)
|
|
137
150
|
kweaver auth status/list/use/delete/logout
|
|
@@ -149,6 +162,7 @@ kweaver bkn action-execution get
|
|
|
149
162
|
kweaver bkn action-log list/get/cancel
|
|
150
163
|
kweaver agent list/get/chat/sessions/history
|
|
151
164
|
kweaver skill list/market/get/register/status/delete/content/read-file/download/install
|
|
165
|
+
kweaver vega health|stats|inspect|sql|catalog|resource|connector-type
|
|
152
166
|
kweaver context-loader config set/use/list/show
|
|
153
167
|
kweaver context-loader kn-search/query-object-instance/...
|
|
154
168
|
kweaver call <path> [-X METHOD] [-d BODY] [-H header]
|
|
@@ -168,6 +182,20 @@ kweaver dataflow logs <dagId> <instanceId> --detail
|
|
|
168
182
|
|
|
169
183
|
`kweaver dataflow runs --since` 会按本地自然日过滤;如果参数无法被 `new Date(...)` 解析,CLI 会回退到最近 20 条运行记录。`kweaver dataflow logs` 默认输出摘要;加上 `--detail` 会打印带缩进的 `input` 和 `output` 载荷。
|
|
170
184
|
|
|
185
|
+
### Vega `sql` CLI 示例
|
|
186
|
+
|
|
187
|
+
对 Catalog 资源执行直连 SQL(`POST /api/vega-backend/v1/resources/query`)。SQL 中使用 **`{{<resource_id>}}`** 或 **`{{.<resource_id>}}`**(资源 id 来自 `vega resource list` / `get`),后端据此解析物理表与 connector。`--resource-type` 为目标数据源的连接器类型,可通过 `kweaver vega connector-type list` 查看。简单模式下请**用引号包住整个 `--query` 参数**,避免 shell 对花括号做特殊处理。
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# 简单模式(推荐):避免在 JSON 里转义整段 SQL
|
|
191
|
+
kweaver vega sql --resource-type mysql --query "SELECT * FROM {{res-1}} LIMIT 5"
|
|
192
|
+
|
|
193
|
+
# 高级模式:完整 JSON(可带 query_timeout、stream_size,或 OpenSearch DSL 对象等)
|
|
194
|
+
kweaver vega sql -d '{"resource_type":"mysql","query":"SELECT * FROM {{res-1}} LIMIT 5"}'
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
若同时提供 `-d` 与 `--query` / `--resource-type`,**仅以 `-d` 为准**。
|
|
198
|
+
|
|
171
199
|
**无 OAuth 的平台:** 使用 `kweaver auth <url> --no-auth`,或照常 `auth login`;若 `POST /oauth2/clients` 返回 **404**,CLI 会提示并自动保存为 no-auth。凭据仍在 `~/.kweaver/`,可用 `auth use` / `auth list` 切换。可选环境变量 `KWEAVER_NO_AUTH=1`(未设置 `KWEAVER_TOKEN` 时)配合 `KWEAVER_BASE_URL`。SDK:`new KWeaverClient({ baseUrl, auth: false })` 或 `kweaver.configure({ baseUrl, auth: false })`。
|
|
172
200
|
|
|
173
201
|
## 环境变量
|
package/dist/api/vega.d.ts
CHANGED
|
@@ -220,6 +220,14 @@ export interface ExecuteVegaQueryOptions {
|
|
|
220
220
|
businessDomain?: string;
|
|
221
221
|
}
|
|
222
222
|
export declare function executeVegaQuery(options: ExecuteVegaQueryOptions): Promise<string>;
|
|
223
|
+
export interface VegaSQLQueryOptions {
|
|
224
|
+
baseUrl: string;
|
|
225
|
+
accessToken: string;
|
|
226
|
+
body: string;
|
|
227
|
+
businessDomain?: string;
|
|
228
|
+
}
|
|
229
|
+
/** POST /api/vega-backend/v1/resources/query — direct SQL (or OpenSearch DSL) against catalog-backed resources. */
|
|
230
|
+
export declare function vegaSQLQuery(options: VegaSQLQueryOptions): Promise<string>;
|
|
223
231
|
export interface ListAllVegaResourcesOptions {
|
|
224
232
|
baseUrl: string;
|
|
225
233
|
accessToken: string;
|
package/dist/api/vega.js
CHANGED
|
@@ -458,6 +458,24 @@ export async function executeVegaQuery(options) {
|
|
|
458
458
|
throw new HttpError(response.status, response.statusText, body);
|
|
459
459
|
return body;
|
|
460
460
|
}
|
|
461
|
+
/** POST /api/vega-backend/v1/resources/query — direct SQL (or OpenSearch DSL) against catalog-backed resources. */
|
|
462
|
+
export async function vegaSQLQuery(options) {
|
|
463
|
+
const { baseUrl, accessToken, body: requestBody, businessDomain = "bd_public" } = options;
|
|
464
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
465
|
+
const url = `${base}${VEGA_BASE}/resources/query`;
|
|
466
|
+
const response = await fetch(url, {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: {
|
|
469
|
+
...buildHeaders(accessToken, businessDomain),
|
|
470
|
+
"content-type": "application/json",
|
|
471
|
+
},
|
|
472
|
+
body: requestBody,
|
|
473
|
+
});
|
|
474
|
+
const body = await response.text();
|
|
475
|
+
if (!response.ok)
|
|
476
|
+
throw new HttpError(response.status, response.statusText, body);
|
|
477
|
+
return body;
|
|
478
|
+
}
|
|
461
479
|
export async function listAllVegaResources(options) {
|
|
462
480
|
const { baseUrl, accessToken, limit, offset, businessDomain = "bd_public" } = options;
|
|
463
481
|
const base = baseUrl.replace(/\/+$/, "");
|
package/dist/auth/oauth.d.ts
CHANGED
|
@@ -14,20 +14,23 @@ export declare function normalizeBaseUrl(value: string): string;
|
|
|
14
14
|
/**
|
|
15
15
|
* OAuth2 Authorization Code login flow.
|
|
16
16
|
* 1. Register client (if not already registered), OR use a provided client ID
|
|
17
|
-
* 2. Open browser to /oauth2/auth
|
|
18
|
-
* 3. Receive authorization code via local HTTP callback
|
|
17
|
+
* 2. Open browser to /oauth2/auth (unless `noBrowser` or browser launch fails)
|
|
18
|
+
* 3. Receive authorization code via local HTTP callback, or stdin paste (`noBrowser` / fallback)
|
|
19
19
|
* 4. Exchange code for access_token + refresh_token
|
|
20
20
|
* 5. Save token.json + client.json to ~/.kweaver/
|
|
21
21
|
*/
|
|
22
22
|
export declare function oauth2Login(baseUrl: string, options?: {
|
|
23
23
|
port?: number;
|
|
24
|
-
/** Full redirect URI override (e.g. "http://127.0.0.1:9010/callback" or a remote URL). */
|
|
25
|
-
redirectUri?: string;
|
|
26
24
|
scope?: string;
|
|
27
25
|
clientId?: string;
|
|
28
26
|
clientSecret?: string;
|
|
29
27
|
/** Skip TLS certificate verification (self-signed / dev servers only). */
|
|
30
28
|
tlsInsecure?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Do not open a browser; print the auth URL and prompt for the callback URL or code (stdin).
|
|
31
|
+
* For headless servers or when automatic browser launch is unavailable.
|
|
32
|
+
*/
|
|
33
|
+
noBrowser?: boolean;
|
|
31
34
|
}): Promise<TokenConfig>;
|
|
32
35
|
/**
|
|
33
36
|
* Playwright-automated OAuth2 login.
|
|
@@ -44,8 +47,6 @@ export declare function playwrightLogin(baseUrl: string, options?: {
|
|
|
44
47
|
username?: string;
|
|
45
48
|
password?: string;
|
|
46
49
|
port?: number;
|
|
47
|
-
/** Full redirect URI override. */
|
|
48
|
-
redirectUri?: string;
|
|
49
50
|
scope?: string;
|
|
50
51
|
tlsInsecure?: boolean;
|
|
51
52
|
}): Promise<TokenConfig>;
|
package/dist/auth/oauth.js
CHANGED
|
@@ -160,8 +160,7 @@ async function isClientStillValid(baseUrl, clientId, redirectUri) {
|
|
|
160
160
|
});
|
|
161
161
|
if (resp.status === 302) {
|
|
162
162
|
const location = resp.headers.get("location") ?? "";
|
|
163
|
-
if (location.includes("error=
|
|
164
|
-
location.includes("error=error")) {
|
|
163
|
+
if (location.includes("error=")) {
|
|
165
164
|
return false;
|
|
166
165
|
}
|
|
167
166
|
return true;
|
|
@@ -199,73 +198,99 @@ async function resolveOrRegisterClient(baseUrl, redirectUri, scope, options) {
|
|
|
199
198
|
}
|
|
200
199
|
let client = loadClientConfig(baseUrl);
|
|
201
200
|
if (client?.clientId) {
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
201
|
+
const storedUri = client.redirectUri ?? redirectUri;
|
|
202
|
+
const valid = await isClientStillValid(baseUrl, client.clientId, storedUri);
|
|
203
|
+
if (valid) {
|
|
204
|
+
if (storedUri !== redirectUri) {
|
|
205
|
+
process.stderr.write("Redirect URI changed. Re-registering OAuth2 client…\n");
|
|
206
|
+
deleteClientConfig(baseUrl);
|
|
207
|
+
client = null;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
return client;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
process.stderr.write("Cached OAuth2 client is no longer valid on the server. Re-registering…\n");
|
|
215
|
+
deleteClientConfig(baseUrl);
|
|
216
|
+
client = null;
|
|
217
|
+
}
|
|
208
218
|
}
|
|
209
219
|
const registered = await registerOAuth2Client(baseUrl, redirectUri, scope);
|
|
210
220
|
saveClientConfig(baseUrl, registered);
|
|
211
221
|
return registered;
|
|
212
222
|
}
|
|
213
223
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
224
|
+
* Emphasize text on stderr (bold + bright yellow) when stderr is a TTY and `NO_COLOR` is unset.
|
|
225
|
+
* See https://no-color.org/
|
|
216
226
|
*/
|
|
217
|
-
function
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const port = parsed.port ? Number(parsed.port) : (parsed.protocol === "https:" ? 443 : 80);
|
|
222
|
-
const isLocalhost = host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
223
|
-
return { host, port, pathname: parsed.pathname, isLocalhost };
|
|
227
|
+
function stderrEmphasis(text) {
|
|
228
|
+
const noColor = process.env.NO_COLOR;
|
|
229
|
+
if (noColor != null && noColor !== "") {
|
|
230
|
+
return text;
|
|
224
231
|
}
|
|
225
|
-
|
|
226
|
-
return
|
|
232
|
+
if (!process.stderr.isTTY) {
|
|
233
|
+
return text;
|
|
227
234
|
}
|
|
235
|
+
return `\x1b[1;33m${text}\x1b[0m`;
|
|
228
236
|
}
|
|
229
237
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
* to extract the authorization code.
|
|
238
|
+
* Headless login: read authorization code from stdin (full callback URL or raw code).
|
|
239
|
+
* Used with `--no-browser` or when automatic browser launch fails.
|
|
233
240
|
*/
|
|
234
|
-
async function
|
|
241
|
+
async function promptForCode(authUrl, state, port, pasteMode = "explicit") {
|
|
235
242
|
const { createInterface } = await import("node:readline");
|
|
236
|
-
|
|
237
|
-
"
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
"
|
|
243
|
+
const intro = pasteMode === "explicit"
|
|
244
|
+
? "Open this URL on any device (use a private/incognito window if you need the full sign-in form):\n\n"
|
|
245
|
+
: "Could not open a browser automatically. Open this URL on any device:\n\n";
|
|
246
|
+
const pasteInstructions = "After login, the browser may show an error page (this is expected if nothing listens on localhost).\n" +
|
|
247
|
+
"Copy the FULL URL from the address bar and paste it here, or paste only the authorization code.\n" +
|
|
248
|
+
`The URL looks like: http://127.0.0.1:${port}/callback?code=THIS_PART&state=...\n\n`;
|
|
249
|
+
process.stderr.write("\n" +
|
|
250
|
+
intro +
|
|
251
|
+
` ${authUrl}\n\n` +
|
|
252
|
+
stderrEmphasis(pasteInstructions));
|
|
241
253
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
242
|
-
const
|
|
243
|
-
rl.
|
|
254
|
+
const input = await new Promise((resolve, reject) => {
|
|
255
|
+
rl.on("close", () => reject(new Error("Login cancelled.")));
|
|
256
|
+
rl.question("Paste URL or code> ", (answer) => {
|
|
244
257
|
rl.close();
|
|
245
258
|
resolve(answer.trim());
|
|
246
259
|
});
|
|
247
260
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
261
|
+
if (input.includes("code=")) {
|
|
262
|
+
let url;
|
|
263
|
+
try {
|
|
264
|
+
url = new URL(input.startsWith("http") ? input : `http://x/?${input}`);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
throw new Error("Could not parse the pasted URL. Paste the full callback URL or the code value.");
|
|
268
|
+
}
|
|
269
|
+
const receivedState = url.searchParams.get("state");
|
|
270
|
+
if (receivedState && receivedState !== state) {
|
|
271
|
+
throw new Error("OAuth2 state mismatch — possible CSRF attack.");
|
|
272
|
+
}
|
|
273
|
+
const err = url.searchParams.get("error");
|
|
274
|
+
if (err) {
|
|
275
|
+
const desc = url.searchParams.get("error_description") ?? "";
|
|
276
|
+
throw new Error(desc ? `Authorization failed: ${err} — ${desc}` : `Authorization failed: ${err}`);
|
|
277
|
+
}
|
|
278
|
+
const code = url.searchParams.get("code");
|
|
279
|
+
if (!code) {
|
|
280
|
+
throw new Error("No authorization code found in the pasted URL.");
|
|
281
|
+
}
|
|
282
|
+
return code;
|
|
257
283
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
throw new Error("No authorization code found in the callback URL.");
|
|
284
|
+
if (!input) {
|
|
285
|
+
throw new Error("No authorization code entered.");
|
|
261
286
|
}
|
|
262
|
-
return
|
|
287
|
+
return input;
|
|
263
288
|
}
|
|
264
289
|
/**
|
|
265
290
|
* OAuth2 Authorization Code login flow.
|
|
266
291
|
* 1. Register client (if not already registered), OR use a provided client ID
|
|
267
|
-
* 2. Open browser to /oauth2/auth
|
|
268
|
-
* 3. Receive authorization code via local HTTP callback
|
|
292
|
+
* 2. Open browser to /oauth2/auth (unless `noBrowser` or browser launch fails)
|
|
293
|
+
* 3. Receive authorization code via local HTTP callback, or stdin paste (`noBrowser` / fallback)
|
|
269
294
|
* 4. Exchange code for access_token + refresh_token
|
|
270
295
|
* 5. Save token.json + client.json to ~/.kweaver/
|
|
271
296
|
*/
|
|
@@ -276,12 +301,7 @@ export async function oauth2Login(baseUrl, options) {
|
|
|
276
301
|
const base = normalizeBaseUrl(baseUrl);
|
|
277
302
|
const port = options?.port ?? DEFAULT_REDIRECT_PORT;
|
|
278
303
|
const scope = options?.scope ?? DEFAULT_SCOPE;
|
|
279
|
-
|
|
280
|
-
const redirectUri = options?.redirectUri ?? `http://127.0.0.1:${port}/callback`;
|
|
281
|
-
const parsedRedirect = parseRedirectUri(redirectUri);
|
|
282
|
-
const isLocalRedirect = parsedRedirect?.isLocalhost ?? true;
|
|
283
|
-
const listenPort = parsedRedirect?.port ?? port;
|
|
284
|
-
const callbackPathname = parsedRedirect?.pathname ?? "/callback";
|
|
304
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
285
305
|
// Step 1: Determine client — use provided client ID or fall back to dynamic registration
|
|
286
306
|
let client;
|
|
287
307
|
try {
|
|
@@ -315,9 +335,19 @@ export async function oauth2Login(baseUrl, options) {
|
|
|
315
335
|
authParams.set("code_challenge_method", "S256");
|
|
316
336
|
}
|
|
317
337
|
const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
|
|
338
|
+
const runPasteCodeFlow = async (pasteMode) => {
|
|
339
|
+
const code = await promptForCode(authUrl, state, port, pasteMode);
|
|
340
|
+
const exchanged = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri, pkce?.verifier, options?.tlsInsecure);
|
|
341
|
+
const copyCommand = buildCopyCommand(base, client.clientId, client.clientSecret, exchanged.refreshToken, options?.tlsInsecure);
|
|
342
|
+
process.stderr.write("\nOn a machine without a browser, run:\n\n " + copyCommand + "\n\n");
|
|
343
|
+
return exchanged;
|
|
344
|
+
};
|
|
318
345
|
let token;
|
|
319
|
-
if (
|
|
320
|
-
|
|
346
|
+
if (options?.noBrowser) {
|
|
347
|
+
token = await runPasteCodeFlow("explicit");
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// Step 4: Local HTTP callback, or paste-code if browser cannot be opened
|
|
321
351
|
token = await new Promise((resolve, reject) => {
|
|
322
352
|
let server;
|
|
323
353
|
const timeoutId = setTimeout(() => {
|
|
@@ -327,8 +357,8 @@ export async function oauth2Login(baseUrl, options) {
|
|
|
327
357
|
server = createServer((req, res) => {
|
|
328
358
|
void (async () => {
|
|
329
359
|
try {
|
|
330
|
-
const url = new URL(req.url ?? "/", `http://127.0.0.1:${
|
|
331
|
-
if (url.pathname !==
|
|
360
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
361
|
+
if (url.pathname !== "/callback") {
|
|
332
362
|
res.writeHead(404);
|
|
333
363
|
res.end();
|
|
334
364
|
return;
|
|
@@ -387,19 +417,25 @@ export async function oauth2Login(baseUrl, options) {
|
|
|
387
417
|
}
|
|
388
418
|
})();
|
|
389
419
|
});
|
|
390
|
-
server.listen(
|
|
391
|
-
|
|
392
|
-
openBrowser(
|
|
393
|
-
|
|
394
|
-
|
|
420
|
+
server.listen(port, "127.0.0.1", () => {
|
|
421
|
+
void (async () => {
|
|
422
|
+
const { openBrowser } = await import("../utils/browser.js");
|
|
423
|
+
const opened = await openBrowser(authUrl);
|
|
424
|
+
process.stderr.write(`If the wrong browser opens, copy this URL to your correct browser:\n ${authUrl}\n`);
|
|
425
|
+
if (!opened) {
|
|
426
|
+
clearTimeout(timeoutId);
|
|
427
|
+
server.close();
|
|
428
|
+
try {
|
|
429
|
+
resolve(await runPasteCodeFlow("fallback"));
|
|
430
|
+
}
|
|
431
|
+
catch (err) {
|
|
432
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
})();
|
|
395
436
|
});
|
|
396
437
|
});
|
|
397
438
|
}
|
|
398
|
-
else {
|
|
399
|
-
// Step 4b: Non-localhost redirect — manual code entry flow
|
|
400
|
-
const code = await waitForManualCode(authUrl, state);
|
|
401
|
-
token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri, pkce?.verifier, options?.tlsInsecure);
|
|
402
|
-
}
|
|
403
439
|
setCurrentPlatform(base);
|
|
404
440
|
return token;
|
|
405
441
|
});
|
|
@@ -520,10 +556,7 @@ export async function playwrightLogin(baseUrl, options) {
|
|
|
520
556
|
const base = normalizeBaseUrl(baseUrl);
|
|
521
557
|
const port = options?.port ?? DEFAULT_REDIRECT_PORT;
|
|
522
558
|
const scope = options?.scope ?? DEFAULT_SCOPE;
|
|
523
|
-
const redirectUri =
|
|
524
|
-
const parsedRedirect = parseRedirectUri(redirectUri);
|
|
525
|
-
const listenPort = parsedRedirect?.port ?? port;
|
|
526
|
-
const callbackPathname = parsedRedirect?.pathname ?? "/callback";
|
|
559
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
527
560
|
const hasCredentials = !!(options?.username && options?.password);
|
|
528
561
|
// Step 1: Ensure registered OAuth2 client (with stale-client auto-recovery)
|
|
529
562
|
let client;
|
|
@@ -564,8 +597,8 @@ export async function playwrightLogin(baseUrl, options) {
|
|
|
564
597
|
server = createServer((req, res) => {
|
|
565
598
|
void (async () => {
|
|
566
599
|
try {
|
|
567
|
-
const url = new URL(req.url ?? "/", `http://127.0.0.1:${
|
|
568
|
-
if (url.pathname !==
|
|
600
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
601
|
+
if (url.pathname !== "/callback") {
|
|
569
602
|
res.writeHead(404);
|
|
570
603
|
res.end();
|
|
571
604
|
return;
|
|
@@ -629,7 +662,7 @@ export async function playwrightLogin(baseUrl, options) {
|
|
|
629
662
|
}
|
|
630
663
|
})();
|
|
631
664
|
});
|
|
632
|
-
server.listen(
|
|
665
|
+
server.listen(port, "127.0.0.1", async () => {
|
|
633
666
|
try {
|
|
634
667
|
browser = await chromium.launch({ headless: hasCredentials });
|
|
635
668
|
const context = await browser.newContext({ ignoreHTTPSErrors: !!options?.tlsInsecure });
|
|
@@ -670,7 +703,7 @@ export async function playwrightLogin(baseUrl, options) {
|
|
|
670
703
|
*/
|
|
671
704
|
export async function refreshTokenLogin(baseUrl, options) {
|
|
672
705
|
const base = normalizeBaseUrl(baseUrl);
|
|
673
|
-
const redirectUri = `http://
|
|
706
|
+
const redirectUri = `http://localhost:${DEFAULT_REDIRECT_PORT}/callback`;
|
|
674
707
|
const client = {
|
|
675
708
|
baseUrl: base,
|
|
676
709
|
clientId: options.clientId,
|
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ Usage:
|
|
|
21
21
|
kweaver --version | -V
|
|
22
22
|
kweaver --help | -h
|
|
23
23
|
|
|
24
|
-
kweaver auth <platform-url> [--alias name] [--no-auth] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
24
|
+
kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
|
|
25
25
|
kweaver auth login <platform-url> (alias for auth <url>)
|
|
26
26
|
kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (run on host without browser)
|
|
27
27
|
kweaver auth whoami [platform-url|alias] [--json]
|
|
@@ -102,6 +102,7 @@ Usage:
|
|
|
102
102
|
kweaver vega health|stats|inspect
|
|
103
103
|
kweaver vega catalog list|get|health|test-connection|discover|resources [options]
|
|
104
104
|
kweaver vega resource list|get|query [options]
|
|
105
|
+
kweaver vega query execute|sql [options]
|
|
105
106
|
kweaver vega connector-type list|get [options]
|
|
106
107
|
|
|
107
108
|
kweaver context-loader config set|use|list|remove|show [options]
|
|
@@ -128,7 +129,7 @@ Commands:
|
|
|
128
129
|
object-type, relation-type, subgraph, action-type, action-execution, action-log)
|
|
129
130
|
config Per-platform configuration (business domain)
|
|
130
131
|
skill Skill registry and market (register, search, progressive read, download/install)
|
|
131
|
-
vega Vega observability (catalog, resource, connector-type, health/stats/inspect)
|
|
132
|
+
vega Vega observability (catalog, resource, query/sql, connector-type, health/stats/inspect)
|
|
132
133
|
context-loader Context-loader MCP (config, tools, resources, prompts, kn-search, query-*, etc.)
|
|
133
134
|
help Show this message`);
|
|
134
135
|
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -28,9 +28,8 @@ Login options:
|
|
|
28
28
|
Requires --client-id and --client-secret.
|
|
29
29
|
Get these from the callback page after browser login or \`auth export\`.
|
|
30
30
|
--port <n> Local callback port (default: 9010). Use when 9010 is occupied.
|
|
31
|
-
--
|
|
32
|
-
|
|
33
|
-
When set, --port is ignored. Example: --redirect-uri http://127.0.0.1:9010/callback
|
|
31
|
+
--no-browser Do not open a browser; print the auth URL and prompt for the callback URL or code (stdin).
|
|
32
|
+
Use on headless servers or when automatic browser launch fails.
|
|
34
33
|
-u, --username Username (with -p triggers Playwright headless login)
|
|
35
34
|
-p, --password Password
|
|
36
35
|
--playwright Force Playwright browser login even without -u/-p
|
|
@@ -40,7 +39,7 @@ Login options:
|
|
|
40
39
|
}
|
|
41
40
|
if (target === "login") {
|
|
42
41
|
if (rest[0] === "--help" || rest[0] === "-h") {
|
|
43
|
-
console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [-u user] [-p pass] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
|
|
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]`);
|
|
44
43
|
return 0;
|
|
45
44
|
}
|
|
46
45
|
const url = rest[0];
|
|
@@ -73,19 +72,22 @@ Login options:
|
|
|
73
72
|
const clientId = readOption(args, "--client-id");
|
|
74
73
|
const clientSecret = readOption(args, "--client-secret");
|
|
75
74
|
const refreshToken = readOption(args, "--refresh-token");
|
|
76
|
-
const customRedirectUri = readOption(args, "--redirect-uri");
|
|
77
75
|
const customPortStr = readOption(args, "--port");
|
|
78
76
|
const customPort = customPortStr ? parseInt(customPortStr, 10) : undefined;
|
|
79
77
|
const tlsInsecure = args.includes("--insecure") || args.includes("-k");
|
|
80
78
|
const noAuth = args.includes("--no-auth");
|
|
79
|
+
const noBrowser = args.includes("--no-browser");
|
|
80
|
+
if (args.includes("--redirect-uri")) {
|
|
81
|
+
console.error("Warning: --redirect-uri is deprecated and ignored. The redirect URI is always http://127.0.0.1:<port>/callback.");
|
|
82
|
+
}
|
|
81
83
|
const KNOWN_LOGIN_FLAGS = new Set([
|
|
82
84
|
"--alias", "--client-id", "--client-secret", "--refresh-token",
|
|
83
|
-
"--port", "--
|
|
84
|
-
"--playwright", "--insecure", "-k", "--no-auth",
|
|
85
|
+
"--port", "--no-browser", "--username", "-u", "--password", "-p",
|
|
86
|
+
"--playwright", "--insecure", "-k", "--no-auth", "--redirect-uri",
|
|
85
87
|
]);
|
|
86
88
|
const KNOWN_VALUE_FLAGS = new Set([
|
|
87
89
|
"--alias", "--client-id", "--client-secret", "--refresh-token",
|
|
88
|
-
"--port", "--
|
|
90
|
+
"--port", "--username", "-u", "--password", "-p", "--redirect-uri",
|
|
89
91
|
]);
|
|
90
92
|
for (let i = 0; i < args.length; i++) {
|
|
91
93
|
const a = args[i];
|
|
@@ -105,10 +107,21 @@ Login options:
|
|
|
105
107
|
console.error("--no-auth cannot be used with --refresh-token.");
|
|
106
108
|
return 1;
|
|
107
109
|
}
|
|
110
|
+
if (noAuth && noBrowser) {
|
|
111
|
+
console.error("--no-auth does not require a browser; --no-browser is ignored.");
|
|
112
|
+
}
|
|
108
113
|
if (noAuth && (username || password || usePlaywright)) {
|
|
109
114
|
console.error("--no-auth cannot be used with Playwright login or -u/-p.");
|
|
110
115
|
return 1;
|
|
111
116
|
}
|
|
117
|
+
if (noBrowser && (username || password || usePlaywright)) {
|
|
118
|
+
console.error("--no-browser cannot be used with Playwright login or -u/-p.");
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
if (noBrowser && refreshToken) {
|
|
122
|
+
console.error("--no-browser cannot be used with --refresh-token.");
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
112
125
|
let token;
|
|
113
126
|
if (noAuth) {
|
|
114
127
|
token = saveNoAuthPlatform(normalizedTarget, { tlsInsecure });
|
|
@@ -127,19 +140,20 @@ Login options:
|
|
|
127
140
|
else if (username && password) {
|
|
128
141
|
console.log("Logging in (headless)...");
|
|
129
142
|
token = await playwrightLogin(normalizedTarget, {
|
|
130
|
-
username, password, tlsInsecure,
|
|
131
|
-
port: customPort, redirectUri: customRedirectUri ?? undefined,
|
|
143
|
+
username, password, tlsInsecure, port: customPort,
|
|
132
144
|
});
|
|
133
145
|
}
|
|
134
146
|
else if (usePlaywright) {
|
|
135
147
|
console.log("Opening browser for login (Playwright)...");
|
|
136
148
|
token = await playwrightLogin(normalizedTarget, {
|
|
137
|
-
tlsInsecure,
|
|
138
|
-
port: customPort, redirectUri: customRedirectUri ?? undefined,
|
|
149
|
+
tlsInsecure, port: customPort,
|
|
139
150
|
});
|
|
140
151
|
}
|
|
141
152
|
else {
|
|
142
|
-
if (
|
|
153
|
+
if (noBrowser) {
|
|
154
|
+
console.log("OAuth2 login (no browser — open the URL on any device, then paste the callback URL or code)...");
|
|
155
|
+
}
|
|
156
|
+
else if (clientId) {
|
|
143
157
|
console.log(`Opening browser for OAuth2 login (client: ${clientId})...`);
|
|
144
158
|
}
|
|
145
159
|
else {
|
|
@@ -148,8 +162,7 @@ Login options:
|
|
|
148
162
|
token = await oauth2Login(normalizedTarget, {
|
|
149
163
|
clientId: clientId ?? undefined,
|
|
150
164
|
clientSecret: clientSecret ?? undefined,
|
|
151
|
-
tlsInsecure,
|
|
152
|
-
port: customPort, redirectUri: customRedirectUri ?? undefined,
|
|
165
|
+
tlsInsecure, port: customPort, noBrowser,
|
|
153
166
|
});
|
|
154
167
|
}
|
|
155
168
|
if (alias) {
|
package/dist/commands/vega.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { ensureValidToken, formatHttpError, with401RefreshRetry } from "../auth/oauth.js";
|
|
3
|
-
import { vegaHealth, listVegaCatalogs, getVegaCatalog, createVegaCatalog, updateVegaCatalog, deleteVegaCatalogs, vegaCatalogHealthStatus, testVegaCatalogConnection, discoverVegaCatalog, listVegaCatalogResources, listVegaResources, getVegaResource, queryVegaResourceData, createVegaResource, updateVegaResource, deleteVegaResources, listVegaConnectorTypes, getVegaConnectorType, registerVegaConnectorType, updateVegaConnectorType, deleteVegaConnectorType, setVegaConnectorTypeEnabled, createVegaDatasetDocs, updateVegaDatasetDocs, deleteVegaDatasetDocs, deleteVegaDatasetDocsQuery, buildVegaDataset, getVegaDatasetBuildStatus, executeVegaQuery, listAllVegaResources, } from "../api/vega.js";
|
|
3
|
+
import { vegaHealth, listVegaCatalogs, getVegaCatalog, createVegaCatalog, updateVegaCatalog, deleteVegaCatalogs, vegaCatalogHealthStatus, testVegaCatalogConnection, discoverVegaCatalog, listVegaCatalogResources, listVegaResources, getVegaResource, queryVegaResourceData, createVegaResource, updateVegaResource, deleteVegaResources, listVegaConnectorTypes, getVegaConnectorType, registerVegaConnectorType, updateVegaConnectorType, deleteVegaConnectorType, setVegaConnectorTypeEnabled, createVegaDatasetDocs, updateVegaDatasetDocs, deleteVegaDatasetDocs, deleteVegaDatasetDocsQuery, buildVegaDataset, getVegaDatasetBuildStatus, executeVegaQuery, vegaSQLQuery, listAllVegaResources, } from "../api/vega.js";
|
|
4
4
|
import { formatCallOutput } from "./call.js";
|
|
5
5
|
import { resolveBusinessDomain } from "../config/store.js";
|
|
6
6
|
// ---------------------------------------------------------------------------
|
|
@@ -35,7 +35,9 @@ Subcommands:
|
|
|
35
35
|
dataset delete-docs-query <resource-id> -d <filter-json>
|
|
36
36
|
dataset build <resource-id> [--mode full|incremental|realtime]
|
|
37
37
|
dataset build-status <resource-id> <task-id>
|
|
38
|
-
query execute -d <json>
|
|
38
|
+
query execute -d <json> Structured query (tables, joins, filters)
|
|
39
|
+
sql --resource-type <t> --query <sql> Direct SQL / DSL; use {{<resource_id>}} in SQL (quoted)
|
|
40
|
+
sql -d <json> Same API with full JSON body (advanced)
|
|
39
41
|
connector-type list List connector types
|
|
40
42
|
connector-type get <type> Get connector type details
|
|
41
43
|
connector-type register -d <json> Register a new connector type
|
|
@@ -103,6 +105,8 @@ export async function runVegaCommand(args) {
|
|
|
103
105
|
return runVegaDatasetCommand(rest);
|
|
104
106
|
if (subcommand === "query")
|
|
105
107
|
return runVegaQueryCommand(rest);
|
|
108
|
+
if (subcommand === "sql")
|
|
109
|
+
return runVegaSql(rest);
|
|
106
110
|
if (subcommand === "connector-type")
|
|
107
111
|
return runVegaConnectorTypeCommand(rest);
|
|
108
112
|
return Promise.resolve(-1);
|
|
@@ -1321,6 +1325,101 @@ Options:
|
|
|
1321
1325
|
return 0;
|
|
1322
1326
|
}
|
|
1323
1327
|
// ---------------------------------------------------------------------------
|
|
1328
|
+
// sql (POST /resources/query)
|
|
1329
|
+
// ---------------------------------------------------------------------------
|
|
1330
|
+
async function runVegaSql(args) {
|
|
1331
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1332
|
+
console.log(`kweaver vega sql --resource-type <type> --query "<sql-or-dsl>"
|
|
1333
|
+
kweaver vega sql -d <json>
|
|
1334
|
+
|
|
1335
|
+
POST /api/vega-backend/v1/resources/query — execute SQL (MySQL/MariaDB/PostgreSQL) or OpenSearch DSL.
|
|
1336
|
+
|
|
1337
|
+
Simple mode (no JSON escaping for query + type):
|
|
1338
|
+
--resource-type <t> Required with --query unless using -d
|
|
1339
|
+
--query <string> One shell argument: the full SQL (or DSL string). Always quote it.
|
|
1340
|
+
|
|
1341
|
+
Advanced mode (full request body, optional fields):
|
|
1342
|
+
-d, --data <json> Raw JSON body. When present, this mode is used and any --query / --resource-type are ignored.
|
|
1343
|
+
|
|
1344
|
+
Resource placeholders (how to reference Vega tables in SQL):
|
|
1345
|
+
{{<resource_id>}} Required token form: double braces around the Vega resource id (from vega resource list / get).
|
|
1346
|
+
{{.<resource_id>}} Alternate form with a dot after {{ ; same replacement and routing.
|
|
1347
|
+
|
|
1348
|
+
The backend swaps each placeholder for that resource's physical SourceIdentifier and picks the catalog connector.
|
|
1349
|
+
Without at least one placeholder, queries often fail (e.g. connector config is incomplete) unless a default connector exists.
|
|
1350
|
+
|
|
1351
|
+
Shell (simple mode) — wrap the whole SQL so braces are not interpreted by the shell:
|
|
1352
|
+
kweaver vega sql --resource-type mysql --query "SELECT * FROM {{abc123xyz}} LIMIT 5"
|
|
1353
|
+
|
|
1354
|
+
Shell (-d mode) — placeholders live inside the JSON string value; use single quotes around the JSON so inner double quotes work:
|
|
1355
|
+
kweaver vega sql -d '{"resource_type":"mysql","query":"SELECT * FROM {{abc123xyz}} LIMIT 5"}'
|
|
1356
|
+
|
|
1357
|
+
Body fields (JSON / simple mode mapping):
|
|
1358
|
+
query (required) SQL string or OpenSearch DSL object
|
|
1359
|
+
resource_type (required) e.g. mysql, mariadb, postgresql, opensearch (see vega connector-type list)
|
|
1360
|
+
stream_size optional batch size for streaming (100–10000, default 10000)
|
|
1361
|
+
query_timeout optional seconds (1–3600, default 60)
|
|
1362
|
+
query_id optional cursor session id
|
|
1363
|
+
|
|
1364
|
+
Do not use --type; use --resource-type.
|
|
1365
|
+
|
|
1366
|
+
Common flags:
|
|
1367
|
+
-bd, --biz-domain <s> Business domain (default: bd_public)
|
|
1368
|
+
--pretty Pretty-print JSON (default)`);
|
|
1369
|
+
return 0;
|
|
1370
|
+
}
|
|
1371
|
+
let data;
|
|
1372
|
+
let query;
|
|
1373
|
+
let resourceType;
|
|
1374
|
+
const { remaining, businessDomain, pretty } = parseCommonFlags(args);
|
|
1375
|
+
for (let i = 0; i < remaining.length; i += 1) {
|
|
1376
|
+
const arg = remaining[i];
|
|
1377
|
+
if (arg === "--type") {
|
|
1378
|
+
console.error("Use --resource-type instead of --type (e.g. --resource-type mysql).");
|
|
1379
|
+
return 1;
|
|
1380
|
+
}
|
|
1381
|
+
if ((arg === "-d" || arg === "--data") && remaining[i + 1]) {
|
|
1382
|
+
data = remaining[++i];
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
if (arg === "--query" && remaining[i + 1]) {
|
|
1386
|
+
query = remaining[++i];
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
if (arg === "--resource-type" && remaining[i + 1]) {
|
|
1390
|
+
resourceType = remaining[++i];
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
let requestBody;
|
|
1395
|
+
if (data !== undefined) {
|
|
1396
|
+
try {
|
|
1397
|
+
JSON.parse(data);
|
|
1398
|
+
}
|
|
1399
|
+
catch {
|
|
1400
|
+
console.error(`Invalid JSON: ${data}`);
|
|
1401
|
+
return 1;
|
|
1402
|
+
}
|
|
1403
|
+
requestBody = data;
|
|
1404
|
+
}
|
|
1405
|
+
else {
|
|
1406
|
+
if (!query || !resourceType) {
|
|
1407
|
+
console.error("Usage: kweaver vega sql --resource-type <type> --query \"<sql-or-dsl>\"\n kweaver vega sql -d <json>");
|
|
1408
|
+
return 1;
|
|
1409
|
+
}
|
|
1410
|
+
requestBody = JSON.stringify({ query, resource_type: resourceType });
|
|
1411
|
+
}
|
|
1412
|
+
const token = await ensureValidToken();
|
|
1413
|
+
const body = await vegaSQLQuery({
|
|
1414
|
+
baseUrl: token.baseUrl,
|
|
1415
|
+
accessToken: token.accessToken,
|
|
1416
|
+
body: requestBody,
|
|
1417
|
+
businessDomain,
|
|
1418
|
+
});
|
|
1419
|
+
console.log(formatCallOutput(body, pretty));
|
|
1420
|
+
return 0;
|
|
1421
|
+
}
|
|
1422
|
+
// ---------------------------------------------------------------------------
|
|
1324
1423
|
// Connector-type router
|
|
1325
1424
|
// ---------------------------------------------------------------------------
|
|
1326
1425
|
async function runVegaConnectorTypeCommand(args) {
|
package/dist/resources/vega.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ export declare class VegaResource {
|
|
|
47
47
|
buildDataset(id: string, mode?: string): Promise<unknown>;
|
|
48
48
|
getDatasetBuildStatus(id: string, taskId: string): Promise<unknown>;
|
|
49
49
|
executeQuery(body: string): Promise<unknown>;
|
|
50
|
+
/** POST /resources/query — direct SQL or OpenSearch DSL (see kweaver vega sql --help). */
|
|
51
|
+
sqlQuery(body: string): Promise<unknown>;
|
|
50
52
|
listAllResources(opts?: {
|
|
51
53
|
limit?: number;
|
|
52
54
|
offset?: number;
|
package/dist/resources/vega.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { vegaHealth, listVegaCatalogs, getVegaCatalog, createVegaCatalog, updateVegaCatalog, deleteVegaCatalogs, vegaCatalogHealthStatus, testVegaCatalogConnection, discoverVegaCatalog, listVegaCatalogResources, listVegaResources, getVegaResource, queryVegaResourceData, createVegaResource, updateVegaResource, deleteVegaResources, createVegaDatasetDocs, updateVegaDatasetDocs, deleteVegaDatasetDocs, deleteVegaDatasetDocsQuery, buildVegaDataset, getVegaDatasetBuildStatus, executeVegaQuery, listAllVegaResources, listVegaConnectorTypes, getVegaConnectorType, registerVegaConnectorType, updateVegaConnectorType, deleteVegaConnectorType, setVegaConnectorTypeEnabled, } from "../api/vega.js";
|
|
1
|
+
import { vegaHealth, listVegaCatalogs, getVegaCatalog, createVegaCatalog, updateVegaCatalog, deleteVegaCatalogs, vegaCatalogHealthStatus, testVegaCatalogConnection, discoverVegaCatalog, listVegaCatalogResources, listVegaResources, getVegaResource, queryVegaResourceData, createVegaResource, updateVegaResource, deleteVegaResources, createVegaDatasetDocs, updateVegaDatasetDocs, deleteVegaDatasetDocs, deleteVegaDatasetDocsQuery, buildVegaDataset, getVegaDatasetBuildStatus, executeVegaQuery, vegaSQLQuery, listAllVegaResources, listVegaConnectorTypes, getVegaConnectorType, registerVegaConnectorType, updateVegaConnectorType, deleteVegaConnectorType, setVegaConnectorTypeEnabled, } from "../api/vega.js";
|
|
2
2
|
function unwrapArray(raw) {
|
|
3
3
|
const parsed = JSON.parse(raw);
|
|
4
4
|
if (Array.isArray(parsed))
|
|
@@ -114,6 +114,11 @@ export class VegaResource {
|
|
|
114
114
|
const raw = await executeVegaQuery({ ...this.ctx.base(), body });
|
|
115
115
|
return JSON.parse(raw);
|
|
116
116
|
}
|
|
117
|
+
/** POST /resources/query — direct SQL or OpenSearch DSL (see kweaver vega sql --help). */
|
|
118
|
+
async sqlQuery(body) {
|
|
119
|
+
const raw = await vegaSQLQuery({ ...this.ctx.base(), body });
|
|
120
|
+
return JSON.parse(raw);
|
|
121
|
+
}
|
|
117
122
|
// ── Resource List All ──────────────────────────────────────────────────────
|
|
118
123
|
async listAllResources(opts = {}) {
|
|
119
124
|
const raw = await listAllVegaResources({ ...this.ctx.base(), ...opts });
|
package/dist/utils/browser.js
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { release } from "node:os";
|
|
4
|
+
let _isWSL;
|
|
5
|
+
function isWSL() {
|
|
6
|
+
if (_isWSL !== undefined)
|
|
7
|
+
return _isWSL;
|
|
8
|
+
if (process.platform !== "linux")
|
|
9
|
+
return (_isWSL = false);
|
|
10
|
+
if (/microsoft/i.test(release()))
|
|
11
|
+
return (_isWSL = true);
|
|
12
|
+
try {
|
|
13
|
+
const procVersion = readFileSync("/proc/version", "utf8");
|
|
14
|
+
return (_isWSL = /microsoft/i.test(procVersion));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return (_isWSL = false);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function resolveCommand(url) {
|
|
21
|
+
if (process.platform === "darwin") {
|
|
22
|
+
return { command: "open", args: [url], detached: true };
|
|
23
|
+
}
|
|
24
|
+
if (process.platform === "win32") {
|
|
25
|
+
return { command: "rundll32.exe", args: ["url.dll,FileProtocolHandler", url], detached: false };
|
|
26
|
+
}
|
|
27
|
+
if (isWSL()) {
|
|
28
|
+
return { command: "cmd.exe", args: ["/c", "start", "", url.replace(/&/g, "^&")], detached: true };
|
|
29
|
+
}
|
|
30
|
+
return { command: "xdg-open", args: [url], detached: true };
|
|
31
|
+
}
|
|
2
32
|
export function openBrowser(url) {
|
|
3
|
-
const
|
|
4
|
-
const command = process.platform === "darwin" ? "open" : isWindows ? "rundll32.exe" : "xdg-open";
|
|
5
|
-
const args = isWindows
|
|
6
|
-
? [
|
|
7
|
-
"url.dll,FileProtocolHandler",
|
|
8
|
-
url,
|
|
9
|
-
]
|
|
10
|
-
: [url];
|
|
33
|
+
const { command, args, detached } = resolveCommand(url);
|
|
11
34
|
return new Promise((resolve) => {
|
|
12
35
|
const child = spawn(command, args, {
|
|
13
36
|
stdio: "ignore",
|
|
14
|
-
detached
|
|
37
|
+
detached,
|
|
15
38
|
windowsHide: true,
|
|
16
39
|
});
|
|
17
40
|
child.once("error", () => resolve(false));
|
|
18
41
|
child.once("spawn", () => resolve(true));
|
|
19
|
-
if (
|
|
42
|
+
if (detached) {
|
|
20
43
|
child.unref();
|
|
21
44
|
}
|
|
22
45
|
});
|
package/package.json
CHANGED