@kweaver-ai/kweaver-sdk 0.4.5 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/oauth.d.ts +16 -0
- package/dist/auth/oauth.js +159 -1
- package/dist/commands/auth.js +23 -3
- package/dist/commands/bkn.js +10 -2
- package/package.json +1 -1
package/dist/auth/oauth.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { type TokenConfig } from "../config/store.js";
|
|
2
2
|
export declare function normalizeBaseUrl(value: string): string;
|
|
3
|
+
/**
|
|
4
|
+
* OAuth2 Authorization Code login flow.
|
|
5
|
+
* 1. Register client (if not already registered)
|
|
6
|
+
* 2. Open browser to /oauth2/auth
|
|
7
|
+
* 3. Receive authorization code via local HTTP callback
|
|
8
|
+
* 4. Exchange code for access_token + refresh_token
|
|
9
|
+
* 5. Save token.json + client.json to ~/.kweaver/
|
|
10
|
+
*/
|
|
11
|
+
export declare function oauth2Login(baseUrl: string, options?: {
|
|
12
|
+
port?: number;
|
|
13
|
+
scope?: string;
|
|
14
|
+
}): Promise<TokenConfig>;
|
|
15
|
+
/**
|
|
16
|
+
* Playwright cookie login (legacy fallback).
|
|
17
|
+
* Does NOT produce a refresh_token — token expires in 1 hour with no auto-refresh.
|
|
18
|
+
*/
|
|
3
19
|
export declare function playwrightLogin(baseUrl: string, options?: {
|
|
4
20
|
username?: string;
|
|
5
21
|
password?: string;
|
package/dist/auth/oauth.js
CHANGED
|
@@ -1,11 +1,169 @@
|
|
|
1
|
-
import { getCurrentPlatform, loadClientConfig, loadTokenConfig, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
|
|
1
|
+
import { getCurrentPlatform, loadClientConfig, loadTokenConfig, saveClientConfig, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
|
|
2
2
|
import { HttpError, NetworkRequestError } from "../utils/http.js";
|
|
3
3
|
const TOKEN_TTL_SECONDS = 3600;
|
|
4
4
|
/** Seconds before access token expiry to trigger refresh (matches Python ConfigAuth). */
|
|
5
5
|
const REFRESH_THRESHOLD_SEC = 60;
|
|
6
|
+
const DEFAULT_REDIRECT_PORT = 9010;
|
|
7
|
+
const DEFAULT_SCOPE = "openid offline all";
|
|
6
8
|
export function normalizeBaseUrl(value) {
|
|
7
9
|
return value.replace(/\/+$/, "");
|
|
8
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* OAuth2 Authorization Code login flow.
|
|
13
|
+
* 1. Register client (if not already registered)
|
|
14
|
+
* 2. Open browser to /oauth2/auth
|
|
15
|
+
* 3. Receive authorization code via local HTTP callback
|
|
16
|
+
* 4. Exchange code for access_token + refresh_token
|
|
17
|
+
* 5. Save token.json + client.json to ~/.kweaver/
|
|
18
|
+
*/
|
|
19
|
+
export async function oauth2Login(baseUrl, options) {
|
|
20
|
+
const { createServer } = await import("node:http");
|
|
21
|
+
const { randomBytes } = await import("node:crypto");
|
|
22
|
+
const base = normalizeBaseUrl(baseUrl);
|
|
23
|
+
const port = options?.port ?? DEFAULT_REDIRECT_PORT;
|
|
24
|
+
const scope = options?.scope ?? DEFAULT_SCOPE;
|
|
25
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
26
|
+
// Step 1: Ensure registered client
|
|
27
|
+
let client = loadClientConfig(base);
|
|
28
|
+
if (!client?.clientId) {
|
|
29
|
+
client = await registerOAuth2Client(base, redirectUri, scope);
|
|
30
|
+
saveClientConfig(base, client);
|
|
31
|
+
}
|
|
32
|
+
// Step 2: Generate CSRF state
|
|
33
|
+
const state = randomBytes(12).toString("hex");
|
|
34
|
+
// Step 3: Build authorization URL
|
|
35
|
+
const authParams = new URLSearchParams({
|
|
36
|
+
redirect_uri: redirectUri,
|
|
37
|
+
"x-forwarded-prefix": "",
|
|
38
|
+
client_id: client.clientId,
|
|
39
|
+
scope,
|
|
40
|
+
response_type: "code",
|
|
41
|
+
state,
|
|
42
|
+
lang: "zh-cn",
|
|
43
|
+
product: "adp",
|
|
44
|
+
});
|
|
45
|
+
const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
|
|
46
|
+
// Step 4: Start local callback server, wait for code
|
|
47
|
+
const code = await new Promise((resolve, reject) => {
|
|
48
|
+
const timeoutId = setTimeout(() => {
|
|
49
|
+
server.close();
|
|
50
|
+
reject(new Error("OAuth2 login timed out (120s). No authorization code received."));
|
|
51
|
+
}, 120_000);
|
|
52
|
+
const server = createServer((req, res) => {
|
|
53
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
54
|
+
if (url.pathname === "/callback") {
|
|
55
|
+
const receivedState = url.searchParams.get("state");
|
|
56
|
+
const receivedCode = url.searchParams.get("code");
|
|
57
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
58
|
+
res.end("<html><body><h2>Login successful. You can close this tab.</h2></body></html>");
|
|
59
|
+
clearTimeout(timeoutId);
|
|
60
|
+
server.close();
|
|
61
|
+
if (receivedState !== state) {
|
|
62
|
+
reject(new Error("OAuth2 state mismatch — possible CSRF attack."));
|
|
63
|
+
}
|
|
64
|
+
else if (!receivedCode) {
|
|
65
|
+
reject(new Error("No authorization code received in callback."));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
resolve(receivedCode);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
res.writeHead(404);
|
|
73
|
+
res.end();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
server.listen(port, "127.0.0.1", () => {
|
|
77
|
+
// Step 5: Open browser
|
|
78
|
+
import("node:child_process").then(({ exec }) => {
|
|
79
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
80
|
+
exec(`${cmd} "${authUrl}"`);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// Step 6: Exchange code for tokens
|
|
85
|
+
const token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri);
|
|
86
|
+
setCurrentPlatform(base);
|
|
87
|
+
return token;
|
|
88
|
+
}
|
|
89
|
+
async function registerOAuth2Client(baseUrl, redirectUri, scope) {
|
|
90
|
+
const logoutUri = redirectUri.replace("/callback", "/successful-logout");
|
|
91
|
+
const response = await fetch(`${baseUrl}/oauth2/clients`, {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
94
|
+
body: JSON.stringify({
|
|
95
|
+
client_name: "kweaver-sdk",
|
|
96
|
+
grant_types: ["authorization_code", "implicit", "refresh_token"],
|
|
97
|
+
response_types: ["token id_token", "code", "token"],
|
|
98
|
+
scope: "openid offline all",
|
|
99
|
+
redirect_uris: [redirectUri],
|
|
100
|
+
post_logout_redirect_uris: [logoutUri],
|
|
101
|
+
metadata: {
|
|
102
|
+
device: {
|
|
103
|
+
name: "kweaver-sdk",
|
|
104
|
+
client_type: "web",
|
|
105
|
+
description: "KWeaver TypeScript SDK",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
const text = await response.text();
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new HttpError(response.status, response.statusText, text);
|
|
113
|
+
}
|
|
114
|
+
const data = JSON.parse(text);
|
|
115
|
+
return {
|
|
116
|
+
baseUrl,
|
|
117
|
+
clientId: data.client_id,
|
|
118
|
+
clientSecret: data.client_secret,
|
|
119
|
+
redirectUri,
|
|
120
|
+
logoutRedirectUri: logoutUri,
|
|
121
|
+
scope,
|
|
122
|
+
lang: "zh-cn",
|
|
123
|
+
product: "adp",
|
|
124
|
+
xForwardedPrefix: "",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
async function exchangeCodeForToken(baseUrl, code, clientId, clientSecret, redirectUri) {
|
|
128
|
+
const credentials = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
|
129
|
+
const response = await fetch(`${baseUrl}/oauth2/token`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: {
|
|
132
|
+
Authorization: `Basic ${credentials}`,
|
|
133
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
134
|
+
Accept: "application/json",
|
|
135
|
+
},
|
|
136
|
+
body: new URLSearchParams({
|
|
137
|
+
grant_type: "authorization_code",
|
|
138
|
+
code,
|
|
139
|
+
redirect_uri: redirectUri,
|
|
140
|
+
}).toString(),
|
|
141
|
+
});
|
|
142
|
+
const text = await response.text();
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
throw new HttpError(response.status, response.statusText, text);
|
|
145
|
+
}
|
|
146
|
+
const data = JSON.parse(text);
|
|
147
|
+
const now = new Date();
|
|
148
|
+
const expiresIn = typeof data.expires_in === "number" ? data.expires_in : 3600;
|
|
149
|
+
const token = {
|
|
150
|
+
baseUrl,
|
|
151
|
+
accessToken: data.access_token,
|
|
152
|
+
tokenType: data.token_type ?? "Bearer",
|
|
153
|
+
scope: data.scope ?? "",
|
|
154
|
+
expiresIn,
|
|
155
|
+
expiresAt: new Date(now.getTime() + expiresIn * 1000).toISOString(),
|
|
156
|
+
refreshToken: data.refresh_token ?? "",
|
|
157
|
+
idToken: data.id_token ?? "",
|
|
158
|
+
obtainedAt: now.toISOString(),
|
|
159
|
+
};
|
|
160
|
+
saveTokenConfig(token);
|
|
161
|
+
return token;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Playwright cookie login (legacy fallback).
|
|
165
|
+
* Does NOT produce a refresh_token — token expires in 1 hour with no auto-refresh.
|
|
166
|
+
*/
|
|
9
167
|
export async function playwrightLogin(baseUrl, options) {
|
|
10
168
|
let chromium;
|
|
11
169
|
try {
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clearPlatformSession, deletePlatform, getConfigDir, getCurrentPlatform, getPlatformAlias, hasPlatform, listPlatforms, loadTokenConfig, resolvePlatformIdentifier, setCurrentPlatform, setPlatformAlias, } from "../config/store.js";
|
|
2
|
-
import { formatHttpError, normalizeBaseUrl, playwrightLogin, } from "../auth/oauth.js";
|
|
2
|
+
import { formatHttpError, normalizeBaseUrl, oauth2Login, playwrightLogin, } from "../auth/oauth.js";
|
|
3
3
|
export async function runAuthCommand(args) {
|
|
4
4
|
const target = args[0];
|
|
5
5
|
const rest = args.slice(1);
|
|
@@ -27,13 +27,23 @@ kweaver auth delete <url> Delete saved credentials`);
|
|
|
27
27
|
const alias = readOption(args, "--alias");
|
|
28
28
|
const username = readOption(args, "--username") ?? readOption(args, "-u");
|
|
29
29
|
const password = readOption(args, "--password") ?? readOption(args, "-p");
|
|
30
|
+
const usePlaywright = args.includes("--playwright");
|
|
31
|
+
let token;
|
|
30
32
|
if (username && password) {
|
|
33
|
+
// Headless Playwright login with credentials
|
|
31
34
|
console.log("Logging in (headless)...");
|
|
35
|
+
token = await playwrightLogin(normalizedTarget, { username, password });
|
|
36
|
+
}
|
|
37
|
+
else if (usePlaywright) {
|
|
38
|
+
// Explicit Playwright fallback
|
|
39
|
+
console.log("Opening browser for login (Playwright)...");
|
|
40
|
+
token = await playwrightLogin(normalizedTarget);
|
|
32
41
|
}
|
|
33
42
|
else {
|
|
34
|
-
|
|
43
|
+
// Default: OAuth2 authorization code flow (supports refresh_token)
|
|
44
|
+
console.log("Opening browser for OAuth2 login...");
|
|
45
|
+
token = await oauth2Login(normalizedTarget);
|
|
35
46
|
}
|
|
36
|
-
const token = await playwrightLogin(normalizedTarget, username && password ? { username, password } : undefined);
|
|
37
47
|
if (alias) {
|
|
38
48
|
setPlatformAlias(normalizedTarget, alias);
|
|
39
49
|
}
|
|
@@ -49,6 +59,12 @@ kweaver auth delete <url> Delete saved credentials`);
|
|
|
49
59
|
}
|
|
50
60
|
console.log(`Current platform: ${normalizedTarget}`);
|
|
51
61
|
console.log(`Access token saved: yes`);
|
|
62
|
+
if (token.refreshToken) {
|
|
63
|
+
console.log(`Refresh token: yes (auto-refresh enabled)`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log(`Refresh token: no (token will expire in 1 hour)`);
|
|
67
|
+
}
|
|
52
68
|
if (token.expiresAt) {
|
|
53
69
|
console.log(`Token expires at: ${token.expiresAt}`);
|
|
54
70
|
}
|
|
@@ -79,6 +95,7 @@ kweaver auth delete <url> Delete saved credentials`);
|
|
|
79
95
|
`Current platform: ${token.baseUrl === currentPlatform ? "yes" : "no"}`,
|
|
80
96
|
`Token present: yes`,
|
|
81
97
|
];
|
|
98
|
+
lines.push(`Refresh token: ${token.refreshToken ? "yes (auto-refresh enabled)" : "no"}`);
|
|
82
99
|
if (token.expiresAt) {
|
|
83
100
|
const expiry = new Date(token.expiresAt);
|
|
84
101
|
const remainingMs = expiry.getTime() - Date.now();
|
|
@@ -86,6 +103,9 @@ kweaver auth delete <url> Delete saved credentials`);
|
|
|
86
103
|
const remainingMin = Math.ceil(remainingMs / 60_000);
|
|
87
104
|
lines.push(`Token status: active (expires in ${remainingMin} min)`);
|
|
88
105
|
}
|
|
106
|
+
else if (token.refreshToken) {
|
|
107
|
+
lines.push(`Token status: expired (will auto-refresh on next command)`);
|
|
108
|
+
}
|
|
89
109
|
else {
|
|
90
110
|
lines.push(`Token status: expired (run \`kweaver auth login ${token.baseUrl}\` again)`);
|
|
91
111
|
}
|
package/dist/commands/bkn.js
CHANGED
|
@@ -528,7 +528,7 @@ export function parseKnObjectTypeQueryArgs(args) {
|
|
|
528
528
|
body.search_after = searchAfter;
|
|
529
529
|
}
|
|
530
530
|
if (typeof body.limit !== "number" || !Number.isFinite(body.limit) || body.limit < 1) {
|
|
531
|
-
|
|
531
|
+
body.limit = 30;
|
|
532
532
|
}
|
|
533
533
|
if (!businessDomain)
|
|
534
534
|
businessDomain = resolveBusinessDomain();
|
|
@@ -916,7 +916,8 @@ kweaver bkn object-type properties <kn-id> <ot-id> '<json>' [--pretty] [-bd valu
|
|
|
916
916
|
list: List object types (schema) from ontology-manager.
|
|
917
917
|
get: Get single object type details.
|
|
918
918
|
create/update/delete: Schema CRUD (create requires dataview-id).
|
|
919
|
-
query
|
|
919
|
+
query: Query via ontology-query API. Default limit is 30 if not specified. Use --search-after for pagination.
|
|
920
|
+
properties: Query instance properties by primary key.
|
|
920
921
|
|
|
921
922
|
properties JSON format: {"_instance_identities":[{"<primary-key>":"<value>"}],"properties":["prop1","prop2"]}`);
|
|
922
923
|
return 0;
|
|
@@ -1016,6 +1017,10 @@ properties JSON format: {"_instance_identities":[{"<primary-key>":"<value>"}],"p
|
|
|
1016
1017
|
body: options.body,
|
|
1017
1018
|
businessDomain: options.businessDomain,
|
|
1018
1019
|
});
|
|
1020
|
+
const OUTPUT_WARN_BYTES = 100_000;
|
|
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
|
+
}
|
|
1019
1024
|
console.log(formatCallOutput(result, options.pretty));
|
|
1020
1025
|
return 0;
|
|
1021
1026
|
}
|
|
@@ -1343,6 +1348,9 @@ Query subgraph via ontology-query API. JSON body format see references/json-form
|
|
|
1343
1348
|
businessDomain,
|
|
1344
1349
|
queryType,
|
|
1345
1350
|
});
|
|
1351
|
+
if (result.length > 100_000) {
|
|
1352
|
+
console.error(`[warn] Response is ${(result.length / 1024).toFixed(0)}KB. Consider narrowing the subgraph query.`);
|
|
1353
|
+
}
|
|
1346
1354
|
console.log(formatCallOutput(result, pretty));
|
|
1347
1355
|
return 0;
|
|
1348
1356
|
}
|
package/package.json
CHANGED