@xqyz/xq-cli 0.3.9 → 0.3.11

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,7 +10,59 @@
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
- ## [0.3.9 / WorkBuddy Plugin 0.10.22 / SkillHub 1.0.6] - 2026-08-26
13
+ (暂无未发布变更)
14
+
15
+ ## [xq-cli 0.3.11 / WorkBuddy Plugin 0.10.27 / SkillHub 1.0.11] - 2026-08-27
16
+
17
+ ### 写作质量与导出设置
18
+
19
+ - WorkBuddy 配置卡将“写作模式”改为“写作质量”,新增“旗舰智笔”,并保留旧参数兼容映射。
20
+ - 极短篇、超短篇按前端规则禁用专家智笔和旗舰智笔;图片来源选项保持直接展示。
21
+ - 表格、流程图、模板、编号、修改标记、页边距和图片尺寸等低频选项统一收纳到默认收起的“导出格式设置”。
22
+ - 同步 CLI、MCP App、Connector 和 Skill 文档,保持配置提交和规划流程不变。
23
+ - `@xqyz/xq-cli@0.3.11` 与 `@xqyz/workbuddy-plugin-xique@0.10.27` 待发布;SkillHub `1.0.11` 上传包待手动上传。
24
+
25
+ ## [WorkBuddy Plugin 0.10.26 / SkillHub 1.0.10] - 2026-08-27
26
+
27
+ ### MCP 中文显示名
28
+
29
+ - WorkBuddy MCP 配置键统一写入“喜鹊标书”,安装器会将已有 `xique-bid` 配置迁移为中文键并保留原有运行参数。
30
+ - Connector 清单、安装提示和 MCP 初始化名称同步为“喜鹊标书”;Connector ID、工具命名空间和功能路径保持不变。
31
+ - `@xqyz/workbuddy-plugin-xique@0.10.26` 已发布,`latest` 与 `workbuddy-stable` 均指向该版本;SkillHub `1.0.10` 待手动上传。
32
+
33
+ ## [WorkBuddy Plugin 0.10.25 / SkillHub 1.0.9] - 2026-08-26
34
+
35
+ ### Plugin 发布
36
+
37
+ - WorkBuddy Plugin 固定依赖 `@xqyz/xq-cli@0.3.10`,MCP 运行时拒绝低于该版本的 CLI。
38
+ - MCP 初始化信息的用户可见名称为“喜鹊标书”,保留 `xique-bid` Connector ID、工具命名空间和功能路径不变。
39
+ - Windows 安装器的临时 npm 缓存改为异步清理,避免升级后残留句柄影响 WorkBuddy 重启。
40
+ - `@xqyz/workbuddy-plugin-xique@0.10.25` 已发布,`latest` 与 `workbuddy-stable` 均指向该版本;SkillHub `1.0.9` 上传包已生成,待手动上传。
41
+
42
+ ## [WorkBuddy Plugin 0.10.24 / SkillHub 1.0.8] - 2026-08-26
43
+
44
+ ### MCP 中文展示名
45
+
46
+ - MCP 初始化信息的用户可见名称改为“喜鹊标书”,同时保留 `xique-bid` 内部标识、工具命名空间和配置升级键不变。
47
+
48
+ ### CLI 本地登录页视觉升级
49
+
50
+ - 登录页改为响应式双栏工作台,补充一次登录复用、分阶段工作流和 Key 轮换等接入信息。
51
+ - API Key 说明改为“凭证保存在本机,后续命令无需重复输入”,保留 API Key 登录与浏览器授权两条入口及原有提交接口。
52
+
53
+ ### CLI 登录页支持 API Key
54
+
55
+ - `xq-cli login` 默认打开本机登录页,普通用户可在页面中选择浏览器授权或输入 API Key。
56
+ - API Key 只通过本机回环地址提交,由 CLI 调用 `/user/info` 验证后保存;不会进入 URL 或日志。
57
+ - 保留 `xq-cli login --browser` 直接打开浏览器授权,以及 `--no-open` 手动复制本机页面地址。
58
+
59
+ ### WorkBuddy Skill 自助更新
60
+
61
+ - 用户只需说“更新喜鹊标书”或“升级喜鹊标书”,即可授权拉取正式 SkillHub `@org-kkw9656k/xique-ai`,无需重复粘贴完整安装提示。
62
+ - 安装器优先下载并校验 SkillHub 最新包;远端不可用、版本落后或要求更高运行版本时使用兼容的内置/已安装 Skill,不覆盖现有新版本。
63
+ - SkillHub 下载只接受经过身份、版本、路径、文件类型和大小校验的 Skill Markdown 内容;更新仍会备份旧 Skill,并在新会话中生效。
64
+
65
+ ## [0.3.9 / WorkBuddy Plugin 0.10.23 / SkillHub 1.0.6] - 2026-08-26
14
66
 
15
67
  ### WorkBuddy 规划模式自动推进
16
68
 
@@ -18,6 +70,7 @@
18
70
  - WorkBuddy 检测到 `analysis_ready` 后自动对同一 `cid` 执行 `plan confirm-outline`,不再展示 `analysis-review` 或智能解读确认卡。
19
71
  - 已被旧版本误标失败、但错误中已记录解析 `success=3` 的规划任务,在查询同一 `runId` 时会原地恢复并自动确认生成大纲。
