momo-ai 1.0.21 → 1.0.22

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 (94) hide show
  1. package/.claude/skills/r2mo-rad-lain/SKILL.md +63 -374
  2. package/.trae/skills/algorithmic-art/LICENSE.txt +202 -0
  3. package/.trae/skills/algorithmic-art/SKILL.md +405 -0
  4. package/.trae/skills/algorithmic-art/templates/generator_template.js +223 -0
  5. package/.trae/skills/algorithmic-art/templates/viewer.html +599 -0
  6. package/.trae/skills/doc-coauthoring/SKILL.md +375 -0
  7. package/.trae/skills/frontend-design/LICENSE.txt +177 -0
  8. package/.trae/skills/frontend-design/SKILL.md +42 -0
  9. package/.trae/skills/r2mo-rad-lain/SKILL.md +101 -0
  10. package/README.md +9 -32
  11. package/docs/images/r2mo-lain.png +0 -0
  12. package/package.json +11 -11
  13. package/skills/r2mo-rad-domain/SKILL.md +70 -0
  14. package/src/_skill/repositories.json +9 -3
  15. package/src/_template/LAIN/.obsidian/app.json +1 -0
  16. package/src/_template/LAIN/.obsidian/appearance.json +10 -0
  17. package/src/_template/LAIN/.obsidian/community-plugins.json +7 -0
  18. package/src/_template/LAIN/.obsidian/core-plugins.json +33 -0
  19. package/src/_template/LAIN/.obsidian/plugins/dataview/main.js +20876 -0
  20. package/src/_template/LAIN/.obsidian/plugins/dataview/manifest.json +11 -0
  21. package/src/_template/LAIN/.obsidian/plugins/dataview/styles.css +141 -0
  22. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/data.json +815 -0
  23. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
  24. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
  25. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
  26. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/main.js +153 -0
  27. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
  28. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
  29. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/main.js +7732 -0
  30. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/manifest.json +10 -0
  31. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/styles.css +38 -0
  32. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/main.js +504 -0
  33. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
  34. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
  35. package/src/_template/LAIN/.obsidian/snippets/body-font.css +27 -0
  36. package/src/_template/LAIN/.obsidian/themes/Primary/manifest.json +9 -0
  37. package/src/_template/LAIN/.obsidian/themes/Primary/theme.css +3878 -0
  38. package/src/_template/LAIN/.obsidian/themes/Retro Windows/manifest.json +7 -0
  39. package/src/_template/LAIN/.obsidian/themes/Retro Windows/theme.css +582 -0
  40. package/src/_template/LAIN/.obsidian/themes/RetroOS 98/manifest.json +9 -0
  41. package/src/_template/LAIN/.obsidian/themes/RetroOS 98/theme.css +2566 -0
  42. package/src/_template/LAIN/.obsidian/types.json +28 -0
  43. package/src/_template/LAIN/.obsidian/workspace.json +184 -0
  44. package/src/_template/LAIN/AGENTS.md +170 -16
  45. package/src/_template/R2MO/domain-enhance.md +10 -0
  46. package/src/commander/app.json +13 -0
  47. package/src/commander/apply.json +13 -0
  48. package/src/commander/ask.json +6 -0
  49. package/src/commander/docs.json +13 -0
  50. package/src/commander/domain.json +19 -0
  51. package/src/commander/init.json +1 -1
  52. package/src/commander/mmr0.json +6 -0
  53. package/src/commander/mmr2.json +6 -0
  54. package/src/executor/executeApp.js +133 -0
  55. package/src/executor/{executeSkills.js → executeApply.js} +166 -302
  56. package/src/executor/executeAsk.js +274 -0
  57. package/src/executor/executeDocs.js +498 -0
  58. package/src/executor/executeDomain.js +293 -0
  59. package/src/executor/executeInit.js +159 -383
  60. package/src/executor/executeMcp.js +74 -1
  61. package/src/executor/executeMmr0.js +488 -0
  62. package/src/executor/executeMmr2.js +880 -0
  63. package/src/executor/index.js +15 -3
  64. package/src/python/r2mo_proto.py +418 -0
  65. package/src/python/r2mo_proto_database.py +369 -0
  66. package/src/python/r2mo_proto_domain.py +458 -0
  67. package/src/utils/momo-menu.js +43 -13
  68. package/.claude/skills/r2mo-rad-lain/PROMPT.md +0 -281
  69. package/.claude/skills/r2mo-rad-lain/README.md +0 -192
  70. package/.claude/skills/r2mo-rad-lain/examples/argument-parsing.js +0 -154
  71. package/.claude/skills/r2mo-rad-lain/examples/file-operations.js +0 -182
  72. package/.claude/skills/r2mo-rad-lain/file-utils-api.md +0 -281
  73. package/.claude/skills/r2mo-rad-lain/menu-api.md +0 -187
  74. package/.claude/skills/r2mo-rad-lain/scripts/file-utils.js +0 -223
  75. package/.claude/skills/r2mo-rad-lain/scripts/menu.js +0 -289
  76. package/.claude/skills/r2mo-rad-lain/scripts/yaml-parser.js +0 -209
  77. package/.claude/skills/r2mo-rad-lain/templates/command.json.template +0 -13
  78. package/.claude/skills/r2mo-rad-lain/templates/executor.js.template +0 -32
  79. package/.claude/skills/r2mo-rad-lain/templates/interactive-menu.js.template +0 -221
  80. package/src/_template/LAIN/.momo/advanced/actor.md +0 -42
  81. package/src/_template/LAIN/.momo/advanced/refer.json +0 -46
  82. package/src/_template/LAIN/.momo/scripts/submodule-clean.sh +0 -56
  83. package/src/_template/LAIN/changes/proposal.md +0 -39
  84. package/src/_template/LAIN/changes/tasks/task-detail.md +0 -45
  85. package/src/_template/LAIN/changes/tasks.md +0 -49
  86. package/src/_template/LAIN/execute/admin-n-f-dashboard.md +0 -53
  87. package/src/_template/LAIN/execute/admin-n-f-form.md +0 -51
  88. package/src/_template/LAIN/execute/admin-n-f-home.md +0 -49
  89. package/src/_template/LAIN/execute/admin-n-f-list.md +0 -52
  90. package/src/_template/LAIN/execute/admin-n-f-login.md +0 -56
  91. package/src/_template/LAIN/specification/project-model.md +0 -13
  92. package/src/_template/LAIN/specification/project.md +0 -73
  93. package/src/_template/LAIN/specification/requirement.md +0 -25
  94. package/src/commander/skills.json +0 -20
