@xqyz/xq-cli 0.1.3 → 0.2.1

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 ADDED
@@ -0,0 +1,85 @@
1
+ # 版本迭代日志
2
+
3
+ 本项目遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/):
4
+
5
+ - `MAJOR`:存在不兼容变更。
6
+ - `MINOR`:向后兼容的新功能。
7
+ - `PATCH`:向后兼容的修复或维护更新。
8
+
9
+ 发布日期以 npm 官方仓库记录为准。发布前必须先更新本文件,再执行版本发布。
10
+
11
+ ## [Unreleased]
12
+
13
+ ## [0.2.1] - 2026-08-11
14
+
15
+ ### 安全性
16
+
17
+ - 将 Axios 升级到 `1.19.0`、Form Data 升级到 `4.0.6`,消除旧版 HTTP 与文件上传依赖的已知高危漏洞,保持现有 CLI 命令和参数兼容。
18
+
19
+ ## [0.2.0] - 2026-08-10
20
+
21
+ ### 新增
22
+
23
+ - 新增 `xq-cli directory view --cid <cid>`,通过只读目录树接口查看大纲续写后的完整目录。
24
+ - 支持完整目录的递归编号展示、节点统计、`--json` 结构化输出和 `--out <path>` UTF-8 JSON 导出。
25
+ - 新增 `xq-cli gallery-status`,调用与最新喜鹊前端一致的图库列表接口。
26
+ - 结构化返回 `available` 和 `totalRecords`,供 WorkBuddy 在展示“我的图库”前判断是否可选。
27
+
28
+ ### 文档
29
+
30
+ - 建立正式版本迭代日志。
31
+ - 增加 npm 发布、验证、清理和故障处理手册。
32
+ - 更新 CLI 帮助、README 和 OpenCLI 使用说明。
33
+
34
+ ### 维护
35
+
36
+ - 规范 npm `bin` 入口路径,避免发布时由 npm 自动纠正清单。
37
+ - 增加完整目录格式化与只读 API 集成测试。
38
+
39
+ ## [0.1.3] - 2026-08-07
40
+
41
+ ### 新增
42
+
43
+ - 新增 `xq-cli outline view --cid <cid>`,只读查看已有大纲,不会重新触发大纲生成。
44
+ - 支持通过 `--json` 返回完整大纲数据。
45
+ - 支持通过 `--out <path>` 导出可编辑的大纲 JSON。
46
+ - 新增 `xq-cli outline update --cid <cid> --file <file>`,从完整 JSON 文件更新大纲。
47
+ - 支持修改章节标题、主题、重点程度和章节顺序,以及新增、删除章节。
48
+ - 新增 `--dry-run`,在写入后台前展示新增、修改、删除、排序和重点程度差异。
49
+
50
+ ### 安全性
51
+
52
+ - 自动校验编辑文件中的任务 CID、章节 ID、重复 ID 和必填字段。
53
+ - 自动根据服务端当前大纲计算 `updateFlag`。
54
+ - 非交互环境必须传入 `--yes` 才能保存修改。
55
+ - 删除已有章节必须额外传入 `--allow-delete`。
56
+
57
+ ### 测试与文档
58
+
59
+ - 增加大纲差异处理单元测试和模拟 API 集成测试,共 7 项。
60
+ - 更新 CLI 帮助、README 和 OpenCLI 使用说明。
61
+
62
+ ## [0.1.2] - 2026-04-28
63
+
64
+ ### 变更
65
+
66
+ - 默认使用生产 API 地址 `https://ai.bidfile.qianlima.com/api`。
67
+ - 首次使用可直接执行 `xq-cli login`,不再要求每次显式提供 `--base-url`。
68
+ - 完善本地配置读取与默认值合并,兼容旧配置和配置文件异常场景。
69
+ - 更新生产环境和测试环境登录示例。
70
+
71
+ ## [0.1.1] - 2026-04-27
72
+
73
+ ### 变更
74
+
75
+ - 增加 npm 官方仓库安装和全局升级说明。
76
+ - 将登录示例从本地地址调整为生产环境地址。
77
+ - 清理示例中的真实账号信息,改为通用占位符。
78
+
79
+ ## [0.1.0] - 2026-04-27
80
+
81
+ ### 新增
82
+
83
+ - `@xqyz/xq-cli` 首次公开发布。
84
+ - 提供登录、任务初始化、文件解析、大纲生成、正文生成、状态查询和 Word 导出能力。
85
+ - 提供 `wizard` 全流程向导和生成配置选项查询。
package/README.md CHANGED
@@ -4,6 +4,11 @@ Standalone CLI project for xique brand task workflows.
4
4
 
