@kweaver-ai/kweaver-sdk 0.4.10 → 0.4.11

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
@@ -98,7 +98,7 @@ const results = await cl.search({ query: "hypertension treatment" });
98
98
  ## CLI Reference
99
99
 
100
100
  ```
101
- kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] — also: status, list, use, delete, logout
101
+ kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k] — also: status, list, use, delete, logout
102
102
  kweaver token
103
103
  kweaver bkn list/get/stats/export/create/update/delete
104
104
  kweaver bkn object-type list/get/create/update/delete/query/properties
@@ -120,6 +120,33 @@ kweaver call <path> [-X METHOD] [-d BODY] [-H header]
120
120
  | `KWEAVER_BASE_URL` | KWeaver instance URL |
121
121
  | `KWEAVER_BUSINESS_DOMAIN` | Business domain identifier |
122
122
  | `KWEAVER_TOKEN` | Access token |
123
+ | `KWEAVER_TLS_INSECURE` | Set to `1` or `true` to skip TLS certificate verification for all HTTPS in the process (dev only; prefer `kweaver auth … --insecure` which saves per platform) |
124
+ | `NODE_TLS_REJECT_UNAUTHORIZED` | Node.js built-in TLS switch: set to `0` to skip certificate verification for HTTPS in this process. The `kweaver` CLI sets this when `KWEAVER_TLS_INSECURE` is set or the saved token has insecure TLS (same scope as above; dev only). |
125
+
126
+ ### TLS Certificate Troubleshooting
127
+
128
+ If you encounter errors like `fetch failed`, `self-signed certificate`, or `UNABLE_TO_GET_ISSUER_CERT`, the target server likely uses a self-signed certificate or Kubernetes Ingress default fake certificate. Try the following in order of preference:
129
+
130
+ 1. **Recommended (persists per platform)** — add `--insecure` during login:
131
+ ```bash
132
+ kweaver auth login https://your-host --insecure
133
+ # or shorthand
134
+ kweaver auth login https://your-host -k
135
+ ```
136
+ The flag is saved to `token.json` in `~/.kweaver/`, so all subsequent CLI commands for that platform skip TLS verification automatically.
137
+
138
+ 2. **Temporary (current shell)** — set an environment variable:
139
+ ```bash
140
+ export KWEAVER_TLS_INSECURE=1
141
+ kweaver bkn list
142
+ ```
143
+
144
+ 3. **Node.js native** — set `NODE_TLS_REJECT_UNAUTHORIZED` directly:
145
+ ```bash
146
+ NODE_TLS_REJECT_UNAUTHORIZED=0 kweaver bkn list
147
+ ```
148
+
149
+ > **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.
123
150
 
124
151
  ## Using with AI Agents
125
152
 
package/README.zh.md CHANGED
@@ -98,7 +98,7 @@ const results = await cl.search({ query: "高血压 治疗" });
98
98
  ## 命令速查
99
99
 
100
100
  ```
101
- kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] — 另有 status、list、use、delete、logout
101
+ kweaver auth login <url> [--alias name] [-u user] [-p pass] [--playwright] [--insecure|-k] — 另有 status、list、use、delete、logout
102
102
  kweaver token
103
103
  kweaver bkn list/get/stats/export/create/update/delete
104
104
  kweaver bkn object-type list/get/create/update/delete/query/properties
@@ -120,6 +120,33 @@ kweaver call <path> [-X METHOD] [-d BODY] [-H header]
120
120
  | `KWEAVER_BASE_URL` | KWeaver 实例地址 |
121
121
  | `KWEAVER_BUSINESS_DOMAIN` | 业务域标识 |
122
122
  | `KWEAVER_TOKEN` | 访问令牌 |
