momo-ai 1.0.20 → 1.0.21

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 (103) hide show
  1. package/.claude/skills/algorithmic-art/LICENSE.txt +202 -0
  2. package/.claude/skills/algorithmic-art/SKILL.md +405 -0
  3. package/.claude/skills/algorithmic-art/templates/generator_template.js +223 -0
  4. package/.claude/skills/algorithmic-art/templates/viewer.html +599 -0
  5. package/.claude/skills/r2mo-rad-lain/PROMPT.md +281 -0
  6. package/.claude/skills/r2mo-rad-lain/README.md +192 -0
  7. package/.claude/skills/r2mo-rad-lain/SKILL.md +412 -0
  8. package/.claude/skills/r2mo-rad-lain/examples/argument-parsing.js +154 -0
  9. package/.claude/skills/r2mo-rad-lain/examples/file-operations.js +182 -0
  10. package/.claude/skills/r2mo-rad-lain/file-utils-api.md +281 -0
  11. package/.claude/skills/r2mo-rad-lain/menu-api.md +187 -0
  12. package/.claude/skills/r2mo-rad-lain/scripts/file-utils.js +223 -0
  13. package/.claude/skills/r2mo-rad-lain/scripts/menu.js +289 -0
  14. package/.claude/skills/r2mo-rad-lain/scripts/yaml-parser.js +209 -0
  15. package/.claude/skills/r2mo-rad-lain/templates/command.json.template +13 -0
  16. package/.claude/skills/r2mo-rad-lain/templates/executor.js.template +32 -0
  17. package/.claude/skills/r2mo-rad-lain/templates/interactive-menu.js.template +221 -0
  18. package/.cursor/mcp.json +17 -0
  19. package/.obsidian/app.json +1 -0
  20. package/.obsidian/appearance.json +4 -0
  21. package/.obsidian/community-plugins.json +4 -0
  22. package/.obsidian/core-plugins.json +33 -0
  23. package/.obsidian/plugins/ai-agent/main.js +98495 -0
  24. package/.obsidian/plugins/ai-agent/manifest.json +11 -0
  25. package/.obsidian/plugins/ai-agent/styles.css +806 -0
  26. package/.obsidian/plugins/dataview/main.js +20876 -0
  27. package/.obsidian/plugins/dataview/manifest.json +11 -0
  28. package/.obsidian/plugins/dataview/styles.css +141 -0
  29. package/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
  30. package/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
  31. package/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
  32. package/.obsidian/plugins/templater-obsidian/main.js +45 -0
  33. package/.obsidian/plugins/templater-obsidian/manifest.json +11 -0
  34. package/.obsidian/plugins/templater-obsidian/styles.css +226 -0
  35. package/.obsidian/plugins/terminal/main.js +200 -0
  36. package/.obsidian/plugins/terminal/manifest.json +14 -0
  37. package/.obsidian/plugins/terminal/styles.css +32 -0
  38. package/.obsidian/themes/AnuPpuccin/manifest.json +7 -0
  39. package/.obsidian/themes/AnuPpuccin/theme.css +9080 -0
  40. package/.obsidian/themes/Things/manifest.json +7 -0
  41. package/.obsidian/themes/Things/theme.css +1628 -0
  42. package/.obsidian/workspace.json +196 -0
  43. package/README.md +10 -123
  44. package/docs/images/logo.jpeg +0 -0
  45. package/install.sh +1 -0
  46. package/package.json +6 -2
  47. package/skills/r2mo-rad-lain/SKILL.md +101 -0
  48. package/src/_mcp/skills-server.mjs +70 -0
  49. package/src/_skill/repositories.json +16 -0
  50. package/src/commander/help.json +5 -0
  51. package/src/commander/mcp.json +13 -0
  52. package/src/commander/open.json +8 -2
  53. package/src/commander/skills.json +20 -0
  54. package/src/executor/executeEnv.js +48 -38
  55. package/src/executor/executeHelp.js +77 -16
  56. package/src/executor/executeInit.js +203 -149
  57. package/src/executor/executeMcp.js +290 -0
  58. package/src/executor/executeOpen.js +144 -125
  59. package/src/executor/executeSkills.js +747 -0
  60. package/src/executor/index.js +5 -39
  61. package/src/momo.js +2 -1
  62. package/src/utils/momo-args.js +39 -0
  63. package/src/utils/momo-file-utils.js +75 -0
  64. package/src/utils/momo-menu.js +54 -0
  65. package/src/commander/actor.json +0 -12
  66. package/src/commander/actors.json +0 -6
  67. package/src/commander/add.json +0 -12
  68. package/src/commander/agent.json +0 -12
  69. package/src/commander/agentcfg.json +0 -5
  70. package/src/commander/archive.json +0 -12
  71. package/src/commander/commit.json +0 -12
  72. package/src/commander/console.json +0 -7
  73. package/src/commander/lain.json +0 -7
  74. package/src/commander/list.json +0 -7
  75. package/src/commander/plan.json +0 -12
  76. package/src/commander/project.json +0 -12
  77. package/src/commander/pull.json +0 -6
  78. package/src/commander/push.json +0 -6
  79. package/src/commander/repo.json +0 -18
  80. package/src/commander/run.json +0 -18
  81. package/src/commander/show.json +0 -12
  82. package/src/commander/tasks.json +0 -18
  83. package/src/commander/unlock.json +0 -6
  84. package/src/commander/validate.json +0 -12
  85. package/src/executor/executeActor.js +0 -133
  86. package/src/executor/executeActors.js +0 -58
  87. package/src/executor/executeAdd.js +0 -307
  88. package/src/executor/executeAgent.js +0 -299
  89. package/src/executor/executeAgentCfg.js +0 -210
  90. package/src/executor/executeArchive.js +0 -124
  91. package/src/executor/executeCommit.js +0 -202
  92. package/src/executor/executeConsole.js +0 -142
  93. package/src/executor/executeList.js +0 -133
  94. package/src/executor/executePlan.js +0 -164
  95. package/src/executor/executeProject.js +0 -313
  96. package/src/executor/executePull.js +0 -127
  97. package/src/executor/executePush.js +0 -243
  98. package/src/executor/executeRepo.js +0 -238
  99. package/src/executor/executeRun.js +0 -644
  100. package/src/executor/executeShow.js +0 -164
  101. package/src/executor/executeTasks.js +0 -384
  102. package/src/executor/executeUnlock.js +0 -110
  103. package/src/executor/executeValidate.js +0 -210