5
5
  Project execution rules for agents live in [AGENTS.md](./AGENTS.md). That file defines the mandatory config-confirmation flow before any bid generation run.
6
6
 
7
+ Project maintenance documents:
8
+
9
+ - [Version changelog](./CHANGELOG.md)
10
+ - [Release process](./docs/RELEASING.md)
11
+
7
12
  ## Install from npm
8
13
 
9
14
  Install the published package from the public npm registry:
@@ -40,7 +45,7 @@ npm pack
40
45
  That command produces a `.tgz` tarball which can be shared and installed with:
41
46
 
42
47
  ```bash
43
- npm install -g .\xqyz-xq-cli-0.1.3.tgz
48
+ npm install -g .\xqyz-xq-cli-0.2.0.tgz
44
49
  ```
45
50
 
46
51
  ## Usage
@@ -58,6 +63,9 @@ xq-cli outline view --cid <cid>
58
63
  xq-cli outline view --cid <cid> --out D:\output\outline.json
59
64
  xq-cli outline update --cid <cid> --file D:\output\outline.json --dry-run --json
60
65
  xq-cli outline update --cid <cid> --file D:\output\outline.json --yes
66
+ xq-cli directory view --cid <cid>
67
+ xq-cli directory view --cid <cid> --out D:\output\directory.json
68
+ xq-cli gallery-status --json
61
69
  xq-cli write --cid <cid> --wait
62
70
  xq-cli status --cid <cid> --content --json
63
71
  xq-cli export --cid <cid> --interactive --out D:\output
@@ -68,6 +76,13 @@ Fresh installs default to the production API address `https://ai.bidfile.qianlim
68
76
 
69
77
  `login` now defaults to browser authorization. If you explicitly pass `--name` and `--password`, the CLI falls back to password login.
70
78
 
79
+ `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.
80
+
81
+ `gallery-status` checks the same `/image/library/query` endpoint as the latest
82
+ frontend. `available=true` means “我的图库” contains at least one image and may
83
+ be enabled for generation; an empty gallery remains unavailable until the user
84
+ uploads an image in the Xique frontend.
85
+
71
86
  When `outline --wait` or `write --wait` hits a required response-basis step, the CLI will pause and ask you to choose `0/1/2` before continuing. It no longer auto-selects a default when you just press Enter.
72
87
 
73
88
  `write --wait` now also retries `sequelForOutline` when the directory phase stalls at the outline-complete step, and auto-calls `generateAllUnfinished` if the backend reports `100%` but still leaves unfinished chapters behind.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xqyz/xq-cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "CLI for xique bid-book task workflows",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -8,18 +8,19 @@
8
8
  "test": "node --test"
9
9
  },
10
10
  "bin": {
11
- "xq-cli": "./bin/xq-cli.js"
11
+ "xq-cli": "bin/xq-cli.js"
12
12
  },
13
13
  "files": [
14
14
  "bin",
15
15
  "src",
16
- "README.md"
16
+ "README.md",
17
+ "CHANGELOG.md"
17
18
  ],
18
19
  "engines": {
19
20
  "node": ">=18"
20
21
  },
21
22
  "dependencies": {
22
- "axios": "^0.27.2",
23
- "form-data": "^4.0.4"
23
+ "axios": "^1.19.0",
24
+ "form-data": "^4.0.6"
24
25
  }
25
26
  }
package/src/cli.mjs CHANGED
@@ -19,6 +19,10 @@ import {
19
19
  formatOutlineLines,
20
20
  prepareOutlineUpdate,
21
21
  } from './outline-edit.mjs';
22
+ import {
23
+ buildDirectoryViewDocument,
24
+ formatDirectoryLines,
25
+ } from './directory-view.mjs';
22
26
 
23
27
  const APP_DIR = path.join(os.homedir(), '.xq-opencli');
24
28
  const CONFIG_FILE = path.join(APP_DIR, 'config.json');