20
72
  - 保留大纲和目录人工确认闸门;自动推进只覆盖后台智能解读到大纲生成这一段,不会提前生成正文。
73
+ - WorkBuddy Plugin `0.10.22` 发布时依赖范围被 npm 写成 `^0.3.9`,已弃用且未进入 `workbuddy-stable`;`0.10.23` 恢复固定依赖 `0.3.9`。
21
74
 
22
75
  ## [0.3.8 / WorkBuddy Plugin 0.10.21 / SkillHub 1.0.5] - 2026-08-25
23
76
 
package/README.md CHANGED
@@ -76,7 +76,15 @@ xq-cli export --cid <cid> --out D:\output
76
76
 
77
77
  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.
78
78
 
79
- `login` now defaults to browser authorization. If you explicitly pass `--name` and `--password`, the CLI falls back to password login. If you pass `--api-key`, it saves the API key locally and uses it for later requests.
79
+ `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.
80
+
81
+ When no credential flags are supplied, `xq-cli login` opens a local login page on
82
+ `127.0.0.1` with both choices: browser authorization or an API Key from the
83
+ Xique API Key management page. The page submits the Key only to the local CLI,
84
+ which verifies it with `/user/info` before saving it to the user-scoped config.
85
+ Use `xq-cli login --browser` to skip the local page and open browser
86
+ authorization directly, or `xq-cli login --no-open` to copy the local page URL
87
+ manually. Never paste an API Key into chat or commit it to a repository.
80
88
 
81
89
  `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.
82
90
 
@@ -176,7 +184,7 @@ The wizard will prompt for:
176
184
 
177
185
  - source bid file
178
186
  - article length
179
- - writing mode
187
+ - writing quality (standard writing / expert / flagship)
180
188
  - visual style
181
189
  - table quantity / color / style
182
190
  - planning mode
@@ -254,7 +262,7 @@ xq-cli choices export --json
254
262
  Outline generation now supports frontend-backed options such as:
255
263
 
256
264
  - `--page-scope` / `--page-scope-code`
257
- - `--base`
265
+ - `--writing-quality` (0=standard writing, 1=expert, 2=flagship; `--base` remains a compatibility alias)
258
266
  - `--theme-style`
259
267
  - `--table-quantity`
260
268
  - `--table-color`
@@ -283,7 +291,7 @@ Export now supports frontend-backed style options such as:
283
291
  Example outline config:
284
292
 
285
293
  ```bash
286
- xq-cli outline --cid <cid> --page-scope 3 --base expert --theme-style 4 --table-quantity rich --table-color blue --table-style zebra --mermaid-style blue --knowledge-img 1 --scene-img 1 --plan-mode quick --wait
294
+ xq-cli outline --cid <cid> --page-scope 3 --writing-quality flagship --theme-style 4 --table-quantity rich --table-color blue --table-style zebra --mermaid-style blue --knowledge-img 1 --scene-img 1 --plan-mode quick --wait
287
295
  ```
288
296
 
289
297
  Example export config:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xqyz/xq-cli",
3
- "version": "0.3.9",
3
+ "version": "0.3.11",
4
4
  "description": "CLI for xique bid-book task workflows",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/cli.mjs CHANGED
@@ -183,6 +183,26 @@ const BASE_OPTIONS = [
183
183
  { value: '0', label: 'standard', description: 'standard writing', aliases: ['0', 'standard', 'default'] },
184
184
  { value: '1', label: 'expert', description: 'expert polish / public knowledge reference', aliases: ['1', 'expert', 'polish'] },
185
185
  ];
