@ohos-cpf/3rdloop 0.0.9 → 0.0.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/README.md +16 -0
- package/lib/cli.js +16 -0
- package/lib/knowledge.js +363 -0
- package/package.json +1 -1
- package/vendor/VERSION +2 -2
package/README.md
CHANGED
|
@@ -190,6 +190,22 @@ npm 包内置 MCP Gateway(`vendor/MCP`)、知识库检索引擎(`vendor/Ar
|
|
|
190
190
|
|
|
191
191
|
> SKILL 中的分析脚本(如 `analyze-xts-coverage.cjs`)通过 `{{skillDir}}/../../../MCP/scripts/` 相对路径调用,npm 安装态与仓库开发态一致,无需额外配置。装配通常无需手动执行——`3rdloop doctor`(README 快速上手第一步)会自动完成;`run/submit` 等任务命令的预检若发现未装配,仅在 stderr 提示一行(不修改任何文件)。
|
|
192
192
|
|
|
193
|
+
### 场景七:知识回收(把沉淀的知识打包发给维护者)
|
|
194
|
+
|
|
195
|
+
任务执行会在本机沉淀知识 JSON(`<dataDir>/Knowledge/` 下的 `index.json` 登记表 + `kn_*.json` 条目)。想贡献给社区/团队,打包发给维护者即可:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# 用户侧:打包(纯本地操作,不上传任何数据)
|
|
199
|
+
3rdloop knowledge export # → ./3rdloop-knowledge-<日期>-<主机名>.zip
|
|
200
|
+
3rdloop knowledge export --note "mp3agic 批次" # 附备注
|
|
201
|
+
|
|
202
|
+
# 维护者侧:收到 zip 后解包到收件箱
|
|
203
|
+
3rdloop knowledge import <zip> --dry-run # 预览
|
|
204
|
+
3rdloop knowledge import <zip> # → ~/.3lib/3rdloop/knowledge-inbox/<来源主机>-<日期>/
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
包内容只有知识 JSON(不含 md / kb 文档 / BadCase)。zip 为标准格式,可先解压自查;多个用户的包在收件箱按 `主机名-日期` 隔离,互不冲突,也不覆盖维护者本地知识库——人工评估后再入库。
|
|
208
|
+
|
|
193
209
|
更多进阶用法(任务管理、`step` 单步执行、`orch` 自定义编排、工作流注册等),见仓库 `cli/README_DEVELOP.md` 或 `3rdloop <子命令> --help`。
|
|
194
210
|
|
|
195
211
|
|
package/lib/cli.js
CHANGED
|
@@ -146,6 +146,9 @@ export async function runCli(argv) {
|
|
|
146
146
|
case 'mcp':
|
|
147
147
|
return cmdMcpImpl({ rest, user3libHome, jsonMode });
|
|
148
148
|
|
|
149
|
+
case 'knowledge':
|
|
150
|
+
return cmdKnowledgeImpl({ rest, user3libHome, dataDir, jsonMode });
|
|
151
|
+
|
|
149
152
|
case 'serve':
|
|
150
153
|
return cmdServe({ rest, jsonMode, dataDir, user3libHome });
|
|
151
154
|
|
|
@@ -238,6 +241,16 @@ async function cmdMcpImpl({ rest, user3libHome, jsonMode }) {
|
|
|
238
241
|
return cmdMcp({ rest, user3libHome, jsonMode });
|
|
239
242
|
}
|
|
240
243
|
|
|
244
|
+
// ─── knowledge(知识打包回收)──────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 3rdloop knowledge 命令二段分发(转发到 lib/knowledge.js)。
|
|
248
|
+
*/
|
|
249
|
+
async function cmdKnowledgeImpl({ rest, user3libHome, dataDir, jsonMode }) {
|
|
250
|
+
const { cmdKnowledge } = await import('./knowledge.js');
|
|
251
|
+
return cmdKnowledge({ rest, user3libHome, dataDir, jsonMode });
|
|
252
|
+
}
|
|
253
|
+
|
|
241
254
|
// ─── update(自更新)─────────────────────────────────────────────
|
|
242
255
|
|
|
243
256
|
async function cmdUpdate({ flags, rest, jsonMode, out }) {
|
|
@@ -684,6 +697,9 @@ function printHelp() {
|
|
|
684
697
|
3rdloop mcp install [--cli opencode|deveco|all] 装配 MCP Gateway(注册到 AI CLI +
|
|
685
698
|
种子复制知识库数据/BadCase 到 ~/.3lib/3rdloop/,npm 安装态必需)
|
|
686
699
|
3rdloop mcp uninstall|status 移除注册 / 查看装配状态
|
|
700
|
+
3rdloop knowledge export [--note <text>]
|
|
701
|
+
打包本地知识沉淀 JSON(db/Knowledge)为 zip 发给维护者回收
|
|
702
|
+
3rdloop knowledge import <zip> [--dry-run] 维护者解包收到的知识包到收件箱
|
|
687
703
|
3rdloop serve [--port N] [--web-port N] 启动 Server 后端 + Web 前端(同时启动)
|
|
688
704
|
[--web-root <dir>] [--ext-root <dir>] [--ext <name,...>] [--no-ext] [--open]
|
|
689
705
|
3rdloop serve install-ext <dir> 安装扩展(tag/issue/prcheck 等二级功能)
|
package/lib/knowledge.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knowledge.js —— 3rdloop knowledge:知识沉淀 JSON 打包回收(export)与收件(import)
|
|
3
|
+
*
|
|
4
|
+
* 最简人工中转通道:知识沉淀系统的数据全部是 JSON(<dataDir>/Knowledge/ 下的
|
|
5
|
+
* index.json 登记表 + kn_*.json 条目),export 打包这些 JSON 为 zip 发给维护者;
|
|
6
|
+
* 维护者 import 解包到收件箱目录人工评估,不直接覆盖本地知识库。
|
|
7
|
+
*
|
|
8
|
+
* 用户侧: 3rdloop knowledge export [--output <file>] [--note <text>]
|
|
9
|
+
* 维护者侧: 3rdloop knowledge import <zip> [--dry-run]
|
|
10
|
+
*
|
|
11
|
+
* 设计约束:
|
|
12
|
+
* - 只打 JSON,不碰任何 md / kb-data / BadCase
|
|
13
|
+
* - export 纯本地打包,不上传
|
|
14
|
+
* - import 落位到收件箱 ~/.3lib/3rdloop/knowledge-inbox/<来源>-<日期>/,
|
|
15
|
+
* 多个用户的包互不冲突,也不污染维护者自己的 Knowledge 库(人工评估后自行入库)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import os from 'node:os';
|
|
21
|
+
import zlib from 'node:zlib';
|
|
22
|
+
import process from 'node:process';
|
|
23
|
+
|
|
24
|
+
// ─── 路径 ─────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 知识目录。
|
|
28
|
+
* @param {string} dataDir - CLI 解析的数据目录(Knowledge = <dataDir>/Knowledge)
|
|
29
|
+
*/
|
|
30
|
+
export function knowledgePaths(dataDir) {
|
|
31
|
+
return {
|
|
32
|
+
knowledgeDir: path.join(dataDir, 'Knowledge'),
|
|
33
|
+
inboxRoot: null, // import 侧单独解析(基于 user3libHome)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 维护者收件箱根目录。 */
|
|
38
|
+
function inboxRoot(user3libHome) {
|
|
39
|
+
return path.join(user3libHome, '3rdloop', 'knowledge-inbox');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ─── zip 读写(单成员 DEFLATE zip,纯 Node 无第三方依赖)──────────────
|
|
43
|
+
// 一个 zip 只含一个被 deflate 压缩的 JSON 载荷文件 payload.json。
|
|
44
|
+
|
|
45
|
+
const PAYLOAD_NAME = 'payload.json';
|
|
46
|
+
|
|
47
|
+
/** 单文件 DEFLATE zip 写入(无第三方依赖)。 */
|
|
48
|
+
function writeSingleFileZip(filePath, content) {
|
|
49
|
+
const data = Buffer.from(content, 'utf8');
|
|
50
|
+
const compressed = zlib.deflateRawSync(data, { level: 9 });
|
|
51
|
+
|
|
52
|
+
// Local file header
|
|
53
|
+
const local = Buffer.alloc(30);
|
|
54
|
+
local.writeUInt32LE(0x04034b50, 0); // signature
|
|
55
|
+
local.writeUInt16LE(20, 4); // version needed
|
|
56
|
+
local.writeUInt16LE(0, 6); // flags
|
|
57
|
+
local.writeUInt16LE(8, 8); // method: deflate
|
|
58
|
+
local.writeUInt16LE(0, 10); // mod time
|
|
59
|
+
local.writeUInt16LE(0x2d21, 12); // mod date(任意合法值)
|
|
60
|
+
local.writeUInt32LE(crc32(data), 14); // crc32
|
|
61
|
+
local.writeUInt32LE(compressed.length, 18);
|
|
62
|
+
local.writeUInt32LE(data.length, 22);
|
|
63
|
+
local.writeUInt16LE(PAYLOAD_NAME.length, 26);
|
|
64
|
+
local.writeUInt16LE(0, 28); // extra len
|
|
65
|
+
|
|
66
|
+
// Central directory
|
|
67
|
+
const central = Buffer.alloc(46);
|
|
68
|
+
central.writeUInt32LE(0x02014b50, 0);
|
|
69
|
+
central.writeUInt16LE(20, 4); // version made by
|
|
70
|
+
central.writeUInt16LE(20, 6); // version needed
|
|
71
|
+
central.writeUInt16LE(0, 8); // flags
|
|
72
|
+
central.writeUInt16LE(8, 10); // method
|
|
73
|
+
central.writeUInt16LE(0, 12); // time
|
|
74
|
+
central.writeUInt16LE(0x2d21, 14); // date
|
|
75
|
+
central.writeUInt32LE(crc32(data), 16);
|
|
76
|
+
central.writeUInt32LE(compressed.length, 20);
|
|
77
|
+
central.writeUInt32LE(data.length, 24);
|
|
78
|
+
central.writeUInt16LE(PAYLOAD_NAME.length, 28);
|
|
79
|
+
// 其余字段 0(comment/extra/disk/attrs/offset 均为 0)
|
|
80
|
+
|
|
81
|
+
const nameBuf = Buffer.from(PAYLOAD_NAME, 'utf8');
|
|
82
|
+
// central directory 大小含文件名(header 46 + name),起始偏移 = local(30) + name + data
|
|
83
|
+
const centralSize = central.length + nameBuf.length;
|
|
84
|
+
const centralOffset = local.length + nameBuf.length + compressed.length;
|
|
85
|
+
const eocd = Buffer.alloc(22);
|
|
86
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
87
|
+
eocd.writeUInt16LE(1, 8); // entries
|
|
88
|
+
eocd.writeUInt16LE(1, 10);
|
|
89
|
+
eocd.writeUInt32LE(centralSize, 12); // central directory 总大小(含文件名)
|
|
90
|
+
eocd.writeUInt32LE(centralOffset, 16); // central directory 起始偏移
|
|
91
|
+
|
|
92
|
+
fs.writeFileSync(filePath, Buffer.concat([local, nameBuf, compressed, central, nameBuf, eocd]));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 读取单成员 zip(自动识别 STORE/DEFLATE)。 */
|
|
96
|
+
function readSingleFileZip(filePath) {
|
|
97
|
+
const buf = fs.readFileSync(filePath);
|
|
98
|
+
// 找 EOCD
|
|
99
|
+
let eocd = -1;
|
|
100
|
+
for (let i = buf.length - 22; i >= 0; i--) {
|
|
101
|
+
if (buf.readUInt32LE(i) === 0x06054b50) { eocd = i; break; }
|
|
102
|
+
}
|
|
103
|
+
if (eocd < 0) throw new Error('非法 zip:未找到 EOCD');
|
|
104
|
+
const entryCount = buf.readUInt16LE(eocd + 10);
|
|
105
|
+
if (entryCount !== 1) throw new Error(`非法载荷 zip:期望单成员,实际 ${entryCount} 个`);
|
|
106
|
+
|
|
107
|
+
// Local header
|
|
108
|
+
if (buf.readUInt32LE(0) !== 0x04034b50) throw new Error('非法 zip:缺少 local header');
|
|
109
|
+
const method = buf.readUInt16LE(8);
|
|
110
|
+
const compSize = buf.readUInt32LE(18);
|
|
111
|
+
const nameLen = buf.readUInt16LE(26);
|
|
112
|
+
const extraLen = buf.readUInt16LE(28);
|
|
113
|
+
const dataStart = 30 + nameLen + extraLen;
|
|
114
|
+
const compressed = buf.subarray(dataStart, dataStart + compSize);
|
|
115
|
+
|
|
116
|
+
const data = method === 8 ? zlib.inflateRawSync(compressed) : compressed;
|
|
117
|
+
return data.toString('utf8');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** crc32(IEEE 802.3,zip 标准)。 */
|
|
121
|
+
function crc32(buf) {
|
|
122
|
+
let c;
|
|
123
|
+
const table = crc32._table || (crc32._table = (() => {
|
|
124
|
+
const t = new Uint32Array(256);
|
|
125
|
+
for (let n = 0; n < 256; n++) {
|
|
126
|
+
c = n;
|
|
127
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
128
|
+
t[n] = c >>> 0;
|
|
129
|
+
}
|
|
130
|
+
return t;
|
|
131
|
+
})());
|
|
132
|
+
let crc = 0xffffffff;
|
|
133
|
+
for (let i = 0; i < buf.length; i++) {
|
|
134
|
+
crc = table[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8);
|
|
135
|
+
}
|
|
136
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ─── export ───────────────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 3rdloop knowledge export 实现:打包 <dataDir>/Knowledge/ 下全部 JSON。
|
|
143
|
+
* @param {object} opts
|
|
144
|
+
* @param {string} opts.dataDir
|
|
145
|
+
* @param {string[]} opts.rest - 命令参数(--output/--note)
|
|
146
|
+
* @param {boolean} opts.jsonMode
|
|
147
|
+
* @returns {Promise<number>} 退出码
|
|
148
|
+
*/
|
|
149
|
+
export async function knowledgeExport({ dataDir, rest = [], jsonMode = false }) {
|
|
150
|
+
const out = (data) => { if (jsonMode) process.stdout.write(JSON.stringify(data, null, 2) + '\n'); };
|
|
151
|
+
const log = (msg) => process.stderr.write(`${msg}\n`);
|
|
152
|
+
|
|
153
|
+
const output = _flagValue(rest, '--output');
|
|
154
|
+
const note = _flagValue(rest, '--note') || '';
|
|
155
|
+
|
|
156
|
+
const knowledgeDir = path.join(dataDir, 'Knowledge');
|
|
157
|
+
if (!fs.existsSync(knowledgeDir)) {
|
|
158
|
+
log(`未找到知识目录: ${knowledgeDir}`);
|
|
159
|
+
log('提示: 知识在任务执行后自动沉淀(db/Knowledge),先跑几个任务再看。');
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 收集全部 .json(index.json 登记表 + kn_*.json 知识条目)
|
|
164
|
+
const files = {};
|
|
165
|
+
for (const entry of fs.readdirSync(knowledgeDir, { withFileTypes: true })) {
|
|
166
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) continue;
|
|
167
|
+
files[entry.name] = fs.readFileSync(path.join(knowledgeDir, entry.name), 'utf8');
|
|
168
|
+
}
|
|
169
|
+
if (Object.keys(files).length === 0) {
|
|
170
|
+
log(`知识目录为空: ${knowledgeDir}`);
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const manifest = {
|
|
175
|
+
format: '3rdloop-knowledge/1',
|
|
176
|
+
exportedAt: new Date().toISOString(),
|
|
177
|
+
host: os.hostname(),
|
|
178
|
+
platform: process.platform,
|
|
179
|
+
note,
|
|
180
|
+
stats: {
|
|
181
|
+
files: Object.keys(files).length,
|
|
182
|
+
entries: files['index.json']
|
|
183
|
+
? _countIndexEntries(files['index.json'])
|
|
184
|
+
: Object.keys(files).filter(n => n.startsWith('kn_')).length,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
const payload = JSON.stringify({ manifest, knowledgeFiles: files }, null, 2);
|
|
188
|
+
|
|
189
|
+
// 默认文件名:./3rdloop-knowledge-<YYYYMMDD>-<主机名>.zip
|
|
190
|
+
const date = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
191
|
+
const defaultName = `3rdloop-knowledge-${date}-${os.hostname().split('.')[0]}.zip`;
|
|
192
|
+
const target = output ? path.resolve(output) : path.join(process.cwd(), defaultName);
|
|
193
|
+
|
|
194
|
+
writeSingleFileZip(target, payload);
|
|
195
|
+
|
|
196
|
+
if (jsonMode) {
|
|
197
|
+
out({ ok: true, file: target, ...manifest.stats });
|
|
198
|
+
} else {
|
|
199
|
+
log(`[3rdloop knowledge] 知识包已生成: ${target}`);
|
|
200
|
+
log(` JSON 文件: ${manifest.stats.files} 个(index.json + kn_*.json)`);
|
|
201
|
+
log(` 知识条目: ${manifest.stats.entries} 条`);
|
|
202
|
+
if (note) log(` 备注: ${note}`);
|
|
203
|
+
log(` 大小: ${(fs.statSync(target).size / 1024).toFixed(1)} KB`);
|
|
204
|
+
log(`请将 zip 发送给维护者(微信/邮件均可);内容由你本机知识 JSON 打包而成,可先解压自查。`);
|
|
205
|
+
}
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** 解析 index.json 统计条目数(容错:解析失败返回 0)。 */
|
|
210
|
+
function _countIndexEntries(indexRaw) {
|
|
211
|
+
try {
|
|
212
|
+
const arr = JSON.parse(indexRaw);
|
|
213
|
+
return Array.isArray(arr) ? arr.length : 0;
|
|
214
|
+
} catch {
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ─── import ───────────────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* 3rdloop knowledge import 实现(维护者侧:收 zip → 解到收件箱目录)。
|
|
223
|
+
* 收件箱按 <来源主机>-<导出时间> 隔离,多包互不冲突;不覆盖本地 Knowledge 库。
|
|
224
|
+
* @param {object} opts
|
|
225
|
+
* @param {string} opts.user3libHome
|
|
226
|
+
* @param {string[]} opts.rest - <zip> --dry-run
|
|
227
|
+
* @param {boolean} opts.jsonMode
|
|
228
|
+
*/
|
|
229
|
+
export async function knowledgeImport({ user3libHome, rest = [], jsonMode = false }) {
|
|
230
|
+
const out = (data) => { if (jsonMode) process.stdout.write(JSON.stringify(data, null, 2) + '\n'); };
|
|
231
|
+
const log = (msg) => process.stderr.write(`${msg}\n`);
|
|
232
|
+
|
|
233
|
+
const dryRun = rest.includes('--dry-run');
|
|
234
|
+
const zipPath = rest.find(a => !a.startsWith('--'));
|
|
235
|
+
if (!zipPath) {
|
|
236
|
+
log('缺少 zip 路径。用法: 3rdloop knowledge import <zip> [--dry-run]');
|
|
237
|
+
return 1;
|
|
238
|
+
}
|
|
239
|
+
const absZip = path.resolve(zipPath);
|
|
240
|
+
if (!fs.existsSync(absZip)) {
|
|
241
|
+
log(`错误: 文件不存在: ${absZip}`);
|
|
242
|
+
return 1;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let payload;
|
|
246
|
+
try {
|
|
247
|
+
payload = JSON.parse(readSingleFileZip(absZip));
|
|
248
|
+
} catch (err) {
|
|
249
|
+
log(`错误: 解析知识包失败: ${err.message}`);
|
|
250
|
+
return 1;
|
|
251
|
+
}
|
|
252
|
+
const { manifest, knowledgeFiles } = payload;
|
|
253
|
+
if (!manifest || manifest.format !== '3rdloop-knowledge/1') {
|
|
254
|
+
log(`错误: 未知知识包格式: ${manifest?.format || '(缺失 manifest)'}`);
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 收件箱:~/.3lib/3rdloop/knowledge-inbox/<主机名>-<导出日期>/
|
|
259
|
+
const date = String(manifest.exportedAt).slice(0, 10);
|
|
260
|
+
const host = String(manifest.host || 'unknown').split('.')[0];
|
|
261
|
+
const inboxDir = path.join(inboxRoot(user3libHome), `${host}-${date}`);
|
|
262
|
+
|
|
263
|
+
if (dryRun) {
|
|
264
|
+
if (jsonMode) out({ ok: true, dryRun: true, manifest, inboxDir, files: Object.keys(knowledgeFiles || {}) });
|
|
265
|
+
else {
|
|
266
|
+
log(`[3rdloop knowledge] 预演(不写入任何文件)`);
|
|
267
|
+
log(` 知识包: ${absZip}`);
|
|
268
|
+
log(` 导出时间: ${manifest.exportedAt} 来源: ${manifest.host}(${manifest.platform})`);
|
|
269
|
+
if (manifest.note) log(` 备注: ${manifest.note}`);
|
|
270
|
+
log(` JSON 文件: ${Object.keys(knowledgeFiles || {}).length} 个 → 收件箱 ${inboxDir}`);
|
|
271
|
+
}
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let written = 0;
|
|
276
|
+
for (const [name, content] of Object.entries(knowledgeFiles || {})) {
|
|
277
|
+
fs.mkdirSync(inboxDir, { recursive: true });
|
|
278
|
+
fs.writeFileSync(path.join(inboxDir, name), content);
|
|
279
|
+
written++;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (jsonMode) {
|
|
283
|
+
out({ ok: true, inboxDir, written, manifest });
|
|
284
|
+
} else {
|
|
285
|
+
log(`[3rdloop knowledge] 已解包到收件箱: ${inboxDir}`);
|
|
286
|
+
log(` JSON 文件: ${written} 个(含 index.json + kn_*.json)`);
|
|
287
|
+
log(` 来源: ${manifest.host} 导出时间: ${manifest.exportedAt}${manifest.note ? ` 备注: ${manifest.note}` : ''}`);
|
|
288
|
+
log(`人工评估后可将有价值条目拷入仓库或本地 <dataDir>/Knowledge/。`);
|
|
289
|
+
}
|
|
290
|
+
return 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ─── 帮助 / 入口 ──────────────────────────────────────────────────────
|
|
294
|
+
|
|
295
|
+
function _flagValue(rest, name) {
|
|
296
|
+
const idx = rest.indexOf(name);
|
|
297
|
+
return idx !== -1 ? rest[idx + 1] : undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** 该 flag 是否携带值。 */
|
|
301
|
+
function _hasValue(flag) {
|
|
302
|
+
return flag === '--output' || flag === '--note';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function printKnowledgeHelp() {
|
|
306
|
+
process.stdout.write(`3rdloop knowledge —— 知识沉淀 JSON 打包回收(人工中转通道)
|
|
307
|
+
|
|
308
|
+
用法(用户侧:打包发给维护者):
|
|
309
|
+
3rdloop knowledge export [选项]
|
|
310
|
+
打包 <dataDir>/Knowledge/ 下的全部 JSON(index.json 登记表 + kn_*.json 知识条目)
|
|
311
|
+
选项:
|
|
312
|
+
--output <file> 输出路径(默认 ./3rdloop-knowledge-<日期>-<主机名>.zip)
|
|
313
|
+
--note <text> 备注(写入 manifest,如 "mp3agic 检视批次")
|
|
314
|
+
--json 结构化输出
|
|
315
|
+
|
|
316
|
+
用法(维护者侧:收到 zip 后解包到收件箱):
|
|
317
|
+
3rdloop knowledge import <zip> [--dry-run]
|
|
318
|
+
解包到 ~/.3lib/3rdloop/knowledge-inbox/<来源主机>-<日期>/
|
|
319
|
+
多个用户的包互不冲突;不覆盖本地知识库,人工评估后自行入库
|
|
320
|
+
|
|
321
|
+
说明:
|
|
322
|
+
- export 纯本地打包,不上传任何数据;可先解压自查内容再发送
|
|
323
|
+
- 只含知识沉淀 JSON,不含 md / kb 文档 / BadCase
|
|
324
|
+
`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 3rdloop knowledge 命令入口。
|
|
329
|
+
* @param {object} opts
|
|
330
|
+
* @param {string[]} opts.rest
|
|
331
|
+
* @param {string} opts.user3libHome
|
|
332
|
+
* @param {string} opts.dataDir
|
|
333
|
+
* @param {boolean} opts.jsonMode
|
|
334
|
+
* @returns {Promise<number>}
|
|
335
|
+
*/
|
|
336
|
+
export async function cmdKnowledge({ rest = [], user3libHome = '', dataDir = '', jsonMode = false }) {
|
|
337
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
338
|
+
printKnowledgeHelp();
|
|
339
|
+
return 0;
|
|
340
|
+
}
|
|
341
|
+
// 定位位置子命令(第一个非 flag 参数),并从参数中剥离子命令词
|
|
342
|
+
let subIdx = -1;
|
|
343
|
+
for (let i = 0; i < rest.length; i++) {
|
|
344
|
+
if (rest[i].startsWith('--')) {
|
|
345
|
+
if (_hasValue(rest[i]) && rest[i + 1] !== undefined && !rest[i + 1].startsWith('--')) i++;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
subIdx = i;
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
const args = subIdx === -1 ? [] : [...rest.slice(0, subIdx), ...rest.slice(subIdx + 1)];
|
|
352
|
+
|
|
353
|
+
if (subIdx === -1) {
|
|
354
|
+
printKnowledgeHelp();
|
|
355
|
+
return rest.length === 0 ? 0 : 1;
|
|
356
|
+
}
|
|
357
|
+
const sub = rest[subIdx];
|
|
358
|
+
if (sub === 'export') return knowledgeExport({ dataDir, rest: args, jsonMode });
|
|
359
|
+
if (sub === 'import') return knowledgeImport({ user3libHome, rest: args, jsonMode });
|
|
360
|
+
process.stderr.write(`未知子命令: ${sub}\n`);
|
|
361
|
+
printKnowledgeHelp();
|
|
362
|
+
return 1;
|
|
363
|
+
}
|
package/package.json
CHANGED
package/vendor/VERSION
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
0.0.
|
|
2
|
-
built=2026-09-04T07:
|
|
1
|
+
0.0.11
|
|
2
|
+
built=2026-09-04T07:50:10.913Z
|
|
3
3
|
sha=84d14394dbb68bd4
|