@kweaver-ai/kweaver-sdk 0.4.12 → 0.4.14

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 CHANGED
@@ -31,6 +31,18 @@ export KWEAVER_BASE_URL=https://your-kweaver-instance.com
31
31
  export KWEAVER_TOKEN=your-token
32
32
  ```
33
33
 
34
+ ### Business domain (platform)
35
+
36
+ Set or verify **before** calling list/query APIs that scope by tenant. DIP deployments often need a UUID, not only `bd_public`.
37
+
38
+ ```bash
39
+ kweaver config show
40
+ kweaver config list-bd
41
+ kweaver config set-bd <uuid>
42
+ ```
43
+
44
+ After `kweaver auth login`, the CLI may auto-select a domain when none is saved yet. Override with `KWEAVER_BUSINESS_DOMAIN` or `-bd` / `--biz-domain` on commands. See [`../../skills/kweaver-core/references/config.md`](../../skills/kweaver-core/references/config.md).
45
+
34
46
  ### Simple API (recommended)
35
47
 
36
48
  ```typescript
@@ -102,6 +114,11 @@ const exact = await client.dataviews.find("orders", {
102
114
  wait: true,
103
115
  });
104
116
  const dv = await client.dataviews.get(viewId);
117
+ const queryRows = await client.dataviews.query(viewId, {
118
+ sql: "SELECT id, name FROM orders LIMIT 10",
119
+ limit: 10,
120
+ needTotal: true,
121
+ });
105
122
 
106
123
  // Dataflow automation (CSV import pipeline, etc.)
