ethan-skill 1.0.0 → 1.1.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.
Files changed (136) hide show
  1. package/dist/cli/config.d.ts +26 -0
  2. package/dist/cli/config.d.ts.map +1 -0
  3. package/dist/cli/config.js +74 -0
  4. package/dist/cli/config.js.map +1 -0
  5. package/dist/cli/index.d.ts +1 -1
  6. package/dist/cli/index.js +363 -62
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/update-check.d.ts +11 -0
  9. package/dist/cli/update-check.d.ts.map +1 -0
  10. package/dist/cli/update-check.js +128 -0
  11. package/dist/cli/update-check.js.map +1 -0
  12. package/dist/context/detector.d.ts +25 -0
  13. package/dist/context/detector.d.ts.map +1 -0
  14. package/dist/context/detector.js +230 -0
  15. package/dist/context/detector.js.map +1 -0
  16. package/dist/ethan-skill-1.0.0.vsix +0 -0
  17. package/dist/ethan-skill-1.1.0.vsix +0 -0
  18. package/dist/loader/custom-skill-loader.d.ts +15 -0
  19. package/dist/loader/custom-skill-loader.d.ts.map +1 -0
  20. package/dist/loader/custom-skill-loader.js +158 -0
  21. package/dist/loader/custom-skill-loader.js.map +1 -0
  22. package/dist/server/dashboard.d.ts +2 -0
  23. package/dist/server/dashboard.d.ts.map +1 -0
  24. package/dist/server/dashboard.js +272 -0
  25. package/dist/server/dashboard.js.map +1 -0
  26. package/dist/skills/01-requirement.d.ts.map +1 -1
  27. package/dist/skills/01-requirement.js +4 -0
  28. package/dist/skills/01-requirement.js.map +1 -1
  29. package/dist/skills/02-task-breakdown.d.ts.map +1 -1
  30. package/dist/skills/02-task-breakdown.js +4 -0
  31. package/dist/skills/02-task-breakdown.js.map +1 -1
  32. package/dist/skills/03-design.d.ts.map +1 -1
  33. package/dist/skills/03-design.js +4 -0
  34. package/dist/skills/03-design.js.map +1 -1
  35. package/dist/skills/04-implementation.d.ts.map +1 -1
  36. package/dist/skills/04-implementation.js +4 -0
  37. package/dist/skills/04-implementation.js.map +1 -1
  38. package/dist/skills/05-progress-tracking.d.ts.map +1 -1
  39. package/dist/skills/05-progress-tracking.js +4 -0
  40. package/dist/skills/05-progress-tracking.js.map +1 -1
  41. package/dist/skills/06-task-report.d.ts.map +1 -1
  42. package/dist/skills/06-task-report.js +4 -0
  43. package/dist/skills/06-task-report.js.map +1 -1
  44. package/dist/skills/07-weekly-report.d.ts.map +1 -1
  45. package/dist/skills/07-weekly-report.js +3 -0
  46. package/dist/skills/07-weekly-report.js.map +1 -1
  47. package/dist/skills/08-code-review.d.ts.map +1 -1
  48. package/dist/skills/08-code-review.js +2 -0
  49. package/dist/skills/08-code-review.js.map +1 -1
  50. package/dist/skills/09-debug.d.ts.map +1 -1
  51. package/dist/skills/09-debug.js +3 -0
  52. package/dist/skills/09-debug.js.map +1 -1
  53. package/dist/skills/10-tech-research.d.ts.map +1 -1
  54. package/dist/skills/10-tech-research.js +4 -0
  55. package/dist/skills/10-tech-research.js.map +1 -1
  56. package/dist/skills/types.d.ts +4 -0
  57. package/dist/skills/types.d.ts.map +1 -1
  58. package/dist/templates/copilot-md.template.d.ts.map +1 -1
  59. package/dist/templates/copilot-md.template.js +107 -26
  60. package/dist/templates/copilot-md.template.js.map +1 -1
  61. package/dist/vscode/router/trigger-router.d.ts +16 -0
  62. package/dist/vscode/router/trigger-router.d.ts.map +1 -0
  63. package/dist/vscode/router/trigger-router.js +45 -0
  64. package/dist/vscode/router/trigger-router.js.map +1 -0
  65. package/dist/vscode/skills/01-requirement.d.ts +3 -0
  66. package/dist/vscode/skills/01-requirement.d.ts.map +1 -0
  67. package/dist/vscode/skills/01-requirement.js +104 -0
  68. package/dist/vscode/skills/01-requirement.js.map +1 -0
  69. package/dist/vscode/skills/02-task-breakdown.d.ts +3 -0
  70. package/dist/vscode/skills/02-task-breakdown.d.ts.map +1 -0
  71. package/dist/vscode/skills/02-task-breakdown.js +86 -0
  72. package/dist/vscode/skills/02-task-breakdown.js.map +1 -0
  73. package/dist/vscode/skills/03-design.d.ts +3 -0
  74. package/dist/vscode/skills/03-design.d.ts.map +1 -0
  75. package/dist/vscode/skills/03-design.js +84 -0
  76. package/dist/vscode/skills/03-design.js.map +1 -0
  77. package/dist/vscode/skills/04-implementation.d.ts +3 -0
  78. package/dist/vscode/skills/04-implementation.d.ts.map +1 -0
  79. package/dist/vscode/skills/04-implementation.js +81 -0
  80. package/dist/vscode/skills/04-implementation.js.map +1 -0
  81. package/dist/vscode/skills/05-progress-tracking.d.ts +3 -0
  82. package/dist/vscode/skills/05-progress-tracking.d.ts.map +1 -0
  83. package/dist/vscode/skills/05-progress-tracking.js +82 -0
  84. package/dist/vscode/skills/05-progress-tracking.js.map +1 -0
  85. package/dist/vscode/skills/06-task-report.d.ts +3 -0
  86. package/dist/vscode/skills/06-task-report.d.ts.map +1 -0
  87. package/dist/vscode/skills/06-task-report.js +79 -0
  88. package/dist/vscode/skills/06-task-report.js.map +1 -0
  89. package/dist/vscode/skills/07-weekly-report.d.ts +3 -0
  90. package/dist/vscode/skills/07-weekly-report.d.ts.map +1 -0
  91. package/dist/vscode/skills/07-weekly-report.js +104 -0
  92. package/dist/vscode/skills/07-weekly-report.js.map +1 -0
  93. package/dist/vscode/skills/08-code-review.d.ts +3 -0
  94. package/dist/vscode/skills/08-code-review.d.ts.map +1 -0
  95. package/dist/vscode/skills/08-code-review.js +138 -0
  96. package/dist/vscode/skills/08-code-review.js.map +1 -0
  97. package/dist/vscode/skills/09-debug.d.ts +3 -0
  98. package/dist/vscode/skills/09-debug.d.ts.map +1 -0
  99. package/dist/vscode/skills/09-debug.js +154 -0
  100. package/dist/vscode/skills/09-debug.js.map +1 -0
  101. package/dist/vscode/skills/10-tech-research.d.ts +3 -0
  102. package/dist/vscode/skills/10-tech-research.d.ts.map +1 -0
  103. package/dist/vscode/skills/10-tech-research.js +145 -0
  104. package/dist/vscode/skills/10-tech-research.js.map +1 -0
  105. package/dist/vscode/skills/index.d.ts +18 -0
  106. package/dist/vscode/skills/index.d.ts.map +1 -0
  107. package/dist/vscode/skills/index.js +51 -0
  108. package/dist/vscode/skills/index.js.map +1 -0
  109. package/dist/vscode/skills/pipeline.d.ts +15 -0
  110. package/dist/vscode/skills/pipeline.d.ts.map +1 -0
  111. package/dist/vscode/skills/pipeline.js +55 -0
  112. package/dist/vscode/skills/pipeline.js.map +1 -0
  113. package/dist/vscode/skills/types.d.ts +64 -0
  114. package/dist/vscode/skills/types.d.ts.map +1 -0
  115. package/dist/vscode/skills/types.js +7 -0
  116. package/dist/vscode/skills/types.js.map +1 -0
  117. package/dist/vscode/vscode/commands.d.ts +63 -0
  118. package/dist/vscode/vscode/commands.d.ts.map +1 -0
  119. package/dist/vscode/vscode/commands.js +428 -0
  120. package/dist/vscode/vscode/commands.js.map +1 -0
  121. package/dist/vscode/vscode/extension.d.ts +4 -0
  122. package/dist/vscode/vscode/extension.d.ts.map +1 -0
  123. package/dist/vscode/vscode/extension.js +103 -0
  124. package/dist/vscode/vscode/extension.js.map +1 -0
  125. package/package.json +7 -3
  126. package/rules/claude-code/CLAUDE.md +12 -12
  127. package/rules/cline/.clinerules +3 -3
  128. package/rules/codebuddy/CODEBUDDY.md +12 -12
  129. package/rules/continue/.continuerules +3 -3
  130. package/rules/copilot/copilot-instructions.md +12 -12
  131. package/rules/cursor/.cursorrules +12 -12
  132. package/rules/cursor/smart-flow.mdc +12 -12
  133. package/rules/jetbrains/smart-flow.md +12 -12
  134. package/rules/lingma/smart-flow.md +2 -2
  135. package/rules/windsurf/.windsurf/rules/smart-flow.md +12 -12
  136. package/rules/zed/smart-flow.rules +1 -1