@@ -0,0 +1,290 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const Ec = require('../epic');
5
+
6
+ // 源 MCP 脚本路径
7
+ const SOURCE_MCP_SCRIPT = path.join(__dirname, '../_mcp/skills-server.mjs');
8
+
9
+ // 必需的依赖包
10
+ const REQUIRED_DEPS = ['@modelcontextprotocol/sdk', 'zod', 'front-matter', 'chokidar'];
11
+
12
+ /**
13
+ * 检查依赖是否已安装
14
+ */
15
+ const _checkDependencies = () => {
16
+ const missing = [];
17
+ const projectRoot = path.join(__dirname, '../..');
18
+ for (const dep of REQUIRED_DEPS) {
19
+ const depPath = path.join(projectRoot, 'node_modules', dep);
20
+ if (!fs.existsSync(depPath)) {
21
+ missing.push(dep);
22
+ }
23
+ }
24
+ return { installed: missing.length === 0, missing };
25
+ };
26
+
27
+ /**
28
+ * 显示依赖安装命令
29
+ */
30
+ const _showInstallCommand = (missing) => {
31
+ console.log('');
32
+ Ec.error('❌ 缺少必要依赖,请先安装:');
33
+ console.log(` npm install ${missing.join(' ')}`.cyan);
34
+ console.log('');
35
+ };
36
+
37
+ /**
38
+ * 生成 MCP 脚本内容
39
+ */
40
+ const _getMcpScriptContent = () => {
41
+ return `#!/usr/bin/env node
42
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
43
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
44
+ import { z } from "zod";
45
+ import fs from "fs/promises";
46
+ import fsSync from "fs";
47
+ import path from "path";
48
+ import fm from "front-matter";
49
+ import os from "os";
50
+
51
+ const GLOBAL_SKILLS_DIR = process.env.MOMO_GLOBAL_SKILLS_DIR || path.join(os.homedir(), '.claude', 'skills');
52
+ const PROJECT_SKILLS_DIR = process.env.MOMO_PROJECT_SKILLS_DIR || path.join(process.cwd(), '.claude', 'skills');
53
+
54
+ const server = new McpServer({ name: "MomoSkills", version: "2.0.0" });
55
+
56
+ async function parseSkillFile(filePath) {
57
+ try {
58
+ const content = await fs.readFile(filePath, "utf8");
59
+ const parsed = fm(content);
60
+ return { attributes: parsed.attributes, body: parsed.body };
61
+ } catch (err) { return null; }
62
+ }
63
+
64
+ async function scanAndRegister(dir, source) {
65
+ if (!fsSync.existsSync(dir)) return;
66
+ try {
67
+ const items = await fs.readdir(dir);
68
+ for (const item of items) {
69
+ const skillDir = path.join(dir, item);
70
+ try {
71
+ if ((await fs.stat(skillDir)).isDirectory()) {
72
+ const skillFile = path.join(skillDir, 'SKILL.md');
73
+ if (fsSync.existsSync(skillFile)) {
74
+ await registerTool(item, skillFile, source);
75
+ }
76
+ }
77
+ } catch(e){}
78
+ }
79
+ } catch(e) {}
80
+ }
81
+
82
+ async function registerTool(folderName, filePath, source) {
83
+ try {
84
+ const parsed = await parseSkillFile(filePath);
85
+ if(!parsed || !parsed.attributes) return;
86
+ const attr = parsed.attributes;
87
+ const toolName = attr.name || folderName;
88
+
89
+ const argsSchema = {};
90
+ if (Array.isArray(attr.arguments)) {
91
+ attr.arguments.forEach(arg => {
92
+ argsSchema[arg.name] = z.string().describe(arg.description || "");
93
+ });
94
+ }
95
+
96
+ server.tool(toolName, attr.description || "No desc", argsSchema, async (args) => {
97
+ let res = parsed.body;
98
+ for (const [k, v] of Object.entries(args)) res = res.replace(new RegExp(\`{{\\\${k}}}\`, 'g'), v);
99
+ return { content: [{ type: "text", text: res }] };
100
+ });
101
+ } catch(e) { console.error(e); }
102
+ }
103
+
104
+ async function main() {
105
+ await scanAndRegister(PROJECT_SKILLS_DIR, 'project');
106
+ await scanAndRegister(GLOBAL_SKILLS_DIR, 'global');
107
+ const transport = new StdioServerTransport();
108
+ await server.connect(transport);
109
+ }
110
+ main().catch(err => { console.error(err); process.exit(1); });
111
+ `;
112
+ };
113
+
114
+ /**
115
+ * 确保源 MCP 脚本存在
116
+ */
117
+ const _ensureSourceScript = () => {
118
+ const mcpDir = path.dirname(SOURCE_MCP_SCRIPT);
119
+ if (!fs.existsSync(mcpDir)) {
120
+ fs.mkdirSync(mcpDir, { recursive: true });
121
+ }
122
+ fs.writeFileSync(SOURCE_MCP_SCRIPT, _getMcpScriptContent());
123
+ };
124
+
125
+ /**
126
+ * 拷贝 MCP 脚本到项目目录
127
+ * @param {string} projectDir 项目目录
128
+ * @returns {string} 目标脚本路径
129
+ */
130
+ const _copyMcpScriptToProject = (projectDir) => {
131
+ const targetDir = path.join(projectDir, '.r2mo', 'mcpserver');
132
+ const targetScript = path.join(targetDir, 'skills-server.mjs');
133
+
134
+ // 创建目录
135
+ if (!fs.existsSync(targetDir)) {
136
+ fs.mkdirSync(targetDir, { recursive: true });
137
+ Ec.waiting(`创建目录: ${targetDir}`);
138
+ }
139
+
140
+ // 拷贝脚本
141
+ const content = _getMcpScriptContent();
142
+ fs.writeFileSync(targetScript, content);
143
+ Ec.waiting(`✓ MCP 脚本已拷贝到: ${targetScript}`);
144
+
145
+ return targetScript;
146
+ };
147
+
148
+ /**
149
+ * 更新 .cursor/mcp.json 配置
150
+ * @param {string} projectDir 项目目录
151
+ * @param {string} mcpScriptPath MCP 脚本路径
152
+ * @param {string} skillsDir 技能目录路径
153
+ */
154
+ const _updateMcpConfig = (projectDir, mcpScriptPath, skillsDir) => {
155
+ const cursorDir = path.join(projectDir, '.cursor');
156
+ const mcpConfigPath = path.join(cursorDir, 'mcp.json');
157
+
158
+ // 确保 .cursor 目录存在
159
+ if (!fs.existsSync(cursorDir)) {
160
+ fs.mkdirSync(cursorDir, { recursive: true });
161
+ Ec.waiting(`创建目录: ${cursorDir}`);
162
+ }
163
+
164
+ // 读取现有配置或创建新配置
165
+ let config = { mcpServers: {} };
166
+
167
+ if (fs.existsSync(mcpConfigPath)) {
168
+ try {
169
+ const content = fs.readFileSync(mcpConfigPath, 'utf8');
170
+ config = JSON.parse(content);
171
+ if (!config.mcpServers) {
172
+ config.mcpServers = {};
173
+ }
174
+ Ec.waiting(`读取现有配置: ${mcpConfigPath}`);
175
+ } catch (e) {
176
+ Ec.warn(`配置文件解析失败,将创建新配置`);
177
+ config = { mcpServers: {} };
178
+ }
179
+ }
180
+
181
+ // 添加/更新 momo-skills 配置
182
+ config.mcpServers['momo-skills'] = {
183
+ command: 'node',
184
+ args: [mcpScriptPath],
185
+ env: {
186
+ NODE_ENV: 'production',
187
+ MOMO_PROJECT_SKILLS_DIR: skillsDir,
188
+ MOMO_GLOBAL_SKILLS_DIR: path.join(os.homedir(), '.claude', 'skills')
189
+ },
190
+ disabled: false,
191
+ alwaysAllow: []
192
+ };
193
+
194
+ // 写入配置
195
+ fs.writeFileSync(mcpConfigPath, JSON.stringify(config, null, 2));
196
+ Ec.info(`✓ MCP 配置已更新: ${mcpConfigPath}`);
197
+
198
+ return mcpConfigPath;
199
+ };
200
+
201
+ /**
202
+ * 确保 .r2mo/mcpserver 在 .gitignore 中
203
+ * @param {string} projectDir 项目目录
204
+ */
205
+ const _ensureGitIgnore = (projectDir) => {
206
+ const gitignorePath = path.join(projectDir, '.gitignore');
207
+ const ignoreEntry = '.r2mo/mcpserver';
208
+
209
+ try {
210
+ let content = '';
211
+ if (fs.existsSync(gitignorePath)) {
212
+ content = fs.readFileSync(gitignorePath, 'utf8');
213
+ }
214
+
215
+ const lines = content.split('\n');
216
+ const hasEntry = lines.some(line => line.trim() === ignoreEntry);
217
+
218
+ if (!hasEntry) {
219
+ const newContent = content.endsWith('\n') || content === ''
220
+ ? content + ignoreEntry + '\n'
221
+ : content + '\n' + ignoreEntry + '\n';
222
+ fs.writeFileSync(gitignorePath, newContent);
223
+ Ec.waiting(`已将 ${ignoreEntry} 添加到 .gitignore`);
224
+ }
225
+ } catch (error) {
226
+ Ec.warn(`更新 .gitignore 失败: ${error.message}`);
227
+ }
228
+ };
229
+
230
+ module.exports = async (options) => {
231
+ try {
232
+ const projectDir = process.cwd();
233
+ const skillsDir = path.join(projectDir, '.claude', 'skills');
234
+
235
+ // 1. 检查依赖
236
+ Ec.waiting('正在检查依赖...');
237
+ const { installed, missing } = _checkDependencies();
238
+
239
+ if (!installed) {
240
+ _showInstallCommand(missing);
241
+ process.exit(1);
242
+ }
243
+ Ec.info('✓ 所有依赖已安装');
244
+
245
+ // 2. 确保源脚本存在
246
+ _ensureSourceScript();
247
+
248
+ // 3. 拷贝 MCP 脚本到项目
249
+ console.log('');
250
+ const mcpScriptPath = _copyMcpScriptToProject(projectDir);
251
+
252
+ // 4. 更新 .cursor/mcp.json
253
+ console.log('');
254
+ const mcpConfigPath = _updateMcpConfig(projectDir, mcpScriptPath, skillsDir);
255
+
256
+ // 5. 更新 .gitignore
257
+ _ensureGitIgnore(projectDir);
258
+
259
+ // 6. 显示结果
260
+ // 将项目目录内的绝对路径转换为 {ROOT} 相对路径
261
+ const formatPath = (absPath) => {
262
+ if (absPath.startsWith(projectDir)) {
263
+ return absPath.replace(projectDir, '{ROOT}');
264
+ }
265
+ return absPath;
266
+ };
267
+
268
+ const globalSkillsPath = path.join(os.homedir(), '.claude', 'skills');
269
+
270
+ console.log('');
271
+ console.log('─'.repeat(60));
272
+ console.log(' MCP Skills Server 配置完成'.green);
273
+ console.log('─'.repeat(60));
274
+ console.log(` 脚本位置: ${formatPath(mcpScriptPath)}`);
275
+ console.log(` 配置文件: ${formatPath(mcpConfigPath)}`);
276
+ console.log(` 项目技能: ${formatPath(skillsDir)}`);
277
+ console.log(` 全局技能: ${globalSkillsPath}`);
278
+ console.log('─'.repeat(60));
279
+ console.log('');
280
+ console.log(` {ROOT} = 当前目录`.gray);
281
+ console.log('');
282
+
283
+ Ec.info('🎉 配置完成!重启 Cursor 后生效');
284
+
285
+ process.exit(0);
286
+ } catch (error) {
287
+ Ec.error(`❌ 执行失败: ${error.message}`);
288
+ process.exit(1);
289
+ }
290
+ };
@@ -1,159 +1,178 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
1
3
  const Ec = require('../epic');