107
124
  const result = await client.dataflows.execute({
@@ -121,12 +138,15 @@ const results = await cl.search({ query: "hypertension treatment" });
121
138
  ## CLI Reference
122
139
 
123
140
  ```
124
- kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k] — also: status, list, use, delete, logout
141
+ kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k]
142
+ kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (headless login)
143
+ kweaver auth export [url|alias] [--json] (export command to run on a headless host)
144
+ kweaver auth status/list/use/delete/logout
145
+ kweaver config show / list-bd / set-bd <value> # platform business domain — after login
125
146
  kweaver token
126
- kweaver config show / set-bd <value>
127
147
  kweaver ds list/get/delete/tables/connect
128
148
  kweaver ds import-csv <ds_id> --files <glob> [--table-prefix <p>] [--batch-size 500]
129
- kweaver dataview list/find/get/delete
149
+ kweaver dataview list/find/get/query/delete
130
150
  kweaver bkn list/get/stats/export/create/update/delete
131
151
  kweaver bkn create-from-ds <ds_id> --name <name> [--tables t1,t2] [--build]
132
152
  kweaver bkn create-from-csv <ds_id> --files <glob> --name <name> [--build]
@@ -179,6 +199,23 @@ If you encounter errors like `fetch failed`, `self-signed certificate`, or `UNAB
179
199
 
180
200
  > **Security note:** All of the above disable HTTPS certificate verification and should only be used in development or internal network environments. Use trusted CA-signed certificates in production.
181
201
 
202
+ ### Headless / Server Authentication
203
+
204
+ For servers or CI environments without a browser, log in on any machine that has one, then transfer credentials:
205
+
206
+ **Step 1 — Browser machine:** Run `kweaver auth login` as usual. The callback page displays a ready-to-copy command with `--client-id`, `--client-secret`, and `--refresh-token`. Alternatively, run `kweaver auth export` to print the same command.
207
+
208
+ **Step 2 — On the machine without a browser:** Run the pasted command there (SSH server, CI, etc.):
209
+
210
+ ```bash
211
+ kweaver auth login https://your-platform \
212
+ --client-id abc123 \
213
+ --client-secret def456 \
214
+ --refresh-token ghi789
215
+ ```
216
+
217
+ The SDK exchanges the refresh token for a new access token and saves it locally. Auto-refresh works normally from that point on.
218
+
182
219
  ## Using with AI Agents
183
220
 
184
221
  Install the KWeaver skill for Claude Code, Cursor, or other AI coding agents:
package/README.zh.md CHANGED
@@ -31,6 +31,18 @@ export KWEAVER_BASE_URL=https://your-kweaver-instance.com
31
31
  export KWEAVER_TOKEN=your-token
32
32
  ```
33
33
 
34
+ ### 业务域(平台配置)
35
+
36
+ 在调用依赖租户范围的接口前,应先确认业务域;DIP 环境通常使用 **UUID**,不能长期只依赖默认 `bd_public`。
37
+
38
+ ```bash
39
+ kweaver config show
40
+ kweaver config list-bd
41
+ kweaver config set-bd <uuid>
42
+ ```
43
+
44
+ `kweaver auth login` 成功后,若尚未配置,CLI 可能自动选择业务域。也可用环境变量 `KWEAVER_BUSINESS_DOMAIN` 或各命令的 `-bd` / `--biz-domain` 覆盖。详见 [`../../skills/kweaver-core/references/config.md`](../../skills/kweaver-core/references/config.md)。
45
+
34
46
  ### 简洁 API(推荐)
35
47
 
36
48
  ```typescript
@@ -101,6 +113,11 @@ const exact = await client.dataviews.find("orders", {
101
113
  wait: true,
102
114
  });
103
115
  const dv = await client.dataviews.get(viewId);
116
+ const queryRows = await client.dataviews.query(viewId, {
117
+ sql: "SELECT id, name FROM orders LIMIT 10",
118
+ limit: 10,
119
+ needTotal: true,
120
+ });
104
121
 
105
122
  // Context Loader(通过 MCP 对 BKN 做语义搜索)
106
123
  const cl = client.contextLoader(mcpUrl, "bkn-id");
@@ -110,10 +127,14 @@ const results = await cl.search({ query: "高血压 治疗" });
110
127
  ## 命令速查
111
128
 
112
129
  ```
113
- kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k] — 另有 status、list、use、delete、logout
130
+ kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k]
131
+ kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (无浏览器登录)
132
+ kweaver auth export [url|alias] [--json] (导出在无浏览器机器上运行的命令)
133
+ kweaver auth status/list/use/delete/logout
134
+ kweaver config show / list-bd / set-bd <value> # 平台业务域,登录后优先
114
135
  kweaver token
115
136
  kweaver ds list/get/delete/tables/connect
116
- kweaver dataview list/find/get/delete
137
+ kweaver dataview list/find/get/query/delete
117
138
  kweaver bkn list/get/stats/export/create/update/delete
118
139
  kweaver bkn object-type list/get/create/update/delete/query/properties
119
140
  kweaver bkn relation-type list/get/create/update/delete
@@ -162,6 +183,23 @@ kweaver call <path> [-X METHOD] [-d BODY] [-H header]
162
183
 
163
184
  > **安全提示:** 以上方式均会跳过 HTTPS 证书校验,仅适用于开发/内网环境。生产环境请使用受信任的 CA 签发证书。
164
185
 
186
+ ### 无浏览器 / 服务器端认证
187
+
188
+ 适用于 SSH 远程服务器、CI 环境等无浏览器场景:
189
+
190
+ **第一步 — 有浏览器的机器:** 正常运行 `kweaver auth login`。登录成功后,回调页面会显示一条可复制的命令(含 `--client-id`、`--client-secret`、`--refresh-token`)。也可以用 `kweaver auth export` 查看。
191
+
192
+ **第二步 — 在没有浏览器的那台机器上:** 在 SSH 服务器、CI 等环境中执行下面这条命令:
193
+
194
+ ```bash
195
+ kweaver auth login https://your-platform \
196
+ --client-id abc123 \
197
+ --client-secret def456 \
198
+ --refresh-token ghi789
199
+ ```
200
+
201
+ SDK 会用 refresh token 换取新的 access token 并保存到本地,之后自动续期正常工作。
202
+
165
203
  ## 在 AI 智能体中使用
166
204
 
167
205
  为 Claude Code、Cursor 等 AI 编程助手安装 KWeaver 技能:
@@ -0,0 +1,20 @@
1
+ /** One business domain entry from GET /api/business-system/v1/business-domain */
2
+ export interface BusinessDomain {
3
+ id: string;
4
+ name?: string;
5
+ description?: string;
6
+ creator?: string;
7
+ products?: string[];
8
+ create_time?: string;
9
+ }
10
+ export interface ListBusinessDomainsOptions {
11
+ baseUrl: string;
12
+ accessToken: string;
13
+ /** When true, skip TLS verification (matches `--insecure` login). */
14
+ tlsInsecure?: boolean;
15
+ }
16
+ /**
17
+ * List business domains for the authenticated user. Does not send x-business-domain
18
+ * (the endpoint returns all domains the user can access).
19
+ */
20
+ export declare function listBusinessDomains(options: ListBusinessDomainsOptions): Promise<BusinessDomain[]>;
@@ -0,0 +1,54 @@
1
+ import { HttpError } from "../utils/http.js";
2
+ async function withTlsInsecure(tlsInsecure, fn) {
3
+ if (!tlsInsecure) {
4
+ return fn();
5
+ }
6
+ const prev = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
7
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
8
+ try {
9
+ return await fn();
10
+ }
11
+ finally {
12
+ if (prev === undefined) {
13
+ delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
14
+ }
15
+ else {
16
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = prev;
17
+ }
18
+ }
19
+ }
20
+ /**
21
+ * List business domains for the authenticated user. Does not send x-business-domain
22
+ * (the endpoint returns all domains the user can access).
23
+ */
24
+ export async function listBusinessDomains(options) {
25
+ const { baseUrl, accessToken, tlsInsecure } = options;
26
+ const base = baseUrl.replace(/\/+$/, "");
27
+ const url = `${base}/api/business-system/v1/business-domain`;
28
+ return withTlsInsecure(tlsInsecure, async () => {
29
+ const response = await fetch(url, {
30
+ method: "GET",
31
+ headers: {
32
+ accept: "application/json, text/plain, */*",
33
+ authorization: `Bearer ${accessToken}`,
34
+ token: accessToken,
35
+ },
36
+ });
37
+ const body = await response.text();
38
+ if (!response.ok) {
39
+ throw new HttpError(response.status, response.statusText, body);
40
+ }
41
+ const data = JSON.parse(body);
42
+ if (!Array.isArray(data)) {
43
+ throw new Error("Business domain list response was not a JSON array.");
44
+ }
45
+ return data.map((item) => {
46
+ const row = item;
47
+ const id = row.id;
48
+ if (typeof id !== "string" || id.length === 0) {
49
+ throw new Error("Business domain entry missing string id.");
50
+ }
51
+ return item;
52
+ });
53
+ });
54
+ }
@@ -45,6 +45,8 @@ export interface PollDataflowOptions {
45
45
  interval?: number;
46
46
  /** Maximum time to wait in seconds. Default: 900 */
47
47
  timeout?: number;
48
+ /** Test injection: override sleep function. */
49
+ _sleep?: (ms: number) => Promise<void>;
48
50
  }
49
51
  /**
50
52
  * Poll GET /api/automation/v1/dag/{dagId}/results until the run is done.
@@ -69,10 +69,11 @@ export async function runDataflow(options) {
69
69
  * Throws on "failed"/"error" status or timeout.
70
70
  */
71
71
  export async function pollDataflowResults(options) {
72
- const { baseUrl, accessToken, businessDomain = "bd_public", dagId, interval = 3, timeout = 900, } = options;
72
+ const { baseUrl, accessToken, businessDomain = "bd_public", dagId, interval = 3, timeout = 900, _sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), } = options;
73
73
  const base = baseUrl.replace(/\/+$/, "");
74
74
  const url = `${base}/api/automation/v1/dag/${encodeURIComponent(dagId)}/results`;
75
75
  const deadlineMs = Date.now() + timeout * 1000;
76
+ let currentInterval = interval;
76
77
  while (Date.now() < deadlineMs) {
77
78
  const response = await fetch(url, {
78
79
  method: "GET",
@@ -95,9 +96,10 @@ export async function pollDataflowResults(options) {
95
96
  }
96
97
  }
97
98
  // Still running — wait before next poll
98
- if (interval > 0) {
99
- await new Promise((resolve) => setTimeout(resolve, interval * 1000));
99
+ if (currentInterval > 0) {
100
+ await _sleep(currentInterval * 1000);
100
101
  }
102
+ currentInterval = Math.min(currentInterval * 2, 30);
101
103
  }
102
104
  throw new Error(`Dataflow polling timed out after ${timeout}s for DAG ${dagId}`);
103
105
  }
@@ -11,7 +11,18 @@ export interface DataView {
11
11
  name: string;
12
12
  query_type: string;
13
13
  datasource_id: string;
14
- fields: ViewField[];
14
+ /** View type, e.g. "atomic" or "custom". */
15
+ type?: string;
16
+ /** Underlying data source engine, e.g. "mysql", "postgresql". */
17
+ data_source_type?: string;
18
+ /** Human-readable data source name. */
19
+ data_source_name?: string;
20
+ /** Full SQL expression stored in the view definition (Trino catalog.schema.table). */
21
+ sql_str?: string;
22
+ /** Fully-qualified table reference (catalog."schema"."table"). */
23
+ meta_table_name?: string;
24
+ /** Field metadata. Populated by `get`; absent (`undefined`) in `list` results. */
25
+ fields?: ViewField[];
15
26
  }
16
27
  export declare function parseDataView(raw: Record<string, unknown>): DataView;
17
28
  export interface CreateDataViewOptions {
@@ -75,3 +86,32 @@ export interface FindDataViewOptions {
75
86
  * applies client-side `name ===` filter. Optional polling with exponential backoff.
76
87
  */
77
88
  export declare function findDataView(options: FindDataViewOptions): Promise<DataView[]>;
89
+ /** Options for querying data view rows via mdl-uniquery (SQL / view definition). */
90
+ export interface QueryDataViewOptions {
91
+ baseUrl: string;
92
+ accessToken: string;
93
+ id: string;
94
+ sql?: string;
95
+ offset?: number;
96
+ limit?: number;
97
+ needTotal?: boolean;
98
+ outputFields?: string[];
99
+ filters?: Record<string, unknown>;
100
+ sort?: Array<Record<string, unknown>>;
101
+ businessDomain?: string;
102
+ }
103
+ /** Query result from mdl-uniquery data-views POST (shape varies by backend). */
104
+ export interface DataViewQueryResult {
105
+ columns?: Array<{
106
+ name: string;
107
+ type?: string;
108
+ vega_type?: string;
109
+ }>;
110
+ entries?: unknown;
111
+ total_count?: number;
112
+ }
113
+ /**
114
+ * Execute a query against a data view (POST /api/mdl-uniquery/v1/data-views/:id).
115
+ * When `sql` is omitted, the server uses the view's stored SQL definition.
116
+ */
117
+ export declare function queryDataView(options: QueryDataViewOptions): Promise<DataViewQueryResult>;
@@ -26,8 +26,9 @@ function extractViewId(data) {
26
26
  }
27
27
  export function parseDataView(raw) {
28
28
  const fieldsRaw = raw.fields;
29
- const fields = [];
30
- if (Array.isArray(fieldsRaw)) {
29
+ let fields;
30
+ if (Array.isArray(fieldsRaw) && fieldsRaw.length > 0) {
31
+ fields = [];
31
32
  for (const f of fieldsRaw) {
32
33
  if (f && typeof f === "object") {
33
34
  const fr = f;
@@ -40,13 +41,25 @@ export function parseDataView(raw) {
40
41
  }
41
42
  }
42
43
  }
43
- return {
44
+ const dv = {
44
45
  id: String(raw.id ?? ""),
45
46
  name: String(raw.name ?? ""),
46
47
  query_type: String(raw.query_type ?? "SQL"),
47
48
  datasource_id: String(raw.data_source_id ?? raw.group_id ?? ""),
48
- fields,
49
49
  };
50
+ if (raw.type != null)
51
+ dv.type = String(raw.type);
52
+ if (raw.data_source_type != null)
53
+ dv.data_source_type = String(raw.data_source_type);
54
+ if (raw.data_source_name != null)
55
+ dv.data_source_name = String(raw.data_source_name);
56
+ if (raw.sql_str != null)
57
+ dv.sql_str = String(raw.sql_str);
58
+ if (raw.meta_table_name != null)
59
+ dv.meta_table_name = String(raw.meta_table_name);
60
+ if (fields)
61
+ dv.fields = fields;
62
+ return dv;
50
63
  }
51
64
  function extractListPayload(data) {
52
65
  if (Array.isArray(data))
@@ -129,7 +142,7 @@ async function findDataViewByName(options) {
129
142
  return match?.id ?? null;
130
143
  }
131
144
  export async function listDataViews(options) {
132
- const { baseUrl, accessToken, businessDomain = "bd_public", datasourceId, name, type, limit = -1, } = options;
145
+ const { baseUrl, accessToken, businessDomain = "bd_public", datasourceId, name, type, limit = 30, } = options;
133
146
  const base = baseUrl.replace(/\/+$/, "");
134
147
  const url = new URL(`${base}/api/mdl-data-model/v1/data-views`);
135
148
  url.searchParams.set("limit", String(limit));
@@ -219,3 +232,43 @@ export async function findDataView(options) {
219
232
  await sleepMs(delayMs);
220
233
  }
221
234
  }
235
+ /**
236
+ * Execute a query against a data view (POST /api/mdl-uniquery/v1/data-views/:id).
237
+ * When `sql` is omitted, the server uses the view's stored SQL definition.
238
+ */
239
+ export async function queryDataView(options) {
240
+ const { baseUrl, accessToken, id, sql, offset = 0, limit = 50, needTotal = false, outputFields, filters, sort, businessDomain = "bd_public", } = options;
241
+ const base = baseUrl.replace(/\/+$/, "");
242
+ const url = `${base}/api/mdl-uniquery/v1/data-views/${encodeURIComponent(id)}`;
243
+ const body = {
244
+ offset,
245
+ limit,
246
+ need_total: needTotal,
247
+ };
248
+ if (sql !== undefined && sql !== "")
249
+ body.sql = sql;
250
+ if (outputFields !== undefined)
251
+ body.output_fields = outputFields;
252
+ if (filters !== undefined)
253
+ body.filters = filters;
254
+ if (sort !== undefined)
255
+ body.sort = sort;
256
+ const response = await fetch(url, {
257
+ method: "POST",
258
+ headers: {
259
+ ...buildHeaders(accessToken, businessDomain),
260
+ "content-type": "application/json",
261
+ "x-http-method-override": "GET",
262
+ },
263
+ body: JSON.stringify(body),
264
+ });
265
+ const bodyText = await response.text();
266
+ if (!response.ok) {
267
+ throw new HttpError(response.status, response.statusText, bodyText);
268
+ }
269
+ const parsed = JSON.parse(bodyText);
270
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
271
+ return parsed;
272
+ }
273
+ return {};
274
+ }
package/dist/api/vega.js CHANGED
@@ -190,7 +190,7 @@ export async function queryVegaResourceData(options) {
190
190
  return body;
191
191
  }
192
192
  export async function previewVegaResource(options) {
193
- const { baseUrl, accessToken, id, limit = 10, businessDomain = "bd_public", } = options;
193
+ const { baseUrl, accessToken, id, limit = 50, businessDomain = "bd_public", } = options;
194
194
  const base = baseUrl.replace(/\/+$/, "");
195
195
  const url = new URL(`${base}${VEGA_BASE}/resources/${encodeURIComponent(id)}/preview`);
196
196
  url.searchParams.set("limit", String(limit));
@@ -1,4 +1,15 @@
1
1
  import { type TokenConfig } from "../config/store.js";
2
+ /** POSIX shell single-quote escaping for copy-paste commands. */
3
+ export declare function shellQuoteForShell(value: string): string;
4
+ /**
5
+ * Build a one-line `kweaver auth login ...` command for headless / other machines.
6
+ * Omits `--client-secret` when empty (PKCE-only client); headless refresh may still require a confidential client.
7
+ */
8
+ export declare function buildCopyCommand(baseUrl: string, clientId: string, clientSecret: string, refreshToken: string | undefined, tlsInsecure?: boolean): string;
9
+ /**
10
+ * HTML shown after successful OAuth callback with a copyable headless login command.
11
+ */
12
+ export declare function buildCallbackHtml(copyCommand: string): string;
2
13
  export declare function normalizeBaseUrl(value: string): string;
3
14
  /**
4
15
  * OAuth2 Authorization Code login flow.
@@ -34,11 +45,30 @@ export declare function playwrightLogin(baseUrl: string, options?: {
34
45
  scope?: string;
35
46
  tlsInsecure?: boolean;
36
47
  }): Promise<TokenConfig>;
48
+ /**
49
+ * Log in on a headless machine using OAuth2 client credentials and a refresh token (no browser).
50
+ * Exchanges the refresh token for a new access token and persists ~/.kweaver/ state.
51
+ */
52
+ export declare function refreshTokenLogin(baseUrl: string, options: {
53
+ clientId: string;
54
+ clientSecret: string;
55
+ refreshToken: string;
56
+ tlsInsecure?: boolean;
57
+ }): Promise<TokenConfig>;
37
58
  /**
38
59
  * Exchange refresh_token for a new access token (OAuth2 password grant style, same as Python ConfigAuth).
39
60
  * Persists the new token to ~/.kweaver/ and returns it.
40
61
  */
41
62
  export declare function refreshAccessToken(token: TokenConfig): Promise<TokenConfig>;
63
+ /**
64
+ * Resolve a usable access token for the current platform.
65
+ *
66
+ * **Default behavior** (saved `~/.kweaver/` session from OAuth2 code login): when the access
67
+ * token is expired or near expiry, automatically exchanges the saved **refresh_token** for a new
68
+ * access token (OAuth2 `refresh_token` grant) and persists it. No extra flags are required.
69
+ *
70
+ * Static env `KWEAVER_TOKEN` bypasses refresh (see implementation).
71
+ */
42
72
  export declare function ensureValidToken(opts?: {
43
73
  forceRefresh?: boolean;
44
74
  }): Promise<TokenConfig>;