@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 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
- - 计划发布 `@xqyz/xq-cli@0.3.12`、`@xqyz/workbuddy-plugin-xique@0.10.30`,生成 SkillHub `1.0.12` 上传包(待发布人手动上传)。
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 defaults to a local page with browser and API Key choices. If you explicitly pass `--name` and `--password`, the CLI uses password login. If you pass `--api-key`, it saves the API key locally and uses it for later requests.
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` with both choices: browser authorization or an API Key from the
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 --browser` to skip the local page and open browser
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xqyz/xq-cli",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "CLI for xique bid-book task workflows",
5
5
  "type": "module",
6
6
  "scripts": {
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 = resolveLoginMode(args);
650
- const loginResult = loginMode === 'browser'
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 loginWithPassword(baseUrl, sourceFrom, args) {
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 loginState = randomUUID();
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: ({ redirectUri }) => renderLoginPage({
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 or browser authorization');
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: await postJson(createClient(baseUrl), '/user/cli/token', {
781
- code: selected.code,
782
- state: selected.state,
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 buildBrowserAuthorizeUrl(baseUrl, redirectUri, state, sourceFrom) {
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
- if (pathname !== BROWSER_LOGIN_CALLBACK_PATH) {
4304
- response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
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({ authorizeUrl }) {
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
- .hint{margin:16px 0 0;color:#8390a6;font-size:12px;line-height:1.7;}.hint strong{color:#536da9;font-weight:700;}.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);}
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
- <div class="divider">或</div>
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>