186
+ const WRITING_QUALITY_OPTIONS = [
187
+ {
188
+ value: 0,
189
+ label: 'standard-writing',
190
+ description: 'standard writing',
191
+ aliases: ['0', 'standard', 'standard-writing', '标准', '标准写作'],
192
+ },
193
+ {
194
+ value: 1,
195
+ label: 'expert',
196
+ description: 'expert writing',
197
+ aliases: ['1', 'expert', '专家智笔'],
198
+ },
199
+ {
200
+ value: 2,
201
+ label: 'flagship',
202
+ description: 'flagship deep-thinking writing',
203
+ aliases: ['2', 'flagship', '旗舰', '旗舰智笔'],
204
+ },
205
+ ];
186
206
  const THEME_STYLE_OPTIONS = [
187
207
  { value: 1, label: 'enhanced', description: 'enhanced image-rich layout', aliases: ['1', 'enhanced'] },
188
208
  { value: 2, label: 'table', description: 'table-first layout', aliases: ['2', 'table', 'table-first'] },
@@ -291,6 +311,7 @@ const PAGE_SCOPE_PROMPT_LABELS = {
291
311
  const BASE_PROMPT_LABELS = {
292
312
  0: '\u6807\u51C6\u5199\u4F5C',
293
313
  1: '\u4E13\u5BB6\u667A\u7B14',
314
+ 2: '\u65D7\u8230\u667A\u7B14',
294
315
  };
295
316
  const THEME_STYLE_PROMPT_LABELS = {
296
317
  1: '\u589E\u5F3A\u56FE\u6587',
@@ -441,7 +462,7 @@ function printHelp() {
441
462
  xq-cli: xique brand task CLI
442
463
 
443
464
  Commands
444
- login Save xique API key or account token locally
465
+ login Open a local login page or save xique credentials locally
445
466
  init Upload files and initialize a task
446
467
  parse-status Query or wait for parse completion
447
468
  period-status Query engineering project period confirmation status
@@ -472,6 +493,7 @@ Plan actions
472
493
 
473
494
  Examples
474
495
  xq-cli login
496
+ xq-cli login --no-open
475
497
  xq-cli login --name your-account --password your-password
476
498
  xq-cli login --api-key xq_sk_xxx
477
499
  xq-cli login --browser
@@ -489,7 +511,7 @@ Examples
489
511
  xq-cli outline --cid <cid> --wait --ignore-short-requirement
490
512
  xq-cli outline --cid <cid> --wait --ignore-missing-bill
491
513
  xq-cli outline --cid <cid> --wait --reference-type 0
492
- xq-cli outline --cid <cid> --page-scope 3 --base expert --theme-style 4 --table-quantity rich --plan-mode quick --wait
514
+ xq-cli outline --cid <cid> --page-scope 3 --writing-quality flagship --theme-style 4 --table-quantity rich --plan-mode quick --wait
493
515
  xq-cli outline view --cid <cid>
494
516
  xq-cli outline view --cid <cid> --out D:\\output\\outline.json
495
517
  xq-cli outline update --cid <cid> --file D:\\output\\outline.json --dry-run --json
@@ -517,8 +539,9 @@ Common options
517
539
  --timeout-sec <n> Poll timeout in seconds, default 600
518
540
  --interval-sec <n> Poll interval in seconds, default 10
519
541
  --reference-type <0|1|2> Outline reference choice: 0=merge, 1=format only, 2=score only
520
- --browser Force browser authorization login; browser is the default when credentials are not provided
542
+ --browser Skip the local login page and open browser authorization directly
521
543
  --api-key <key> Save a user API key locally and verify it with /user/info
544
+ --no-open Do not open the local login page automatically
522
545
  --interactive Open prompt menus for the current command
523
546
  --dry-run Preview an outline update without saving it
524
547
  --yes Confirm an outline update without an interactive prompt
@@ -529,7 +552,8 @@ Common options
529
552
  Outline config
530
553
  --page-scope <code> Full-length preset. See "xq-cli choices outline"
531
554
  --page-scope-code <code> Alias of --page-scope
532
- --base <0|1|standard|expert>
555
+ --writing-quality <0|1|2|standard|expert|flagship> 0=standard writing, 1=expert, 2=flagship
556
+ --base <0|1|standard|expert> Deprecated compatibility alias for writing quality
533
557
  --theme-style <1|2|3|4|5|enhanced|table|rich|basic|text>
534
558
  --table-quantity <0|1|2|none|few|rich>
535
559
  --table-color <black|blue|red|green|purple|cyan|orange>
@@ -610,33 +634,42 @@ async function handleLogin(args, state) {
610
634
  }, [
611
635
  `login success: ${state.user.username}`,
612
636
  'mode: api-key',
637
+ 'authType: api-key',
613
638
  `baseUrl: ${baseUrl}`,
614
639
  `config: ${CONFIG_FILE}`,
615
640
  ]);
616
641
  return;
617
642
  }
618
643
  const loginMode = resolveLoginMode(args);
619
- const result = loginMode === 'browser'
620
- ? await loginWithBrowser(baseUrl, sourceFrom, args)
621
- : await loginWithPassword(baseUrl, sourceFrom, args);
644
+ const loginResult = loginMode === 'browser'
645
+ ? { result: await loginWithBrowser(baseUrl, sourceFrom, args), authType: 'token' }
646
+ : loginMode === 'page'
647
+ ? await loginWithLoginPage(baseUrl, sourceFrom, args)
648
+ : { result: await loginWithPassword(baseUrl, sourceFrom, args), authType: 'token' };
649
+ const result = loginResult.result;
650
+ const apiKeyFromPage = loginResult.apiKey || '';
622
651
 
623
652
  saveLoginState(state, {
624
653
  baseUrl,
625
- name: stringOption(args, 'name', result.data?.user?.username || ''),
654
+ name: stringOption(args, 'name', result.data?.user?.username || result.data?.username || ''),
626
655
  sourceFrom,
627
656
  result,
657
+ apiKey: apiKeyFromPage,
658
+ authType: loginResult.authType,
628
659
  });
629
660
 
630
661
  outputResult(args, {
631
662
  command: 'login',
632
663
  mode: loginMode,
633
- authType: loginMode === 'browser' ? 'token' : 'token',
664
+ authType: loginResult.authType,
634
665
  baseUrl,
635
666
  username: state.user.username,
636
- tokenSaved: true,
667
+ tokenSaved: loginResult.authType === 'token',
668
+ apiKeySaved: loginResult.authType === 'api-key',
637
669
  }, [
638
670
  `login success: ${state.user.username}`,
639
671
  `mode: ${loginMode}`,
672
+ `authType: ${loginResult.authType}`,
640
673
  `baseUrl: ${baseUrl}`,
641
674
  `config: ${CONFIG_FILE}`,
642
675
  ]);
@@ -651,7 +684,7 @@ function resolveLoginMode(args) {
651
684
  }
652
685
  const hasName = stringOption(args, 'name');
653
686
  const hasPassword = stringOption(args, 'password');
654
- return hasName || hasPassword ? 'password' : 'browser';
687
+ return hasName || hasPassword ? 'password' : 'page';
655
688
  }
656
689
 
657
690
  async function loginWithApiKey(baseUrl, apiKey) {
@@ -702,6 +735,53 @@ async function loginWithBrowser(baseUrl, sourceFrom, args) {
702
735
  }
703
736
  }
704
737
 
738
+ async function loginWithLoginPage(baseUrl, sourceFrom, args) {
739
+ const timeoutMs = secondsToMs(numberOption(args, 'timeoutSec', DEFAULT_LOGIN_TIMEOUT_MS / 1000));
740
+ const loginState = randomUUID();
741
+ const server = await startBrowserLoginServer({
742
+ loginState,
743
+ onApiKey: async apiKey => ({
744
+ result: await loginWithApiKey(baseUrl, apiKey),
745
+ apiKey,
746
+ }),
747
+ renderPage: ({ redirectUri }) => renderLoginPage({
748
+ authorizeUrl: buildBrowserAuthorizeUrl(baseUrl, redirectUri, loginState, sourceFrom),
749
+ }),
750
+ });
751
+ writeNotice(args, `login page url: ${server.pageUrl}`);
752
+ if (process.env.XQ_CLI_NO_BROWSER !== '1') {
753
+ if (openBrowser(server.pageUrl)) {
754
+ writeNotice(args, 'login page: waiting for API key or browser authorization');
755
+ } else {
756
+ writeNotice(args, 'login page: automatic open failed, paste the URL above into a browser on this machine');
757
+ }
758
+ }
759
+
760
+ try {
761
+ const selected = await withTimeout(
762
+ server.result,
763
+ timeoutMs,
764
+ `Login timed out after ${Math.round(timeoutMs / 1000)} seconds.`,
765
+ );
766
+ if (selected.mode === 'api-key') {
767
+ return {
768
+ result: selected.result,
769
+ apiKey: selected.apiKey,
770
+ authType: 'api-key',
771
+ };
772
+ }
773
+ return {
774
+ result: await postJson(createClient(baseUrl), '/user/cli/token', {
775
+ code: selected.code,
776
+ state: selected.state,
777
+ }),
778
+ authType: 'token',
779
+ };
780
+ } finally {
781
+ await server.close();
782
+ }
783
+ }
784
+
705
785
  function writeNotice(args, message) {
706
786
  if (booleanOption(args, 'json')) {
707
787
  console.error(message);
@@ -2005,7 +2085,13 @@ async function fetchAuthoritativeTaskState(client, cid, options = {}) {
2005
2085
 
2006
2086
  function buildOutlineParams(snapshot, args) {
2007
2087
  const pageScopeCode = enumOption(args, ['pageScope', 'pageScopeCode'], PAGE_SCOPE_OPTIONS, snapshot.pageScopeCode ?? DEFAULT_PAGE_SCOPE_CODE);
2008
- const base = enumOption(args, 'base', BASE_OPTIONS, snapshot.base ?? DEFAULT_BASE);
2088
+ const writingQuality = enumOption(
2089
+ args,
2090
+ ['writingQuality', 'writingMode', 'base'],
2091
+ WRITING_QUALITY_OPTIONS,
2092
+ snapshot.writingQuality ?? (String(snapshot.base ?? DEFAULT_BASE) === '1' ? 1 : 0),
2093
+ );
2094
+ const base = pageScopeCode <= 0 ? '0' : writingQuality === 1 ? '1' : '0';
2009
2095
  const uuid = snapshot.uuid;
2010
2096
  const title = stringOption(args, 'title', snapshot.projectName || snapshot.title);
2011
2097
  const plan = stringOption(args, 'plan', snapshot.plan || '');
@@ -2034,7 +2120,9 @@ function buildOutlineParams(snapshot, args) {
2034
2120
  tableStyle: enumOption(args, 'tableStyle', TABLE_STYLE_OPTIONS, snapshot.tableStyle),
2035
2121
  annexIds: snapshot.annexIds || [],
2036
2122
  knowledgeProductIds: snapshot.knowledgeProductIds || [],
2037
- base: pageScopeCode <= 0 ? '0' : base,
2123
+ base,
2124
+ writingQuality,
2125
+ deepThinkingFlowVersion: writingQuality === 2 ? 1 : null,
2038
2126
  bidFileIds: [uuid],
2039
2127
  directoryNodes: [],
2040
2128
  generateType: 1,
@@ -2156,13 +2244,17 @@ async function verifyParserWakeUp(client, state, cid, uuid, params, planningMode
2156
2244
  const parseAdvanced = [1, 3, 4].includes(parseSuccess);
2157
2245
  const selectionRequired = expectedMultiBidId && expectedMultiBidId !== 'notMultiBid';
2158
2246
  const selectionMatches = !selectionRequired || selectedMultiBidId === expectedMultiBidId;
2247
+ const selectionMetadataMissing = selectionRequired && !selectedMultiBidId;
2159
2248
  // Planning mode persists the full configuration again when
2160
2249
  // `plan confirm-outline` is called. Some backend deployments complete
2161
2250
  // intelligent analysis (success=3) without mirroring planMode or the
2162
2251
  // selected package onto getTaskDetail. Treat the parse result as the
2163
2252
  // authority here so WorkBuddy can trigger the explicit outline
2164
2253
  // confirmation instead of failing before that call.
2165
- if (parseAdvanced && (planningMode || selectionMatches)) {
2254
+ // The same response shape occurs in quick mode: an advanced parse
2255
+ // with an omitted package field is still ahead of the success=2
2256
+ // selection gate. Keep rejecting an explicitly different package.
2257
+ if (parseAdvanced && (planningMode || selectionMatches || selectionMetadataMissing)) {
2166
2258
  return latest;
2167
2259
  }
2168
2260
 
@@ -3285,6 +3377,7 @@ function buildChoicesPayload(scope) {
3285
3377
  payload.outline = {
3286
3378
  pageScope: PAGE_SCOPE_OPTIONS,
3287
3379
  base: BASE_OPTIONS,
3380
+ writingQuality: WRITING_QUALITY_OPTIONS,
3288
3381
  themeStyle: THEME_STYLE_OPTIONS,
3289
3382
  tableQuantity: TABLE_QUANTITY_OPTIONS,
3290
3383
  tableColor: TABLE_COLOR_OPTIONS,
@@ -3329,6 +3422,7 @@ function renderChoicesLines(payload) {
3329
3422
  lines.push('outline:');
3330
3423
  lines.push(` page-scope: ${formatChoiceOptions(PAGE_SCOPE_OPTIONS)}`);
3331
3424
  lines.push(` base: ${formatChoiceOptions(BASE_OPTIONS)}`);
3425
+ lines.push(` writing-quality: ${formatChoiceOptions(WRITING_QUALITY_OPTIONS)}`);
3332
3426
  lines.push(` theme-style: ${formatChoiceOptions(THEME_STYLE_OPTIONS)}`);
3333
3427
  lines.push(` table-quantity: ${formatChoiceOptions(TABLE_QUANTITY_OPTIONS)}`);
3334
3428
  lines.push(` table-color: ${formatChoiceOptions(TABLE_COLOR_OPTIONS)}`);
@@ -3443,18 +3537,20 @@ async function collectOutlineInteractiveArgs(args, snapshot, context = null, opt
3443
3537
  nextArgs.pageScope = pageScopeCode;
3444
3538
 
3445
3539
  if (pageScopeCode <= 0) {
3446
- promptContext.promptStream.write('\u5DF2\u9009\u62E9\u77ED\u7BC7\u6A21\u5F0F\uFF0C\u5199\u4F5C\u6A21\u5F0F\u5C06\u56FA\u5B9A\u4E3A\u201C\u6807\u51C6\u5199\u4F5C\u201D\u3002\n');
3540
+ promptContext.promptStream.write('\u5DF2\u9009\u62E9\u77ED\u7BC7\u6A21\u5F0F\uFF0C\u5199\u4F5C\u8D28\u91CF\u5C06\u56FA\u5B9A\u4E3A\u201C\u6807\u51C6\u5199\u4F5C\u201D\u3002\n');
3541
+ nextArgs.writingQuality = 0;
3447
3542
  nextArgs.base = '0';
3448
3543
  } else {
3449
- nextArgs.base = await promptChoice(promptContext, {
3450
- title: '\u5199\u4F5C\u6A21\u5F0F',
3451
- question: '\u8BF7\u9009\u62E9\u5199\u4F5C\u6A21\u5F0F',
3452
- options: BASE_OPTIONS.map(option => ({
3544
+ nextArgs.writingQuality = await promptChoice(promptContext, {
3545
+ title: '\u5199\u4F5C\u8D28\u91CF',
3546
+ question: '\u8BF7\u9009\u62E9\u5199\u4F5C\u8D28\u91CF',
3547
+ options: WRITING_QUALITY_OPTIONS.map(option => ({
3453
3548
  ...option,
3454
3549
  promptLabel: BASE_PROMPT_LABELS[option.value] || option.label,
3455
3550
  })),
3456
- defaultValue: enumOption(args, 'base', BASE_OPTIONS, defaults.base),
3551
+ defaultValue: enumOption(args, ['writingQuality', 'writingMode', 'base'], WRITING_QUALITY_OPTIONS, defaults.writingQuality),
3457
3552
  });
3553
+ nextArgs.base = nextArgs.writingQuality === 1 ? '1' : '0';
3458
3554
  }
3459
3555
 
3460
3556
  nextArgs.themeStyle = await promptChoice(promptContext, {
@@ -3677,9 +3773,11 @@ async function collectExportInteractiveArgs(args, snapshot, context = null, opti
3677
3773
  }
3678
3774
 
3679
3775
  function buildInteractiveSnapshotDefaults(source = {}) {
3776
+ const writingQuality = source.writingQuality ?? (String(source.base ?? DEFAULT_BASE) === '1' ? 1 : 0);
3680
3777
  return {
3681
3778
  pageScopeCode: source.pageScopeCode ?? DEFAULT_PAGE_SCOPE_CODE,
3682
3779
  base: source.base ?? DEFAULT_BASE,
3780
+ writingQuality,
3683
3781
  themeStyle: source.themeStyle ?? DEFAULT_THEME_STYLE,
3684
3782
  tableQuantity: source.tableQuantity ?? DEFAULT_TABLE_QUANTITY,
3685
3783
  tableColor: source.tableColor ?? DEFAULT_TABLE_COLOR,
@@ -4116,7 +4214,7 @@ function buildBrowserAuthorizeUrl(baseUrl, redirectUri, state, sourceFrom) {
4116
4214
  return `${baseUrl}/user/cli/authorize?${query.toString()}`;
4117
4215
  }
4118
4216
 
4119
- async function startBrowserLoginServer() {
4217
+ async function startBrowserLoginServer({ loginState = '', renderPage = null, onApiKey = null } = {}) {
4120
4218
  let settled = false;
4121
4219
  let resolveResult;
4122
4220
  let rejectResult;
@@ -4124,7 +4222,7 @@ async function startBrowserLoginServer() {
4124
4222
  resolveResult = resolve;
4125
4223
  rejectResult = reject;
4126
4224
  });
4127
- const server = http.createServer((request, response) => {
4225
+ const server = http.createServer(async (request, response) => {
4128
4226
  const origin = server.address();
4129
4227
  const requestUrl = new URL(request.url || '/', `http://127.0.0.1:${origin?.port || 0}`);
4130
4228
  const pathname = requestUrl.pathname;
@@ -4134,6 +4232,33 @@ async function startBrowserLoginServer() {
4134
4232
  response.end();
4135
4233
  return;
4136
4234
  }
4235
+ if (pathname === '/' && typeof renderPage === 'function') {
4236
+ response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
4237
+ response.end(renderPage({
4238
+ redirectUri: `http://127.0.0.1:${origin?.port || 0}${BROWSER_LOGIN_CALLBACK_PATH}`,
4239
+ }));
4240
+ return;
4241
+ }
4242
+ if (pathname === '/api-key' && request.method === 'POST' && typeof onApiKey === 'function') {
4243
+ try {
4244
+ const body = await readJsonRequestBody(request, 16 * 1024);
4245
+ const apiKey = normalizeApiKey(body?.apiKey);
4246
+ if (!apiKey) {
4247
+ throw new Error('请输入 API Key。');
4248
+ }
4249
+ const selected = await onApiKey(apiKey);
4250
+ response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
4251
+ response.end(renderBrowserLoginResultPage('登录成功', 'API Key 已验证,CLI 正在保存登录状态;现在可以关闭此页面。'));
4252
+ if (!settled) {
4253
+ settled = true;
4254
+ resolveResult({ mode: 'api-key', ...selected });
4255
+ }
4256
+ } catch (error) {
4257
+ response.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
4258
+ response.end(renderBrowserLoginResultPage('登录未完成', error.message || 'API Key 验证失败,请检查后重试。'));
4259
+ }
4260
+ return;
4261
+ }
4137
4262
  if (pathname !== BROWSER_LOGIN_CALLBACK_PATH) {
4138
4263
  response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
4139
4264
  response.end('Not found');
@@ -4152,11 +4277,20 @@ async function startBrowserLoginServer() {
4152
4277
  return;
4153
4278
  }
4154
4279
 
4280
+ if (loginState && state !== loginState) {
4281
+ response.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
4282
+ response.end(renderBrowserLoginResultPage('登录未完成', '授权状态校验失败,请返回 CLI 重新执行登录。'));
4283
+ if (!settled) {
4284
+ settled = true;
4285
+ rejectResult(new Error('Browser login state validation failed.'));
4286
+ }
4287
+ return;
4288
+ }
4155
4289
  response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
4156
4290
  response.end(renderBrowserLoginResultPage('登录成功', '授权已完成,现在可以回到 CLI 继续使用了。'));
4157
4291
  if (!settled) {
4158
4292
  settled = true;
4159
- resolveResult({ code, state });
4293
+ resolveResult({ mode: 'browser', code, state });
4160
4294
  }
4161
4295
  });
4162
4296
  server.on('error', error => {
@@ -4178,11 +4312,111 @@ async function startBrowserLoginServer() {
4178
4312
  const redirectUri = `http://127.0.0.1:${address.port}${BROWSER_LOGIN_CALLBACK_PATH}`;
4179
4313
  return {
4180
4314
  redirectUri,
4315
+ pageUrl: `http://127.0.0.1:${address.port}/`,
4316
+ loginState,
4181
4317
  result,
4182
4318
  close: () => closeServer(server),
4183
4319
  };
4184
4320
  }
4185
4321
 
4322
+ async function readJsonRequestBody(request, maxBytes) {
4323
+ const chunks = [];
4324
+ let total = 0;
4325
+ for await (const chunk of request) {
4326
+ total += chunk.length;
4327
+ if (total > maxBytes) {
4328
+ throw new Error('请求内容过大。');
4329
+ }
4330
+ chunks.push(chunk);
4331
+ }
4332
+ const text = Buffer.concat(chunks).toString('utf8');
4333
+ try {
4334
+ if (String(request.headers['content-type'] || '').includes('application/x-www-form-urlencoded')) {
4335
+ return Object.fromEntries(new URLSearchParams(text));
4336
+ }
4337
+ return JSON.parse(text || '{}');
4338
+ } catch {
4339
+ throw new Error('请求格式无效。');
4340
+ }
4341
+ }
4342
+
4343
+ function renderLoginPage({ authorizeUrl }) {
4344
+ return `<!doctype html>
4345
+ <html lang="zh-CN">
4346
+ <head>
4347
+ <meta charset="UTF-8" />
4348
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
4349
+ <title>喜鹊标书登录</title>
4350
+ <style>
4351
+ :root{font-family:Inter,Segoe UI,Microsoft YaHei,sans-serif;color:#182238;background:#eef3fb;}
4352
+ *{box-sizing:border-box;} body{min-height:100vh;margin:0;padding:32px;display:grid;place-items:center;background:#eef3fb;}
4353
+ .page-shell{width:min(100%,1080px);display:grid;grid-template-columns:minmax(0,1fr) 448px;gap:24px;align-items:stretch;}
4354
+ .brand-panel{position:relative;overflow:hidden;display:flex;min-height:584px;flex-direction:column;justify-content:space-between;padding:42px 44px;border:1px solid #dce5f4;border-radius:24px;background:linear-gradient(145deg,#f8fbff 0%,#eef4ff 58%,#e9f0fc 100%);box-shadow:0 20px 55px rgba(35,58,105,.10);}
4355
+ .brand-panel:after{content:"";position:absolute;width:310px;height:310px;right:-128px;bottom:-142px;border:1px solid rgba(83,111,179,.20);border-radius:50%;box-shadow:0 0 0 26px rgba(83,111,179,.05),0 0 0 52px rgba(83,111,179,.04);}
4356
+ .brand-top,.brand-footer{position:relative;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:12px;}
4357
+ .brand-kicker{display:flex;align-items:center;gap:10px;color:#536da9;font-size:12px;font-weight:800;letter-spacing:.18em;}
4358
+ .brand-symbol{display:grid;width:30px;height:30px;place-items:center;border-radius:10px;color:#fff;background:#536da9;box-shadow:0 7px 16px rgba(83,109,169,.25);font-size:18px;letter-spacing:0;}
4359
+ .brand-status{display:flex;align-items:center;gap:7px;color:#6b7b99;font-size:12px;}
4360
+ .brand-status i{width:7px;height:7px;border-radius:50%;background:#35a779;box-shadow:0 0 0 4px rgba(53,167,121,.13);}
4361
+ .brand-copy{position:relative;z-index:1;margin:auto 0;max-width:470px;}
4362
+ .brand-copy h1{margin:0;color:#14213b;font-size:clamp(38px,5vw,62px);font-weight:800;letter-spacing:-.055em;line-height:1.08;}
4363
+ .brand-copy h1 em{font-style:normal;color:#536da9;}
4364
+ .brand-copy p{max-width:400px;margin:24px 0 0;color:#637593;font-size:16px;line-height:1.85;}
4365
+ .brand-features{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:34px 0 0;}
4366
+ .brand-feature{padding:14px 13px;border:1px solid rgba(188,204,231,.72);border-radius:12px;background:rgba(255,255,255,.58);}
4367
+ .brand-feature strong,.brand-feature span{display:block;}.brand-feature strong{color:#324a7e;font-size:13px;}.brand-feature span{margin-top:5px;color:#7a89a3;font-size:11px;line-height:1.45;}
4368
+ .brand-footer{justify-content:flex-start;color:#8290a8;font-size:11px;}
4369
+ .card{width:100%;min-height:584px;padding:42px 40px;border:1px solid #e0e7f2;border-radius:24px;background:#fff;box-shadow:0 20px 55px rgba(35,58,105,.13);}
4370
+ .card-heading{display:flex;align-items:center;gap:11px;margin-bottom:24px;}.card-heading-mark{display:grid;width:38px;height:38px;place-items:center;border:1px solid #dce4f4;border-radius:12px;color:#536da9;background:#f2f6ff;font-size:18px;}.card-heading small{display:block;color:#7a89a3;font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;}.card-heading h2{margin:3px 0 0;color:#182238;font-size:25px;letter-spacing:-.03em;}
4371
+ .muted{margin:0 0 28px;color:#6e7e99;line-height:1.75;font-size:14px;}
4372
+ .field-label{display:flex;align-items:center;justify-content:space-between;margin:0 0 9px;color:#2c3a55;font-size:13px;font-weight:700;}.field-label span{color:#a4afc0;font-size:11px;font-weight:500;}
4373
+ .input-wrap{position:relative;}.field-icon{position:absolute;top:50%;left:15px;width:18px;height:18px;transform:translateY(-50%);}.field-icon:before{content:"";position:absolute;top:1px;left:5px;width:7px;height:7px;border:1.5px solid #7183a5;border-radius:50%;}.field-icon:after{content:"";position:absolute;top:8px;left:8px;width:1.5px;height:9px;background:#7183a5;box-shadow:3px 5px 0 -0.25px #7183a5;}
4374
+ input{display:block;width:100%;height:50px;padding:0 14px 0 47px;border:1px solid #d4deec;border-radius:12px;outline:0;color:#1c2b46;background:#fbfcfe;font:inherit;font-size:14px;transition:border-color .18s,box-shadow .18s,background .18s;} input::placeholder{color:#aeb9c9;} input:focus{border-color:#6d86bc;background:#fff;box-shadow:0 0 0 4px rgba(83,109,169,.12);}
4375
+ button,a{display:flex;width:100%;min-height:50px;box-sizing:border-box;align-items:center;justify-content:center;gap:8px;padding:12px 14px;border-radius:12px;font:inherit;font-size:14px;font-weight:700;text-align:center;text-decoration:none;cursor:pointer;transition:transform .18s,box-shadow .18s,border-color .18s,background .18s;}
4376
+ 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;}
4377
+ .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;}
4378
+ a{border:1px solid #d5dfed;color:#304364;background:#fff;} a:hover{border-color:#91a6cc;background:#f7f9fd;transform:translateY(-1px);}
4379
+ .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);}
4380
+ @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;}}
4381
+ @media (max-width:420px){body{padding:12px;}.card{padding:27px 20px;}.card-heading h2{font-size:22px;}}
4382
+ </style>
4383
+ </head>
4384
+ <body>
4385
+ <div class="page-shell">
4386
+ <section class="brand-panel" aria-label="喜鹊标书 CLI">
4387
+ <div class="brand-top">
4388
+ <div class="brand-kicker"><span class="brand-symbol">↗</span> XIQUE CLI</div>
4389
+ <div class="brand-status"><i></i> 本机安全接入</div>
4390
+ </div>
4391
+ <div class="brand-copy">
4392
+ <h1>把喜鹊标书<br /><em>带到终端</em></h1>
4393
+ <p>一次登录,连接初始化、大纲、正文与 Word 导出。把重复操作交给 CLI,把精力留给标书本身。</p>
4394
+ <div class="brand-features">
4395
+ <div class="brand-feature"><strong>一次登录</strong><span>后续命令自动复用</span></div>
4396
+ <div class="brand-feature"><strong>分阶段工作流</strong><span>从大纲到正文可控推进</span></div>
4397
+ <div class="brand-feature"><strong>随时可轮换</strong><span>账号中心独立管理 Key</span></div>
4398
+ </div>
4399
+ </div>
4400
+ <div class="brand-footer">喜鹊标书 AI · 命令行工作台</div>
4401
+ </section>
4402
+ <main class="card">
4403
+ <div class="card-heading"><span class="card-heading-mark">⌁</span><div><small>LOCAL ACCESS</small><h2>登录喜鹊标书</h2></div></div>
4404
+ <p class="muted">使用 API Key 登录后,xq-cli 会在本机保存凭证,后续命令无需重复输入。</p>
4405
+ <form method="post" action="/api-key" onsubmit="this.querySelector('button').disabled=true">
4406
+ <label class="field-label" for="api-key">API Key <span>xq_sk_ 开头</span></label>
4407
+ <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>
4408
+ <button type="submit"><span>使用 API Key 登录</span><span class="button-arrow">→</span></button>
4409
+ </form>
4410
+ <div class="divider">或</div>
4411
+ <a href="${escapeHtml(authorizeUrl)}"><span>使用浏览器授权登录</span><span class="button-arrow">↗</span></a>
4412
+ <p class="hint">API Key 可在喜鹊的 API Key 管理页创建。验证成功后即可关闭页面。</p>
4413
+ <div class="secure-note"><span></span>登录状态仅用于当前 CLI 工作流</div>
4414
+ </main>
4415
+ </div>
4416
+ </body>
4417
+ </html>`;
4418
+ }
4419
+
4186
4420
  function renderBrowserLoginResultPage(title, message) {
4187
4421
  return `<!doctype html>
4188
4422
  <html lang="zh-CN">
@@ -4765,6 +4999,8 @@ function pickOutlineConfig(params) {
4765
4999
  return compactObject({
4766
5000
  pageScopeCode: params.pageScopeCode,
4767
5001
  base: params.base,
5002
+ writingQuality: params.writingQuality,
5003
+ deepThinkingFlowVersion: params.deepThinkingFlowVersion,
4768
5004
  themeStyle: params.themeStyle,
4769
5005
  tableQuantity: params.tableQuantity,
4770
5006
  tableColor: params.tableColor,
@@ -4787,6 +5023,9 @@ function buildOutlineConfigSummary(params) {
4787
5023
  return [
4788
5024
  `pageScope=${describeEnumValue(PAGE_SCOPE_OPTIONS, params.pageScopeCode)}`,
4789
5025
  `base=${describeEnumValue(BASE_OPTIONS, params.base)}`,
5026
+ params.writingQuality !== undefined
5027
+ ? `writingQuality=${describeEnumValue(WRITING_QUALITY_OPTIONS, params.writingQuality)}`
5028
+ : null,
4790
5029
  params.themeStyle !== undefined ? `themeStyle=${describeEnumValue(THEME_STYLE_OPTIONS, params.themeStyle)}` : null,
4791
5030
  params.tableQuantity !== undefined ? `tableQuantity=${describeEnumValue(TABLE_QUANTITY_OPTIONS, params.tableQuantity)}` : null,
4792
5031
  params.tableColor !== undefined ? `tableColor=${describeEnumValue(TABLE_COLOR_OPTIONS, params.tableColor)}` : null,
@@ -4982,6 +5221,8 @@ function pickTaskSnapshot(source) {
4982
5221
  rating: source.rating,
4983
5222
  plan: source.plan,
4984
5223
  base: source.base,
5224
+ writingQuality: source.writingQuality,
5225
+ deepThinkingFlowVersion: source.deepThinkingFlowVersion,
4985
5226
  pageScopeCode: source.pageScopeCode,
4986
5227
  task_status: source.task_status,
4987
5228
  plan_mode_phase: source.plan_mode_phase,