@kweaver-ai/kweaver-sdk 0.6.3 → 0.6.4

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,8 @@ export KWEAVER_BASE_URL=https://your-kweaver-instance.com
31
31
  export KWEAVER_TOKEN=your-token
32
32
  ```
33
33
 
34
+ With both set, API commands use that token even if you never ran `auth login`. You can also run **`kweaver auth status`**, **`kweaver auth whoami`** (supports `--json`), and **`kweaver config show`** when there is **no** current platform in `~/.kweaver/` — the CLI decodes the token locally (JWT only). If the token is opaque, identity fields are omitted and a short hint is printed.
35
+
34
36
  ### Business domain (platform)
35
37
 
36
38
  Set or verify **before** calling list/query APIs that scope by tenant. DIP deployments often need a UUID, not only `bd_public`.
@@ -151,11 +153,13 @@ const skillMd = await client.skills.fetchContent("skill-id");
151
153
  ## CLI Reference
152
154
 
153
155
  ```
154
- kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
156
+ kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--playwright] [--insecure|-k]
157
+ # -u/-p: tries HTTP /oauth2/signin first (refresh_token). If studioweb is missing: falls back to Playwright when installed, else prints install hint. --http-signin: HTTP only. --playwright: force browser automation.
155
158
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (headless login)
156
159
  kweaver auth export [url|alias] [--json] (export command to run on a headless host)
157
- kweaver auth status/list/use/delete/logout
158
- kweaver config show / list-bd / set-bd <value> # platform business domain — after login
160
+ kweaver auth status / whoami [url|alias] [--json] # whoami: --json; with KWEAVER_BASE_URL+KWEAVER_TOKEN when no ~/.kweaver/ platform
161
+ kweaver auth list/use/delete/logout
162
+ kweaver config show / list-bd / set-bd <value> # platform business domain — show/list-bd work with KWEAVER_BASE_URL (+ KWEAVER_TOKEN for list-bd)
159
163
  kweaver token
160
164
  kweaver ds list/get/delete/tables/connect
161
165
  kweaver ds import-csv <ds_id> --files <glob> [--table-prefix <p>] [--batch-size 500] [--recreate]
package/README.zh.md CHANGED
@@ -31,6 +31,8 @@ export KWEAVER_BASE_URL=https://your-kweaver-instance.com
31
31
  export KWEAVER_TOKEN=your-token
32
32
  ```
33
33
 
34
+ 两者同时设置时,即使未执行 `auth login`,业务命令也会使用该 token。若 **`~/.kweaver/` 无当前平台**,仍可使用 **`kweaver auth status`**、**`kweaver auth whoami`**(支持 `--json`)、**`kweaver config show`**:CLI 会在本地解 JWT 展示身份;若 token 为 opaque,则省略身份字段并给出简短提示。
35
+
34
36
  ### 业务域(平台配置)
35
37
 
36
38
  在调用依赖租户范围的接口前,应先确认业务域;DIP 环境通常使用 **UUID**,不能长期只依赖默认 `bd_public`。
@@ -144,11 +146,13 @@ const skillMd = await client.skills.fetchContent("skill-id");
144
146
  ## 命令速查
145
147
 
146
148
  ```
147
- kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
149
+ kweaver auth login <url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--playwright] [--insecure|-k]
150
+ # -u/-p:默认先试 HTTP /oauth2/signin(可拿 refresh_token);无 studioweb 时:已装 Playwright 则回退无头浏览器,否则提示安装 Playwright;--http-signin 仅 HTTP;--playwright 强制浏览器
148
151
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (无浏览器登录)
149
152
  kweaver auth export [url|alias] [--json] (导出在无浏览器机器上运行的命令)
150
- kweaver auth status/list/use/delete/logout
151
- kweaver config show / list-bd / set-bd <value> # 平台业务域,登录后优先
153
+ kweaver auth status / whoami [url|alias] [--json] # whoami 支持 --json;无 ~/.kweaver/ 当前平台时可配 KWEAVER_BASE_URL+KWEAVER_TOKEN
154
+ kweaver auth list/use/delete/logout
155
+ kweaver config show / list-bd / set-bd <value> # 业务域;show/list-bd 在无已保存平台时可与 env 配对
152
156
  kweaver token
153
157
  kweaver ds list/get/delete/tables/connect
154
158
  kweaver dataflow list/run/runs/logs
@@ -82,7 +82,10 @@ export async function pollDataflowResults(options) {
82
82
  return latest;
83
83
  }
84
84
  if (latest.status === "failed" || latest.status === "error") {
85
- const reason = latest.reason ? `: ${latest.reason}` : "";
85
+ const reasonVal = latest.reason;
86
+ const reason = reasonVal
87
+ ? `: ${typeof reasonVal === "string" ? reasonVal : JSON.stringify(reasonVal)}`
88
+ : "";
86
89
  throw new Error(`Dataflow run ${latest.status}${reason}`);
87
90
  }
88
91
  }
@@ -1,4 +1,19 @@
1
1
  import { type TokenConfig } from "../config/store.js";
2
+ /**
3
+ * Studioweb hardcoded LOGIN public key (PEM) — the single key used for HTTP `/oauth2/signin`.
4
+ * Source: kweaver-ai/kweaver `deploy/auto_cofig/auto_config.sh` `LOGIN_PUBLIC_KEY`.
5
+ */
6
+ export declare const STUDIOWEB_LOGIN_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyOstgbYuubBi2PUqeVj\nGKlkwVUY6w1Y8d4k116dI2SkZI8fxcjHALv77kItO4jYLVplk9gO4HAtsisnNE2o\nwlYIqdmyEPMwupaeFFFcg751oiTXJiYbtX7ABzU5KQYPjRSEjMq6i5qu/mL67XTk\nhvKwrC83zme66qaKApmKupDODPb0RRkutK/zHfd1zL7sciBQ6psnNadh8pE24w8O\n2XVy1v2bgSNkGHABgncR7seyIg81JQ3c/Axxd6GsTztjLnlvGAlmT1TphE84mi99\nfUaGD2A1u1qdIuNc+XuisFeNcUW6fct0+x97eS2eEGRr/7qxWmO/P20sFVzXc2bF\n1QIDAQAB\n-----END PUBLIC KEY-----";
7
+ /**
8
+ * Default RSA modulus (hex) for `/oauth2/signin` when `__NEXT_DATA__` has no `publicKey` / `modulus`.
9
+ * DIP / EACP / AnyShare-style deployments use the ISFWeb `core/auth` PUBLIC_KEY (1024-bit, exp 65537).
10
+ * Prefer key material from the sign-in page when present.
11
+ */
12
+ export declare const DEFAULT_SIGNIN_RSA_MODULUS_HEX = "C1D9F84B95AF6B331FBA2D64D76A39CAD7529DA79DB4B3543E4DF3DF21723FEC6F7E2F6602E11037339AE0462DF6B39F94150FC256A505A8CA95BB3699E25C3FB84764D6A1DC3F483A2C1DC4F70925D85725151D0CFBF1EB5A6C4FA0E37ED32FED150C717CD82C528745CDB761D17635AC855421B3CBBEE7D405B2CA5C70CFA7";
13
+ /**
14
+ * Build an SPKI PEM from an RSA modulus (hex) and public exponent (default 65537 / 0x10001).
15
+ */
16
+ export declare function rsaModulusHexToSpkiPem(modulusHex: string, exponent?: number): string;
2
17
  /** POSIX shell single-quote escaping for copy-paste commands. */
3
18
  export declare function shellQuoteForShell(value: string): string;
4
19
  /**
@@ -11,6 +26,12 @@ export declare function buildCopyCommand(baseUrl: string, clientId: string, clie
11
26
  */
12
27
  export declare function buildCallbackHtml(copyCommand: string): string;
13
28
  export declare function normalizeBaseUrl(value: string): string;