2
- const {spawn} = require('child_process');
3
-
4
- module.exports = async (options) => {
5
- // 参数提取
6
- const parsed = Ec.parseArgument(options);
7
-
8
- try {
9
- // 定义可用的AI工具
10
- const aiTools = [
11
- {name: 'Trae', command: 'trae'},
12
- {name: 'Cursor', command: 'cursor'},
13
- {name: 'Lingma', command: 'lingma'},
14
- {name: 'Kiro', command: 'kiro'}
15
- ];
16
-
17
- // 检查哪些工具可用
18
- const availableTools = [];
19
- for (const tool of aiTools) {
20
- if (await _isCommandAvailable(tool.command)) {
21
- availableTools.push(tool);
22
- }
23
- }
24
-
25
- // 如果没有可用工具,提示用户安装
26
- if (availableTools.length === 0) {
27
- Ec.error('❌ 未找到可用的AI工具,请确保已安装以下工具之一:Trae、Cursor、Lingma、Kiro');
28
- Ec.askClose();
29
- process.exit(1);
30
- }
31
-
32
- // 如果只有一个工具可用,直接使用它
33
- if (availableTools.length === 1) {
34
- const tool = availableTools[0];
35
- let toolDisplayName = tool.name;
36
- if (tool.name === 'Trae' || tool.name === 'Cursor') {
37
- toolDisplayName = `${tool.name}(${'Multi-Agent'.cyan})`;
38
- }
39
- if (tool.name === 'Trae') {
40
- Ec.waiting(`🔍 检测到可用工具: ${toolDisplayName.green}`);
41
- } else {
42
- Ec.waiting(`🔍 检测到可用工具: ${toolDisplayName}`);
43
- }
44
- await _openWithTool(tool.command);
45
- Ec.askClose();
46
- process.exit(0);
47
- }
48
-
49
- // 显示交互式选择菜单
50
- Ec.waiting('🔍 检测到多个可用的AI工具,请选择要使用的工具:');
51
- const choices = availableTools.map((tool, index) => {
52
- let toolDisplayName = tool.name;
53
- if (tool.name === 'Trae' || tool.name === 'Cursor') {
54
- toolDisplayName = `${tool.name}(${'Multi-Agent'.cyan})`;
55
- }
56
- if (tool.name === 'Trae') {
57
- return `${index + 1}. ${toolDisplayName} ${'推荐'.green}`;
58
- } else {
59
- return `${index + 1}. ${toolDisplayName}`;
60
- }
61
- });
62
-
63
- // 添加退出选项
64
- choices.push(`${choices.length + 1}. 退出`);
65
-
66
- for (const choice of choices) {
67
- Ec.waiting(choice);
68
- }
69
-
70
- // 获取用户选择
71
- const answer = await Ec.ask('请输入选项编号: ');
72
- const selectedIndex = parseInt(answer) - 1;
4
+ const { spawn } = require('child_process');
5
+ const { selectSingle } = require('../utils/momo-menu');
6
+
7
+ // AI 工具配置
8
+ const AI_TOOLS = [
9
+ {
10
+ name: 'Antigravity',
11
+ command: 'antigravity',
12
+ description: 'Claude Code 官方客户端',
13
+ openMethod: 'spawn' // 使用 spawn 直接执行
14
+ },
15
+ {
16
+ name: 'Trae',
17
+ command: 'trae',
18
+ description: '字节跳动 AI IDE',
19
+ openMethod: 'spawn'
20
+ },
21
+ {
22
+ name: 'Cursor',
23
+ command: 'cursor',
24
+ description: 'Cursor AI IDE',
25
+ openMethod: 'spawn'
26
+ }
27
+ ];
73
28
 
74
- // 检查用户选择
75
- if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex >= availableTools.length) {
76
- if (selectedIndex === availableTools.length) {
77
- Ec.waiting('👋 已取消操作');
78
- Ec.askClose();
79
- process.exit(0);
29
+ /**
30
+ * 解析 -d 参数
31
+ * @returns {string|null} 目录路径
32
+ */
33
+ const _parseDirArg = () => {
34
+ const args = process.argv.slice(3);
35
+
36
+ for (let i = 0; i < args.length; i++) {
37
+ if (args[i] === '-d' || args[i] === '--dir') {
38
+ const next = args[i + 1];
39
+ if (next && !next.startsWith('-')) {
40
+ return next;
80
41
  }
81
- Ec.error('❌ 无效的选择');
82
- Ec.askClose();
83
- process.exit(1);
84
42
  }
85
-
86
- // 执行选择的工具
87
- const selectedTool = availableTools[selectedIndex];
88
- let toolDisplayName = selectedTool.name;
89
- if (selectedTool.name === 'Trae' || selectedTool.name === 'Cursor') {
90
- toolDisplayName = `${selectedTool.name}(${'Multi-Agent'.cyan})`;
91
- }
92
- Ec.waiting(`🚀 正在使用 ${toolDisplayName} 打开项目...`);
93
- await _openWithTool(selectedTool.command);
94
- Ec.askClose();
95
- process.exit(0);
96
-
97
- } catch (error) {
98
- console.error(error);
99
- Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
100
- if (process.platform === 'win32') {
101
- Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
102
- }
103
- Ec.askClose();
104
- process.exit(1);
105
43
  }
44
+
45
+ return null;
106
46
  };
107
47
 
108
48
  /**
109
49
  * 检查命令是否可用
110
50
  * @param {string} command 命令名称
111
- * @returns {Promise<boolean>} 命令是否可用
51
+ * @returns {Promise<boolean>}
112
52
  */
113
53
  const _isCommandAvailable = async (command) => {
114
54
  return new Promise((resolve) => {
115
- // 在 Windows 上使用 where 命令,在其他系统上使用 which 命令
116
55
  const whereCmd = process.platform === 'win32' ? 'where' : 'which';
117
- const childProcess = spawn(whereCmd, [command]);
56
+ const childProcess = spawn(whereCmd, [command], { stdio: 'pipe' });
118
57
  childProcess.on('close', (code) => {
119
58
  resolve(code === 0);
120
59
  });
60
+ childProcess.on('error', () => {
61
+ resolve(false);
62
+ });
121
63
  });
122
64
  };
123
65
 
124
66
  /**
125
- * 使用指定工具打开当前目录
126
- * @param {string} tool 工具命令
67
+ * 使用 spawn 方式打开目录
68
+ * @param {string} command 命令
69
+ * @param {string} targetDir 目标目录
127
70
  */
128
- const _openWithTool = async (tool) => {
71
+ const _openWithSpawn = async (command, targetDir) => {
129
72
  return new Promise((resolve, reject) => {
130
- // 使用工具命令打开当前目录
131
- const child = spawn(tool, ['.'], {
73
+ const child = spawn(command, [targetDir], {
132
74
  stdio: 'inherit',
133
- cwd: process.cwd()
75
+ detached: true
134
76
  });
135
77
 
136
- child.on('close', (code) => {
137
- if (code === 0) {
138
- Ec.info(`✅ 项目已成功在 ${tool} 中打开`);
139
- resolve();
140
- } else {
141
- Ec.error(`❌ ${tool} 执行失败,退出码: ${code}`);
142
- reject(new Error(`${tool} 执行失败,退出码: ${code}`));
143
- }
144
- });
78
+ // 分离子进程,让它独立运行
79
+ child.unref();
80
+
81
+ // 给一点时间让进程启动
82
+ setTimeout(() => {
83
+ resolve();
84
+ }, 500);
145
85
 
146
86
  child.on('error', (error) => {
147
87
  if (error.code === 'ENOENT') {
148
- Ec.error(`❌ 未找到命令: ${tool},请确保已正确安装`);
149
- if (process.platform === 'win32') {
150
- Ec.waiting('💡 Windows 用户提示: 请确保工具已正确安装并在 PATH 环境变量中');
151
- }
152
- reject(new Error(`未找到命令: ${tool},请确保已正确安装`));
88
+ reject(new Error(`未找到命令: ${command}`));
153
89
  } else {
154
- Ec.error(`❌ 执行 ${tool} 时发生错误: ${error.message}`);
155
90
  reject(error);
156
91
  }
157
92
  });
158
93
  });
159
- };
94
+ };
95
+
96
+ /**
97
+ * 打开目录
98
+ * @param {Object} tool 工具配置
99
+ * @param {string} targetDir 目标目录
100
+ */
101
+ const _openDirectory = async (tool, targetDir) => {
102
+ Ec.waiting(`🚀 正在使用 ${tool.name.cyan} 打开目录...`);
103
+
104
+ switch (tool.openMethod) {
105
+ case 'spawn':
106
+ default:
107
+ await _openWithSpawn(tool.command, targetDir);
108
+ break;
109
+ }
110
+
111
+ Ec.info(`✅ 已启动 ${tool.name}`);
112
+ };
113
+
114
+ module.exports = async (options) => {
115
+ try {
116
+ // 1. 解析目录参数
117
+ const dirArg = _parseDirArg();
118
+ let targetDir = dirArg ? path.resolve(dirArg) : process.cwd();
119
+
120
+ // 验证目录是否存在
121
+ if (!fs.existsSync(targetDir)) {
122
+ Ec.error(`❌ 目录不存在: ${targetDir}`);
123
+ process.exit(1);
124
+ }
125
+
126
+ if (!fs.statSync(targetDir).isDirectory()) {
127
+ Ec.error(`❌ 路径不是目录: ${targetDir}`);
128
+ process.exit(1);
129
+ }
130
+
131
+ // 2. 检查可用工具
132
+ const availableTools = [];
133
+
134
+ for (const tool of AI_TOOLS) {
135
+ if (await _isCommandAvailable(tool.command)) {
136
+ availableTools.push(tool);
137
+ }
138
+ }
139
+
140
+ if (availableTools.length === 0) {
141
+ Ec.error('❌ 未找到可用的 AI 工具');
142
+ console.log('');
143
+ console.log(' 支持的工具:'.gray);
144
+ AI_TOOLS.forEach(tool => {
145
+ console.log(` - ${tool.name}: ${tool.description}`.gray);
146
+ });
147
+ process.exit(1);
148
+ }
149
+
150
+ // 3. 显示目标目录
151
+ console.log('');
152
+ Ec.info(`📂 目标目录: ${targetDir.cyan}`);
153
+ console.log('');
154
+
155
+ // 4. 交互式选择工具
156
+ const menuItems = availableTools.map(tool => ({
157
+ name: tool.name,
158
+ description: tool.description,
159
+ _tool: tool
160
+ }));
161
+
162
+ const selected = await selectSingle(menuItems, '选择 AI 工具');
163
+
164
+ if (!selected) {
165
+ Ec.waiting('已取消');
166
+ process.exit(0);
167
+ }
168
+
169
+ // 5. 打开目录
170
+ await _openDirectory(selected._tool, targetDir);
171
+
172
+ process.exit(0);
173
+
174
+ } catch (error) {
175
+ Ec.error(`❌ 执行失败: ${error.message}`);
176
+ process.exit(1);
177
+ }
178
+ };