@@ -0,0 +1,26 @@
1
+ /**
2
+ * .ethanrc.json 配置文件的读写与类型定义
3
+ */
4
+ export interface EthanConfig {
5
+ /** 输出语言:zh(中文,默认)或 en(英文) */
6
+ lang?: 'zh' | 'en';
7
+ /** 禁用的 Skill ID 列表 */
8
+ disabledSkills?: string[];
9
+ /** 自定义触发词映射,key 为触发词,value 为 Skill ID */
10
+ customTriggers?: Record<string, string>;
11
+ /** 已安装的插件包名列表 */
12
+ plugins?: string[];
13
+ }
14
+ /**
15
+ * 从当前工作目录(或指定目录)读取配置
16
+ */
17
+ export declare function readConfig(cwd?: string): EthanConfig;
18
+ /**
19
+ * 写入配置到指定目录
20
+ */
21
+ export declare function writeConfig(config: EthanConfig, cwd?: string): void;
22
+ /**
23
+ * 获取配置文件路径
24
+ */
25
+ export declare function getConfigPath(cwd?: string): string;
26
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACnB,sBAAsB;IACtB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,yCAAyC;IACzC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,iBAAiB;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAID;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,WAAW,CAWnE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,GAAE,MAAsB,GAAG,IAAI,CAGlF;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAsB,GAAG,MAAM,CAEjE"}
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * .ethanrc.json 配置文件的读写与类型定义
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.readConfig = readConfig;
40
+ exports.writeConfig = writeConfig;
41
+ exports.getConfigPath = getConfigPath;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const CONFIG_FILE = '.ethanrc.json';
45
+ /**
46
+ * 从当前工作目录(或指定目录)读取配置
47
+ */
48
+ function readConfig(cwd = process.cwd()) {
49
+ const configPath = path.join(cwd, CONFIG_FILE);
50
+ try {
51
+ if (fs.existsSync(configPath)) {
52
+ const raw = fs.readFileSync(configPath, 'utf-8');
53
+ return JSON.parse(raw);
54
+ }
55
+ }
56
+ catch {
57
+ // 配置解析失败时返回空配置
58
+ }
59
+ return {};
60
+ }
61
+ /**
62
+ * 写入配置到指定目录
63
+ */
64
+ function writeConfig(config, cwd = process.cwd()) {
65
+ const configPath = path.join(cwd, CONFIG_FILE);
66
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf-8');
67
+ }
68
+ /**
69
+ * 获取配置文件路径
70
+ */
71
+ function getConfigPath(cwd = process.cwd()) {
72
+ return path.join(cwd, CONFIG_FILE);
73
+ }
74
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBH,gCAWC;AAKD,kCAGC;AAKD,sCAEC;AA7CD,uCAAyB;AACzB,2CAA6B;AAa7B,MAAM,WAAW,GAAG,eAAe,CAAC;AAEpC;;GAEG;AACH,SAAgB,UAAU,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IACpD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC/C,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACjD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAgB,CAAC;QACxC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;GAEG;AACH,SAAgB,WAAW,CAAC,MAAmB,EAAE,MAAc,OAAO,CAAC,GAAG,EAAE;IAC1E,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC/C,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAChF,CAAC;AAED;;GAEG;AACH,SAAgB,aAAa,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IACvD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;AACrC,CAAC"}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * npx ethan CLI
4
- * 命令:install | list | mcp | validate | pipeline | doctor | stats
4
+ * 命令:install | list | mcp | validate | pipeline | doctor | stats | init | run
5
5
  */