29
+ /**
30
+ * Temporarily disable TLS certificate verification for Node `fetch` (sets
31
+ * NODE_TLS_REJECT_UNAUTHORIZED). Used for `--insecure` login and token refresh.
32
+ */
33
+ /** @internal Exported for CLI env-only identity resolution (`env-snapshot.ts`). */
34
+ export declare function runWithTlsInsecure<T>(tlsInsecure: boolean | undefined, fn: () => Promise<T>): Promise<T>;
14
35
  /**
15
36
  * OAuth2 Authorization Code login flow.
16
37
  * 1. Register client (if not already registered), OR use a provided client ID
@@ -50,6 +71,54 @@ export declare function playwrightLogin(baseUrl: string, options?: {
50
71
  scope?: string;
51
72
  tlsInsecure?: boolean;
52
73
  }): Promise<TokenConfig>;
74
+ /**
75
+ * Parse Next.js `__NEXT_DATA__` from the OAuth2 sign-in HTML shell (CSRF + optional challenge/remember for POST /oauth2/signin).
76
+ * Hydra `login_challenge` may appear only in the sign-in URL; use that when `pageProps.challenge` is absent.
77
+ */
78
+ export declare function parseSigninPageHtmlProps(html: string): {
79
+ challenge?: string;
80
+ csrftoken: string;
81
+ remember?: boolean;
82
+ /** Hex modulus, PEM, or Base64 SPKI from page (nested search + HTML regex fallback). */
83
+ rsaPublicKeyMaterial?: string;
84
+ };
85
+ /**
86
+ * True when {@link oauth2PasswordSigninLogin} failed because the Studio web sign-in shell
87
+ * (`/interface/studioweb/login`) is missing or unreachable — callers may fall back to Playwright.
88
+ */
89
+ export declare function isStudiowebShellUnavailableError(err: unknown): boolean;
90
+ /**
91
+ * OAuth2 Authorization Code login using HTTP **only**: `GET /oauth2/signin` (Next.js shell) and
92
+ * `POST /oauth2/signin` with an RSA PKCS#1 v1.5–encrypted password (same as the browser `rsa.min` / Studio
93
+ * `core/mediator/auth` path).
94
+ *
95
+ * `/oauth2/auth` uses `product` `adp` by default (KWeaver Studio shell); set `oauthProduct` or `KWEAVER_OAUTH_PRODUCT` for DIP (`dip`).
96
+ * Password ciphertext defaults to **single-line base64** (PyCrypto-style); set `KWEAVER_SIGNIN_PASSWORD_B64_RSA_MIN=1` for rsa.min-style wrapped lines.
97
+ */
98
+ export declare function oauth2PasswordSigninLogin(baseUrl: string, options: {
99
+ username: string;
100
+ password: string;
101
+ port?: number;
102
+ scope?: string;
103
+ clientId?: string;
104
+ clientSecret?: string;
105
+ tlsInsecure?: boolean;
106
+ /**
107
+ * `product` query for `/oauth2/auth` (must match deployment). Default `adp`; DIP deployments often use `dip`.
108
+ * @default KWEAVER_OAUTH_PRODUCT env or `adp`
109
+ */
110
+ oauthProduct?: string;
111
+ /**
112
+ * Password ciphertext: `rsa.min` uses newline every 64 chars; PyCrypto / some gateways expect a single base64 line.
113
+ * @default false (single-line base64, matches kweaver-core EACP-style encryption)
114
+ */
115
+ signinPasswordBase64Plain?: boolean;
116
+ /**
117
+ * PEM / hex / Base64-SPKI file path — overrides key from the sign-in HTML.
118
+ * Env: `KWEAVER_SIGNIN_RSA_PUBLIC_KEY` (same path semantics as CLI `--signin-public-key-file`).
119
+ */
120
+ signinPublicKeyPemPath?: string;
121
+ }): Promise<TokenConfig>;
53
122
  /**
54
123
  * Log in on a headless machine using OAuth2 client credentials and a refresh token (no browser).
55
124
  * Exchanges the refresh token for a new access token and persists ~/.kweaver/ state.
@@ -1,3 +1,5 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { createPublicKey } from "node:crypto";
1
3
  import { isNoAuth } from "../config/no-auth.js";
2
4
  import { deleteClientConfig, getCurrentPlatform, loadClientConfig, loadTokenConfig, loadUserTokenConfig, resolveUserId, saveClientConfig, saveNoAuthPlatform, saveTokenConfig, setCurrentPlatform, } from "../config/store.js";
3
5
  import { HttpError, NetworkRequestError, fetchWithRetry } from "../utils/http.js";
@@ -6,6 +8,229 @@ const TOKEN_TTL_SECONDS = 3600;
6
8
  const REFRESH_THRESHOLD_SEC = 60;
7
9
  const DEFAULT_REDIRECT_PORT = 9010;
8
10
  const DEFAULT_SCOPE = "openid offline all";
11
+ /**
12
+ * Studioweb hardcoded LOGIN public key (PEM) — the single key used for HTTP `/oauth2/signin`.
13
+ * Source: kweaver-ai/kweaver `deploy/auto_cofig/auto_config.sh` `LOGIN_PUBLIC_KEY`.
14
+ */
15
+ export const STUDIOWEB_LOGIN_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
16
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyOstgbYuubBi2PUqeVj
17
+ GKlkwVUY6w1Y8d4k116dI2SkZI8fxcjHALv77kItO4jYLVplk9gO4HAtsisnNE2o
18
+ wlYIqdmyEPMwupaeFFFcg751oiTXJiYbtX7ABzU5KQYPjRSEjMq6i5qu/mL67XTk
19
+ hvKwrC83zme66qaKApmKupDODPb0RRkutK/zHfd1zL7sciBQ6psnNadh8pE24w8O
20
+ 2XVy1v2bgSNkGHABgncR7seyIg81JQ3c/Axxd6GsTztjLnlvGAlmT1TphE84mi99
21
+ fUaGD2A1u1qdIuNc+XuisFeNcUW6fct0+x97eS2eEGRr/7qxWmO/P20sFVzXc2bF
22
+ 1QIDAQAB
23
+ -----END PUBLIC KEY-----`;
24
+ /**
25
+ * Default RSA modulus (hex) for `/oauth2/signin` when `__NEXT_DATA__` has no `publicKey` / `modulus`.
26
+ * DIP / EACP / AnyShare-style deployments use the ISFWeb `core/auth` PUBLIC_KEY (1024-bit, exp 65537).
27
+ * Prefer key material from the sign-in page when present.
28
+ */
29
+ export const DEFAULT_SIGNIN_RSA_MODULUS_HEX = "C1D9F84B95AF6B331FBA2D64D76A39CAD7529DA79DB4B3543E4DF3DF21723FEC6F7E2F6602E11037339AE0462DF6B39F94150FC256A505A8CA95BB3699E25C3FB84764D6A1DC3F483A2C1DC4F70925D85725151D0CFBF1EB5A6C4FA0E37ED32FED150C717CD82C528745CDB761D17635AC855421B3CBBEE7D405B2CA5C70CFA7";
30
+ /**
31
+ * Default PEM for HTTP `/oauth2/signin`: **the fixed `STUDIOWEB_LOGIN_PUBLIC_KEY_PEM`** (matches
32
+ * `kweaver-ai/kweaver/deploy/auto_cofig/auto_config.sh` `LOGIN_PUBLIC_KEY`). KWeaver platforms have
33
+ * standardized on this single key — no fallback list, no probing of `__NEXT_DATA__` page key, no
34
+ * `trying next candidate…` noise. Override with `--signin-public-key-file` /
35
+ * `KWEAVER_SIGNIN_RSA_PUBLIC_KEY` if a deployment ever ships a different public key.
36
+ */
37
+ function buildHttpSigninPemCandidates(_parsedMaterial) {
38
+ return [STUDIOWEB_LOGIN_PUBLIC_KEY_PEM];
39
+ }
40
+ /**
41
+ * Build an SPKI PEM from an RSA modulus (hex) and public exponent (default 65537 / 0x10001).
42
+ */
43
+ export function rsaModulusHexToSpkiPem(modulusHex, exponent = 65537) {
44
+ const hex = modulusHex.replace(/\s+/g, "");
45
+ if (!/^[0-9a-fA-F]+$/.test(hex) || hex.length % 2 !== 0) {
46
+ throw new Error("RSA modulus must be an even-length hex string.");
47
+ }
48
+ const nBuf = Buffer.from(hex, "hex");
49
+ const eBytes = [];
50
+ let exp = exponent;
51
+ while (exp > 0) {
52
+ eBytes.unshift(exp & 0xff);
53
+ exp >>= 8;
54
+ }
55
+ const eBuf = Buffer.from(eBytes);
56
+ const key = createPublicKey({
57
+ key: {
58
+ kty: "RSA",
59
+ n: nBuf.toString("base64url"),
60
+ e: eBuf.toString("base64url"),
61
+ },
62
+ format: "jwk",
63
+ });
64
+ return key.export({ type: "spki", format: "pem" });
65
+ }
66
+ /**
67
+ * Import SPKI DER from Base64 (no PEM headers) — same shape as af-agent `RSA.importKey(base64.b64decode(...))`.
68
+ */
69
+ function tryDerSpkiBase64ToPem(material) {
70
+ const trimmed = material.replace(/\s+/g, "");
71
+ if (trimmed.length < 80 || !/^[A-Za-z0-9+/]+=*$/.test(trimmed)) {
72
+ return null;
73
+ }
74
+ try {
75
+ const buf = Buffer.from(trimmed, "base64");
76
+ const key = createPublicKey({ key: buf, format: "der", type: "spki" });
77
+ return key.export({ type: "spki", format: "pem" });
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ function isLikelyRsaHexModulusString(s) {
84
+ const h = s.replace(/\s+/g, "");
85
+ return h.length >= 128 && h.length % 2 === 0 && /^[0-9a-fA-F]+$/.test(h);
86
+ }
87
+ function isLikelySpkiBase64String(s) {
88
+ const t = s.replace(/\s+/g, "");
89
+ if (t.length < 200 || !/^[A-Za-z0-9+/]+=*$/.test(t)) {
90
+ return false;
91
+ }
92
+ return tryDerSpkiBase64ToPem(t) !== null;
93
+ }
94
+ /**
95
+ * PEM from page (`BEGIN PUBLIC KEY` / `BEGIN RSA PUBLIC KEY`), hex modulus, or Base64 SPKI DER.
96
+ * When `material` is missing, uses the built-in modulus (`DEFAULT_SIGNIN_RSA_MODULUS_HEX`) so
97
+ * `--http-signin` does not require extra CLI flags. Opt out with `KWEAVER_SIGNIN_DISALLOW_BUILTIN_MODULUS=1`.
98
+ */
99
+ function resolveSigninPublicKeyPem(material, opts) {
100
+ const disallowBuiltin = opts?.allowBuiltinModulus === false || process.env.KWEAVER_SIGNIN_DISALLOW_BUILTIN_MODULUS === "1";
101
+ if (material?.trim()) {
102
+ const m = material.trim();
103
+ if (m.includes("BEGIN PUBLIC KEY") || m.includes("BEGIN RSA PUBLIC KEY")) {
104
+ return m;
105
+ }
106
+ const hex = m.replace(/\s+/g, "");
107
+ if (/^[0-9a-fA-F]+$/.test(hex) && hex.length % 2 === 0) {
108
+ return rsaModulusHexToSpkiPem(hex);
109
+ }
110
+ const fromDer = tryDerSpkiBase64ToPem(m);
111
+ if (fromDer) {
112
+ return fromDer;
113
+ }
114
+ throw new Error("RSA public key material is present but could not be parsed (expected PEM, hex modulus, or Base64 SPKI).");
115
+ }
116
+ if (disallowBuiltin) {
117
+ throw new Error("No RSA public key in sign-in HTML and built-in modulus disabled (KWEAVER_SIGNIN_DISALLOW_BUILTIN_MODULUS=1). " +
118
+ "Use --signin-public-key-file or KWEAVER_SIGNIN_RSA_PUBLIC_KEY.");
119
+ }
120
+ return rsaModulusHexToSpkiPem(DEFAULT_SIGNIN_RSA_MODULUS_HEX.replace(/\s+/g, ""));
121
+ }
122
+ /**
123
+ * Recursively find a string that looks like PEM, hex modulus, or Base64 SPKI in `pageProps` (nested configs).
124
+ */
125
+ function deepFindSigninRsaMaterial(obj, depth, seen) {
126
+ if (depth < 0 || obj === null || obj === undefined) {
127
+ return undefined;
128
+ }
129
+ if (typeof obj === "string") {
130
+ const t = obj.trim();
131
+ if (!t) {
132
+ return undefined;
133
+ }
134
+ if (t.includes("BEGIN PUBLIC KEY") || t.includes("BEGIN RSA PUBLIC KEY")) {
135
+ return t;
136
+ }
137
+ if (isLikelyRsaHexModulusString(t)) {
138
+ return t.replace(/\s+/g, "");
139
+ }
140
+ if (isLikelySpkiBase64String(t)) {
141
+ return t.replace(/\s+/g, "");
142
+ }
143
+ return undefined;
144
+ }
145
+ if (typeof obj !== "object") {
146
+ return undefined;
147
+ }
148
+ if (seen.has(obj)) {
149
+ return undefined;
150
+ }
151
+ seen.add(obj);
152
+ if (Array.isArray(obj)) {
153
+ for (const el of obj) {
154
+ const r = deepFindSigninRsaMaterial(el, depth - 1, seen);
155
+ if (r) {
156
+ return r;
157
+ }
158
+ }
159
+ return undefined;
160
+ }
161
+ const rec = obj;
162
+ for (const k of Object.keys(rec)) {
163
+ const r = deepFindSigninRsaMaterial(rec[k], depth - 1, seen);
164
+ if (r) {
165
+ return r;
166
+ }
167
+ }
168
+ return undefined;
169
+ }
170
+ /**
171
+ * Regex fallback when JSON path differs (escaped quotes, minified bundles, inline scripts).
172
+ * Some deployments put the SPKI Base64 as a raw substring (no JSON key).
173
+ */
174
+ function extractSigninRsaMaterialFromHtml(html) {
175
+ const pemPub = html.match(/-----BEGIN PUBLIC KEY-----[\s\S]*?-----END PUBLIC KEY-----/);
176
+ if (pemPub) {
177
+ return pemPub[0].trim();
178
+ }
179
+ const pemRsa = html.match(/-----BEGIN RSA PUBLIC KEY-----[\s\S]*?-----END RSA PUBLIC KEY-----/);
180
+ if (pemRsa) {
181
+ return pemRsa[0].trim();
182
+ }
183
+ const jsonPatterns = [
184
+ /"modulus"\s*:\s*"([0-9a-fA-F]{128,})"/,
185
+ /'modulus'\s*:\s*'([0-9a-fA-F]{128,})'/,
186
+ /"(?:publicKey|rsaPublicKey|public_key|encryptPublicKey|rsaModulus|passwordPublicKey|loginPublicKey|pwdPublicKey|encryptKey)"\s*:\s*"([A-Za-z0-9+/=\s]{200,})"/,
187
+ /'(?:publicKey|rsaPublicKey|public_key)'\s*:\s*'([A-Za-z0-9+/=]{200,})'/,
188
+ ];
189
+ for (const re of jsonPatterns) {
190
+ const m = html.match(re);
191
+ if (m?.[1]) {
192
+ return m[1].replace(/\s+/g, "");
193
+ }
194
+ }
195
+ // Raw SPKI Base64 blocks (2048-bit RSA SPKI often starts with MIIBIjAN…)
196
+ const rawSpki = html.match(/(MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA[A-Za-z0-9+/=]{80,800})/);
197
+ if (rawSpki) {
198
+ return rawSpki[1].replace(/\s+/g, "");
199
+ }
200
+ const rawSpki1024 = html.match(/(MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQ[A-Za-z0-9+/=\s]{80,500})/);
201
+ if (rawSpki1024) {
202
+ return rawSpki1024[1].replace(/\s+/g, "");
203
+ }
204
+ return undefined;
205
+ }
206
+ /** Match `rsa.min` / Studio `rsaEncrypt`: base64 with newline every 64 chars (EACP may validate format). */
207
+ function formatPasswordBase64LikeRsaMin(b64) {
208
+ return b64.replace(/(.{64})/g, "$1\n");
209
+ }
210
+ function extractRsaPublicKeyMaterialFromPageProps(pageProps) {
211
+ const keys = [
212
+ "publicKey",
213
+ "rsaPublicKey",
214
+ "public_key",
215
+ "modulus",
216
+ "encryptPublicKey",
217
+ "pubKey",
218
+ "rsaModulus",
219
+ "passwordPublicKey",
220
+ "loginPublicKey",
221
+ "encryptKey",
222
+ "pwdPublicKey",
223
+ "modulusHex",
224
+ "rsaPublicKeyHex",
225
+ ];
226
+ for (const k of keys) {
227
+ const v = pageProps[k];
228
+ if (typeof v === "string" && v.trim()) {
229
+ return v.trim();
230
+ }
231
+ }
232
+ return deepFindSigninRsaMaterial(pageProps, 5, new Set());
233
+ }
9
234
  /** Best-effort fetch of display name via EACP userinfo (ShareServer). */
10
235
  async function fetchDisplayName(baseUrl, accessToken, tlsInsecure) {
11
236
  try {
@@ -115,7 +340,8 @@ export function normalizeBaseUrl(value) {
115
340
  * Temporarily disable TLS certificate verification for Node `fetch` (sets
116
341
  * NODE_TLS_REJECT_UNAUTHORIZED). Used for `--insecure` login and token refresh.
117
342
  */
118
- async function runWithTlsInsecure(tlsInsecure, fn) {
343
+ /** @internal Exported for CLI env-only identity resolution (`env-snapshot.ts`). */
344
+ export async function runWithTlsInsecure(tlsInsecure, fn) {
119
345
  if (!tlsInsecure) {
120
346
  return fn();
121
347
  }
@@ -697,6 +923,419 @@ export async function playwrightLogin(baseUrl, options) {
697
923
  return token;
698
924
  });
699
925
  }
926
+ function mergeCookieJarForSignin(existing, response) {
927
+ const setCookies = typeof response.headers.getSetCookie === "function"
928
+ ? response.headers.getSetCookie()
929
+ : (() => {
930
+ const raw = response.headers.get("set-cookie");
931
+ return raw ? [raw] : [];
932
+ })();
933
+ const map = new Map();
934
+ for (const part of existing
935
+ .split(";")
936
+ .map((s) => s.trim())
937
+ .filter(Boolean)) {
938
+ const eq = part.indexOf("=");
939
+ if (eq > 0)
940
+ map.set(part.slice(0, eq), part.slice(eq + 1));
941
+ }
942
+ for (const sc of setCookies) {
943
+ const first = sc.split(";")[0]?.trim() ?? "";
944
+ const eq = first.indexOf("=");
945
+ if (eq > 0)
946
+ map.set(first.slice(0, eq), first.slice(eq + 1));
947
+ }
948
+ return [...map.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
949
+ }
950
+ /**
951
+ * Parse query parameters from a `Location` header value (absolute or relative URL).
952
+ */
953
+ function parseQueryFromLocationHeader(location) {
954
+ const q = location.includes("?") ? location.slice(location.indexOf("?")) : "";
955
+ const params = new URLSearchParams(q.startsWith("?") ? q.slice(1) : q);
956
+ const out = {};
957
+ params.forEach((v, k) => {
958
+ out[k] = v;
959
+ });
960
+ return out;
961
+ }
962
+ /**
963
+ * Parse Next.js `__NEXT_DATA__` from the OAuth2 sign-in HTML shell (CSRF + optional challenge/remember for POST /oauth2/signin).
964
+ * Hydra `login_challenge` may appear only in the sign-in URL; use that when `pageProps.challenge` is absent.
965
+ */
966
+ export function parseSigninPageHtmlProps(html) {
967
+ const m = html.match(/<script[^>]*\bid=["']__NEXT_DATA__["'][^>]*>([\s\S]*?)<\/script>/i);
968
+ if (!m) {
969
+ throw new Error("Could not find __NEXT_DATA__ on the sign-in page.");
970
+ }
971
+ const data = JSON.parse(m[1]);
972
+ const pageProps = data.props?.pageProps;
973
+ if (!pageProps) {
974
+ throw new Error("Invalid __NEXT_DATA__: missing pageProps.");
975
+ }
976
+ const challenge = pageProps.challenge;
977
+ const csrftoken = pageProps.csrftoken ?? pageProps._csrf;
978
+ if (typeof csrftoken !== "string") {
979
+ throw new Error("Sign-in page did not expose csrftoken (expected in __NEXT_DATA__.props.pageProps).");
980
+ }
981
+ const challengeStr = typeof challenge === "string" ? challenge : undefined;
982
+ const rememberRaw = pageProps.remember;
983
+ const remember = typeof rememberRaw === "boolean"
984
+ ? rememberRaw
985
+ : typeof rememberRaw === "string"
986
+ ? rememberRaw === "true"
987
+ : undefined;
988
+ let rsaPublicKeyMaterial = extractRsaPublicKeyMaterialFromPageProps(pageProps);
989
+ if (!rsaPublicKeyMaterial) {
990
+ rsaPublicKeyMaterial = deepFindSigninRsaMaterial(data, 10, new Set());
991
+ }
992
+ if (!rsaPublicKeyMaterial) {
993
+ rsaPublicKeyMaterial = extractSigninRsaMaterialFromHtml(html);
994
+ }
995
+ return { challenge: challengeStr, csrftoken, remember, rsaPublicKeyMaterial };
996
+ }
997
+ async function followSigninRedirectsUntilCallback(startUrl, initialJar, state, redirectUri, base, scope) {
998
+ let url = startUrl;
999
+ let jar = initialJar;
1000
+ const callbackHost = new URL(redirectUri).origin;
1001
+ const callbackPath = new URL(redirectUri).pathname;
1002
+ for (let hop = 0; hop < 40; hop++) {
1003
+ const resp = await fetch(url, {
1004
+ method: "GET",
1005
+ headers: {
1006
+ Cookie: jar,
1007
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
1008
+ },
1009
+ redirect: "manual",
1010
+ });
1011
+ jar = mergeCookieJarForSignin(jar, resp);
1012
+ if (resp.status === 302 || resp.status === 303 || resp.status === 307 || resp.status === 308) {
1013
+ const loc = resp.headers.get("location");
1014
+ if (!loc) {
1015
+ throw new HttpError(resp.status, "Missing Location", "");
1016
+ }
1017
+ const next = new URL(loc, url);
1018
+ if (next.origin === callbackHost && next.pathname === callbackPath) {
1019
+ const code = next.searchParams.get("code");
1020
+ const st = next.searchParams.get("state");
1021
+ if (st !== state) {
1022
+ throw new Error("OAuth2 state mismatch — possible CSRF attack.");
1023
+ }
1024
+ const err = next.searchParams.get("error");
1025
+ if (err) {
1026
+ const desc = next.searchParams.get("error_description") ?? "";
1027
+ throw new Error(desc ? `Authorization failed: ${err} — ${desc}` : `Authorization failed: ${err}`);
1028
+ }
1029
+ if (!code) {
1030
+ throw new Error("Callback URL missing authorization code.");
1031
+ }
1032
+ return { code, jar };
1033
+ }
1034
+ url = next.href;
1035
+ continue;
1036
+ }
1037
+ if (resp.status === 200) {
1038
+ const html = await resp.text();
1039
+ const consentResult = await tryAcceptConsentAfterSignin(base, url, html, jar, scope, state, redirectUri);
1040
+ if (consentResult) {
1041
+ return consentResult;
1042
+ }
1043
+ throw new Error(`Unexpected OAuth page (HTTP 200) at ${url.slice(0, 120)}… ` +
1044
+ `If this is a consent or MFA screen, use browser login or Playwright.`);
1045
+ }
1046
+ const text = await resp.text().catch(() => "");
1047
+ throw new HttpError(resp.status, resp.statusText, text);
1048
+ }
1049
+ throw new Error("Too many OAuth redirects.");
1050
+ }
1051
+ async function tryAcceptConsentAfterSignin(base, pageUrl, html, jar, scope, state, redirectUri) {
1052
+ let data;
1053
+ try {
1054
+ const m = html.match(/<script[^>]*\bid=["']__NEXT_DATA__["'][^>]*>([\s\S]*?)<\/script>/i);
1055
+ if (!m) {
1056
+ return null;
1057
+ }
1058
+ data = JSON.parse(m[1]);
1059
+ }
1060
+ catch {
1061
+ return null;
1062
+ }
1063
+ const pageProps = data.props?.pageProps;
1064
+ if (!pageProps) {
1065
+ return null;
1066
+ }
1067
+ const consentChallenge = pageProps.consent_challenge;
1068
+ if (typeof consentChallenge !== "string") {
1069
+ return null;
1070
+ }
1071
+ const scopes = scope.split(/\s+/).filter(Boolean);
1072
+ const body = new URLSearchParams();
1073
+ body.set("consent_challenge", consentChallenge);
1074
+ for (const s of scopes) {
1075
+ body.append("grant_scope", s);
1076
+ }
1077
+ const resp = await fetch(`${base}/oauth2/consent`, {
1078
+ method: "POST",
1079
+ headers: {
1080
+ Cookie: jar,
1081
+ "Content-Type": "application/x-www-form-urlencoded",
1082
+ Accept: "text/html,application/xhtml+xml,application/json;q=0.9,*/*;q=0.8",
1083
+ },
1084
+ body: body.toString(),
1085
+ redirect: "manual",
1086
+ });
1087
+ const newJar = mergeCookieJarForSignin(jar, resp);
1088
+ if (resp.status === 302 || resp.status === 303 || resp.status === 307) {
1089
+ const loc = resp.headers.get("location");
1090
+ if (!loc) {
1091
+ throw new HttpError(resp.status, "Missing Location after consent", "");
1092
+ }
1093
+ return followSigninRedirectsUntilCallback(new URL(loc, pageUrl).href, newJar, state, redirectUri, base, scope);
1094
+ }
1095
+ return null;
1096
+ }
1097
+ const STUDIOWEB_SHELL_UNAVAILABLE_SNIPPETS = [
1098
+ "Studioweb signin endpoint not available",
1099
+ "Cannot reach studioweb signin endpoint",
1100
+ ];
1101
+ /**
1102
+ * True when {@link oauth2PasswordSigninLogin} failed because the Studio web sign-in shell
1103
+ * (`/interface/studioweb/login`) is missing or unreachable — callers may fall back to Playwright.
1104
+ */
1105
+ export function isStudiowebShellUnavailableError(err) {
1106
+ const msg = err instanceof Error ? err.message : String(err);
1107
+ return STUDIOWEB_SHELL_UNAVAILABLE_SNIPPETS.some((s) => msg.includes(s));
1108
+ }
1109
+ /**
1110
+ * OAuth2 Authorization Code login using HTTP **only**: `GET /oauth2/signin` (Next.js shell) and
1111
+ * `POST /oauth2/signin` with an RSA PKCS#1 v1.5–encrypted password (same as the browser `rsa.min` / Studio
1112
+ * `core/mediator/auth` path).
1113
+ *
1114
+ * `/oauth2/auth` uses `product` `adp` by default (KWeaver Studio shell); set `oauthProduct` or `KWEAVER_OAUTH_PRODUCT` for DIP (`dip`).
1115
+ * Password ciphertext defaults to **single-line base64** (PyCrypto-style); set `KWEAVER_SIGNIN_PASSWORD_B64_RSA_MIN=1` for rsa.min-style wrapped lines.
1116
+ */
1117
+ export async function oauth2PasswordSigninLogin(baseUrl, options) {
1118
+ return runWithTlsInsecure(options.tlsInsecure, async () => {
1119
+ const { publicEncrypt, constants: cryptoConstants } = await import("node:crypto");
1120
+ const { randomBytes } = await import("node:crypto");
1121
+ const base = normalizeBaseUrl(baseUrl);
1122
+ const port = options.port ?? DEFAULT_REDIRECT_PORT;
1123
+ const scope = options.scope ?? DEFAULT_SCOPE;
1124
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
1125
+ const state = randomBytes(12).toString("hex");
1126
+ const oauthProduct = options.oauthProduct?.trim() ||
1127
+ (typeof process.env.KWEAVER_OAUTH_PRODUCT === "string" && process.env.KWEAVER_OAUTH_PRODUCT.trim()
1128
+ ? process.env.KWEAVER_OAUTH_PRODUCT.trim()
1129
+ : "adp");
1130
+ // Pre-flight: verify studioweb signin shell exists (same entry as deploy auto_config.sh get_token).
1131
+ // If the deployment lacks studioweb, abort before OAuth client registration.
1132
+ const studiowebProbeUrl = `${base}/interface/studioweb/login?lang=zh-cn&state=${encodeURIComponent(state)}` +
1133
+ `&x-forwarded-prefix=&integrated=false&product=${encodeURIComponent(oauthProduct)}&_t=${Date.now()}`;
1134
+ let probeResp;
1135
+ try {
1136
+ probeResp = await fetch(studiowebProbeUrl, { method: "GET", redirect: "manual" });
1137
+ }
1138
+ catch (cause) {
1139
+ throw new Error(`Cannot reach studioweb signin endpoint at ${base}/interface/studioweb/login. ` +
1140
+ `The deployment may not include studioweb. Use \`kweaver auth login ${base}\` ` +
1141
+ `(OAuth code flow) instead.\n Cause: ${cause instanceof Error ? cause.message : String(cause)}`);
1142
+ }
1143
+ const probeOk2xx = probeResp.status >= 200 && probeResp.status < 300;
1144
+ const probeOkRedirect = [301, 302, 303, 307, 308].includes(probeResp.status);
1145
+ await probeResp.text().catch(() => "");
1146
+ if (!probeOk2xx && !probeOkRedirect) {
1147
+ throw new Error(`Studioweb signin endpoint not available at ${base}/interface/studioweb/login ` +
1148
+ `(HTTP ${probeResp.status}). The deployment may not include studioweb. ` +
1149
+ `Use \`kweaver auth login ${base}\` (OAuth code flow) instead.`);
1150
+ }
1151
+ let client;
1152
+ try {
1153
+ client = await resolveOrRegisterClient(base, redirectUri, scope, {
1154
+ clientId: options.clientId,
1155
+ clientSecret: options.clientSecret,
1156
+ });
1157
+ }
1158
+ catch (e) {
1159
+ if (e instanceof HttpError && e.status === 404) {
1160
+ process.stderr.write("OAuth2 endpoint not found (404). Saving platform in no-auth mode.\n");
1161
+ return saveNoAuthPlatform(base, { tlsInsecure: options.tlsInsecure });
1162
+ }
1163
+ throw e;
1164
+ }
1165
+ const usePkce = !client.clientSecret;
1166
+ const pkce = usePkce ? await generatePkce() : null;
1167
+ const authParams = new URLSearchParams({
1168
+ redirect_uri: redirectUri,
1169
+ "x-forwarded-prefix": "",
1170
+ client_id: client.clientId,
1171
+ scope,
1172
+ response_type: "code",
1173
+ state,
1174
+ lang: "zh-cn",
1175
+ product: oauthProduct,
1176
+ });
1177
+ if (pkce) {
1178
+ authParams.set("code_challenge", pkce.challenge);
1179
+ authParams.set("code_challenge_method", "S256");
1180
+ }
1181
+ const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
1182
+ let jar = "";
1183
+ const authResp = await fetch(authUrl, { method: "GET", redirect: "manual" });
1184
+ jar = mergeCookieJarForSignin(jar, authResp);
1185
+ if (authResp.status !== 302 && authResp.status !== 303 && authResp.status !== 307) {
1186
+ const t = await authResp.text();
1187
+ throw new HttpError(authResp.status, authResp.statusText, t);
1188
+ }
1189
+ const authLoc = authResp.headers.get("location");
1190
+ if (!authLoc) {
1191
+ throw new HttpError(authResp.status, "Missing Location after /oauth2/auth", "");
1192
+ }
1193
+ const signinUrl = new URL(authLoc, base);
1194
+ if (!signinUrl.pathname.includes("signin")) {
1195
+ throw new Error(`Expected redirect to a sign-in page, got: ${authLoc}`);
1196
+ }
1197
+ const signinPageResp = await fetch(signinUrl.href, {
1198
+ method: "GET",
1199
+ headers: {
1200
+ Cookie: jar,
1201
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
1202
+ },
1203
+ redirect: "manual",
1204
+ });
1205
+ jar = mergeCookieJarForSignin(jar, signinPageResp);
1206
+ if (signinPageResp.status !== 200) {
1207
+ const t = await signinPageResp.text();
1208
+ throw new HttpError(signinPageResp.status, signinPageResp.statusText, t);
1209
+ }
1210
+ const html = await signinPageResp.text();
1211
+ const parsed = parseSigninPageHtmlProps(html);
1212
+ const loginChallenge = signinUrl.searchParams.get("login_challenge")?.trim() || parsed.challenge?.trim();
1213
+ if (!loginChallenge) {
1214
+ throw new Error("Could not resolve login challenge: missing login_challenge in sign-in URL and __NEXT_DATA__.props.pageProps.challenge.");
1215
+ }
1216
+ const csrftoken = parsed.csrftoken;
1217
+ const remember = parsed.remember ?? false;
1218
+ const keyPath = options.signinPublicKeyPemPath?.trim() ||
1219
+ (typeof process.env.KWEAVER_SIGNIN_RSA_PUBLIC_KEY === "string"
1220
+ ? process.env.KWEAVER_SIGNIN_RSA_PUBLIC_KEY.trim()
1221
+ : "");
1222
+ const pems = keyPath
1223
+ ? [
1224
+ resolveSigninPublicKeyPem((await readFile(keyPath, "utf8")).trim(), {
1225
+ allowBuiltinModulus: false,
1226
+ }),
1227
+ ]
1228
+ : buildHttpSigninPemCandidates(parsed.rsaPublicKeyMaterial);
1229
+ const usePlainB64 = options.signinPasswordBase64Plain === true
1230
+ ? true
1231
+ : options.signinPasswordBase64Plain === false
1232
+ ? false
1233
+ : process.env.KWEAVER_SIGNIN_PASSWORD_B64_RSA_MIN !== "1";
1234
+ // Body shape matches browser `POST /oauth2/signin` (EACP / oauth2-ui); omitting vcode/dualfactorauthinfo
1235
+ // causes 400 from eachttpserver (invalid parameter).
1236
+ const postBody = {
1237
+ _csrf: csrftoken,
1238
+ challenge: loginChallenge,
1239
+ account: options.username,
1240
+ password: "",
1241
+ vcode: { id: "", content: "" },
1242
+ dualfactorauthinfo: {
1243
+ validcode: { vcode: "" },
1244
+ OTP: { OTP: "" },
1245
+ },
1246
+ remember,
1247
+ device: {
1248
+ name: "",
1249
+ description: "",
1250
+ client_type: "unknown",
1251
+ udids: [],
1252
+ },
1253
+ };
1254
+ const origin = new URL(base).origin;
1255
+ /** Some gateways (e.g. DIP) return HTTP 200 + `{"redirect":"..."}` instead of 3xx Location. */
1256
+ let signinRedirectFromJson;
1257
+ // Single fixed RSA public key (STUDIOWEB_LOGIN_PUBLIC_KEY_PEM) unless the caller overrides via
1258
+ // --signin-public-key-file / KWEAVER_SIGNIN_RSA_PUBLIC_KEY. No fallback list, no candidate noise.
1259
+ const pem = pems[0];
1260
+ const encrypted = publicEncrypt({ key: pem, padding: cryptoConstants.RSA_PKCS1_PADDING }, Buffer.from(options.password, "utf8"));
1261
+ const rawB64 = encrypted.toString("base64");
1262
+ const passwordB64 = usePlainB64 ? rawB64 : formatPasswordBase64LikeRsaMin(rawB64);
1263
+ postBody.password = passwordB64;
1264
+ const postResp = await fetch(`${base}/oauth2/signin`, {
1265
+ method: "POST",
1266
+ headers: {
1267
+ Cookie: jar,
1268
+ "Content-Type": "application/json",
1269
+ Accept: "application/json, text/plain, */*",
1270
+ Origin: origin,
1271
+ Referer: signinUrl.href,
1272
+ },
1273
+ body: JSON.stringify(postBody),
1274
+ redirect: "manual",
1275
+ });
1276
+ jar = mergeCookieJarForSignin(jar, postResp);
1277
+ if (postResp.status !== 302 && postResp.status !== 303 && postResp.status !== 307) {
1278
+ const bodyText = await postResp.text();
1279
+ if (/RSA_private_decrypt/i.test(bodyText)) {
1280
+ throw new Error("HTTP sign-in: RSA ciphertext rejected by server. The built-in STUDIOWEB_LOGIN_PUBLIC_KEY_PEM " +
1281
+ "does not match this deployment's `/oauth2/signin` public key. Provide the correct key via " +
1282
+ "--signin-public-key-file <pem> or KWEAVER_SIGNIN_RSA_PUBLIC_KEY=...");
1283
+ }
1284
+ if (postResp.status === 200) {
1285
+ const ct = postResp.headers.get("content-type") ?? "";
1286
+ const looksLikeJson = ct.includes("application/json") || /^\s*\{/.test(bodyText);
1287
+ if (looksLikeJson) {
1288
+ let j;
1289
+ try {
1290
+ j = JSON.parse(bodyText);
1291
+ }
1292
+ catch {
1293
+ throw new Error(`Sign-in failed: ${bodyText.slice(0, 500)}`);
1294
+ }
1295
+ const redir = j.redirect;
1296
+ if (typeof redir === "string" && redir.trim() !== "") {
1297
+ signinRedirectFromJson = redir.trim();
1298
+ }
1299
+ else {
1300
+ const msg = typeof j.message === "string"
1301
+ ? j.message
1302
+ : typeof j.error === "string"
1303
+ ? j.error
1304
+ : bodyText.slice(0, 500);
1305
+ throw new Error(`Sign-in failed: ${msg}`);
1306
+ }
1307
+ }
1308
+ else {
1309
+ throw new Error("Sign-in POST returned 200 without redirect. Check password, CSRF, or RSA public key PEM.");
1310
+ }
1311
+ }
1312
+ else {
1313
+ throw new HttpError(postResp.status, postResp.statusText, bodyText);
1314
+ }
1315
+ }
1316
+ let code;
1317
+ if (signinRedirectFromJson) {
1318
+ const out = await followSigninRedirectsUntilCallback(new URL(signinRedirectFromJson, base).href, jar, state, redirectUri, base, scope);
1319
+ code = out.code;
1320
+ }
1321
+ else if (postResp.status === 302 || postResp.status === 303 || postResp.status === 307) {
1322
+ const loc = postResp.headers.get("location");
1323
+ if (!loc) {
1324
+ throw new HttpError(postResp.status, "Missing Location after sign-in", "");
1325
+ }
1326
+ const out = await followSigninRedirectsUntilCallback(new URL(loc, base).href, jar, state, redirectUri, base, scope);
1327
+ code = out.code;
1328
+ }
1329
+ else {
1330
+ throw new Error("HTTP sign-in: exhausted RSA key candidates without redirect");
1331
+ }
1332
+ const token = await exchangeCodeForToken(base, code, client.clientId, client.clientSecret, redirectUri, pkce?.verifier, options.tlsInsecure);
1333
+ const copyCommand = buildCopyCommand(base, client.clientId, client.clientSecret, token.refreshToken, options.tlsInsecure);
1334
+ process.stderr.write("\nHTTP sign-in: copy this command for headless hosts:\n\n" + copyCommand + "\n\n");
1335
+ setCurrentPlatform(base);
1336
+ return token;
1337
+ });
1338
+ }
700
1339
  /**
701
1340
  * Log in on a headless machine using OAuth2 client credentials and a refresh token (no browser).
702
1341
  * Exchanges the refresh token for a new access token and persists ~/.kweaver/ state.
@@ -952,6 +1591,13 @@ export async function withTokenRetry(fn) {
952
1591
  throw error;
953
1592
  }
954
1593
  const platformUrl = normalizeBaseUrl(token.baseUrl);
1594
+ // env-sourced token: no refresh_token / OAuth client — refresh is impossible.
1595
+ // Surface an env-aware hint instead of telling the user to `auth login` (which writes to disk).
1596
+ if (process.env.KWEAVER_TOKEN && !token.refreshToken) {
1597
+ throw new Error(`Authentication failed (401) for ${platformUrl}. Your KWEAVER_TOKEN appears to be invalid or expired.\n` +
1598
+ ` - Refresh the token and re-export: export KWEAVER_TOKEN=<new-token>\n` +
1599
+ ` - Or run \`kweaver auth login ${platformUrl}\` to save a full session (with refresh_token) to ~/.kweaver/.`, { cause: error });
1600
+ }
955
1601
  const envUser = process.env.KWEAVER_USER;
956
1602
  let latest;
957
1603
  if (envUser) {
package/dist/cli.js CHANGED
@@ -21,7 +21,7 @@ Usage:
21
21
  kweaver --version | -V
22
22
  kweaver --help | -h
23
23
 
24
- kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--insecure|-k]
24
+ kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--playwright] [--insecure|-k]
25
25
  kweaver auth login <platform-url> (alias for auth <url>)
26
26
  kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (run on host without browser)
27
27
  kweaver auth whoami [platform-url|alias] [--json]
@@ -1,14 +1,25 @@
1
1
  import { isNoAuth } from "../config/no-auth.js";
2
2
  import { autoSelectBusinessDomain, clearPlatformSession, deletePlatform, deleteUser, getActiveUser, getConfigDir, getCurrentPlatform, getPlatformAlias, hasPlatform, listPlatforms, listUserProfiles, loadClientConfig, loadTokenConfig, resolveBusinessDomain, resolvePlatformIdentifier, resolveUserId, saveNoAuthPlatform, setActiveUser, setCurrentPlatform, setPlatformAlias, } from "../config/store.js";
3
3
  import { decodeJwtPayload } from "../config/jwt.js";
4
- import { buildCopyCommand, formatHttpError, normalizeBaseUrl, oauth2Login, playwrightLogin, refreshTokenLogin, } from "../auth/oauth.js";
4
+ import { buildCopyCommand, formatHttpError, isStudiowebShellUnavailableError, normalizeBaseUrl, oauth2Login, oauth2PasswordSigninLogin, playwrightLogin, refreshTokenLogin, } from "../auth/oauth.js";
5
+ /** True when the `playwright` npm package can be imported (browser binaries may still need `npx playwright install`). */
6
+ async function isPlaywrightPackageResolvable() {
7
+ try {
8
+ const modName = "playwright";
9
+ await import(/* webpackIgnore: true */ modName);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
5
16
  export async function runAuthCommand(args) {
6
17
  const target = args[0];
7
18
  const rest = args.slice(1);
8
19
  if (!target || target === "--help" || target === "-h") {
9
20
  console.log(`kweaver auth login <url> [options] Login to a platform (browser OAuth2 by default)
10
21
  kweaver auth <url> Login (shorthand; same options as login)
11
- kweaver auth whoami [url|alias] Show current user identity (from id_token)
22
+ kweaver auth whoami [url|alias] [--json] Show current user identity (from id_token)
12
23
  kweaver auth export [url|alias] [--json] Export credentials; run printed command on a headless host
13
24
  kweaver auth status [url|alias] Show current auth status
14
25
  kweaver auth list List all platforms and users (tree view)
@@ -30,16 +41,17 @@ Login options:
30
41
  --port <n> Local callback port (default: 9010). Use when 9010 is occupied.
31
42
  --no-browser Do not open a browser; print the auth URL and prompt for the callback URL or code (stdin).
32
43
  Use on headless servers or when automatic browser launch fails.
33
- -u, --username Username (with -p triggers Playwright headless login)
34
- -p, --password Password
35
- --playwright Force Playwright browser login even without -u/-p
44
+ -u, --username Username (with -p: tries HTTP /oauth2/signin first when the Studio web shell is available)
45
+ -p, --password Password (with -u: falls back to Playwright headless only when studioweb is unavailable and Playwright is installed)
46
+ --http-signin With -u/-p: HTTP POST /oauth2/signin only (no Playwright fallback). Uses the built-in RSA public key.
47
+ --playwright With -u/-p: force Playwright (skip HTTP sign-in). Without -u/-p: open Playwright for manual login.
36
48
  --insecure, -k Skip TLS certificate verification (self-signed / dev HTTPS only)
37
49
  --no-auth Save platform without OAuth (servers with no authentication). Same as detecting OAuth 404 during login.`);
38
50
  return 0;
39
51
  }
40
52
  if (target === "login") {
41
53
  if (rest[0] === "--help" || rest[0] === "-h") {
42
- console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
54
+ console.log(`kweaver auth login <platform-url> [--alias <name>] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--playwright] [--refresh-token T --client-id ID --client-secret S]`);
43
55
  return 0;
44
56
  }
45
57
  const url = rest[0];
@@ -69,6 +81,9 @@ Login options:
69
81
  const username = readOption(args, "--username") ?? readOption(args, "-u");
70
82
  const password = readOption(args, "--password") ?? readOption(args, "-p");
71
83
  const usePlaywright = args.includes("--playwright");
84
+ const httpSignin = args.includes("--http-signin");
85
+ const oauthProduct = readOption(args, "--oauth-product");
86
+ const signinPublicKeyFile = readOption(args, "--signin-public-key-file");
72
87
  const clientId = readOption(args, "--client-id");
73
88
  const clientSecret = readOption(args, "--client-secret");
74
89
  const refreshToken = readOption(args, "--refresh-token");
@@ -83,11 +98,16 @@ Login options:
83
98
  const KNOWN_LOGIN_FLAGS = new Set([
84
99
  "--alias", "--client-id", "--client-secret", "--refresh-token",
85
100
  "--port", "--no-browser", "--username", "-u", "--password", "-p",
101
+ "--http-signin",
102
+ "--oauth-product",
103
+ "--signin-public-key-file",
86
104
  "--playwright", "--insecure", "-k", "--no-auth", "--redirect-uri",
87
105
  ]);
88
106
  const KNOWN_VALUE_FLAGS = new Set([
89
107
  "--alias", "--client-id", "--client-secret", "--refresh-token",
90
108
  "--port", "--username", "-u", "--password", "-p", "--redirect-uri",
109
+ "--oauth-product",
110
+ "--signin-public-key-file",
91
111
  ]);
92
112
  for (let i = 0; i < args.length; i++) {
93
113
  const a = args[i];
@@ -110,12 +130,24 @@ Login options:
110
130
  if (noAuth && noBrowser) {
111
131
  console.error("--no-auth does not require a browser; --no-browser is ignored.");
112
132
  }
113
- if (noAuth && (username || password || usePlaywright)) {
114
- console.error("--no-auth cannot be used with Playwright login or -u/-p.");
133
+ if (noAuth && (username || password || usePlaywright || httpSignin)) {
134
+ console.error("--no-auth cannot be used with Playwright login, HTTP sign-in, or -u/-p.");
135
+ return 1;
136
+ }
137
+ if (noBrowser && (username || password || usePlaywright || httpSignin)) {
138
+ console.error("--no-browser cannot be used with Playwright login, HTTP sign-in, or -u/-p.");
115
139
  return 1;
116
140
  }
117
- if (noBrowser && (username || password || usePlaywright)) {
118
- console.error("--no-browser cannot be used with Playwright login or -u/-p.");
141
+ if (httpSignin && usePlaywright) {
142
+ console.error("--http-signin cannot be used with --playwright.");
143
+ return 1;
144
+ }
145
+ if (httpSignin && refreshToken) {
146
+ console.error("--http-signin cannot be used with --refresh-token.");
147
+ return 1;
148
+ }
149
+ if (httpSignin && (!username || !password)) {
150
+ console.error("--http-signin requires -u/--username and -p/--password.");
119
151
  return 1;
120
152
  }
121
153
  if (noBrowser && refreshToken) {
@@ -137,12 +169,67 @@ Login options:
137
169
  clientId, clientSecret, refreshToken, tlsInsecure,
138
170
  });
139
171
  }
140
- else if (username && password) {
141
- console.log("Logging in (headless)...");
172
+ else if (username && password && httpSignin) {
173
+ console.log("Logging in (HTTP /oauth2/signin)...");
174
+ token = await oauth2PasswordSigninLogin(normalizedTarget, {
175
+ username,
176
+ password,
177
+ tlsInsecure,
178
+ port: customPort,
179
+ clientId: clientId ?? undefined,
180
+ clientSecret: clientSecret ?? undefined,
181
+ oauthProduct: oauthProduct ?? undefined,
182
+ signinPublicKeyPemPath: signinPublicKeyFile ?? undefined,
183
+ });
184
+ }
185
+ else if (username && password && usePlaywright) {
186
+ console.log("Logging in (headless, Playwright)...");
142
187
  token = await playwrightLogin(normalizedTarget, {
143
- username, password, tlsInsecure, port: customPort,
188
+ username,
189
+ password,
190
+ tlsInsecure,
191
+ port: customPort,
144
192
  });
145
193
  }
194
+ else if (username && password) {
195
+ const signinOpts = {
196
+ username,
197
+ password,
198
+ tlsInsecure,
199
+ port: customPort,
200
+ clientId: clientId ?? undefined,
201
+ clientSecret: clientSecret ?? undefined,
202
+ oauthProduct: oauthProduct ?? undefined,
203
+ signinPublicKeyPemPath: signinPublicKeyFile ?? undefined,
204
+ };
205
+ console.log("Logging in (HTTP /oauth2/signin)...");
206
+ try {
207
+ token = await oauth2PasswordSigninLogin(normalizedTarget, signinOpts);
208
+ }
209
+ catch (err) {
210
+ if (!isStudiowebShellUnavailableError(err)) {
211
+ throw err;
212
+ }
213
+ const playwrightOk = await isPlaywrightPackageResolvable();
214
+ if (playwrightOk) {
215
+ process.stderr.write("Studio web sign-in shell is not available; falling back to Playwright headless login.\n");
216
+ console.log("Logging in (headless, Playwright)...");
217
+ token = await playwrightLogin(normalizedTarget, {
218
+ username,
219
+ password,
220
+ tlsInsecure,
221
+ port: customPort,
222
+ });
223
+ }
224
+ else {
225
+ console.error("Studio web sign-in shell is not available on this platform, and the Playwright package is not installed.");
226
+ console.error("Install Playwright for headless browser login: npm install playwright && npx playwright install chromium");
227
+ console.error("Alternatively, use OAuth without credentials:");
228
+ console.error(` kweaver auth login ${normalizedTarget} --no-browser`);
229
+ throw err;
230
+ }
231
+ }
232
+ }
146
233
  else if (usePlaywright) {
147
234
  console.log("Opening browser for login (Playwright)...");
148
235
  token = await playwrightLogin(normalizedTarget, {
@@ -217,8 +304,19 @@ Login options:
217
304
  const statusTarget = resolvedTarget && /^https?:\/\//.test(resolvedTarget) ? normalizeBaseUrl(resolvedTarget) : resolvedTarget ?? undefined;
218
305
  const platform = statusTarget ?? getCurrentPlatform();
219
306
  if (!platform) {
220
- console.error("No active platform. Run `kweaver auth login <platform-url>` first.");
221
- return 1;
307
+ const envRaw = process.env.KWEAVER_BASE_URL?.trim();
308
+ const envUrl = envRaw ? normalizeBaseUrl(envRaw) : undefined;
309
+ const envToken = process.env.KWEAVER_TOKEN?.trim();
310
+ if (!envUrl || !envToken) {
311
+ console.error("No active platform. Run `kweaver auth login <platform-url>` first.\n" +
312
+ " Tip: set KWEAVER_BASE_URL and KWEAVER_TOKEN to use this command without a saved login.");
313
+ return 1;
314
+ }
315
+ console.log(`Config directory: ${getConfigDir()}`);
316
+ console.log(`Platform: ${envUrl} (KWEAVER_BASE_URL)`);
317
+ console.log(`Token present: yes (KWEAVER_TOKEN)`);
318
+ console.log(`Refresh token: n/a (env)`);
319
+ return 0;
222
320
  }
223
321
  const token = loadTokenConfig(platform);
224
322
  if (!token) {
@@ -359,7 +457,7 @@ Login options:
359
457
  return 0;
360
458
  }
361
459
  console.error("Usage: kweaver auth login <platform-url> [--alias <name>] [-u user] [-p pass] [--playwright]");
362
- console.error(" kweaver auth whoami [platform-url|alias]");
460
+ console.error(" kweaver auth whoami [platform-url|alias] [--json]");
363
461
  console.error(" kweaver auth export [platform-url|alias] [--json]");
364
462
  console.error(" kweaver auth status [platform-url|alias]");
365
463
  console.error(" kweaver auth list");
@@ -456,8 +554,37 @@ Options:
456
554
  const resolved = positional ? resolvePlatformIdentifier(positional) : null;
457
555
  const platform = resolved && /^https?:\/\//.test(resolved) ? normalizeBaseUrl(resolved) : resolved ?? getCurrentPlatform();
458
556
  if (!platform) {
459
- console.error("No active platform. Run `kweaver auth login <platform-url>` first.");
460
- return 1;
557
+ const envRaw = process.env.KWEAVER_BASE_URL?.trim();
558
+ const envUrl = envRaw ? normalizeBaseUrl(envRaw) : undefined;
559
+ const envToken = process.env.KWEAVER_TOKEN?.trim();
560
+ if (!envUrl || !envToken) {
561
+ console.error("No active platform. Run `kweaver auth login <platform-url>` first.");
562
+ return 1;
563
+ }
564
+ const accessToken = envToken.replace(/^Bearer\s+/i, "");
565
+ const payload = decodeJwtPayload(accessToken);
566
+ if (jsonOutput) {
567
+ console.log(JSON.stringify({ platform: envUrl, source: "env", ...(payload ?? {}) }, null, 2));
568
+ return 0;
569
+ }
570
+ console.log(`Platform: ${envUrl}`);
571
+ console.log(`Source: env (KWEAVER_TOKEN)`);
572
+ if (payload) {
573
+ const uname = payload.preferred_username ?? payload.name;
574
+ if (uname)
575
+ console.log(`Username: ${uname}`);
576
+ console.log(`User ID: ${payload.sub ?? "(unknown)"}`);
577
+ console.log(`Issuer: ${payload.iss ?? "(unknown)"}`);
578
+ if (payload.iat)
579
+ console.log(`Issued: ${new Date(payload.iat * 1000).toISOString()}`);
580
+ if (payload.exp)
581
+ console.log(`Expires: ${new Date(payload.exp * 1000).toISOString()}`);
582
+ }
583
+ else {
584
+ console.log(`User info unavailable: opaque access token.`);
585
+ console.log(`Hint: run \`kweaver auth login ${envUrl}\` to obtain a full session.`);
586
+ }
587
+ return 0;
461
588
  }
462
589
  const token = loadTokenConfig(platform);
463
590
  if (!token) {
@@ -1,5 +1,14 @@
1
1
  import { listBusinessDomains } from "../api/business-domains.js";
2
- import { withTokenRetry } from "../auth/oauth.js";
2
+ import { normalizeBaseUrl, withTokenRetry } from "../auth/oauth.js";
3
+ // Resolve platform URL: saved current platform > KWEAVER_BASE_URL (normalized to
4
+ // match what `auth login` writes, so env users share the same platforms/<key>/ dir).
5
+ function resolvePlatformUrl() {
6
+ const saved = getCurrentPlatform();
7
+ if (saved)
8
+ return saved;
9
+ const env = process.env.KWEAVER_BASE_URL?.trim();
10
+ return env ? normalizeBaseUrl(env) : undefined;
11
+ }
3
12
  import { getCurrentPlatform, loadPlatformBusinessDomain, resolveBusinessDomain, savePlatformBusinessDomain, } from "../config/store.js";
4
13
  const HELP = `kweaver config
5
14
 
@@ -20,9 +29,9 @@ export async function runConfigCommand(args) {
20
29
  return 0;
21
30
  }
22
31
  if (sub === "show") {
23
- const platform = getCurrentPlatform();
32
+ const platform = resolvePlatformUrl();
24
33
  if (!platform) {
25
- console.error("No active platform. Run `kweaver auth login <url>` first.");
34
+ console.error("No active platform. Run `kweaver auth login <url>` first.\n Tip: set KWEAVER_BASE_URL to use this command without a saved login.");
26
35
  return 1;
27
36
  }
28
37
  const bd = resolveBusinessDomain(platform);
@@ -31,7 +40,8 @@ export async function runConfigCommand(args) {
31
40
  : loadPlatformBusinessDomain(platform)
32
41
  ? "config"
33
42
  : "default";
34
- console.log(`Platform: ${platform}`);
43
+ const platformSource = getCurrentPlatform() ? "" : " (KWEAVER_BASE_URL)";
44
+ console.log(`Platform: ${platform}${platformSource}`);
35
45
  console.log(`Business Domain: ${bd} (${source})`);
36
46
  return 0;
37
47
  }
@@ -41,19 +51,19 @@ export async function runConfigCommand(args) {
41
51
  console.error("Usage: kweaver config set-bd <value>");
42
52
  return 1;
43
53
  }
44
- const platform = getCurrentPlatform();
54
+ const platform = resolvePlatformUrl();
45
55
  if (!platform) {
46
- console.error("No active platform. Run `kweaver auth login <url>` first.");
56
+ console.error("No active platform. Run `kweaver auth login <url>` first.\n Tip: set KWEAVER_BASE_URL to write the business domain for that platform.");
47
57
  return 1;
48
58
  }
49
59
  savePlatformBusinessDomain(platform, value);
50
- console.log(`Business domain set to: ${value}`);
60
+ console.log(`Business domain set to: ${value} (${getCurrentPlatform() ? platform : `${platform} via KWEAVER_BASE_URL`})`);
51
61
  return 0;
52
62
  }
53
63
  if (sub === "list-bd") {
54
- const platform = getCurrentPlatform();
64
+ const platform = resolvePlatformUrl();
55
65
  if (!platform) {
56
- console.error("No active platform. Run `kweaver auth login <url>` first.");
66
+ console.error("No active platform. Run `kweaver auth login <url>` first.\n Tip: set KWEAVER_BASE_URL and KWEAVER_TOKEN to use this command without a saved login.");
57
67
  return 1;
58
68
  }
59
69
  try {
@@ -281,20 +281,26 @@ async function runGetPrompt(options, args, pretty) {
281
281
  async function runKnSearch(options, args, pretty) {
282
282
  let query;
283
283
  let onlySchema = false;
284
+ let knIdOverride;
284
285
  for (let i = 0; i < args.length; i += 1) {
285
286
  const arg = args[i];
286
287
  if (arg === "--only-schema") {
287
288
  onlySchema = true;
288
289
  }
290
+ else if ((arg === "--kn-id" || arg === "-k") && args[i + 1]) {
291
+ knIdOverride = args[i + 1];
292
+ i += 1;
293
+ }
289
294
  else if (!arg.startsWith("-") && !query) {
290
295
  query = arg;
291
296
  }
292
297
  }
293
298
  if (!query) {
294
- console.error("Usage: kweaver context-loader kn-search <query> [--only-schema]");
299
+ console.error("Usage: kweaver context-loader kn-search <query> [--kn-id <id>] [--only-schema]");
295
300
  return 1;
296
301
  }
297
- const result = await knSearch(options, { query, only_schema: onlySchema });
302
+ const effectiveOptions = knIdOverride ? { ...options, knId: knIdOverride } : options;
303
+ const result = await knSearch(effectiveOptions, { query, only_schema: onlySchema });
298
304
  console.log(formatOutput(result, pretty));
299
305
  return 0;
300
306
  }
@@ -17,6 +17,7 @@ export declare function resolveFiles(pattern: string): Promise<string[]>;
17
17
  export interface ImportCsvResult {
18
18
  code: number;
19
19
  tables: string[];
20
+ failed: string[];
20
21
  tableColumns: Record<string, string[]>;
21
22
  sampleRows: Record<string, Array<Record<string, string | null>>>;
22
23
  }
@@ -384,17 +384,17 @@ export async function runDsImportCsv(args) {
384
384
  catch (error) {
385
385
  if (error instanceof Error && error.message === "help") {
386
386
  console.log(IMPORT_CSV_HELP);
387
- return { code: 0, tables: [], tableColumns: {}, sampleRows: {} };
387
+ return { code: 0, tables: [], failed: [], tableColumns: {}, sampleRows: {} };
388
388
  }
389
389
  throw error;
390
390
  }
391
391
  if (!options.datasourceId) {
392
392
  console.error("Usage: kweaver ds import-csv <ds-id> --files <glob_or_list> [options]");
393
- return { code: 1, tables: [], tableColumns: {}, sampleRows: {} };
393
+ return { code: 1, tables: [], failed: [], tableColumns: {}, sampleRows: {} };
394
394
  }
395
395
  if (!options.files) {
396
396
  console.error("Error: --files is required");
397
- return { code: 1, tables: [], tableColumns: {}, sampleRows: {} };
397
+ return { code: 1, tables: [], failed: [], tableColumns: {}, sampleRows: {} };
398
398
  }
399
399
  // 1. Get credentials
400
400
  const token = await ensureValidToken();
@@ -429,7 +429,7 @@ export async function runDsImportCsv(args) {
429
429
  }
430
430
  if (parsed.length === 0) {
431
431
  console.error("All files were skipped — nothing to import");
432
- return { code: 1, tables: [], tableColumns: {}, sampleRows: {} };
432
+ return { code: 1, tables: [], failed: [], tableColumns: {}, sampleRows: {} };
433
433
  }
434
434
  // Phase 2: Import each file in batches
435
435
  const succeeded = [];
@@ -487,14 +487,14 @@ export async function runDsImportCsv(args) {
487
487
  if (failed.length > 0) {
488
488
  console.error(`Failed tables: ${failed.join(", ")}`);
489
489
  }
490
- console.log(JSON.stringify({
491
- tables: succeeded,
492
- failed,
493
- summary: { succeeded: succeeded.length, failed: failed.length },
494
- }, null, 2));
495
- return { code: failed.length > 0 ? 1 : 0, tables: succeeded, tableColumns, sampleRows };
490
+ return { code: failed.length > 0 ? 1 : 0, tables: succeeded, failed, tableColumns, sampleRows };
496
491
  }
497
492
  export async function runDsImportCsvCommand(args) {
498
493
  const result = await runDsImportCsv(args);
494
+ console.log(JSON.stringify({
495
+ tables: result.tables,
496
+ failed: result.failed,
497
+ summary: { succeeded: result.tables.length, failed: result.failed.length },
498
+ }, null, 2));
499
499
  return result.code;
500
500
  }
package/dist/index.d.ts CHANGED
@@ -62,3 +62,4 @@ export type { TokenConfig, ContextLoaderEntry, ContextLoaderConfig, } from "./co
62
62
  export type { UserProfile } from "./config/store.js";
63
63
  export { NO_AUTH_TOKEN, isNoAuth, saveNoAuthPlatform, autoSelectBusinessDomain, getConfigDir, getCurrentPlatform, getActiveUser, setActiveUser, listUsers, listUserProfiles, resolveUserId, extractUserId, } from "./config/store.js";
64
64
  export { decodeJwtPayload, extractUserIdFromJwt } from "./config/jwt.js";
65
+ export { DEFAULT_SIGNIN_RSA_MODULUS_HEX, oauth2PasswordSigninLogin, parseSigninPageHtmlProps, rsaModulusHexToSpkiPem, STUDIOWEB_LOGIN_PUBLIC_KEY_PEM, } from "./auth/oauth.js";
package/dist/index.js CHANGED
@@ -49,3 +49,5 @@ export { HttpError, NetworkRequestError, fetchTextOrThrow } from "./utils/http.j
49
49
  export { NO_AUTH_TOKEN, isNoAuth, saveNoAuthPlatform, autoSelectBusinessDomain, getConfigDir, getCurrentPlatform, getActiveUser, setActiveUser, listUsers, listUserProfiles, resolveUserId, extractUserId, } from "./config/store.js";
50
50
  // ── JWT utilities ─────────────────────────────────────────────────────────────
51
51
  export { decodeJwtPayload, extractUserIdFromJwt } from "./config/jwt.js";
52
+ // ── OAuth (advanced — CLI uses these internally; optional for custom login tools) ─
53
+ export { DEFAULT_SIGNIN_RSA_MODULUS_HEX, oauth2PasswordSigninLogin, parseSigninPageHtmlProps, rsaModulusHexToSpkiPem, STUDIOWEB_LOGIN_PUBLIC_KEY_PEM, } from "./auth/oauth.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kweaver-ai/kweaver-sdk",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
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",