@xqyz/xq-cli 0.3.12 → 0.3.13
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 +24 -1
- package/README.md +3 -8
- package/package.json +1 -1
- package/src/cli.mjs +17 -139
package/CHANGELOG.md
CHANGED
|
@@ -10,12 +10,35 @@
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
(暂无未发布变更)
|
|
14
|
+
|
|
15
|
+
## [xq-cli 0.3.13 / WorkBuddy Plugin 0.10.32] - 2026-08-28
|
|
16
|
+
|
|
17
|
+
### API Key-only 登录
|
|
18
|
+
|
|
19
|
+
- CLI 本机登录页只保留 API Key 输入,并继续通过 `/user/info` 使用现有校验链路。
|
|
20
|
+
- 移除 CLI 和后端旧的账号密码、浏览器授权及 CLI 一次性授权码流程。
|
|
21
|
+
- WorkBuddy 安装器只在没有 API Key 时打开本机登录页,并同步内置 CLI `0.3.13`。
|
|
22
|
+
- `@xqyz/xq-cli@0.3.13` 与 `@xqyz/workbuddy-plugin-xique@0.10.32` 待本次发布;SkillHub `1.0.14` 仅生成上传包,需发布人手动上传。
|
|
23
|
+
|
|
24
|
+
## [WorkBuddy Plugin 0.10.31] - 2026-08-28
|
|
25
|
+
|
|
26
|
+
### 模板 5 自定义编号排版
|
|
27
|
+
|
|
28
|
+
- 按 Web 页面参考重排模板 5 自定义编号:标题设置、正文序号设置、起始编号和各级排版控件层次更清晰。
|
|
29
|
+
- 宽屏按行横向排列,窄卡片按稳定的 2/3/3 控件分组换行,避免自适应网格造成错位或挤压。
|
|
30
|
+
- 保持 `customNumbering` 字段结构、默认值和导出参数不变。
|
|
31
|
+
- `@xqyz/workbuddy-plugin-xique@0.10.31` 已发布,npm `latest` 和 `workbuddy-stable` 均已指向该版本。
|
|
32
|
+
- 已生成 `output/skillhub-xique-ai-1.0.13.zip`,由发布人手动上传;SkillHub 不由代理自动发布。
|
|
33
|
+
|
|
34
|
+
## [WorkBuddy Plugin 0.10.30] - 2026-08-27
|
|
35
|
+
|
|
13
36
|
### 供应方式确认恢复
|
|
14
37
|
|
|
15
38
|
- 修复解析完成但 `bidderIdentity_judgement=0` 时 WorkBuddy 直接阻塞的问题,新增“我们自己生产 / 我们采购供应”选择卡。
|
|
16
39
|
- 供应方式提交后沿用同一 `runId/cid/uuid` 继续生成,兼容旧版本已标记为 `blocked` 的历史任务,并防止重复提交。
|
|
17
40
|
- 同步 CLI `bidder-role` 命令、MCP App、Hook 白名单、Connector/Plugin Skill 和回归测试。
|
|
18
|
-
-
|
|
41
|
+
- `@xqyz/xq-cli@0.3.12` 与 `@xqyz/workbuddy-plugin-xique@0.10.30` 已发布,npm `latest` 和 `workbuddy-stable` 均已指向对应版本;SkillHub `1.0.12` 上传包由发布人手动上传。
|
|
19
42
|
|
|
20
43
|
## [WorkBuddy Plugin 0.10.29] - 2026-08-27
|
|
21
44
|
|
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';
|
|
@@ -73,7 +72,6 @@ const DIRECTORY_SEQUEL_RETRY_INTERVAL_MS = 60_000;
|
|
|
73
72
|
const GENERATE_UNFINISHED_RETRY_INTERVAL_MS = 60_000;
|
|
74
73
|
const RETRYABLE_NETWORK_CODES = new Set(['ECONNRESET', 'ECONNABORTED', 'ETIMEDOUT', 'EAI_AGAIN']);
|
|
75
74
|
const SOFT_CONTENT_TRIGGER_CODES = new Set([411, 429]);
|
|
76
|
-
const BROWSER_LOGIN_CALLBACK_PATH = '/callback';
|
|
77
75
|
const HTTP_AGENT = new http.Agent({ keepAlive: true });
|
|
78
76
|
const HTTPS_AGENT = new https.Agent({ keepAlive: true });
|
|
79
77
|
const AUTO_IGNORE_ISSUE_CONFIG = {
|
|
@@ -498,9 +496,7 @@ Plan actions
|
|
|
498
496
|
Examples
|
|
499
497
|
xq-cli login
|
|
500
498
|
xq-cli login --no-open
|
|
501
|
-
xq-cli login --name your-account --password your-password
|
|
502
499
|
xq-cli login --api-key xq_sk_xxx
|
|
503
|
-
xq-cli login --browser
|
|
504
500
|
$env:XQ_API_KEY="xq_sk_xxx"
|
|
505
501
|
xq-cli init --file D:\\bids\\tender.docx --wait
|
|
506
502
|
xq-cli parse-status --cid <cid> --uuid <uuid> --wait
|
|
@@ -545,7 +541,6 @@ Common options
|
|
|
545
541
|
--interval-sec <n> Poll interval in seconds, default 10
|
|
546
542
|
--reference-type <0|1|2> Outline reference choice: 0=merge, 1=format only, 2=score only
|
|
547
543
|
--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
544
|
--api-key <key> Save a user API key locally and verify it with /user/info
|
|
550
545
|
--no-open Do not open the local login page automatically
|
|
551
546
|
--interactive Open prompt menus for the current command
|
|
@@ -617,6 +612,9 @@ async function handleLogin(args, state) {
|
|
|
617
612
|
if (!baseUrl) {
|
|
618
613
|
throw new Error('Missing base URL. Pass --base-url <url>.');
|
|
619
614
|
}
|
|
615
|
+
if (args.browser !== undefined || stringOption(args, 'name') || stringOption(args, 'password')) {
|
|
616
|
+
throw new Error('当前仅支持 API Key 登录,请使用 xq-cli login --api-key <key> 或打开本机登录页。');
|
|
617
|
+
}
|
|
620
618
|
const sourceFrom = stringOption(args, 'sourceFrom', DEFAULT_SOURCE_FROM);
|
|
621
619
|
const apiKey = normalizeApiKey(stringOption(args, 'apiKey'));
|
|
622
620
|
if (apiKey) {
|
|
@@ -646,12 +644,8 @@ async function handleLogin(args, state) {
|
|
|
646
644
|
]);
|
|
647
645
|
return;
|
|
648
646
|
}
|
|
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' };
|
|
647
|
+
const loginMode = 'page';
|
|
648
|
+
const loginResult = await loginWithLoginPage(baseUrl, args);
|
|
655
649
|
const result = loginResult.result;
|
|
656
650
|
const apiKeyFromPage = loginResult.apiKey || '';
|
|
657
651
|
|
|
@@ -670,7 +664,6 @@ async function handleLogin(args, state) {
|
|
|
670
664
|
authType: loginResult.authType,
|
|
671
665
|
baseUrl,
|
|
672
666
|
username: state.user.username,
|
|
673
|
-
tokenSaved: loginResult.authType === 'token',
|
|
674
667
|
apiKeySaved: loginResult.authType === 'api-key',
|
|
675
668
|
}, [
|
|
676
669
|
`login success: ${state.user.username}`,
|
|
@@ -681,83 +674,24 @@ async function handleLogin(args, state) {
|
|
|
681
674
|
]);
|
|
682
675
|
}
|
|
683
676
|
|
|
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
677
|
async function loginWithApiKey(baseUrl, apiKey) {
|
|
697
678
|
const client = createClient(baseUrl, apiKey);
|
|
698
679
|
return getJson(client, '/user/info');
|
|
699
680
|
}
|
|
700
681
|
|
|
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) {
|
|
682
|
+
async function loginWithLoginPage(baseUrl, args) {
|
|
745
683
|
const timeoutMs = secondsToMs(numberOption(args, 'timeoutSec', DEFAULT_LOGIN_TIMEOUT_MS / 1000));
|
|
746
|
-
const
|
|
747
|
-
const server = await startBrowserLoginServer({
|
|
748
|
-
loginState,
|
|
684
|
+
const server = await startApiKeyLoginServer({
|
|
749
685
|
onApiKey: async apiKey => ({
|
|
750
686
|
result: await loginWithApiKey(baseUrl, apiKey),
|
|
751
687
|
apiKey,
|
|
752
688
|
}),
|
|
753
|
-
renderPage: (
|
|
754
|
-
authorizeUrl: buildBrowserAuthorizeUrl(baseUrl, redirectUri, loginState, sourceFrom),
|
|
755
|
-
}),
|
|
689
|
+
renderPage: () => renderLoginPage(),
|
|
756
690
|
});
|
|
757
691
|
writeNotice(args, `login page url: ${server.pageUrl}`);
|
|
758
692
|
if (process.env.XQ_CLI_NO_BROWSER !== '1') {
|
|
759
693
|
if (openBrowser(server.pageUrl)) {
|
|
760
|
-
writeNotice(args, 'login page: waiting for API key
|
|
694
|
+
writeNotice(args, 'login page: waiting for API key');
|
|
761
695
|
} else {
|
|
762
696
|
writeNotice(args, 'login page: automatic open failed, paste the URL above into a browser on this machine');
|
|
763
697
|
}
|
|
@@ -769,19 +703,10 @@ async function loginWithLoginPage(baseUrl, sourceFrom, args) {
|
|
|
769
703
|
timeoutMs,
|
|
770
704
|
`Login timed out after ${Math.round(timeoutMs / 1000)} seconds.`,
|
|
771
705
|
);
|
|
772
|
-
if (selected.mode === 'api-key') {
|
|
773
|
-
return {
|
|
774
|
-
result: selected.result,
|
|
775
|
-
apiKey: selected.apiKey,
|
|
776
|
-
authType: 'api-key',
|
|
777
|
-
};
|
|
778
|
-
}
|
|
779
706
|
return {
|
|
780
|
-
result:
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}),
|
|
784
|
-
authType: 'token',
|
|
707
|
+
result: selected.result,
|
|
708
|
+
apiKey: selected.apiKey,
|
|
709
|
+
authType: 'api-key',
|
|
785
710
|
};
|
|
786
711
|
} finally {
|
|
787
712
|
await server.close();
|
|
@@ -4246,16 +4171,7 @@ function saveLoginState(state, options) {
|
|
|
4246
4171
|
writeState(state);
|
|
4247
4172
|
}
|
|
4248
4173
|
|
|
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 } = {}) {
|
|
4174
|
+
async function startApiKeyLoginServer({ renderPage = null, onApiKey = null } = {}) {
|
|
4259
4175
|
let settled = false;
|
|
4260
4176
|
let resolveResult;
|
|
4261
4177
|
let rejectResult;
|
|
@@ -4275,9 +4191,7 @@ async function startBrowserLoginServer({ loginState = '', renderPage = null, onA
|
|
|
4275
4191
|
}
|
|
4276
4192
|
if (pathname === '/' && typeof renderPage === 'function') {
|
|
4277
4193
|
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
|
-
}));
|
|
4194
|
+
response.end(renderPage());
|
|
4281
4195
|
return;
|
|
4282
4196
|
}
|
|
4283
4197
|
if (pathname === '/api-key' && request.method === 'POST' && typeof onApiKey === 'function') {
|
|
@@ -4300,39 +4214,8 @@ async function startBrowserLoginServer({ loginState = '', renderPage = null, onA
|
|
|
4300
4214
|
}
|
|
4301
4215
|
return;
|
|
4302
4216
|
}
|
|
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
|
-
}
|
|
4217
|
+
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
4218
|
+
response.end('Not found');
|
|
4336
4219
|
});
|
|
4337
4220
|
server.on('error', error => {
|
|
4338
4221
|
if (!settled) {
|
|
@@ -4350,11 +4233,8 @@ async function startBrowserLoginServer({ loginState = '', renderPage = null, onA
|
|
|
4350
4233
|
});
|
|
4351
4234
|
|
|
4352
4235
|
const address = server.address();
|
|
4353
|
-
const redirectUri = `http://127.0.0.1:${address.port}${BROWSER_LOGIN_CALLBACK_PATH}`;
|
|
4354
4236
|
return {
|
|
4355
|
-
redirectUri,
|
|
4356
4237
|
pageUrl: `http://127.0.0.1:${address.port}/`,
|
|
4357
|
-
loginState,
|
|
4358
4238
|
result,
|
|
4359
4239
|
close: () => closeServer(server),
|
|
4360
4240
|
};
|
|
@@ -4381,7 +4261,7 @@ async function readJsonRequestBody(request, maxBytes) {
|
|
|
4381
4261
|
}
|
|
4382
4262
|
}
|
|
4383
4263
|
|
|
4384
|
-
function renderLoginPage(
|
|
4264
|
+
function renderLoginPage() {
|
|
4385
4265
|
return `<!doctype html>
|
|
4386
4266
|
<html lang="zh-CN">
|
|
4387
4267
|
<head>
|
|
@@ -4448,8 +4328,6 @@ function renderLoginPage({ authorizeUrl }) {
|
|
|
4448
4328
|
<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
4329
|
<button type="submit"><span>使用 API Key 登录</span><span class="button-arrow">→</span></button>
|
|
4450
4330
|
</form>
|
|
4451
|
-
<div class="divider">或</div>
|
|
4452
|
-
<a href="${escapeHtml(authorizeUrl)}"><span>使用浏览器授权登录</span><span class="button-arrow">↗</span></a>
|
|
4453
4331
|
<p class="hint">API Key 可在喜鹊的 API Key 管理页创建。验证成功后即可关闭页面。</p>
|
|
4454
4332
|
<div class="secure-note"><span></span>登录状态仅用于当前 CLI 工作流</div>
|
|
4455
4333
|
</main>
|