dxkj 0.1.0 → 0.1.2

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/src/cli.js CHANGED
@@ -1,321 +1,320 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { parseArgs } from 'node:util';
4
- import { loadConfig, saveConfig, resolveServerUrl, CONFIG_PATH, getByPath, setByPath } from './lib/config.js';
5
- import { loadRegistry, filterItems, findByRef, resolveSource } from './lib/registry.js';
6
- import { validateItem, formatReport } from './lib/validator.js';
7
- import { renderList, renderDetail } from './lib/format.js';
8
- import { resolveTargetDir } from './lib/targets.js';
9
- import { downloadToTemp, extractZip, writeManifest, cleanup } from './lib/installer.js';
10
-
11
- const VERSION = '0.1.0';
12
-
13
- const OPTIONS = {
14
- source: { type: 'string' },
15
- server: { type: 'string' },
16
- kind: { type: 'string' },
17
- category: { type: 'string' },
18
- tag: { type: 'string' },
19
- status: { type: 'string' },
20
- target: { type: 'string' },
21
- editor: { type: 'string' },
22
- dir: { type: 'string' },
23
- json: { type: 'boolean' },
24
- all: { type: 'boolean' },
25
- force: { type: 'boolean' },
26
- help: { type: 'boolean', short: 'h' },
27
- version: { type: 'boolean', short: 'v' },
28
- yes: { type: 'boolean', short: 'y' }
29
- };
30
-
31
- function parse(argv) {
32
- return parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: false });
33
- }
34
-
35
- function help() {
36
- return `dxkj v${VERSION} — dxkj 目录 CLI(prompt / skill / agent / plugin / tool)
37
-
38
- 用法:
39
- dxkj list [--kind skill] [--category CG] [--tag x] [--source <url|file>] [--json]
40
- dxkj search <关键词>
41
- dxkj show <编号|id>
42
- dxkj install <编号...> [--target ai-editor|navlink|generic] [--editor opencode|claude] [--dir 路径]
43
- dxkj install --all [--kind skill]
44
- dxkj uninstall <编号...>
45
- dxkj validate <条目.json>
46
- dxkj config get [键] | config set <键> <值> | config path
47
- dxkj --help | --version
48
-
49
- 默认服务地址:${resolveServerUrl()}
50
- 配置文件:${CONFIG_PATH}
51
- 站点未集成时可用 --source ./fixtures/sample-registry.json 本地验证。`;
52
- }
53
-
54
- async function cmdList(argv) {
55
- const { values } = parse(argv);
56
- const cfg = loadConfig();
57
- const { items, source } = await loadRegistry(values, cfg);
58
- const list = filterItems(items, values);
59
- if (values.json) {
60
- console.log(JSON.stringify({ source, count: list.length, items: list }, null, 2));
61
- return 0;
62
- }
63
- console.log(`数据源: ${source}`);
64
- console.log(renderList(list));
65
- console.log(`\n共 ${list.length} 条`);
66
- return 0;
67
- }
68
-
69
- async function cmdSearch(argv) {
70
- const { values, positionals } = parse(argv);
71
- const kw = positionals.slice(1).join(' ').toLowerCase();
72
- if (!kw) {
73
- console.error('用法:dxkj search <关键词>');
74
- return 1;
75
- }
76
- const cfg = loadConfig();
77
- const { items } = await loadRegistry(values, cfg);
78
- const list = filterItems(items, values).filter((it) =>
79
- [it.id, it.name, it.description, it.intro, it.category, ...it.tags].join(' ').toLowerCase().includes(kw)
80
- );
81
- console.log(renderList(list));
82
- console.log(`\n命中 ${list.length} 条`);
83
- return 0;
84
- }
85
-
86
- async function cmdShow(argv) {
87
- const { values, positionals } = parse(argv);
88
- const ref = positionals[1];
89
- if (!ref) {
90
- console.error('用法:dxkj show <编号|id>');
91
- return 1;
92
- }
93
- const cfg = loadConfig();
94
- const { items } = await loadRegistry(values, cfg);
95
- const item = findByRef(items, ref);
96
- if (!item) {
97
- console.error(`未找到条目:${ref}`);
98
- return 1;
99
- }
100
- if (values.json) {
101
- console.log(JSON.stringify(item, null, 2));
102
- return 0;
103
- }
104
- console.log(renderDetail(item, resolveServerUrl(values, cfg)));
105
- return 0;
106
- }
107
-
108
- async function cmdInstall(argv) {
109
- const { values, positionals } = parse(argv);
110
- const cfg = loadConfig();
111
- const { items } = await loadRegistry(values, cfg);
112
-
113
- let targets = positionals.slice(1);
114
- if (values.all) {
115
- targets = filterItems(items, values).map((it) => String(it.number));
116
- } else if (values.kind && targets.length === 0) {
117
- targets = filterItems(items, values).map((it) => String(it.number));
118
- }
119
- if (targets.length === 0) {
120
- console.error('用法:dxkj install <编号...> | dxkj install --all [--kind skill]');
121
- return 1;
122
- }
123
-
124
- // navlink 目标可直接复用插件安装包(先支持 zip 解压方式)
125
- let ok = 0;
126
- let failed = 0;
127
- for (const ref of targets) {
128
- const item = findByRef(items, ref);
129
- if (!item) {
130
- console.error(`✗ 未找到条目:${ref}`);
131
- failed++;
132
- continue;
133
- }
134
- if (!item.install.url) {
135
- console.error(`✗ #${item.number} ${item.name} 缺少安装包地址,跳过`);
136
- failed++;
137
- continue;
138
- }
139
- let dest;
140
- try {
141
- dest = resolveTargetDir(item, values, cfg);
142
- } catch (err) {
143
- console.error(`✗ #${item.number} ${item.name}: ${err.message}`);
144
- failed++;
145
- continue;
146
- }
147
- let temp = null;
148
- try {
149
- temp = await downloadToTemp(item.install.url);
150
- extractZip(temp, dest.dir);
151
- writeManifest(dest.dir, {
152
- id: item.id,
153
- number: item.number,
154
- name: item.name,
155
- kind: item.kind,
156
- version: item.version,
157
- source: item.install.url,
158
- target: dest.target,
159
- installedAt: new Date().toISOString()
160
- });
161
- console.log(`✓ 已安装 #${item.number} ${item.name} → ${dest.dir} (${dest.hint})`);
162
- ok++;
163
- } catch (err) {
164
- console.error(`✗ #${item.number} ${item.name}: ${err.message}`);
165
- failed++;
166
- } finally {
167
- if (temp) cleanup(temp);
168
- }
169
- }
170
- console.log(`\n完成:成功 ${ok},失败 ${failed}`);
171
- return failed === 0 ? 0 : 1;
172
- }
173
-
174
- async function cmdUninstall(argv) {
175
- const { values, positionals } = parse(argv);
176
- const cfg = loadConfig();
177
- const { items } = await loadRegistry(values, cfg);
178
- const { removeDirSafe, readManifest } = await import('./lib/installer.js');
179
-
180
- const refs = positionals.slice(1);
181
- if (refs.length === 0) {
182
- console.error('用法:dxkj uninstall <编号...>');
183
- return 1;
184
- }
185
- let ok = 0;
186
- let failed = 0;
187
- for (const ref of refs) {
188
- const item = findByRef(items, ref);
189
- if (!item) {
190
- console.error(`✗ 未找到条目:${ref}`);
191
- failed++;
192
- continue;
193
- }
194
- let dest;
195
- try {
196
- dest = resolveTargetDir(item, values, cfg);
197
- } catch (err) {
198
- console.error(`✗ ${item.name}: ${err.message}`);
199
- failed++;
200
- continue;
201
- }
202
- if (!fs.existsSync(dest.dir)) {
203
- console.error(`✗ ${item.name}: 未安装(${dest.dir} 不存在)`);
204
- failed++;
205
- continue;
206
- }
207
- const meta = readManifest(dest.dir);
208
- removeDirSafe(dest.dir);
209
- console.log(`✓ 已卸载 ${meta ? meta.name : item.name}(${dest.dir})`);
210
- ok++;
211
- }
212
- console.log(`\n完成:成功 ${ok},失败 ${failed}`);
213
- return failed === 0 ? 0 : 1;
214
- }
215
-
216
- async function cmdValidate(argv) {
217
- const { positionals } = parse(argv);
218
- const file = positionals[1];
219
- if (!file) {
220
- console.error('用法:dxkj validate <条目.json>');
221
- return 1;
222
- }
223
- const abs = path.resolve(process.cwd(), file);
224
- if (!fs.existsSync(abs)) {
225
- console.error(`文件不存在:${abs}`);
226
- return 1;
227
- }
228
- let data;
229
- try {
230
- data = JSON.parse(fs.readFileSync(abs, 'utf-8').replace(/^\uFEFF/, ''));
231
- } catch (err) {
232
- console.error(`JSON 解析失败:${err.message}`);
233
- return 1;
234
- }
235
- const list = Array.isArray(data) ? data : [data];
236
- let hasError = false;
237
- for (const it of list) {
238
- const report = formatReport(it.id || it.name || '(未命名)', validateItem(it));
239
- console.log(report.text);
240
- if (!report.ok) hasError = true;
241
- }
242
- return hasError ? 1 : 0;
243
- }
244
-
245
- async function cmdConfig(argv) {
246
- const { values, positionals } = parse(argv);
247
- const action = positionals[1] || 'get';
248
- const cfg = loadConfig();
249
-
250
- if (action === 'path') {
251
- console.log(CONFIG_PATH);
252
- return 0;
253
- }
254
- if (action === 'get') {
255
- const key = positionals[2];
256
- if (!key) {
257
- console.log(JSON.stringify(cfg, null, 2));
258
- return 0;
259
- }
260
- const val = getByPath(cfg, key);
261
- console.log(typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val));
262
- return 0;
263
- }
264
- if (action === 'set') {
265
- const key = positionals[2];
266
- let val = positionals.slice(3).join(' ');
267
- if (!key || val === '') {
268
- console.error('用法:dxkj config set <键> <值>');
269
- return 1;
270
- }
271
- // 数字/布尔简单转换
272
- if (val === 'true') val = true;
273
- else if (val === 'false') val = false;
274
- const base = loadConfig();
275
- setByPath(base, key, val);
276
- saveConfig(base);
277
- console.log(`✓ 已写入 ${key} = ${val}`);
278
- return 0;
279
- }
280
- console.error(`未知 config 操作:${action}`);
281
- return 1;
282
- }
283
-
284
- export async function run(argv) {
285
- const command = argv[0];
286
-
287
- if (!command || command === 'help' || argv.includes('--help') || argv.includes('-h')) {
288
- console.log(help());
289
- return 0;
290
- }
291
- if (command === 'version' || argv.includes('--version') || argv.includes('-v')) {
292
- console.log(VERSION);
293
- return 0;
294
- }
295
-
296
- switch (command) {
297
- case 'list':
298
- return cmdList(argv);
299
- case 'search':
300
- return cmdSearch(argv);
301
- case 'show':
302
- case 'info':
303
- return cmdShow(argv);
304
- case 'install':
305
- case 'i':
306
- return cmdInstall(argv);
307
- case 'uninstall':
308
- case 'remove':
309
- case 'rm':
310
- return cmdUninstall(argv);
311
- case 'validate':
312
- case 'lint':
313
- return cmdValidate(argv);
314
- case 'config':
315
- return cmdConfig(argv);
316
- default:
317
- console.error(`未知命令:${command}\n`);
318
- console.log(help());
319
- return 1;
320
- }
321
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parseArgs } from 'node:util';
4
+ import { loadConfig, saveConfig, resolveServerUrl, CONFIG_PATH, getByPath, setByPath } from './lib/config.js';
5
+ import { loadRegistry, filterItems, findByRef, resolveSource } from './lib/registry.js';
6
+ import { validateItem, formatReport } from './lib/validator.js';
7
+ import { renderList, renderDetail } from './lib/format.js';
8
+ import { resolveTargetDir } from './lib/targets.js';
9
+ import { downloadToTemp, extractZip, writeManifest, cleanup } from './lib/installer.js';
10
+ import { VERSION } from './lib/version.js';
11
+
12
+ const OPTIONS = {
13
+ source: { type: 'string' },
14
+ server: { type: 'string' },
15
+ kind: { type: 'string' },
16
+ category: { type: 'string' },
17
+ tag: { type: 'string' },
18
+ status: { type: 'string' },
19
+ target: { type: 'string' },
20
+ editor: { type: 'string' },
21
+ dir: { type: 'string' },
22
+ json: { type: 'boolean' },
23
+ all: { type: 'boolean' },
24
+ force: { type: 'boolean' },
25
+ help: { type: 'boolean', short: 'h' },
26
+ version: { type: 'boolean', short: 'v' },
27
+ yes: { type: 'boolean', short: 'y' }
28
+ };
29
+
30
+ function parse(argv) {
31
+ return parseArgs({ args: argv, options: OPTIONS, allowPositionals: true, strict: false });
32
+ }
33
+
34
+ function help() {
35
+ return `dxkj v${VERSION} — dxkj 目录 CLI(prompt / skill / agent / plugin / tool)
36
+
37
+ 用法:
38
+ dxkj list [--kind skill] [--category CG] [--tag x] [--source <url|file>] [--json]
39
+ dxkj search <关键词>
40
+ dxkj show <编号|id>
41
+ dxkj install <编号...> [--target ai-editor|navlink|generic] [--editor opencode|claude] [--dir 路径]
42
+ dxkj install --all [--kind skill]
43
+ dxkj uninstall <编号...>
44
+ dxkj validate <条目.json>
45
+ dxkj config get [键] | config set <键> <值> | config path
46
+ dxkj --help | --version
47
+
48
+ 默认服务地址:${resolveServerUrl()}
49
+ 配置文件:${CONFIG_PATH}
50
+ 站点未集成时可用 --source ./fixtures/sample-registry.json 本地验证。`;
51
+ }
52
+
53
+ async function cmdList(argv) {
54
+ const { values } = parse(argv);
55
+ const cfg = loadConfig();
56
+ const { items, source } = await loadRegistry(values, cfg);
57
+ const list = filterItems(items, values);
58
+ if (values.json) {
59
+ console.log(JSON.stringify({ source, count: list.length, items: list }, null, 2));
60
+ return 0;
61
+ }
62
+ console.log(`数据源: ${source}`);
63
+ console.log(renderList(list));
64
+ console.log(`\n共 ${list.length} 条`);
65
+ return 0;
66
+ }
67
+
68
+ async function cmdSearch(argv) {
69
+ const { values, positionals } = parse(argv);
70
+ const kw = positionals.slice(1).join(' ').toLowerCase();
71
+ if (!kw) {
72
+ console.error('用法:dxkj search <关键词>');
73
+ return 1;
74
+ }
75
+ const cfg = loadConfig();
76
+ const { items } = await loadRegistry(values, cfg);
77
+ const list = filterItems(items, values).filter((it) =>
78
+ [it.id, it.name, it.description, it.intro, it.category, ...it.tags].join(' ').toLowerCase().includes(kw)
79
+ );
80
+ console.log(renderList(list));
81
+ console.log(`\n命中 ${list.length} 条`);
82
+ return 0;
83
+ }
84
+
85
+ async function cmdShow(argv) {
86
+ const { values, positionals } = parse(argv);
87
+ const ref = positionals[1];
88
+ if (!ref) {
89
+ console.error('用法:dxkj show <编号|id>');
90
+ return 1;
91
+ }
92
+ const cfg = loadConfig();
93
+ const { items } = await loadRegistry(values, cfg);
94
+ const item = findByRef(items, ref);
95
+ if (!item) {
96
+ console.error(`未找到条目:${ref}`);
97
+ return 1;
98
+ }
99
+ if (values.json) {
100
+ console.log(JSON.stringify(item, null, 2));
101
+ return 0;
102
+ }
103
+ console.log(renderDetail(item, resolveServerUrl(values, cfg)));
104
+ return 0;
105
+ }
106
+
107
+ async function cmdInstall(argv) {
108
+ const { values, positionals } = parse(argv);
109
+ const cfg = loadConfig();
110
+ const { items } = await loadRegistry(values, cfg);
111
+
112
+ let targets = positionals.slice(1);
113
+ if (values.all) {
114
+ targets = filterItems(items, values).map((it) => String(it.number));
115
+ } else if (values.kind && targets.length === 0) {
116
+ targets = filterItems(items, values).map((it) => String(it.number));
117
+ }
118
+ if (targets.length === 0) {
119
+ console.error('用法:dxkj install <编号...> | dxkj install --all [--kind skill]');
120
+ return 1;
121
+ }
122
+
123
+ // navlink 目标可直接复用插件安装包(先支持 zip 解压方式)
124
+ let ok = 0;
125
+ let failed = 0;
126
+ for (const ref of targets) {
127
+ const item = findByRef(items, ref);
128
+ if (!item) {
129
+ console.error(`✗ 未找到条目:${ref}`);
130
+ failed++;
131
+ continue;
132
+ }
133
+ if (!item.install.url) {
134
+ console.error(`✗ #${item.number} ${item.name} 缺少安装包地址,跳过`);
135
+ failed++;
136
+ continue;
137
+ }
138
+ let dest;
139
+ try {
140
+ dest = resolveTargetDir(item, values, cfg);
141
+ } catch (err) {
142
+ console.error(`✗ #${item.number} ${item.name}: ${err.message}`);
143
+ failed++;
144
+ continue;
145
+ }
146
+ let temp = null;
147
+ try {
148
+ temp = await downloadToTemp(item.install.url);
149
+ extractZip(temp, dest.dir);
150
+ writeManifest(dest.dir, {
151
+ id: item.id,
152
+ number: item.number,
153
+ name: item.name,
154
+ kind: item.kind,
155
+ version: item.version,
156
+ source: item.install.url,
157
+ target: dest.target,
158
+ installedAt: new Date().toISOString()
159
+ });
160
+ console.log(`✓ 已安装 #${item.number} ${item.name} → ${dest.dir} (${dest.hint})`);
161
+ ok++;
162
+ } catch (err) {
163
+ console.error(`✗ #${item.number} ${item.name}: ${err.message}`);
164
+ failed++;
165
+ } finally {
166
+ if (temp) cleanup(temp);
167
+ }
168
+ }
169
+ console.log(`\n完成:成功 ${ok},失败 ${failed}`);
170
+ return failed === 0 ? 0 : 1;
171
+ }
172
+
173
+ async function cmdUninstall(argv) {
174
+ const { values, positionals } = parse(argv);
175
+ const cfg = loadConfig();
176
+ const { items } = await loadRegistry(values, cfg);
177
+ const { removeDirSafe, readManifest } = await import('./lib/installer.js');
178
+
179
+ const refs = positionals.slice(1);
180
+ if (refs.length === 0) {
181
+ console.error('用法:dxkj uninstall <编号...>');
182
+ return 1;
183
+ }
184
+ let ok = 0;
185
+ let failed = 0;
186
+ for (const ref of refs) {
187
+ const item = findByRef(items, ref);
188
+ if (!item) {
189
+ console.error(`✗ 未找到条目:${ref}`);
190
+ failed++;
191
+ continue;
192
+ }
193
+ let dest;
194
+ try {
195
+ dest = resolveTargetDir(item, values, cfg);
196
+ } catch (err) {
197
+ console.error(`✗ ${item.name}: ${err.message}`);
198
+ failed++;
199
+ continue;
200
+ }
201
+ if (!fs.existsSync(dest.dir)) {
202
+ console.error(`✗ ${item.name}: 未安装(${dest.dir} 不存在)`);
203
+ failed++;
204
+ continue;
205
+ }
206
+ const meta = readManifest(dest.dir);
207
+ removeDirSafe(dest.dir);
208
+ console.log(`✓ 已卸载 ${meta ? meta.name : item.name}(${dest.dir})`);
209
+ ok++;
210
+ }
211
+ console.log(`\n完成:成功 ${ok},失败 ${failed}`);
212
+ return failed === 0 ? 0 : 1;
213
+ }
214
+
215
+ async function cmdValidate(argv) {
216
+ const { positionals } = parse(argv);
217
+ const file = positionals[1];
218
+ if (!file) {
219
+ console.error('用法:dxkj validate <条目.json>');
220
+ return 1;
221
+ }
222
+ const abs = path.resolve(process.cwd(), file);
223
+ if (!fs.existsSync(abs)) {
224
+ console.error(`文件不存在:${abs}`);
225
+ return 1;
226
+ }
227
+ let data;
228
+ try {
229
+ data = JSON.parse(fs.readFileSync(abs, 'utf-8').replace(/^\uFEFF/, ''));
230
+ } catch (err) {
231
+ console.error(`JSON 解析失败:${err.message}`);
232
+ return 1;
233
+ }
234
+ const list = Array.isArray(data) ? data : [data];
235
+ let hasError = false;
236
+ for (const it of list) {
237
+ const report = formatReport(it.id || it.name || '(未命名)', validateItem(it));
238
+ console.log(report.text);
239
+ if (!report.ok) hasError = true;
240
+ }
241
+ return hasError ? 1 : 0;
242
+ }
243
+
244
+ async function cmdConfig(argv) {
245
+ const { values, positionals } = parse(argv);
246
+ const action = positionals[1] || 'get';
247
+ const cfg = loadConfig();
248
+
249
+ if (action === 'path') {
250
+ console.log(CONFIG_PATH);
251
+ return 0;
252
+ }
253
+ if (action === 'get') {
254
+ const key = positionals[2];
255
+ if (!key) {
256
+ console.log(JSON.stringify(cfg, null, 2));
257
+ return 0;
258
+ }
259
+ const val = getByPath(cfg, key);
260
+ console.log(typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val));
261
+ return 0;
262
+ }
263
+ if (action === 'set') {
264
+ const key = positionals[2];
265
+ let val = positionals.slice(3).join(' ');
266
+ if (!key || val === '') {
267
+ console.error('用法:dxkj config set <键> <值>');
268
+ return 1;
269
+ }
270
+ // 数字/布尔简单转换
271
+ if (val === 'true') val = true;
272
+ else if (val === 'false') val = false;
273
+ const base = loadConfig();
274
+ setByPath(base, key, val);
275
+ saveConfig(base);
276
+ console.log(`✓ 已写入 ${key} = ${val}`);
277
+ return 0;
278
+ }
279
+ console.error(`未知 config 操作:${action}`);
280
+ return 1;
281
+ }
282
+
283
+ export async function run(argv) {
284
+ const command = argv[0];
285
+
286
+ if (!command || command === 'help' || argv.includes('--help') || argv.includes('-h')) {
287
+ console.log(help());
288
+ return 0;
289
+ }
290
+ if (command === 'version' || argv.includes('--version') || argv.includes('-v')) {
291
+ console.log(VERSION);
292
+ return 0;
293
+ }
294
+
295
+ switch (command) {
296
+ case 'list':
297
+ return cmdList(argv);
298
+ case 'search':
299
+ return cmdSearch(argv);
300
+ case 'show':
301
+ case 'info':
302
+ return cmdShow(argv);
303
+ case 'install':
304
+ case 'i':
305
+ return cmdInstall(argv);
306
+ case 'uninstall':
307
+ case 'remove':
308
+ case 'rm':
309
+ return cmdUninstall(argv);
310
+ case 'validate':
311
+ case 'lint':
312
+ return cmdValidate(argv);
313
+ case 'config':
314
+ return cmdConfig(argv);
315
+ default:
316
+ console.error(`未知命令:${command}\n`);
317
+ console.log(help());
318
+ return 1;
319
+ }
320
+ }