@@ -349,6 +353,16 @@ async function main() {
349
353
  case 'outline-update':
350
354
  await handleOutlineUpdate(parsed, state);
351
355
  return;
356
+ case 'directory':
357
+ if (!parsed._[1] || isOutlineAction(parsed, ['view', 'show'])) {
358
+ await handleDirectoryView(parsed, state);
359
+ } else {
360
+ throw new Error(`Unknown directory action: ${parsed._[1]}`);
361
+ }
362
+ return;
363
+ case 'directory-view':
364
+ await handleDirectoryView(parsed, state);
365
+ return;
352
366
  case 'write':
353
367
  await handleWrite(parsed, state);
354
368
  return;
@@ -361,6 +375,9 @@ async function main() {
361
375
  case 'choices':
362
376
  handleChoices(parsed);
363
377
  return;
378
+ case 'gallery-status':
379
+ await handleGalleryStatus(parsed, state);
380
+ return;
364
381
  case 'wizard':
365
382
  await handleWizard(parsed, state);
366
383
  return;
@@ -380,9 +397,11 @@ Commands
380
397
  outline Pre-set task params and trigger outline generation
381
398
  outline view View the current outline without triggering generation
382
399
  outline update Update the current outline from an edited JSON file
400
+ directory view View the complete generated directory tree without triggering generation
383
401
  write Charge if needed, enter content phase, and trigger content generation
384
402
  status Query task status
385
403
  choices Print selectable outline/export config values
404
+ gallery-status Check whether My Gallery contains selectable images
386
405
  wizard Run init -> outline -> write -> export with interactive menus
387
406
  export Download the generated docx
388
407
 
@@ -394,6 +413,7 @@ Examples
394
413
  xq-cli parse-status --cid <cid> --uuid <uuid> --wait
395
414
  xq-cli choices
396
415
  xq-cli choices outline --json
416
+ xq-cli gallery-status --json
397
417
  xq-cli wizard
398
418
  xq-cli wizard --file D:\\bids\\tender.docx
399
419
  xq-cli outline --cid <cid> --wait
@@ -407,6 +427,8 @@ Examples
407
427
  xq-cli outline view --cid <cid> --out D:\\output\\outline.json
408
428
  xq-cli outline update --cid <cid> --file D:\\output\\outline.json --dry-run --json
409
429
  xq-cli outline update --cid <cid> --file D:\\output\\outline.json --yes
430
+ xq-cli directory view --cid <cid>
431
+ xq-cli directory view --cid <cid> --out D:\\output\\directory.json
410
432
  xq-cli write --cid <cid> --type 1 --wait
411
433
  xq-cli status --cid <cid> --content
412
434
  xq-cli export --cid <cid> --interactive --out D:\\output\\
@@ -665,6 +687,29 @@ function handleChoices(args) {
665
687
  return payload;
666
688
  }
667
689
 
690
+ async function handleGalleryStatus(args, state) {
691
+ const client = createAuthorizedClient(args, state);
692
+ const response = await postJson(client, '/image/library/query', {
693
+ pageSize: 1,
694
+ pageNum: 1,
695
+ });
696
+ const rawTotal = Number(response?.data?.pageModel?.totalRecords ?? 0);
697
+ const totalRecords = Number.isFinite(rawTotal) && rawTotal > 0 ? rawTotal : 0;
698
+ const resultPayload = {
699
+ command: 'gallery-status',
700
+ available: totalRecords > 0,
701
+ totalRecords,
702
+ };
703
+ outputResult(args, resultPayload, [
704
+ `my gallery: ${resultPayload.available ? 'available' : 'unavailable'}`,
705
+ `totalRecords: ${totalRecords}`,
706
+ resultPayload.available
707
+ ? 'knowledge-img can be enabled.'
708
+ : 'upload at least one image in the Xique frontend before enabling knowledge-img.',
709
+ ]);
710
+ return resultPayload;
711
+ }
712
+
668
713
  async function handleWizard(args, state) {
669
714
  const wizardArgs = await collectWizardArgs(args, state);
670
715
  writeNotice(args, `wizard config: ${buildWizardSummary(wizardArgs)}`);
@@ -903,6 +948,35 @@ async function handleOutlineUpdate(args, state) {
903
948
  return resultPayload;
904
949
  }
905
950
 
951
+ async function handleDirectoryView(args, state) {
952
+ const client = createAuthorizedClient(args, state);
953
+ const cid = resolveCid(args, state);
954
+ const response = await getJson(
955
+ client,
956
+ `/task/directory/tree/${encodeURIComponent(cid)}`,
957
+ {},
958
+ { retries: DEFAULT_RETRY_COUNT },
959
+ );
960
+ const directoryDetail = response?.data || {};
961
+ const directoryDocument = buildDirectoryViewDocument(cid, directoryDetail);
962
+ const savedPath = stringOption(args, 'out')
963
+ ? writeDirectoryViewFile(stringOption(args, 'out'), cid, directoryDocument)
964
+ : null;
965
+
966
+ const resultPayload = {
967
+ command: 'directory-view',
968
+ cid,
969
+ directoryDetail,
970
+ directoryDocument: savedPath ? directoryDocument : undefined,
971
+ savedPath,
972
+ };
973
+ outputResult(args, resultPayload, [
974
+ ...formatDirectoryLines(cid, directoryDetail),
975
+ savedPath ? `savedPath: ${savedPath}` : 'hint: pass --out <file-or-directory> to save the complete directory JSON.',
976
+ ]);
977
+ return resultPayload;
978
+ }
979
+
906
980
  async function confirmOutlineUpdate(args, cid, diff) {
907
981
  if (!supportsInteractivePrompt()) {
908
982
  throw new Error('Outline update changes server data. Review it with --dry-run, then rerun with --yes.');
@@ -940,6 +1014,16 @@ function writeOutlineEditFile(outputOption, cid, document) {
940
1014
  return finalPath;
941
1015
  }
942
1016
 
1017
+ function writeDirectoryViewFile(outputOption, cid, document) {
1018
+ const resolvedOutput = path.resolve(outputOption);
1019
+ const outputIsDirectory = !path.extname(resolvedOutput);
1020
+ const defaultName = `directory-${normalizeDownloadFileName(cid) || 'task'}.json`;
1021
+ const finalPath = outputIsDirectory ? path.join(resolvedOutput, defaultName) : resolvedOutput;
1022
+ fs.mkdirSync(path.dirname(finalPath), { recursive: true });
1023
+ fs.writeFileSync(finalPath, `${JSON.stringify(document, null, 2)}\n`, 'utf8');
1024
+ return finalPath;
1025
+ }
1026
+
943
1027
  async function handleWrite(args, state) {
944
1028
  const client = createAuthorizedClient(args, state);
945
1029
  const cid = resolveCid(args, state);
@@ -0,0 +1,89 @@
1
+ export function extractDirectoryNodes(document = {}) {
2
+ const candidates = [
3
+ document,
4
+ document?.directoryNodes,
5
+ document?.data?.directoryNodes,
6
+ document?.directoryDetail?.directoryNodes,
7
+ document?.contentDetail?.directoryNodes,
8
+ ];
9
+ for (const candidate of candidates) {
10
+ if (Array.isArray(candidate)) {
11
+ return candidate;
12
+ }
13
+ }
14
+ return [];
15
+ }
16
+
17
+ export function buildDirectoryViewDocument(cid, directoryDetail = {}) {
18
+ const detail = directoryDetail && typeof directoryDetail === 'object' && !Array.isArray(directoryDetail)
19
+ ? directoryDetail
20
+ : {};
21
+ return {
22
+ format: 'xq-cli-directory@1',
23
+ ...detail,
24
+ cid,
25
+ directoryNodes: extractDirectoryNodes(detail),
26
+ };
27
+ }
28
+
29
+ export function getDirectoryStats(document = {}) {
30
+ const roots = extractDirectoryNodes(document);
31
+ let nodes = 0;
32
+ let maxDepth = 0;
33
+
34
+ function visit(rows, depth) {
35
+ for (const row of rows) {
36
+ nodes += 1;
37
+ maxDepth = Math.max(maxDepth, depth);
38
+ const children = Array.isArray(row?.children) ? row.children : [];
39
+ visit(children, depth + 1);
40
+ }
41
+ }
42
+
43
+ visit(roots, 1);
44
+ return {
45
+ roots: roots.length,
46
+ nodes,
47
+ maxDepth,
48
+ };
49
+ }
50
+
51
+ export function formatDirectoryLines(cid, directoryDetail = {}) {
52
+ const roots = extractDirectoryNodes(directoryDetail);
53
+ const stats = getDirectoryStats(roots);
54
+ const lines = [
55
+ `directory: cid=${cid}`,
56
+ `title: ${normalizeText(directoryDetail?.title) || '-'}`,
57
+ `task_status: ${directoryDetail?.task_status ?? '-'}`,
58
+ `roots: ${stats.roots}`,
59
+ `nodes: ${stats.nodes}`,
60
+ `max_depth: ${stats.maxDepth}`,
61
+ ];
62
+
63
+ if (roots.length === 0) {
64
+ lines.push('directory tree: empty');
65
+ return lines;
66
+ }
67
+
68
+ lines.push('directory tree:');
69
+ appendDirectoryRows(lines, roots);
70
+ return lines;
71
+ }
72
+
73
+ function appendDirectoryRows(lines, rows, prefix = [], depth = 0) {
74
+ rows.forEach((row, index) => {
75
+ const position = [...prefix, index + 1];
76
+ const text = normalizeText(row?.text || row?.title || row?.name) || '(unnamed directory)';
77
+ const id = normalizeText(row?.id);
78
+ lines.push(`${' '.repeat(depth)}${position.join('.')}. ${text}${id ? ` [id=${id}]` : ''}`);
79
+ const children = Array.isArray(row?.children) ? row.children : [];
80
+ appendDirectoryRows(lines, children, position, depth + 1);
81
+ });
82
+ }
83
+
84
+ function normalizeText(value) {
85
+ if (value === undefined || value === null) {
86
+ return '';
87
+ }
88
+ return String(value).trim();
89
+ }