@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.
@@ -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;
@@ -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 {
@@ -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
- console.log("Opening browser for login...");
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
  }
@@ -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
- throw new Error("Missing limit. Provide it in body JSON or via --limit <n>.");
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/properties: Query via ontology-query API. For query, --limit and --search-after are merged into the JSON body.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kweaver-ai/kweaver-sdk",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
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",