momo-ai 1.0.5 → 1.0.6

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.
@@ -0,0 +1,50 @@
1
+ {
2
+ "maven": {
3
+ "file": "pom.xml",
4
+ "target": "reference/maven"
5
+ },
6
+ "gradle": {
7
+ "file": "build.gradle",
8
+ "target": "reference/gradle"
9
+ },
10
+ "npm": {
11
+ "file": "package.json",
12
+ "target": "reference/npm"
13
+ },
14
+ "rust": {
15
+ "file": "Cargo.toml",
16
+ "target": "reference/rust"
17
+ },
18
+ "go": {
19
+ "file": "go.mod",
20
+ "target": "reference/go"
21
+ },
22
+ "ruby": {
23
+ "file": "Gemfile",
24
+ "target": "reference/ruby"
25
+ },
26
+ "python": {
27
+ "file": "requirements.txt",
28
+ "target": "reference/python"
29
+ },
30
+ "php": {
31
+ "file": "composer.json",
32
+ "target": "reference/php"
33
+ },
34
+ "dotnet": {
35
+ "file": "project.csproj",
36
+ "target": "reference/dotnet"
37
+ },
38
+ "java": {
39
+ "file": "pom.xml",
40
+ "target": "reference/java"
41
+ },
42
+ "cmake": {
43
+ "file": "CMakeLists.txt",
44
+ "target": "reference/cmake"
45
+ },
46
+ "make": {
47
+ "file": "Makefile",
48
+ "target": "reference/make"
49
+ }
50
+ }
package/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  ## 1. 介绍
7
7
 
8
+ ### 1.1. 功能说明
9
+
8
10
  当前工具会在操作系统中安装 `momo` 命令,使用它进行 `SDD - Spec Driven Development` 开发:
9
11
 
10
12
  1. 参考:`OpenSpec / Spec-Kit / Kiro`
@@ -22,6 +24,30 @@
22
24
 
23
25
  > 除开 `momo` 命令后续会提供和模型直接交互的 `lain` 命令,近似 `iFlow / openspec` 的功能。
24
26
 
27
+ ### 1.2. 开始步骤
28
+
29
+ 1. 使用 `momo init` 初始化协同工程项目(运行之前可使用 `momo env` 检查环境)。
30
+ 2. 使用 `momo repo` 添加项目代码库以及工程实例(有多少个 `Agent` 工作就添加多少工程实例),添加完成后可使用 `momo open`
31
+ 直接打开工程。
32
+ 3. 更新 `project.md / project-model.md / requirement.md` 的细节文档(可用AI帮助拆分和书写)
33
+ 4. 使用 `momo add -n 需求名称` 添加细节需求(可用AI帮助拆分和书写:`momo plan -n 需求名称`)
34
+ 5. 使用 `momo tasks` 列出所有任务,并且使用 `momo run` 运行任务得到任务提示词
35
+
36
+ > 带 (CV) 📋️ 标记的命令——运行成功后可直接“Ctrl + V”粘贴到 TRAE / Lingma / Cursor / Kiro 中执行。
37
+
38
+ ### 1.3. 项目计划
39
+
40
+ 1. 使用 `momo project` 将后端项目拷贝到 `reference` 目录中(主要是提供数据模型)。
41
+ 2. (唯一的人工处理)更新 `project.md` 中的所有信息,核心包括:
42
+ - 功能描述
43
+ - 场景描述等
44
+ - 风格
45
+ - 技术栈
46
+ 3. 使用 `momo agentcfg` 给 TRAE 工具配置智能体,主要是导入部分已经定制好的智能体。
47
+ 4. 使用 `momo agent -a "智能体名称"` 调用智能体进行开发之前的准备工作,目前支持的智能体可直接使用 `momo agentcfg` 查看。
48
+
49
+ <hr/>
50
+
25
51
  ## 2. 工具使用
26
52
 
27
53
  ### 2.1. 安装
@@ -32,67 +58,55 @@ npm install -g momo-ai
32
58
  momo help
33
59
  ```
34
60
 
35
- ### 2.2. 操作步骤
36
-
37
- 1. 使用 `momo init` 初始化协同工程项目(运行之前可使用 `momo env` 检查环境)。
38
- 2. 使用 `momo repo` 添加项目代码库以及工程实例(有多少个 `Agent` 工作就添加多少工程实例),添加完成后可使用 `momo open`
39
- 直接打开工程。
40
- 3. 更新 `project.md / project-model.md / requirement.md` 的细节文档(可用AI帮助拆分和书写)
41
- 4. 使用 `momo add -n 需求名称` 添加细节需求(可用AI帮助拆分和书写:`momo plan -n 需求名称`)
42
- 5. 使用 `momo tasks` 列出所有任务,并且使用 `momo run` 运行任务得到任务提示词
43
-
44
- > 带 (CV) 标记的命令运行成功后可直接“Ctrl + V”粘贴到 TRAE / Lingma / Cursor / Kiro 中执行。
45
-
46
61
  ### 2.3. 命令:不定模型
47
62
 
48
63
  ```bash
49
64
  # ------------ 工程初始化 -----------------
50
- # 检查环境相关信息
51
- momo env # 环境检查
65
+ # 🌷 环境检查、工程和代码初始化、工具启动等
66
+ momo env # 环境检查
52
67
 
53
- # momo init
54
- momo init -d app-fly # 初始化 directory = app-fly 的项目
55
- momo init # 初始化当前项目
68
+ momo init -d app-fly # 初始化 directory = app-fly 的项目
69
+ momo init # 初始化当前项目
56
70
 
57
- # momo repo
58
71
  momo repo -a https://xxx/repo.git # 克隆远程代码库 -> source/repo/develop-01(名称靠解析) -> git submodule
59
72
  momo repo -a xxx -i 10 # 克隆远程代码库(10个副本)
60
73
 
61
74
  momo open # 交互式使用 TRAE / Lingma / Cursor / Kiro 打开当前项目
62
75
 
