superpowers-zh 1.1.9 → 1.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/bin/superpowers-zh.js +238 -16
- package/package.json +1 -1
package/bin/superpowers-zh.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync, lstatSync, realpathSync } from 'fs';
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, copyFileSync, lstatSync, realpathSync, rmSync } from 'fs';
|
|
4
4
|
import { resolve, dirname, join } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import { homedir } from 'os';
|
|
6
7
|
|
|
7
8
|
// 手动递归复制:跨 Node 版本和操作系统行为一致
|
|
8
9
|
// 不使用 cpSync —— 在 Windows + npx 缓存(含 junction)+ Node 16.7-18 下不稳定
|
|
@@ -14,6 +15,7 @@ function copyDirSync(src, dest) {
|
|
|
14
15
|
mkdirSync(dest, { recursive: true });
|
|
15
16
|
const entries = readdirSync(realSrc, { withFileTypes: true });
|
|
16
17
|
for (const entry of entries) {
|
|
18
|
+
if (entry.name === '.DS_Store') continue;
|
|
17
19
|
const srcPath = join(realSrc, entry.name);
|
|
18
20
|
const destPath = join(dest, entry.name);
|
|
19
21
|
let stat;
|
|
@@ -86,6 +88,15 @@ function scanSkillEntries(skillsDir) {
|
|
|
86
88
|
return entries;
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
// 段落哨兵:v1.2.1+ 安装时把追加内容包在两条 HTML 注释之间,
|
|
92
|
+
// 让卸载可以精确切除,无需依赖标题层级猜测段尾。
|
|
93
|
+
const SENTINEL_BEGIN = '<!-- superpowers-zh:begin (do not edit between these markers) -->';
|
|
94
|
+
const SENTINEL_END = '<!-- superpowers-zh:end -->';
|
|
95
|
+
|
|
96
|
+
function wrapWithSentinel(body) {
|
|
97
|
+
return `${SENTINEL_BEGIN}\n${body.replace(/\n+$/, '')}\n${SENTINEL_END}\n`;
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
function generateTraeBootstrapRule(projectDir) {
|
|
90
101
|
const rulesDir = resolve(projectDir, '.trae', 'rules');
|
|
91
102
|
mkdirSync(rulesDir, { recursive: true });
|
|
@@ -190,13 +201,13 @@ ${skillList}
|
|
|
190
201
|
if (existsSync(convPath)) {
|
|
191
202
|
const existing = readFileSync(convPath, 'utf8');
|
|
192
203
|
if (!existing.includes('superpowers-zh')) {
|
|
193
|
-
writeFileSync(convPath, existing + '\n\n' + content, 'utf8');
|
|
204
|
+
writeFileSync(convPath, existing.replace(/\s+$/, '') + '\n\n' + wrapWithSentinel(content), 'utf8');
|
|
194
205
|
console.log(` ✅ Aider: 追加 skills 引用 -> ${convPath}`);
|
|
195
206
|
} else {
|
|
196
207
|
console.log(` ✅ Aider: CONVENTIONS.md 已包含 superpowers-zh 引用`);
|
|
197
208
|
}
|
|
198
209
|
} else {
|
|
199
|
-
writeFileSync(convPath, content, 'utf8');
|
|
210
|
+
writeFileSync(convPath, wrapWithSentinel(content), 'utf8');
|
|
200
211
|
console.log(` ✅ Aider: bootstrap -> ${convPath}`);
|
|
201
212
|
}
|
|
202
213
|
}
|
|
@@ -232,13 +243,13 @@ ${skillList}
|
|
|
232
243
|
if (existsSync(geminiPath)) {
|
|
233
244
|
const existing = readFileSync(geminiPath, 'utf8');
|
|
234
245
|
if (!existing.includes('superpowers-zh')) {
|
|
235
|
-
writeFileSync(geminiPath, existing + '\n\n' + content, 'utf8');
|
|
246
|
+
writeFileSync(geminiPath, existing.replace(/\s+$/, '') + '\n\n' + wrapWithSentinel(content), 'utf8');
|
|
236
247
|
console.log(` ✅ Gemini CLI: 追加 skills 引用 -> ${geminiPath}`);
|
|
237
248
|
} else {
|
|
238
249
|
console.log(` ✅ Gemini CLI: GEMINI.md 已包含 superpowers-zh 引用`);
|
|
239
250
|
}
|
|
240
251
|
} else {
|
|
241
|
-
writeFileSync(geminiPath, content, 'utf8');
|
|
252
|
+
writeFileSync(geminiPath, wrapWithSentinel(content), 'utf8');
|
|
242
253
|
console.log(` ✅ Gemini CLI: bootstrap -> ${geminiPath}`);
|
|
243
254
|
}
|
|
244
255
|
}
|
|
@@ -288,13 +299,13 @@ ${skillList}
|
|
|
288
299
|
if (existsSync(hermesPath)) {
|
|
289
300
|
const existing = readFileSync(hermesPath, 'utf8');
|
|
290
301
|
if (!existing.includes('superpowers-zh')) {
|
|
291
|
-
writeFileSync(hermesPath, existing + '\n\n' + content, 'utf8');
|
|
302
|
+
writeFileSync(hermesPath, existing.replace(/\s+$/, '') + '\n\n' + wrapWithSentinel(content), 'utf8');
|
|
292
303
|
console.log(` ✅ Hermes Agent: 追加 skills 引用 -> ${hermesPath}`);
|
|
293
304
|
} else {
|
|
294
305
|
console.log(` ✅ Hermes Agent: HERMES.md 已包含 superpowers-zh 引用`);
|
|
295
306
|
}
|
|
296
307
|
} else {
|
|
297
|
-
writeFileSync(hermesPath, content, 'utf8');
|
|
308
|
+
writeFileSync(hermesPath, wrapWithSentinel(content), 'utf8');
|
|
298
309
|
console.log(` ✅ Hermes Agent: bootstrap -> ${hermesPath}`);
|
|
299
310
|
}
|
|
300
311
|
}
|
|
@@ -331,13 +342,13 @@ ${skillList}
|
|
|
331
342
|
if (existsSync(mdPath)) {
|
|
332
343
|
const existing = readFileSync(mdPath, 'utf8');
|
|
333
344
|
if (!existing.includes('superpowers-zh')) {
|
|
334
|
-
writeFileSync(mdPath, existing + '\n\n' + content, 'utf8');
|
|
345
|
+
writeFileSync(mdPath, existing.replace(/\s+$/, '') + '\n\n' + wrapWithSentinel(content), 'utf8');
|
|
335
346
|
console.log(` ✅ Claude Code: 追加 skills 引用 -> ${mdPath}`);
|
|
336
347
|
} else {
|
|
337
348
|
console.log(` ✅ Claude Code: CLAUDE.md 已包含 superpowers-zh 引用`);
|
|
338
349
|
}
|
|
339
350
|
} else {
|
|
340
|
-
writeFileSync(mdPath, content, 'utf8');
|
|
351
|
+
writeFileSync(mdPath, wrapWithSentinel(content), 'utf8');
|
|
341
352
|
console.log(` ✅ Claude Code: bootstrap -> ${mdPath}`);
|
|
342
353
|
}
|
|
343
354
|
}
|
|
@@ -380,6 +391,8 @@ function showHelp() {
|
|
|
380
391
|
用法:
|
|
381
392
|
npx superpowers-zh 自动检测工具并安装
|
|
382
393
|
npx superpowers-zh --tool cursor 指定工具安装(检测不到时使用)
|
|
394
|
+
npx superpowers-zh --uninstall 卸载当前目录下的 superpowers-zh
|
|
395
|
+
npx superpowers-zh --force 允许在用户主目录(~)安装(默认拒绝)
|
|
383
396
|
npx superpowers-zh --help 显示帮助
|
|
384
397
|
npx superpowers-zh --version 显示版本
|
|
385
398
|
|
|
@@ -392,6 +405,9 @@ function showHelp() {
|
|
|
392
405
|
npx superpowers-zh --tool cursor
|
|
393
406
|
npx superpowers-zh --tool trae
|
|
394
407
|
|
|
408
|
+
误装到主目录可以这样清理:
|
|
409
|
+
cd ~ && npx superpowers-zh --uninstall
|
|
410
|
+
|
|
395
411
|
项目:https://github.com/jnMetaCode/superpowers-zh
|
|
396
412
|
`);
|
|
397
413
|
}
|
|
@@ -401,8 +417,8 @@ function installForTarget(target) {
|
|
|
401
417
|
const srcCount = countDirs(SKILLS_SRC);
|
|
402
418
|
mkdirSync(dest, { recursive: true });
|
|
403
419
|
copyDirSync(SKILLS_SRC, dest);
|
|
404
|
-
const
|
|
405
|
-
if (srcCount > 0 &&
|
|
420
|
+
const totalAfter = countDirs(dest);
|
|
421
|
+
if (srcCount > 0 && totalAfter === 0) {
|
|
406
422
|
throw new Error(
|
|
407
423
|
`复制 skills 失败:源目录 ${SKILLS_SRC} 有 ${srcCount} 个 skill,但目标 ${dest} 为空。` +
|
|
408
424
|
`\n 这通常是 npx 缓存目录权限或路径问题。请尝试:\n` +
|
|
@@ -411,7 +427,7 @@ function installForTarget(target) {
|
|
|
411
427
|
` 3. 或手动克隆复制: 见 https://github.com/jnMetaCode/superpowers-zh#方式二手动安装`
|
|
412
428
|
);
|
|
413
429
|
}
|
|
414
|
-
console.log(` ✅ ${target.name}: ${
|
|
430
|
+
console.log(` ✅ ${target.name}: ${srcCount} 个 skills -> ${dest}`);
|
|
415
431
|
|
|
416
432
|
if (target.name === 'Trae') {
|
|
417
433
|
generateTraeBootstrapRule(PROJECT_DIR);
|
|
@@ -443,7 +459,187 @@ function installForTarget(target) {
|
|
|
443
459
|
}
|
|
444
460
|
}
|
|
445
461
|
|
|
446
|
-
function
|
|
462
|
+
function isHomeDir(p) {
|
|
463
|
+
const home = homedir();
|
|
464
|
+
if (!home) return false;
|
|
465
|
+
try {
|
|
466
|
+
return realpathSync(p) === realpathSync(home);
|
|
467
|
+
} catch { return resolve(p) === resolve(home); }
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// 卸载支持:完整删除的 bootstrap 文件、需要清理段落的 bootstrap 文件
|
|
471
|
+
const BOOTSTRAP_DELETE = [
|
|
472
|
+
'.trae/rules/superpowers-zh.md',
|
|
473
|
+
'.antigravity/rules.md',
|
|
474
|
+
];
|
|
475
|
+
const BOOTSTRAP_CLEAN_SECTION = [
|
|
476
|
+
'CLAUDE.md',
|
|
477
|
+
'GEMINI.md',
|
|
478
|
+
'HERMES.md',
|
|
479
|
+
'CONVENTIONS.md',
|
|
480
|
+
];
|
|
481
|
+
const BOOTSTRAP_SECTION_MARKERS = [
|
|
482
|
+
'# Superpowers-ZH 中文增强版',
|
|
483
|
+
'# Superpowers-ZH 工作方法论',
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
// v1.1.x 安装的旧 bootstrap 没有 sentinel,只能凭模板末尾固定句子识别段尾。
|
|
487
|
+
// 这些短语必须出现在 superpowers 段最后一行,且足够独特不易在用户内容里重合。
|
|
488
|
+
const FALLBACK_TAIL_HINTS = [
|
|
489
|
+
'你必须调用该 skill 检查。',
|
|
490
|
+
'严格遵循其流程。',
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
function writeOrDelete(filePath, head, tail) {
|
|
494
|
+
const headTrim = head.replace(/\s+$/, '');
|
|
495
|
+
const tailTrim = tail.replace(/^\s+/, '');
|
|
496
|
+
let body = headTrim;
|
|
497
|
+
if (headTrim && tailTrim) body += '\n\n' + tailTrim;
|
|
498
|
+
else body += tailTrim;
|
|
499
|
+
body = body.replace(/\s+$/, '');
|
|
500
|
+
if (body.length === 0) {
|
|
501
|
+
rmSync(filePath);
|
|
502
|
+
} else {
|
|
503
|
+
writeFileSync(filePath, body + '\n', 'utf8');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function cleanBootstrapSection(filePath) {
|
|
508
|
+
if (!existsSync(filePath)) return false;
|
|
509
|
+
const content = readFileSync(filePath, 'utf8');
|
|
510
|
+
|
|
511
|
+
// 1. 哨兵模式(v1.2.1+)— 精确切除
|
|
512
|
+
const sBegin = content.indexOf(SENTINEL_BEGIN);
|
|
513
|
+
if (sBegin !== -1) {
|
|
514
|
+
const sEnd = content.indexOf(SENTINEL_END, sBegin + SENTINEL_BEGIN.length);
|
|
515
|
+
if (sEnd !== -1) {
|
|
516
|
+
writeOrDelete(filePath, content.slice(0, sBegin), content.slice(sEnd + SENTINEL_END.length));
|
|
517
|
+
return true;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// 2. 标题 marker(v1.1.x 安装的)— 找下一个 \n# 一级标题做段尾
|
|
522
|
+
let idx = -1;
|
|
523
|
+
for (const marker of BOOTSTRAP_SECTION_MARKERS) {
|
|
524
|
+
const i = content.indexOf(marker);
|
|
525
|
+
if (i !== -1 && (idx === -1 || i < idx)) idx = i;
|
|
526
|
+
}
|
|
527
|
+
if (idx === -1) return false;
|
|
528
|
+
|
|
529
|
+
let end = -1;
|
|
530
|
+
const nextHeading = content.indexOf('\n# ', idx + 1);
|
|
531
|
+
if (nextHeading !== -1) end = nextHeading + 1;
|
|
532
|
+
|
|
533
|
+
// 3. 一级标题找不到 — 用末尾固定短语做兜底
|
|
534
|
+
if (end === -1) {
|
|
535
|
+
for (const hint of FALLBACK_TAIL_HINTS) {
|
|
536
|
+
const i = content.lastIndexOf(hint);
|
|
537
|
+
if (i > idx) {
|
|
538
|
+
const nl = content.indexOf('\n', i + hint.length);
|
|
539
|
+
const after = nl !== -1 ? nl + 1 : content.length;
|
|
540
|
+
if (after > end) end = after;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// 4. 都找不到 — 数据安全,跳过 + 警告
|
|
546
|
+
if (end === -1) {
|
|
547
|
+
console.warn(` ⚠️ ${filePath}: 无法可靠识别 superpowers-zh 段尾,已跳过以避免数据丢失。`);
|
|
548
|
+
console.warn(` 请手动编辑此文件并删除以 "${BOOTSTRAP_SECTION_MARKERS[0]}" 开头的整段。`);
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
writeOrDelete(filePath, content.slice(0, idx), content.slice(end));
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function uninstallForTarget(target, srcSkillNames) {
|
|
557
|
+
const dest = resolve(PROJECT_DIR, target.dir);
|
|
558
|
+
if (!existsSync(dest)) return 0;
|
|
559
|
+
let removed = 0;
|
|
560
|
+
for (const entry of readdirSync(dest, { withFileTypes: true })) {
|
|
561
|
+
if (entry.isDirectory() && srcSkillNames.has(entry.name)) {
|
|
562
|
+
rmSync(resolve(dest, entry.name), { recursive: true, force: true });
|
|
563
|
+
removed++;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// 如果目录已空(或仅剩 .DS_Store),顺手清掉,避免留下空骨架
|
|
567
|
+
try {
|
|
568
|
+
if (existsSync(dest)) {
|
|
569
|
+
const left = readdirSync(dest).filter(n => n !== '.DS_Store');
|
|
570
|
+
if (left.length === 0) rmSync(dest, { recursive: true, force: true });
|
|
571
|
+
}
|
|
572
|
+
} catch {}
|
|
573
|
+
return removed;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function uninstall() {
|
|
577
|
+
console.log(`\n superpowers-zh v${PKG.version} — 卸载\n`);
|
|
578
|
+
console.log(` 目标项目: ${PROJECT_DIR}\n`);
|
|
579
|
+
|
|
580
|
+
if (!existsSync(SKILLS_SRC)) {
|
|
581
|
+
console.error(' ❌ 错误:skills 源目录不存在,无法识别要卸载的 skill 名单。');
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const srcSkillNames = new Set(
|
|
586
|
+
readdirSync(SKILLS_SRC, { withFileTypes: true })
|
|
587
|
+
.filter(e => e.isDirectory())
|
|
588
|
+
.map(e => e.name)
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
let totalSkills = 0;
|
|
592
|
+
for (const target of TARGETS) {
|
|
593
|
+
const removed = uninstallForTarget(target, srcSkillNames);
|
|
594
|
+
if (removed > 0) {
|
|
595
|
+
console.log(` ✅ ${target.name}: 移除 ${removed} 个 skills <- ${resolve(PROJECT_DIR, target.dir)}`);
|
|
596
|
+
totalSkills += removed;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// 顺带清理 .claude/agents 下我们装过的 agent(agents 源是平铺的文件,例如 code-reviewer.md)
|
|
601
|
+
const agentsDest = resolve(PROJECT_DIR, '.claude', 'agents');
|
|
602
|
+
if (existsSync(AGENTS_SRC) && existsSync(agentsDest)) {
|
|
603
|
+
const srcAgentNames = new Set(readdirSync(AGENTS_SRC).filter(n => n !== '.DS_Store'));
|
|
604
|
+
let agentsRemoved = 0;
|
|
605
|
+
for (const entry of readdirSync(agentsDest)) {
|
|
606
|
+
if (srcAgentNames.has(entry)) {
|
|
607
|
+
rmSync(resolve(agentsDest, entry), { recursive: true, force: true });
|
|
608
|
+
agentsRemoved++;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (agentsRemoved > 0) console.log(` ✅ Claude Code agents: 移除 ${agentsRemoved} 个 -> ${agentsDest}`);
|
|
612
|
+
try {
|
|
613
|
+
const left = readdirSync(agentsDest).filter(n => n !== '.DS_Store');
|
|
614
|
+
if (left.length === 0) rmSync(agentsDest, { recursive: true, force: true });
|
|
615
|
+
} catch {}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
let bootstrapsRemoved = 0;
|
|
619
|
+
for (const rel of BOOTSTRAP_DELETE) {
|
|
620
|
+
const full = resolve(PROJECT_DIR, rel);
|
|
621
|
+
if (existsSync(full)) {
|
|
622
|
+
rmSync(full);
|
|
623
|
+
console.log(` ✅ 删除 bootstrap: ${full}`);
|
|
624
|
+
bootstrapsRemoved++;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
for (const rel of BOOTSTRAP_CLEAN_SECTION) {
|
|
628
|
+
const full = resolve(PROJECT_DIR, rel);
|
|
629
|
+
if (cleanBootstrapSection(full)) {
|
|
630
|
+
console.log(` ✅ 清理 bootstrap: ${full}`);
|
|
631
|
+
bootstrapsRemoved++;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (totalSkills === 0 && bootstrapsRemoved === 0) {
|
|
636
|
+
console.log(' ⚠️ 未在当前目录找到 superpowers-zh 安装痕迹。');
|
|
637
|
+
} else {
|
|
638
|
+
console.log(`\n 卸载完成。共移除 ${totalSkills} 个 skill 目录、${bootstrapsRemoved} 个 bootstrap 文件。\n`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function install(forceToolName, force) {
|
|
447
643
|
try {
|
|
448
644
|
console.log(`\n superpowers-zh v${PKG.version} — AI 编程超能力中文版\n`);
|
|
449
645
|
|
|
@@ -452,6 +648,27 @@ function install(forceToolName) {
|
|
|
452
648
|
process.exit(1);
|
|
453
649
|
}
|
|
454
650
|
|
|
651
|
+
if (!force && isHomeDir(PROJECT_DIR)) {
|
|
652
|
+
console.error(
|
|
653
|
+
` ⚠️ 当前目录是用户主目录: ${PROJECT_DIR}
|
|
654
|
+
|
|
655
|
+
superpowers-zh 应该装到具体项目目录,而不是 ~/。
|
|
656
|
+
在主目录安装会把 skills 和 bootstrap 文件(CLAUDE.md / HERMES.md 等)
|
|
657
|
+
写入你的 home,污染所有项目。
|
|
658
|
+
|
|
659
|
+
请先 cd 到项目目录:
|
|
660
|
+
cd /path/to/your/project
|
|
661
|
+
npx superpowers-zh
|
|
662
|
+
|
|
663
|
+
如果你确实要在主目录安装(不推荐),加 --force:
|
|
664
|
+
npx superpowers-zh --force
|
|
665
|
+
|
|
666
|
+
如果你已经在主目录误装过,可以用 --uninstall 清理:
|
|
667
|
+
npx superpowers-zh --uninstall
|
|
668
|
+
`);
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
|
|
455
672
|
console.log(` 源: ${countDirs(SKILLS_SRC)} 个 skills`);
|
|
456
673
|
console.log(` 目标项目: ${PROJECT_DIR}\n`);
|
|
457
674
|
|
|
@@ -512,11 +729,16 @@ const args = process.argv.slice(2);
|
|
|
512
729
|
const helpIdx = args.findIndex(a => a === '--help' || a === '-h');
|
|
513
730
|
const versionIdx = args.findIndex(a => a === '--version' || a === '-v');
|
|
514
731
|
const toolIdx = args.findIndex(a => a === '--tool' || a === '-t');
|
|
732
|
+
const uninstallIdx = args.findIndex(a => a === '--uninstall' || a === '-u');
|
|
733
|
+
const forceIdx = args.findIndex(a => a === '--force' || a === '-f');
|
|
734
|
+
const force = forceIdx !== -1;
|
|
515
735
|
|
|
516
736
|
if (helpIdx !== -1) {
|
|
517
737
|
showHelp();
|
|
518
738
|
} else if (versionIdx !== -1) {
|
|
519
739
|
console.log(PKG.version);
|
|
740
|
+
} else if (uninstallIdx !== -1) {
|
|
741
|
+
uninstall();
|
|
520
742
|
} else if (toolIdx !== -1) {
|
|
521
743
|
const toolArg = args[toolIdx + 1];
|
|
522
744
|
if (!toolArg) {
|
|
@@ -530,11 +752,11 @@ if (helpIdx !== -1) {
|
|
|
530
752
|
console.error(` 支持的工具: ${Object.keys(TOOL_ALIASES).join(', ')}\n`);
|
|
531
753
|
process.exit(1);
|
|
532
754
|
}
|
|
533
|
-
install(toolName);
|
|
534
|
-
} else if (args.length > 0 && args[0].startsWith('-')) {
|
|
755
|
+
install(toolName, force);
|
|
756
|
+
} else if (args.length > 0 && args[0].startsWith('-') && forceIdx === -1) {
|
|
535
757
|
console.warn(` 未知参数: ${args[0]}\n`);
|
|
536
758
|
showHelp();
|
|
537
759
|
process.exit(1);
|
|
538
760
|
} else {
|
|
539
|
-
install();
|
|
761
|
+
install(undefined, force);
|
|
540
762
|
}
|