@kweaver-ai/kweaver-sdk 0.4.6 → 0.4.8
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/bin/kweaver.js +17 -2
- package/dist/auth/oauth.d.ts +11 -2
- package/dist/auth/oauth.js +95 -63
- package/dist/cli.js +93 -26
- package/dist/commands/bkn.d.ts +36 -0
- package/dist/commands/bkn.js +246 -20
- package/dist/commands/call.js +7 -1
- package/package.json +2 -2
package/bin/kweaver.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
function exit(code) {
|
|
4
|
+
if (process.stdout.writableNeedDrain || process.stderr.writableNeedDrain) {
|
|
5
|
+
const done = () => {
|
|
6
|
+
if (!process.stdout.writableNeedDrain && !process.stderr.writableNeedDrain) {
|
|
7
|
+
process.exit(code);
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
process.stdout.once("drain", done);
|
|
11
|
+
process.stderr.once("drain", done);
|
|
12
|
+
} else {
|
|
13
|
+
process.exit(code);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
2
17
|
import("../dist/cli.js").then(({ run }) => {
|
|
3
18
|
run(process.argv.slice(2))
|
|
4
|
-
.then((code) =>
|
|
19
|
+
.then((code) => exit(code))
|
|
5
20
|
.catch((err) => {
|
|
6
21
|
console.error(err instanceof Error ? err.message : String(err));
|
|
7
|
-
|
|
22
|
+
exit(1);
|
|
8
23
|
});
|
|
9
24
|
});
|
package/dist/auth/oauth.d.ts
CHANGED
|
@@ -13,12 +13,21 @@ export declare function oauth2Login(baseUrl: string, options?: {
|
|
|
13
13
|
scope?: string;
|
|
14
14
|
}): Promise<TokenConfig>;
|
|
15
15
|
/**
|
|
16
|
-
* Playwright
|
|
17
|
-
*
|
|
16
|
+
* Playwright-automated OAuth2 login.
|
|
17
|
+
*
|
|
18
|
+
* Uses the full OAuth2 authorization code flow (same as `oauth2Login`) but
|
|
19
|
+
* automates the browser interaction with Playwright. This produces a
|
|
20
|
+
* refresh_token so the CLI can auto-refresh without re-login.
|
|
21
|
+
*
|
|
22
|
+
* When `username` and `password` are provided the browser runs headless and
|
|
23
|
+
* fills the login form automatically. Otherwise it opens a visible browser
|
|
24
|
+
* window for manual login (same UX as the old cookie-based flow).
|
|
18
25
|
*/
|
|
19
26
|
export declare function playwrightLogin(baseUrl: string, options?: {
|
|
20
27
|
username?: string;
|
|
21
28
|
password?: string;
|
|
29
|
+
port?: number;
|
|
30
|
+
scope?: string;
|
|
22
31
|
}): Promise<TokenConfig>;
|
|
23
32
|
/**
|
|
24
33
|
* Exchange refresh_token for a new access token (OAuth2 password grant style, same as Python ConfigAuth).
|
package/dist/auth/oauth.js
CHANGED
|
@@ -161,10 +161,19 @@ async function exchangeCodeForToken(baseUrl, code, clientId, clientSecret, redir
|
|
|
161
161
|
return token;
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
|
-
* Playwright
|
|
165
|
-
*
|
|
164
|
+
* Playwright-automated OAuth2 login.
|
|
165
|
+
*
|
|
166
|
+
* Uses the full OAuth2 authorization code flow (same as `oauth2Login`) but
|
|
167
|
+
* automates the browser interaction with Playwright. This produces a
|
|
168
|
+
* refresh_token so the CLI can auto-refresh without re-login.
|
|
169
|
+
*
|
|
170
|
+
* When `username` and `password` are provided the browser runs headless and
|
|
171
|
+
* fills the login form automatically. Otherwise it opens a visible browser
|
|
172
|
+
* window for manual login (same UX as the old cookie-based flow).
|
|
166
173
|
*/
|
|
167
174
|
export async function playwrightLogin(baseUrl, options) {
|
|
175
|
+
const { createServer } = await import("node:http");
|
|
176
|
+
const { randomBytes } = await import("node:crypto");
|
|
168
177
|
let chromium;
|
|
169
178
|
try {
|
|
170
179
|
const modName = "playwright";
|
|
@@ -174,71 +183,94 @@ export async function playwrightLogin(baseUrl, options) {
|
|
|
174
183
|
catch {
|
|
175
184
|
throw new Error("Playwright is not installed. Run:\n npm install playwright && npx playwright install chromium");
|
|
176
185
|
}
|
|
177
|
-
const
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
186
|
+
const base = normalizeBaseUrl(baseUrl);
|
|
187
|
+
const port = options?.port ?? DEFAULT_REDIRECT_PORT;
|
|
188
|
+
const scope = options?.scope ?? DEFAULT_SCOPE;
|
|
189
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
190
|
+
const hasCredentials = !!(options?.username && options?.password);
|
|
191
|
+
// Step 1: Ensure registered OAuth2 client
|
|
192
|
+
let client = loadClientConfig(base);
|
|
193
|
+
if (!client?.clientId) {
|
|
194
|
+
client = await registerOAuth2Client(base, redirectUri, scope);
|
|
195
|
+
saveClientConfig(base, client);
|
|
196
|
+
}
|
|
197
|
+
// Step 2: Generate CSRF state
|
|
198
|
+
const state = randomBytes(12).toString("hex");
|
|
199
|
+
// Step 3: Build authorization URL
|
|
200
|
+
const authParams = new URLSearchParams({
|
|
201
|
+
redirect_uri: redirectUri,
|
|
202
|
+
"x-forwarded-prefix": "",
|
|
203
|
+
client_id: client.clientId,
|
|
204
|
+
scope,
|
|
205
|
+
response_type: "code",
|
|
206
|
+
state,
|
|
207
|
+
lang: "zh-cn",
|
|
208
|
+
product: "adp",
|
|
209
|
+
});
|
|
210
|
+
const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
|
|
211
|
+
// Step 4: Start local callback server to capture the authorization code
|
|
212
|
+
const code = await new Promise((resolve, reject) => {
|
|
213
|
+
const TIMEOUT_MS = hasCredentials ? 30_000 : 120_000;
|
|
214
|
+
const timeoutId = setTimeout(() => {
|
|
215
|
+
server.close();
|
|
216
|
+
browser?.close();
|
|
217
|
+
reject(new Error(`OAuth2 login timed out (${TIMEOUT_MS / 1000}s). No authorization code received.`));
|
|
218
|
+
}, TIMEOUT_MS);
|
|
219
|
+
const server = createServer((req, res) => {
|
|
220
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
221
|
+
if (url.pathname === "/callback") {
|
|
222
|
+
const receivedState = url.searchParams.get("state");
|
|
223
|
+
const receivedCode = url.searchParams.get("code");
|
|
224
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
225
|
+
res.end("<html><body><h2>Login successful. You can close this tab.</h2></body></html>");
|
|
226
|
+
clearTimeout(timeoutId);
|
|
227
|
+
server.close();
|
|
228
|
+
browser?.close();
|
|
229
|
+
if (receivedState !== state) {
|
|
230
|
+
reject(new Error("OAuth2 state mismatch — possible CSRF attack."));
|
|
203
231
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
break;
|
|
207
|
-
// In headless mode, check for login error messages
|
|
208
|
-
if (hasCredentials) {
|
|
209
|
-
try {
|
|
210
|
-
const errorEl = await page.$(".ant-message-error, .ant-alert-error");
|
|
211
|
-
if (errorEl) {
|
|
212
|
-
const errorText = await errorEl.textContent();
|
|
213
|
-
throw new Error(`Login failed: ${errorText?.trim() || "unknown error"}`);
|
|
214
|
-
}
|
|
232
|
+
else if (!receivedCode) {
|
|
233
|
+
reject(new Error("No authorization code received in callback."));
|
|
215
234
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
throw e;
|
|
235
|
+
else {
|
|
236
|
+
resolve(receivedCode);
|
|
219
237
|
}
|
|
220
238
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
else {
|
|
240
|
+
res.writeHead(404);
|
|
241
|
+
res.end();
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
let browser;
|
|
245
|
+
server.listen(port, "127.0.0.1", async () => {
|
|
246
|
+
try {
|
|
247
|
+
browser = await chromium.launch({ headless: hasCredentials });
|
|
248
|
+
const context = await browser.newContext();
|
|
249
|
+
const page = await context.newPage();
|
|
250
|
+
// Navigate to OAuth2 auth URL — redirects to signin page
|
|
251
|
+
await page.goto(authUrl, { waitUntil: "networkidle", timeout: 30_000 });
|
|
252
|
+
if (hasCredentials) {
|
|
253
|
+
// Auto-fill credentials
|
|
254
|
+
await page.waitForSelector('input[name="account"]', { timeout: 10_000 });
|
|
255
|
+
await page.fill('input[name="account"]', options.username);
|
|
256
|
+
await page.fill('input[name="password"]', options.password);
|
|
257
|
+
await page.click("button.ant-btn-primary");
|
|
258
|
+
}
|
|
259
|
+
// else: visible browser — user logs in manually
|
|
260
|
+
// The OAuth2 callback will fire when login completes, resolving the promise above
|
|
261
|
+
}
|
|
262
|
+
catch (err) {
|
|
263
|
+
clearTimeout(timeoutId);
|
|
264
|
+
server.close();
|
|
265
|
+
browser?.close();
|
|
266
|
+
reject(err);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
// Step 5: Exchange authorization code for tokens (includes refresh_token)
|
|
271
|
+
const token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri);
|
|
272
|
+
setCurrentPlatform(base);
|
|
273
|
+
return token;
|
|
242
274
|
}
|
|
243
275
|
function tokenNeedsRefresh(token) {
|
|
244
276
|
if (!token.expiresAt) {
|
package/dist/cli.js
CHANGED
|
@@ -11,47 +11,100 @@ function printHelp() {
|
|
|
11
11
|
console.log(`kweaver
|
|
12
12
|
|
|
13
13
|
Usage:
|
|
14
|
-
kweaver
|
|
15
|
-
kweaver
|
|
16
|
-
|
|
17
|
-
kweaver auth
|
|
14
|
+
kweaver --version | -V
|
|
15
|
+
kweaver --help | -h
|
|
16
|
+
|
|
17
|
+
kweaver auth <platform-url> [--alias name] [-u user] [-p pass] [--playwright]
|
|
18
|
+
kweaver auth login <platform-url> (alias for auth <url>)
|
|
19
|
+
kweaver auth status [platform-url|alias]
|
|
18
20
|
kweaver auth list
|
|
19
|
-
kweaver auth use <platform-url>
|
|
20
|
-
kweaver auth logout [platform-url]
|
|
21
|
-
kweaver auth delete <platform-url>
|
|
21
|
+
kweaver auth use <platform-url|alias>
|
|
22
|
+
kweaver auth logout [platform-url|alias]
|
|
23
|
+
kweaver auth delete <platform-url|alias>
|
|
22
24
|
kweaver token
|
|
23
|
-
|
|
24
|
-
kweaver
|
|
25
|
-
|
|
26
|
-
kweaver
|
|
27
|
-
|
|
25
|
+
|
|
26
|
+
kweaver call <url> [-X METHOD] [-H "Name: value"] [-d BODY] [--data-raw BODY]
|
|
27
|
+
[--url URL] [--pretty] [--verbose] [-bd value]
|
|
28
|
+
(alias: kweaver curl ...)
|
|
29
|
+
|
|
30
|
+
kweaver agent chat <agent_id> [-m "message"] [--version value] [--conversation-id id]
|
|
31
|
+
[--stream] [--no-stream] [--verbose] [-bd value]
|
|
32
|
+
kweaver agent list [--name X] [--limit N] [--offset N] [-bd value] [--pretty]
|
|
33
|
+
kweaver agent get <agent_id> [-bd value] [--pretty]
|
|
34
|
+
kweaver agent get-by-key <key> [-bd value] [--pretty]
|
|
35
|
+
kweaver agent sessions <agent_id> [-bd value] [--limit N] [--pretty]
|
|
36
|
+
kweaver agent history <agent_id> <session_id> [-bd value] [--limit N] [--pretty]
|
|
37
|
+
kweaver agent create [options]
|
|
38
|
+
kweaver agent update <agent_id> [options]
|
|
39
|
+
kweaver agent delete <agent_id> [-bd value]
|
|
40
|
+
kweaver agent publish <agent_id> [-bd value]
|
|
41
|
+
kweaver agent unpublish <agent_id> [-bd value]
|
|
42
|
+
|
|
43
|
+
kweaver ds list [--keyword X] [--type T] [-bd value] [--pretty]
|
|
28
44
|
kweaver ds get <id>
|
|
29
|
-
kweaver ds
|
|
45
|
+
kweaver ds delete <id> [-y]
|
|
46
|
+
kweaver ds tables <id> [--keyword X] [--pretty]
|
|
47
|
+
kweaver ds connect <db_type> <host> <port> <database> --account X --password Y [--schema S] [--name N]
|
|
48
|
+
|
|
30
49
|
kweaver bkn list [options]
|
|
31
50
|
kweaver bkn get <kn-id> [options]
|
|
32
|
-
kweaver bkn search <kn-id> <query> [
|
|
51
|
+
kweaver bkn search <kn-id> <query> [--max-concepts N] [--mode M] [--pretty] [-bd value]
|
|
33
52
|
kweaver bkn create [options]
|
|
53
|
+
kweaver bkn create-from-ds [options]
|
|
34
54
|
kweaver bkn update <kn-id> [options]
|
|
35
|
-
kweaver bkn delete <kn-id>
|
|
36
|
-
kweaver
|
|
37
|
-
kweaver
|
|
38
|
-
kweaver
|
|
39
|
-
kweaver
|
|
55
|
+
kweaver bkn delete <kn-id> [-y]
|
|
56
|
+
kweaver bkn build <kn-id> [--wait] [--no-wait] [--timeout N]
|
|
57
|
+
kweaver bkn validate <kn-id>
|
|
58
|
+
kweaver bkn export <kn-id>
|
|
59
|
+
kweaver bkn stats <kn-id>
|
|
60
|
+
kweaver bkn push <directory> [--branch main] [-bd value]
|
|
61
|
+
kweaver bkn pull <kn-id> [directory] [--branch main] [-bd value]
|
|
62
|
+
kweaver bkn object-type list|get|create|update|delete|query|properties <kn-id> ...
|
|
63
|
+
kweaver bkn relation-type list|get|create|update|delete <kn-id> ...
|
|
64
|
+
kweaver bkn subgraph <kn-id> <body-json>
|
|
65
|
+
kweaver bkn action-type list|query|execute <kn-id> ... [--wait] [--no-wait] [--timeout N]
|
|
66
|
+
kweaver bkn action-execution get <kn-id> <execution-id>
|
|
67
|
+
kweaver bkn action-log list|get|cancel <kn-id> ...
|
|
68
|
+
|
|
69
|
+
kweaver config set-bd <value>
|
|
70
|
+
kweaver config show
|
|
71
|
+
|
|
72
|
+
kweaver vega health|stats|inspect
|
|
73
|
+
kweaver vega catalog list|get|health|test-connection|discover|resources [options]
|
|
74
|
+
kweaver vega resource list|get|query|preview [options]
|
|
75
|
+
kweaver vega connector-type list|get [options]
|
|
76
|
+
|
|
77
|
+
kweaver context-loader config set|use|list|remove|show [options]
|
|
78
|
+
kweaver context-loader tools|resources|templates|prompts [--cursor]
|
|
79
|
+
kweaver context-loader resource <uri>
|
|
80
|
+
kweaver context-loader prompt <name> [--args json]
|
|
81
|
+
kweaver context-loader kn-search <query> [--only-schema]
|
|
82
|
+
kweaver context-loader kn-schema-search <query> [--max N]
|
|
83
|
+
kweaver context-loader query-object-instance|query-instance-subgraph|get-logic-properties|get-action-info ...
|
|
84
|
+
(alias: kweaver context ...)
|
|
40
85
|
|
|
41
86
|
Commands:
|
|
42
87
|
auth Login, list, inspect, and switch saved platform auth profiles
|
|
43
88
|
token Print the current access token, refreshing it first if needed
|
|
44
|
-
call
|
|
89
|
+
call (curl) Call an API with curl-style flags and auto-injected token headers
|
|
90
|
+
agent Agent CRUD, chat, sessions, history, publish/unpublish
|
|
45
91
|
ds Manage datasources (list, get, delete, tables, connect)
|
|
46
|
-
|
|
47
|
-
|
|
92
|
+
bkn Knowledge network (CRUD, build, validate, export, stats, push/pull,
|
|
93
|
+
object-type, relation-type, subgraph, action-type, action-execution, action-log)
|
|
48
94
|
config Per-platform configuration (business domain)
|
|
49
|
-
vega Vega observability
|
|
50
|
-
context-loader
|
|
95
|
+
vega Vega observability (catalog, resource, connector-type, health/stats/inspect)
|
|
96
|
+
context-loader Context-loader MCP (config, tools, resources, prompts, kn-search, query-*, etc.)
|
|
51
97
|
help Show this message`);
|
|
52
98
|
}
|
|
53
99
|
export async function run(argv) {
|
|
54
100
|
const [command, ...rest] = argv;
|
|
101
|
+
if (command === "--version" || command === "-V" || command === "version") {
|
|
102
|
+
const { createRequire } = await import("node:module");
|
|
103
|
+
const require = createRequire(import.meta.url);
|
|
104
|
+
const pkg = require("../package.json");
|
|
105
|
+
console.log(pkg.version);
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
55
108
|
if (argv.length === 0 || !command || command === "--help" || command === "-h" || command === "help") {
|
|
56
109
|
printHelp();
|
|
57
110
|
return 0;
|
|
@@ -87,14 +140,28 @@ export async function run(argv) {
|
|
|
87
140
|
printHelp();
|
|
88
141
|
return 1;
|
|
89
142
|
}
|
|
143
|
+
function safeExit(code) {
|
|
144
|
+
if (process.stdout.writableNeedDrain || process.stderr.writableNeedDrain) {
|
|
145
|
+
const done = () => {
|
|
146
|
+
if (!process.stdout.writableNeedDrain && !process.stderr.writableNeedDrain) {
|
|
147
|
+
process.exit(code);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
process.stdout.once("drain", done);
|
|
151
|
+
process.stderr.once("drain", done);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
process.exit(code);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
90
157
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
91
158
|
run(process.argv.slice(2))
|
|
92
159
|
.then((code) => {
|
|
93
|
-
|
|
160
|
+
safeExit(code);
|
|
94
161
|
})
|
|
95
162
|
.catch((error) => {
|
|
96
163
|
const message = error instanceof Error ? error.message : String(error);
|
|
97
164
|
console.error(message);
|
|
98
|
-
|
|
165
|
+
safeExit(1);
|
|
99
166
|
});
|
|
100
167
|
}
|
package/dist/commands/bkn.d.ts
CHANGED
|
@@ -64,6 +64,42 @@ export interface KnObjectTypeQueryOptions {
|
|
|
64
64
|
}
|
|
65
65
|
export declare function parseKnObjectTypeQueryArgs(args: string[]): KnObjectTypeQueryOptions;
|
|
66
66
|
export declare function runKnCommand(args: string[]): Promise<number>;
|
|
67
|
+
/** Fields merged via GET → modify → PUT (not raw body mode). */
|
|
68
|
+
export interface ObjectTypeMergeFields {
|
|
69
|
+
name?: string;
|
|
70
|
+
displayKey?: string;
|
|
71
|
+
addProperties: Record<string, unknown>[];
|
|
72
|
+
removeProperties: string[];
|
|
73
|
+
tags?: string[];
|
|
74
|
+
comment?: string;
|
|
75
|
+
icon?: string;
|
|
76
|
+
color?: string;
|
|
77
|
+
}
|
|
78
|
+
export type ObjectTypeUpdateParsed = {
|
|
79
|
+
mode: "body";
|
|
80
|
+
knId: string;
|
|
81
|
+
otId: string;
|
|
82
|
+
body: string;
|
|
83
|
+
businessDomain: string;
|
|
84
|
+
pretty: boolean;
|
|
85
|
+
} | {
|
|
86
|
+
mode: "merge";
|
|
87
|
+
knId: string;
|
|
88
|
+
otId: string;
|
|
89
|
+
merge: ObjectTypeMergeFields;
|
|
90
|
+
businessDomain: string;
|
|
91
|
+
pretty: boolean;
|
|
92
|
+
branch: string;
|
|
93
|
+
};
|
|
94
|
+
/** Prepare a GET response entry for PUT (drop read-only fields). */
|
|
95
|
+
export declare function stripObjectTypeForPut(entry: Record<string, unknown>): Record<string, unknown>;
|
|
96
|
+
/**
|
|
97
|
+
* Apply merge flags onto a stripped object-type object (mutates copy).
|
|
98
|
+
* - Add: property `name` not in list → append.
|
|
99
|
+
* - Update: property `name` exists → replace entry (same as add; CLI also accepts `--update-property`).
|
|
100
|
+
* - Delete: `--remove-property` removes by `name` before adds are applied.
|
|
101
|
+
*/
|
|
102
|
+
export declare function applyObjectTypeMerge(target: Record<string, unknown>, merge: ObjectTypeMergeFields): Record<string, unknown>;
|
|
67
103
|
export interface KnActionTypeExecuteOptions {
|
|
68
104
|
knId: string;
|
|
69
105
|
atId: string;
|
package/dist/commands/bkn.js
CHANGED
|
@@ -456,6 +456,57 @@ function parseJsonObject(text, errorMessage) {
|
|
|
456
456
|
}
|
|
457
457
|
return parsed;
|
|
458
458
|
}
|
|
459
|
+
const MAX_OUTPUT_BYTES = 100_000;
|
|
460
|
+
/**
|
|
461
|
+
* If a query response exceeds MAX_OUTPUT_BYTES, trim the datas array
|
|
462
|
+
* to fit, preserving valid JSON and the search_after cursor for pagination.
|
|
463
|
+
*/
|
|
464
|
+
function truncateQueryResult(raw) {
|
|
465
|
+
if (raw.length <= MAX_OUTPUT_BYTES) {
|
|
466
|
+
return raw;
|
|
467
|
+
}
|
|
468
|
+
let parsed;
|
|
469
|
+
try {
|
|
470
|
+
parsed = JSON.parse(raw);
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
return raw;
|
|
474
|
+
}
|
|
475
|
+
const datas = parsed.datas;
|
|
476
|
+
if (!Array.isArray(datas) || datas.length === 0) {
|
|
477
|
+
return raw;
|
|
478
|
+
}
|
|
479
|
+
const originalCount = datas.length;
|
|
480
|
+
while (datas.length > 1) {
|
|
481
|
+
datas.pop();
|
|
482
|
+
const candidate = JSON.stringify(parsed);
|
|
483
|
+
if (candidate.length <= MAX_OUTPUT_BYTES) {
|
|
484
|
+
const remaining = originalCount - datas.length;
|
|
485
|
+
const sa = parsed.search_after;
|
|
486
|
+
parsed._truncated = {
|
|
487
|
+
returned: datas.length,
|
|
488
|
+
total_fetched: originalCount,
|
|
489
|
+
remaining,
|
|
490
|
+
next_search_after: sa ?? null,
|
|
491
|
+
hint: sa
|
|
492
|
+
? `Pass --search-after '${JSON.stringify(sa)}' --limit ${datas.length} to fetch the next page.`
|
|
493
|
+
: `Reduce --limit to ${datas.length} or less to avoid truncation.`,
|
|
494
|
+
};
|
|
495
|
+
console.error(`[warn] Truncated ${originalCount} → ${datas.length} records (output exceeded ${Math.round(MAX_OUTPUT_BYTES / 1024)}KB). ${parsed._truncated.hint}`);
|
|
496
|
+
return JSON.stringify(parsed);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const sa = parsed.search_after;
|
|
500
|
+
parsed._truncated = {
|
|
501
|
+
returned: 1,
|
|
502
|
+
total_fetched: originalCount,
|
|
503
|
+
remaining: originalCount - 1,
|
|
504
|
+
next_search_after: sa ?? null,
|
|
505
|
+
hint: `Single record is very large. Use --limit 1 and --search-after to iterate.`,
|
|
506
|
+
};
|
|
507
|
+
console.error(`[warn] Truncated ${originalCount} → 1 record. Single record is very large. Use --limit 1 and --search-after to iterate.`);
|
|
508
|
+
return JSON.stringify(parsed);
|
|
509
|
+
}
|
|
459
510
|
function parseSearchAfterArray(text) {
|
|
460
511
|
let parsed;
|
|
461
512
|
try {
|
|
@@ -716,12 +767,84 @@ function parseObjectTypeCreateArgs(args) {
|
|
|
716
767
|
businessDomain = resolveBusinessDomain();
|
|
717
768
|
return { knId, body, businessDomain, branch, pretty };
|
|
718
769
|
}
|
|
719
|
-
|
|
770
|
+
const OBJECT_TYPE_PUT_STRIP_KEYS = new Set([
|
|
771
|
+
"status",
|
|
772
|
+
"creator",
|
|
773
|
+
"updater",
|
|
774
|
+
"create_time",
|
|
775
|
+
"update_time",
|
|
776
|
+
"module_type",
|
|
777
|
+
"kn_id",
|
|
778
|
+
]);
|
|
779
|
+
/** Prepare a GET response entry for PUT (drop read-only fields). */
|
|
780
|
+
export function stripObjectTypeForPut(entry) {
|
|
781
|
+
const out = { ...entry };
|
|
782
|
+
for (const k of OBJECT_TYPE_PUT_STRIP_KEYS) {
|
|
783
|
+
delete out[k];
|
|
784
|
+
}
|
|
785
|
+
return out;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Apply merge flags onto a stripped object-type object (mutates copy).
|
|
789
|
+
* - Add: property `name` not in list → append.
|
|
790
|
+
* - Update: property `name` exists → replace entry (same as add; CLI also accepts `--update-property`).
|
|
791
|
+
* - Delete: `--remove-property` removes by `name` before adds are applied.
|
|
792
|
+
*/
|
|
793
|
+
export function applyObjectTypeMerge(target, merge) {
|
|
794
|
+
if (merge.name !== undefined)
|
|
795
|
+
target.name = merge.name;
|
|
796
|
+
if (merge.displayKey !== undefined)
|
|
797
|
+
target.display_key = merge.displayKey;
|
|
798
|
+
if (merge.comment !== undefined)
|
|
799
|
+
target.comment = merge.comment;
|
|
800
|
+
if (merge.icon !== undefined)
|
|
801
|
+
target.icon = merge.icon;
|
|
802
|
+
if (merge.color !== undefined)
|
|
803
|
+
target.color = merge.color;
|
|
804
|
+
if (merge.tags !== undefined)
|
|
805
|
+
target.tags = merge.tags;
|
|
806
|
+
let props = target.data_properties;
|
|
807
|
+
if (!Array.isArray(props)) {
|
|
808
|
+
props = [];
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
props = props.map((p) => p && typeof p === "object" && !Array.isArray(p) ? { ...p } : p);
|
|
812
|
+
}
|
|
813
|
+
const list = props;
|
|
814
|
+
for (const rm of merge.removeProperties) {
|
|
815
|
+
for (let j = list.length - 1; j >= 0; j -= 1) {
|
|
816
|
+
const n = list[j]?.name;
|
|
817
|
+
if (typeof n === "string" && n === rm)
|
|
818
|
+
list.splice(j, 1);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
for (const add of merge.addProperties) {
|
|
822
|
+
const nm = add.name;
|
|
823
|
+
if (typeof nm !== "string" || !nm) {
|
|
824
|
+
throw new Error("--add-property / --update-property JSON must include a non-empty string \"name\" field.");
|
|
825
|
+
}
|
|
826
|
+
const idx = list.findIndex((p) => p?.name === nm);
|
|
827
|
+
if (idx >= 0)
|
|
828
|
+
list[idx] = add;
|
|
829
|
+
else
|
|
830
|
+
list.push(add);
|
|
831
|
+
}
|
|
832
|
+
target.data_properties = list;
|
|
833
|
+
return target;
|
|
834
|
+
}
|
|
835
|
+
/** Parse object-type update: raw JSON body OR merge flags (GET-merge-PUT). */
|
|
720
836
|
function parseObjectTypeUpdateArgs(args) {
|
|
721
837
|
let name;
|
|
722
838
|
let displayKey;
|
|
723
839
|
let businessDomain = "";
|
|
724
840
|
let pretty = true;
|
|
841
|
+
let branch = "main";
|
|
842
|
+
let comment;
|
|
843
|
+
let icon;
|
|
844
|
+
let color;
|
|
845
|
+
let tagsJson;
|
|
846
|
+
const addProperties = [];
|
|
847
|
+
const removeProperties = [];
|
|
725
848
|
const positional = [];
|
|
726
849
|
for (let i = 0; i < args.length; i += 1) {
|
|
727
850
|
const arg = args[i];
|
|
@@ -735,6 +858,35 @@ function parseObjectTypeUpdateArgs(args) {
|
|
|
735
858
|
displayKey = args[++i];
|
|
736
859
|
continue;
|
|
737
860
|
}
|
|
861
|
+
if ((arg === "--add-property" || arg === "--update-property") && args[i + 1]) {
|
|
862
|
+
const raw = args[++i];
|
|
863
|
+
addProperties.push(parseJsonObject(raw, `--add-property / --update-property must be valid JSON object: ${raw}`));
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
if (arg === "--remove-property" && args[i + 1]) {
|
|
867
|
+
removeProperties.push(args[++i]);
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
if (arg === "--tags" && args[i + 1]) {
|
|
871
|
+
tagsJson = args[++i];
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (arg === "--comment" && args[i + 1]) {
|
|
875
|
+
comment = args[++i];
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
if (arg === "--icon" && args[i + 1]) {
|
|
879
|
+
icon = args[++i];
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
if (arg === "--color" && args[i + 1]) {
|
|
883
|
+
color = args[++i];
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (arg === "--branch" && args[i + 1]) {
|
|
887
|
+
branch = args[++i];
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
738
890
|
if ((arg === "-bd" || arg === "--biz-domain") && args[i + 1]) {
|
|
739
891
|
businessDomain = args[++i];
|
|
740
892
|
continue;
|
|
@@ -746,21 +898,72 @@ function parseObjectTypeUpdateArgs(args) {
|
|
|
746
898
|
if (!arg.startsWith("-"))
|
|
747
899
|
positional.push(arg);
|
|
748
900
|
}
|
|
749
|
-
const [knId, otId] = positional;
|
|
901
|
+
const [knId, otId, maybeBody] = positional;
|
|
750
902
|
if (!knId || !otId) {
|
|
751
|
-
throw new Error("Usage: kweaver bkn object-type update <kn-id> <ot-id> [--name
|
|
903
|
+
throw new Error("Usage: kweaver bkn object-type update <kn-id> <ot-id> [ '<full-json-body>' ] [--name ...] [--add-property|--update-property '<json>' ...] [--remove-property <name> ...]");
|
|
904
|
+
}
|
|
905
|
+
const hasMergeFlags = name !== undefined ||
|
|
906
|
+
displayKey !== undefined ||
|
|
907
|
+
addProperties.length > 0 ||
|
|
908
|
+
removeProperties.length > 0 ||
|
|
909
|
+
tagsJson !== undefined ||
|
|
910
|
+
comment !== undefined ||
|
|
911
|
+
icon !== undefined ||
|
|
912
|
+
color !== undefined;
|
|
913
|
+
if (maybeBody !== undefined && maybeBody.trim().startsWith("{")) {
|
|
914
|
+
if (hasMergeFlags) {
|
|
915
|
+
throw new Error("Do not combine a raw JSON body with --name/--add-property/--update-property/--remove-property and other merge flags.");
|
|
916
|
+
}
|
|
917
|
+
if (!businessDomain)
|
|
918
|
+
businessDomain = resolveBusinessDomain();
|
|
919
|
+
return {
|
|
920
|
+
mode: "body",
|
|
921
|
+
knId,
|
|
922
|
+
otId,
|
|
923
|
+
body: maybeBody.trim(),
|
|
924
|
+
businessDomain,
|
|
925
|
+
pretty,
|
|
926
|
+
};
|
|
752
927
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
928
|
+
if (maybeBody !== undefined) {
|
|
929
|
+
throw new Error(`Unexpected third argument "${maybeBody}". For raw PUT body, pass a single JSON object starting with "{".`);
|
|
930
|
+
}
|
|
931
|
+
let tags;
|
|
932
|
+
if (tagsJson !== undefined) {
|
|
933
|
+
try {
|
|
934
|
+
const t = JSON.parse(tagsJson);
|
|
935
|
+
if (!Array.isArray(t) || !t.every((x) => typeof x === "string")) {
|
|
936
|
+
throw new Error("invalid");
|
|
937
|
+
}
|
|
938
|
+
tags = t;
|
|
939
|
+
}
|
|
940
|
+
catch {
|
|
941
|
+
throw new Error(`--tags must be a JSON array of strings, e.g. '["足球","球员"]'`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
const merge = {
|
|
945
|
+
addProperties,
|
|
946
|
+
removeProperties,
|
|
947
|
+
...(name !== undefined ? { name } : {}),
|
|
948
|
+
...(displayKey !== undefined ? { displayKey } : {}),
|
|
949
|
+
...(tags !== undefined ? { tags } : {}),
|
|
950
|
+
...(comment !== undefined ? { comment } : {}),
|
|
951
|
+
...(icon !== undefined ? { icon } : {}),
|
|
952
|
+
...(color !== undefined ? { color } : {}),
|
|
953
|
+
};
|
|
954
|
+
if (merge.name === undefined &&
|
|
955
|
+
merge.displayKey === undefined &&
|
|
956
|
+
merge.addProperties.length === 0 &&
|
|
957
|
+
merge.removeProperties.length === 0 &&
|
|
958
|
+
merge.tags === undefined &&
|
|
959
|
+
merge.comment === undefined &&
|
|
960
|
+
merge.icon === undefined &&
|
|
961
|
+
merge.color === undefined) {
|
|
962
|
+
throw new Error("No update fields. Use --name, --display-key, --add-property (new), --update-property (same as add; replaces by name), --remove-property, --tags, --comment, --icon, --color, or pass a full JSON object as the third argument.");
|
|
760
963
|
}
|
|
761
964
|
if (!businessDomain)
|
|
762
965
|
businessDomain = resolveBusinessDomain();
|
|
763
|
-
return { knId, otId,
|
|
966
|
+
return { mode: "merge", knId, otId, merge, businessDomain, pretty, branch };
|
|
764
967
|
}
|
|
765
968
|
/** Parse object-type delete args: <kn-id> <ot-ids> [-y] */
|
|
766
969
|
function parseObjectTypeDeleteArgs(args) {
|
|
@@ -908,14 +1111,15 @@ async function runKnObjectTypeCommand(args) {
|
|
|
908
1111
|
console.log(`kweaver bkn object-type list <kn-id> [--pretty] [-bd value]
|
|
909
1112
|
kweaver bkn object-type get <kn-id> <ot-id> [--pretty] [-bd value]
|
|
910
1113
|
kweaver bkn object-type create <kn-id> --name X --dataview-id Y --primary-key Z --display-key W [--property '<json>' ...]
|
|
911
|
-
kweaver bkn object-type update <kn-id> <ot-id> [--name X] [--display-key Y]
|
|
1114
|
+
kweaver bkn object-type update <kn-id> <ot-id> [--name X] [--display-key Y] [--add-property|--update-property '<json>' ...] [--remove-property N ...] [--tags '["a","b"]'] [--comment S] [--icon I] [--color C] [--branch main]
|
|
1115
|
+
kweaver bkn object-type update <kn-id> <ot-id> '<full-json-body>'
|
|
912
1116
|
kweaver bkn object-type delete <kn-id> <ot-ids> [-y]
|
|
913
1117
|
kweaver bkn object-type query <kn-id> <ot-id> ['<json>'] [--limit <n>] [--search-after '<json-array>'] [--pretty] [-bd value]
|
|
914
1118
|
kweaver bkn object-type properties <kn-id> <ot-id> '<json>' [--pretty] [-bd value]
|
|
915
1119
|
|
|
916
1120
|
list: List object types (schema) from ontology-manager.
|
|
917
1121
|
get: Get single object type details.
|
|
918
|
-
create/update/delete: Schema CRUD (create requires dataview-id).
|
|
1122
|
+
create/update/delete: Schema CRUD (create requires dataview-id). update: merge flags (--add-property / --update-property / --remove-property, etc.) GET-merge-PUT; or full JSON as third arg.
|
|
919
1123
|
query: Query via ontology-query API. Default limit is 30 if not specified. Use --search-after for pagination.
|
|
920
1124
|
properties: Query instance properties by primary key.
|
|
921
1125
|
|
|
@@ -958,12 +1162,37 @@ properties JSON format: {"_instance_identities":[{"<primary-key>":"<value>"}],"p
|
|
|
958
1162
|
if (action === "update") {
|
|
959
1163
|
const opts = parseObjectTypeUpdateArgs(rest);
|
|
960
1164
|
const token = await ensureValidToken();
|
|
1165
|
+
let putBody;
|
|
1166
|
+
if (opts.mode === "body") {
|
|
1167
|
+
putBody = opts.body;
|
|
1168
|
+
}
|
|
1169
|
+
else {
|
|
1170
|
+
const raw = await getObjectType({
|
|
1171
|
+
baseUrl: token.baseUrl,
|
|
1172
|
+
accessToken: token.accessToken,
|
|
1173
|
+
knId: opts.knId,
|
|
1174
|
+
otId: opts.otId,
|
|
1175
|
+
businessDomain: opts.businessDomain,
|
|
1176
|
+
branch: opts.branch,
|
|
1177
|
+
});
|
|
1178
|
+
const parsed = JSON.parse(raw);
|
|
1179
|
+
const entryUnknown = parsed.entries;
|
|
1180
|
+
const entry = Array.isArray(entryUnknown) && entryUnknown.length > 0 && entryUnknown[0] && typeof entryUnknown[0] === "object"
|
|
1181
|
+
? entryUnknown[0]
|
|
1182
|
+
: parsed;
|
|
1183
|
+
if (!entry || typeof entry !== "object") {
|
|
1184
|
+
throw new Error("Unexpected object-type GET response shape.");
|
|
1185
|
+
}
|
|
1186
|
+
const stripped = stripObjectTypeForPut(entry);
|
|
1187
|
+
applyObjectTypeMerge(stripped, opts.merge);
|
|
1188
|
+
putBody = JSON.stringify(stripped);
|
|
1189
|
+
}
|
|
961
1190
|
const body = await updateObjectType({
|
|
962
1191
|
baseUrl: token.baseUrl,
|
|
963
1192
|
accessToken: token.accessToken,
|
|
964
1193
|
knId: opts.knId,
|
|
965
1194
|
otId: opts.otId,
|
|
966
|
-
body:
|
|
1195
|
+
body: putBody,
|
|
967
1196
|
businessDomain: opts.businessDomain,
|
|
968
1197
|
});
|
|
969
1198
|
console.log(formatCallOutput(body, opts.pretty));
|
|
@@ -1017,11 +1246,7 @@ properties JSON format: {"_instance_identities":[{"<primary-key>":"<value>"}],"p
|
|
|
1017
1246
|
body: options.body,
|
|
1018
1247
|
businessDomain: options.businessDomain,
|
|
1019
1248
|
});
|
|
1020
|
-
|
|
1021
|
-
if (result.length > OUTPUT_WARN_BYTES) {
|
|
1022
|
-
console.error(`[warn] Response is ${(result.length / 1024).toFixed(0)}KB. Use a smaller --limit or --search-after to paginate.`);
|
|
1023
|
-
}
|
|
1024
|
-
console.log(formatCallOutput(result, options.pretty));
|
|
1249
|
+
console.log(formatCallOutput(truncateQueryResult(result), options.pretty));
|
|
1025
1250
|
return 0;
|
|
1026
1251
|
}
|
|
1027
1252
|
if (action === "properties") {
|
|
@@ -1050,7 +1275,8 @@ JSON: {"_instance_identities":[{"<primary-key>":"<value>"}],"properties":["prop1
|
|
|
1050
1275
|
catch (error) {
|
|
1051
1276
|
if (error instanceof Error && error.message === "help") {
|
|
1052
1277
|
console.log(`kweaver bkn object-type create <kn-id> --name X --dataview-id Y --primary-key Z --display-key W [--property '<json>' ...]
|
|
1053
|
-
kweaver bkn object-type update <kn-id> <ot-id> [--name X] [--display-key Y]
|
|
1278
|
+
kweaver bkn object-type update <kn-id> <ot-id> [--name X] [--display-key Y] [--add-property|--update-property '<json>' ...] [--remove-property N ...] [--tags '["a"]'] [--comment S] [--icon I] [--color C] [--branch main]
|
|
1279
|
+
kweaver bkn object-type update <kn-id> <ot-id> '<full-json-body>'
|
|
1054
1280
|
kweaver bkn object-type delete <kn-id> <ot-ids> [-y]`);
|
|
1055
1281
|
return 0;
|
|
1056
1282
|
}
|
package/dist/commands/call.js
CHANGED
|
@@ -125,7 +125,7 @@ Options:
|
|
|
125
125
|
<url> API path (e.g. /api/ontology-manager/v1/knowledge-networks)
|
|
126
126
|
-X, --request HTTP method (default: GET)
|
|
127
127
|
-H, --header Extra header (repeatable)
|
|
128
|
-
-d, --data
|
|
128
|
+
-d, --data, --data-raw JSON request body (sets Content-Type: application/json if not set)
|
|
129
129
|
-bd, --biz-domain Override x-business-domain (default: bd_public)
|
|
130
130
|
-v, --verbose Print request info to stderr
|
|
131
131
|
--pretty Pretty-print JSON output (default)`);
|
|
@@ -147,6 +147,12 @@ Options:
|
|
|
147
147
|
: invocation.url;
|
|
148
148
|
const headers = new Headers(invocation.headers);
|
|
149
149
|
injectAuthHeaders(headers, token.accessToken, invocation.businessDomain);
|
|
150
|
+
if (invocation.body !== undefined &&
|
|
151
|
+
invocation.body.length > 0 &&
|
|
152
|
+
!headers.has("content-type") &&
|
|
153
|
+
!headers.has("Content-Type")) {
|
|
154
|
+
headers.set("content-type", "application/json");
|
|
155
|
+
}
|
|
150
156
|
if (invocation.verbose) {
|
|
151
157
|
for (const line of formatVerboseRequest({ ...invocation, url, headers })) {
|
|
152
158
|
console.error(line);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kweaver-ai/kweaver-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "KWeaver TypeScript SDK — CLI tool and programmatic API for knowledge networks and Decision Agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"start": "node ./dist/cli.js",
|
|
29
29
|
"lint": "tsc --noEmit -p tsconfig.json",
|
|
30
30
|
"test": "node --import tsx --test test/*.test.ts",
|
|
31
|
-
"test:e2e": "node --import tsx --test test/e2e/**/*.test.ts",
|
|
31
|
+
"test:e2e": "node --import tsx test/e2e/ensure-token.ts && node --import tsx --test --test-concurrency=1 test/e2e/**/*.test.ts",
|
|
32
32
|
"prepublishOnly": "npm run build"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|