63
- # ------------ 需求管理 -----------------
64
- # momo add
65
- momo add -n "需求名称" # 添加新需求 -> changes 下追加新内容 -> 包括任务
66
- momo add -n "需求 md 文档路径" # 添加需求文档 -> changes 下追加新内容 -> 包括任务,如果带后缀直接用文件名做需求名称
76
+ momo project -s "项目路径" # 将路径中的显示拷贝到当前项目的 reference 目录下,若不提供 -s 则直接列举所有支持的项目类型
77
+
78
+ momo agentcfg # 列出所有智能体,并且打开浏览器配置
79
+ momo agent -a "智能体名称" # 📋️ 调用智能体 -> prompt 得到提示词到剪切板
67
80
 
68
- # momo plan
69
- momo plan -n "需求名称" # 生成需求的整体开发计划 -> prompt 剪切板
81
+ # ------------ 需求相关 -----------------
82
+ # 🌷 需求管理:添加、归档、校验、列出、显示
83
+ momo add -n "需求名称" # 📋️ 添加新需求 -> changes 下追加新内容 -> 包括任务
84
+ momo add -n "需求(MD)文档路径" # 📋️ 添加需求文档 -> changes 下追加新内容 -> 包括任务,如果带后缀直接用文件名做需求名称
70
85
 
71
- # momo archive
72
86
  momo archive -n "需求名称" # 需求归档 -> archive 下追加新内容,删除 changes 下内容
73
87
 
74
- # momo show
75
88
  momo show -n "需求名称" # 显示需求 -> 显示 changes 下内容
76
89
 
77
- # momo validate
78
90
  momo validate -n "需求名称" # 需求校验 -> 校验 changes 下内容
79
91
 
80
- # momo list
81
92
  momo list # 列出所有需求 -> changes 下内容
82
93
 
83
- # ------------ 任务执行 -----------------
84
- # momo tasks
85
- momo tasks # 显示任务 -> 需求 / 任务 / 状态 -> 状态 = 待办 / 进行中 / 已完成
86
94
 
87
- # momo actors
95
+ # 🌷 需求分析:计划、执行、拆分
96
+ momo plan -n "需求名称" # 📋️ 生成需求的整体开发计划 -> prompt 剪切板
97
+
98
+ # ------------ 任务执行 -----------------
99
+ # 🌷 任务执行:定义角色、执行任务
88
100
  momo actors # 列出所有角色
89
101
 
90
- # momo actor
91
102
  momo actor -a "角色名称" # 添加新角色 -> 交互式命令,每个角色可以指定不同的大模型
92
103
 
93
- # momo run ( 生成提示词 -> 剪切板,自动计算空闲的 source-01 )
94
- momo run -a {actor} -t {task} # 将任务分配给角色,检查执行目录看哪些任务正在执行
95
- momo run # 从菜单中选择任务
104
+
105
+ momo tasks # 📋️ 显示任务 -> 需求 / 任务 / 状态 -> 状态 = 待办 / 进行中 / 已完成
106
+
107
+ momo run -a {actor} -t {task} # 📋️ 将任务分配给角色,检查执行目录看哪些任务正在执行
108
+ momo run # 📋️ 从菜单中选择任务
109
+
96
110
  momo unlock # 解锁任务 -> 任务状态 = 待办