6
6
  export {};
7
7
  //# sourceMappingURL=index.d.ts.map
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
6
6
  */
7
7
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
8
  if (k2 === undefined) k2 = k;
@@ -43,7 +43,17 @@ const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
44
  const os = __importStar(require("os"));
45
45
  const index_1 = require("../skills/index");
46
+ const update_check_1 = require("./update-check");
47
+ const config_1 = require("./config");
48
+ // ─── 加载自定义 Skill(透明合并到 ALL_SKILLS) ───────────────────────────────
49
+ async function getActiveSkills() {
50
+ const { loadCustomSkills } = await Promise.resolve().then(() => __importStar(require('../loader/custom-skill-loader')));
51
+ const custom = loadCustomSkills(process.cwd());
52
+ return [...index_1.ALL_SKILLS, ...custom];
53
+ }
46
54
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'));
55
+ // 静默后台检查更新(不阻塞 CLI)
56
+ (0, update_check_1.checkForUpdates)(pkg.version, pkg.name);
47
57
  const program = new commander_1.Command();
48
58
  program
49
59
  .name('ethan')
@@ -76,74 +86,51 @@ program
76
86
  .description('将 Ethan 规则文件安装到当前项目')
77
87
  .option('-p, --platform <platform>', '目标平台:cursor | copilot | cline | lingma | codebuddy | windsurf | zed | jetbrains | continue | claude-code | all', 'all')