123
+ | `KWEAVER_TLS_INSECURE` | 设为 `1` 或 `true` 时跳过 TLS 证书校验(仅开发;更推荐 `kweaver auth … --insecure` 以按平台持久化) |
124
+ | `NODE_TLS_REJECT_UNAUTHORIZED` | Node.js 内置 TLS 开关:设为 `0` 时在本进程内跳过 HTTPS 证书校验。`kweaver` 在 `KWEAVER_TLS_INSECURE` 生效或已保存 token 为不安全 TLS 时会设置此项(范围同上;仅开发)。 |
125
+
126
+ ### TLS 证书问题排查
127
+
128
+ 如果遇到 `fetch failed`、`self-signed certificate`、`UNABLE_TO_GET_ISSUER_CERT` 等 TLS 相关错误,通常是目标服务器使用了自签名证书或 Kubernetes Ingress 默认假证书。可按优先级尝试以下方案:
129
+
130
+ 1. **推荐(按平台持久化)** — 登录时加 `--insecure`:
131
+ ```bash
132
+ kweaver auth login https://your-host --insecure
133
+ # 或简写
134
+ kweaver auth login https://your-host -k
135
+ ```
136
+ 该标记会写入 `~/.kweaver/` 的 `token.json`,后续所有 CLI 命令对该平台自动生效,无需额外操作。
137
+
138
+ 2. **临时生效(当前 shell)** — 设置环境变量:
139
+ ```bash
140
+ export KWEAVER_TLS_INSECURE=1
141
+ kweaver bkn list
142
+ ```
143
+
144
+ 3. **Node.js 原生方式** — 直接设置 `NODE_TLS_REJECT_UNAUTHORIZED`:
145
+ ```bash
146
+ NODE_TLS_REJECT_UNAUTHORIZED=0 kweaver bkn list
147
+ ```
148
+
149
+ > **安全提示:** 以上方式均会跳过 HTTPS 证书校验,仅适用于开发/内网环境。生产环境请使用受信任的 CA 签发证书。
123
150
 
124
151
  ## 在 AI 智能体中使用
125
152
 
