ethan-skill 1.0.0 → 1.2.0
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/dist/cli/config.d.ts +26 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +74 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +606 -62
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/update-check.d.ts +11 -0
- package/dist/cli/update-check.d.ts.map +1 -0
- package/dist/cli/update-check.js +128 -0
- package/dist/cli/update-check.js.map +1 -0
- package/dist/context/detector.d.ts +25 -0
- package/dist/context/detector.d.ts.map +1 -0
- package/dist/context/detector.js +230 -0
- package/dist/context/detector.js.map +1 -0
- package/dist/ethan-skill-1.0.0.vsix +0 -0
- package/dist/ethan-skill-1.1.0.vsix +0 -0
- package/dist/loader/custom-skill-loader.d.ts +15 -0
- package/dist/loader/custom-skill-loader.d.ts.map +1 -0
- package/dist/loader/custom-skill-loader.js +158 -0
- package/dist/loader/custom-skill-loader.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +152 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/server/dashboard.d.ts +2 -0
- package/dist/server/dashboard.d.ts.map +1 -0
- package/dist/server/dashboard.js +573 -0
- package/dist/server/dashboard.js.map +1 -0
- package/dist/skills/01-requirement.d.ts.map +1 -1
- package/dist/skills/01-requirement.js +4 -0
- package/dist/skills/01-requirement.js.map +1 -1
- package/dist/skills/02-task-breakdown.d.ts.map +1 -1
- package/dist/skills/02-task-breakdown.js +4 -0
- package/dist/skills/02-task-breakdown.js.map +1 -1
- package/dist/skills/03-design.d.ts.map +1 -1
- package/dist/skills/03-design.js +4 -0
- package/dist/skills/03-design.js.map +1 -1
- package/dist/skills/04-implementation.d.ts.map +1 -1
- package/dist/skills/04-implementation.js +4 -0
- package/dist/skills/04-implementation.js.map +1 -1
- package/dist/skills/05-progress-tracking.d.ts.map +1 -1
- package/dist/skills/05-progress-tracking.js +4 -0
- package/dist/skills/05-progress-tracking.js.map +1 -1
- package/dist/skills/06-task-report.d.ts.map +1 -1
- package/dist/skills/06-task-report.js +4 -0
- package/dist/skills/06-task-report.js.map +1 -1
- package/dist/skills/07-weekly-report.d.ts.map +1 -1
- package/dist/skills/07-weekly-report.js +3 -0
- package/dist/skills/07-weekly-report.js.map +1 -1
- package/dist/skills/08-code-review.d.ts.map +1 -1
- package/dist/skills/08-code-review.js +2 -0
- package/dist/skills/08-code-review.js.map +1 -1
- package/dist/skills/09-debug.d.ts.map +1 -1
- package/dist/skills/09-debug.js +3 -0
- package/dist/skills/09-debug.js.map +1 -1
- package/dist/skills/10-tech-research.d.ts.map +1 -1
- package/dist/skills/10-tech-research.js +4 -0
- package/dist/skills/10-tech-research.js.map +1 -1
- package/dist/skills/types.d.ts +4 -0
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/templates/copilot-md.template.d.ts.map +1 -1
- package/dist/templates/copilot-md.template.js +107 -26
- package/dist/templates/copilot-md.template.js.map +1 -1
- package/dist/vscode/router/trigger-router.d.ts +16 -0
- package/dist/vscode/router/trigger-router.d.ts.map +1 -0
- package/dist/vscode/router/trigger-router.js +45 -0
- package/dist/vscode/router/trigger-router.js.map +1 -0
- package/dist/vscode/skills/01-requirement.d.ts +3 -0
- package/dist/vscode/skills/01-requirement.d.ts.map +1 -0
- package/dist/vscode/skills/01-requirement.js +104 -0
- package/dist/vscode/skills/01-requirement.js.map +1 -0
- package/dist/vscode/skills/02-task-breakdown.d.ts +3 -0
- package/dist/vscode/skills/02-task-breakdown.d.ts.map +1 -0
- package/dist/vscode/skills/02-task-breakdown.js +86 -0
- package/dist/vscode/skills/02-task-breakdown.js.map +1 -0
- package/dist/vscode/skills/03-design.d.ts +3 -0
- package/dist/vscode/skills/03-design.d.ts.map +1 -0
- package/dist/vscode/skills/03-design.js +84 -0
- package/dist/vscode/skills/03-design.js.map +1 -0
- package/dist/vscode/skills/04-implementation.d.ts +3 -0
- package/dist/vscode/skills/04-implementation.d.ts.map +1 -0
- package/dist/vscode/skills/04-implementation.js +81 -0
- package/dist/vscode/skills/04-implementation.js.map +1 -0
- package/dist/vscode/skills/05-progress-tracking.d.ts +3 -0
- package/dist/vscode/skills/05-progress-tracking.d.ts.map +1 -0
- package/dist/vscode/skills/05-progress-tracking.js +82 -0
- package/dist/vscode/skills/05-progress-tracking.js.map +1 -0
- package/dist/vscode/skills/06-task-report.d.ts +3 -0
- package/dist/vscode/skills/06-task-report.d.ts.map +1 -0
- package/dist/vscode/skills/06-task-report.js +79 -0
- package/dist/vscode/skills/06-task-report.js.map +1 -0
- package/dist/vscode/skills/07-weekly-report.d.ts +3 -0
- package/dist/vscode/skills/07-weekly-report.d.ts.map +1 -0
- package/dist/vscode/skills/07-weekly-report.js +104 -0
- package/dist/vscode/skills/07-weekly-report.js.map +1 -0
- package/dist/vscode/skills/08-code-review.d.ts +3 -0
- package/dist/vscode/skills/08-code-review.d.ts.map +1 -0
- package/dist/vscode/skills/08-code-review.js +138 -0
- package/dist/vscode/skills/08-code-review.js.map +1 -0
- package/dist/vscode/skills/09-debug.d.ts +3 -0
- package/dist/vscode/skills/09-debug.d.ts.map +1 -0
- package/dist/vscode/skills/09-debug.js +154 -0
- package/dist/vscode/skills/09-debug.js.map +1 -0
- package/dist/vscode/skills/10-tech-research.d.ts +3 -0
- package/dist/vscode/skills/10-tech-research.d.ts.map +1 -0
- package/dist/vscode/skills/10-tech-research.js +145 -0
- package/dist/vscode/skills/10-tech-research.js.map +1 -0
- package/dist/vscode/skills/index.d.ts +18 -0
- package/dist/vscode/skills/index.d.ts.map +1 -0
- package/dist/vscode/skills/index.js +51 -0
- package/dist/vscode/skills/index.js.map +1 -0
- package/dist/vscode/skills/pipeline.d.ts +15 -0
- package/dist/vscode/skills/pipeline.d.ts.map +1 -0
- package/dist/vscode/skills/pipeline.js +55 -0
- package/dist/vscode/skills/pipeline.js.map +1 -0
- package/dist/vscode/skills/types.d.ts +64 -0
- package/dist/vscode/skills/types.d.ts.map +1 -0
- package/dist/vscode/skills/types.js +7 -0
- package/dist/vscode/skills/types.js.map +1 -0
- package/dist/vscode/vscode/commands.d.ts +63 -0
- package/dist/vscode/vscode/commands.d.ts.map +1 -0
- package/dist/vscode/vscode/commands.js +428 -0
- package/dist/vscode/vscode/commands.js.map +1 -0
- package/dist/vscode/vscode/extension.d.ts +4 -0
- package/dist/vscode/vscode/extension.d.ts.map +1 -0
- package/dist/vscode/vscode/extension.js +103 -0
- package/dist/vscode/vscode/extension.js.map +1 -0
- package/dist/workflow/state.d.ts +48 -0
- package/dist/workflow/state.d.ts.map +1 -0
- package/dist/workflow/state.js +162 -0
- package/dist/workflow/state.js.map +1 -0
- package/package.json +7 -3
- package/rules/claude-code/CLAUDE.md +12 -12
- package/rules/cline/.clinerules +3 -3
- package/rules/codebuddy/CODEBUDDY.md +12 -12
- package/rules/continue/.continuerules +3 -3
- package/rules/copilot/copilot-instructions.md +12 -12
- package/rules/cursor/.cursorrules +12 -12
- package/rules/cursor/smart-flow.mdc +12 -12
- package/rules/jetbrains/smart-flow.md +12 -12
- package/rules/lingma/smart-flow.md +2 -2
- package/rules/windsurf/.windsurf/rules/smart-flow.md +12 -12
- package/rules/zed/smart-flow.rules +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
4
|
* npx ethan CLI
|
|
5
|
-
* 命令:install | list | mcp | validate | pipeline | doctor | stats
|
|
5
|
+
* 命令:install | list | mcp | validate | pipeline | doctor | stats | init | run | workflow
|
|
6
6
|
*/
|
|
7
7
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
8
|
if (k2 === undefined) k2 = k;
|
|
@@ -42,8 +42,39 @@ const commander_1 = require("commander");
|
|
|
42
42
|
const fs = __importStar(require("fs"));
|
|
43
43
|
const path = __importStar(require("path"));
|
|
44
44
|
const os = __importStar(require("os"));
|
|
45
|
+
const child_process_1 = require("child_process");
|
|
45
46
|
const index_1 = require("../skills/index");
|
|
47
|
+
const update_check_1 = require("./update-check");
|
|
48
|
+
const config_1 = require("./config");
|
|
49
|
+
// ─── 剪贴板工具函数(不经过 shell,避免 backtick 命令注入) ─────────────────
|
|
50
|
+
function copyToClipboard(text) {
|
|
51
|
+
try {
|
|
52
|
+
if (process.platform === 'darwin') {
|
|
53
|
+
(0, child_process_1.spawnSync)('pbcopy', [], { input: text, encoding: 'utf-8' });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
else if (process.platform === 'win32') {
|
|
57
|
+
(0, child_process_1.spawnSync)('clip', [], { input: text, encoding: 'utf-8', shell: false });
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
(0, child_process_1.spawnSync)('xclip', ['-selection', 'clipboard'], { input: text, encoding: 'utf-8' });
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ─── 加载自定义 Skill(透明合并到 ALL_SKILLS) ───────────────────────────────
|
|
70
|
+
async function getActiveSkills() {
|
|
71
|
+
const { loadCustomSkills } = await Promise.resolve().then(() => __importStar(require('../loader/custom-skill-loader')));
|
|
72
|
+
const custom = loadCustomSkills(process.cwd());
|
|
73
|
+
return [...index_1.ALL_SKILLS, ...custom];
|
|
74
|
+
}
|
|
46
75
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
|
|
76
|
+
// 静默后台检查更新(不阻塞 CLI)
|
|
77
|
+
(0, update_check_1.checkForUpdates)(pkg.version, pkg.name);
|
|
47
78
|
const program = new commander_1.Command();
|
|
48
79
|
program
|
|
49
80
|
.name('ethan')
|
|
@@ -76,74 +107,51 @@ program
|
|
|
76
107
|
.description('将 Ethan 规则文件安装到当前项目')
|
|
77
108
|
.option('-p, --platform <platform>', '目标平台:cursor | copilot | cline | lingma | codebuddy | windsurf | zed | jetbrains | continue | claude-code | all', 'all')
|
|
78
109
|
.option('-d, --dir <dir>', '目标目录(默认为当前目录)', process.cwd())
|
|
110
|
+
.option('--lang <lang>', '输出语言:zh(默认)或 en', '')
|
|
111
|
+
.option('--auto-context', '自动检测项目技术栈并注入规则文件头部')
|
|
79
112
|
.action(async (options) => {
|
|
80
113
|
const { platform, dir } = options;
|
|
114
|
+
// 语言优先级:--lang 参数 > .ethanrc.json > 默认 zh
|
|
115
|
+
const config = (0, config_1.readConfig)(dir);
|
|
116
|
+
const lang = options.lang === 'en' || options.lang === 'zh'
|
|
117
|
+
? options.lang
|
|
118
|
+
: config.lang ?? 'zh';
|
|
119
|
+
// 自动上下文检测
|
|
120
|
+
let contextPrefix = '';
|
|
121
|
+
if (options.autoContext) {
|
|
122
|
+
const { detectProjectContext, formatContextBlock } = await Promise.resolve().then(() => __importStar(require('../context/detector')));
|
|
123
|
+
const projCtx = detectProjectContext(dir);
|
|
124
|
+
contextPrefix = formatContextBlock(projCtx, lang);
|
|
125
|
+
const detected = [
|
|
126
|
+
...projCtx.languages,
|
|
127
|
+
...projCtx.frameworks,
|
|
128
|
+
...projCtx.tools,
|
|
129
|
+
].join(', ');
|
|
130
|
+
console.log(`\n🔍 检测到技术栈:${detected || '未识别,仍可注入项目名称'}`);
|
|
131
|
+
}
|
|
81
132
|
const rulesDir = path.join(__dirname, '../../rules');
|
|
82
133
|
const platformMap = {
|
|
83
134
|
cursor: [
|
|
84
135
|
{
|
|
85
136
|
src: path.join(rulesDir, 'cursor/smart-flow.mdc'),
|
|
86
137
|
dest: path.join(dir, '.cursor/rules/smart-flow.mdc'),
|
|
138
|
+
platformKey: 'cursor-new',
|
|
87
139
|
},
|
|
88
140
|
{
|
|
89
141
|
src: path.join(rulesDir, 'cursor/.cursorrules'),
|
|
90
142
|
dest: path.join(dir, '.cursorrules'),
|
|
143
|
+
platformKey: 'cursor-old',
|
|
91
144
|
},
|
|
92
145
|
],
|
|
93
|
-
copilot: [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
],
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
dest: path.join(dir, '.clinerules'),
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
lingma: [
|
|
106
|
-
{
|
|
107
|
-
src: path.join(rulesDir, 'lingma/smart-flow.md'),
|
|
108
|
-
dest: path.join(dir, '.lingma/rules/smart-flow.md'),
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
codebuddy: [
|
|
112
|
-
{
|
|
113
|
-
src: path.join(rulesDir, 'codebuddy/CODEBUDDY.md'),
|
|
114
|
-
dest: path.join(dir, 'CODEBUDDY.md'),
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
windsurf: [
|
|
118
|
-
{
|
|
119
|
-
src: path.join(rulesDir, 'windsurf/.windsurf/rules/smart-flow.md'),
|
|
120
|
-
dest: path.join(dir, '.windsurf/rules/smart-flow.md'),
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
zed: [
|
|
124
|
-
{
|
|
125
|
-
src: path.join(rulesDir, 'zed/smart-flow.rules'),
|
|
126
|
-
dest: path.join(dir, 'smart-flow.rules'),
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
jetbrains: [
|
|
130
|
-
{
|
|
131
|
-
src: path.join(rulesDir, 'jetbrains/smart-flow.md'),
|
|
132
|
-
dest: path.join(dir, '.github/ai-instructions.md'),
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
continue: [
|
|
136
|
-
{
|
|
137
|
-
src: path.join(rulesDir, 'continue/.continuerules'),
|
|
138
|
-
dest: path.join(dir, '.continuerules'),
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
'claude-code': [
|
|
142
|
-
{
|
|
143
|
-
src: path.join(rulesDir, 'claude-code/CLAUDE.md'),
|
|
144
|
-
dest: path.join(dir, 'CLAUDE.md'),
|
|
145
|
-
},
|
|
146
|
-
],
|
|
146
|
+
copilot: [{ src: path.join(rulesDir, 'copilot/copilot-instructions.md'), dest: path.join(dir, '.github/copilot-instructions.md'), platformKey: 'copilot' }],
|
|
147
|
+
cline: [{ src: path.join(rulesDir, 'cline/.clinerules'), dest: path.join(dir, '.clinerules'), platformKey: 'cline' }],
|
|
148
|
+
lingma: [{ src: path.join(rulesDir, 'lingma/smart-flow.md'), dest: path.join(dir, '.lingma/rules/smart-flow.md'), platformKey: 'lingma' }],
|
|
149
|
+
codebuddy: [{ src: path.join(rulesDir, 'codebuddy/CODEBUDDY.md'), dest: path.join(dir, 'CODEBUDDY.md'), platformKey: 'codebuddy' }],
|
|
150
|
+
windsurf: [{ src: path.join(rulesDir, 'windsurf/.windsurf/rules/smart-flow.md'), dest: path.join(dir, '.windsurf/rules/smart-flow.md'), platformKey: 'windsurf' }],
|
|
151
|
+
zed: [{ src: path.join(rulesDir, 'zed/smart-flow.rules'), dest: path.join(dir, 'smart-flow.rules'), platformKey: 'zed' }],
|
|
152
|
+
jetbrains: [{ src: path.join(rulesDir, 'jetbrains/smart-flow.md'), dest: path.join(dir, '.github/ai-instructions.md'), platformKey: 'jetbrains' }],
|
|
153
|
+
continue: [{ src: path.join(rulesDir, 'continue/.continuerules'), dest: path.join(dir, '.continuerules'), platformKey: 'continue' }],
|
|
154
|
+
'claude-code': [{ src: path.join(rulesDir, 'claude-code/CLAUDE.md'), dest: path.join(dir, 'CLAUDE.md'), platformKey: 'claude-code' }],
|
|
147
155
|
};
|
|
148
156
|
const targets = platform === 'all'
|
|
149
157
|
? Object.values(platformMap).flat()
|
|
@@ -153,6 +161,41 @@ program
|
|
|
153
161
|
console.error(`Available: cursor | copilot | cline | lingma | codebuddy | windsurf | zed | jetbrains | continue | claude-code | all`);
|
|
154
162
|
process.exit(1);
|
|
155
163
|
}
|
|
164
|
+
// 英文模式:按需渲染模板写入,无需预构建文件
|
|
165
|
+
if (lang === 'en') {
|
|
166
|
+
const { renderMarkdown } = await Promise.resolve().then(() => __importStar(require('../templates/copilot-md.template')));
|
|
167
|
+
const { renderCursorMdc, renderCursorOld } = await Promise.resolve().then(() => __importStar(require('../templates/cursor-mdc.template')));
|
|
168
|
+
const now = new Date().toISOString();
|
|
169
|
+
let installed = 0;
|
|
170
|
+
for (const { dest, platformKey } of targets) {
|
|
171
|
+
const makeCtx = () => ({
|
|
172
|
+
platform: platformKey,
|
|
173
|
+
skills: index_1.ALL_SKILLS,
|
|
174
|
+
generatedAt: now,
|
|
175
|
+
version: pkg.version,
|
|
176
|
+
lang: 'en',
|
|
177
|
+
});
|
|
178
|
+
let content;
|
|
179
|
+
if (platformKey === 'cursor-new')
|
|
180
|
+
content = renderCursorMdc(makeCtx());
|
|
181
|
+
else if (platformKey === 'cursor-old')
|
|
182
|
+
content = renderCursorOld(makeCtx());
|
|
183
|
+
else
|
|
184
|
+
content = renderMarkdown(makeCtx());
|
|
185
|
+
if (contextPrefix)
|
|
186
|
+
content = contextPrefix + content;
|
|
187
|
+
const destDir = path.dirname(dest);
|
|
188
|
+
if (!fs.existsSync(destDir))
|
|
189
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
190
|
+
fs.writeFileSync(dest, content, 'utf-8');
|
|
191
|
+
console.log(` ✅ ${path.relative(dir, dest)} [en]`);
|
|
192
|
+
installed++;
|
|
193
|
+
}
|
|
194
|
+
console.log(`\nInstalled ${installed} rule file(s) to ${dir} (lang: en)`);
|
|
195
|
+
if (installed > 0)
|
|
196
|
+
console.log('Restart your AI editor to apply the new rules.');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
156
199
|
let installed = 0;
|
|
157
200
|
for (const { src, dest } of targets) {
|
|
158
201
|
if (!fs.existsSync(src)) {
|
|
@@ -163,7 +206,14 @@ program
|
|
|
163
206
|
if (!fs.existsSync(destDir)) {
|
|
164
207
|
fs.mkdirSync(destDir, { recursive: true });
|
|
165
208
|
}
|
|
166
|
-
|
|
209
|
+
// auto-context 模式:读取内容并注入上下文前缀
|
|
210
|
+
if (contextPrefix) {
|
|
211
|
+
const content = fs.readFileSync(src, 'utf-8');
|
|
212
|
+
fs.writeFileSync(dest, contextPrefix + content, 'utf-8');
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
fs.copyFileSync(src, dest);
|
|
216
|
+
}
|
|
167
217
|
console.log(` ✅ ${path.relative(dir, dest)}`);
|
|
168
218
|
installed++;
|
|
169
219
|
}
|
|
@@ -175,11 +225,13 @@ program
|
|
|
175
225
|
// ─── list 命令 ──────────────────────────────────────────────────────────────
|
|
176
226
|
program
|
|
177
227
|
.command('list')
|
|
178
|
-
.description('列出所有可用 Skill')
|
|
228
|
+
.description('列出所有可用 Skill(含自定义 Skill)')
|
|
179
229
|
.option('--json', '以 JSON 格式输出')
|
|
180
|
-
.action((options) => {
|
|
230
|
+
.action(async (options) => {
|
|
231
|
+
const skills = await getActiveSkills();
|
|
232
|
+
const customCount = skills.length - index_1.ALL_SKILLS.length;
|
|
181
233
|
if (options.json) {
|
|
182
|
-
console.log(JSON.stringify(
|
|
234
|
+
console.log(JSON.stringify(skills.map((s) => ({
|
|
183
235
|
id: s.id,
|
|
184
236
|
name: s.name,
|
|
185
237
|
nameEn: s.nameEn,
|
|
@@ -192,14 +244,125 @@ program
|
|
|
192
244
|
}
|
|
193
245
|
console.log('\nEthan Skills\n');
|
|
194
246
|
console.log('─'.repeat(60));
|
|
195
|
-
for (const skill of
|
|
247
|
+
for (const skill of skills) {
|
|
196
248
|
const categoryTag = skill.category ? ` [${skill.category}]` : '';
|
|
197
|
-
|
|
249
|
+
const customTag = skill.order >= 100 ? ' 🔧' : '';
|
|
250
|
+
console.log(`\n${skill.order}. ${skill.name} (${skill.id})${categoryTag}${customTag}`);
|
|
198
251
|
console.log(` ${skill.description}`);
|
|
199
252
|
console.log(` Triggers: ${skill.triggers.slice(0, 3).join(' | ')}`);
|
|
200
253
|
}
|
|
201
254
|
console.log('\n' + '─'.repeat(60));
|
|
202
|
-
console.log(`Total: ${index_1.ALL_SKILLS.length}
|
|
255
|
+
console.log(`Total: ${skills.length} skills (${index_1.ALL_SKILLS.length} built-in${customCount > 0 ? `, ${customCount} custom` : ''})`);
|
|
256
|
+
});
|
|
257
|
+
// ─── skill 子命令(自定义 Skill 管理) ──────────────────────────────────────
|
|
258
|
+
const skillCmd = program.command('skill').description('自定义 Skill 管理');
|
|
259
|
+
skillCmd
|
|
260
|
+
.command('new [name]')
|
|
261
|
+
.description('在 .ethan/skills/ 目录生成自定义 Skill YAML 模板')
|
|
262
|
+
.action(async (name) => {
|
|
263
|
+
const { generateSkillTemplate } = await Promise.resolve().then(() => __importStar(require('../loader/custom-skill-loader')));
|
|
264
|
+
const skillsDir = path.join(process.cwd(), '.ethan/skills');
|
|
265
|
+
if (!fs.existsSync(skillsDir))
|
|
266
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
267
|
+
const filename = name ? `${name}.yaml` : 'my-skill.yaml';
|
|
268
|
+
const filePath = path.join(skillsDir, filename);
|
|
269
|
+
if (fs.existsSync(filePath)) {
|
|
270
|
+
console.log(`⚠️ 文件已存在:${filePath}`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
fs.writeFileSync(filePath, generateSkillTemplate(), 'utf-8');
|
|
274
|
+
console.log(`\n✅ 已创建自定义 Skill 模板:${filePath}`);
|
|
275
|
+
console.log(' 编辑该文件后运行 ethan list 验证加载结果\n');
|
|
276
|
+
});
|
|
277
|
+
skillCmd
|
|
278
|
+
.command('list')
|
|
279
|
+
.description('列出当前项目的自定义 Skill')
|
|
280
|
+
.action(async () => {
|
|
281
|
+
const { loadCustomSkills } = await Promise.resolve().then(() => __importStar(require('../loader/custom-skill-loader')));
|
|
282
|
+
const custom = loadCustomSkills(process.cwd());
|
|
283
|
+
if (custom.length === 0) {
|
|
284
|
+
console.log('\n暂无自定义 Skill。运行 ethan skill new 创建一个。\n');
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
console.log(`\n🔧 自定义 Skill(${custom.length} 个)\n`);
|
|
288
|
+
for (const s of custom) {
|
|
289
|
+
console.log(` ${s.name} (${s.id})`);
|
|
290
|
+
console.log(` 触发词:${s.triggers.slice(0, 3).join(' | ')}`);
|
|
291
|
+
console.log('');
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
// ─── plugin 命令(Skill Marketplace) ───────────────────────────────────────
|
|
295
|
+
const OFFICIAL_PLUGINS = [
|
|
296
|
+
{ name: 'ethan-plugin-deploy', description: '部署工作流 Skill(CI/CD、Docker、K8s)', author: 'community' },
|
|
297
|
+
{ name: 'ethan-plugin-prd', description: 'PRD 文档生成 Skill', author: 'community' },
|
|
298
|
+
{ name: 'ethan-plugin-api-design', description: 'RESTful/GraphQL API 设计 Skill', author: 'community' },
|
|
299
|
+
{ name: 'ethan-plugin-security', description: '安全审查 Skill(OWASP、依赖检查)', author: 'community' },
|
|
300
|
+
];
|
|
301
|
+
const pluginCmd = program.command('plugin').description('Skill 插件市场管理');
|
|
302
|
+
pluginCmd
|
|
303
|
+
.command('list')
|
|
304
|
+
.description('列出官方推荐插件及已安装插件')
|
|
305
|
+
.action(() => {
|
|
306
|
+
const config = (0, config_1.readConfig)(process.cwd());
|
|
307
|
+
const installed = config.plugins ?? [];
|
|
308
|
+
console.log('\n📦 Ethan 插件市场\n');
|
|
309
|
+
console.log('─'.repeat(60));
|
|
310
|
+
console.log('\n[官方推荐插件]\n');
|
|
311
|
+
for (const p of OFFICIAL_PLUGINS) {
|
|
312
|
+
const tag = installed.includes(p.name) ? ' ✅ 已安装' : '';
|
|
313
|
+
console.log(` ${p.name}${tag}`);
|
|
314
|
+
console.log(` ${p.description}\n`);
|
|
315
|
+
}
|
|
316
|
+
if (installed.length > 0) {
|
|
317
|
+
console.log('[已安装插件]\n');
|
|
318
|
+
for (const name of installed) {
|
|
319
|
+
console.log(` ${name}`);
|
|
320
|
+
}
|
|
321
|
+
console.log('');
|
|
322
|
+
}
|
|
323
|
+
console.log('─'.repeat(60));
|
|
324
|
+
console.log('\n安装插件:ethan plugin install <plugin-name>');
|
|
325
|
+
console.log('卸载插件:ethan plugin remove <plugin-name>\n');
|
|
326
|
+
});
|
|
327
|
+
pluginCmd
|
|
328
|
+
.command('install <name>')
|
|
329
|
+
.description('从 npm 安装 Skill 插件包')
|
|
330
|
+
.action(async (name) => {
|
|
331
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
332
|
+
console.log(`\n📦 安装插件:${name}\n`);
|
|
333
|
+
try {
|
|
334
|
+
execSync(`npm install ${name}`, { stdio: 'inherit', cwd: process.cwd() });
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
console.error(`\n❌ 安装失败,请确认包名正确且已发布到 npm\n`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
// 注册到 .ethanrc.json
|
|
341
|
+
const config = (0, config_1.readConfig)(process.cwd());
|
|
342
|
+
const plugins = config.plugins ?? [];
|
|
343
|
+
if (!plugins.includes(name)) {
|
|
344
|
+
plugins.push(name);
|
|
345
|
+
(0, config_1.writeConfig)({ ...config, plugins }, process.cwd());
|
|
346
|
+
}
|
|
347
|
+
console.log(`\n✅ 插件 ${name} 安装完成`);
|
|
348
|
+
console.log(` 运行 ethan list 可查看新增 Skill\n`);
|
|
349
|
+
});
|
|
350
|
+
pluginCmd
|
|
351
|
+
.command('remove <name>')
|
|
352
|
+
.description('卸载 Skill 插件包')
|
|
353
|
+
.action(async (name) => {
|
|
354
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
355
|
+
console.log(`\n🗑 卸载插件:${name}\n`);
|
|
356
|
+
try {
|
|
357
|
+
execSync(`npm uninstall ${name}`, { stdio: 'inherit', cwd: process.cwd() });
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
console.warn(` ⚠️ npm uninstall 失败,仍会从配置中移除`);
|
|
361
|
+
}
|
|
362
|
+
const config = (0, config_1.readConfig)(process.cwd());
|
|
363
|
+
const plugins = (config.plugins ?? []).filter((p) => p !== name);
|
|
364
|
+
(0, config_1.writeConfig)({ ...config, plugins }, process.cwd());
|
|
365
|
+
console.log(`\n✅ 插件 ${name} 已卸载\n`);
|
|
203
366
|
});
|
|
204
367
|
// ─── mcp 命令 ───────────────────────────────────────────────────────────────
|
|
205
368
|
program
|
|
@@ -448,5 +611,386 @@ program
|
|
|
448
611
|
console.log('─'.repeat(60));
|
|
449
612
|
console.log(` Total executions: ${total}\n`);
|
|
450
613
|
});
|
|
614
|
+
// ─── serve 命令(Web UI Dashboard) ─────────────────────────────────────────
|
|
615
|
+
program
|
|
616
|
+
.command('serve')
|
|
617
|
+
.description('启动本地 Web UI Dashboard(默认端口 3000)')
|
|
618
|
+
.option('--port <port>', '监听端口', '3000')
|
|
619
|
+
.action(async (options) => {
|
|
620
|
+
const port = parseInt(options.port, 10) || 3000;
|
|
621
|
+
const { startDashboardServer } = await Promise.resolve().then(() => __importStar(require('../server/dashboard')));
|
|
622
|
+
startDashboardServer(port, process.cwd());
|
|
623
|
+
});
|
|
624
|
+
// ─── run 命令(交互式向导) ──────────────────────────────────────────────────
|
|
625
|
+
program
|
|
626
|
+
.command('run')
|
|
627
|
+
.description('交互式 Skill 执行向导:选择 Skill → 填写上下文 → 生成提示词')
|
|
628
|
+
.action(async () => {
|
|
629
|
+
const clack = await Promise.resolve().then(() => __importStar(require('@clack/prompts')));
|
|
630
|
+
const { intro, outro, select, text, isCancel, cancel, note, spinner } = clack;
|
|
631
|
+
// 读取项目配置
|
|
632
|
+
const config = (0, config_1.readConfig)(process.cwd());
|
|
633
|
+
const isEn = config.lang === 'en';
|
|
634
|
+
const activeSkills = index_1.ALL_SKILLS.filter((s) => !config.disabledSkills?.includes(s.id));
|
|
635
|
+
intro(isEn ? '✨ Ethan - Skill Wizard' : '✨ Ethan - 技能执行向导');
|
|
636
|
+
// 按分类分组展示
|
|
637
|
+
const categoryLabel = {
|
|
638
|
+
'需求侧': isEn ? '[Requirements]' : '[需求侧]',
|
|
639
|
+
'执行侧': isEn ? '[Execution]' : '[执行侧]',
|
|
640
|
+
'跟踪侧': isEn ? '[Tracking]' : '[跟踪侧]',
|
|
641
|
+
'输出侧': isEn ? '[Output]' : '[输出侧]',
|
|
642
|
+
'质量侧': isEn ? '[Quality]' : '[质量侧]',
|
|
643
|
+
};
|
|
644
|
+
const skillOptions = activeSkills.map((s) => ({
|
|
645
|
+
value: s.id,
|
|
646
|
+
label: isEn
|
|
647
|
+
? `${s.nameEn.replace(/_/g, ' ')} ${categoryLabel[s.category ?? ''] ?? ''}`
|
|
648
|
+
: `${s.name} ${categoryLabel[s.category ?? ''] ?? ''}`,
|
|
649
|
+
hint: isEn ? (s.descriptionEn ?? s.description) : s.description,
|
|
650
|
+
}));
|
|
651
|
+
const skillId = await select({
|
|
652
|
+
message: isEn ? 'Which Skill do you want to run?' : '选择要执行的 Skill:',
|
|
653
|
+
options: skillOptions,
|
|
654
|
+
});
|
|
655
|
+
if (isCancel(skillId)) {
|
|
656
|
+
cancel(isEn ? 'Cancelled.' : '已取消');
|
|
657
|
+
process.exit(0);
|
|
658
|
+
}
|
|
659
|
+
const skill = index_1.ALL_SKILLS.find((s) => s.id === skillId);
|
|
660
|
+
const context = await text({
|
|
661
|
+
message: isEn
|
|
662
|
+
? `Describe your context for "${skill.nameEn.replace(/_/g, ' ')}":`
|
|
663
|
+
: `请输入「${skill.name}」的上下文描述:`,
|
|
664
|
+
placeholder: isEn
|
|
665
|
+
? 'e.g. Build a user login feature with JWT auth'
|
|
666
|
+
: '例如:实现用户登录功能,支持 JWT 认证',
|
|
667
|
+
validate: (v) => {
|
|
668
|
+
if (!v?.trim())
|
|
669
|
+
return isEn ? 'Context cannot be empty' : '上下文不能为空';
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
if (isCancel(context)) {
|
|
673
|
+
cancel(isEn ? 'Cancelled.' : '已取消');
|
|
674
|
+
process.exit(0);
|
|
675
|
+
}
|
|
676
|
+
const s = spinner();
|
|
677
|
+
s.start(isEn ? 'Generating prompt...' : '生成提示词中...');
|
|
678
|
+
// 组装完整提示词
|
|
679
|
+
const stepsText = skill.steps
|
|
680
|
+
.map((step, i) => `${i + 1}. ${step.title.replace(/^\d+\.\s*/, '')}`)
|
|
681
|
+
.join('\n');
|
|
682
|
+
const prompt = [
|
|
683
|
+
`## ${isEn ? skill.nameEn.replace(/_/g, ' ') : skill.name}`,
|
|
684
|
+
'',
|
|
685
|
+
`**${isEn ? 'Context' : '上下文'}**: ${context}`,
|
|
686
|
+
'',
|
|
687
|
+
`**${isEn ? 'Goal' : '目标'}**: ${isEn ? (skill.descriptionEn ?? skill.description) : skill.description}`,
|
|
688
|
+
'',
|
|
689
|
+
`**${isEn ? 'Please follow these steps' : '请按以下步骤执行'}**:`,
|
|
690
|
+
stepsText,
|
|
691
|
+
'',
|
|
692
|
+
`**${isEn ? 'Output format' : '输出格式'}**: ${skill.outputFormat}`,
|
|
693
|
+
].join('\n');
|
|
694
|
+
s.stop(isEn ? 'Prompt ready!' : '提示词已生成!');
|
|
695
|
+
note(prompt, isEn ? 'Your Prompt' : '你的提示词');
|
|
696
|
+
// 尝试复制到剪贴板
|
|
697
|
+
if (copyToClipboard(prompt)) {
|
|
698
|
+
outro(isEn
|
|
699
|
+
? '✅ Prompt copied to clipboard! Paste it into your AI editor.'
|
|
700
|
+
: '✅ 提示词已复制到剪贴板!粘贴到你的 AI 编辑器中使用。');
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
outro(isEn
|
|
704
|
+
? '✅ Done! Copy the prompt above and paste it into your AI editor.'
|
|
705
|
+
: '✅ 完成!请复制上方提示词,粘贴到你的 AI 编辑器中使用。');
|
|
706
|
+
}
|
|
707
|
+
// 记录使用统计
|
|
708
|
+
const stats = readStats();
|
|
709
|
+
stats[skill.id] = (stats[skill.id] || 0) + 1;
|
|
710
|
+
writeStats(stats);
|
|
711
|
+
});
|
|
712
|
+
// ─── init 命令 ───────────────────────────────────────────────────────────────
|
|
713
|
+
program
|
|
714
|
+
.command('init')
|
|
715
|
+
.description('在当前项目生成 .ethanrc.json 配置文件')
|
|
716
|
+
.option('-d, --dir <dir>', '目标目录(默认为当前目录)', process.cwd())
|
|
717
|
+
.action(async (options) => {
|
|
718
|
+
const { dir } = options;
|
|
719
|
+
const configPath = (0, config_1.getConfigPath)(dir);
|
|
720
|
+
if (fs.existsSync(configPath)) {
|
|
721
|
+
const existing = (0, config_1.readConfig)(dir);
|
|
722
|
+
console.log(`\n⚠️ ${configPath} 已存在,当前配置:`);
|
|
723
|
+
console.log(JSON.stringify(existing, null, 2));
|
|
724
|
+
console.log('\n如需修改,请直接编辑该文件。\n');
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
// 使用 readline 简单交互
|
|
728
|
+
const readline = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
729
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
730
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
731
|
+
console.log('\n🚀 Ethan 项目配置向导\n');
|
|
732
|
+
console.log('─'.repeat(50));
|
|
733
|
+
const langInput = await ask('\n输出语言 [zh/en](默认 zh): ');
|
|
734
|
+
const lang = langInput === 'en' ? 'en' : 'zh';
|
|
735
|
+
const disabledInput = await ask('\n要禁用的 Skill ID(逗号分隔,留空跳过)\n可选: ' +
|
|
736
|
+
index_1.ALL_SKILLS.map((s) => s.id).join(', ') +
|
|
737
|
+
'\n> ');
|
|
738
|
+
const disabledSkills = disabledInput
|
|
739
|
+
? disabledInput
|
|
740
|
+
.split(',')
|
|
741
|
+
.map((s) => s.trim())
|
|
742
|
+
.filter((s) => index_1.ALL_SKILLS.some((sk) => sk.id === s))
|
|
743
|
+
: [];
|
|
744
|
+
const customTriggersInput = await ask('\n自定义触发词(格式: 缩写=skill-id,逗号分隔,留空跳过)\n例如: cr=code-review,fix=debug\n> ');
|
|
745
|
+
const customTriggers = {};
|
|
746
|
+
if (customTriggersInput) {
|
|
747
|
+
for (const pair of customTriggersInput.split(',')) {
|
|
748
|
+
const [key, val] = pair.split('=').map((s) => s.trim());
|
|
749
|
+
if (key && val && index_1.ALL_SKILLS.some((sk) => sk.id === val)) {
|
|
750
|
+
customTriggers[key] = val;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
rl.close();
|
|
755
|
+
const config = {
|
|
756
|
+
lang,
|
|
757
|
+
...(disabledSkills.length > 0 ? { disabledSkills } : {}),
|
|
758
|
+
...(Object.keys(customTriggers).length > 0 ? { customTriggers } : {}),
|
|
759
|
+
};
|
|
760
|
+
(0, config_1.writeConfig)(config, dir);
|
|
761
|
+
console.log('\n✅ 已生成 .ethanrc.json:');
|
|
762
|
+
console.log(JSON.stringify(config, null, 2));
|
|
763
|
+
console.log(`\n文件路径:${configPath}`);
|
|
764
|
+
console.log('\n💡 提示:现在运行 ethan install --platform <platform> 将使用此配置\n');
|
|
765
|
+
});
|
|
766
|
+
// ─── workflow 命令(有状态一键工作流) ──────────────────────────────────────
|
|
767
|
+
const workflowCmd = program.command('workflow').description('有状态工作流执行:一键推进各阶段任务');
|
|
768
|
+
workflowCmd
|
|
769
|
+
.command('start [pipelineId]')
|
|
770
|
+
.description('启动工作流会话(默认 dev-workflow),输出第一步提示词')
|
|
771
|
+
.option('-c, --context <context>', '初始任务上下文', '')
|
|
772
|
+
.action(async (pipelineId, options) => {
|
|
773
|
+
const { loadSession, createSession, buildStepPrompt, calcProgress, } = await Promise.resolve().then(() => __importStar(require('../workflow/state')));
|
|
774
|
+
const { resolvePipeline, PIPELINES } = await Promise.resolve().then(() => __importStar(require('../skills/pipeline')));
|
|
775
|
+
// 检查是否已有进行中的 session
|
|
776
|
+
const existing = loadSession(process.cwd());
|
|
777
|
+
if (existing && !existing.completed) {
|
|
778
|
+
console.log('\n⚠️ 已有进行中的工作流:');
|
|
779
|
+
console.log(` Pipeline: ${existing.pipelineName}`);
|
|
780
|
+
console.log(` 进度: ${calcProgress(existing)}%`);
|
|
781
|
+
console.log('\n💡 使用 ethan workflow status 查看进度');
|
|
782
|
+
console.log(' 使用 ethan workflow reset 重置后再启动新工作流\n');
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const id = pipelineId ?? 'dev-workflow';
|
|
786
|
+
const resolved = resolvePipeline(id);
|
|
787
|
+
if (!resolved) {
|
|
788
|
+
console.error(`Unknown pipeline: ${id}`);
|
|
789
|
+
console.error(`Available: ${PIPELINES.map((p) => p.id).join(' | ')}`);
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
const { pipeline, skills } = resolved;
|
|
793
|
+
// 交互式获取上下文(如果未传)
|
|
794
|
+
let context = options.context.trim();
|
|
795
|
+
if (!context) {
|
|
796
|
+
const readline = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
797
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
798
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
799
|
+
console.log(`\n🚀 启动工作流:${pipeline.name}`);
|
|
800
|
+
console.log(` ${pipeline.description}\n`);
|
|
801
|
+
context = await ask('请描述你的任务背景(例如:实现用户登录功能,支持 JWT 认证):\n> ');
|
|
802
|
+
rl.close();
|
|
803
|
+
if (!context) {
|
|
804
|
+
console.error('\n❌ 任务上下文不能为空\n');
|
|
805
|
+
process.exit(1);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const session = createSession(pipeline, context, process.cwd());
|
|
809
|
+
const firstStep = session.steps[0];
|
|
810
|
+
const firstSkill = skills[0];
|
|
811
|
+
console.log(`\n🚀 工作流已启动:${pipeline.name}`);
|
|
812
|
+
console.log(` ID: ${session.id}`);
|
|
813
|
+
console.log(` 共 ${session.steps.length} 步\n`);
|
|
814
|
+
console.log('─'.repeat(60));
|
|
815
|
+
const prompt = buildStepPrompt(session, firstStep, firstSkill);
|
|
816
|
+
console.log('\n' + prompt + '\n');
|
|
817
|
+
console.log('─'.repeat(60));
|
|
818
|
+
// 复制到剪贴板
|
|
819
|
+
if (copyToClipboard(prompt)) {
|
|
820
|
+
console.log('\n✅ 提示词已复制到剪贴板!粘贴到你的 AI 编辑器中执行。');
|
|
821
|
+
}
|
|
822
|
+
console.log(`\n💡 完成本步后,运行:ethan workflow done "你的本步摘要"\n`);
|
|
823
|
+
// 记录使用统计
|
|
824
|
+
const stats = readStats();
|
|
825
|
+
stats[firstSkill.id] = (stats[firstSkill.id] || 0) + 1;
|
|
826
|
+
writeStats(stats);
|
|
827
|
+
});
|
|
828
|
+
workflowCmd
|
|
829
|
+
.command('done [summary]')
|
|
830
|
+
.description('完成当前步骤,传入本步摘要,自动推进到下一步')
|
|
831
|
+
.action(async (summary) => {
|
|
832
|
+
const { loadSession, markStepDone, buildStepPrompt, getCurrentStep, getCurrentStepIndex, calcProgress, } = await Promise.resolve().then(() => __importStar(require('../workflow/state')));
|
|
833
|
+
const { resolvePipeline } = await Promise.resolve().then(() => __importStar(require('../skills/pipeline')));
|
|
834
|
+
const session = loadSession(process.cwd());
|
|
835
|
+
if (!session) {
|
|
836
|
+
console.error('\n❌ 未找到进行中的工作流。运行 ethan workflow start 启动。\n');
|
|
837
|
+
process.exit(1);
|
|
838
|
+
}
|
|
839
|
+
if (session.completed) {
|
|
840
|
+
console.log('\n🎉 工作流已全部完成!运行 ethan workflow reset 开始新工作流。\n');
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
const currentStep = getCurrentStep(session);
|
|
844
|
+
if (!currentStep) {
|
|
845
|
+
console.error('\n❌ 未找到当前步骤,工作流状态异常。\n');
|
|
846
|
+
process.exit(1);
|
|
847
|
+
}
|
|
848
|
+
// 获取摘要(命令行参数 or 交互输入)
|
|
849
|
+
let stepSummary = summary?.trim() ?? '';
|
|
850
|
+
if (!stepSummary) {
|
|
851
|
+
const readline = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
852
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
853
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
|
|
854
|
+
const currentIdx = getCurrentStepIndex(session);
|
|
855
|
+
console.log(`\n✅ 完成第 ${currentIdx + 1} 步:${currentStep.skillId}`);
|
|
856
|
+
stepSummary = await ask('请输入本步执行摘要(将作为下一步的上下文):\n> ');
|
|
857
|
+
rl.close();
|
|
858
|
+
if (!stepSummary) {
|
|
859
|
+
console.error('\n❌ 摘要不能为空\n');
|
|
860
|
+
process.exit(1);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
const nextStep = markStepDone(session, stepSummary, process.cwd());
|
|
864
|
+
const progress = calcProgress(session);
|
|
865
|
+
if (!nextStep) {
|
|
866
|
+
console.log('\n🎉 恭喜!工作流全部完成!');
|
|
867
|
+
console.log(` Pipeline: ${session.pipelineName}`);
|
|
868
|
+
console.log(` 进度: ${progress}%`);
|
|
869
|
+
console.log(`\n📄 运行 ethan workflow status 查看完整报告`);
|
|
870
|
+
console.log(` 运行 ethan workflow reset 开始新工作流\n`);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
// 加载下一步 Skill 信息
|
|
874
|
+
const resolved = resolvePipeline(session.pipelineId);
|
|
875
|
+
if (!resolved) {
|
|
876
|
+
console.error('\n❌ 无法加载 Pipeline 信息\n');
|
|
877
|
+
process.exit(1);
|
|
878
|
+
}
|
|
879
|
+
const nextSkill = resolved.skills.find((s) => s.id === nextStep.skillId);
|
|
880
|
+
if (!nextSkill) {
|
|
881
|
+
console.error(`\n❌ 未找到 Skill:${nextStep.skillId}\n`);
|
|
882
|
+
process.exit(1);
|
|
883
|
+
}
|
|
884
|
+
console.log(`\n✅ 已完成 ${session.steps.filter((s) => s.status === 'done').length}/${session.steps.length} 步(${progress}%)`);
|
|
885
|
+
console.log('─'.repeat(60));
|
|
886
|
+
const prompt = buildStepPrompt(session, nextStep, nextSkill);
|
|
887
|
+
console.log('\n' + prompt + '\n');
|
|
888
|
+
console.log('─'.repeat(60));
|
|
889
|
+
// 复制到剪贴板
|
|
890
|
+
if (copyToClipboard(prompt)) {
|
|
891
|
+
console.log('\n✅ 下一步提示词已复制到剪贴板!');
|
|
892
|
+
}
|
|
893
|
+
console.log(`\n💡 完成本步后,运行:ethan workflow done "你的本步摘要"\n`);
|
|
894
|
+
// 记录使用统计
|
|
895
|
+
const stats = readStats();
|
|
896
|
+
stats[nextSkill.id] = (stats[nextSkill.id] || 0) + 1;
|
|
897
|
+
writeStats(stats);
|
|
898
|
+
});
|
|
899
|
+
workflowCmd
|
|
900
|
+
.command('status')
|
|
901
|
+
.description('查看当前工作流进度看板')
|
|
902
|
+
.action(async () => {
|
|
903
|
+
const { loadSession, getCurrentStepIndex, calcProgress, } = await Promise.resolve().then(() => __importStar(require('../workflow/state')));
|
|
904
|
+
const session = loadSession(process.cwd());
|
|
905
|
+
if (!session) {
|
|
906
|
+
console.log('\n📋 当前目录暂无工作流会话。\n');
|
|
907
|
+
console.log(' ��行 ethan workflow start 启动新工作流\n');
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
const progress = calcProgress(session);
|
|
911
|
+
const currentIdx = session.completed ? -1 : getCurrentStepIndex(session);
|
|
912
|
+
const statusIcon = {
|
|
913
|
+
'done': '✅',
|
|
914
|
+
'in-progress': '▶️ ',
|
|
915
|
+
'pending': '⬜',
|
|
916
|
+
'skipped': '⏭️ ',
|
|
917
|
+
};
|
|
918
|
+
console.log('\n📋 工作流进度看板');
|
|
919
|
+
console.log('─'.repeat(60));
|
|
920
|
+
console.log(` Pipeline : ${session.pipelineName}`);
|
|
921
|
+
console.log(` Session : ${session.id}`);
|
|
922
|
+
console.log(` 创建时间 : ${session.createdAt.slice(0, 19).replace('T', ' ')}`);
|
|
923
|
+
console.log(` 更新时间 : ${session.updatedAt.slice(0, 19).replace('T', ' ')}`);
|
|
924
|
+
console.log(` 总进度 : [${'█'.repeat(Math.round(progress / 5))}${'░'.repeat(20 - Math.round(progress / 5))}] ${progress}%`);
|
|
925
|
+
console.log(` 状态 : ${session.completed ? '🎉 已完成' : `第 ${currentIdx + 1}/${session.steps.length} 步进行中`}`);
|
|
926
|
+
console.log('\n[步骤明细]\n');
|
|
927
|
+
for (let i = 0; i < session.steps.length; i++) {
|
|
928
|
+
const step = session.steps[i];
|
|
929
|
+
const icon = statusIcon[step.status] ?? '❓';
|
|
930
|
+
const isCurrent = i === currentIdx;
|
|
931
|
+
const marker = isCurrent ? ' ◀ 当前' : '';
|
|
932
|
+
console.log(` ${icon} ${i + 1}. ${step.skillId}${marker}`);
|
|
933
|
+
if (step.summary) {
|
|
934
|
+
const preview = step.summary.length > 80 ? step.summary.slice(0, 80) + '…' : step.summary;
|
|
935
|
+
console.log(` 摘要:${preview}`);
|
|
936
|
+
}
|
|
937
|
+
if (step.completedAt) {
|
|
938
|
+
console.log(` 完成:${step.completedAt.slice(0, 19).replace('T', ' ')}`);
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
console.log('\n' + '─'.repeat(60));
|
|
942
|
+
if (session.completed) {
|
|
943
|
+
console.log('\n🎉 工作流已全部完成!运行 ethan workflow reset 开始新工作流\n');
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
console.log(`\n💡 当前任务背景:${session.initialContext}`);
|
|
947
|
+
console.log(` 完成当前步骤后运行:ethan workflow done "你的摘要"\n`);
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
workflowCmd
|
|
951
|
+
.command('reset')
|
|
952
|
+
.description('清除当前工作流会话(不可恢复)')
|
|
953
|
+
.action(async () => {
|
|
954
|
+
const { loadSession, deleteSession, calcProgress } = await Promise.resolve().then(() => __importStar(require('../workflow/state')));
|
|
955
|
+
const session = loadSession(process.cwd());
|
|
956
|
+
if (!session) {
|
|
957
|
+
console.log('\n📋 当前目录无工作流会话,无需重置。\n');
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
const progress = calcProgress(session);
|
|
961
|
+
// 简单确认
|
|
962
|
+
const readline = await Promise.resolve().then(() => __importStar(require('readline')));
|
|
963
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
964
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim().toLowerCase())));
|
|
965
|
+
console.log(`\n⚠️ 即将重置工作流:${session.pipelineName}(进度 ${progress}%)`);
|
|
966
|
+
const confirm = await ask('确认重置?(y/N): ');
|
|
967
|
+
rl.close();
|
|
968
|
+
if (confirm !== 'y' && confirm !== 'yes') {
|
|
969
|
+
console.log('\n已取消重置。\n');
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
deleteSession(process.cwd());
|
|
973
|
+
console.log('\n✅ 工作流已重置。运行 ethan workflow start 开始新工作流。\n');
|
|
974
|
+
});
|
|
975
|
+
workflowCmd
|
|
976
|
+
.command('list')
|
|
977
|
+
.description('列出所有可用的工作流 Pipeline')
|
|
978
|
+
.action(async () => {
|
|
979
|
+
const { PIPELINES } = await Promise.resolve().then(() => __importStar(require('../skills/pipeline')));
|
|
980
|
+
const { loadSession, calcProgress } = await Promise.resolve().then(() => __importStar(require('../workflow/state')));
|
|
981
|
+
const current = loadSession(process.cwd());
|
|
982
|
+
console.log('\n🔄 可用工作流\n');
|
|
983
|
+
console.log('─'.repeat(60));
|
|
984
|
+
for (const p of PIPELINES) {
|
|
985
|
+
const isCurrent = current?.pipelineId === p.id && !current.completed;
|
|
986
|
+
const tag = isCurrent ? ` ◀ 进行中(${calcProgress(current)}%)` : '';
|
|
987
|
+
console.log(`\n ${p.id}${tag}`);
|
|
988
|
+
console.log(` 名称:${p.name}`);
|
|
989
|
+
console.log(` 描述:${p.description}`);
|
|
990
|
+
console.log(` 步骤:${p.skillIds.join(' → ')}`);
|
|
991
|
+
}
|
|
992
|
+
console.log('\n' + '─'.repeat(60));
|
|
993
|
+
console.log('\n启动工作流:ethan workflow start <pipeline-id> -c "任务描述"\n');
|
|
994
|
+
});
|
|
451
995
|
program.parse(process.argv);
|
|
452
996
|
//# sourceMappingURL=index.js.map
|