78
88
  .option('-d, --dir <dir>', '目标目录(默认为当前目录)', process.cwd())
89
+ .option('--lang <lang>', '输出语言:zh(默认)或 en', '')
90
+ .option('--auto-context', '自动检测项目技术栈并注入规则文件头部')
79
91
  .action(async (options) => {
80
92
  const { platform, dir } = options;
93
+ // 语言优先级:--lang 参数 > .ethanrc.json > 默认 zh
94
+ const config = (0, config_1.readConfig)(dir);
95
+ const lang = options.lang === 'en' || options.lang === 'zh'
96
+ ? options.lang
97
+ : config.lang ?? 'zh';
98
+ // 自动上下文检测
99
+ let contextPrefix = '';
100
+ if (options.autoContext) {
101
+ const { detectProjectContext, formatContextBlock } = await Promise.resolve().then(() => __importStar(require('../context/detector')));
102
+ const projCtx = detectProjectContext(dir);
103
+ contextPrefix = formatContextBlock(projCtx, lang);
104
+ const detected = [
105
+ ...projCtx.languages,
106
+ ...projCtx.frameworks,
107
+ ...projCtx.tools,
108
+ ].join(', ');
109
+ console.log(`\n🔍 检测到技术栈:${detected || '未识别,仍可注入项目名称'}`);
110
+ }
81
111
  const rulesDir = path.join(__dirname, '../../rules');
82
112
  const platformMap = {
83
113
  cursor: [
84
114
  {
85
115
  src: path.join(rulesDir, 'cursor/smart-flow.mdc'),
86
116
  dest: path.join(dir, '.cursor/rules/smart-flow.mdc'),
117
+ platformKey: 'cursor-new',
87
118
  },
88
119
  {
89
120
  src: path.join(rulesDir, 'cursor/.cursorrules'),
90
121
  dest: path.join(dir, '.cursorrules'),
122
+ platformKey: 'cursor-old',
91
123
  },
92
124
  ],
93
- copilot: [
94
- {
95
- src: path.join(rulesDir, 'copilot/copilot-instructions.md'),
96
- dest: path.join(dir, '.github/copilot-instructions.md'),
97
- },
98
- ],
99
- cline: [
100
- {
101
- src: path.join(rulesDir, 'cline/.clinerules'),
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
- ],
125
+ copilot: [{ src: path.join(rulesDir, 'copilot/copilot-instructions.md'), dest: path.join(dir, '.github/copilot-instructions.md'), platformKey: 'copilot' }],
126
+ cline: [{ src: path.join(rulesDir, 'cline/.clinerules'), dest: path.join(dir, '.clinerules'), platformKey: 'cline' }],
127
+ lingma: [{ src: path.join(rulesDir, 'lingma/smart-flow.md'), dest: path.join(dir, '.lingma/rules/smart-flow.md'), platformKey: 'lingma' }],
128
+ codebuddy: [{ src: path.join(rulesDir, 'codebuddy/CODEBUDDY.md'), dest: path.join(dir, 'CODEBUDDY.md'), platformKey: 'codebuddy' }],
129
+ windsurf: [{ src: path.join(rulesDir, 'windsurf/.windsurf/rules/smart-flow.md'), dest: path.join(dir, '.windsurf/rules/smart-flow.md'), platformKey: 'windsurf' }],
130
+ zed: [{ src: path.join(rulesDir, 'zed/smart-flow.rules'), dest: path.join(dir, 'smart-flow.rules'), platformKey: 'zed' }],
131
+ jetbrains: [{ src: path.join(rulesDir, 'jetbrains/smart-flow.md'), dest: path.join(dir, '.github/ai-instructions.md'), platformKey: 'jetbrains' }],
132
+ continue: [{ src: path.join(rulesDir, 'continue/.continuerules'), dest: path.join(dir, '.continuerules'), platformKey: 'continue' }],
133
+ 'claude-code': [{ src: path.join(rulesDir, 'claude-code/CLAUDE.md'), dest: path.join(dir, 'CLAUDE.md'), platformKey: 'claude-code' }],
147
134
  };
148
135
  const targets = platform === 'all'
149
136
  ? Object.values(platformMap).flat()
@@ -153,6 +140,41 @@ program
153
140
  console.error(`Available: cursor | copilot | cline | lingma | codebuddy | windsurf | zed | jetbrains | continue | claude-code | all`);
154
141
  process.exit(1);
155
142
  }
143
+ // 英文模式:按需渲染模板写入,无需预构建文件
144
+ if (lang === 'en') {
145
+ const { renderMarkdown } = await Promise.resolve().then(() => __importStar(require('../templates/copilot-md.template')));
146
+ const { renderCursorMdc, renderCursorOld } = await Promise.resolve().then(() => __importStar(require('../templates/cursor-mdc.template')));
147
+ const now = new Date().toISOString();
148
+ let installed = 0;
149
+ for (const { dest, platformKey } of targets) {
150
+ const makeCtx = () => ({
151
+ platform: platformKey,
152
+ skills: index_1.ALL_SKILLS,
153
+ generatedAt: now,
154
+ version: pkg.version,
155
+ lang: 'en',
156
+ });
157
+ let content;
158
+ if (platformKey === 'cursor-new')
159
+ content = renderCursorMdc(makeCtx());
160
+ else if (platformKey === 'cursor-old')
161
+ content = renderCursorOld(makeCtx());
162
+ else
163
+ content = renderMarkdown(makeCtx());
164
+ if (contextPrefix)
165
+ content = contextPrefix + content;
166
+ const destDir = path.dirname(dest);
167
+ if (!fs.existsSync(destDir))
168
+ fs.mkdirSync(destDir, { recursive: true });
169
+ fs.writeFileSync(dest, content, 'utf-8');
170
+ console.log(` ✅ ${path.relative(dir, dest)} [en]`);
171
+ installed++;
172
+ }
173
+ console.log(`\nInstalled ${installed} rule file(s) to ${dir} (lang: en)`);
174
+ if (installed > 0)
175
+ console.log('Restart your AI editor to apply the new rules.');
176
+ return;
177
+ }
156
178
  let installed = 0;
157
179
  for (const { src, dest } of targets) {
158
180
  if (!fs.existsSync(src)) {
@@ -163,7 +185,14 @@ program
163
185
  if (!fs.existsSync(destDir)) {
164
186
  fs.mkdirSync(destDir, { recursive: true });
165
187
  }
166
- fs.copyFileSync(src, dest);
188
+ // auto-context 模式:读取内容并注入上下文前缀
189
+ if (contextPrefix) {
190
+ const content = fs.readFileSync(src, 'utf-8');
191
+ fs.writeFileSync(dest, contextPrefix + content, 'utf-8');
192
+ }
193
+ else {
194
+ fs.copyFileSync(src, dest);
195
+ }
167
196
  console.log(` ✅ ${path.relative(dir, dest)}`);
168
197
  installed++;
169
198
  }
@@ -175,11 +204,13 @@ program
175
204
  // ─── list 命令 ──────────────────────────────────────────────────────────────
176
205
  program
177
206
  .command('list')
178
- .description('列出所有可用 Skill')
207
+ .description('列出所有可用 Skill(含自定义 Skill)')
179
208
  .option('--json', '以 JSON 格式输出')
180
- .action((options) => {
209
+ .action(async (options) => {
210
+ const skills = await getActiveSkills();
211
+ const customCount = skills.length - index_1.ALL_SKILLS.length;
181
212
  if (options.json) {
182
- console.log(JSON.stringify(index_1.ALL_SKILLS.map((s) => ({
213
+ console.log(JSON.stringify(skills.map((s) => ({
183
214
  id: s.id,
184
215
  name: s.name,
185
216
  nameEn: s.nameEn,
@@ -192,14 +223,125 @@ program
192
223
  }
193
224
  console.log('\nEthan Skills\n');
194
225
  console.log('─'.repeat(60));
195
- for (const skill of index_1.ALL_SKILLS) {
226
+ for (const skill of skills) {
196
227
  const categoryTag = skill.category ? ` [${skill.category}]` : '';
197
- console.log(`\n${skill.order}. ${skill.name} (${skill.id})${categoryTag}`);
228
+ const customTag = skill.order >= 100 ? ' 🔧' : '';
229
+ console.log(`\n${skill.order}. ${skill.name} (${skill.id})${categoryTag}${customTag}`);
198
230
  console.log(` ${skill.description}`);
199
231
  console.log(` Triggers: ${skill.triggers.slice(0, 3).join(' | ')}`);
200
232
  }
201
233
  console.log('\n' + '─'.repeat(60));
202
- console.log(`Total: ${index_1.ALL_SKILLS.length} skills`);
234
+ console.log(`Total: ${skills.length} skills (${index_1.ALL_SKILLS.length} built-in${customCount > 0 ? `, ${customCount} custom` : ''})`);
235
+ });
236
+ // ─── skill 子命令(自定义 Skill 管理) ──────────────────────────────────────
237
+ const skillCmd = program.command('skill').description('自定义 Skill 管理');
238
+ skillCmd
239
+ .command('new [name]')
240
+ .description('在 .ethan/skills/ 目录生成自定义 Skill YAML 模板')
241
+ .action(async (name) => {
242
+ const { generateSkillTemplate } = await Promise.resolve().then(() => __importStar(require('../loader/custom-skill-loader')));
243
+ const skillsDir = path.join(process.cwd(), '.ethan/skills');
244
+ if (!fs.existsSync(skillsDir))
245
+ fs.mkdirSync(skillsDir, { recursive: true });
246
+ const filename = name ? `${name}.yaml` : 'my-skill.yaml';
247
+ const filePath = path.join(skillsDir, filename);
248
+ if (fs.existsSync(filePath)) {
249
+ console.log(`⚠️ 文件已存在:${filePath}`);
250
+ return;
251
+ }
252
+ fs.writeFileSync(filePath, generateSkillTemplate(), 'utf-8');
253
+ console.log(`\n✅ 已创建自定义 Skill 模板:${filePath}`);
254
+ console.log(' 编辑该文件后运行 ethan list 验证加载结果\n');
255
+ });
256
+ skillCmd
257
+ .command('list')
258
+ .description('列出当前项目的自定义 Skill')
259
+ .action(async () => {
260
+ const { loadCustomSkills } = await Promise.resolve().then(() => __importStar(require('../loader/custom-skill-loader')));
261
+ const custom = loadCustomSkills(process.cwd());
262
+ if (custom.length === 0) {
263
+ console.log('\n暂无自定义 Skill。运行 ethan skill new 创建一个。\n');
264
+ return;
265
+ }
266
+ console.log(`\n🔧 自定义 Skill(${custom.length} 个)\n`);
267
+ for (const s of custom) {
268
+ console.log(` ${s.name} (${s.id})`);
269
+ console.log(` 触发词:${s.triggers.slice(0, 3).join(' | ')}`);
270
+ console.log('');
271
+ }
272
+ });
273
+ // ─── plugin 命令(Skill Marketplace) ───────────────────────────────────────
274
+ const OFFICIAL_PLUGINS = [
275
+ { name: 'ethan-plugin-deploy', description: '部署工作流 Skill(CI/CD、Docker、K8s)', author: 'community' },
276
+ { name: 'ethan-plugin-prd', description: 'PRD 文档生成 Skill', author: 'community' },
277
+ { name: 'ethan-plugin-api-design', description: 'RESTful/GraphQL API 设计 Skill', author: 'community' },
278
+ { name: 'ethan-plugin-security', description: '安全审查 Skill(OWASP、依赖检查)', author: 'community' },
279
+ ];
280
+ const pluginCmd = program.command('plugin').description('Skill 插件市场管理');
281
+ pluginCmd
282
+ .command('list')
283
+ .description('列出官方推荐插件及已安装插件')
284
+ .action(() => {
285
+ const config = (0, config_1.readConfig)(process.cwd());
286
+ const installed = config.plugins ?? [];
287
+ console.log('\n📦 Ethan 插件市场\n');
288
+ console.log('─'.repeat(60));
289
+ console.log('\n[官方推荐插件]\n');
290
+ for (const p of OFFICIAL_PLUGINS) {
291
+ const tag = installed.includes(p.name) ? ' ✅ 已安装' : '';
292
+ console.log(` ${p.name}${tag}`);
293
+ console.log(` ${p.description}\n`);
294
+ }
295
+ if (installed.length > 0) {
296
+ console.log('[已安装插件]\n');
297
+ for (const name of installed) {
298
+ console.log(` ${name}`);
299
+ }
300
+ console.log('');
301
+ }
302
+ console.log('─'.repeat(60));
303
+ console.log('\n安装插件:ethan plugin install <plugin-name>');
304
+ console.log('卸载插件:ethan plugin remove <plugin-name>\n');
305
+ });
306
+ pluginCmd
307
+ .command('install <name>')
308
+ .description('从 npm 安装 Skill 插件包')
309
+ .action(async (name) => {
310
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
311
+ console.log(`\n📦 安装插件:${name}\n`);
312
+ try {
313
+ execSync(`npm install ${name}`, { stdio: 'inherit', cwd: process.cwd() });
314
+ }
315
+ catch {
316
+ console.error(`\n❌ 安装失败,请确认包名正确且已发布到 npm\n`);
317
+ process.exit(1);
318
+ }
319
+ // 注册到 .ethanrc.json
320
+ const config = (0, config_1.readConfig)(process.cwd());
321
+ const plugins = config.plugins ?? [];
322
+ if (!plugins.includes(name)) {
323
+ plugins.push(name);
324
+ (0, config_1.writeConfig)({ ...config, plugins }, process.cwd());
325
+ }
326
+ console.log(`\n✅ 插件 ${name} 安装完成`);
327
+ console.log(` 运行 ethan list 可查看新增 Skill\n`);
328
+ });
329
+ pluginCmd
330
+ .command('remove <name>')
331
+ .description('卸载 Skill 插件包')
332
+ .action(async (name) => {
333
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
334
+ console.log(`\n🗑 卸载插件:${name}\n`);
335
+ try {
336
+ execSync(`npm uninstall ${name}`, { stdio: 'inherit', cwd: process.cwd() });
337
+ }
338
+ catch {
339
+ console.warn(` ⚠️ npm uninstall 失败,仍会从配置中移除`);
340
+ }
341
+ const config = (0, config_1.readConfig)(process.cwd());
342
+ const plugins = (config.plugins ?? []).filter((p) => p !== name);
343
+ (0, config_1.writeConfig)({ ...config, plugins }, process.cwd());
344
+ console.log(`\n✅ 插件 ${name} 已卸载\n`);
203
345
  });
204
346
  // ─── mcp 命令 ───────────────────────────────────────────────────────────────
205
347
  program
@@ -448,5 +590,164 @@ program
448
590
  console.log('─'.repeat(60));
449
591
  console.log(` Total executions: ${total}\n`);
450
592
  });
593
+ // ─── serve 命令(Web UI Dashboard) ─────────────────────────────────────────
594
+ program
595
+ .command('serve')
596
+ .description('启动本地 Web UI Dashboard(默认端口 3000)')
597
+ .option('--port <port>', '监听端口', '3000')
598
+ .action(async (options) => {
599
+ const port = parseInt(options.port, 10) || 3000;
600
+ const { startDashboardServer } = await Promise.resolve().then(() => __importStar(require('../server/dashboard')));
601
+ startDashboardServer(port);
602
+ });
603
+ // ─── run 命令(交互式向导) ──────────────────────────────────────────────────
604
+ program
605
+ .command('run')
606
+ .description('交互式 Skill 执行向导:选择 Skill → 填写上下文 → 生成提示词')
607
+ .action(async () => {
608
+ const clack = await Promise.resolve().then(() => __importStar(require('@clack/prompts')));
609
+ const { intro, outro, select, text, isCancel, cancel, note, spinner } = clack;
610
+ // 读取项目配置
611
+ const config = (0, config_1.readConfig)(process.cwd());
612
+ const isEn = config.lang === 'en';
613
+ const activeSkills = index_1.ALL_SKILLS.filter((s) => !config.disabledSkills?.includes(s.id));
614
+ intro(isEn ? '✨ Ethan - Skill Wizard' : '✨ Ethan - 技能执行向导');
615
+ // 按分类分组展示
616
+ const categoryLabel = {
617
+ '需求侧': isEn ? '[Requirements]' : '[需求侧]',
618
+ '执行侧': isEn ? '[Execution]' : '[执行侧]',
619
+ '跟踪侧': isEn ? '[Tracking]' : '[跟踪侧]',
620
+ '输出侧': isEn ? '[Output]' : '[输出侧]',
621
+ '质量侧': isEn ? '[Quality]' : '[质量侧]',
622
+ };
623
+ const skillOptions = activeSkills.map((s) => ({
624
+ value: s.id,
625
+ label: isEn
626
+ ? `${s.nameEn.replace(/_/g, ' ')} ${categoryLabel[s.category ?? ''] ?? ''}`
627
+ : `${s.name} ${categoryLabel[s.category ?? ''] ?? ''}`,
628
+ hint: isEn ? (s.descriptionEn ?? s.description) : s.description,
629
+ }));
630
+ const skillId = await select({
631
+ message: isEn ? 'Which Skill do you want to run?' : '选择要执行的 Skill:',
632
+ options: skillOptions,
633
+ });
634
+ if (isCancel(skillId)) {
635
+ cancel(isEn ? 'Cancelled.' : '已取消');
636
+ process.exit(0);
637
+ }
638
+ const skill = index_1.ALL_SKILLS.find((s) => s.id === skillId);
639
+ const context = await text({
640
+ message: isEn
641
+ ? `Describe your context for "${skill.nameEn.replace(/_/g, ' ')}":`
642
+ : `请输入「${skill.name}」的上下文描述:`,
643
+ placeholder: isEn
644
+ ? 'e.g. Build a user login feature with JWT auth'
645
+ : '例如:实现用户登录功能,支持 JWT 认证',
646
+ validate: (v) => {
647
+ if (!v?.trim())
648
+ return isEn ? 'Context cannot be empty' : '上下文不能为空';
649
+ },
650
+ });
651
+ if (isCancel(context)) {
652
+ cancel(isEn ? 'Cancelled.' : '已取消');
653
+ process.exit(0);
654
+ }
655
+ const s = spinner();
656
+ s.start(isEn ? 'Generating prompt...' : '生成提示词中...');
657
+ // 组装完整提示词
658
+ const stepsText = skill.steps
659
+ .map((step, i) => `${i + 1}. ${step.title.replace(/^\d+\.\s*/, '')}`)
660
+ .join('\n');
661
+ const prompt = [
662
+ `## ${isEn ? skill.nameEn.replace(/_/g, ' ') : skill.name}`,
663
+ '',
664
+ `**${isEn ? 'Context' : '上下文'}**: ${context}`,
665
+ '',
666
+ `**${isEn ? 'Goal' : '目标'}**: ${isEn ? (skill.descriptionEn ?? skill.description) : skill.description}`,
667
+ '',
668
+ `**${isEn ? 'Please follow these steps' : '请按以下步骤执行'}**:`,
669
+ stepsText,
670
+ '',
671
+ `**${isEn ? 'Output format' : '输出格式'}**: ${skill.outputFormat}`,
672
+ ].join('\n');
673
+ s.stop(isEn ? 'Prompt ready!' : '提示词已生成!');
674
+ note(prompt, isEn ? 'Your Prompt' : '你的提示词');
675
+ // 尝试复制到剪贴板
676
+ try {
677
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
678
+ const cmd = process.platform === 'darwin'
679
+ ? `echo ${JSON.stringify(prompt)} | pbcopy`
680
+ : process.platform === 'win32'
681
+ ? `echo ${JSON.stringify(prompt)} | clip`
682
+ : `echo ${JSON.stringify(prompt)} | xclip -selection clipboard`;
683
+ execSync(cmd, { stdio: 'pipe' });
684
+ outro(isEn
685
+ ? '✅ Prompt copied to clipboard! Paste it into your AI editor.'
686
+ : '✅ 提示词已复制到剪贴板!粘贴到你的 AI 编辑器中使用。');
687
+ }
688
+ catch {
689
+ outro(isEn
690
+ ? '✅ Done! Copy the prompt above and paste it into your AI editor.'
691
+ : '✅ 完成!请复制上方提示词,粘贴到你的 AI 编辑器中使用。');
692
+ }
693
+ // 记录使用统计
694
+ const stats = readStats();
695
+ stats[skill.id] = (stats[skill.id] || 0) + 1;
696
+ writeStats(stats);
697
+ });
698
+ // ─── init 命令 ───────────────────────────────────────────────────────────────
699
+ program
700
+ .command('init')
701
+ .description('在当前项目生成 .ethanrc.json 配置文件')
702
+ .option('-d, --dir <dir>', '目标目录(默认为当前目录)', process.cwd())
703
+ .action(async (options) => {
704
+ const { dir } = options;
705
+ const configPath = (0, config_1.getConfigPath)(dir);
706
+ if (fs.existsSync(configPath)) {
707
+ const existing = (0, config_1.readConfig)(dir);
708
+ console.log(`\n⚠️ ${configPath} 已存在,当前配置:`);
709
+ console.log(JSON.stringify(existing, null, 2));
710
+ console.log('\n如需修改,请直接编辑该文件。\n');
711
+ return;
712
+ }
713
+ // 使用 readline 简单交互
714
+ const readline = await Promise.resolve().then(() => __importStar(require('readline')));
715
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
716
+ const ask = (q) => new Promise((resolve) => rl.question(q, (a) => resolve(a.trim())));
717
+ console.log('\n🚀 Ethan 项目配置向导\n');
718
+ console.log('─'.repeat(50));
719
+ const langInput = await ask('\n输出语言 [zh/en](默认 zh): ');
720
+ const lang = langInput === 'en' ? 'en' : 'zh';
721
+ const disabledInput = await ask('\n要禁用的 Skill ID(逗号分隔,留空跳过)\n可选: ' +
722
+ index_1.ALL_SKILLS.map((s) => s.id).join(', ') +
723
+ '\n> ');
724
+ const disabledSkills = disabledInput
725
+ ? disabledInput
726
+ .split(',')
727
+ .map((s) => s.trim())
728
+ .filter((s) => index_1.ALL_SKILLS.some((sk) => sk.id === s))
729
+ : [];
730
+ const customTriggersInput = await ask('\n自定义触发词(格式: 缩写=skill-id,逗号分隔,留空跳过)\n例如: cr=code-review,fix=debug\n> ');
731
+ const customTriggers = {};
732
+ if (customTriggersInput) {
733
+ for (const pair of customTriggersInput.split(',')) {
734
+ const [key, val] = pair.split('=').map((s) => s.trim());
735
+ if (key && val && index_1.ALL_SKILLS.some((sk) => sk.id === val)) {
736
+ customTriggers[key] = val;
737
+ }
738
+ }
739
+ }
740
+ rl.close();
741
+ const config = {
742
+ lang,
743
+ ...(disabledSkills.length > 0 ? { disabledSkills } : {}),
744
+ ...(Object.keys(customTriggers).length > 0 ? { customTriggers } : {}),
745
+ };
746
+ (0, config_1.writeConfig)(config, dir);
747
+ console.log('\n✅ 已生成 .ethanrc.json:');
748
+ console.log(JSON.stringify(config, null, 2));
749
+ console.log(`\n文件路径:${configPath}`);
750
+ console.log('\n💡 提示:现在运行 ethan install --platform <platform> 将使用此配置\n');
751
+ });
451
752
  program.parse(process.argv);
452
753
  //# sourceMappingURL=index.js.map