@@ -0,0 +1,78 @@
1
+ export interface DataflowStep {
2
+ id: string;
3
+ title: string;
4
+ operator: string;
5
+ parameters: Record<string, unknown>;
6
+ }
7
+ export interface DataflowCreateBody {
8
+ title: string;
9
+ description?: string;
10
+ trigger_config: {
11
+ operator: string;
12
+ };
13
+ steps: DataflowStep[];
14
+ }
15
+ export interface DataflowResult {
16
+ status: "success" | "completed" | "failed" | "error";
17
+ reason?: string;
18
+ }
19
+ export interface CreateDataflowOptions {
20
+ baseUrl: string;
21
+ accessToken: string;
22
+ businessDomain?: string;
23
+ body: DataflowCreateBody;
24
+ }
25
+ /**
26
+ * Create a new dataflow (DAG). Returns the new DAG id.
27
+ */
28
+ export declare function createDataflow(options: CreateDataflowOptions): Promise<string>;
29
+ export interface RunDataflowOptions {
30
+ baseUrl: string;
31
+ accessToken: string;
32
+ businessDomain?: string;
33
+ dagId: string;
34
+ }
35
+ /**
36
+ * Trigger a run for an existing dataflow DAG.
37
+ */
38
+ export declare function runDataflow(options: RunDataflowOptions): Promise<void>;
39
+ export interface PollDataflowOptions {
40
+ baseUrl: string;
41
+ accessToken: string;
42
+ businessDomain?: string;
43
+ dagId: string;
44
+ /** Poll interval in seconds. Default: 3 */
45
+ interval?: number;
46
+ /** Maximum time to wait in seconds. Default: 900 */
47
+ timeout?: number;
48
+ }
49
+ /**
50
+ * Poll GET /api/automation/v1/dag/{dagId}/results until the run is done.
51
+ * Throws on "failed"/"error" status or timeout.
52
+ */
53
+ export declare function pollDataflowResults(options: PollDataflowOptions): Promise<DataflowResult>;
54
+ export interface DeleteDataflowOptions {
55
+ baseUrl: string;
56
+ accessToken: string;
57
+ businessDomain?: string;
58
+ dagId: string;
59
+ }
60
+ /**
61
+ * Delete a dataflow DAG. Best-effort — does not throw on errors.
62
+ */
63
+ export declare function deleteDataflow(options: DeleteDataflowOptions): Promise<void>;
64
+ export interface ExecuteDataflowOptions {
65
+ baseUrl: string;
66
+ accessToken: string;
67
+ businessDomain?: string;
68
+ body: DataflowCreateBody;
69
+ /** Poll interval in seconds. Default: 3 */
70
+ interval?: number;
71
+ /** Maximum polling time in seconds. Default: 900 */
72
+ timeout?: number;
73
+ }
74
+ /**
75
+ * Full dataflow lifecycle: create → run → poll → delete (always, even on error).
76
+ * Returns the final DataflowResult.
77
+ */
78
+ export declare function executeDataflow(options: ExecuteDataflowOptions): Promise<DataflowResult>;
@@ -0,0 +1,135 @@
1
+ import { HttpError } from "../utils/http.js";
2
+ function buildHeaders(accessToken, businessDomain) {
3
+ return {
4
+ accept: "application/json, text/plain, */*",
5
+ "accept-language": "zh-cn",
6
+ authorization: `Bearer ${accessToken}`,
7
+ token: accessToken,
8
+ "x-business-domain": businessDomain,
9
+ "x-language": "zh-cn",
10
+ };
11
+ }
12
+ function debugLog(method, url, headers, body) {
13
+ if (!process.env["KWEAVER_DEBUG_HTTP"])
14
+ return;
15
+ const masked = { ...headers };
16
+ if (masked.authorization)
17
+ masked.authorization = masked.authorization.slice(0, 20) + "…";
18
+ if (masked.token)
19
+ masked.token = masked.token.slice(0, 20) + "…";
20
+ process.stderr.write(`[debug] ${method} ${url}\n`);
21
+ process.stderr.write(`[debug] headers: ${JSON.stringify(masked)}\n`);
22
+ if (body)
23
+ process.stderr.write(`[debug] body (first 300): ${body.slice(0, 300)}\n`);
24
+ }
25
+ /**
26
+ * Create a new dataflow (DAG). Returns the new DAG id.
27
+ */
28
+ export async function createDataflow(options) {
29
+ const { baseUrl, accessToken, businessDomain = "bd_public", body } = options;
30
+ const base = baseUrl.replace(/\/+$/, "");
31
+ const url = `${base}/api/automation/v1/data-flow/flow`;
32
+ const reqHeaders = { ...buildHeaders(accessToken, businessDomain), "content-type": "application/json" };
33
+ const reqBody = JSON.stringify(body);
34
+ debugLog("POST", url, reqHeaders, reqBody);
35
+ const response = await fetch(url, {
36
+ method: "POST",
37
+ headers: reqHeaders,
38
+ body: reqBody,
39
+ });
40
+ const responseBody = await response.text();
41
+ if (!response.ok) {
42
+ throw new HttpError(response.status, response.statusText, responseBody);
43
+ }
44
+ const parsed = JSON.parse(responseBody);
45
+ return parsed.id;
46
+ }
47
+ /**
48
+ * Trigger a run for an existing dataflow DAG.
49
+ */
50
+ export async function runDataflow(options) {
51
+ const { baseUrl, accessToken, businessDomain = "bd_public", dagId } = options;
52
+ const base = baseUrl.replace(/\/+$/, "");
53
+ const url = `${base}/api/automation/v1/run-instance/${encodeURIComponent(dagId)}`;
54
+ const response = await fetch(url, {
55
+ method: "POST",
56
+ headers: {
57
+ ...buildHeaders(accessToken, businessDomain),
58
+ "content-type": "application/json",
59
+ },
60
+ body: "{}",
61
+ });
62
+ if (!response.ok) {
63
+ const responseBody = await response.text();
64
+ throw new HttpError(response.status, response.statusText, responseBody);
65
+ }
66
+ }
67
+ /**
68
+ * Poll GET /api/automation/v1/dag/{dagId}/results until the run is done.
69
+ * Throws on "failed"/"error" status or timeout.
70
+ */
71
+ export async function pollDataflowResults(options) {
72
+ const { baseUrl, accessToken, businessDomain = "bd_public", dagId, interval = 3, timeout = 900, } = options;
73
+ const base = baseUrl.replace(/\/+$/, "");
74
+ const url = `${base}/api/automation/v1/dag/${encodeURIComponent(dagId)}/results`;
75
+ const deadlineMs = Date.now() + timeout * 1000;
76
+ while (Date.now() < deadlineMs) {
77
+ const response = await fetch(url, {
78
+ method: "GET",
79
+ headers: buildHeaders(accessToken, businessDomain),
80
+ });
81
+ const responseBody = await response.text();
82
+ if (!response.ok) {
83
+ throw new HttpError(response.status, response.statusText, responseBody);
84
+ }
85
+ const parsed = JSON.parse(responseBody);
86
+ const results = parsed.results ?? [];
87
+ const latest = results[0];
88
+ if (latest) {
89
+ if (latest.status === "success" || latest.status === "completed") {
90
+ return latest;
91
+ }
92
+ if (latest.status === "failed" || latest.status === "error") {
93
+ const reason = latest.reason ? `: ${latest.reason}` : "";
94
+ throw new Error(`Dataflow run ${latest.status}${reason}`);
95
+ }
96
+ }
97
+ // Still running — wait before next poll
98
+ if (interval > 0) {
99
+ await new Promise((resolve) => setTimeout(resolve, interval * 1000));
100
+ }
101
+ }
102
+ throw new Error(`Dataflow polling timed out after ${timeout}s for DAG ${dagId}`);
103
+ }
104
+ /**
105
+ * Delete a dataflow DAG. Best-effort — does not throw on errors.
106
+ */
107
+ export async function deleteDataflow(options) {
108
+ const { baseUrl, accessToken, businessDomain = "bd_public", dagId } = options;
109
+ const base = baseUrl.replace(/\/+$/, "");
110
+ const url = `${base}/api/automation/v1/data-flow/flow/${encodeURIComponent(dagId)}`;
111
+ try {
112
+ await fetch(url, {
113
+ method: "DELETE",
114
+ headers: buildHeaders(accessToken, businessDomain),
115
+ });
116
+ }
117
+ catch {
118
+ // Best-effort: swallow all errors
119
+ }
120
+ }
121
+ /**
122
+ * Full dataflow lifecycle: create → run → poll → delete (always, even on error).
123
+ * Returns the final DataflowResult.
124
+ */
125
+ export async function executeDataflow(options) {
126
+ const { baseUrl, accessToken, businessDomain = "bd_public", body, interval, timeout } = options;
127
+ const dagId = await createDataflow({ baseUrl, accessToken, businessDomain, body });
128
+ try {
129
+ await runDataflow({ baseUrl, accessToken, businessDomain, dagId });
130
+ return await pollDataflowResults({ baseUrl, accessToken, businessDomain, dagId, interval, timeout });
131
+ }
132
+ finally {
133
+ await deleteDataflow({ baseUrl, accessToken, businessDomain, dagId }).catch(() => { });
134
+ }
135
+ }
@@ -51,11 +51,60 @@ export async function createDataView(options) {
51
51
  });