97
111
  ```
98
112
 
@@ -100,6 +114,8 @@ momo unlock # 解锁任务 -> 任务状态 = 待办
100
114
 
101
115
  > 控制台开发中(Lain 模式)
102
116
 
117
+ <hr/>
118
+
103
119
  ## 3. 参考链接
104
120
 
105
121
  ### 3.1. 旧版
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "momo-ai",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Rachel Momo ( OpenSpec )",
5
5
  "main": "src/momo.js",
6
6
  "bin": {
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "领域建模师",
3
+ "id": "momo-datamodel",
4
+ "uri": "https://s.trae.ai/a/a9dcaa"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "模块需求分析师",
3
+ "id": "momo-module-req",
4
+ "uri": "https://s.trae.ai/a/c4fc46"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "系统需求分析师",
3
+ "id": "momo-system-req",
4
+ "uri": "https://s.trae.ai/a/d0c6b9"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "任务规划师",
3
+ "id": "momo-task",
4
+ "uri": "https://s.trae.ai/a/1fdc4e"
5
+ }
File without changes
@@ -1,4 +1,4 @@
1
- # 添加需求提示词
1
+ # 需求计划提示词
2
2
 
3
3
  <!-- BEGIN -->
4
4
  阅读 specification/project.md 中的基本需求信息,根据 <%= NAME %> 的描述信息,重新制定需求的开发计划,更改掉 specification/changes/<%= NAME %>/ 目录中的需求说明,重新制定任务清单并且更新(子任务清单在specification/changes/<%= NAME %>/tasks/目录下),项目本身的技术栈和数据模型可参考 specification/project-model.md 文件。
@@ -0,0 +1,8 @@
1
+ # 领域建模提示词
2
+
3
+ <!-- BEGIN -->
4
+ 分析 reference/ 下的项目,查找对应的数据模型,根据查找的数据模型更新 specification/project-model.md 文档作为当前项目所需的领域模型核心文档
5
+
6
+ 1. 主要分析实体模型、关系模型、常量和枚举等。
7
+ 2. 若分析过程中出现了多种不同的项目则让我选择用哪种项目做初始分析。
8
+ <!-- END -->
@@ -0,0 +1,11 @@
1
+ # 模块需求提示词
2
+
3
+ <!-- BEGIN -->
4
+ 详细解读 specification/requirement.md 需求文档中的模块和每个模块的功能列表,在 specification/changes/ 目录之下创建模块的核心文档,每个模块用 MXX- 前缀,其中 XX 是模块编号。
5
+
6
+ 1. 每个模块中包括 tasks/ 目录、任务明细文档存放位置。
7
+ 2. 每个模块中包括 proposal.md,此文档中用来存放模块的详细需求。
8
+ 3. 每个模块中包括 tasks.md,此文档中是描述所有任务的总清单。
9
+ 4. 模块所需的数据模型可参考 specification/project-model.md 文档。
10
+ 5. 有多少模块在于你的模块的整体设计,记得模块内部业务要形成完整闭环。
11
+ <!-- END -->
@@ -0,0 +1,9 @@
1
+ # 系统需求提示词
2
+
3
+ <!-- BEGIN -->
4
+ 根据人工提供的 specification/project.md 的详细需求描述进行核心分析,生成一份当前项目的比较规范的需求描述文档,用文档更新 specification/requirement.md 文档,这份需求文档的读者是前端架构师和前端工程师,所以一定要阐述清楚整个系统实现想要达到的效果和功能
5
+
6
+ 1. 数据模型可以参考 specification/project-model.md 文档。
7
+ 2. 技术栈信息可以直接从 specification/project.md 需求文档中提取。
8
+ 3. 输出文档中一定要输出的核心内容:模块清单、模块中的功能列表。
9
+ <!-- END -->
@@ -0,0 +1,11 @@
1
+ # 领域建模提示词
2
+
3
+ <!-- BEGIN -->
4
+ 解读 specification/changes/ 下的所有模块(必须是 MXX- 开头的目录)对应的变更内容,并生成领域模型,根据每个目录中的 tasks.md 生成对应的任务并且更新 tasks.md 文件:
5
+
6
+ 1. 每个模块的详细需求位于 specification/changes/MXX-模块/proposal.md 文件中。
7
+ 2. 每个任务存放在 specification/changes/MXX-模块/tasks/ 目录下。
8
+ 3. 任务文件名统一成 MXX-TNNN.md,其中 NN 是任务编号,NNN 是任务编号,编号长度不足用 0 填充。
9
+ 4. 每个任务文件中要写出任务实施过程的详细步骤。
10
+ 5. 系统相关的数据模型参考 specification/data-model.md 文件。
11
+ <!-- END -->
@@ -0,0 +1,12 @@
1
+ {
2
+ "executor": "executeAgent",
3
+ "description": "加载并复制指定 Agent 的提示词到剪切板",
4
+ "command": "agent",
5
+ "options": [
6
+ {
7
+ "name": "agent",
8
+ "alias": "a",
9
+ "description": "智能体名称(Arg Name)"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "executor": "executeAgentCfg",
3
+ "description": "配置和打开系统内置 agents",
4
+ "command": "agentcfg"
5
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "executor": "executeProject",
3
+ "description": "引用外部项目到 reference 目录中",
4
+ "command": "project",
5
+ "options": [
6
+ {
7
+ "name": "source",
8
+ "alias": "s",
9
+ "description": "项目路径,必须提供"
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,214 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const util = require('util');
4
+ const Ec = require('../epic');
5
+
6
+ // 将 fs 方法转换为 Promise 版本
7
+ const fsAsync = {
8
+ readFile: util.promisify(fs.readFile),
9
+ readdir: util.promisify(fs.readdir),
10
+ stat: util.promisify(fs.stat)
11
+ };
12
+
13
+ /**
14
+ * 获取所有 agents 信息
15
+ * @returns {Array} agents 信息数组
16
+ */
17
+ const _getAgents = () => {
18
+ const agentsDir = path.resolve(__dirname, '../_agent');
19
+ const agents = [];
20
+
21
+ if (!fs.existsSync(agentsDir)) {
22
+ return agents;
23
+ }
24
+
25
+ // 遍历 _agent 目录下的所有子目录
26
+ const types = fs.readdirSync(agentsDir).filter(file =>
27
+ fs.statSync(path.join(agentsDir, file)).isDirectory()
28
+ );
29
+
30
+ // 遍历每个类型目录
31
+ types.forEach(type => {
32
+ const typeDir = path.join(agentsDir, type);
33
+ const files = fs.readdirSync(typeDir).filter(file => file.endsWith('.json'));
34
+
35
+ // 遍历每个 json 文件
36
+ files.forEach(file => {
37
+ try {
38
+ const filePath = path.join(typeDir, file);
39
+ const content = fs.readFileSync(filePath, 'utf8');
40
+ const agentInfo = JSON.parse(content);
41
+
42
+ agents.push({
43
+ argName: file.replace('momo-', '').replace('.json', ''),
44
+ type: type,
45
+ id: agentInfo.id,
46
+ name: agentInfo.name,
47
+ uri: agentInfo.uri,
48
+ filePath: filePath
49
+ });
50
+ } catch (error) {
51
+ Ec.waiting(`⚠️ 无法解析文件: ${file} - ${error.message}`);
52
+ }
53
+ });
54
+ });
55
+
56
+ return agents;
57
+ };
58
+
59
+ /**
60
+ * 从 EJS 模板中提取内容
61
+ * @param {string} templatePath 模板路径
62
+ * @returns {Promise<string>} 提取的模板内容
63
+ */
64
+ const _extractTemplateContent = async (templatePath) => {
65
+ try {
66
+ const content = await fsAsync.readFile(templatePath, 'utf8');
67
+ const lines = content.split('\n');
68
+ let beginIndex = -1;
69
+ let endIndex = -1;
70
+
71
+ for (let i = 0; i < lines.length; i++) {
72
+ if (lines[i].includes('<!-- BEGIN -->')) {
73
+ beginIndex = i;
74
+ } else if (lines[i].includes('<!-- END -->')) {
75
+ endIndex = i;
76
+ break;
77
+ }
78
+ }
79
+
80
+ if (beginIndex !== -1 && endIndex !== -1) {
81
+ // 提取 BEGIN 和 END 之间的内容(不包括 BEGIN 和 END 行)
82
+ return lines.slice(beginIndex + 1, endIndex).join('\n');
83
+ }
84
+
85
+ return content;
86
+ } catch (error) {
87
+ throw new Error(`无法读取模板文件: ${error.message}`);
88
+ }
89
+ };
90
+
91
+ /**
92
+ * 将内容复制到剪贴板
93
+ * @param {string} content 要复制的内容
94
+ */
95
+ const _copyToClipboard = async (content) => {
96
+ const { outCopy } = require('../epic/momo.fn.out');
97
+ try {
98
+ // 先打印内容,按行打印并保留前缀
99
+ Ec.waiting('📄 提示词内容:');
100
+ Ec.waiting('----------------------------------------');
101
+ const lines = content.split('\n');
102
+ lines.forEach(line => {
103
+ Ec.waiting(line);
104
+ });
105
+ Ec.waiting('----------------------------------------');
106
+
107
+ // 再复制到剪贴板
108
+ await outCopy(content);
109
+ Ec.waiting('✅ 提示词已复制到剪贴板');
110
+ } catch (error) {
111
+ Ec.waiting(`复制到剪贴板失败: ${error.message}`);
112
+ // 提供备选方案
113
+ Ec.waiting('提示词内容:');
114
+ const lines = content.split('\n');
115
+ lines.forEach(line => {
116
+ Ec.waiting(line);
117
+ });
118
+ }
119
+ };
120
+
121
+ module.exports = async (options) => {
122
+ // 参数提取
123
+ const parsed = Ec.parseArgument(options);
124
+ const agentName = parsed.agent || parsed.a;
125
+
126
+ try {
127
+ // 获取所有 agents
128
+ const agents = _getAgents();
129
+
130
+ if (agents.length === 0) {
131
+ Ec.error("❌ 未找到任何 agents");
132
+ Ec.askClose();
133
+ process.exit(1);
134
+ }
135
+
136
+ // 如果没有提供 agent 参数,显示可用的 agents 并让用户选择
137
+ if (!agentName) {
138
+ Ec.waiting('可用的 Agents:');
139
+ agents.forEach((agent, index) => {
140
+ Ec.waiting(`${index + 1}. ${agent.argName.red.bold} (${agent.name}), id = ${agent.id}, uri = ${agent.uri}`);
141
+ });
142
+
143
+ // 获取用户选择
144
+ const answer = await Ec.ask('请选择要使用的 Agent (输入编号): ');
145
+ const selectedIndex = parseInt(answer) - 1;
146
+
147
+ // 验证选择
148
+ if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex >= agents.length) {
149
+ Ec.error("❌ 无效的选择");
150
+ Ec.askClose();
151
+ process.exit(1);
152
+ }
153
+
154
+ // 使用选择的 agent
155
+ const selectedAgent = agents[selectedIndex];
156
+ return await _processAgent(selectedAgent);
157
+ }
158
+
159
+ // 查找指定的 agent
160
+ const agent = agents.find(a => a.argName === agentName);
161
+ if (!agent) {
162
+ Ec.error(`❌ 未找到名为 "${agentName}" 的 Agent`);
163
+ Ec.waiting('可用的 Agents:');
164
+ agents.forEach((a, index) => {
165
+ Ec.waiting(`${index + 1}. ${a.argName.red.bold} (${a.name}), id = ${a.id}, uri = ${a.uri}`);
166
+ });
167
+ Ec.askClose();
168
+ process.exit(1);
169
+ }
170
+
171
+ // 处理选中的 agent
172
+ await _processAgent(agent);
173
+
174
+ } catch (error) {
175
+ Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
176
+ Ec.askClose();
177
+ process.exit(1);
178
+ }
179
+ };
180
+
181
+ /**
182
+ * 处理选中的 agent
183
+ * @param {Object} agent 选中的 agent 对象
184
+ */
185
+ const _processAgent = async (agent) => {
186
+ try {
187
+ // 构建模板路径
188
+ const templatePath = path.resolve(__dirname, `../_template/PROMPT/${agent.type}/${agent.id}.ejs`);
189
+
190
+ // 检查模板文件是否存在
191
+ if (!fs.existsSync(templatePath)) {
192
+ Ec.error(`❌ 未找到模板文件: ${templatePath}`);
193
+ Ec.askClose();
194
+ process.exit(1);
195
+ }
196
+
197
+ // 提取模板内容
198
+ const content = await _extractTemplateContent(templatePath);
199
+
200
+ // 复制到剪贴板
201
+ await _copyToClipboard(content);
202
+
203
+ // 显示警告提示
204
+ Ec.waiting('');
205
+ Ec.warn('⚠️ 为了获得最佳效果,请在您的 IDE 工具窗口中选择对应的 Agent 来配合使用此提示词');
206
+
207
+ Ec.askClose();
208
+ process.exit(0);
209
+ } catch (error) {
210
+ Ec.error(`❌ 处理 Agent 时发生错误: ${error.message}`);
211
+ Ec.askClose();
212
+ process.exit(1);
213
+ }
214
+ };
@@ -0,0 +1,195 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { spawn } = require('child_process');
4
+ const Ec = require('../epic');
5
+
6
+ /**
7
+ * 获取所有 agents 信息
8
+ * @returns {Array} agents 信息数组
9
+ */
10
+ const _getAgents = () => {
11
+ const agentsDir = path.resolve(__dirname, '../_agent');
12
+ const agents = [];
13
+
14
+ if (!fs.existsSync(agentsDir)) {
15
+ return agents;
16
+ }
17
+
18
+ // 遍历 _agent 目录下的所有子目录
19
+ const types = fs.readdirSync(agentsDir).filter(file =>
20
+ fs.statSync(path.join(agentsDir, file)).isDirectory()
21
+ );
22
+
23
+ // 遍历每个类型目录
24
+ types.forEach(type => {
25
+ const typeDir = path.join(agentsDir, type);
26
+ const files = fs.readdirSync(typeDir).filter(file => file.endsWith('.json'));
27
+
28
+ // 遍历每个 json 文件
29
+ files.forEach(file => {
30
+ try {
31
+ const filePath = path.join(typeDir, file);
32
+ const content = fs.readFileSync(filePath, 'utf8');
33
+ const agentInfo = JSON.parse(content);
34
+
35
+ agents.push({
36
+ argName: file.replace('momo-', '').replace('.json', ''),
37
+ type: type,
38
+ name: agentInfo.name,
39
+ id: agentInfo.id,
40
+ uri: agentInfo.uri,
41
+ filePath: filePath
42
+ });
43
+ } catch (error) {
44
+ Ec.waiting(`⚠️ 无法解析文件: ${file} - ${error.message}`);
45
+ }
46
+ });
47
+ });
48
+
49
+ return agents;
50
+ };
51
+
52
+ /**
53
+ * 显示 agents 表格
54
+ * @param {Array} agents agents 信息数组
55
+ */
56
+ const _showAgents = (agents) => {
57
+ if (agents.length === 0) {
58
+ Ec.waiting('未找到任何 agents');
59
+ return;
60
+ }
61
+
62
+ // 计算列宽
63
+ const columnWidth = 20;
64
+ const uriColumnWidth = 30;
65
+
66
+ // 表头
67
+ const header = "编号".padEnd(6) +
68
+ "Arg Name".padEnd(columnWidth) +
69
+ "Type".padEnd(columnWidth) +
70
+ "ID".padEnd(columnWidth) +
71
+ "URI".padEnd(uriColumnWidth) +
72
+ "Name";
73
+ Ec.waiting(header);
74
+
75
+ // 分隔线
76
+ const separator = "-".repeat(5) + " " +
77
+ "-".repeat(columnWidth - 1) + " " +
78
+ "-".repeat(columnWidth - 1) + " " +
79
+ "-".repeat(columnWidth - 1) + " " +
80
+ "-".repeat(uriColumnWidth - 1) + " " +
81
+ "-".repeat(columnWidth - 1);
82
+ Ec.waiting(separator);
83
+
84
+ // 数据行
85
+ agents.forEach((agent, index) => {
86
+ const row = `${(index + 1).toString().padEnd(6)}` +
87
+ `${agent.argName.padEnd(columnWidth).red.bold}` +
88
+ `${agent.type.padEnd(columnWidth)}` +
89
+ `${agent.id.padEnd(columnWidth)}` +
90
+ `${agent.uri.padEnd(uriColumnWidth)}` +
91
+ agent.name;
92
+ Ec.waiting(row);
93
+ });
94
+
95
+ Ec.waiting(separator);
96
+ Ec.waiting(`${(agents.length + 1).toString().padEnd(6)}退出`);
97
+ Ec.waiting('');
98
+ };
99
+
100
+ /**
101
+ * 在浏览器中打开 URI
102
+ * @param {string} uri 要打开的 URI
103
+ * @param {string} agentType agent 类型
104
+ */
105
+ const _openInBrowser = (uri, agentType) => {
106
+ Ec.waiting(`🚀 正在打开 ${agentType} 中的 agent...`);
107
+
108
+ let command;
109
+ switch (process.platform) {
110
+ case 'darwin': // macOS
111
+ command = `open "${uri}"`;
112
+ break;
113
+ case 'win32': // Windows
114
+ command = `start "" "${uri}"`;
115
+ break;
116
+ default: // Linux
117
+ command = `xdg-open "${uri}"`;
118
+ break;
119
+ }
120
+
121
+ const child = spawn(command, { shell: true });
122
+
123
+ child.on('error', (error) => {
124
+ Ec.waiting(`⚠️ 无法打开浏览器: ${error.message}`);
125
+ });
126
+
127
+ child.on('close', (code) => {
128
+ if (code === 0) {
129
+ Ec.waiting(`✅ 浏览器已打开,请确保您的 IDE 已启动`);
130
+ } else {
131
+ Ec.waiting(`⚠️ 浏览器打开失败,退出码: ${code}`);
132
+ }
133
+ });
134
+ };
135
+
136
+ /**
137
+ * 交互式选择 agents
138
+ * @param {Array} agents agents 信息数组
139
+ */
140
+ const _interactiveSelect = async (agents) => {
141
+ while (true) {
142
+ _showAgents(agents);
143
+ Ec.waiting('💡 提示: 请确保您的 IDE 已启动,以便正确配置 agent');
144
+ const answer = await Ec.ask('请输入编号选择 agent (或输入退出编号): ');
145
+
146
+ const selectedIndex = parseInt(answer) - 1;
147
+
148
+ // 检查是否选择退出
149
+ if (selectedIndex === agents.length) {
150
+ Ec.waiting('👋 再见!');
151
+ break;
152
+ }
153
+
154
+ // 检查选择是否有效
155
+ if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex >= agents.length) {
156
+ Ec.waiting('❌ 无效的选择,请重新输入');
157
+ continue;
158
+ }
159
+
160
+ // 打开选中的 agent
161
+ const selectedAgent = agents[selectedIndex];
162
+ _openInBrowser(selectedAgent.uri, selectedAgent.type);
163
+
164
+ // 等待一段时间让用户看到结果
165
+ await new Promise(resolve => setTimeout(resolve, 2000));
166
+ }
167
+ };
168
+
169
+ module.exports = async () => {
170
+ try {
171
+ Ec.waiting('🔍 正在枚举所有可用的 agents...');
172
+
173
+ // 获取所有 agents
174
+ const agents = _getAgents();
175
+
176
+ if (agents.length === 0) {
177
+ Ec.waiting('❌ 未找到任何 agents');
178
+ Ec.askClose();
179
+ process.exit(0);
180
+ }
181
+
182
+ Ec.waiting(`✅ 找到 ${agents.length} 个 agents`);
183
+ Ec.waiting('');
184
+
185
+ // 进入交互式选择
186
+ await _interactiveSelect(agents);
187
+
188
+ Ec.askClose();
189
+ process.exit(0);
190
+ } catch (error) {
191
+ Ec.error(`执行过程中发生错误: ${error.message}`);
192
+ Ec.askClose();
193
+ process.exit(1);
194
+ }
195
+ };
@@ -133,6 +133,8 @@ const _ioFile = async (baseDir) => {
133
133
  ".momo/prompt/",
134
134
  ".momo/template/",
135
135
  ".momo/scripts/",
136
+ "reference/maven",
137
+ "reference/npm",
136
138
  "specification/changes/",
137
139
  "specification/actor/",
138
140
  "specification/.archives/",
@@ -255,7 +257,9 @@ const _ioFile = async (baseDir) => {
255
257
  * .momo/advanced/ 高级配置说明
256
258
  * /actor.md - 角色定制说明
257
259
  * .momo/scripts/ 特殊脚本目录
258
- *
260
+ * reference/
261
+ * /maven/ Maven 外置项目
262
+ * /npm/ NPM 外置项目
259
263
  * specification/ 工作目录
260
264
  * /project.md - 项目基本说明文件
261
265
  * /project-model.md - 模型说明文件(通常是建模所需的核心概念模型)
@@ -1,19 +1,19 @@
1
1
  const Ec = require('../epic');
2
- const { spawn } = require('child_process');
2
+ const {spawn} = require('child_process');
3
3
 
4
4
  module.exports = async (options) => {
5
5
  // 参数提取
6
6
  const parsed = Ec.parseArgument(options);
7
-
7
+
8
8
  try {
9
9
  // 定义可用的AI工具
10
10
  const aiTools = [
11
- { name: 'Trae', command: 'trae' },
12
- { name: 'Cursor', command: 'cursor' },
13
- { name: 'Lingma', command: 'lingma' },
14
- { name: 'Kiro', command: 'kiro' }
11
+ {name: 'Trae', command: 'trae'},
12
+ {name: 'Cursor', command: 'cursor'},
13
+ {name: 'Lingma', command: 'lingma'},
14
+ {name: 'Kiro', command: 'kiro'}
15
15
  ];
16
-
16
+
17
17
  // 检查哪些工具可用
18
18
  const availableTools = [];
19
19
  for (const tool of aiTools) {
@@ -21,14 +21,14 @@ module.exports = async (options) => {
21
21
  availableTools.push(tool);
22
22
  }
23
23
  }
24
-
24
+
25
25
  // 如果没有可用工具,提示用户安装
26
26
  if (availableTools.length === 0) {
27
27
  Ec.error('❌ 未找到可用的AI工具,请确保已安装以下工具之一:Trae、Cursor、Lingma、Kiro');
28
28
  Ec.askClose();
29
29
  process.exit(1);
30
30
  }
31
-
31
+
32
32
  // 如果只有一个工具可用,直接使用它
33
33
  if (availableTools.length === 1) {
34
34
  const tool = availableTools[0];
@@ -45,7 +45,7 @@ module.exports = async (options) => {
45
45
  Ec.askClose();
46
46
  process.exit(0);
47
47
  }
48
-
48
+
49
49
  // 显示交互式选择菜单
50
50
  Ec.waiting('🔍 检测到多个可用的AI工具,请选择要使用的工具:');
51
51
  const choices = availableTools.map((tool, index) => {
@@ -59,18 +59,18 @@ module.exports = async (options) => {
59
59
  return `${index + 1}. ${toolDisplayName}`;
60
60
  }
61
61
  });
62
-
62
+
63
63
  // 添加退出选项
64
64
  choices.push(`${choices.length + 1}. 退出`);
65
-
65
+
66
66
  for (const choice of choices) {
67
67
  Ec.waiting(choice);
68
68
  }
69
-
69
+
70
70
  // 获取用户选择
71
71
  const answer = await Ec.ask('请输入选项编号: ');
72
72
  const selectedIndex = parseInt(answer) - 1;
73
-
73
+
74
74
  // 检查用户选择
75
75
  if (isNaN(selectedIndex) || selectedIndex < 0 || selectedIndex >= availableTools.length) {
76
76
  if (selectedIndex === availableTools.length) {
@@ -82,7 +82,7 @@ module.exports = async (options) => {
82
82
  Ec.askClose();
83
83
  process.exit(1);
84
84
  }
85
-
85
+
86
86
  // 执行选择的工具
87
87
  const selectedTool = availableTools[selectedIndex];
88
88
  let toolDisplayName = selectedTool.name;
@@ -93,8 +93,9 @@ module.exports = async (options) => {
93
93
  await _openWithTool(selectedTool.command);
94
94
  Ec.askClose();
95
95
  process.exit(0);
96
-
96
+
97
97
  } catch (error) {
98
+ console.error(error);
98
99
  Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
99
100
  if (process.platform === 'win32') {
100
101
  Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
@@ -113,8 +114,8 @@ const _isCommandAvailable = async (command) => {
113
114
  return new Promise((resolve) => {
114
115
  // 在 Windows 上使用 where 命令,在其他系统上使用 which 命令
115
116
  const whereCmd = process.platform === 'win32' ? 'where' : 'which';
116
- const process = spawn(whereCmd, [command]);
117
- process.on('close', (code) => {
117
+ const childProcess = spawn(whereCmd, [command]);
118
+ childProcess.on('close', (code) => {
118
119
  resolve(code === 0);
119
120
  });
120
121
  });
@@ -127,11 +128,11 @@ const _isCommandAvailable = async (command) => {
127
128
  const _openWithTool = async (tool) => {
128
129
  return new Promise((resolve, reject) => {
129
130
  // 使用工具命令打开当前目录
130
- const child = spawn(tool, ['.'], {
131
+ const child = spawn(tool, ['.'], {
131
132
  stdio: 'inherit',
132
133
  cwd: process.cwd()
133
134
  });
134
-
135
+
135
136
  child.on('close', (code) => {
136
137
  if (code === 0) {
137
138
  Ec.info(`✅ 项目已成功在 ${tool} 中打开`);
@@ -141,7 +142,7 @@ const _openWithTool = async (tool) => {
141
142
  reject(new Error(`${tool} 执行失败,退出码: ${code}`));
142
143
  }
143
144
  });
144
-
145
+
145
146
  child.on('error', (error) => {
146
147
  if (error.code === 'ENOENT') {
147
148
  Ec.error(`❌ 未找到命令: ${tool},请确保已正确安装`);
@@ -0,0 +1,296 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const util = require('util');
4
+ const Ec = require('../epic');
5
+
6
+ // 将 fs 方法转换为 Promise 版本
7
+ const fsAsync = {
8
+ mkdir: util.promisify(fs.mkdir),
9
+ copyFile: util.promisify(fs.copyFile),
10
+ readdir: util.promisify(fs.readdir),
11
+ stat: util.promisify(fs.stat),
12
+ readFile: util.promisify(fs.readFile),
13
+ writeFile: util.promisify(fs.writeFile)
14
+ };
15
+
16
+ /**
17
+ * 获取或创建 refer.json 配置文件路径
18
+ * @param {string} baseDir 项目根目录
19
+ * @returns {string} refer.json 文件路径
20
+ */
21
+ const _getReferConfigPath = (baseDir) => {
22
+ return path.resolve(baseDir, '.momo', 'advanced', 'refer.json');
23
+ };
24
+
25
+ /**
26
+ * 获取默认的 refer 配置
27
+ * @returns {Object} 默认配置对象
28
+ */
29
+ const _getDefaultReferConfig = () => {
30
+ return {
31
+ maven: {
32
+ file: 'pom.xml',
33
+ target: 'reference/maven'
34
+ },
35
+ gradle: {
36
+ file: 'build.gradle',
37
+ target: 'reference/gradle'
38
+ },
39
+ npm: {
40
+ file: 'package.json',
41
+ target: 'reference/npm'
42
+ },
43
+ rust: {
44
+ file: 'Cargo.toml',
45
+ target: 'reference/rust'
46
+ },
47
+ go: {
48
+ file: 'go.mod',
49
+ target: 'reference/go'
50
+ },
51
+ ruby: {
52
+ file: 'Gemfile',
53
+ target: 'reference/ruby'
54
+ },
55
+ python: {
56
+ file: 'requirements.txt',
57
+ target: 'reference/python'
58
+ },
59
+ php: {
60
+ file: 'composer.json',
61
+ target: 'reference/php'
62
+ },
63
+ dotnet: {
64
+ file: 'project.csproj',
65
+ target: 'reference/dotnet'
66
+ },
67
+ java: {
68
+ file: 'pom.xml',
69
+ target: 'reference/java'
70
+ },
71
+ cmake: {
72
+ file: 'CMakeLists.txt',
73
+ target: 'reference/cmake'
74
+ },
75
+ make: {
76
+ file: 'Makefile',
77
+ target: 'reference/make'
78
+ }
79
+ };
80
+ };
81
+
82
+ /**
83
+ * 读取或创建 refer.json 配置文件
84
+ * @param {string} configPath 配置文件路径
85
+ * @returns {Promise<Object>} 配置对象
86
+ */
87
+ const _readOrCreateReferConfig = async (configPath) => {
88
+ // 确保 .momo/advanced 目录存在
89
+ const configDir = path.dirname(configPath);
90
+ if (!fs.existsSync(configDir)) {
91
+ await fsAsync.mkdir(configDir, { recursive: true });
92
+ }
93
+
94
+ const defaultConfig = _getDefaultReferConfig();
95
+
96
+ // 如果配置文件不存在,创建默认配置
97
+ if (!fs.existsSync(configPath)) {
98
+ await fsAsync.writeFile(configPath, JSON.stringify(defaultConfig, null, 4));
99
+ Ec.waiting(`创建默认配置文件: ${path.relative(process.cwd(), configPath)}`);
100
+ return defaultConfig;
101
+ }
102
+
103
+ // 读取现有配置
104
+ try {
105
+ const content = await fsAsync.readFile(configPath, 'utf8');
106
+ const existingConfig = JSON.parse(content);
107
+
108
+ // 检查是否需要更新配置(添加新的项目类型)
109
+ let updated = false;
110
+ for (const [key, value] of Object.entries(defaultConfig)) {
111
+ if (!existingConfig[key]) {
112
+ existingConfig[key] = value;
113
+ updated = true;
114
+ }
115
+ }
116
+
117
+ // 如果有更新,则写回文件
118
+ if (updated) {
119
+ await fsAsync.writeFile(configPath, JSON.stringify(existingConfig, null, 4));
120
+ Ec.waiting(`更新配置文件: ${path.relative(process.cwd(), configPath)}`);
121
+ }
122
+
123
+ return existingConfig;
124
+ } catch (error) {
125
+ Ec.error(`配置文件格式错误: ${configPath}`);
126
+ throw error;
127
+ }
128
+ };
129
+
130
+ /**
131
+ * 显示当前支持的项目类型(表格形式)
132
+ * @param {Object} referConfig 引用配置
133
+ */
134
+ const _showSupportedTypes = (referConfig) => {
135
+ Ec.waiting("当前支持的项目类型:");
136
+
137
+ // 定义列宽
138
+ const columnWidth = 24;
139
+
140
+ // 表头
141
+ const header = "Type".padEnd(columnWidth) +
142
+ "ID File".padEnd(columnWidth) +
143
+ "Store Path";
144
+ Ec.waiting(header);
145
+
146
+ // 分隔线
147
+ const separator = "-".repeat(columnWidth - 1) + " " +
148
+ "-".repeat(columnWidth - 1) + " " +
149
+ "-".repeat(columnWidth - 1);
150
+ Ec.waiting(separator);
151
+
152
+ // 数据行
153
+ for (const [type, config] of Object.entries(referConfig)) {
154
+ const row = type.padEnd(columnWidth) +
155
+ config.file.padEnd(columnWidth) +
156
+ config.target;
157
+ Ec.waiting(row);
158
+ }
159
+
160
+ Ec.info("使用方法: momo project -s \"项目路径\"");
161
+ };
162
+
163
+ /**
164
+ * 检查路径是否为目录
165
+ * @param {string} sourcePath 路径
166
+ * @returns {Promise<boolean>} 是否为目录
167
+ */
168
+ const _isDirectory = async (sourcePath) => {
169
+ try {
170
+ const stats = await fsAsync.stat(sourcePath);
171
+ return stats.isDirectory();
172
+ } catch (error) {
173
+ return false;
174
+ }
175
+ };
176
+
177
+ /**
178
+ * 检查目录中是否存在指定文件
179
+ * @param {string} sourceDir 源目录
180
+ * @param {string} fileName 文件名
181
+ * @returns {Promise<boolean>} 是否存在文件
182
+ */
183
+ const _hasFile = async (sourceDir, fileName) => {
184
+ try {
185
+ const filePath = path.join(sourceDir, fileName);
186
+ const stats = await fsAsync.stat(filePath);
187
+ return stats.isFile();
188
+ } catch (error) {
189
+ return false;
190
+ }
191
+ };
192
+
193
+ /**
194
+ * 递归拷贝目录
195
+ * @param {string} src 源目录
196
+ * @param {string} dest 目标目录
197
+ */
198
+ const _copyDir = async (src, dest) => {
199
+ const entries = await fsAsync.readdir(src, { withFileTypes: true });
200
+ await fsAsync.mkdir(dest, { recursive: true });
201
+
202
+ for (let entry of entries) {
203
+ const srcPath = path.join(src, entry.name);
204
+ const destPath = path.join(dest, entry.name);
205
+
206
+ if (entry.isDirectory()) {
207
+ await _copyDir(srcPath, destPath);
208
+ } else {
209
+ await fsAsync.copyFile(srcPath, destPath);
210
+ }
211
+ }
212
+ };
213
+
214
+ /**
215
+ * 处理项目引用
216
+ * @param {string} sourcePath 源项目路径
217
+ * @param {Object} referConfig 引用配置
218
+ * @param {string} baseDir 项目根目录
219
+ */
220
+ const _processProjectReference = async (sourcePath, referConfig, baseDir) => {
221
+ // 检查源路径是否为目录
222
+ if (!(await _isDirectory(sourcePath))) {
223
+ Ec.error(`源路径必须是一个目录: ${sourcePath}`);
224
+ process.exit(1);
225
+ }
226
+
227
+ Ec.waiting(`检查项目: ${sourcePath}`);
228
+
229
+ // 遍历配置中的每种项目类型
230
+ for (const [type, config] of Object.entries(referConfig)) {
231
+ const { file, target } = config;
232
+
233
+ // 检查源目录中是否存在指定文件
234
+ if (await _hasFile(sourcePath, file)) {
235
+ Ec.waiting(`检测到 ${type} 项目 (${file})`);
236
+
237
+ // 构建目标目录路径
238
+ const targetDir = path.resolve(baseDir, target);
239
+
240
+ // 确保目标目录存在
241
+ if (!fs.existsSync(targetDir)) {
242
+ Ec.waiting(`创建目录: ${target}`);
243
+ await fsAsync.mkdir(targetDir, { recursive: true });
244
+ }
245
+
246
+ // 生成目标子目录名称(使用源目录名称)
247
+ const sourceDirName = path.basename(sourcePath);
248
+ const finalTargetDir = path.join(targetDir, sourceDirName);
249
+
250
+ // 拷贝项目
251
+ try {
252
+ Ec.waiting(`拷贝项目到: ${path.join(target, sourceDirName)}`);
253
+ await _copyDir(sourcePath, finalTargetDir);
254
+ Ec.waiting(`✅ ${type} 项目引用成功`);
255
+ } catch (error) {
256
+ Ec.error(`❌ 拷贝项目失败: ${error.message}`);
257
+ throw error;
258
+ }
259
+ }
260
+ }
261
+ };
262
+
263
+ module.exports = async (options) => {
264
+ // 参数提取
265
+ const parsed = Ec.parseArgument(options);
266
+ const sourcePath = parsed.source;
267
+
268
+ try {
269
+ // 获取项目根目录
270
+ const baseDir = process.cwd();
271
+
272
+ // 获取 refer.json 配置文件路径
273
+ const configPath = _getReferConfigPath(baseDir);
274
+
275
+ // 读取或创建配置文件
276
+ const referConfig = await _readOrCreateReferConfig(configPath);
277
+
278
+ // 如果没有提供 source 参数,则显示支持的项目类型
279
+ if (!sourcePath) {
280
+ _showSupportedTypes(referConfig);
281
+ Ec.askClose();
282
+ process.exit(0);
283
+ }
284
+
285
+ // 处理项目引用
286
+ await _processProjectReference(sourcePath, referConfig, baseDir);
287
+
288
+ Ec.info("项目引用处理完成!");
289
+ Ec.askClose();
290
+ process.exit(0);
291
+ } catch (error) {
292
+ Ec.error(`执行过程中发生错误: ${error.message}`);
293
+ Ec.askClose();
294
+ process.exit(1);
295
+ }
296
+ };
@@ -14,6 +14,10 @@ const executeActors = require('./executeActors');
14
14
  const executeRun = require('./executeRun');
15
15
  const executeTasks = require('./executeTasks');
16
16
  const executeUnlock = require('./executeUnlock');
17
+ const executeProject = require('./executeProject');
18
+ const executeAgentCfg = require('./executeAgentCfg');
19
+ const executeAgent = require('./executeAgent');
20
+
17
21
  const exported = {
18
22
  executeHelp,
19
23
  executeInit,
@@ -30,6 +34,9 @@ const exported = {
30
34
  executeActors,
31
35
  executeRun,
32
36
  executeTasks,
33
- executeUnlock
37
+ executeUnlock,
38
+ executeProject,
39
+ executeAgentCfg,
40
+ executeAgent
34
41
  };
35
42
  module.exports = exported;