momo-ai 1.0.21 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/.claude/skills/r2mo-rad-lain/SKILL.md +63 -374
  2. package/.trae/skills/algorithmic-art/LICENSE.txt +202 -0
  3. package/.trae/skills/algorithmic-art/SKILL.md +405 -0
  4. package/.trae/skills/algorithmic-art/templates/generator_template.js +223 -0
  5. package/.trae/skills/algorithmic-art/templates/viewer.html +599 -0
  6. package/.trae/skills/doc-coauthoring/SKILL.md +375 -0
  7. package/.trae/skills/frontend-design/LICENSE.txt +177 -0
  8. package/.trae/skills/frontend-design/SKILL.md +42 -0
  9. package/.trae/skills/r2mo-rad-lain/SKILL.md +101 -0
  10. package/README.md +9 -32
  11. package/docs/images/r2mo-lain.png +0 -0
  12. package/package.json +11 -11
  13. package/skills/r2mo-rad-domain/SKILL.md +70 -0
  14. package/src/_skill/repositories.json +9 -3
  15. package/src/_template/LAIN/.obsidian/app.json +1 -0
  16. package/src/_template/LAIN/.obsidian/appearance.json +10 -0
  17. package/src/_template/LAIN/.obsidian/community-plugins.json +7 -0
  18. package/src/_template/LAIN/.obsidian/core-plugins.json +33 -0
  19. package/src/_template/LAIN/.obsidian/plugins/dataview/main.js +20876 -0
  20. package/src/_template/LAIN/.obsidian/plugins/dataview/manifest.json +11 -0
  21. package/src/_template/LAIN/.obsidian/plugins/dataview/styles.css +141 -0
  22. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/data.json +815 -0
  23. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
  24. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
  25. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
  26. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/main.js +153 -0
  27. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
  28. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
  29. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/main.js +7732 -0
  30. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/manifest.json +10 -0
  31. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/styles.css +38 -0
  32. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/main.js +504 -0
  33. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
  34. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
  35. package/src/_template/LAIN/.obsidian/snippets/body-font.css +27 -0
  36. package/src/_template/LAIN/.obsidian/themes/Primary/manifest.json +9 -0
  37. package/src/_template/LAIN/.obsidian/themes/Primary/theme.css +3878 -0
  38. package/src/_template/LAIN/.obsidian/themes/Retro Windows/manifest.json +7 -0
  39. package/src/_template/LAIN/.obsidian/themes/Retro Windows/theme.css +582 -0
  40. package/src/_template/LAIN/.obsidian/themes/RetroOS 98/manifest.json +9 -0
  41. package/src/_template/LAIN/.obsidian/themes/RetroOS 98/theme.css +2566 -0
  42. package/src/_template/LAIN/.obsidian/types.json +28 -0
  43. package/src/_template/LAIN/.obsidian/workspace.json +184 -0
  44. package/src/_template/LAIN/AGENTS.md +170 -16
  45. package/src/_template/R2MO/domain-enhance.md +10 -0
  46. package/src/commander/app.json +13 -0
  47. package/src/commander/apply.json +13 -0
  48. package/src/commander/ask.json +6 -0
  49. package/src/commander/docs.json +13 -0
  50. package/src/commander/domain.json +19 -0
  51. package/src/commander/init.json +1 -1
  52. package/src/commander/mmr0.json +6 -0
  53. package/src/commander/mmr2.json +6 -0
  54. package/src/executor/executeApp.js +133 -0
  55. package/src/executor/{executeSkills.js → executeApply.js} +166 -302
  56. package/src/executor/executeAsk.js +274 -0
  57. package/src/executor/executeDocs.js +498 -0
  58. package/src/executor/executeDomain.js +293 -0
  59. package/src/executor/executeInit.js +159 -383
  60. package/src/executor/executeMcp.js +74 -1
  61. package/src/executor/executeMmr0.js +488 -0
  62. package/src/executor/executeMmr2.js +880 -0
  63. package/src/executor/index.js +15 -3
  64. package/src/python/r2mo_proto.py +418 -0
  65. package/src/python/r2mo_proto_database.py +369 -0
  66. package/src/python/r2mo_proto_domain.py +458 -0
  67. package/src/utils/momo-menu.js +43 -13
  68. package/.claude/skills/r2mo-rad-lain/PROMPT.md +0 -281
  69. package/.claude/skills/r2mo-rad-lain/README.md +0 -192
  70. package/.claude/skills/r2mo-rad-lain/examples/argument-parsing.js +0 -154
  71. package/.claude/skills/r2mo-rad-lain/examples/file-operations.js +0 -182
  72. package/.claude/skills/r2mo-rad-lain/file-utils-api.md +0 -281
  73. package/.claude/skills/r2mo-rad-lain/menu-api.md +0 -187
  74. package/.claude/skills/r2mo-rad-lain/scripts/file-utils.js +0 -223
  75. package/.claude/skills/r2mo-rad-lain/scripts/menu.js +0 -289
  76. package/.claude/skills/r2mo-rad-lain/scripts/yaml-parser.js +0 -209
  77. package/.claude/skills/r2mo-rad-lain/templates/command.json.template +0 -13
  78. package/.claude/skills/r2mo-rad-lain/templates/executor.js.template +0 -32
  79. package/.claude/skills/r2mo-rad-lain/templates/interactive-menu.js.template +0 -221
  80. package/src/_template/LAIN/.momo/advanced/actor.md +0 -42
  81. package/src/_template/LAIN/.momo/advanced/refer.json +0 -46
  82. package/src/_template/LAIN/.momo/scripts/submodule-clean.sh +0 -56
  83. package/src/_template/LAIN/changes/proposal.md +0 -39
  84. package/src/_template/LAIN/changes/tasks/task-detail.md +0 -45
  85. package/src/_template/LAIN/changes/tasks.md +0 -49
  86. package/src/_template/LAIN/execute/admin-n-f-dashboard.md +0 -53
  87. package/src/_template/LAIN/execute/admin-n-f-form.md +0 -51
  88. package/src/_template/LAIN/execute/admin-n-f-home.md +0 -49
  89. package/src/_template/LAIN/execute/admin-n-f-list.md +0 -52
  90. package/src/_template/LAIN/execute/admin-n-f-login.md +0 -56
  91. package/src/_template/LAIN/specification/project-model.md +0 -13
  92. package/src/_template/LAIN/specification/project.md +0 -73
  93. package/src/_template/LAIN/specification/requirement.md +0 -25
  94. package/src/commander/skills.json +0 -20
