@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 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
- - 计划发布 `@xqyz/xq-cli@0.3.12`、`@xqyz/workbuddy-plugin-xique@0.10.30`,生成 SkillHub `1.0.12` 上传包(待发布人手动上传)。
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 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.13",
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';
@@ -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 = 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' };
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 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) {
682
+ async function loginWithLoginPage(baseUrl, args) {
745
683
  const timeoutMs = secondsToMs(numberOption(args, 'timeoutSec', DEFAULT_LOGIN_TIMEOUT_MS / 1000));
746
- const loginState = randomUUID();
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: ({ redirectUri }) => renderLoginPage({
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 or browser authorization');
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: await postJson(createClient(baseUrl), '/user/cli/token', {
781
- code: selected.code,
782
- state: selected.state,
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 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 } = {}) {
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
- 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
- }
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({ authorizeUrl }) {
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>