@@ -5,23 +5,48 @@ const Ec = require('../epic');
5
5
  const fsAsync = require('fs').promises;
6
6
  const readline = require('readline');
7
7
  const { execSync } = require('child_process');
8
- const { parseOptional, parseBool } = require('../utils/momo-args');
8
+ const { parseOptional } = require('../utils/momo-args');
9
9
  const { copyDir, readJson, parseFile } = require('../utils/momo-file-utils');
10
+ const { selectSingle } = require('../utils/momo-menu');
10
11
 
11
- // 全局技能仓库路径
12
- const GLOBAL_SKILLS_DIR = path.join(os.homedir(), '.claude', 'skills');
13
12
  // 远程仓库配置文件路径
14
13
  const REPOSITORIES_CONFIG = path.join(__dirname, '../_skill/repositories.json');
15
14
  // 本地缓存仓库目录
16
15
  const LOCAL_REPO_CACHE_DIR = '.r2mo/repo';
17
16
 
18
- // 来源标记
19
- const SOURCE_GLOBAL = 'NORM';
20
- const SOURCE_PROJECT = 'R2MO';
21
-
22
17
  // 限定技仓库地址
23
18
  const RESTRICTED_REPOSITORY = 'https://gitee.com/silentbalanceyh/r2mo-lain.git';
24
19
 