52
52
  const responseBody = await response.text();
53
53
  if (!response.ok) {
54
+ // If DataView already exists (403 with "Existed" error code), delete and recreate
55
+ if (response.status === 403) {
56
+ try {
57
+ const errBody = JSON.parse(responseBody);
58
+ if (errBody.error_code?.includes("Existed")) {
59
+ const actualId = await findDataViewByName({ baseUrl, accessToken, name, groupId: datasourceId, businessDomain });
60
+ if (actualId && fields.length > 0) {
61
+ // Delete the bare DataView (created by scanMetadata) and recreate with fields
62
+ await deleteDataView({ baseUrl, accessToken, id: actualId, businessDomain });
63
+ const retryResponse = await fetch(url, {
64
+ method: "POST",
65
+ headers: { ...buildHeaders(accessToken, businessDomain), "content-type": "application/json" },
66
+ body,
67
+ });
68
+ if (retryResponse.ok) {
69
+ const retryBody = await retryResponse.text();
70
+ const retryId = extractViewId(JSON.parse(retryBody));
71
+ return retryId ?? viewId;
72
+ }
73
+ }
74
+ if (actualId)
75
+ return actualId;
76
+ return viewId;
77
+ }
78
+ }
79
+ catch { /* fall through to throw */ }
80
+ }
54
81
  throw new HttpError(response.status, response.statusText, responseBody);
55
82
  }