@@ -1,182 +0,0 @@
1
- /**
2
- * 文件操作示例
3
- * 演示常用的文件操作模式
4
- */
5
-
6
- const fs = require('fs');
7
- const path = require('path');
8
- const os = require('os');
9
- const fsAsync = require('fs').promises;
10
- const { execSync } = require('child_process');
11
-
12
- // ============================================================
13
- // 递归拷贝目录
14
- // ============================================================
15
-
16
- const copyDirectory = async (src, dest) => {
17
- // 确保目标目录存在
18
- await fsAsync.mkdir(dest, { recursive: true });
19
-
20
- const items = await fsAsync.readdir(src);
21
-
22
- for (const item of items) {
23
- const srcPath = path.join(src, item);
24
- const destPath = path.join(dest, item);
25
- const stat = await fsAsync.stat(srcPath);
26
-
27
- if (stat.isDirectory()) {
28
- await copyDirectory(srcPath, destPath);
29
- } else {
30
- await fsAsync.copyFile(srcPath, destPath);
31
- }
32
- }
33
- };
34
-
35
- // ============================================================
36
- // 扫描目录
37
- // ============================================================
38
-
39
- const scanDirectory = (dirPath, filter = () => true) => {
40
- const results = [];
41
-
42
- if (!fs.existsSync(dirPath)) {
43
- return results;
44
- }
45
-
46
- const items = fs.readdirSync(dirPath);
47
-
48
- for (const item of items) {
49
- const itemPath = path.join(dirPath, item);
50
- const stat = fs.statSync(itemPath);
51
-
52
- if (stat.isDirectory() && filter(item, itemPath)) {
53
- results.push({
54
- name: item,
55
- path: itemPath,
56
- isDirectory: true
57
- });
58
- }
59
- }
60
-
61
- return results;
62
- };
63
-
64
- // ============================================================
65
- // 解析 YAML 头部 (Frontmatter)
66
- // ============================================================
67
-
68
- const parseYamlFrontmatter = (filePath) => {
69
- try {
70
- if (!fs.existsSync(filePath)) {
71
- return null;
72
- }
73
-
74
- const content = fs.readFileSync(filePath, 'utf8');
75
- const lines = content.split('\n');
76
-
77
- let inYaml = false;
78
- let yamlLines = [];
79
-
80
- for (const line of lines) {
81
- if (line.trim() === '---') {
82
- if (!inYaml) {
83
- inYaml = true;
84
- continue;
85
- } else {
86
- break;
87
- }
88
- }
89
- if (inYaml) {
90
- yamlLines.push(line);
91
- }
92
- }
93
-
94
- if (yamlLines.length === 0) {
95
- return null;
96
- }
97
-
98
- // 简单 YAML 解析
99
- const metadata = {};
100
- let arrayKey = null;
101
-
102
- for (const line of yamlLines) {
103
- if (line.trim() === '') continue;
104
-
105
- // 数组项
106
- const arrayMatch = line.match(/^\s+-\s+(.+)$/);
107
- if (arrayMatch && arrayKey) {
108
- if (!metadata[arrayKey]) {
109
- metadata[arrayKey] = [];
110
- }
111
- metadata[arrayKey].push(arrayMatch[1].trim());
112
- continue;
113
- }
114
-
115
- // 键值对
116
- const kvMatch = line.match(/^(\w+):\s*(.*)$/);
117
- if (kvMatch) {
118
- const key = kvMatch[1];
119
- const value = kvMatch[2].trim();
120
-
121
- if (value === '') {
122
- arrayKey = key;
123
- metadata[key] = [];
124
- } else {
125
- // 移除引号
126
- metadata[key] = value.replace(/^["']|["']$/g, '');
127
- arrayKey = null;
128
- }
129
- }
130
- }
131
-
132
- return metadata;
133
- } catch (error) {
134
- return null;
135
- }
136
- };
137
-
138
- // ============================================================
139
- // 临时目录管理
140
- // ============================================================
141
-
142
- const createTempDir = (prefix = 'momo') => {
143
- return path.join(os.tmpdir(), `.${prefix}-${Date.now()}`);
144
- };
145
-
146
- const cleanupTempDir = async (tempDir) => {
147
- try {
148
- if (tempDir && fs.existsSync(tempDir)) {
149
- await fsAsync.rm(tempDir, { recursive: true, force: true });
150
- }
151
- } catch (error) {
152
- console.warn(`清理临时目录失败: ${error.message}`);
153
- }
154
- };
155
-
156
- // ============================================================
157
- // Git 操作
158
- // ============================================================
159
-
160
- const gitClone = (url, destDir) => {
161
- try {
162
- execSync(`git clone --depth 1 "${url}" "${destDir}"`, {
163
- stdio: ['pipe', 'pipe', 'pipe']
164
- });
165
- return true;
166
- } catch (error) {
167
- throw new Error(`克隆仓库失败: ${error.message}`);
168
- }
169
- };
170
-
171
- // ============================================================
172
- // 导出
173
- // ============================================================
174
-
175
- module.exports = {
176
- copyDirectory,
177
- scanDirectory,
178
- parseYamlFrontmatter,
179
- createTempDir,
180
- cleanupTempDir,
181
- gitClone
182
- };
@@ -1,281 +0,0 @@
1
- # 文件操作工具 API
2
-
3
- 本文档描述 `scripts/file-utils.js` 和 `scripts/yaml-parser.js` 模块的完整 API。
4
-
5
- ## file-utils.js
6
-
7
- ### 快速开始
8
-
9
- ```javascript
10
- const {
11
- copyDir,
12
- scanDir,
13
- createTempDir,
14
- cleanup,
15
- ensureDir,
16
- exists,
17
- readJson,
18
- writeJson,
19
- gitClone
20
- } = require('./scripts/file-utils');
21
- ```
22
-
23
- ### API 参考
24
-
25
- #### copyDir(src, dest)
26
-
27
- 递归拷贝目录。
28
-
29
- ```javascript
30
- await copyDir('/source/path', '/dest/path');
31
- ```
32
-
33
- | 参数 | 类型 | 描述 |
34
- |------|------|------|
35
- | src | string | 源目录路径 |
36
- | dest | string | 目标目录路径 |
37
-
38
- **返回值:** Promise\<void\>
39
-
40
- #### scanDir(dirPath, filter?)
41
-
42
- 扫描目录,返回子目录列表。
43
-
44
- ```javascript
45
- // 扫描所有子目录
46
- const dirs = scanDir('/some/path');
47
-
48
- // 带过滤条件(排除隐藏目录)
49
- const dirs = scanDir('/path', (name) => !name.startsWith('.'));
50
- ```
51
-
52
- | 参数 | 类型 | 描述 |
53
- |------|------|------|
54
- | dirPath | string | 目录路径 |
55
- | filter | Function | 过滤函数 (name, fullPath) => boolean |
56
-
57
- **返回值:** Array\<{ name, path, isDirectory }\>
58
-
59
- #### createTempDir(prefix?)
60
-
61
- 创建临时目录路径(不会实际创建目录)。
62
-
63
- ```javascript
64
- const tempDir = createTempDir('my-task');
65
- // 返回: /tmp/.my-task-1234567890
66
-
67
- // 需要配合 ensureDir 或 gitClone 等实际创建
68
- await ensureDir(tempDir);
69
- ```
70
-
71
- | 参数 | 类型 | 默认值 | 描述 |
72
- |------|------|--------|------|
73
- | prefix | string | 'momo' | 目录前缀 |
74
-
75
- **返回值:** string
76
-
77
- #### cleanup(dirPath)
78
-
79
- 删除目录(递归)。
80
-
81
- ```javascript
82
- await cleanup('/tmp/.momo-123');
83
- ```
84
-
85
- | 参数 | 类型 | 描述 |
86
- |------|------|------|
87
- | dirPath | string | 目录路径 |
88
-
89
- **返回值:** Promise\<boolean\> - 是否成功
90
-
91
- #### ensureDir(dirPath)
92
-
93
- 确保目录存在(不存在则创建)。
94
-
95
- ```javascript
96
- await ensureDir('/path/to/dir');
97
- ```
98
-
99
- #### exists(filePath)
100
-
101
- 检查文件或目录是否存在。
102
-
103
- ```javascript
104
- if (exists('/path/to/file')) {
105
- // ...
106
- }
107
- ```
108
-
109
- #### readJson(filePath)
110
-
111
- 读取 JSON 文件。
112
-
113
- ```javascript
114
- const config = readJson('/path/config.json');
115
- if (config) {
116
- console.log(config.name);
117
- }
118
- ```
119
-
120
- **返回值:** Object | null
121
-
122
- #### writeJson(filePath, data, indent?)
123
-
124
- 写入 JSON 文件。
125
-
126
- ```javascript
127
- await writeJson('/path/config.json', { key: 'value' }, 2);
128
- ```
129
-
130
- | 参数 | 类型 | 默认值 | 描述 |
131
- |------|------|--------|------|
132
- | filePath | string | - | 文件路径 |
133
- | data | Object | - | 要写入的数据 |
134
- | indent | number | 4 | 缩进空格数 |
135
-
136
- #### gitClone(url, destDir, options?)
137
-
138
- Git 克隆仓库。
139
-
140
- ```javascript
141
- const tempDir = createTempDir('repo');
142
- try {
143
- gitClone('https://github.com/user/repo.git', tempDir);
144
- // 使用克隆的内容
145
- } finally {
146
- await cleanup(tempDir);
147
- }
148
- ```
149
-
150
- | 参数 | 类型 | 描述 |
151
- |------|------|------|
152
- | url | string | 仓库 URL |
153
- | destDir | string | 目标目录 |
154
- | options.shallow | boolean | 是否浅克隆(默认 true) |
155
-
156
- **返回值:** boolean
157
- **异常:** 失败时抛出 Error
158
-
159
- ---
160
-
161
- ## yaml-parser.js
162
-
163
- ### 快速开始
164
-
165
- ```javascript
166
- const { parseFile, parseYaml, extractBody } = require('./scripts/yaml-parser');
167
- ```
168
-
169
- ### API 参考
170
-
171
- #### parseFile(filePath)
172
-
173
- 从文件中解析 YAML Frontmatter。
174
-
175
- ```javascript
176
- const metadata = parseFile('/path/to/SKILL.md');
177
- if (metadata) {
178
- console.log(metadata.name);
179
- console.log(metadata.description);
180
- }
181
- ```
182
-
183
- **返回值:** Object | null
184
-
185
- #### parseYaml(yamlStr)
186
-
187
- 解析 YAML 字符串。
188
-
189
- ```javascript
190
- const yaml = `
191
- name: my-skill
192
- version: 1.0.0
193
- tags:
194
- - tag1
195
- - tag2
196
- `;
197
- const obj = parseYaml(yaml);
198
- // { name: 'my-skill', version: '1.0.0', tags: ['tag1', 'tag2'] }
199
- ```
200
-
201
- **支持的格式:**
202
- - 简单键值对: `key: value`
203
- - 带引号值: `key: "value"` 或 `key: 'value'`
204
- - 数组(缩进列表)
205
- - 布尔值: `true`, `false`
206
- - 数字: `123`, `1.23`
207
-
208
- **不支持:**
209
- - 嵌套对象
210
- - 多行字符串
211
- - 复杂 YAML 语法
212
-
213
- #### parseContent(content)
214
-
215
- 从字符串内容中解析 YAML Frontmatter。
216
-
217
- ```javascript
218
- const content = fs.readFileSync('file.md', 'utf8');
219
- const metadata = parseContent(content);
220
- ```
221
-
222
- #### extractBody(content)
223
-
224
- 提取 Frontmatter 之后的正文内容。
225
-
226
- ```javascript
227
- const content = fs.readFileSync('file.md', 'utf8');
228
- const body = extractBody(content);
229
- console.log(body); // Markdown 正文
230
- ```
231
-
232
- ---
233
-
234
- ## 常用模式
235
-
236
- ### 临时目录工作流
237
-
238
- ```javascript
239
- const { createTempDir, cleanup, gitClone, scanDir } = require('./scripts/file-utils');
240
-
241
- const tempDir = createTempDir('task');
242
-
243
- try {
244
- // 克隆仓库
245
- gitClone('https://github.com/user/repo.git', tempDir);
246
-
247
- // 扫描目录
248
- const items = scanDir(path.join(tempDir, 'skills'));
249
-
250
- // 处理内容...
251
-
252
- } finally {
253
- // 确保清理
254
- await cleanup(tempDir);
255
- }
256
- ```
257
-
258
- ### 技能扫描模式
259
-
260
- ```javascript
261
- const { scanDir } = require('./scripts/file-utils');
262
- const { parseFile } = require('./scripts/yaml-parser');
263
- const path = require('path');
264
-
265
- const scanSkills = (skillsDir) => {
266
- const dirs = scanDir(skillsDir);
267
-
268
- return dirs.map(dir => {
269
- const skillFile = path.join(dir.path, 'SKILL.md');
270
- const metadata = parseFile(skillFile);
271
-
272
- return {
273
- dirname: dir.name,
274
- path: dir.path,
275
- name: metadata?.name || dir.name,
276
- description: metadata?.description || '无描述',
277
- version: metadata?.version || '未知'
278
- };
279
- });
280
- };
281
- ```
@@ -1,187 +0,0 @@
1
- # 交互式菜单 API
2
-
3
- 本文档描述 `scripts/menu.js` 模块的完整 API。该模块提供统一的交互式选择菜单功能。
4
-
5
- ## 快速开始
6
-
7
- ```javascript
8
- const { selectMultiple, selectSingle } = require('./scripts/menu');
9
-
10
- // 多选菜单
11
- const items = [
12
- { name: 'option-1', description: '第一个选项' },
13
- { name: 'option-2', description: '第二个选项' }
14
- ];
15
- const result = await selectMultiple(items, '请选择');
16
-
17
- // 单选菜单
18
- const selected = await selectSingle(items, '选择一个');
19
- ```
20
-
21
- ## API 参考
22
-
23
- ### selectMultiple(items, title, options)
24
-
25
- 创建多选菜单,支持空格选择、全选、清空等操作。
26
-
27
- **参数:**
28
-
29
- | 参数 | 类型 | 必需 | 描述 |
30
- |------|------|------|------|
31
- | items | Array\<Object\> | 是 | 菜单项列表 |
32
- | items[].name | string | 是 | 项目名称 |
33
- | items[].description | string | 否 | 项目描述 |
34
- | title | string | 否 | 菜单标题,默认 '选择菜单' |
35
- | options | Object | 否 | 配置选项 |
36
- | options.showIndex | boolean | 否 | 是否显示序号,默认 false |
37
- | options.allowEmpty | boolean | 否 | 是否允许空选择,默认 false |
38
-
39
- **返回值:**
40
-
41
- ```javascript
42
- {
43
- indices: number[], // 选中项的索引
44
- items: Object[] // 选中的项
45
- }
46
- ```
47
-
48
- **操作说明:**
49
-
50
- | 按键 | 功能 |
51
- |------|------|
52
- | ↑/↓ | 上下移动光标 |
53
- | 空格 | 选择/取消当前项 |
54
- | a | 全选 |
55
- | n | 清空选择 |
56
- | 回车 | 确认选择 |
57
- | q/ESC | 退出(返回空结果) |
58
-
59
- **示例:**
60
-
61
- ```javascript
62
- const items = [
63
- { name: 'skill-1', description: '技能描述一' },
64
- { name: 'skill-2', description: '技能描述二' },
65
- { name: 'skill-3', description: '技能描述三' }
66
- ];
67
-
68
- const result = await selectMultiple(items, '选择要安装的技能', {
69
- showIndex: true,
70
- allowEmpty: false
71
- });
72
-
73
- if (result.items.length > 0) {
74
- console.log('选中的技能:');
75
- result.items.forEach(item => {
76
- console.log(` - ${item.name}`);
77
- });
78
- }
79
- ```
80
-
81
- ### selectSingle(items, title)
82
-
83
- 创建单选菜单,只能选择一项。
84
-
85
- **参数:**
86
-
87
- | 参数 | 类型 | 必需 | 描述 |
88
- |------|------|------|------|
89
- | items | Array\<Object\> | 是 | 菜单项列表 |
90
- | items[].name | string | 是 | 项目名称 |
91
- | items[].description | string | 否 | 项目描述 |
92
- | title | string | 否 | 菜单标题 |
93
-
94
- **返回值:**
95
-
96
- - 选中的项(Object)
97
- - 取消时返回 `null`
98
-
99
- **操作说明:**
100
-
101
- | 按键 | 功能 |
102
- |------|------|
103
- | ↑/↓ | 上下移动光标 |
104
- | 回车 | 确认选择 |
105
- | q/ESC | 退出(返回 null) |
106
-
107
- **示例:**
108
-
109
- ```javascript
110
- const repos = [
111
- { name: 'repo-1', description: '仓库一' },
112
- { name: 'repo-2', description: '仓库二' }
113
- ];
114
-
115
- const selected = await selectSingle(repos, '选择仓库');
116
- if (selected) {
117
- console.log(`已选择: ${selected.name}`);
118
- } else {
119
- console.log('已取消');
120
- }
121
- ```
122
-
123
- ### clearScreen()
124
-
125
- 清屏并将光标移动到顶部。
126
-
127
- ```javascript
128
- const { clearScreen } = require('./scripts/menu');
129
- clearScreen();
130
- ```
131
-
132
- ## 使用模式
133
-
134
- ### 菜单 + 确认模式
135
-
136
- 推荐将菜单选择和确认分离:
137
-
138
- ```javascript
139
- const Ec = require('../epic');
140
- const { selectMultiple } = require('./scripts/menu');
141
-
142
- // 1. 菜单选择(raw mode)
143
- const result = await selectMultiple(items, '选择菜单');
144
-
145
- if (result.items.length === 0) {
146
- Ec.waiting('未选择任何项');
147
- process.exit(0);
148
- }
149
-
150
- // 2. 确认(普通输入模式)
151
- const answer = await Ec.ask('确认操作?(y/N): ');
152
- if (answer.toLowerCase() !== 'y') {
153
- Ec.waiting('已取消');
154
- process.exit(0);
155
- }
156
-
157
- // 3. 执行操作
158
- ```
159
-
160
- ### 注意事项
161
-
162
- 1. **不要在菜单操作中使用 Ec.ask**:菜单使用 raw mode,与 Ec.ask 的 readline 冲突
163
- 2. **菜单完成后才能使用 Ec.ask**:菜单函数返回后已退出 raw mode
164
- 3. **Ctrl+C 会直接退出进程**:这是预期行为,确保用户可以随时终止
165
-
166
- ## 自定义扩展
167
-
168
- 如需自定义菜单样式,可以基于 `selectMultiple` 的实现进行修改。关键点:
169
-
170
- ```javascript
171
- // 1. 启用 raw mode
172
- readline.emitKeypressEvents(process.stdin);
173
- process.stdin.setRawMode(true);
174
-
175
- // 2. 渲染菜单
176
- const render = () => {
177
- process.stdout.write('\x1B[2J\x1B[0f'); // 清屏
178
- // ... 绘制菜单
179
- };
180
-
181
- // 3. 监听按键
182
- process.stdin.on('keypress', onKeypress);
183
-
184
- // 4. 退出时恢复
185
- process.stdin.setRawMode(false);
186
- process.stdin.removeListener('keypress', onKeypress);
187
- ```