@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 +85 -0
- package/README.md +16 -1
- package/package.json +6 -5
- package/src/cli.mjs +84 -0
- package/src/directory-view.mjs +89 -0
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.
|
|
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
|
+
"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": "
|
|
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": "^
|
|
23
|
-
"form-data": "^4.0.
|
|
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
|
+
}
|