20
+ /**
21
+ * 获取目标路径配置(基于当前项目目录)
22
+ * @returns {Array} 目标路径配置列表
23
+ */
24
+ const _getTargetPaths = () => {
25
+ const projectDir = process.cwd();
26
+ return [
27
+ {
28
+ name: 'Cursor 默认',
29
+ description: '.claude/skills/',
30
+ path: path.join(projectDir, '.claude', 'skills')
31
+ },
32
+ {
33
+ name: 'Antigravity',
34
+ description: '.agent/skills/',
35
+ path: path.join(projectDir, '.agent', 'skills')
36
+ },
37
+ {
38
+ name: 'Trae CN',
39
+ description: '.trae/skills/',
40
+ path: path.join(projectDir, '.trae', 'skills')
41
+ },
42
+ {
43
+ name: 'Trae',
44
+ description: '.trae/skills/',
45
+ path: path.join(projectDir, '.trae', 'skills')
46
+ }
47
+ ];
48
+ };
49
+
25
50
  /**
26
51
  * 确保 .r2mo/repo 在 .gitignore 中
27
52
  * @param {string} projectDir 项目目录
@@ -65,6 +90,17 @@ const _getLocalRepoPath = (projectDir, repoName) => {
65
90
  return path.join(projectDir, LOCAL_REPO_CACHE_DIR, safeName);
66
91
  };
67
92
 
93
+ /**
94
+ * 检查错误是否为服务器端错误
95
+ * @param {Error} error 错误对象
96
+ * @returns {boolean} 是否为服务器错误
97
+ */
98
+ const _isServerError = (error) => {
99
+ const errorMsg = error.message || '';
100
+ // 检查常见的服务器错误代码
101
+ return /500|502|503|504|Internal Server Error/i.test(errorMsg);
102
+ };
103
+
68
104
  /**
69
105
  * 克隆或更新远程仓库到本地缓存
70
106
  * @param {string} url 仓库 URL
@@ -89,6 +125,14 @@ const _cloneOrUpdateRepository = (url, localPath) => {
89
125
  Ec.waiting('✓ 仓库已更新'.green);
90
126
  return true;
91
127
  } catch (error) {
128
+ // 检查是否为服务器错误
129
+ if (_isServerError(error)) {
130
+ Ec.warn('⚠️ 远程服务器暂时不可用(可能是 GitHub/Gitee 服务器问题)');
131
+ Ec.waiting('将使用本地缓存继续操作...'.yellow);
132
+ // 保留现有缓存,不删除
133
+ return true;
134
+ }
135
+
92
136
  Ec.warn(`更新失败,将重新克隆: ${error.message}`);
93
137
  // 删除损坏的缓存
94
138
  fs.rmSync(localPath, { recursive: true, force: true });
@@ -106,42 +150,18 @@ const _cloneOrUpdateRepository = (url, localPath) => {
106
150
  Ec.waiting('✓ 仓库克隆完成'.green);
107
151
  return true;
108
152
  } catch (error) {
153
+ // 检查是否为服务器错误
154
+ if (_isServerError(error)) {
155
+ Ec.error('❌ 远程服务器暂时不可用(可能是 GitHub/Gitee 服务器问题)');
156
+ Ec.waiting('请稍后重试,或检查网络连接');
157
+ return false;
158
+ }
159
+
109
160
  Ec.error(`克隆仓库失败: ${error.message}`);
110
161
  return false;
111
162
  }
112
163
  };
113
164
 
114
- /**
115
- * 截断描述文本
116
- * @param {string} text 原始文本
117
- * @param {number} maxLen 最大长度
118
- * @returns {string} 截断后的文本
119
- */
120
- const _truncateDescription = (text, maxLen = 50) => {
121
- if (!text) return '无描述';
122
- // 移除引号
123
- text = text.replace(/^["']|["']$/g, '');
124
- if (text.length <= maxLen) return text;
125
- return text.substring(0, maxLen - 3) + '...';
126
- };
127
-
128
- /**
129
- * 解析 -r 参数(支持无值的情况)
130
- * @returns {Object} { hasRemote: boolean, remoteValue: string|null }
131
- */
132
- const _parseRemoteArg = () => {
133
- const result = parseOptional('remote', 'r');
134
- return { hasRemote: result.hasFlag, remoteValue: result.value };
135
- };
136
-
137
- /**
138
- * 解析 -g 参数
139
- * @returns {boolean} 是否安装到全局目录
140
- */
141
- const _parseGlobalArg = () => {
142
- return parseBool('global', 'g');
143
- };
144
-
145
165
  /**
146
166
  * 读取远程仓库配置
147
167
  * @returns {Array} 仓库列表
@@ -169,10 +189,9 @@ const _parseSkillYaml = (filePath) => {
169
189
  /**
170
190
  * 扫描技能目录
171
191
  * @param {string} skillsDir 技能目录路径
172
- * @param {string} source 来源标记
173
192
  * @returns {Array} 技能列表
174
193
  */
175
- const _scanSkillsFromDir = (skillsDir, source = '') => {
194
+ const _scanSkillsFromDir = (skillsDir) => {
176
195
  const skills = [];
177
196
 
178
197
  if (!fs.existsSync(skillsDir)) {
@@ -204,7 +223,6 @@ const _scanSkillsFromDir = (skillsDir, source = '') => {
204
223
  version: metadata.version || '未知',
205
224
  category: metadata.category || '未分类',
206
225
  tags: metadata.tags || [],
207
- source: source,
208
226
  skillType: skillType,
209
227
  repository: repository
210
228
  });
@@ -217,7 +235,6 @@ const _scanSkillsFromDir = (skillsDir, source = '') => {
217
235
  version: '未知',
218
236
  category: '未分类',
219
237
  tags: [],
220
- source: source,
221
238
  skillType: '通用技',
222
239
  repository: ''
223
240
  });
@@ -231,9 +248,8 @@ const _scanSkillsFromDir = (skillsDir, source = '') => {
231
248
  return skills;
232
249
  };
233
250
 
234
-
235
251
  /**
236
- * 递归拷贝目录(使用工具函数)
252
+ * 递归拷贝目录
237
253
  * @param {string} src 源目录
238
254
  * @param {string} dest 目标目录
239
255
  */
@@ -242,107 +258,44 @@ const _copyDirectory = async (src, dest) => {
242
258
  };
243
259
 
244
260
  /**
245
- * 仓库选择菜单
246
- * @param {Array} repositories 仓库列表
247
- * @returns {Promise<Object|null>} 选中的仓库
261
+ * 截断描述文本
262
+ * @param {string} text 原始文本
263
+ * @param {number} maxLen 最大长度
264
+ * @returns {string} 截断后的文本
248
265
  */
249
- const _selectRepository = async (repositories) => {
250
- return new Promise((resolve) => {
251
- let cursor = 0;
252
-
253
- const render = () => {
254
- process.stdout.write('\x1B[2J\x1B[0f');
255
- console.log('');
256
- console.log('[Momo AI]'.blue.bold + ' ====== 选择远程仓库 ======'.blue);
257
- console.log('');
258
-
259
- repositories.forEach((repo, index) => {
260
- const isActive = index === cursor;
261
- const pointer = isActive ? '▸'.cyan : ' ';
262
- const name = repo.name;
263
-
264
- if (isActive) {
265
- console.log(` ${pointer} ${name.cyan.bold}`);
266
- } else {
267
- console.log(` ${pointer} ${name.cyan}`);
268
- }
269
- console.log(` ${repo.description.gray}`);
270
- console.log(` ${repo.url.gray}`);
271
- });
272
-
273
- console.log('');
274
- console.log(' ─────────────────────────────────────────────────'.gray);
275
- console.log(' ↑/↓ 移动 │ 回车 确认 │ q 退出'.gray);
276
- };
277
-
278
- readline.emitKeypressEvents(process.stdin);
279
- if (process.stdin.isTTY) {
280
- process.stdin.setRawMode(true);
281
- }
282
-
283
- render();
284
-
285
- const onKeypress = (str, key) => {
286
- if (!key) return;
287
-
288
- if (key.ctrl && key.name === 'c') {
289
- process.stdin.setRawMode(false);
290
- process.stdin.removeListener('keypress', onKeypress);
291
- process.exit(0);
292
- }
293
-
294
- if (key.name === 'q' || key.name === 'escape') {
295
- process.stdin.setRawMode(false);
296
- process.stdin.removeListener('keypress', onKeypress);
297
- process.stdout.write('\x1B[2J\x1B[0f');
298
- resolve(null);
299
- return;
300
- }
301
-
302
- if (key.name === 'up') {
303
- cursor = cursor > 0 ? cursor - 1 : repositories.length - 1;
304
- render();
305
- return;
306
- }
307
-
308
- if (key.name === 'down') {
309
- cursor = cursor < repositories.length - 1 ? cursor + 1 : 0;
310
- render();
311
- return;
312
- }
313
-
314
- if (key.name === 'return') {
315
- process.stdin.setRawMode(false);
316
- process.stdin.removeListener('keypress', onKeypress);
317
- process.stdout.write('\x1B[2J\x1B[0f');
318
- resolve(repositories[cursor]);
319
- return;
320
- }
321
- };
266
+ const _truncateDescription = (text, maxLen = 50) => {
267
+ if (!text) return '无描述';
268
+ // 移除引号
269
+ text = text.replace(/^["']|["']$/g, '');
270
+ if (text.length <= maxLen) return text;
271
+ return text.substring(0, maxLen - 3) + '...';
272
+ };
322
273
 
323
- process.stdin.on('keypress', onKeypress);
324
- });
274
+ /**
275
+ * 选择目标路径
276
+ * @returns {Promise<Object|null>} 选中的目标路径配置
277
+ */
278
+ const _selectTargetPath = async () => {
279
+ const targetPaths = _getTargetPaths();
280
+ const selected = await selectSingle(targetPaths, '选择安装目标路径');
281
+ return selected;
325
282
  };
326
283
 
327
284
  /**
328
- * 交互式多选菜单(仅选择阶段)
285
+ * 选择技能(多选菜单)
329
286
  * @param {Array} skills 技能列表
330
287
  * @param {string} title 菜单标题
331
- * @param {Object} options 显示选项
332
- * @param {boolean} options.showSource 是否显示来源
333
- * @param {boolean} options.showDescription 是否显示描述
334
- * @param {boolean} options.showVersion 是否显示版本
335
- * @returns {Promise<Object>} { indices: 选中的索引列表, names: 选中的名称列表 }
288
+ * @returns {Promise<Array>} 选中的技能索引列表
336
289
  */
337
- const _selectSkillsMenu = (skills, title = '技能安装菜单', options = {}) => {
338
- const { showSource = false, showDescription = true, showVersion = true } = options;
290
+ const _selectSkills = async (skills, title) => {
291
+ // 对于远程技能(官方),不显示描述
292
+ const showDescription = false;
339
293
 
340
294
  return new Promise((resolve) => {
341
295
  const selected = new Array(skills.length).fill(false);
342
296
  let cursor = 0;
343
297
 
344
298
  const maxNameLen = Math.max(...skills.map(s => s.name.length), 4);
345
- const maxSourceLen = showSource ? Math.max(...skills.map(s => (s.source || '').length), 4) : 0;
346
299
  const maxTypeLen = Math.max(...skills.map(s => (s.skillType || '').length), 4);
347
300
 
348
301
  const render = () => {
@@ -357,20 +310,7 @@ const _selectSkillsMenu = (skills, title = '技能安装菜单', options = {}) =
357
310
  const checkbox = selected[index] ? '[✓]'.green : '[ ]';
358
311
  const pointer = isActive ? '▸'.cyan : ' ';
359
312
  const name = skill.name.padEnd(maxNameLen);
360
- const version = (showVersion && skill.version) ? `v${skill.version}`.yellow : '';
361
-
362
- // 来源标记
363
- let sourceTag = '';
364
- if (showSource && skill.source) {
365
- if (skill.source === SOURCE_GLOBAL) {
366
- sourceTag = `[${skill.source}]`.gray;
367
- } else if (skill.source === SOURCE_PROJECT) {
368
- sourceTag = `[${skill.source}]`.magenta;
369
- } else {
370
- sourceTag = `[${skill.source}]`.gray;
371
- }
372
- sourceTag = sourceTag.padEnd(maxSourceLen + 10); // 补偿颜色码长度
373
- }
313
+ const version = skill.version ? `v${skill.version}`.yellow : '';
374
314
 
375
315
  // 技能类型标记(限定技/通用技)
376
316
  const skillType = skill.skillType || '通用技';
@@ -378,16 +318,13 @@ const _selectSkillsMenu = (skills, title = '技能安装菜单', options = {}) =
378
318
  ? `[${skillType}]`.red
379
319
  : `[${skillType}]`.green;
380
320
  // 统一对齐:限定技和通用技都是3个字符,使用固定宽度确保对齐
381
- // 实际显示宽度 = 方括号(2) + 文本(3) + 颜色码补偿(约15-20)
382
321
  const typeTagPadded = typeTag.padEnd(25); // 固定宽度确保对齐
383
322
 
384
- if (isActive) {
385
- console.log(` ${pointer} ${checkbox} ${sourceTag}${typeTagPadded}${name.cyan.bold} ${version}`);
386
- } else {
387
- console.log(` ${pointer} ${checkbox} ${sourceTag}${typeTagPadded}${name.cyan} ${version}`);
388
- }
323
+ // 统一技能名称样式,不加粗
324
+ const nameStyled = isActive ? name.cyan.bold : name.cyan;
325
+ console.log(` ${pointer} ${checkbox} ${typeTagPadded}${nameStyled} ${version}`);
389
326
 
390
- // 显示描述(可选)
327
+ // 显示描述(可选,远程技能不显示)
391
328
  if (showDescription) {
392
329
  const desc = _truncateDescription(skill.description, 50);
393
330
  console.log(` ${desc.gray}`);
@@ -439,7 +376,7 @@ const _selectSkillsMenu = (skills, title = '技能安装菜单', options = {}) =
439
376
  process.stdin.setRawMode(false);
440
377
  process.stdin.removeListener('keypress', onKeypress);
441
378
  process.stdout.write('\x1B[2J\x1B[0f');
442
- resolve({ indices: [], names: [] });
379
+ resolve([]);
443
380
  return;
444
381
  }
445
382
 
@@ -493,36 +430,26 @@ const _selectSkillsMenu = (skills, title = '技能安装菜单', options = {}) =
493
430
  });
494
431
  console.log('');
495
432
 
496
- resolve(result);
433
+ // 等待用户确认
434
+ resolve(result.indices);
497
435
  return;
498
436
  }
499
437
  };
500
438
 
501
439
  process.stdin.on('keypress', onKeypress);
502
- });
503
- };
504
-
505
- /**
506
- * 交互式选择技能(包含确认步骤)
507
- * @param {Array} skills 技能列表
508
- * @param {string} title 菜单标题
509
- * @param {Object} options 显示选项
510
- * @returns {Promise<Array>} 选中的技能索引列表
511
- */
512
- const _selectSkills = async (skills, title, options = {}) => {
513
- const result = await _selectSkillsMenu(skills, title, options);
514
-
515
- if (result.indices.length === 0) {
516
- return [];
517
- }
440
+ }).then(async (indices) => {
441
+ if (indices.length === 0) {
442
+ return [];
443
+ }
518
444
 
519
- const answer = await Ec.ask('确认安装?(y/N): ');
520
- if (answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes') {
521
- return result.indices;
522
- } else {
523
- Ec.waiting('已取消安装');
524
- return [];
525
- }
445
+ const answer = await Ec.ask('确认安装?(y/N): ');
446
+ if (answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes') {
447
+ return indices;
448
+ } else {
449
+ Ec.waiting('已取消安装');
450
+ return [];
451
+ }
452
+ });
526
453
  };
527
454
 
528
455
  /**
@@ -552,75 +479,44 @@ const _installSkills = async (skills, selectedIndices, targetDir) => {
552
479
  };
553
480
 
554
481
  /**
555
- * 从本地安装技能(扫描全局和项目目录)
556
- * @param {boolean} isGlobal 是否安装到全局目录
482
+ * 选择远程仓库
483
+ * @param {Array} repositories 仓库列表
484
+ * @param {string} remoteValue -r 参数的值(如果提供)
485
+ * @returns {Promise<Object|null>} 选中的仓库
557
486
  */
558
- const _installFromLocal = async (isGlobal = false) => {
559
- const allSkills = [];
560
-
561
- // 1. 扫描全局技能目录
562
- Ec.waiting('正在扫描全局技能仓库...');
563
- if (fs.existsSync(GLOBAL_SKILLS_DIR)) {
564
- const globalSkills = _scanSkillsFromDir(GLOBAL_SKILLS_DIR, SOURCE_GLOBAL);
565
- allSkills.push(...globalSkills);
566
- Ec.waiting(` 找到 ${globalSkills.length} 个全局技能`);
567
- } else {
568
- Ec.waiting(` 全局目录不存在: ${GLOBAL_SKILLS_DIR}`.gray);
569
- }
570
-
571
- // 2. 扫描当前项目技能目录
572
- const projectDir = process.cwd();
573
- const projectSkillsDir = path.join(projectDir, 'skills');
574
-
575
- Ec.waiting('正在扫描项目技能目录...');
576
- if (fs.existsSync(projectSkillsDir)) {
577
- const projectSkills = _scanSkillsFromDir(projectSkillsDir, SOURCE_PROJECT);
578
- allSkills.push(...projectSkills);
579
- Ec.waiting(` 找到 ${projectSkills.length} 个项目技能`);
580
- } else {
581
- Ec.waiting(` 项目目录不存在: ${projectSkillsDir}`.gray);
582
- }
583
-
584
- // 检查是否有可用技能
585
- if (allSkills.length === 0) {
586
- Ec.warn('未找到任何可用技能');
587
- Ec.waiting(`请在 ${GLOBAL_SKILLS_DIR} 或 ${projectSkillsDir} 目录下添加技能`);
588
- Ec.waiting('或使用 -r 参数从远程仓库安装');
589
- process.exit(0);
590
- }
591
-
592
- Ec.info(`共找到 ${allSkills.length} 个本地技能,正在打开选择菜单...`);
593
-
594
- const selectedIndices = await _selectSkills(allSkills, '本地技能安装', {
595
- showSource: true,
596
- showDescription: true,
597
- showVersion: true
598
- });
599
-
600
- if (selectedIndices.length === 0) {
601
- Ec.waiting('未选择任何技能,退出');
602
- process.exit(0);
487
+ const _selectRepository = async (repositories, remoteValue) => {
488
+ // 如果 -r 有值,直接查找对应的仓库
489
+ if (remoteValue) {
490
+ const repo = repositories.find(r =>
491
+ r.name === remoteValue ||
492
+ r.name.toLowerCase() === remoteValue.toLowerCase()
493
+ );
494
+ if (repo) {
495
+ return repo;
496
+ }
497
+ Ec.warn(`未找到仓库: ${remoteValue}`);
498
+ Ec.waiting('将显示仓库选择菜单...');
603
499
  }
604
500
 
605
- // 根据 -g 参数决定安装位置
606
- const targetDir = isGlobal
607
- ? GLOBAL_SKILLS_DIR
608
- : path.join(projectDir, '.claude', 'skills');
609
-
610
- console.log('');
611
- Ec.info(`将安装 ${selectedIndices.length} 个技能到 ${targetDir}`);
612
-
613
- await _installSkills(allSkills, selectedIndices, targetDir);
501
+ // 显示仓库选择菜单
502
+ const menuItems = repositories.map(repo => ({
503
+ name: repo.name,
504
+ description: `${repo.description || ''} - ${repo.url}`,
505
+ repo: repo // 保存原始仓库对象的引用
506
+ }));
614
507
 
615
- Ec.info(`✅ 成功安装 ${selectedIndices.length} 个技能!`);
508
+ const selected = await selectSingle(menuItems, '选择远程仓库');
509
+
510
+ // 返回原始仓库对象
511
+ return selected ? selected.repo : null;
616
512
  };
617
513
 
618
514
  /**
619
515
  * 从远程仓库安装技能
620
516
  * @param {Object} repository 仓库配置
621
- * @param {boolean} isGlobal 是否安装到全局目录
517
+ * @param {string} targetPath 目标路径
622
518
  */
623
- const _installFromRemote = async (repository, isGlobal = false) => {
519
+ const _installFromRemote = async (repository, targetPath) => {
624
520
  const projectDir = process.cwd();
625
521
 
626
522
  console.log('');
@@ -660,88 +556,56 @@ const _installFromRemote = async (repository, isGlobal = false) => {
660
556
 
661
557
  Ec.info(`找到 ${skills.length} 个远程技能,正在打开选择菜单...`);
662
558
 
663
- const selectedIndices = await _selectSkills(skills, `远程技能 (${repository.name})`, {
664
- showSource: false,
665
- showDescription: false,
666
- showVersion: false
667
- });
559
+ const selectedIndices = await _selectSkills(skills, `远程技能 (${repository.name})`);
668
560
 
669
561
  if (selectedIndices.length === 0) {
670
562
  Ec.waiting('未选择任何技能,退出');
671
563
  return;
672
564
  }
673
565
 
674
- // 根据 -g 参数决定安装位置
675
- const targetDir = isGlobal
676
- ? GLOBAL_SKILLS_DIR
677
- : path.join(projectDir, '.claude', 'skills');
678
-
679
566
  console.log('');
680
- Ec.info(`将安装 ${selectedIndices.length} 个技能到 ${targetDir}`);
567
+ Ec.info(`将安装 ${selectedIndices.length} 个技能到 ${targetPath}`);
681
568
 
682
- await _installSkills(skills, selectedIndices, targetDir);
569
+ await _installSkills(skills, selectedIndices, targetPath);
683
570
 
684
571
  Ec.info(`✅ 成功安装 ${selectedIndices.length} 个技能!`);
685
572
  };
686
573
 
687
574
  module.exports = async (options) => {
688
- try {
689
- // 解析 -r -g 参数
690
- const { hasRemote, remoteValue } = _parseRemoteArg();
691
- const isGlobal = _parseGlobalArg();
692
-
693
- // 显示安装目标信息
694
- if (isGlobal) {
695
- console.log('');
696
- Ec.info(`📦 安装目标: 全局目录 (${GLOBAL_SKILLS_DIR.cyan})`);
697
- } else {
698
- const projectDir = process.cwd();
699
- const projectSkillsDir = path.join(projectDir, '.claude', 'skills');
700
- console.log('');
701
- Ec.info(`📦 安装目标: 项目目录 (${projectSkillsDir.cyan})`);
702
- }
703
-
704
- if (!hasRemote) {
705
- // 不带 -r:从本地安装
706
- await _installFromLocal(isGlobal);
707
- process.exit(0);
708
- }
709
-
710
- // 带 -r 参数:从远程安装
711
- const repositories = _loadRepositories();
712
-
713
- if (repositories.length === 0) {
714
- Ec.error('❌ 未配置任何远程仓库');
715
- Ec.waiting(`请在 ${REPOSITORIES_CONFIG} 中配置仓库`);
716
- process.exit(1);
717
- }
575
+ // 检查 -r 参数
576
+ const { hasFlag: hasRemote, value: remoteValue } = parseOptional('remote', 'r');
577
+
578
+ if (!hasRemote) {
579
+ Ec.error('❌ 此命令必须使用 -r 参数指定远程仓库');
580
+ Ec.waiting('用法: momo apply -r [仓库名]');
581
+ process.exit(1);
582
+ }
718
583
 
719
- let selectedRepo = null;
584
+ // 加载远程仓库配置
585
+ const repositories = _loadRepositories();
586
+
587
+ if (repositories.length === 0) {
588
+ Ec.error('❌ 未找到任何远程仓库配置');
589
+ Ec.waiting(`请检查配置文件: ${REPOSITORIES_CONFIG}`);
590
+ process.exit(1);
591
+ }
720
592
 
721
- if (remoteValue) {
722
- // -r 有值:直接使用指定仓库
723
- selectedRepo = repositories.find(r => r.name === remoteValue);
724
- if (!selectedRepo) {
725
- Ec.error(`❌ 未找到仓库: ${remoteValue}`);
726
- Ec.waiting('可用的仓库:');
727
- repositories.forEach(r => {
728
- Ec.waiting(` - ${r.name}`);
729
- });
730
- process.exit(1);
731
- }
732
- } else {
733
- // -r 无值:显示仓库选择菜单
734
- selectedRepo = await _selectRepository(repositories);
735
- if (!selectedRepo) {
736
- Ec.waiting('未选择任何仓库,退出');
737
- process.exit(0);
738
- }
739
- }
593
+ // 选择仓库
594
+ const repository = await _selectRepository(repositories, remoteValue);
595
+ if (!repository) {
596
+ Ec.waiting('已取消选择仓库');
597
+ process.exit(0);
598
+ }
740
599
 
741
- await _installFromRemote(selectedRepo, isGlobal);
600
+ // 选择目标路径
601
+ const targetPathConfig = await _selectTargetPath();
602
+ if (!targetPathConfig) {
603
+ Ec.waiting('已取消选择目标路径');
742
604
  process.exit(0);
743
- } catch (error) {
744
- Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
745
- process.exit(1);
746
605
  }
606
+
607
+ // 从远程仓库安装技能
608
+ await _installFromRemote(repository, targetPathConfig.path);
609
+
610
+ process.exit(0);
747
611
  };