@kweaver-ai/kweaver-sdk 0.6.4 → 0.6.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/README.md +24 -3
- package/README.zh.md +2 -2
- package/dist/api/dataflow.d.ts +1 -1
- package/dist/api/toolboxes.d.ts +47 -0
- package/dist/api/toolboxes.js +90 -0
- package/dist/auth/oauth.d.ts +52 -21
- package/dist/auth/oauth.js +178 -220
- package/dist/cli.js +20 -1
- package/dist/commands/auth.js +32 -81
- package/dist/commands/bkn-ops.d.ts +1 -0
- package/dist/commands/bkn-ops.js +8 -1
- package/dist/commands/call.d.ts +10 -0
- package/dist/commands/call.js +61 -5
- package/dist/commands/ds.js +1 -1
- package/dist/commands/import-csv.d.ts +1 -1
- package/dist/commands/import-csv.js +3 -1
- package/dist/commands/tool.d.ts +16 -0
- package/dist/commands/tool.js +208 -0
- package/dist/commands/toolbox.d.ts +14 -0
- package/dist/commands/toolbox.js +256 -0
- package/package.json +1 -11
package/dist/auth/oauth.js
CHANGED
|
@@ -463,23 +463,36 @@ function stderrEmphasis(text) {
|
|
|
463
463
|
/**
|
|
464
464
|
* Headless login: read authorization code from stdin (full callback URL or raw code).
|
|
465
465
|
* Used with `--no-browser` or when automatic browser launch fails.
|
|
466
|
+
*
|
|
467
|
+
* `io` is injectable for tests; defaults to `process.stdin` / `process.stderr`.
|
|
466
468
|
*/
|
|
467
|
-
async function promptForCode(authUrl, state, port, pasteMode = "explicit") {
|
|
469
|
+
export async function promptForCode(authUrl, state, port, pasteMode = "explicit", io) {
|
|
468
470
|
const { createInterface } = await import("node:readline");
|
|
471
|
+
const stdin = io?.input ?? process.stdin;
|
|
472
|
+
const stderr = io?.output ?? process.stderr;
|
|
469
473
|
const intro = pasteMode === "explicit"
|
|
470
474
|
? "Open this URL on any device (use a private/incognito window if you need the full sign-in form):\n\n"
|
|
471
475
|
: "Could not open a browser automatically. Open this URL on any device:\n\n";
|
|
472
476
|
const pasteInstructions = "After login, the browser may show an error page (this is expected if nothing listens on localhost).\n" +
|
|
473
477
|
"Copy the FULL URL from the address bar and paste it here, or paste only the authorization code.\n" +
|
|
474
478
|
`The URL looks like: http://127.0.0.1:${port}/callback?code=THIS_PART&state=...\n\n`;
|
|
475
|
-
|
|
479
|
+
stderr.write("\n" +
|
|
476
480
|
intro +
|
|
477
481
|
` ${authUrl}\n\n` +
|
|
478
482
|
stderrEmphasis(pasteInstructions));
|
|
479
|
-
const rl = createInterface({ input:
|
|
483
|
+
const rl = createInterface({ input: stdin, output: stderr });
|
|
484
|
+
// The `close` listener exists to surface Ctrl-D / EOF before the user answers.
|
|
485
|
+
// It MUST be a no-op once the question callback has fired, because `rl.close()`
|
|
486
|
+
// emits `close` synchronously and would otherwise reject the promise before
|
|
487
|
+
// `resolve(answer)` runs (race condition that turns valid input into "Login cancelled.").
|
|
480
488
|
const input = await new Promise((resolve, reject) => {
|
|
481
|
-
|
|
489
|
+
let answered = false;
|
|
490
|
+
rl.on("close", () => {
|
|
491
|
+
if (!answered)
|
|
492
|
+
reject(new Error("Login cancelled."));
|
|
493
|
+
});
|
|
482
494
|
rl.question("Paste URL or code> ", (answer) => {
|
|
495
|
+
answered = true;
|
|
483
496
|
rl.close();
|
|
484
497
|
resolve(answer.trim());
|
|
485
498
|
});
|
|
@@ -512,6 +525,128 @@ async function promptForCode(authUrl, state, port, pasteMode = "explicit") {
|
|
|
512
525
|
}
|
|
513
526
|
return input;
|
|
514
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* Prompt the user for a username on stderr (input echoed).
|
|
530
|
+
*
|
|
531
|
+
* `io` is injectable for tests; defaults to `process.stdin` / `process.stderr`.
|
|
532
|
+
*/
|
|
533
|
+
export async function promptForUsername(promptLabel = "Username", io) {
|
|
534
|
+
const { createInterface } = await import("node:readline");
|
|
535
|
+
const stdin = io?.input ?? process.stdin;
|
|
536
|
+
const stderr = io?.output ?? process.stderr;
|
|
537
|
+
const rl = createInterface({ input: stdin, output: stderr });
|
|
538
|
+
const value = await new Promise((resolve, reject) => {
|
|
539
|
+
let answered = false;
|
|
540
|
+
rl.on("close", () => {
|
|
541
|
+
if (!answered)
|
|
542
|
+
reject(new Error("Login cancelled."));
|
|
543
|
+
});
|
|
544
|
+
rl.question(`${promptLabel}: `, (answer) => {
|
|
545
|
+
answered = true;
|
|
546
|
+
rl.close();
|
|
547
|
+
resolve(answer.trim());
|
|
548
|
+
});
|
|
549
|
+
});
|
|
550
|
+
if (!value) {
|
|
551
|
+
throw new Error(`${promptLabel} is required.`);
|
|
552
|
+
}
|
|
553
|
+
return value;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Prompt the user for a password on stderr without echoing keystrokes (TTY only).
|
|
557
|
+
*
|
|
558
|
+
* Falls back to a regular readline prompt when stdin is not a TTY (e.g. piped input
|
|
559
|
+
* during scripted use); callers needing strict no-echo should detect this case themselves.
|
|
560
|
+
*
|
|
561
|
+
* `io` is injectable for tests.
|
|
562
|
+
*/
|
|
563
|
+
export async function promptForPassword(promptLabel = "Password", io) {
|
|
564
|
+
const stdin = io?.input ?? process.stdin;
|
|
565
|
+
const stderr = io?.output ?? process.stderr;
|
|
566
|
+
// Non-TTY (piped, redirected, tests): use regular readline — no masking is possible
|
|
567
|
+
// without raw mode, so we accept echoed input rather than block forever.
|
|
568
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") {
|
|
569
|
+
const { createInterface } = await import("node:readline");
|
|
570
|
+
const rl = createInterface({ input: stdin, output: stderr });
|
|
571
|
+
const value = await new Promise((resolve, reject) => {
|
|
572
|
+
let answered = false;
|
|
573
|
+
rl.on("close", () => {
|
|
574
|
+
if (!answered)
|
|
575
|
+
reject(new Error("Login cancelled."));
|
|
576
|
+
});
|
|
577
|
+
rl.question(`${promptLabel}: `, (answer) => {
|
|
578
|
+
answered = true;
|
|
579
|
+
rl.close();
|
|
580
|
+
resolve(answer);
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
if (!value)
|
|
584
|
+
throw new Error(`${promptLabel} is required.`);
|
|
585
|
+
return value;
|
|
586
|
+
}
|
|
587
|
+
// TTY: read byte-by-byte in raw mode so keystrokes are not echoed.
|
|
588
|
+
return new Promise((resolve, reject) => {
|
|
589
|
+
stderr.write(`${promptLabel}: `);
|
|
590
|
+
let buf = "";
|
|
591
|
+
const onData = (chunk) => {
|
|
592
|
+
const s = chunk.toString("utf8");
|
|
593
|
+
for (const ch of s) {
|
|
594
|
+
const code = ch.charCodeAt(0);
|
|
595
|
+
if (ch === "\n" || ch === "\r") {
|
|
596
|
+
cleanup();
|
|
597
|
+
stderr.write("\n");
|
|
598
|
+
if (!buf) {
|
|
599
|
+
reject(new Error(`${promptLabel} is required.`));
|
|
600
|
+
}
|
|
601
|
+
else {
|
|
602
|
+
resolve(buf);
|
|
603
|
+
}
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (code === 3) {
|
|
607
|
+
// Ctrl-C
|
|
608
|
+
cleanup();
|
|
609
|
+
stderr.write("\n");
|
|
610
|
+
reject(new Error("Login cancelled."));
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (code === 4 && buf.length === 0) {
|
|
614
|
+
// Ctrl-D on empty buffer -> cancel
|
|
615
|
+
cleanup();
|
|
616
|
+
stderr.write("\n");
|
|
617
|
+
reject(new Error("Login cancelled."));
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (code === 8 || code === 127) {
|
|
621
|
+
// Backspace / DEL
|
|
622
|
+
buf = buf.slice(0, -1);
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
if (code < 32)
|
|
626
|
+
continue; // ignore other control chars
|
|
627
|
+
buf += ch;
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
const cleanup = () => {
|
|
631
|
+
try {
|
|
632
|
+
stdin.setRawMode(false);
|
|
633
|
+
}
|
|
634
|
+
catch { /* noop */ }
|
|
635
|
+
stdin.removeListener("data", onData);
|
|
636
|
+
if (typeof stdin.pause === "function") {
|
|
637
|
+
stdin.pause();
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
try {
|
|
641
|
+
stdin.setRawMode(true);
|
|
642
|
+
}
|
|
643
|
+
catch { /* noop */ }
|
|
644
|
+
if (typeof stdin.resume === "function") {
|
|
645
|
+
stdin.resume();
|
|
646
|
+
}
|
|
647
|
+
stdin.on("data", onData);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
515
650
|
/**
|
|
516
651
|
* OAuth2 Authorization Code login flow.
|
|
517
652
|
* 1. Register client (if not already registered), OR use a provided client ID
|
|
@@ -755,174 +890,6 @@ async function exchangeCodeForToken(baseUrl, code, clientId, clientSecret, redir
|
|
|
755
890
|
saveTokenConfig(token);
|
|
756
891
|
return token;
|
|
757
892
|
}
|
|
758
|
-
/**
|
|
759
|
-
* Playwright-automated OAuth2 login.
|
|
760
|
-
*
|
|
761
|
-
* Uses the full OAuth2 authorization code flow (same as `oauth2Login`) but
|
|
762
|
-
* automates the browser interaction with Playwright. This produces a
|
|
763
|
-
* refresh_token so the CLI can auto-refresh without re-login.
|
|
764
|
-
*
|
|
765
|
-
* When `username` and `password` are provided the browser runs headless and
|
|
766
|
-
* fills the login form automatically. Otherwise it opens a visible browser
|
|
767
|
-
* window for manual login (same UX as the old cookie-based flow).
|
|
768
|
-
*/
|
|
769
|
-
export async function playwrightLogin(baseUrl, options) {
|
|
770
|
-
return runWithTlsInsecure(options?.tlsInsecure, async () => {
|
|
771
|
-
const { createServer } = await import("node:http");
|
|
772
|
-
const { randomBytes } = await import("node:crypto");
|
|
773
|
-
let chromium;
|
|
774
|
-
try {
|
|
775
|
-
const modName = "playwright";
|
|
776
|
-
const pw = await import(/* webpackIgnore: true */ modName);
|
|
777
|
-
chromium = pw.chromium;
|
|
778
|
-
}
|
|
779
|
-
catch {
|
|
780
|
-
throw new Error("Playwright is not installed. Run:\n npm install playwright && npx playwright install chromium");
|
|
781
|
-
}
|
|
782
|
-
const base = normalizeBaseUrl(baseUrl);
|
|
783
|
-
const port = options?.port ?? DEFAULT_REDIRECT_PORT;
|
|
784
|
-
const scope = options?.scope ?? DEFAULT_SCOPE;
|
|
785
|
-
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
786
|
-
const hasCredentials = !!(options?.username && options?.password);
|
|
787
|
-
// Step 1: Ensure registered OAuth2 client (with stale-client auto-recovery)
|
|
788
|
-
let client;
|
|
789
|
-
try {
|
|
790
|
-
client = await resolveOrRegisterClient(base, redirectUri, scope);
|
|
791
|
-
}
|
|
792
|
-
catch (e) {
|
|
793
|
-
if (e instanceof HttpError && e.status === 404) {
|
|
794
|
-
process.stderr.write("OAuth2 endpoint not found (404). Saving platform in no-auth mode.\n");
|
|
795
|
-
return saveNoAuthPlatform(base, { tlsInsecure: options?.tlsInsecure });
|
|
796
|
-
}
|
|
797
|
-
throw e;
|
|
798
|
-
}
|
|
799
|
-
// Step 2: Generate CSRF state
|
|
800
|
-
const state = randomBytes(12).toString("hex");
|
|
801
|
-
// Step 3: Build authorization URL
|
|
802
|
-
const authParams = new URLSearchParams({
|
|
803
|
-
redirect_uri: redirectUri,
|
|
804
|
-
"x-forwarded-prefix": "",
|
|
805
|
-
client_id: client.clientId,
|
|
806
|
-
scope,
|
|
807
|
-
response_type: "code",
|
|
808
|
-
state,
|
|
809
|
-
lang: "zh-cn",
|
|
810
|
-
product: "adp",
|
|
811
|
-
});
|
|
812
|
-
const authUrl = `${base}/oauth2/auth?${authParams.toString()}`;
|
|
813
|
-
// Step 4: Start local callback server; exchange code inside handler, then show credentials HTML
|
|
814
|
-
let browser;
|
|
815
|
-
const token = await new Promise((resolve, reject) => {
|
|
816
|
-
const TIMEOUT_MS = hasCredentials ? 30_000 : 120_000;
|
|
817
|
-
let server;
|
|
818
|
-
const timeoutId = setTimeout(() => {
|
|
819
|
-
server?.close();
|
|
820
|
-
browser?.close();
|
|
821
|
-
reject(new Error(`OAuth2 login timed out (${TIMEOUT_MS / 1000}s). No authorization code received.`));
|
|
822
|
-
}, TIMEOUT_MS);
|
|
823
|
-
server = createServer((req, res) => {
|
|
824
|
-
void (async () => {
|
|
825
|
-
try {
|
|
826
|
-
const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
|
|
827
|
-
if (url.pathname !== "/callback") {
|
|
828
|
-
res.writeHead(404);
|
|
829
|
-
res.end();
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
const receivedState = url.searchParams.get("state");
|
|
833
|
-
const receivedCode = url.searchParams.get("code");
|
|
834
|
-
const callbackError = url.searchParams.get("error");
|
|
835
|
-
const callbackErrorDesc = url.searchParams.get("error_description");
|
|
836
|
-
if (receivedState !== state) {
|
|
837
|
-
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
838
|
-
res.end(buildCallbackExchangeErrorHtml("OAuth2 state mismatch — possible CSRF attack."));
|
|
839
|
-
clearTimeout(timeoutId);
|
|
840
|
-
server.close();
|
|
841
|
-
browser?.close();
|
|
842
|
-
reject(new Error("OAuth2 state mismatch — possible CSRF attack."));
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
if (callbackError) {
|
|
846
|
-
const msg = callbackErrorDesc
|
|
847
|
-
? `Authorization failed: ${callbackError} — ${callbackErrorDesc}`
|
|
848
|
-
: `Authorization failed: ${callbackError}`;
|
|
849
|
-
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
850
|
-
res.end(buildCallbackExchangeErrorHtml(msg));
|
|
851
|
-
clearTimeout(timeoutId);
|
|
852
|
-
server.close();
|
|
853
|
-
browser?.close();
|
|
854
|
-
reject(new Error(msg));
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
if (!receivedCode) {
|
|
858
|
-
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
|
|
859
|
-
res.end(buildCallbackExchangeErrorHtml("No authorization code received in callback."));
|
|
860
|
-
clearTimeout(timeoutId);
|
|
861
|
-
server.close();
|
|
862
|
-
browser?.close();
|
|
863
|
-
reject(new Error("No authorization code received in callback."));
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
const exchanged = await exchangeCodeForToken(base, receivedCode, client.clientId, client.clientSecret, redirectUri, undefined, options?.tlsInsecure);
|
|
867
|
-
const copyCommand = buildCopyCommand(base, client.clientId, client.clientSecret, exchanged.refreshToken, options?.tlsInsecure);
|
|
868
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
869
|
-
res.end(buildCallbackHtml(copyCommand));
|
|
870
|
-
clearTimeout(timeoutId);
|
|
871
|
-
server.close();
|
|
872
|
-
browser?.close();
|
|
873
|
-
resolve(exchanged);
|
|
874
|
-
}
|
|
875
|
-
catch (err) {
|
|
876
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
877
|
-
try {
|
|
878
|
-
res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" });
|
|
879
|
-
res.end(buildCallbackExchangeErrorHtml(message));
|
|
880
|
-
}
|
|
881
|
-
catch {
|
|
882
|
-
/* response may already be sent */
|
|
883
|
-
}
|
|
884
|
-
clearTimeout(timeoutId);
|
|
885
|
-
server.close();
|
|
886
|
-
browser?.close();
|
|
887
|
-
reject(err instanceof Error ? err : new Error(message));
|
|
888
|
-
}
|
|
889
|
-
})();
|
|
890
|
-
});
|
|
891
|
-
server.listen(port, "127.0.0.1", async () => {
|
|
892
|
-
try {
|
|
893
|
-
browser = await chromium.launch({ headless: hasCredentials });
|
|
894
|
-
const context = await browser.newContext({ ignoreHTTPSErrors: !!options?.tlsInsecure });
|
|
895
|
-
const page = await context.newPage();
|
|
896
|
-
// Navigate to OAuth2 auth URL — redirects to signin page
|
|
897
|
-
await page.goto(authUrl, { waitUntil: "networkidle", timeout: 30_000 });
|
|
898
|
-
if (hasCredentials) {
|
|
899
|
-
// Auto-fill credentials
|
|
900
|
-
await page.waitForSelector('input[name="account"]', { timeout: 10_000 });
|
|
901
|
-
await page.fill('input[name="account"]', options.username);
|
|
902
|
-
await page.fill('input[name="password"]', options.password);
|
|
903
|
-
await page.click("button.ant-btn-primary");
|
|
904
|
-
}
|
|
905
|
-
// else: visible browser — user logs in manually
|
|
906
|
-
// The OAuth2 callback will fire when login completes, resolving the promise above
|
|
907
|
-
}
|
|
908
|
-
catch (err) {
|
|
909
|
-
clearTimeout(timeoutId);
|
|
910
|
-
server.close();
|
|
911
|
-
browser?.close();
|
|
912
|
-
reject(err);
|
|
913
|
-
}
|
|
914
|
-
});
|
|
915
|
-
});
|
|
916
|
-
if (hasCredentials) {
|
|
917
|
-
const copyCommand = buildCopyCommand(base, client.clientId, client.clientSecret, token.refreshToken, options?.tlsInsecure);
|
|
918
|
-
process.stderr.write("\nHeadless login: copy this command and run it on a machine without a browser, or use `kweaver auth export`:\n\n" +
|
|
919
|
-
copyCommand +
|
|
920
|
-
"\n\n");
|
|
921
|
-
}
|
|
922
|
-
setCurrentPlatform(base);
|
|
923
|
-
return token;
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
893
|
function mergeCookieJarForSignin(existing, response) {
|
|
927
894
|
const setCookies = typeof response.headers.getSetCookie === "function"
|
|
928
895
|
? response.headers.getSetCookie()
|
|
@@ -1041,7 +1008,7 @@ async function followSigninRedirectsUntilCallback(startUrl, initialJar, state, r
|
|
|
1041
1008
|
return consentResult;
|
|
1042
1009
|
}
|
|
1043
1010
|
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
|
|
1011
|
+
`If this is a consent or MFA screen, use browser login (kweaver auth login <url>).`);
|
|
1045
1012
|
}
|
|
1046
1013
|
const text = await resp.text().catch(() => "");
|
|
1047
1014
|
throw new HttpError(resp.status, resp.statusText, text);
|
|
@@ -1094,17 +1061,38 @@ async function tryAcceptConsentAfterSignin(base, pageUrl, html, jar, scope, stat
|
|
|
1094
1061
|
}
|
|
1095
1062
|
return null;
|
|
1096
1063
|
}
|
|
1097
|
-
const STUDIOWEB_SHELL_UNAVAILABLE_SNIPPETS = [
|
|
1098
|
-
"Studioweb signin endpoint not available",
|
|
1099
|
-
"Cannot reach studioweb signin endpoint",
|
|
1100
|
-
];
|
|
1101
1064
|
/**
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
1065
|
+
* Build the JSON body for `POST /oauth2/signin` (matches the browser `oauth2-ui` form).
|
|
1066
|
+
*
|
|
1067
|
+
* `device.client_type` MUST be a value present in the EACP whitelist defined by
|
|
1068
|
+
* `kweaver/deploy/auto_cofig/auto_config.sh`. `console_web` is the canonical CLI value
|
|
1069
|
+
* (also used by `kweaver-admin`); other values such as `unknown` are rejected by strict
|
|
1070
|
+
* deployments with `管理员已禁止此类客户端登录` — surfaced upstream as a `request_forbidden`
|
|
1071
|
+
* `No CSRF value available in the session cookie` error after Hydra discards the rejected
|
|
1072
|
+
* login challenge.
|
|
1073
|
+
*
|
|
1074
|
+
* `vcode` and `dualfactorauthinfo` must be present even when empty; otherwise eachttpserver
|
|
1075
|
+
* returns HTTP 400 (invalid parameter).
|
|
1104
1076
|
*/
|
|
1105
|
-
export function
|
|
1106
|
-
|
|
1107
|
-
|
|
1077
|
+
export function buildOauth2SigninPostBody(opts) {
|
|
1078
|
+
return {
|
|
1079
|
+
_csrf: opts.csrftoken,
|
|
1080
|
+
challenge: opts.challenge,
|
|
1081
|
+
account: opts.account,
|
|
1082
|
+
password: opts.passwordCipher,
|
|
1083
|
+
vcode: { id: "", content: "" },
|
|
1084
|
+
dualfactorauthinfo: {
|
|
1085
|
+
validcode: { vcode: "" },
|
|
1086
|
+
OTP: { OTP: "" },
|
|
1087
|
+
},
|
|
1088
|
+
remember: opts.remember,
|
|
1089
|
+
device: {
|
|
1090
|
+
name: "",
|
|
1091
|
+
description: "",
|
|
1092
|
+
client_type: "console_web",
|
|
1093
|
+
udids: [],
|
|
1094
|
+
},
|
|
1095
|
+
};
|
|
1108
1096
|
}
|
|
1109
1097
|
/**
|
|
1110
1098
|
* OAuth2 Authorization Code login using HTTP **only**: `GET /oauth2/signin` (Next.js shell) and
|
|
@@ -1127,27 +1115,10 @@ export async function oauth2PasswordSigninLogin(baseUrl, options) {
|
|
|
1127
1115
|
(typeof process.env.KWEAVER_OAUTH_PRODUCT === "string" && process.env.KWEAVER_OAUTH_PRODUCT.trim()
|
|
1128
1116
|
? process.env.KWEAVER_OAUTH_PRODUCT.trim()
|
|
1129
1117
|
: "adp");
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
-
}
|
|
1118
|
+
// Note: previously we pre-flighted `/interface/studioweb/login` to detect deployments
|
|
1119
|
+
// missing the Studio web shell. The probe added an extra round-trip and was unreliable
|
|
1120
|
+
// (see kweaver-admin which works fine without it). HTTP sign-in only needs `/oauth2/auth`
|
|
1121
|
+
// and `/oauth2/signin`; if either is missing the request below will surface a precise error.
|
|
1151
1122
|
let client;
|
|
1152
1123
|
try {
|
|
1153
1124
|
client = await resolveOrRegisterClient(base, redirectUri, scope, {
|
|
@@ -1231,26 +1202,13 @@ export async function oauth2PasswordSigninLogin(baseUrl, options) {
|
|
|
1231
1202
|
: options.signinPasswordBase64Plain === false
|
|
1232
1203
|
? false
|
|
1233
1204
|
: process.env.KWEAVER_SIGNIN_PASSWORD_B64_RSA_MIN !== "1";
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
const postBody = {
|
|
1237
|
-
_csrf: csrftoken,
|
|
1205
|
+
const postBody = buildOauth2SigninPostBody({
|
|
1206
|
+
csrftoken,
|
|
1238
1207
|
challenge: loginChallenge,
|
|
1239
1208
|
account: options.username,
|
|
1240
|
-
|
|
1241
|
-
vcode: { id: "", content: "" },
|
|
1242
|
-
dualfactorauthinfo: {
|
|
1243
|
-
validcode: { vcode: "" },
|
|
1244
|
-
OTP: { OTP: "" },
|
|
1245
|
-
},
|
|
1209
|
+
passwordCipher: "",
|
|
1246
1210
|
remember,
|
|
1247
|
-
|
|
1248
|
-
name: "",
|
|
1249
|
-
description: "",
|
|
1250
|
-
client_type: "unknown",
|
|
1251
|
-
udids: [],
|
|
1252
|
-
},
|
|
1253
|
-
};
|
|
1211
|
+
});
|
|
1254
1212
|
const origin = new URL(base).origin;
|
|
1255
1213
|
/** Some gateways (e.g. DIP) return HTTP 200 + `{"redirect":"..."}` instead of 3xx Location. */
|
|
1256
1214
|
let signinRedirectFromJson;
|
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,8 @@ import { runExploreCommand } from "./commands/explore.js";
|
|
|
12
12
|
import { runDataviewCommand } from "./commands/dataview.js";
|
|
13
13
|
import { runSkillCommand } from "./commands/skill.js";
|
|
14
14
|
import { runTokenCommand } from "./commands/token.js";
|
|
15
|
+
import { runToolboxCommand } from "./commands/toolbox.js";
|
|
16
|
+
import { runToolCommand } from "./commands/tool.js";
|
|
15
17
|
import { runVegaCommand } from "./commands/vega.js";
|
|
16
18
|
function printHelp() {
|
|
17
19
|
console.log(`kweaver
|
|
@@ -21,7 +23,7 @@ Usage:
|
|
|
21
23
|
kweaver --version | -V
|
|
22
24
|
kweaver --help | -h
|
|
23
25
|
|
|
24
|
-
kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--
|
|
26
|
+
kweaver auth <platform-url> [--alias name] [--no-auth] [--no-browser] [-u user] [-p pass] [--http-signin] [--insecure|-k]
|
|
25
27
|
kweaver auth login <platform-url> (alias for auth <url>)
|
|
26
28
|
kweaver auth login <url> --client-id ID --client-secret S --refresh-token T (run on host without browser)
|
|
27
29
|
kweaver auth whoami [platform-url|alias] [--json]
|
|
@@ -99,6 +101,15 @@ Usage:
|
|
|
99
101
|
kweaver skill read-file <skill-id> <rel-path> [--raw] [--output file]
|
|
100
102
|
kweaver skill download|install <skill-id> [path] [options]
|
|
101
103
|
|
|
104
|
+
kweaver toolbox create --name <n> --service-url <url> [--description <d>] [-bd value]
|
|
105
|
+
kweaver toolbox list [--keyword X] [--limit N] [--offset N] [-bd value]
|
|
106
|
+
kweaver toolbox publish|unpublish <box-id> [-bd value]
|
|
107
|
+
kweaver toolbox delete <box-id> [-y] [-bd value]
|
|
108
|
+
|
|
109
|
+
kweaver tool upload --toolbox <box-id> <openapi-spec-path> [--metadata-type openapi]
|
|
110
|
+
kweaver tool list --toolbox <box-id> [-bd value]
|
|
111
|
+
kweaver tool enable|disable --toolbox <box-id> <tool-id>... [-bd value]
|
|
112
|
+
|
|
102
113
|
kweaver vega health|stats|inspect
|
|
103
114
|
kweaver vega catalog list|get|health|test-connection|discover|resources [options]
|
|
104
115
|
kweaver vega resource list|get|query [options]
|
|
@@ -129,6 +140,8 @@ Commands:
|
|
|
129
140
|
object-type, relation-type, subgraph, action-type, action-execution, action-log)
|
|
130
141
|
config Per-platform configuration (business domain)
|
|
131
142
|
skill Skill registry and market (register, search, progressive read, download/install)
|
|
143
|
+
toolbox Agent toolbox lifecycle (create, list, publish, delete)
|
|
144
|
+
tool Tools inside a toolbox (upload OpenAPI spec, list, enable/disable)
|
|
132
145
|
vega Vega observability (catalog, resource, query/sql, connector-type, health/stats/inspect)
|
|
133
146
|
context-loader Context-loader MCP (config, tools, resources, prompts, kn-search, query-*, etc.)
|
|
134
147
|
help Show this message`);
|
|
@@ -195,6 +208,12 @@ export async function run(argv) {
|
|
|
195
208
|
if (command === "skill") {
|
|
196
209
|
return runSkillCommand(rest);
|
|
197
210
|
}
|
|
211
|
+
if (command === "toolbox") {
|
|
212
|
+
return runToolboxCommand(rest);
|
|
213
|
+
}
|
|
214
|
+
if (command === "tool") {
|
|
215
|
+
return runToolCommand(rest);
|
|
216
|
+
}
|
|
198
217
|
if (command === "context-loader" || command === "context") {
|
|
199
218
|
return runContextLoaderCommand(rest);
|
|
200
219
|
}
|