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.
- package/.momo/advanced/refer.json +50 -0
- package/README.md +51 -35
- package/package.json +1 -1
- package/src/_agent/trae/momo-datamodel.json +5 -0
- package/src/_agent/trae/momo-module-req.json +5 -0
- package/src/_agent/trae/momo-system-req.json +5 -0
- package/src/_agent/trae/momo-task.json +5 -0
- package/src/_template/MCP/.gitkeep +0 -0
- package/src/_template/PROMPT/plan.md.ejs +1 -1
- package/src/_template/PROMPT/trae/momo-datamodel.ejs +8 -0
- package/src/_template/PROMPT/trae/momo-module-req.ejs +11 -0
- package/src/_template/PROMPT/trae/momo-system-req.ejs +9 -0
- package/src/_template/PROMPT/trae/momo-task.ejs +11 -0
- package/src/commander/agent.json +12 -0
- package/src/commander/agentcfg.json +5 -0
- package/src/commander/project.json +12 -0
- package/src/executor/executeAgent.js +214 -0
- package/src/executor/executeAgentCfg.js +195 -0
- package/src/executor/executeInit.js +5 -1
- package/src/executor/executeOpen.js +22 -21
- package/src/executor/executeProject.js +296 -0
- package/src/executor/index.js +8 -1
|
@@ -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
|
-
#
|
|
54
|
-
momo init
|
|
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
|
-
|
|
65
|
-
momo
|
|
66
|
-
momo
|
|
76
|
+
momo project -s "项目路径" # 将路径中的显示拷贝到当前项目的 reference 目录下,若不提供 -s 则直接列举所有支持的项目类型
|
|
77
|
+
|
|
78
|
+
momo agentcfg # 列出所有智能体,并且打开浏览器配置
|
|
79
|
+
momo agent -a "智能体名称" # 📋️ 调用智能体 -> prompt 得到提示词到剪切板
|
|
67
80
|
|
|
68
|
-
#
|
|
69
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
94
|
-
momo
|
|
95
|
-
|
|
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
|
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,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,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 {
|
|
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
|
-
{
|
|
12
|
-
{
|
|
13
|
-
{
|
|
14
|
-
{
|
|
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
|
|
117
|
-
|
|
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
|
+
};
|
package/src/executor/index.js
CHANGED
|
@@ -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;
|