56
83
  const createdId = extractViewId(JSON.parse(responseBody));
57
84
  return createdId ?? viewId;
58
85
  }
86
+ async function findDataViewByName(options) {
87
+ const base = options.baseUrl.replace(/\/+$/, "");
88
+ const url = new URL(`${base}/api/mdl-data-model/v1/data-views`);
89
+ url.searchParams.set("keyword", options.name);
90
+ const response = await fetch(url.toString(), {
91
+ method: "GET",
92
+ headers: buildHeaders(options.accessToken, options.businessDomain),
93
+ });
94
+ if (!response.ok)
95
+ return null;
96
+ const body = JSON.parse(await response.text());
97
+ const match = body.entries?.find((e) => e.name === options.name && e.group_id === options.groupId);
98
+ return match?.id ?? null;
99
+ }
100
+ async function deleteDataView(options) {
101
+ const base = options.baseUrl.replace(/\/+$/, "");
102
+ const url = `${base}/api/mdl-data-model/v1/data-views/${encodeURIComponent(options.id)}`;
103
+ await fetch(url, {
104
+ method: "DELETE",
105
+ headers: buildHeaders(options.accessToken, options.businessDomain),
106
+ });
107
+ }
59
108
  export async function getDataView(options) {
60
109
  const { baseUrl, accessToken, id, businessDomain = "bd_public", } = options;
61
110
  const base = baseUrl.replace(/\/+$/, "");
@@ -2,7 +2,7 @@ import { type TokenConfig } from "../config/store.js";
2
2
  export declare function normalizeBaseUrl(value: string): string;
3
3
  /**
4
4
  * OAuth2 Authorization Code login flow.
5
- * 1. Register client (if not already registered)
5
+ * 1. Register client (if not already registered), OR use a provided client ID
6
6
  * 2. Open browser to /oauth2/auth
7
7
  * 3. Receive authorization code via local HTTP callback
8
8
  * 4. Exchange code for access_token + refresh_token
@@ -11,6 +11,10 @@ export declare function normalizeBaseUrl(value: string): string;
11
11
  export declare function oauth2Login(baseUrl: string, options?: {
12
12
  port?: number;
13
13
  scope?: string;
14
+ clientId?: string;
15
+ clientSecret?: string;
16
+ /** Skip TLS certificate verification (self-signed / dev servers only). */
17
+ tlsInsecure?: boolean;
14
18
  }): Promise<TokenConfig>;
15
19
  /**
16
20
  * Playwright-automated OAuth2 login.
@@ -28,6 +32,7 @@ export declare function playwrightLogin(baseUrl: string, options?: {
28
32
  password?: string;
29
33
  port?: number;
30
34
  scope?: string;
35
+ tlsInsecure?: boolean;
31
36
  }): Promise<TokenConfig>;
32
37
  /**
33
38
  * Exchange refresh_token for a new access token (OAuth2 password grant style, same as Python ConfigAuth).