momo-ai 1.0.2 → 1.0.4
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/README.md +25 -25
- package/package.json +1 -1
- package/src/commander/run.json +1 -1
- package/src/epic/momo.fn.out.js +5 -4
- package/src/executor/executeActor.js +71 -51
- package/src/executor/executeAdd.js +72 -3
- package/src/executor/executeEnv.js +8 -12
- package/src/executor/executeHelp.js +6 -1
- package/src/executor/executeInit.js +11 -3
- package/src/executor/executeOpen.js +9 -1
- package/src/executor/executePlan.js +48 -5
- package/src/executor/executeRepo.js +5 -1
- package/src/executor/executeRun.js +35 -4
- package/src/executor/executeTasks.js +17 -2
package/README.md
CHANGED
|
@@ -8,22 +8,21 @@
|
|
|
8
8
|
当前工具会在操作系统中安装 `momo` 命令,使用它进行 `SDD - Spec Driven Development` 开发:
|
|
9
9
|
|
|
10
10
|
1. 参考:`OpenSpec / Spec-Kit / Kiro`
|
|
11
|
-
2.
|
|
12
|
-
|
|
11
|
+
2. 工具支持:
|
|
12
|
+
- `Trea`(推荐使用,可支持多Agent模式)
|
|
13
|
+
- `Cursor`(稍贵,可支持多Agent模式)
|
|
14
|
+
- `Lingma`
|
|
15
|
+
- `Kiro`
|
|
16
|
+
3. 中文规范,命令运行之后将 `prompt` 提示词拷贝到剪切板,直接复制到工具中即可使用,提示词生成之后会存储在 `.working` 目录下。
|
|
13
17
|
4. 多个 Agent 协同,实现 Team 团队模式的 AI 开发,无模型限制(推荐使用同一个模型进行任务协同开发)
|
|
14
18
|
|
|
15
19
|
和 `OpenSpec / Spec-Kit / Kiro` 原生工具不同,当前工具以 AI Agent 开发为核心,提供从 Plan -> Deployment
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`AI 开发 + 人工审核`,本工具的宗旨就是 `prompt` 的模板化。
|
|
20
|
+
完整生命周期的整体开发流程,是 SDD 的一种落地手段——暴力、简单、易懂。由于辅助工具可选择模型,所以本工具不提供模型选择,模型本身取决于你所使用的工具本身,命令行只是一个辅助,核心开发模式为:
|
|
21
|
+
`AI 开发 + 人工审核`,本工具的核心是 `prompt` 的模板化。
|
|
19
22
|
|
|
20
|
-
>
|
|
23
|
+
> 除开 `momo` 命令后续会提供和模型直接交互的 `lain` 命令,近似 `iFlow / openspec` 的功能。
|
|
21
24
|
|
|
22
|
-
## 2.
|
|
23
|
-
|
|
24
|
-
- 执行 `momo repo` 的过程中会依赖一个已经初始化好的项目 `Git` 地址,且当前项目本身依赖一个 `Git`(因为项目是以
|
|
25
|
-
`submodule` 的方式添加到环境中的)。
|
|
26
|
-
- 移除相关命令如 `submodule` 只支持 Linux/Mac 的脚本,这个用独立脚本防止更改丢失。
|
|
25
|
+
## 2. 工具使用
|
|
27
26
|
|
|
28
27
|
### 2.1. 安装
|
|
29
28
|
|
|
@@ -33,9 +32,16 @@ npm install -g momo-ai
|
|
|
33
32
|
momo help
|
|
34
33
|
```
|
|
35
34
|
|
|
36
|
-
### 2.2.
|
|
35
|
+
### 2.2. 操作步骤
|
|
37
36
|
|
|
38
|
-
|
|
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 中执行。
|
|
39
45
|
|
|
40
46
|
### 2.3. 命令:不定模型
|
|
41
47
|
|
|
@@ -86,30 +92,24 @@ momo actor -a "角色名称" # 添加新角色 -> 交互式命令,
|
|
|
86
92
|
|
|
87
93
|
# momo run ( 生成提示词 -> 剪切板,自动计算空闲的 source-01 )
|
|
88
94
|
momo run -a {actor} -t {task} # 将任务分配给角色,检查执行目录看哪些任务正在执行
|
|
95
|
+
momo run # 从菜单中选择任务
|
|
89
96
|
momo unlock # 解锁任务 -> 任务状态 = 待办
|
|
90
97
|
```
|
|
91
98
|
|
|
92
|
-
|
|
99
|
+
### 2.4. 控制台:定模型
|
|
93
100
|
|
|
94
|
-
|
|
95
|
-
2. 使用 `momo repo` 添加项目代码库以及工程实例(有多少个 `Agent` 工作就添加多少工程实例),添加完成后可使用 `momo open`
|
|
96
|
-
直接打开工程。
|
|
97
|
-
3. 更新 `project.md / project-model.md / requirement.md` 的细节文档(可用AI帮助拆分和书写)
|
|
98
|
-
4. 使用 `momo add -n 需求名称` 添加细节需求(可用AI帮助拆分和书写:`momo plan -n 需求名称`)
|
|
99
|
-
5. 使用 `momo tasks` 列出所有任务,并且使用 `momo run` 运行任务得到任务提示词
|
|
100
|
-
|
|
101
|
-
> 带 (CV) 标记的命令运行成功后可直接“Ctrl + V”粘贴到 TRAE / Lingma / Cursor / Kiro 中执行。
|
|
101
|
+
> 控制台开发中(Lain 模式)
|
|
102
102
|
|
|
103
|
-
##
|
|
103
|
+
## 3. 参考链接
|
|
104
104
|
|
|
105
|
-
###
|
|
105
|
+
### 3.1. 旧版
|
|
106
106
|
|
|
107
107
|
- (后端)Zero Ecotope:<https://www.zerows.io>
|
|
108
108
|
- (前端)Zero UI:<https://www.vertxui.cn>
|
|
109
109
|
- (工具)Zero AI:<https://www.vertxai.cn>
|
|
110
110
|
- (标准)Zero Schema:<https://www.vertx-cloud.cn>
|
|
111
111
|
|
|
112
|
-
###
|
|
112
|
+
### 3.2. 新增
|
|
113
113
|
|
|
114
114
|
- Maven 统一版本管理:<https://gitee.com/silentbalanceyh/rachel-momo>
|
|
115
115
|
- Rapid快速开发框架:<https://gitee.com/silentbalanceyh/r2mo-rapid>
|
package/package.json
CHANGED
package/src/commander/run.json
CHANGED
package/src/epic/momo.fn.out.js
CHANGED
|
@@ -22,15 +22,16 @@ const outString = (paths, content, sync = false) => __FX.fxContinue(!!content, (
|
|
|
22
22
|
const outCopy = (data) => new Promise(function (resolve, reject) {
|
|
23
23
|
const platform = os.platform();
|
|
24
24
|
let cmd = '';
|
|
25
|
-
if (
|
|
25
|
+
if (platform === 'win32') {
|
|
26
26
|
cmd = 'clip';
|
|
27
|
-
} else if (
|
|
27
|
+
} else if (platform === 'darwin') {
|
|
28
28
|
cmd = 'pbcopy';
|
|
29
29
|
} else {
|
|
30
|
-
|
|
30
|
+
// 对于Linux和其他Unix-like系统,尝试使用xclip
|
|
31
|
+
cmd = 'xclip';
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
const proc = require('child_process').spawn(cmd);
|
|
34
|
+
const proc = require('child_process').spawn(cmd, platform !== 'win32' && platform !== 'darwin' ? ['-selection', 'clipboard'] : []);
|
|
34
35
|
proc.on('error', function (err) {
|
|
35
36
|
reject(err);
|
|
36
37
|
});
|
|
@@ -49,65 +49,85 @@ const _selectLLM = async (actorName) => {
|
|
|
49
49
|
};
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
module.exports = (options) => {
|
|
52
|
+
module.exports = async (options) => {
|
|
53
53
|
// 参数提取
|
|
54
54
|
const parsed = Ec.parseArgument(options);
|
|
55
|
+
const actorName = parsed.actor || parsed.a;
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
if (!actorName) {
|
|
58
|
+
Ec.error("❌ 请提供角色名称 (-a, --actor)");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 在 Windows 上检查非法字符
|
|
63
|
+
if (process.platform === 'win32') {
|
|
64
|
+
const illegalChars = /[<>:"/\\|?*\x00-\x1F]/g;
|
|
65
|
+
if (illegalChars.test(actorName)) {
|
|
66
|
+
Ec.error(`❌ 角色名称 "${actorName}" 包含非法字符,请避免使用以下字符: < > : " / \\ | ? * 以及控制字符`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const stackMatch = templateContent.match(/## 技能说明\s*([\s\S]*?)\s*## 角色限制/);
|
|
75
|
-
if (stackMatch && stackMatch[1]) {
|
|
76
|
-
const stackContent = `# 技术栈信息\n\n${stackMatch[1].trim()}\n`;
|
|
77
|
-
const stackPath = path.resolve(actorDir, 'stack.md');
|
|
78
|
-
fs.writeFileSync(stackPath, stackContent);
|
|
79
|
-
Ec.waiting(`📄 创建技术栈文件: ${stackPath}`);
|
|
71
|
+
try {
|
|
72
|
+
// 检查 specification/actor 目录是否存在
|
|
73
|
+
const actorBaseDir = path.resolve(process.cwd(), 'specification', 'actor');
|
|
74
|
+
if (!fs.existsSync(actorBaseDir)) {
|
|
75
|
+
Ec.error("❌ 未找到 specification/actor 目录,请先初始化项目");
|
|
76
|
+
process.exit(1);
|
|
80
77
|
}
|
|
81
78
|
|
|
82
|
-
//
|
|
83
|
-
const
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
fs.writeFileSync(limitPath, limitContent);
|
|
88
|
-
Ec.waiting(`📄 创建限制文件: ${limitPath}`);
|
|
79
|
+
// 检查角色是否已存在
|
|
80
|
+
const actorDir = path.resolve(actorBaseDir, actorName);
|
|
81
|
+
if (fs.existsSync(actorDir)) {
|
|
82
|
+
Ec.error(`❌ 角色 "${actorName}" 已存在`);
|
|
83
|
+
process.exit(1);
|
|
89
84
|
}
|
|
90
|
-
}
|
|
91
85
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const configPath = path.resolve(actorDir, 'config.json');
|
|
97
|
-
fs.writeFileSync(configPath, JSON.stringify(llmConfig, null, 4));
|
|
98
|
-
Ec.waiting(`⚙️ 创建配置文件: ${configPath}`);
|
|
86
|
+
// 创建角色目录
|
|
87
|
+
if (!fs.existsSync(actorDir)) {
|
|
88
|
+
fs.mkdirSync(actorDir, { recursive: true });
|
|
89
|
+
}
|
|
99
90
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
91
|
+
// 从模板复制内容到stack.md和limit.md
|
|
92
|
+
const templatePath = path.resolve(__dirname, '../_template/LAIN/.momo/advanced/actor.md');
|
|
93
|
+
if (fs.existsSync(templatePath)) {
|
|
94
|
+
const templateContent = fs.readFileSync(templatePath, 'utf8');
|
|
95
|
+
|
|
96
|
+
// 提取技术栈和限制信息
|
|
97
|
+
const stackMatch = templateContent.match(/## 技术栈([\s\S]*?)## 限制/);
|
|
98
|
+
const limitMatch = templateContent.match(/## 限制([\s\S]*)/);
|
|
99
|
+
|
|
100
|
+
if (stackMatch && limitMatch) {
|
|
101
|
+
const stackContent = `# 技术栈信息\n\n${stackMatch[1].trim()}\n`;
|
|
102
|
+
const stackPath = path.resolve(actorDir, 'stack.md');
|
|
103
|
+
fs.writeFileSync(stackPath, stackContent);
|
|
104
|
+
Ec.waiting(`📄 创建技术栈文件: ${stackPath}`);
|
|
105
|
+
|
|
106
|
+
const limitContent = `# 角色限制\n\n${limitMatch[1].trim()}\n`;
|
|
107
|
+
const limitPath = path.resolve(actorDir, 'limit.md');
|
|
108
|
+
fs.writeFileSync(limitPath, limitContent);
|
|
109
|
+
Ec.waiting(`📄 创建限制文件: ${limitPath}`);
|
|
110
|
+
|
|
111
|
+
// 创建config.json文件
|
|
112
|
+
const configPath = path.resolve(actorDir, 'config.json');
|
|
113
|
+
fs.writeFileSync(configPath, JSON.stringify(llmConfig, null, 4));
|
|
114
|
+
Ec.waiting(`📄 创建配置文件: ${configPath}`);
|
|
115
|
+
|
|
116
|
+
Ec.info(`✅ 成功添加新角色 "${actorName}"`);
|
|
117
|
+
process.exit(0);
|
|
118
|
+
} else {
|
|
119
|
+
Ec.error("❌ 模板文件格式不正确");
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
Ec.error(`❌ 未找到模板文件: ${templatePath}`);
|
|
111
124
|
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
|
|
128
|
+
if (process.platform === 'win32') {
|
|
129
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
130
|
+
}
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
@@ -34,6 +34,9 @@ const _readTemplate = async (templatePath) => {
|
|
|
34
34
|
return content;
|
|
35
35
|
} catch (error) {
|
|
36
36
|
Ec.waiting(`读取模板文件失败: ${error.message}`);
|
|
37
|
+
if (process.platform === 'win32') {
|
|
38
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
39
|
+
}
|
|
37
40
|
throw error;
|
|
38
41
|
}
|
|
39
42
|
};
|
|
@@ -117,6 +120,10 @@ const _copyTemplateFiles = async (templateDir, targetDir, sourceFilePath = null)
|
|
|
117
120
|
} else if (fs.existsSync(proposalSource)) {
|
|
118
121
|
Ec.waiting(`拷贝文件: ${proposalTarget}`);
|
|
119
122
|
await fsAsync.copyFile(proposalSource, proposalTarget);
|
|
123
|
+
} else {
|
|
124
|
+
// 如果模板文件不存在,则创建空文件
|
|
125
|
+
Ec.waiting(`创建空文件: ${proposalTarget}`);
|
|
126
|
+
await fsAsync.writeFile(proposalTarget, '');
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
// 拷贝 tasks.md
|
|
@@ -125,6 +132,10 @@ const _copyTemplateFiles = async (templateDir, targetDir, sourceFilePath = null)
|
|
|
125
132
|
if (fs.existsSync(tasksSource)) {
|
|
126
133
|
Ec.waiting(`拷贝文件: ${tasksTarget}`);
|
|
127
134
|
await fsAsync.copyFile(tasksSource, tasksTarget);
|
|
135
|
+
} else {
|
|
136
|
+
// 如果模板文件不存在,则创建空文件
|
|
137
|
+
Ec.waiting(`创建空文件: ${tasksTarget}`);
|
|
138
|
+
await fsAsync.writeFile(tasksTarget, '');
|
|
128
139
|
}
|
|
129
140
|
|
|
130
141
|
// 创建 tasks 目录
|
|
@@ -140,9 +151,16 @@ const _copyTemplateFiles = async (templateDir, targetDir, sourceFilePath = null)
|
|
|
140
151
|
if (fs.existsSync(taskDetailSource)) {
|
|
141
152
|
Ec.waiting(`拷贝文件: ${taskDetailTarget}`);
|
|
142
153
|
await fsAsync.copyFile(taskDetailSource, taskDetailTarget);
|
|
154
|
+
} else {
|
|
155
|
+
// 如果模板文件不存在,则创建空文件
|
|
156
|
+
Ec.waiting(`创建空文件: ${taskDetailTarget}`);
|
|
157
|
+
await fsAsync.writeFile(taskDetailTarget, '');
|
|
143
158
|
}
|
|
144
159
|
} catch (error) {
|
|
145
160
|
Ec.waiting(`拷贝模板文件失败: ${error.message}`);
|
|
161
|
+
if (process.platform === 'win32') {
|
|
162
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
163
|
+
}
|
|
146
164
|
throw error;
|
|
147
165
|
}
|
|
148
166
|
};
|
|
@@ -155,13 +173,26 @@ const _copyToClipboard = async (content) => {
|
|
|
155
173
|
try {
|
|
156
174
|
// 去除换行符,将内容合并为一行
|
|
157
175
|
const contentWithoutNewlines = content.replace(/\r?\n|\r/g, ' ');
|
|
158
|
-
|
|
176
|
+
|
|
177
|
+
// 根据操作系统选择合适的剪贴板命令
|
|
178
|
+
let proc;
|
|
179
|
+
if (process.platform === 'darwin') {
|
|
180
|
+
// macOS
|
|
181
|
+
proc = spawn('pbcopy', { stdio: 'pipe' });
|
|
182
|
+
} else if (process.platform === 'win32') {
|
|
183
|
+
// Windows
|
|
184
|
+
proc = spawn('clip', { stdio: 'pipe' });
|
|
185
|
+
} else {
|
|
186
|
+
// Linux/其他系统,尝试使用xclip
|
|
187
|
+
proc = spawn('xclip', ['-selection', 'clipboard'], { stdio: 'pipe' });
|
|
188
|
+
}
|
|
189
|
+
|
|
159
190
|
proc.stdin.write(contentWithoutNewlines);
|
|
160
191
|
proc.stdin.end();
|
|
161
192
|
Ec.waiting('✅ 提示词已复制到剪贴板');
|
|
162
193
|
} catch (error) {
|
|
163
194
|
Ec.waiting(`复制到剪贴板失败: ${error.message}`);
|
|
164
|
-
//
|
|
195
|
+
// 提供备选方案
|
|
165
196
|
Ec.waiting('提示词内容:');
|
|
166
197
|
console.log(content);
|
|
167
198
|
}
|
|
@@ -203,6 +234,15 @@ module.exports = async (options) => {
|
|
|
203
234
|
requirementName = await _extractTitleFromMarkdown(sourceFilePath);
|
|
204
235
|
}
|
|
205
236
|
|
|
237
|
+
// 检查需求名称是否包含非法字符(Windows 上特别检查)
|
|
238
|
+
if (process.platform === 'win32') {
|
|
239
|
+
const illegalChars = /[<>:"/\\|?*\x00-\x1F]/g;
|
|
240
|
+
if (illegalChars.test(requirementName)) {
|
|
241
|
+
Ec.error(`❌ 需求名称 "${requirementName}" 包含非法字符,请避免使用以下字符: < > : " / \\ | ? * 以及控制字符`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
206
246
|
// 检查需求名称是否包含点号(除了允许的独立需求)
|
|
207
247
|
if (!hasExtension && requirementName.includes('.')) {
|
|
208
248
|
Ec.error("❌ 独立需求名称不能包含点号(.),以防止与文件扩展名混淆");
|
|
@@ -224,10 +264,36 @@ module.exports = async (options) => {
|
|
|
224
264
|
process.exit(1);
|
|
225
265
|
}
|
|
226
266
|
|
|
267
|
+
// 确保 changes 目录存在
|
|
268
|
+
if (!fs.existsSync(changesDir)) {
|
|
269
|
+
Ec.waiting(`创建目录: ${changesDir}`);
|
|
270
|
+
try {
|
|
271
|
+
await fsAsync.mkdir(changesDir, { recursive: true });
|
|
272
|
+
} catch (mkdirError) {
|
|
273
|
+
Ec.error(`❌ 创建目录失败: ${mkdirError.message}`);
|
|
274
|
+
if (process.platform === 'win32') {
|
|
275
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
276
|
+
}
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
227
281
|
// 创建需求目录
|
|
228
282
|
const requirementDir = path.join(changesDir, requirementName);
|
|
229
283
|
Ec.waiting(`创建需求目录: ${requirementDir}`);
|
|
230
|
-
|
|
284
|
+
try {
|
|
285
|
+
await fsAsync.mkdir(requirementDir, { recursive: true });
|
|
286
|
+
} catch (mkdirError) {
|
|
287
|
+
Ec.error(`❌ 创建目录失败: ${mkdirError.message}`);
|
|
288
|
+
if (process.platform === 'win32') {
|
|
289
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
290
|
+
// 检查是否是由于特殊字符导致的问题
|
|
291
|
+
if (mkdirError.message.includes('Invalid argument') || mkdirError.message.includes('EINVAL')) {
|
|
292
|
+
Ec.waiting('💡 可能是由于需求名称包含非法字符,请尝试使用更简单的名称');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
231
297
|
|
|
232
298
|
// 拷贝模板文件
|
|
233
299
|
await _copyTemplateFiles(templateDir, requirementDir, sourceFilePath);
|
|
@@ -243,6 +309,9 @@ module.exports = async (options) => {
|
|
|
243
309
|
process.exit(0);
|
|
244
310
|
} catch (error) {
|
|
245
311
|
Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
|
|
312
|
+
if (process.platform === 'win32') {
|
|
313
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
314
|
+
}
|
|
246
315
|
process.exit(1);
|
|
247
316
|
}
|
|
248
317
|
};
|
|
@@ -22,22 +22,18 @@ const _getVersion = () => {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* 检查命令是否可用
|
|
26
26
|
* @param {string} command 命令名称
|
|
27
|
-
* @returns {Promise<boolean>}
|
|
27
|
+
* @returns {Promise<boolean>} 命令是否可用
|
|
28
28
|
*/
|
|
29
|
-
const
|
|
29
|
+
const _isCommandAvailable = async (command) => {
|
|
30
30
|
try {
|
|
31
|
-
|
|
31
|
+
// 在 Windows 上使用 where 命令,在其他系统上使用 which 命令
|
|
32
|
+
const whereCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
33
|
+
await execAsync(`${whereCmd} ${command}`);
|
|
32
34
|
return true;
|
|
33
35
|
} catch (error) {
|
|
34
|
-
|
|
35
|
-
// 在 Windows 上尝试 where 命令
|
|
36
|
-
await execAsync(`where ${command}`);
|
|
37
|
-
return true;
|
|
38
|
-
} catch (winError) {
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
36
|
+
return false;
|
|
41
37
|
}
|
|
42
38
|
};
|
|
43
39
|
|
|
@@ -71,7 +67,7 @@ module.exports = async () => {
|
|
|
71
67
|
|
|
72
68
|
// 检查每个命令
|
|
73
69
|
for (const command of requiredCommands) {
|
|
74
|
-
const isAvailable = await
|
|
70
|
+
const isAvailable = await _isCommandAvailable(command);
|
|
75
71
|
|
|
76
72
|
if (isAvailable) {
|
|
77
73
|
// const version = await _getCommandVersion(command);
|
|
@@ -4,7 +4,12 @@ module.exports = (options) => {
|
|
|
4
4
|
const metadata = Ec.parseMetadata();
|
|
5
5
|
metadata.forEach(runner => {
|
|
6
6
|
Ec.waiting(`命令:` + `momo ${runner.command} [-options]`.green);
|
|
7
|
-
|
|
7
|
+
// 检查描述中是否包含"CV",如果包含则在(CV)和说明之间添加红色高亮emoji
|
|
8
|
+
let description = runner.description;
|
|
9
|
+
if (description.includes("(CV)")) {
|
|
10
|
+
description = description.replace("(CV)", "(CV)📋️ ");
|
|
11
|
+
}
|
|
12
|
+
Ec.waiting(`说明`.yellow + `:${description}`);
|
|
8
13
|
const options = runner.options || [];
|
|
9
14
|
options.forEach(option => {
|
|
10
15
|
if (option.hasOwnProperty('default')) {
|
|
@@ -26,7 +26,8 @@ const _ioDirectory = async (baseDir) => {
|
|
|
26
26
|
"integration/windsurf/",
|
|
27
27
|
"integration/github/",
|
|
28
28
|
"integration/claude-code/",
|
|
29
|
-
"integration/chatgpt/"
|
|
29
|
+
"integration/chatgpt/",
|
|
30
|
+
".working/"
|
|
30
31
|
];
|
|
31
32
|
|
|
32
33
|
// 创建所有目录
|
|
@@ -34,8 +35,15 @@ const _ioDirectory = async (baseDir) => {
|
|
|
34
35
|
for (const folder of folders) {
|
|
35
36
|
Ec.waiting("创建目录:" + folder);
|
|
36
37
|
const directory = path.resolve(baseDir, folder);
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
try {
|
|
39
|
+
await fsAsync.mkdir(directory, {recursive: true});
|
|
40
|
+
results.push(true);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (process.platform === 'win32') {
|
|
43
|
+
Ec.waiting(`💡 Windows 用户提示: 创建目录失败,可能是由于权限不足或路径包含非法字符`);
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
39
47
|
}
|
|
40
48
|
return results;
|
|
41
49
|
}
|
|
@@ -96,6 +96,9 @@ module.exports = async (options) => {
|
|
|
96
96
|
|
|
97
97
|
} catch (error) {
|
|
98
98
|
Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
|
|
99
|
+
if (process.platform === 'win32') {
|
|
100
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
101
|
+
}
|
|
99
102
|
Ec.askClose();
|
|
100
103
|
process.exit(1);
|
|
101
104
|
}
|
|
@@ -108,7 +111,9 @@ module.exports = async (options) => {
|
|
|
108
111
|
*/
|
|
109
112
|
const _isCommandAvailable = async (command) => {
|
|
110
113
|
return new Promise((resolve) => {
|
|
111
|
-
|
|
114
|
+
// 在 Windows 上使用 where 命令,在其他系统上使用 which 命令
|
|
115
|
+
const whereCmd = process.platform === 'win32' ? 'where' : 'which';
|
|
116
|
+
const process = spawn(whereCmd, [command]);
|
|
112
117
|
process.on('close', (code) => {
|
|
113
118
|
resolve(code === 0);
|
|
114
119
|
});
|
|
@@ -140,6 +145,9 @@ const _openWithTool = async (tool) => {
|
|
|
140
145
|
child.on('error', (error) => {
|
|
141
146
|
if (error.code === 'ENOENT') {
|
|
142
147
|
Ec.error(`❌ 未找到命令: ${tool},请确保已正确安装`);
|
|
148
|
+
if (process.platform === 'win32') {
|
|
149
|
+
Ec.waiting('💡 Windows 用户提示: 请确保工具已正确安装并在 PATH 环境变量中');
|
|
150
|
+
}
|
|
143
151
|
reject(new Error(`未找到命令: ${tool},请确保已正确安装`));
|
|
144
152
|
} else {
|
|
145
153
|
Ec.error(`❌ 执行 ${tool} 时发生错误: ${error.message}`);
|
|
@@ -68,20 +68,51 @@ const _isRequirementExists = (changesDir, requirementName) => {
|
|
|
68
68
|
/**
|
|
69
69
|
* 将内容复制到剪贴板(去除换行符)
|
|
70
70
|
* @param {string} content 要复制的内容
|
|
71
|
+
* @param {string} requirementName 需求名称
|
|
71
72
|
*/
|
|
72
|
-
const _copyToClipboard = async (content) => {
|
|
73
|
+
const _copyToClipboard = async (content, requirementName) => {
|
|
73
74
|
try {
|
|
74
|
-
//
|
|
75
|
+
// 去除换行符,将内容合并为一行
|
|
75
76
|
const contentWithoutNewlines = content.replace(/\r?\n|\r/g, ' ');
|
|
76
|
-
|
|
77
|
+
|
|
78
|
+
// 根据操作系统选择合适的剪贴板命令
|
|
79
|
+
let proc;
|
|
80
|
+
if (process.platform === 'darwin') {
|
|
81
|
+
// macOS
|
|
82
|
+
proc = spawn('pbcopy', { stdio: 'pipe' });
|
|
83
|
+
} else if (process.platform === 'win32') {
|
|
84
|
+
// Windows
|
|
85
|
+
proc = spawn('clip', { stdio: 'pipe' });
|
|
86
|
+
} else {
|
|
87
|
+
// Linux/其他系统,尝试使用xclip
|
|
88
|
+
proc = spawn('xclip', ['-selection', 'clipboard'], { stdio: 'pipe' });
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
proc.stdin.write(contentWithoutNewlines);
|
|
78
92
|
proc.stdin.end();
|
|
79
93
|
Ec.waiting('✅ 计划提示词已复制到剪贴板');
|
|
94
|
+
|
|
95
|
+
// 保存内容到 .working 目录
|
|
96
|
+
const workingDir = path.resolve(process.cwd(), '.working');
|
|
97
|
+
const fileName = `REQ-${requirementName}.txt`;
|
|
98
|
+
const filePath = path.join(workingDir, fileName);
|
|
99
|
+
|
|
100
|
+
// 确保 .working 目录存在
|
|
101
|
+
if (!fs.existsSync(workingDir)) {
|
|
102
|
+
await fsAsync.mkdir(workingDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 写入文件
|
|
106
|
+
await fsAsync.writeFile(filePath, content);
|
|
107
|
+
Ec.waiting(`📄 计划提示词已保存到文件: ${filePath}`);
|
|
80
108
|
} catch (error) {
|
|
81
109
|
Ec.waiting(`复制到剪贴板失败: ${error.message}`);
|
|
82
|
-
//
|
|
110
|
+
// 提供备选方案
|
|
83
111
|
Ec.waiting('计划提示词内容:');
|
|
84
112
|
console.log(content);
|
|
113
|
+
if (process.platform === 'win32') {
|
|
114
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
115
|
+
}
|
|
85
116
|
}
|
|
86
117
|
};
|
|
87
118
|
|
|
@@ -104,6 +135,15 @@ module.exports = async (options) => {
|
|
|
104
135
|
process.exit(1);
|
|
105
136
|
}
|
|
106
137
|
|
|
138
|
+
// 在 Windows 上检查非法字符
|
|
139
|
+
if (process.platform === 'win32') {
|
|
140
|
+
const illegalChars = /[<>:"/\\|?*\x00-\x1F]/g;
|
|
141
|
+
if (illegalChars.test(requirementName)) {
|
|
142
|
+
Ec.error(`❌ 需求名称 "${requirementName}" 包含非法字符,请避免使用以下字符: < > : " / \\ | ? * 以及控制字符`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
107
147
|
Ec.waiting(`准备生成需求计划: ${requirementName}`);
|
|
108
148
|
|
|
109
149
|
try {
|
|
@@ -123,12 +163,15 @@ module.exports = async (options) => {
|
|
|
123
163
|
const renderedContent = _renderTemplate(templateContent, { NAME: requirementName });
|
|
124
164
|
|
|
125
165
|
// 将提示词复制到剪贴板
|
|
126
|
-
await _copyToClipboard(renderedContent);
|
|
166
|
+
await _copyToClipboard(renderedContent, requirementName);
|
|
127
167
|
|
|
128
168
|
Ec.info(`✅ 成功生成需求 "${requirementName}" 的计划提示词`);
|
|
129
169
|
process.exit(0);
|
|
130
170
|
} catch (error) {
|
|
131
171
|
Ec.error(`❌ 执行过程中发生错误: ${error.message}`);
|
|
172
|
+
if (process.platform === 'win32') {
|
|
173
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
174
|
+
}
|
|
132
175
|
process.exit(1);
|
|
133
176
|
}
|
|
134
177
|
};
|
|
@@ -26,11 +26,15 @@ const _isGitRepo = async () => {
|
|
|
26
26
|
const _initGitRepo = async () => {
|
|
27
27
|
try {
|
|
28
28
|
Ec.waiting('正在初始化 Git 仓库');
|
|
29
|
-
|
|
29
|
+
// 在 Windows 上使用不同的命令格式
|
|
30
|
+
const command = process.platform === 'win32' ? 'git init' : 'git init -b master';
|
|
30
31
|
await execAsync(command);
|
|
31
32
|
Ec.waiting('✅ 成功初始化 Git 仓库');
|
|
32
33
|
} catch (error) {
|
|
33
34
|
Ec.error(`❌ 初始化 Git 仓库失败: ${error.message}`);
|
|
35
|
+
if (process.platform === 'win32') {
|
|
36
|
+
Ec.waiting('💡 Windows 用户提示: 请确保已安装 Git 并在 PATH 环境变量中');
|
|
37
|
+
}
|
|
34
38
|
throw error;
|
|
35
39
|
}
|
|
36
40
|
};
|
|
@@ -58,10 +58,24 @@ const _renderTemplate = (template, data) => {
|
|
|
58
58
|
/**
|
|
59
59
|
* 将内容复制到剪贴板
|
|
60
60
|
* @param {string} content 要复制的内容
|
|
61
|
+
* @param {string} requirementName 需求名称
|
|
62
|
+
* @param {string} taskName 任务名称
|
|
61
63
|
*/
|
|
62
|
-
const _copyToClipboard = async (content) => {
|
|
64
|
+
const _copyToClipboard = async (content, requirementName, taskName) => {
|
|
63
65
|
try {
|
|
64
|
-
|
|
66
|
+
// 根据操作系统选择合适的剪贴板命令
|
|
67
|
+
let proc;
|
|
68
|
+
if (process.platform === 'darwin') {
|
|
69
|
+
// macOS
|
|
70
|
+
proc = spawn('pbcopy', {stdio: 'pipe'});
|
|
71
|
+
} else if (process.platform === 'win32') {
|
|
72
|
+
// Windows
|
|
73
|
+
proc = spawn('clip', {stdio: 'pipe'});
|
|
74
|
+
} else {
|
|
75
|
+
// Linux/其他系统,尝试使用xclip
|
|
76
|
+
proc = spawn('xclip', ['-selection', 'clipboard'], {stdio: 'pipe'});
|
|
77
|
+
}
|
|
78
|
+
|
|
65
79
|
proc.stdin.write(content);
|
|
66
80
|
proc.stdin.end();
|
|
67
81
|
|
|
@@ -71,18 +85,35 @@ const _copyToClipboard = async (content) => {
|
|
|
71
85
|
if (code === 0) {
|
|
72
86
|
resolve();
|
|
73
87
|
} else {
|
|
74
|
-
reject(new Error(
|
|
88
|
+
reject(new Error(`剪贴板进程退出码: ${code}`));
|
|
75
89
|
}
|
|
76
90
|
});
|
|
77
91
|
proc.on('error', reject);
|
|
78
92
|
});
|
|
79
93
|
|
|
80
94
|
Ec.waiting('📄 任务执行提示词已拷贝到剪切板');
|
|
95
|
+
|
|
96
|
+
// 保存内容到 .working 目录
|
|
97
|
+
const workingDir = path.resolve(process.cwd(), '.working');
|
|
98
|
+
const fileName = `REQ-${requirementName}-${taskName}.txt`;
|
|
99
|
+
const filePath = path.join(workingDir, fileName);
|
|
100
|
+
|
|
101
|
+
// 确保 .working 目录存在
|
|
102
|
+
if (!fs.existsSync(workingDir)) {
|
|
103
|
+
await fsAsync.mkdir(workingDir, { recursive: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 写入文件
|
|
107
|
+
await fsAsync.writeFile(filePath, content);
|
|
108
|
+
Ec.waiting(`📄 任务执行提示词已保存到文件: ${filePath}`);
|
|
81
109
|
} catch (error) {
|
|
82
110
|
Ec.waiting(`⚠️ 无法拷贝到剪切板: ${error.message}`);
|
|
83
111
|
// 备选方案:显示内容
|
|
84
112
|
Ec.waiting('提示词内容:');
|
|
85
113
|
console.log(content);
|
|
114
|
+
if (process.platform === 'win32') {
|
|
115
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
116
|
+
}
|
|
86
117
|
}
|
|
87
118
|
};
|
|
88
119
|
|
|
@@ -507,7 +538,7 @@ module.exports = async (options) => {
|
|
|
507
538
|
});
|
|
508
539
|
|
|
509
540
|
// 将提示词复制到剪贴板
|
|
510
|
-
await _copyToClipboard(renderedContent);
|
|
541
|
+
await _copyToClipboard(renderedContent, requirementName, selectedTask.name);
|
|
511
542
|
Ec.info(`✅ 任务 ${selectedTask.name} 准备完成,请查看剪切板中的提示词`);
|
|
512
543
|
} catch (error) {
|
|
513
544
|
Ec.info(`✅ 任务 ${selectedTask.name} 查找完成`);
|
|
@@ -61,7 +61,19 @@ const _renderTemplate = (template, data) => {
|
|
|
61
61
|
*/
|
|
62
62
|
const _copyToClipboard = async (content) => {
|
|
63
63
|
try {
|
|
64
|
-
|
|
64
|
+
// 根据操作系统选择合适的剪贴板命令
|
|
65
|
+
let proc;
|
|
66
|
+
if (process.platform === 'darwin') {
|
|
67
|
+
// macOS
|
|
68
|
+
proc = spawn('pbcopy', { stdio: 'pipe' });
|
|
69
|
+
} else if (process.platform === 'win32') {
|
|
70
|
+
// Windows
|
|
71
|
+
proc = spawn('clip', { stdio: 'pipe' });
|
|
72
|
+
} else {
|
|
73
|
+
// Linux/其他系统,尝试使用xclip
|
|
74
|
+
proc = spawn('xclip', ['-selection', 'clipboard'], { stdio: 'pipe' });
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
proc.stdin.write(content);
|
|
66
78
|
proc.stdin.end();
|
|
67
79
|
|
|
@@ -71,7 +83,7 @@ const _copyToClipboard = async (content) => {
|
|
|
71
83
|
if (code === 0) {
|
|
72
84
|
resolve();
|
|
73
85
|
} else {
|
|
74
|
-
reject(new Error(
|
|
86
|
+
reject(new Error(`剪贴板进程退出码: ${code}`));
|
|
75
87
|
}
|
|
76
88
|
});
|
|
77
89
|
proc.on('error', reject);
|
|
@@ -83,6 +95,9 @@ const _copyToClipboard = async (content) => {
|
|
|
83
95
|
// 备选方案:显示内容
|
|
84
96
|
Ec.waiting('提示词内容:');
|
|
85
97
|
console.log(content);
|
|
98
|
+
if (process.platform === 'win32') {
|
|
99
|
+
Ec.waiting('💡 Windows 用户提示: 请确保您在具有足够权限的命令行中运行此命令');
|
|
100
|
+
}
|
|
86
101
|
}
|
|
87
102
|
};
|
|
88
103
|
|