ethan-skill 1.14.0 → 1.15.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 +47 -0
- package/README.md +31 -8
- package/dist/agents/orchestrator.test.js +5 -6
- package/dist/agents/orchestrator.test.js.map +1 -1
- package/dist/cli/index.js +398 -34
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/update-check.d.ts +8 -1
- package/dist/cli/update-check.d.ts.map +1 -1
- package/dist/cli/update-check.js +86 -11
- package/dist/cli/update-check.js.map +1 -1
- package/dist/extension/index.d.ts +54 -0
- package/dist/extension/index.d.ts.map +1 -0
- package/dist/extension/index.js +198 -0
- package/dist/extension/index.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +61 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/memory/index.d.ts +42 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +320 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/memory/types.d.ts +47 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +7 -0
- package/dist/memory/types.js.map +1 -0
- package/package.json +1 -1
- package/rules/claude-code/CLAUDE.md +2 -2
- package/rules/cline/.clinerules +2 -2
- package/rules/codebuddy/CODEBUDDY.md +2 -2
- package/rules/continue/.continuerules +2 -2
- package/rules/copilot/copilot-instructions.md +2 -2
- package/rules/cursor/.cursorrules +2 -2
- package/rules/cursor/smart-flow.mdc +2 -2
- package/rules/jetbrains/smart-flow.md +2 -2
- package/rules/lingma/smart-flow.md +2 -2
- package/rules/windsurf/.windsurf/rules/smart-flow.md +2 -2
- package/rules/zed/smart-flow.rules +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -628,11 +628,11 @@ program
|
|
|
628
628
|
const platformDirMap = {
|
|
629
629
|
'claude-code': path.join(dir, '.claude/commands'),
|
|
630
630
|
codebuddy: path.join(dir, '.codebuddy/commands'),
|
|
631
|
-
cursor: path.join(dir, '.cursor/
|
|
631
|
+
cursor: path.join(dir, '.cursor/commands'), // v0.50+ 支持独立命令文件
|
|
632
|
+
cline: path.join(dir, '.cline/commands'), // 支持独立命令文件
|
|
633
|
+
windsurf: path.join(dir, '.windsurf/commands'), // Wave 6+ 支持独立命令文件
|
|
632
634
|
copilot: path.join(dir, '.github'),
|
|
633
|
-
|
|
634
|
-
windsurf: path.join(dir, '.windsurf/rules'),
|
|
635
|
-
zed: dir,
|
|
635
|
+
zed: path.join(dir, '.zed/prompts'), // /prompt 机制,独立 .md 文件
|
|
636
636
|
jetbrains: path.join(dir, '.github'),
|
|
637
637
|
continue: dir,
|
|
638
638
|
lingma: path.join(dir, '.lingma/rules'),
|
|
@@ -642,9 +642,11 @@ program
|
|
|
642
642
|
const outDir = platformDirMap[p];
|
|
643
643
|
if (!fs.existsSync(outDir))
|
|
644
644
|
fs.mkdirSync(outDir, { recursive: true });
|
|
645
|
-
|
|
645
|
+
// 支持独立命令文件的平台:claude-code / codebuddy / cursor / cline / windsurf
|
|
646
|
+
const INDIVIDUAL_FILE_PLATFORMS = ['claude-code', 'codebuddy', 'cursor', 'cline', 'windsurf'];
|
|
647
|
+
if (INDIVIDUAL_FILE_PLATFORMS.includes(p)) {
|
|
646
648
|
const isClaudeCode = p === 'claude-code';
|
|
647
|
-
const cmdDir =
|
|
649
|
+
const cmdDir = path.relative(dir, outDir); // 用于日志展示
|
|
648
650
|
// ── 每个 Skill 生成独立 .md slash 命令文件 ──────────────────────────────
|
|
649
651
|
for (const skill of skills) {
|
|
650
652
|
const stepsText = skill.steps
|
|
@@ -752,9 +754,35 @@ program
|
|
|
752
754
|
console.log(` ✅ claude-code → 工作流: ${cmdDir}/ethan-{cmd}.md × ${WORKFLOW_SLASH_COMMANDS.length}`);
|
|
753
755
|
console.log(` 使用方式:在 Claude Code 聊天中输入 /ethan-commit、/ethan-auto 等`);
|
|
754
756
|
console.log(` ⚡ 动态注入:提示词自动注入对话,无需复制粘贴`);
|
|
757
|
+
// ── Agent 命令(Claude Code:每个 Agent 一个动态 .md 文件)─────────────
|
|
758
|
+
const { getActiveAgents } = await Promise.resolve().then(() => __importStar(require('../agents/index')));
|
|
759
|
+
const agents = getActiveAgents(dir);
|
|
760
|
+
// ethan-agent-list.md
|
|
761
|
+
const agentListContent = `---\ndescription: "Ethan — 查看所有可用 Agent 及其 Skill 分配"\n---\n\n` +
|
|
762
|
+
`$(ethan agent list 2>/dev/null)\n`;
|
|
763
|
+
fs.writeFileSync(path.join(outDir, 'ethan-agent-list.md'), agentListContent, 'utf-8');
|
|
764
|
+
// ethan-agent-run.md
|
|
765
|
+
const agentRunContent = `---\ndescription: "Ethan — Multi-Agent 编排:将任务分配给多个专业 Agent 协作执行"\n---\n\n` +
|
|
766
|
+
`$(![ -n "$ARGUMENTS" ] && ethan agent run $ARGUMENTS --no-copy 2>/dev/null || printf "## Ethan Multi-Agent 编排\\n\\n用法: /ethan-agent-run <pipeline> -c \\"任务描述\\"\\n\\n示例:\\n /ethan-agent-run dev-workflow -c \\"实现用户登录功能\\"\\n\\n可用模式: sequential / parallel / review-loop / consensus")\n`;
|
|
767
|
+
fs.writeFileSync(path.join(outDir, 'ethan-agent-run.md'), agentRunContent, 'utf-8');
|
|
768
|
+
// ethan-agent-new.md
|
|
769
|
+
const agentNewContent = `---\ndescription: "Ethan — 创建自定义 Agent,生成 .ethan/agents/<id>.yaml"\n---\n\n` +
|
|
770
|
+
`$(ethan agent new $ARGUMENTS 2>/dev/null)\n`;
|
|
771
|
+
fs.writeFileSync(path.join(outDir, 'ethan-agent-new.md'), agentNewContent, 'utf-8');
|
|
772
|
+
// 每个内置 Agent 生成独立快捷命令(动态)
|
|
773
|
+
let agentFileCount = 3;
|
|
774
|
+
for (const agent of agents) {
|
|
775
|
+
const agentSkillList = agent.skillIds.slice(0, 8).join('、') + (agent.skillIds.length > 8 ? '...' : '');
|
|
776
|
+
const agentContent = `---\ndescription: "Ethan — ${agent.emoji} ${agent.name}:${agent.role}"\n---\n\n` +
|
|
777
|
+
`$(![ -n "$ARGUMENTS" ] && ethan agent run --no-copy -c "$ARGUMENTS" 2>/dev/null || printf "## ${agent.emoji} ${agent.name}\\n\\n**职责**: ${agent.role}\\n\\n**负责 Skill**: ${agentSkillList}\\n\\n用法: /ethan-agent-${agent.id} <任务描述>")\n`;
|
|
778
|
+
fs.writeFileSync(path.join(outDir, `ethan-agent-${agent.id}.md`), agentContent, 'utf-8');
|
|
779
|
+
agentFileCount++;
|
|
780
|
+
total++;
|
|
781
|
+
}
|
|
782
|
+
console.log(` ✅ claude-code → Agents: ${cmdDir}/ethan-agent-*.md × ${agentFileCount}(含 ${agents.length} 个 Agent 快捷键)`);
|
|
755
783
|
}
|
|
756
784
|
else {
|
|
757
|
-
//
|
|
785
|
+
// codebuddy / cursor / cline / windsurf:静态提示词(各 /ethan-xxx 命令文件)
|
|
758
786
|
for (const cmd of WORKFLOW_SLASH_COMMANDS) {
|
|
759
787
|
const content = `---\n` +
|
|
760
788
|
`description: "Ethan — ${cmd.name}:${cmd.description}"\n` +
|
|
@@ -763,8 +791,45 @@ program
|
|
|
763
791
|
fs.writeFileSync(path.join(outDir, `ethan-${cmd.id}.md`), content, 'utf-8');
|
|
764
792
|
total++;
|
|
765
793
|
}
|
|
766
|
-
console.log(` ✅
|
|
767
|
-
console.log(` 使用方式:在
|
|
794
|
+
console.log(` ✅ ${p.padEnd(12)} → 工作流: ${cmdDir}/ethan-{cmd}.md × ${WORKFLOW_SLASH_COMMANDS.length}`);
|
|
795
|
+
console.log(` 使用方式:在 ${p} 聊天中输入 /ethan-commit、/ethan-auto 等`);
|
|
796
|
+
// ── Agent 命令(静态提示词,适用于 codebuddy/cursor/cline/windsurf)──────
|
|
797
|
+
const { getActiveAgents: getAgentsStatic } = await Promise.resolve().then(() => __importStar(require('../agents/index')));
|
|
798
|
+
const agentsStatic = getAgentsStatic(dir);
|
|
799
|
+
// ethan-agent-list.md(静态)
|
|
800
|
+
const agentListRows = agentsStatic
|
|
801
|
+
.map((a) => `| /ethan-agent-${a.id} | ${a.emoji} ${a.name} | ${a.role} |`)
|
|
802
|
+
.join('\n');
|
|
803
|
+
fs.writeFileSync(path.join(outDir, 'ethan-agent-list.md'), `---\ndescription: "Ethan — 查看所有可用 Agent 及其 Skill 分配"\n---\n\n` +
|
|
804
|
+
`# Ethan Agent 列表\n\n` +
|
|
805
|
+
`| 命令 | Agent | 职责 |\n|------|-------|------|\n${agentListRows}\n\n` +
|
|
806
|
+
`运行:\`ethan agent list\`\n`, 'utf-8');
|
|
807
|
+
// ethan-agent-run.md(静态)
|
|
808
|
+
fs.writeFileSync(path.join(outDir, 'ethan-agent-run.md'), `---\ndescription: "Ethan — Multi-Agent 编排:将任务分配给多个专业 Agent 协作执行"\n---\n\n` +
|
|
809
|
+
`# Ethan Multi-Agent 编排\n\n` +
|
|
810
|
+
`用法:\`ethan agent run <pipeline> -c "任务描述"\`\n\n` +
|
|
811
|
+
`示例:\`ethan agent run dev-workflow -c "实现用户登录功能"\`\n\n` +
|
|
812
|
+
`可用模式:sequential / parallel / review-loop / consensus\n`, 'utf-8');
|
|
813
|
+
// ethan-agent-new.md(静态)
|
|
814
|
+
fs.writeFileSync(path.join(outDir, 'ethan-agent-new.md'), `---\ndescription: "Ethan — 创建自定义 Agent,生成 .ethan/agents/<id>.yaml"\n---\n\n` +
|
|
815
|
+
`# 创建自定义 Agent\n\n` +
|
|
816
|
+
`运行:\`ethan agent new\`\n\n` +
|
|
817
|
+
`将在 \`.ethan/agents/\` 目录下生成 Agent 配置文件。\n`, 'utf-8');
|
|
818
|
+
// 每个内置 Agent 生成独立静态命令文件
|
|
819
|
+
let staticAgentFileCount = 3;
|
|
820
|
+
for (const agent of agentsStatic) {
|
|
821
|
+
const agentSkillList = agent.skillIds.slice(0, 8).join('、') + (agent.skillIds.length > 8 ? '...' : '');
|
|
822
|
+
const agentContent = `---\ndescription: "Ethan — ${agent.emoji} ${agent.name}:${agent.role}"\n---\n\n` +
|
|
823
|
+
`# ${agent.emoji} ${agent.name}\n\n` +
|
|
824
|
+
`**职责**: ${agent.role}\n\n` +
|
|
825
|
+
`**负责 Skill**: ${agentSkillList}\n\n` +
|
|
826
|
+
`**运行方式**:\`ethan agent run --no-copy -c "<任务描述>"\`\n\n` +
|
|
827
|
+
`**示例**:\`ethan agent run --no-copy -c "对当前代码进行安全审查"\`\n`;
|
|
828
|
+
fs.writeFileSync(path.join(outDir, `ethan-agent-${agent.id}.md`), agentContent, 'utf-8');
|
|
829
|
+
staticAgentFileCount++;
|
|
830
|
+
total++;
|
|
831
|
+
}
|
|
832
|
+
console.log(` ✅ ${p.padEnd(12)} → Agents: ${cmdDir}/ethan-agent-*.md × ${staticAgentFileCount}(含 ${agentsStatic.length} 个 Agent 快捷键)`);
|
|
768
833
|
}
|
|
769
834
|
}
|
|
770
835
|
else {
|
|
@@ -797,14 +862,28 @@ program
|
|
|
797
862
|
continue: 'Continue(@ethan 触发)',
|
|
798
863
|
lingma: '灵码(@ethan 触发)',
|
|
799
864
|
};
|
|
865
|
+
// ── Agent 速查表章节 ─────────────────────────────────────────────────
|
|
866
|
+
const { getActiveAgents: getAgentsForOther } = await Promise.resolve().then(() => __importStar(require('../agents/index')));
|
|
867
|
+
const otherAgents = getAgentsForOther(dir);
|
|
868
|
+
const agentRows = otherAgents
|
|
869
|
+
.map((a) => `| \`/ethan-agent-${a.id}\` | \`@ethan agent ${a.id}\` | ${a.emoji} ${a.name} | Agent | ${a.role} |`)
|
|
870
|
+
.join('\n');
|
|
871
|
+
const agentSection = `## Multi-Agent 命令\n\n` +
|
|
872
|
+
`| Slash 命令 | @ethan 形式 | Agent | 类型 | 职责 |\n` +
|
|
873
|
+
`|-----------|------------|-------|------|------|\n` +
|
|
874
|
+
`| \`/ethan-agent-list\` | \`@ethan agent list\` | — | Agent | 查看所有可用 Agent |\n` +
|
|
875
|
+
`| \`/ethan-agent-run\` | \`@ethan agent run\` | — | Agent | Multi-Agent 编排执行 |\n` +
|
|
876
|
+
`| \`/ethan-agent-new\` | \`@ethan agent new\` | — | Agent | 创建自定义 Agent |\n` +
|
|
877
|
+
`${agentRows}\n`;
|
|
800
878
|
const content = `# Ethan Slash 命令速查表 — ${platformLabel[p] ?? p}\n\n` +
|
|
801
879
|
`> 生成时间:${new Date().toISOString()}\n\n` +
|
|
802
|
-
`## Skills
|
|
880
|
+
`## Skills 命令(${skills.length} 个)\n\n` +
|
|
803
881
|
`| Slash 命令 | @ethan 形式 | Skill 名称 | 类型 | 说明 |\n` +
|
|
804
882
|
`|-----------|------------|-----------|------|------|\n` +
|
|
805
883
|
`${skillRows}\n\n` +
|
|
806
884
|
`## 工作流命令\n\n` +
|
|
807
885
|
`${wfSections}\n\n` +
|
|
886
|
+
`${agentSection}\n` +
|
|
808
887
|
`## 快速示例\n\n` +
|
|
809
888
|
`\`\`\`\n` +
|
|
810
889
|
`/ethan-code-review # Skill:代码审查\n` +
|
|
@@ -812,7 +891,8 @@ program
|
|
|
812
891
|
`/ethan-commit # 工作流:生成提交信息\n` +
|
|
813
892
|
`/ethan-review # 工作流:Code Review\n` +
|
|
814
893
|
`/ethan-auto # 工作流:Auto-Pilot 超级 Prompt\n` +
|
|
815
|
-
`/ethan-
|
|
894
|
+
`/ethan-agent-run # Agent:Multi-Agent 协作编排\n` +
|
|
895
|
+
`/ethan-agent-architect # Agent:架构师 Agent 直接对话\n` +
|
|
816
896
|
`\`\`\`\n`;
|
|
817
897
|
const destFile = path.join(outDir, 'ethan-commands.md');
|
|
818
898
|
fs.writeFileSync(destFile, content, 'utf-8');
|
|
@@ -820,7 +900,8 @@ program
|
|
|
820
900
|
total++;
|
|
821
901
|
}
|
|
822
902
|
}
|
|
823
|
-
const
|
|
903
|
+
const INDIVIDUAL_FILE_PLATFORMS_CHECK = ['claude-code', 'codebuddy', 'cursor', 'cline', 'windsurf'];
|
|
904
|
+
const totalFiles = INDIVIDUAL_FILE_PLATFORMS_CHECK.includes(platform)
|
|
824
905
|
? skills.length + WORKFLOW_SLASH_COMMANDS.length
|
|
825
906
|
: total;
|
|
826
907
|
console.log(`\n共生成 ${totalFiles} 个 Slash 命令文件(目录:${dir})`);
|
|
@@ -1254,6 +1335,15 @@ pipelineCmd
|
|
|
1254
1335
|
}
|
|
1255
1336
|
writeStats(stats);
|
|
1256
1337
|
});
|
|
1338
|
+
// ─── upgrade 命令 ───────────────────────────────────────────────────────────
|
|
1339
|
+
program
|
|
1340
|
+
.command('upgrade')
|
|
1341
|
+
.description('检查并升级到最新版本')
|
|
1342
|
+
.option('--force', '跳过版本检查,强制重新安装最新版本')
|
|
1343
|
+
.action(async (options) => {
|
|
1344
|
+
const { checkAndPrintUpdate } = await Promise.resolve().then(() => __importStar(require('./update-check')));
|
|
1345
|
+
await checkAndPrintUpdate(pkg.version, pkg.name, options.force ?? false);
|
|
1346
|
+
});
|
|
1257
1347
|
// ─── doctor 命令 ────────────────────────────────────────────────────────────
|
|
1258
1348
|
program
|
|
1259
1349
|
.command('doctor')
|
|
@@ -1267,7 +1357,27 @@ program
|
|
|
1267
1357
|
const nodeOk = nodeMajor >= 18;
|
|
1268
1358
|
console.log(`\n[环境检查]`);
|
|
1269
1359
|
console.log(` Node.js: v${nodeVersion} ${nodeOk ? '✅' : '❌ (需要 Node.js >= 18)'}`);
|
|
1270
|
-
// 2.
|
|
1360
|
+
// 2. Windows 专项检查
|
|
1361
|
+
if (process.platform === 'win32') {
|
|
1362
|
+
console.log('\n[Windows 环境检查]');
|
|
1363
|
+
// 检查 PowerShell 执行策略
|
|
1364
|
+
try {
|
|
1365
|
+
const psPolicy = (0, child_process_1.spawnSync)('powershell', ['-Command', 'Get-ExecutionPolicy'], { encoding: 'utf-8' });
|
|
1366
|
+
const policy = (psPolicy.stdout ?? '').trim();
|
|
1367
|
+
const policyOk = !['Restricted', 'AllSigned'].includes(policy);
|
|
1368
|
+
console.log(` PowerShell 执行策略: ${policy} ${policyOk ? '✅' : '⚠️ 需设置(Set-ExecutionPolicy RemoteSigned -Scope CurrentUser)'}`);
|
|
1369
|
+
}
|
|
1370
|
+
catch { /* PowerShell not available */ }
|
|
1371
|
+
// 检查 npm 全局路径是否在 PATH 中
|
|
1372
|
+
const npmGlobal = (0, child_process_1.spawnSync)('npm', ['config', 'get', 'prefix'], { encoding: 'utf-8', shell: true });
|
|
1373
|
+
const npmPrefix = (npmGlobal.stdout ?? '').trim();
|
|
1374
|
+
if (npmPrefix) {
|
|
1375
|
+
const inPath = (process.env.PATH ?? '').includes(npmPrefix);
|
|
1376
|
+
console.log(` npm global prefix: ${npmPrefix} ${inPath ? '✅ (在 PATH 中)' : '⚠️ 不在 PATH 中(需手动添加)'}`);
|
|
1377
|
+
}
|
|
1378
|
+
console.log(` UTF-8 编码: ${process.env.CHCP === '65001' || process.env.LC_ALL?.includes('UTF') ? '✅' : '⚠️ 建议 chcp 65001'}`);
|
|
1379
|
+
}
|
|
1380
|
+
// 3. MCP SDK check
|
|
1271
1381
|
const sdkOk = fs.existsSync(path.join(__dirname, '../../node_modules/@modelcontextprotocol/sdk'));
|
|
1272
1382
|
console.log(` @modelcontextprotocol/sdk: ${sdkOk ? '✅ 已安装' : '❌ 未安装'}`);
|
|
1273
1383
|
// 3. Rules files status
|
|
@@ -1312,6 +1422,40 @@ program
|
|
|
1312
1422
|
}
|
|
1313
1423
|
console.log('\n' + '─'.repeat(60));
|
|
1314
1424
|
const allBuilt = ruleFiles.every(({ file }) => fs.existsSync(path.join(ROOT, file)));
|
|
1425
|
+
// 版本更新状态检查
|
|
1426
|
+
console.log(`\n[版本更新状态]`);
|
|
1427
|
+
console.log(` 当前版本 : v${pkg.version}`);
|
|
1428
|
+
const updateCachePath = path.join(os.homedir(), '.ethan-update-cache.json');
|
|
1429
|
+
if (fs.existsSync(updateCachePath)) {
|
|
1430
|
+
try {
|
|
1431
|
+
const updateCache = JSON.parse(fs.readFileSync(updateCachePath, 'utf-8'));
|
|
1432
|
+
const lastCheckedAgo = Math.round((Date.now() - updateCache.lastChecked) / 1000 / 60);
|
|
1433
|
+
console.log(` npm 最新版 : v${updateCache.latestVersion}`);
|
|
1434
|
+
console.log(` 上次检查 : ${lastCheckedAgo} 分钟前`);
|
|
1435
|
+
const needsUpdate = (() => {
|
|
1436
|
+
const parse = (v) => v.replace(/^v/, '').split('.').map(Number);
|
|
1437
|
+
const [cMaj, cMin, cPat] = parse(pkg.version);
|
|
1438
|
+
const [lMaj, lMin, lPat] = parse(updateCache.latestVersion);
|
|
1439
|
+
if (lMaj !== cMaj)
|
|
1440
|
+
return lMaj > cMaj;
|
|
1441
|
+
if (lMin !== cMin)
|
|
1442
|
+
return lMin > cMin;
|
|
1443
|
+
return lPat > cPat;
|
|
1444
|
+
})();
|
|
1445
|
+
if (needsUpdate) {
|
|
1446
|
+
console.log(` 升级状态 : ⚠️ 有新版本可用,运行 ethan upgrade 升级`);
|
|
1447
|
+
}
|
|
1448
|
+
else {
|
|
1449
|
+
console.log(` 升级状态 : ✅ 已是最新`);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
catch {
|
|
1453
|
+
console.log(` 升级状态 : ⚠️ 缓存文件损坏,将在下次运行时重建`);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
console.log(` 升级状态 : ⏳ 尚未检查(下次运行 ethan 命令时自动检查)`);
|
|
1458
|
+
}
|
|
1315
1459
|
if (!allBuilt) {
|
|
1316
1460
|
console.log('\n💡 提示:运行 npm run build:rules 生成规则文件\n');
|
|
1317
1461
|
}
|
|
@@ -1471,6 +1615,166 @@ program
|
|
|
1471
1615
|
console.log(`\n文件路径:${configPath}`);
|
|
1472
1616
|
console.log('\n💡 提示:现在运行 ethan install --platform <platform> 将使用此配置\n');
|
|
1473
1617
|
});
|
|
1618
|
+
// ─── setup 命令(零门槛一键安装向导) ────────────────────────────────────────
|
|
1619
|
+
// 面向产品、测试等非技术用户,自动检测环境,提供最简安装体验
|
|
1620
|
+
program
|
|
1621
|
+
.command('setup')
|
|
1622
|
+
.description('一键安装向导:自动检测编辑器,为非技术用户提供最简配置体验')
|
|
1623
|
+
.option('--role <role>', '用户角色:dev(开发)/ pm(产品)/ qa(测试)/ design(设计)')
|
|
1624
|
+
.option('--platform <platform>', '强制指定平台(跳过自动检测)')
|
|
1625
|
+
.option('--lang <lang>', '界面语言:zh(默认)/ en')
|
|
1626
|
+
.option('-y, --yes', '跳过所有确认,使用推荐配置')
|
|
1627
|
+
.action(async (options) => {
|
|
1628
|
+
const readline = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
1629
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1630
|
+
const ask = (q, def) => new Promise((resolve) => rl.question(def !== undefined ? `${q} [${def}]: ` : `${q}: `, (a) => resolve(a.trim() || def || '')));
|
|
1631
|
+
const isEn = options.lang === 'en';
|
|
1632
|
+
const isAuto = !!options.yes;
|
|
1633
|
+
const t = {
|
|
1634
|
+
title: isEn ? 'Ethan Setup Wizard' : 'Ethan 快速安装向导',
|
|
1635
|
+
welcome: isEn
|
|
1636
|
+
? 'This wizard will configure Ethan for your editor in 3 steps.'
|
|
1637
|
+
: '3 步完成配置,无需任何编程知识。',
|
|
1638
|
+
step1: isEn ? '[1/3] Detecting your editor...' : '[1/3] 检测你的编辑器...',
|
|
1639
|
+
step2: isEn ? '[2/3] Configuring your role...' : '[2/3] 配置你的角色...',
|
|
1640
|
+
step3: isEn ? '[3/3] Generating slash commands...' : '[3/3] 生成编辑器快捷指令...',
|
|
1641
|
+
done: isEn ? 'Setup complete!' : '安装完成!',
|
|
1642
|
+
};
|
|
1643
|
+
console.log(`\n${'═'.repeat(52)}`);
|
|
1644
|
+
console.log(` ${t.title}`);
|
|
1645
|
+
console.log(` ${t.welcome}`);
|
|
1646
|
+
console.log(`${'═'.repeat(52)}\n`);
|
|
1647
|
+
// ── 步骤 1:自动检测编辑器 ─────────────────────────────────────────────
|
|
1648
|
+
console.log(t.step1);
|
|
1649
|
+
// 检测逻辑:查找 .cursor / .vscode / .windsurf 等目录
|
|
1650
|
+
const detectedPlatforms = [];
|
|
1651
|
+
const checkDir = (d) => fs.existsSync(path.join(process.cwd(), d));
|
|
1652
|
+
if (checkDir('.cursor'))
|
|
1653
|
+
detectedPlatforms.push('cursor');
|
|
1654
|
+
if (checkDir('.vscode') || checkDir('.github'))
|
|
1655
|
+
detectedPlatforms.push('copilot');
|
|
1656
|
+
if (checkDir('.windsurf'))
|
|
1657
|
+
detectedPlatforms.push('windsurf');
|
|
1658
|
+
if (checkDir('.codebuddy'))
|
|
1659
|
+
detectedPlatforms.push('codebuddy');
|
|
1660
|
+
if (checkDir('.claude'))
|
|
1661
|
+
detectedPlatforms.push('claude-code');
|
|
1662
|
+
if (checkDir('.continue'))
|
|
1663
|
+
detectedPlatforms.push('continue');
|
|
1664
|
+
// 如果没有检测到任何编辑器,给出选项列表
|
|
1665
|
+
const ALL_PLATFORMS_SETUP = ['claude-code', 'cursor', 'copilot', 'codebuddy', 'windsurf', 'zed', 'jetbrains', 'continue', 'cline', 'lingma'];
|
|
1666
|
+
let chosenPlatform = options.platform ?? '';
|
|
1667
|
+
if (!chosenPlatform) {
|
|
1668
|
+
if (detectedPlatforms.length === 1 && isAuto) {
|
|
1669
|
+
chosenPlatform = detectedPlatforms[0];
|
|
1670
|
+
console.log(` ✅ 检测到 ${chosenPlatform},将自动配置`);
|
|
1671
|
+
}
|
|
1672
|
+
else if (detectedPlatforms.length > 0) {
|
|
1673
|
+
console.log(` 检测到以下编辑器:${detectedPlatforms.join(', ')}`);
|
|
1674
|
+
const detected = detectedPlatforms.join('/');
|
|
1675
|
+
if (isAuto) {
|
|
1676
|
+
chosenPlatform = detectedPlatforms[0];
|
|
1677
|
+
}
|
|
1678
|
+
else {
|
|
1679
|
+
const choice = await ask(isEn
|
|
1680
|
+
? ` Which editor? (${ALL_PLATFORMS_SETUP.join('|')}|all)`
|
|
1681
|
+
: ` 选择编辑器(${ALL_PLATFORMS_SETUP.join('|')}|all)`, detectedPlatforms[0]);
|
|
1682
|
+
chosenPlatform = choice || detectedPlatforms[0];
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
else {
|
|
1686
|
+
if (isAuto) {
|
|
1687
|
+
chosenPlatform = 'claude-code';
|
|
1688
|
+
}
|
|
1689
|
+
else {
|
|
1690
|
+
console.log(isEn ? ' No editor auto-detected. Common options:' : ' 未检测到编辑器,请选择:');
|
|
1691
|
+
ALL_PLATFORMS_SETUP.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
|
|
1692
|
+
const choice = await ask(isEn ? ' Enter number or name' : ' 输入序号或名称', '1');
|
|
1693
|
+
const num = parseInt(choice, 10);
|
|
1694
|
+
chosenPlatform = (!isNaN(num) && num >= 1 && num <= ALL_PLATFORMS_SETUP.length)
|
|
1695
|
+
? ALL_PLATFORMS_SETUP[num - 1]
|
|
1696
|
+
: (choice || 'claude-code');
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
console.log(` → 目标平台:${chosenPlatform}\n`);
|
|
1701
|
+
// ── 步骤 2:角色配置 ──────────────────────────────────────────────────
|
|
1702
|
+
console.log(t.step2);
|
|
1703
|
+
const ROLE_PRESETS = {
|
|
1704
|
+
pm: { lang: 'zh', disabledSkills: ['unit-testing', 'docker', 'cicd', 'observability', 'data-pipeline', 'ml-experiment'] },
|
|
1705
|
+
qa: { lang: 'zh', disabledSkills: ['docker', 'cicd', 'ml-experiment', 'green-code', 'api-mock'] },
|
|
1706
|
+
design: { lang: 'zh', disabledSkills: ['unit-testing', 'docker', 'cicd', 'database-optimize', 'data-pipeline', 'ml-experiment', 'observability'] },
|
|
1707
|
+
dev: { lang: 'zh', disabledSkills: [] },
|
|
1708
|
+
};
|
|
1709
|
+
let chosenRole = options.role ?? '';
|
|
1710
|
+
if (!chosenRole && !isAuto) {
|
|
1711
|
+
console.log(isEn ? ' Your role:' : ' 你的角色:');
|
|
1712
|
+
console.log(' 1. dev - 开发工程师(启用所有 36 个 Skill)');
|
|
1713
|
+
console.log(' 2. qa - 测试工程师(测试、审查相关 Skill)');
|
|
1714
|
+
console.log(' 3. pm - 产品经理(需求、设计、周报相关 Skill)');
|
|
1715
|
+
console.log(' 4. design - 设计师(流程、文档类 Skill)');
|
|
1716
|
+
const roleChoice = await ask(isEn ? ' Select (1-4)' : ' 选择(1-4)', '1');
|
|
1717
|
+
const roleMap = { '1': 'dev', '2': 'qa', '3': 'pm', '4': 'design' };
|
|
1718
|
+
chosenRole = roleMap[roleChoice] ?? roleChoice ?? 'dev';
|
|
1719
|
+
}
|
|
1720
|
+
chosenRole = chosenRole || 'dev';
|
|
1721
|
+
console.log(` → 角色:${chosenRole}\n`);
|
|
1722
|
+
const preset = ROLE_PRESETS[chosenRole] ?? ROLE_PRESETS.dev;
|
|
1723
|
+
// 语言选择
|
|
1724
|
+
let chosenLang = preset.lang;
|
|
1725
|
+
if (!isAuto && !options.lang) {
|
|
1726
|
+
const langChoice = await ask(isEn ? ' Output language (zh/en)' : ' 输出语言(zh/en)', 'zh');
|
|
1727
|
+
chosenLang = langChoice === 'en' ? 'en' : 'zh';
|
|
1728
|
+
}
|
|
1729
|
+
rl.close();
|
|
1730
|
+
// 写入 .ethanrc.json
|
|
1731
|
+
const existingConfig = (0, config_1.readConfig)(process.cwd());
|
|
1732
|
+
const newConfig = {
|
|
1733
|
+
...existingConfig,
|
|
1734
|
+
lang: chosenLang,
|
|
1735
|
+
...(preset.disabledSkills.length > 0 ? { disabledSkills: preset.disabledSkills } : {}),
|
|
1736
|
+
setup: { role: chosenRole, platform: chosenPlatform, setupAt: new Date().toISOString() },
|
|
1737
|
+
};
|
|
1738
|
+
(0, config_1.writeConfig)(newConfig, process.cwd());
|
|
1739
|
+
console.log(` ✅ .ethanrc.json 已生成(角色:${chosenRole},语言:${chosenLang})`);
|
|
1740
|
+
// ── 步骤 3:生成编辑器快捷指令 ───────────────────────────────────────
|
|
1741
|
+
console.log(`\n${t.step3}`);
|
|
1742
|
+
const slashPlatforms = chosenPlatform === 'all' ? 'all' : chosenPlatform;
|
|
1743
|
+
// 动态调用 slash 命令逻辑(通过 execSync,保持 tty)
|
|
1744
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
1745
|
+
try {
|
|
1746
|
+
execSync(`ethan slash --platform ${slashPlatforms} --dir "${process.cwd()}"`, { stdio: 'inherit' });
|
|
1747
|
+
}
|
|
1748
|
+
catch {
|
|
1749
|
+
// 如果 ethan 还未在全局安装(开发模式),尝试直接调用 ts-node
|
|
1750
|
+
console.log(isEn ? ' Note: Run "ethan slash" manually if above failed.' : ' 提示:如以上失败,请手动运行 ethan slash。');
|
|
1751
|
+
}
|
|
1752
|
+
// ── 完成 ─────────────────────────────────────────────────────────────
|
|
1753
|
+
console.log(`\n${'═'.repeat(52)}`);
|
|
1754
|
+
console.log(` ${t.done}`);
|
|
1755
|
+
console.log('═'.repeat(52));
|
|
1756
|
+
console.log(isEn ? '\nNext steps:' : '\n下一步:');
|
|
1757
|
+
if (chosenPlatform === 'claude-code') {
|
|
1758
|
+
console.log(isEn
|
|
1759
|
+
? ' 1. Restart Claude Code editor'
|
|
1760
|
+
: ' 1. 重启 Claude Code 编辑器');
|
|
1761
|
+
console.log(isEn
|
|
1762
|
+
? ' 2. Type /ethan- in chat to see all commands'
|
|
1763
|
+
: ' 2. 在聊天中输入 /ethan- 即可看到所有快捷指令');
|
|
1764
|
+
}
|
|
1765
|
+
else {
|
|
1766
|
+
console.log(isEn
|
|
1767
|
+
? ` 1. Open ethan-commands.md in your ${chosenPlatform} context`
|
|
1768
|
+
: ` 1. 将 ethan-commands.md 加入你的 ${chosenPlatform} 上下文`);
|
|
1769
|
+
console.log(isEn
|
|
1770
|
+
? ' 2. Type /ethan-code-review or @ethan code-review to start'
|
|
1771
|
+
: ' 2. 输入 /ethan-code-review 或 @ethan code-review 开始使用');
|
|
1772
|
+
}
|
|
1773
|
+
console.log(isEn
|
|
1774
|
+
? ' 3. Run "ethan agent run" for multi-agent collaboration'
|
|
1775
|
+
: ' 3. 运行 ethan agent run 体验多 Agent 协作');
|
|
1776
|
+
console.log('');
|
|
1777
|
+
});
|
|
1474
1778
|
// ─── workflow 命令(有状态一键工作流) ──────────────────────────────────────
|
|
1475
1779
|
const workflowCmd = program.command('workflow').description('有状态工作流执行:一键推进各阶段任务');
|
|
1476
1780
|
workflowCmd
|
|
@@ -3014,36 +3318,34 @@ memoryCmd
|
|
|
3014
3318
|
});
|
|
3015
3319
|
memoryCmd
|
|
3016
3320
|
.command('search <keyword>')
|
|
3017
|
-
.description('
|
|
3321
|
+
.description('在记忆库中全文检索(加权评分 + 片段高亮)')
|
|
3018
3322
|
.option('--global', '搜索全局记忆库')
|
|
3019
|
-
.option('--tag <tag>', '
|
|
3323
|
+
.option('--tag <tag>', '按标签过滤(AND 语义)')
|
|
3324
|
+
.option('--type <type>', '按类型过滤:workflow|skill|manual|decision|knowledge')
|
|
3020
3325
|
.option('-n, --limit <n>', '最多显示 N 条', '10')
|
|
3021
|
-
.action((keyword, options) => {
|
|
3022
|
-
const
|
|
3023
|
-
const
|
|
3024
|
-
const
|
|
3025
|
-
const
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
e.tags.some((t) => t.toLowerCase().includes(kw));
|
|
3030
|
-
const matchTag = options.tag ? e.tags.includes(options.tag) : true;
|
|
3031
|
-
return matchKw && matchTag;
|
|
3326
|
+
.action(async (keyword, options) => {
|
|
3327
|
+
const { searchMemory, getMemoryDir: getMemDir } = await Promise.resolve().then(() => __importStar(require('../memory/index')));
|
|
3328
|
+
const dir = getMemDir(!!options.global);
|
|
3329
|
+
const limit = parseInt(options.limit ?? '10', 10) || 10;
|
|
3330
|
+
const results = searchMemory(keyword, dir, {
|
|
3331
|
+
tags: options.tag ? [options.tag] : undefined,
|
|
3332
|
+
type: options.type,
|
|
3333
|
+
limit,
|
|
3032
3334
|
});
|
|
3033
|
-
results = results.slice(0, limit);
|
|
3034
3335
|
if (results.length === 0) {
|
|
3035
3336
|
console.log(`\n🔍 未找到匹配 "${keyword}" 的记忆\n`);
|
|
3337
|
+
console.log(' 💡 尝试更宽泛的关键词,或用 ethan memory list 查看所有记忆\n');
|
|
3036
3338
|
return;
|
|
3037
3339
|
}
|
|
3038
3340
|
console.log(`\n🔍 找到 ${results.length} 条记忆(关键词:"${keyword}")\n`);
|
|
3039
|
-
console.log('─'.repeat(
|
|
3040
|
-
for (const e of results) {
|
|
3041
|
-
|
|
3042
|
-
console.log(
|
|
3043
|
-
console.log(`
|
|
3044
|
-
console.log(`
|
|
3341
|
+
console.log('─'.repeat(70));
|
|
3342
|
+
for (const { entry: e, score, matchedFields, snippet } of results) {
|
|
3343
|
+
console.log(`\n 📌 [${e.type}] ${e.title}`);
|
|
3344
|
+
console.log(` ID: ${e.id} | ${e.createdAt.slice(0, 10)} | 相关度: ${score} | 命中: ${matchedFields.join(', ')}`);
|
|
3345
|
+
console.log(` 标签:${e.tags.join(', ') || '无'}`);
|
|
3346
|
+
console.log(` 摘要:${snippet}`);
|
|
3045
3347
|
}
|
|
3046
|
-
console.log('\n' + '─'.repeat(
|
|
3348
|
+
console.log('\n' + '─'.repeat(70));
|
|
3047
3349
|
console.log(`\n💡 用 ethan memory show <id> 查看完整内容\n`);
|
|
3048
3350
|
});
|
|
3049
3351
|
memoryCmd
|
|
@@ -5063,5 +5365,67 @@ agentCmd
|
|
|
5063
5365
|
console.log(` ethan agent run --context "任务" # 在编排中使用此 Agent`);
|
|
5064
5366
|
console.log('');
|
|
5065
5367
|
});
|
|
5368
|
+
// ─── extension 命令(事件钩子 + Webhook 扩展)─────────────────────────────────
|
|
5369
|
+
const extCmd = program
|
|
5370
|
+
.command('extension')
|
|
5371
|
+
.alias('ext')
|
|
5372
|
+
.description('扩展管理:事件钩子、Webhook 集成、Extension SDK');
|
|
5373
|
+
extCmd
|
|
5374
|
+
.command('list')
|
|
5375
|
+
.description('查看当前项目的钩子和 Webhook 配置')
|
|
5376
|
+
.action(async () => {
|
|
5377
|
+
const { readExtensionsConfig } = await Promise.resolve().then(() => __importStar(require('../extension/index')));
|
|
5378
|
+
const cfg = readExtensionsConfig(process.cwd());
|
|
5379
|
+
console.log('\n🔌 Ethan 扩展配置\n');
|
|
5380
|
+
console.log('─'.repeat(50));
|
|
5381
|
+
console.log(`\n钩子文件(${cfg.hooks.length} 个):`);
|
|
5382
|
+
if (cfg.hooks.length === 0)
|
|
5383
|
+
console.log(' (无)');
|
|
5384
|
+
cfg.hooks.forEach((h) => console.log(` ${h.enabled ? '✅' : '⏸️ '} ${h.file}`));
|
|
5385
|
+
console.log(`\nWebhook(${cfg.webhooks.length} 个):`);
|
|
5386
|
+
if (cfg.webhooks.length === 0)
|
|
5387
|
+
console.log(' (无)');
|
|
5388
|
+
cfg.webhooks.forEach((w) => console.log(` ${w.enabled ? '✅' : '⏸️ '} ${w.url} [${w.events.join(', ')}]`));
|
|
5389
|
+
console.log('');
|
|
5390
|
+
});
|
|
5391
|
+
extCmd
|
|
5392
|
+
.command('hook-init [name]')
|
|
5393
|
+
.description('在 .ethan/hooks/ 生成示例钩子文件')
|
|
5394
|
+
.action(async (name) => {
|
|
5395
|
+
const { generateHookTemplate } = await Promise.resolve().then(() => __importStar(require('../extension/index')));
|
|
5396
|
+
const hookName = name || 'my-hook';
|
|
5397
|
+
const file = generateHookTemplate(process.cwd(), hookName);
|
|
5398
|
+
console.log(`\n✅ 钩子文件已生成:${file}`);
|
|
5399
|
+
console.log('\n支持的事件:before:skill | after:skill | before:pipeline | after:pipeline | workflow:done | memory:save\n');
|
|
5400
|
+
});
|
|
5401
|
+
extCmd
|
|
5402
|
+
.command('webhook-add <url>')
|
|
5403
|
+
.description('添加 Webhook(在工作流事件后推送通知)')
|
|
5404
|
+
.option('--events <events>', '监听事件(逗号分隔)', 'after:skill,workflow:done')
|
|
5405
|
+
.option('--secret <secret>', 'HMAC 签名密钥(可选)')
|
|
5406
|
+
.action(async (url, opts) => {
|
|
5407
|
+
const { readExtensionsConfig, writeExtensionsConfig } = await Promise.resolve().then(() => __importStar(require('../extension/index')));
|
|
5408
|
+
const cfg = readExtensionsConfig(process.cwd());
|
|
5409
|
+
const events = opts.events.split(',').map((e) => e.trim());
|
|
5410
|
+
cfg.webhooks.push({ url, events, secret: opts.secret, enabled: true, headers: {} });
|
|
5411
|
+
writeExtensionsConfig(cfg, process.cwd());
|
|
5412
|
+
console.log(`\n✅ Webhook 已添加:${url}`);
|
|
5413
|
+
console.log(` 监听事件:${events.join(', ')}\n`);
|
|
5414
|
+
});
|
|
5415
|
+
extCmd
|
|
5416
|
+
.command('webhook-remove <url>')
|
|
5417
|
+
.description('移除 Webhook')
|
|
5418
|
+
.action(async (url) => {
|
|
5419
|
+
const { readExtensionsConfig, writeExtensionsConfig } = await Promise.resolve().then(() => __importStar(require('../extension/index')));
|
|
5420
|
+
const cfg = readExtensionsConfig(process.cwd());
|
|
5421
|
+
const before = cfg.webhooks.length;
|
|
5422
|
+
cfg.webhooks = cfg.webhooks.filter((w) => w.url !== url);
|
|
5423
|
+
if (cfg.webhooks.length === before) {
|
|
5424
|
+
console.error(`\n❌ 未找到 Webhook: ${url}\n`);
|
|
5425
|
+
process.exit(1);
|
|
5426
|
+
}
|
|
5427
|
+
writeExtensionsConfig(cfg, process.cwd());
|
|
5428
|
+
console.log(`\n✅ Webhook 已移除:${url}\n`);
|
|
5429
|
+
});
|
|
5066
5430
|
program.parse(process.argv);
|
|
5067
5431
|
//# sourceMappingURL=index.js.map
|