@xqyz/xq-cli 0.3.12 → 0.3.14
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/CHANGELOG.md +33 -1
- package/README.md +3 -8
- package/package.json +1 -1
- package/src/cli.mjs +20 -141
package/CHANGELOG.md
CHANGED
|
@@ -10,12 +10,44 @@
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
(暂无未发布变更)
|
|
14
|
+
|
|
15
|
+
## [xq-cli 0.3.14 / WorkBuddy Plugin 0.10.33] - 2026-08-28
|
|
16
|
+
|
|
17
|
+
### 登录与配置卡体验
|
|
18
|
+
|
|
19
|
+
- CLI API Key 登录页增加直接进入 API Key 管理页的入口。
|
|
20
|
+
- WorkBuddy 未检测到登录状态时自动打开本机 API Key 登录页,并继续使用同一任务状态等待登录完成。
|
|
21
|
+
- 模板 5 自定义编号改为浏览器参考式分组布局;导出格式折叠入口、页边距和图片尺寸设置增加更清晰的视觉层次。
|
|
22
|
+
- `@xqyz/xq-cli@0.3.14` 与 `@xqyz/workbuddy-plugin-xique@0.10.33` 已发布到 npm;SkillHub `1.0.15` 仅生成上传包,需发布人手动上传。
|
|
23
|
+
|
|
24
|
+
## [xq-cli 0.3.13 / WorkBuddy Plugin 0.10.32] - 2026-08-28
|
|
25
|
+
|
|
26
|
+
### API Key-only 登录
|
|
27
|
+
|
|
28
|
+
- CLI 本机登录页只保留 API Key 输入,并继续通过 `/user/info` 使用现有校验链路。
|
|
29
|
+
- 移除 CLI 和后端旧的账号密码、浏览器授权及 CLI 一次性授权码流程。
|
|
30
|
+
- WorkBuddy 安装器只在没有 API Key 时打开本机登录页,并同步内置 CLI `0.3.13`。
|
|
31
|
+
- `@xqyz/xq-cli@0.3.13` 与 `@xqyz/workbuddy-plugin-xique@0.10.32` 已发布到 npm;SkillHub `1.0.14` 仅生成上传包,需发布人手动上传。
|
|
32
|
+
|
|
33
|
+
## [WorkBuddy Plugin 0.10.31] - 2026-08-28
|
|
34
|
+
|
|
35
|
+
### 模板 5 自定义编号排版
|
|
36
|
+
|
|
37
|
+
- 按 Web 页面参考重排模板 5 自定义编号:标题设置、正文序号设置、起始编号和各级排版控件层次更清晰。
|
|
38
|
+
- 宽屏按行横向排列,窄卡片按稳定的 2/3/3 控件分组换行,避免自适应网格造成错位或挤压。
|
|
39
|
+
- 保持 `customNumbering` 字段结构、默认值和导出参数不变。
|
|
40
|
+
- `@xqyz/workbuddy-plugin-xique@0.10.31` 已发布,npm `latest` 和 `workbuddy-stable` 均已指向该版本。
|
|
41
|
+
- 已生成 `output/skillhub-xique-ai-1.0.13.zip`,由发布人手动上传;SkillHub 不由代理自动发布。
|
|
42
|
+
|
|
43
|
+
## [WorkBuddy Plugin 0.10.30] - 2026-08-27
|
|
44
|
+
|
|
13
45
|
### 供应方式确认恢复
|
|
14
46
|
|
|
15
47
|
- 修复解析完成但 `bidderIdentity_judgement=0` 时 WorkBuddy 直接阻塞的问题,新增“我们自己生产 / 我们采购供应”选择卡。
|
|
16
48
|
- 供应方式提交后沿用同一 `runId/cid/uuid` 继续生成,兼容旧版本已标记为 `blocked` 的历史任务,并防止重复提交。
|
|
17
49
|
- 同步 CLI `bidder-role` 命令、MCP App、Hook 白名单、Connector/Plugin Skill 和回归测试。
|
|
18
|
-
-
|
|
50
|
+
- `@xqyz/xq-cli@0.3.12` 与 `@xqyz/workbuddy-plugin-xique@0.10.30` 已发布,npm `latest` 和 `workbuddy-stable` 均已指向对应版本;SkillHub `1.0.12` 上传包由发布人手动上传。
|
|
19
51
|
|
|
20
52
|
## [WorkBuddy Plugin 0.10.29] - 2026-08-27
|
|
21
53
|
|
package/README.md
CHANGED
|
@@ -53,9 +53,7 @@ npm install -g .\xqyz-xq-cli-0.2.0.tgz
|
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
xq-cli login
|
|
56
|
-
xq-cli login --name your-account --password your-password
|
|
57
56
|
xq-cli login --api-key xq_sk_xxx
|
|
58
|
-
xq-cli login --browser
|
|
59
57
|
xq-cli wizard
|
|
60
58
|
xq-cli wizard --file D:\bids\tender.docx
|
|
61
59
|
xq-cli init --file D:\bids\tender.docx --wait
|
|
@@ -77,15 +75,12 @@ xq-cli export --cid <cid> --out D:\output
|
|
|
77
75
|
|
|
78
76
|
Fresh installs default to the production API address `https://ai.bidfile.qianlima.com/api`. Use `--base-url` only when you need to target another API endpoint.
|
|
79
77
|
|
|
80
|
-
`login` now
|
|
78
|
+
`login` now supports API Key authentication only. Without `--api-key`, it opens a local page for entering the API Key; with `--api-key`, it verifies and saves the key directly. The old account/password and browser-authorization options are no longer supported.
|
|
81
79
|
|
|
82
80
|
When no credential flags are supplied, `xq-cli login` opens a local login page on
|
|
83
|
-
`127.0.0.1`
|
|
84
|
-
Xique API Key management page. The page submits the Key only to the local CLI,
|
|
81
|
+
`127.0.0.1` for an API Key from the Xique API Key management page. The page submits the Key only to the local CLI,
|
|
85
82
|
which verifies it with `/user/info` before saving it to the user-scoped config.
|
|
86
|
-
Use `xq-cli login --
|
|
87
|
-
authorization directly, or `xq-cli login --no-open` to copy the local page URL
|
|
88
|
-
manually. Never paste an API Key into chat or commit it to a repository.
|
|
83
|
+
Use `xq-cli login --no-open` to copy the local page URL manually. Never paste an API Key into chat or commit it to a repository.
|
|
89
84
|
|
|
90
85
|
`directory view` reads the complete generated directory tree without triggering outline, directory, or content generation. Add `--json` for the raw API structure, or `--out <file-or-directory>` to save a UTF-8 JSON document.
|
|
91
86
|
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -7,7 +7,6 @@ import os from 'os';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import readline from 'node:readline/promises';
|
|
9
9
|
import { spawn } from 'child_process';
|
|
10
|
-
import { randomUUID } from 'crypto';
|
|
11
10
|
import axios from 'axios';
|
|
12
11
|
import FormData from 'form-data';
|
|
13
12
|
import { templateData } from './templateData.mjs';
|
|
@@ -43,6 +42,7 @@ const DEFAULT_PARSER_WAKEUP_TIMEOUT_MS = 30_000;
|
|
|
43
42
|
const PARSER_WAKEUP_INTERVAL_MS = 2_000;
|
|
44
43
|
const DEFAULT_SOURCE_FROM = 'xique';
|
|
45
44
|
const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
45
|
+
const API_KEY_MANAGEMENT_URL = 'https://xiquebiaoshu.com/account/api-keys';
|
|
46
46
|
const DEFAULT_EXPORT_TEMPLATE_INDEX = 1;
|
|
47
47
|
const DEFAULT_PAGE_SCOPE_CODE = 1;
|
|
48
48
|
const DEFAULT_BASE = '0';
|
|
@@ -73,7 +73,6 @@ const DIRECTORY_SEQUEL_RETRY_INTERVAL_MS = 60_000;
|
|
|
73
73
|
const GENERATE_UNFINISHED_RETRY_INTERVAL_MS = 60_000;
|
|
74
74
|
const RETRYABLE_NETWORK_CODES = new Set(['ECONNRESET', 'ECONNABORTED', 'ETIMEDOUT', 'EAI_AGAIN']);
|
|
75
75
|
const SOFT_CONTENT_TRIGGER_CODES = new Set([411, 429]);
|
|
76
|
-
const BROWSER_LOGIN_CALLBACK_PATH = '/callback';
|
|
77
76
|
const HTTP_AGENT = new http.Agent({ keepAlive: true });
|
|
78
77
|
const HTTPS_AGENT = new https.Agent({ keepAlive: true });
|
|
79
78
|
const AUTO_IGNORE_ISSUE_CONFIG = {
|
|
@@ -498,9 +497,7 @@ Plan actions
|
|
|
498
497
|
Examples
|
|
499
498
|
xq-cli login
|
|
500
499
|
xq-cli login --no-open
|
|
501
|
-
xq-cli login --name your-account --password your-password
|
|
502
500
|
xq-cli login --api-key xq_sk_xxx
|
|
503
|
-
xq-cli login --browser
|
|
504
501
|
$env:XQ_API_KEY="xq_sk_xxx"
|
|
505
502
|
xq-cli init --file D:\\bids\\tender.docx --wait
|
|
506
503
|
xq-cli parse-status --cid <cid> --uuid <uuid> --wait
|
|
@@ -545,7 +542,6 @@ Common options
|
|
|
545
542
|
--interval-sec <n> Poll interval in seconds, default 10
|
|
546
543
|
--reference-type <0|1|2> Outline reference choice: 0=merge, 1=format only, 2=score only
|
|
547
544
|
--bidder-role <1|2> Goods supply method: 1=self production, 2=procured supply
|
|
548
|
-
--browser Skip the local login page and open browser authorization directly
|
|
549
545
|
--api-key <key> Save a user API key locally and verify it with /user/info
|
|
550
546
|
--no-open Do not open the local login page automatically
|
|
551
547
|
--interactive Open prompt menus for the current command
|
|
@@ -617,6 +613,9 @@ async function handleLogin(args, state) {
|
|
|
617
613
|
if (!baseUrl) {
|
|
618
614
|
throw new Error('Missing base URL. Pass --base-url <url>.');
|
|
619
615
|
}
|
|
616
|
+
if (args.browser !== undefined || stringOption(args, 'name') || stringOption(args, 'password')) {
|
|
617
|
+
throw new Error('当前仅支持 API Key 登录,请使用 xq-cli login --api-key <key> 或打开本机登录页。');
|
|
618
|
+
}
|
|
620
619
|
const sourceFrom = stringOption(args, 'sourceFrom', DEFAULT_SOURCE_FROM);
|
|
621
620
|
const apiKey = normalizeApiKey(stringOption(args, 'apiKey'));
|
|
622
621
|
if (apiKey) {
|
|
@@ -646,12 +645,8 @@ async function handleLogin(args, state) {
|
|
|
646
645
|
]);
|
|
647
646
|
return;
|
|
648
647
|
}
|
|
649
|
-
const loginMode =
|
|
650
|
-
const loginResult =
|
|
651
|
-
? { result: await loginWithBrowser(baseUrl, sourceFrom, args), authType: 'token' }
|
|
652
|
-
: loginMode === 'page'
|
|
653
|
-
? await loginWithLoginPage(baseUrl, sourceFrom, args)
|
|
654
|
-
: { result: await loginWithPassword(baseUrl, sourceFrom, args), authType: 'token' };
|
|
648
|
+
const loginMode = 'page';
|
|
649
|
+
const loginResult = await loginWithLoginPage(baseUrl, args);
|
|
655
650
|
const result = loginResult.result;
|
|
656
651
|
const apiKeyFromPage = loginResult.apiKey || '';
|
|
657
652
|
|
|
@@ -670,7 +665,6 @@ async function handleLogin(args, state) {
|
|
|
670
665
|
authType: loginResult.authType,
|
|
671
666
|
baseUrl,
|
|
672
667
|
username: state.user.username,
|
|
673
|
-
tokenSaved: loginResult.authType === 'token',
|
|
674
668
|
apiKeySaved: loginResult.authType === 'api-key',
|
|
675
669
|
}, [
|
|
676
670
|
`login success: ${state.user.username}`,
|
|
@@ -681,83 +675,24 @@ async function handleLogin(args, state) {
|
|
|
681
675
|
]);
|
|
682
676
|
}
|
|
683
677
|
|
|
684
|
-
function resolveLoginMode(args) {
|
|
685
|
-
if (booleanOption(args, 'browser')) {
|
|
686
|
-
return 'browser';
|
|
687
|
-
}
|
|
688
|
-
if (stringOption(args, 'apiKey')) {
|
|
689
|
-
return 'api-key';
|
|
690
|
-
}
|
|
691
|
-
const hasName = stringOption(args, 'name');
|
|
692
|
-
const hasPassword = stringOption(args, 'password');
|
|
693
|
-
return hasName || hasPassword ? 'password' : 'page';
|
|
694
|
-
}
|
|
695
|
-
|
|
696
678
|
async function loginWithApiKey(baseUrl, apiKey) {
|
|
697
679
|
const client = createClient(baseUrl, apiKey);
|
|
698
680
|
return getJson(client, '/user/info');
|
|
699
681
|
}
|
|
700
682
|
|
|
701
|
-
async function
|
|
702
|
-
const name = requiredOption(args, 'name');
|
|
703
|
-
const password = requiredOption(args, 'password');
|
|
704
|
-
const client = createClient(baseUrl);
|
|
705
|
-
return postJson(client, '/user/login', {
|
|
706
|
-
name,
|
|
707
|
-
password,
|
|
708
|
-
sourceFrom,
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
async function loginWithBrowser(baseUrl, sourceFrom, args) {
|
|
713
|
-
const client = createClient(baseUrl);
|
|
714
|
-
const loginState = randomUUID();
|
|
715
|
-
const timeoutMs = secondsToMs(numberOption(args, 'timeoutSec', DEFAULT_LOGIN_TIMEOUT_MS / 1000));
|
|
716
|
-
const callbackServer = await startBrowserLoginServer();
|
|
717
|
-
const authorizeUrl = buildBrowserAuthorizeUrl(baseUrl, callbackServer.redirectUri, loginState, sourceFrom);
|
|
718
|
-
|
|
719
|
-
writeNotice(args, `browser login url: ${authorizeUrl}`);
|
|
720
|
-
if (openBrowser(authorizeUrl)) {
|
|
721
|
-
writeNotice(args, 'browser login: waiting for authorization callback');
|
|
722
|
-
} else {
|
|
723
|
-
writeNotice(args, 'browser login: automatic open failed, paste the URL above into a browser on this machine');
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
try {
|
|
727
|
-
const callback = await withTimeout(
|
|
728
|
-
callbackServer.result,
|
|
729
|
-
timeoutMs,
|
|
730
|
-
`Browser login timed out after ${Math.round(timeoutMs / 1000)} seconds.`,
|
|
731
|
-
);
|
|
732
|
-
if (callback.state !== loginState) {
|
|
733
|
-
throw new Error('Browser login state validation failed.');
|
|
734
|
-
}
|
|
735
|
-
return await postJson(client, '/user/cli/token', {
|
|
736
|
-
code: callback.code,
|
|
737
|
-
state: callback.state,
|
|
738
|
-
});
|
|
739
|
-
} finally {
|
|
740
|
-
await callbackServer.close();
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
async function loginWithLoginPage(baseUrl, sourceFrom, args) {
|
|
683
|
+
async function loginWithLoginPage(baseUrl, args) {
|
|
745
684
|
const timeoutMs = secondsToMs(numberOption(args, 'timeoutSec', DEFAULT_LOGIN_TIMEOUT_MS / 1000));
|
|
746
|
-
const
|
|
747
|
-
const server = await startBrowserLoginServer({
|
|
748
|
-
loginState,
|
|
685
|
+
const server = await startApiKeyLoginServer({
|
|
749
686
|
onApiKey: async apiKey => ({
|
|
750
687
|
result: await loginWithApiKey(baseUrl, apiKey),
|
|
751
688
|
apiKey,
|
|
752
689
|
}),
|
|
753
|
-
renderPage: (
|
|
754
|
-
authorizeUrl: buildBrowserAuthorizeUrl(baseUrl, redirectUri, loginState, sourceFrom),
|
|
755
|
-
}),
|
|
690
|
+
renderPage: () => renderLoginPage(),
|
|
756
691
|
});
|
|
757
692
|
writeNotice(args, `login page url: ${server.pageUrl}`);
|
|
758
693
|
if (process.env.XQ_CLI_NO_BROWSER !== '1') {
|
|
759
694
|
if (openBrowser(server.pageUrl)) {
|
|
760
|
-
writeNotice(args, 'login page: waiting for API key
|
|
695
|
+
writeNotice(args, 'login page: waiting for API key');
|
|
761
696
|
} else {
|
|
762
697
|
writeNotice(args, 'login page: automatic open failed, paste the URL above into a browser on this machine');
|
|
763
698
|
}
|
|
@@ -769,19 +704,10 @@ async function loginWithLoginPage(baseUrl, sourceFrom, args) {
|
|
|
769
704
|
timeoutMs,
|
|
770
705
|
`Login timed out after ${Math.round(timeoutMs / 1000)} seconds.`,
|
|
771
706
|
);
|
|
772
|
-
if (selected.mode === 'api-key') {
|
|
773
|
-
return {
|
|
774
|
-
result: selected.result,
|
|
775
|
-
apiKey: selected.apiKey,
|
|
776
|
-
authType: 'api-key',
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
707
|
return {
|
|
780
|
-
result:
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}),
|
|
784
|
-
authType: 'token',
|
|
708
|
+
result: selected.result,
|
|
709
|
+
apiKey: selected.apiKey,
|
|
710
|
+
authType: 'api-key',
|
|
785
711
|
};
|
|
786
712
|
} finally {
|
|
787
713
|
await server.close();
|
|
@@ -4246,16 +4172,7 @@ function saveLoginState(state, options) {
|
|
|
4246
4172
|
writeState(state);
|
|
4247
4173
|
}
|
|
4248
4174
|
|
|
4249
|
-
function
|
|
4250
|
-
const query = new URLSearchParams({
|
|
4251
|
-
redirect_uri: redirectUri,
|
|
4252
|
-
state,
|
|
4253
|
-
sourceFrom,
|
|
4254
|
-
});
|
|
4255
|
-
return `${baseUrl}/user/cli/authorize?${query.toString()}`;
|
|
4256
|
-
}
|
|
4257
|
-
|
|
4258
|
-
async function startBrowserLoginServer({ loginState = '', renderPage = null, onApiKey = null } = {}) {
|
|
4175
|
+
async function startApiKeyLoginServer({ renderPage = null, onApiKey = null } = {}) {
|
|
4259
4176
|
let settled = false;
|
|
4260
4177
|
let resolveResult;
|
|
4261
4178
|
let rejectResult;
|
|
@@ -4275,9 +4192,7 @@ async function startBrowserLoginServer({ loginState = '', renderPage = null, onA
|
|
|
4275
4192
|
}
|
|
4276
4193
|
if (pathname === '/' && typeof renderPage === 'function') {
|
|
4277
4194
|
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
4278
|
-
response.end(renderPage(
|
|
4279
|
-
redirectUri: `http://127.0.0.1:${origin?.port || 0}${BROWSER_LOGIN_CALLBACK_PATH}`,
|
|
4280
|
-
}));
|
|
4195
|
+
response.end(renderPage());
|
|
4281
4196
|
return;
|
|
4282
4197
|
}
|
|
4283
4198
|
if (pathname === '/api-key' && request.method === 'POST' && typeof onApiKey === 'function') {
|
|
@@ -4300,39 +4215,8 @@ async function startBrowserLoginServer({ loginState = '', renderPage = null, onA
|
|
|
4300
4215
|
}
|
|
4301
4216
|
return;
|
|
4302
4217
|
}
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
response.end('Not found');
|
|
4306
|
-
return;
|
|
4307
|
-
}
|
|
4308
|
-
|
|
4309
|
-
const code = requestUrl.searchParams.get('code');
|
|
4310
|
-
const state = requestUrl.searchParams.get('state');
|
|
4311
|
-
if (!code || !state) {
|
|
4312
|
-
response.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
4313
|
-
response.end(renderBrowserLoginResultPage('登录未完成', '缺少 code 或 state,返回 CLI 重新执行登录即可。'));
|
|
4314
|
-
if (!settled) {
|
|
4315
|
-
settled = true;
|
|
4316
|
-
rejectResult(new Error('Browser login callback missing code or state.'));
|
|
4317
|
-
}
|
|
4318
|
-
return;
|
|
4319
|
-
}
|
|
4320
|
-
|
|
4321
|
-
if (loginState && state !== loginState) {
|
|
4322
|
-
response.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
4323
|
-
response.end(renderBrowserLoginResultPage('登录未完成', '授权状态校验失败,请返回 CLI 重新执行登录。'));
|
|
4324
|
-
if (!settled) {
|
|
4325
|
-
settled = true;
|
|
4326
|
-
rejectResult(new Error('Browser login state validation failed.'));
|
|
4327
|
-
}
|
|
4328
|
-
return;
|
|
4329
|
-
}
|
|
4330
|
-
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
4331
|
-
response.end(renderBrowserLoginResultPage('登录成功', '授权已完成,现在可以回到 CLI 继续使用了。'));
|
|
4332
|
-
if (!settled) {
|
|
4333
|
-
settled = true;
|
|
4334
|
-
resolveResult({ mode: 'browser', code, state });
|
|
4335
|
-
}
|
|
4218
|
+
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
4219
|
+
response.end('Not found');
|
|
4336
4220
|
});
|
|
4337
4221
|
server.on('error', error => {
|
|
4338
4222
|
if (!settled) {
|
|
@@ -4350,11 +4234,8 @@ async function startBrowserLoginServer({ loginState = '', renderPage = null, onA
|
|
|
4350
4234
|
});
|
|
4351
4235
|
|
|
4352
4236
|
const address = server.address();
|
|
4353
|
-
const redirectUri = `http://127.0.0.1:${address.port}${BROWSER_LOGIN_CALLBACK_PATH}`;
|
|
4354
4237
|
return {
|
|
4355
|
-
redirectUri,
|
|
4356
4238
|
pageUrl: `http://127.0.0.1:${address.port}/`,
|
|
4357
|
-
loginState,
|
|
4358
4239
|
result,
|
|
4359
4240
|
close: () => closeServer(server),
|
|
4360
4241
|
};
|
|
@@ -4381,7 +4262,7 @@ async function readJsonRequestBody(request, maxBytes) {
|
|
|
4381
4262
|
}
|
|
4382
4263
|
}
|
|
4383
4264
|
|
|
4384
|
-
function renderLoginPage(
|
|
4265
|
+
function renderLoginPage() {
|
|
4385
4266
|
return `<!doctype html>
|
|
4386
4267
|
<html lang="zh-CN">
|
|
4387
4268
|
<head>
|
|
@@ -4417,7 +4298,7 @@ function renderLoginPage({ authorizeUrl }) {
|
|
|
4417
4298
|
button{margin-top:13px;border:0;background:#536da9;color:#fff;box-shadow:0 10px 22px rgba(83,109,169,.23);} button:hover{background:#47619b;box-shadow:0 13px 26px rgba(83,109,169,.28);transform:translateY(-1px);} button:disabled{cursor:wait;opacity:.72;transform:none;}
|
|
4418
4299
|
.button-arrow{font-size:18px;line-height:1;}.divider{display:flex;align-items:center;gap:12px;color:#a1aec2;margin:23px 0 13px;font-size:12px;}.divider:before,.divider:after{content:"";height:1px;background:#e8edf4;flex:1;}
|
|
4419
4300
|
a{border:1px solid #d5dfed;color:#304364;background:#fff;} a:hover{border-color:#91a6cc;background:#f7f9fd;transform:translateY(-1px);}
|
|
4420
|
-
|
|
4301
|
+
.hint{margin:16px 0 0;color:#8390a6;font-size:12px;line-height:1.7;}.hint strong{color:#536da9;font-weight:700;}.hint a{display:inline;width:auto;min-height:0;margin:0;padding:0;border:0;border-radius:0;color:#536da9;background:transparent;font-size:inherit;font-weight:700;line-height:inherit;text-align:left;text-decoration:underline;box-shadow:none;}.hint a:hover{background:transparent;border:0;color:#304f91;transform:none;box-shadow:none;}.secure-note{display:flex;align-items:center;gap:8px;margin-top:30px;padding-top:17px;border-top:1px solid #edf0f5;color:#9aa6b7;font-size:11px;}.secure-note span{width:7px;height:7px;border-radius:50%;background:#35a779;box-shadow:0 0 0 4px rgba(53,167,121,.10);}
|
|
4421
4302
|
@media (max-width:820px){body{padding:18px;}.page-shell{grid-template-columns:1fr;max-width:520px;}.brand-panel{display:none;}.card{min-height:0;padding:32px 26px;border-radius:20px;}}
|
|
4422
4303
|
@media (max-width:420px){body{padding:12px;}.card{padding:27px 20px;}.card-heading h2{font-size:22px;}}
|
|
4423
4304
|
</style>
|
|
@@ -4448,9 +4329,7 @@ function renderLoginPage({ authorizeUrl }) {
|
|
|
4448
4329
|
<div class="input-wrap"><span class="field-icon" aria-hidden="true"></span><input id="api-key" name="apiKey" type="password" autocomplete="off" placeholder="xq_sk_..." required /></div>
|
|
4449
4330
|
<button type="submit"><span>使用 API Key 登录</span><span class="button-arrow">→</span></button>
|
|
4450
4331
|
</form>
|
|
4451
|
-
<
|
|
4452
|
-
<a href="${escapeHtml(authorizeUrl)}"><span>使用浏览器授权登录</span><span class="button-arrow">↗</span></a>
|
|
4453
|
-
<p class="hint">API Key 可在喜鹊的 API Key 管理页创建。验证成功后即可关闭页面。</p>
|
|
4332
|
+
<p class="hint">没有 API Key?<a href="${API_KEY_MANAGEMENT_URL}" target="_blank" rel="noopener noreferrer">前往 API Key 管理页获取</a>。验证成功后即可关闭页面。</p>
|
|
4454
4333
|
<div class="secure-note"><span></span>登录状态仅用于当前 CLI 工作流</div>
|
|
4455
4334
|
</main>
|
|
4456
4335
|
</div>
|