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,223 +0,0 @@
1
- /**
2
- * 文件操作工具模块
3
- *
4
- * 提供常用的文件和目录操作功能。
5
- *
6
- * @module file-utils
7
- * @example
8
- * const { copyDir, scanDir, createTempDir, cleanup } = require('./scripts/file-utils');
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const os = require('os');
14
- const fsAsync = require('fs').promises;
15
- const { execSync } = require('child_process');
16
-
17
- /**
18
- * 递归拷贝目录
19
- *
20
- * @param {string} src - 源目录路径
21
- * @param {string} dest - 目标目录路径
22
- * @returns {Promise<void>}
23
- *
24
- * @example
25
- * await copyDir('/source/dir', '/dest/dir');
26
- */
27
- const copyDir = async (src, dest) => {
28
- await fsAsync.mkdir(dest, { recursive: true });
29
- const items = await fsAsync.readdir(src);
30
-
31
- for (const item of items) {
32
- const srcPath = path.join(src, item);
33
- const destPath = path.join(dest, item);
34
- const stat = await fsAsync.stat(srcPath);
35
-
36
- if (stat.isDirectory()) {
37
- await copyDir(srcPath, destPath);
38
- } else {
39
- await fsAsync.copyFile(srcPath, destPath);
40
- }
41
- }
42
- };
43
-
44
- /**
45
- * 扫描目录,返回子目录列表
46
- *
47
- * @param {string} dirPath - 目录路径
48
- * @param {Function} [filter] - 过滤函数 (name, fullPath) => boolean
49
- * @returns {Array<Object>} [{ name, path, isDirectory }]
50
- *
51
- * @example
52
- * // 扫描所有子目录
53
- * const dirs = scanDir('/some/path');
54
- *
55
- * // 带过滤条件
56
- * const filtered = scanDir('/path', (name) => !name.startsWith('.'));
57
- */
58
- const scanDir = (dirPath, filter = () => true) => {
59
- const results = [];
60
-
61
- if (!fs.existsSync(dirPath)) {
62
- return results;
63
- }
64
-
65
- const items = fs.readdirSync(dirPath);
66
-
67
- for (const item of items) {
68
- const itemPath = path.join(dirPath, item);
69
-
70
- try {
71
- const stat = fs.statSync(itemPath);
72
-
73
- if (stat.isDirectory() && filter(item, itemPath)) {
74
- results.push({
75
- name: item,
76
- path: itemPath,
77
- isDirectory: true
78
- });
79
- }
80
- } catch (e) {
81
- // 忽略无法访问的项
82
- }
83
- }
84
-
85
- return results;
86
- };
87
-
88
- /**
89
- * 创建临时目录
90
- *
91
- * @param {string} [prefix='momo'] - 目录前缀
92
- * @returns {string} 临时目录路径
93
- *
94
- * @example
95
- * const tempDir = createTempDir('my-task');
96
- * // 返回: /tmp/.my-task-1234567890
97
- */
98
- const createTempDir = (prefix = 'momo') => {
99
- return path.join(os.tmpdir(), `.${prefix}-${Date.now()}`);
100
- };
101
-
102
- /**
103
- * 清理目录(删除)
104
- *
105
- * @param {string} dirPath - 目录路径
106
- * @returns {Promise<boolean>} 是否成功
107
- *
108
- * @example
109
- * await cleanup('/tmp/.momo-123');
110
- */
111
- const cleanup = async (dirPath) => {
112
- try {
113
- if (dirPath && fs.existsSync(dirPath)) {
114
- await fsAsync.rm(dirPath, { recursive: true, force: true });
115
- return true;
116
- }
117
- return false;
118
- } catch (error) {
119
- console.warn(`清理目录失败: ${error.message}`);
120
- return false;
121
- }
122
- };
123
-
124
- /**
125
- * 确保目录存在
126
- *
127
- * @param {string} dirPath - 目录路径
128
- * @returns {Promise<void>}
129
- *
130
- * @example
131
- * await ensureDir('/path/to/dir');
132
- */
133
- const ensureDir = async (dirPath) => {
134
- await fsAsync.mkdir(dirPath, { recursive: true });
135
- };
136
-
137
- /**
138
- * 检查文件或目录是否存在
139
- *
140
- * @param {string} filePath - 文件路径
141
- * @returns {boolean}
142
- */
143
- const exists = (filePath) => {
144
- return fs.existsSync(filePath);
145
- };
146
-
147
- /**
148
- * 读取 JSON 文件
149
- *
150
- * @param {string} filePath - JSON 文件路径
151
- * @returns {Object|null} 解析的对象,失败返回 null
152
- *
153
- * @example
154
- * const config = readJson('/path/config.json');
155
- */
156
- const readJson = (filePath) => {
157
- try {
158
- if (!fs.existsSync(filePath)) {
159
- return null;
160
- }
161
- const content = fs.readFileSync(filePath, 'utf8');
162
- return JSON.parse(content);
163
- } catch (error) {
164
- return null;
165
- }
166
- };
167
-
168
- /**
169
- * 写入 JSON 文件
170
- *
171
- * @param {string} filePath - 文件路径
172
- * @param {Object} data - 要写入的数据
173
- * @param {number} [indent=4] - 缩进空格数
174
- * @returns {Promise<void>}
175
- *
176
- * @example
177
- * await writeJson('/path/config.json', { key: 'value' });
178
- */
179
- const writeJson = async (filePath, data, indent = 4) => {
180
- const dir = path.dirname(filePath);
181
- await ensureDir(dir);
182
- await fsAsync.writeFile(filePath, JSON.stringify(data, null, indent));
183
- };
184
-
185
- /**
186
- * Git 克隆仓库
187
- *
188
- * @param {string} url - 仓库 URL
189
- * @param {string} destDir - 目标目录
190
- * @param {Object} [options={}] - 选项
191
- * @param {boolean} [options.shallow=true] - 是否浅克隆
192
- * @returns {boolean} 是否成功
193
- * @throws {Error} 克隆失败时抛出错误
194
- *
195
- * @example
196
- * const tempDir = createTempDir('repo');
197
- * gitClone('https://github.com/user/repo.git', tempDir);
198
- */
199
- const gitClone = (url, destDir, options = {}) => {
200
- const { shallow = true } = options;
201
- const depthArg = shallow ? '--depth 1' : '';
202
-
203
- try {
204
- execSync(`git clone ${depthArg} "${url}" "${destDir}"`, {
205
- stdio: ['pipe', 'pipe', 'pipe']
206
- });
207
- return true;
208
- } catch (error) {
209
- throw new Error(`克隆仓库失败: ${error.message}`);
210
- }
211
- };
212
-
213
- module.exports = {
214
- copyDir,
215
- scanDir,
216
- createTempDir,
217
- cleanup,
218
- ensureDir,
219
- exists,
220
- readJson,
221
- writeJson,
222
- gitClone
223
- };
@@ -1,289 +0,0 @@
1
- /**
2
- * 交互式菜单模块
3
- *
4
- * 提供统一的交互式选择菜单功能,支持单选和多选模式。
5
- *
6
- * @module menu
7
- * @example
8
- * const { selectMultiple, selectSingle } = require('./scripts/menu');
9
- * const result = await selectMultiple(items, '选择菜单');
10
- */
11
-
12
- const readline = require('readline');
13
-
14
- /**
15
- * 清屏并移动光标到顶部
16
- */
17
- const clearScreen = () => {
18
- process.stdout.write('\x1B[2J\x1B[0f');
19
- };
20
-
21
- /**
22
- * 创建多选菜单
23
- *
24
- * @param {Array<Object>} items - 菜单项列表
25
- * @param {string} items[].name - 项目名称(必需)
26
- * @param {string} [items[].description] - 项目描述
27
- * @param {string} [title='选择菜单'] - 菜单标题
28
- * @param {Object} [options={}] - 配置选项
29
- * @param {boolean} [options.showIndex=false] - 是否显示序号
30
- * @param {boolean} [options.allowEmpty=false] - 是否允许空选择
31
- * @returns {Promise<Object>} { indices: number[], items: Object[] }
32
- *
33
- * @example
34
- * const items = [
35
- * { name: 'option-1', description: '第一个选项' },
36
- * { name: 'option-2', description: '第二个选项' }
37
- * ];
38
- * const result = await selectMultiple(items, '请选择');
39
- * console.log(result.items); // 选中的项
40
- */
41
- const selectMultiple = (items, title = '选择菜单', options = {}) => {
42
- const { showIndex = false, allowEmpty = false } = options;
43
-
44
- return new Promise((resolve) => {
45
- const selected = new Array(items.length).fill(false);
46
- let cursor = 0;
47
-
48
- const maxNameLen = Math.max(...items.map(i => (i.name || '').length), 4);
49
-
50
- const render = () => {
51
- clearScreen();
52
-
53
- console.log('');
54
- console.log(`[Momo AI]`.blue.bold + ` ====== ${title} ======`.blue);
55
- console.log('');
56
-
57
- items.forEach((item, index) => {
58
- const isActive = index === cursor;
59
- const checkbox = selected[index] ? '[✓]'.green : '[ ]';
60
- const pointer = isActive ? '▸'.cyan : ' ';
61
- const indexStr = showIndex ? `${index + 1}. `.gray : '';
62
- const name = (item.name || '').padEnd(maxNameLen);
63
-
64
- if (isActive) {
65
- console.log(` ${pointer} ${checkbox} ${indexStr}${name.cyan.bold}`);
66
- } else {
67
- console.log(` ${pointer} ${checkbox} ${indexStr}${name.cyan}`);
68
- }
69
- if (item.description) {
70
- console.log(` ${item.description.gray}`);
71
- }
72
- });
73
-
74
- console.log('');
75
- console.log(' ─────────────────────────────────────────────────'.gray);
76
- console.log(' ↑/↓ 移动 │ 空格 选择/取消 │ a 全选 │ n 清空 │ 回车 确认 │ q 退出'.gray);
77
- console.log('');
78
-
79
- const selectedCount = selected.filter(s => s).length;
80
- if (selectedCount > 0) {
81
- console.log(` 已选择 ${selectedCount} 项`.green);
82
- } else {
83
- console.log(' 未选择任何项'.yellow);
84
- }
85
- };
86
-
87
- const getResult = () => {
88
- const indices = [];
89
- const selectedItems = [];
90
- selected.forEach((sel, idx) => {
91
- if (sel) {
92
- indices.push(idx);
93
- selectedItems.push(items[idx]);
94
- }
95
- });
96
- return { indices, items: selectedItems };
97
- };
98
-
99
- readline.emitKeypressEvents(process.stdin);
100
- if (process.stdin.isTTY) {
101
- process.stdin.setRawMode(true);
102
- }
103
-
104
- render();
105
-
106
- const onKeypress = (str, key) => {
107
- if (!key) return;
108
-
109
- if (key.ctrl && key.name === 'c') {
110
- process.stdin.setRawMode(false);
111
- process.stdin.removeListener('keypress', onKeypress);
112
- process.exit(0);
113
- }
114
-
115
- if (key.name === 'q' || key.name === 'escape') {
116
- process.stdin.setRawMode(false);
117
- process.stdin.removeListener('keypress', onKeypress);
118
- clearScreen();
119
- resolve({ indices: [], items: [] });
120
- return;
121
- }
122
-
123
- if (key.name === 'up') {
124
- cursor = cursor > 0 ? cursor - 1 : items.length - 1;
125
- render();
126
- return;
127
- }
128
-
129
- if (key.name === 'down') {
130
- cursor = cursor < items.length - 1 ? cursor + 1 : 0;
131
- render();
132
- return;
133
- }
134
-
135
- if (key.name === 'space') {
136
- selected[cursor] = !selected[cursor];
137
- render();
138
- return;
139
- }
140
-
141
- if (key.name === 'a') {
142
- selected.fill(true);
143
- render();
144
- return;
145
- }
146
-
147
- if (key.name === 'n') {
148
- selected.fill(false);
149
- render();
150
- return;
151
- }
152
-
153
- if (key.name === 'return') {
154
- const result = getResult();
155
-
156
- if (!allowEmpty && result.indices.length === 0) {
157
- render();
158
- return;
159
- }
160
-
161
- process.stdin.setRawMode(false);
162
- process.stdin.removeListener('keypress', onKeypress);
163
- clearScreen();
164
-
165
- if (result.items.length > 0) {
166
- console.log('');
167
- console.log('[Momo AI]'.blue.bold + ' 已选择:');
168
- console.log('');
169
- result.items.forEach(item => {
170
- console.log(` ✓ ${item.name}`.green);
171
- });
172
- console.log('');
173
- }
174
-
175
- resolve(result);
176
- return;
177
- }
178
- };
179
-
180
- process.stdin.on('keypress', onKeypress);
181
- });
182
- };
183
-
184
- /**
185
- * 创建单选菜单
186
- *
187
- * @param {Array<Object>} items - 菜单项列表
188
- * @param {string} items[].name - 项目名称(必需)
189
- * @param {string} [items[].description] - 项目描述
190
- * @param {string} [title='选择菜单'] - 菜单标题
191
- * @returns {Promise<Object|null>} 选中的项,取消返回 null
192
- *
193
- * @example
194
- * const items = [
195
- * { name: 'repo-1', description: '仓库一' },
196
- * { name: 'repo-2', description: '仓库二' }
197
- * ];
198
- * const selected = await selectSingle(items, '选择仓库');
199
- * if (selected) {
200
- * console.log(selected.name);
201
- * }
202
- */
203
- const selectSingle = (items, title = '选择菜单') => {
204
- return new Promise((resolve) => {
205
- let cursor = 0;
206
-
207
- const maxNameLen = Math.max(...items.map(i => (i.name || '').length), 4);
208
-
209
- const render = () => {
210
- clearScreen();
211
-
212
- console.log('');
213
- console.log('[Momo AI]'.blue.bold + ` ====== ${title} ======`.blue);
214
- console.log('');
215
-
216
- items.forEach((item, index) => {
217
- const isActive = index === cursor;
218
- const pointer = isActive ? '▸'.cyan : ' ';
219
- const name = (item.name || '').padEnd(maxNameLen);
220
-
221
- if (isActive) {
222
- console.log(` ${pointer} ${name.cyan.bold}`);
223
- } else {
224
- console.log(` ${pointer} ${name.cyan}`);
225
- }
226
- if (item.description) {
227
- console.log(` ${item.description.gray}`);
228
- }
229
- });
230
-
231
- console.log('');
232
- console.log(' ─────────────────────────────────────────────────'.gray);
233
- console.log(' ↑/↓ 移动 │ 回车 确认 │ q 退出'.gray);
234
- };
235
-
236
- readline.emitKeypressEvents(process.stdin);
237
- if (process.stdin.isTTY) {
238
- process.stdin.setRawMode(true);
239
- }
240
-
241
- render();
242
-
243
- const onKeypress = (str, key) => {
244
- if (!key) return;
245
-
246
- if (key.ctrl && key.name === 'c') {
247
- process.stdin.setRawMode(false);
248
- process.stdin.removeListener('keypress', onKeypress);
249
- process.exit(0);
250
- }
251
-
252
- if (key.name === 'q' || key.name === 'escape') {
253
- process.stdin.setRawMode(false);
254
- process.stdin.removeListener('keypress', onKeypress);
255
- clearScreen();
256
- resolve(null);
257
- return;
258
- }
259
-
260
- if (key.name === 'up') {
261
- cursor = cursor > 0 ? cursor - 1 : items.length - 1;
262
- render();
263
- return;
264
- }
265
-
266
- if (key.name === 'down') {
267
- cursor = cursor < items.length - 1 ? cursor + 1 : 0;
268
- render();
269
- return;
270
- }
271
-
272
- if (key.name === 'return') {
273
- process.stdin.setRawMode(false);
274
- process.stdin.removeListener('keypress', onKeypress);
275
- clearScreen();
276
- resolve(items[cursor]);
277
- return;
278
- }
279
- };
280
-
281
- process.stdin.on('keypress', onKeypress);
282
- });
283
- };
284
-
285
- module.exports = {
286
- selectMultiple,
287
- selectSingle,
288
- clearScreen
289
- };
@@ -1,209 +0,0 @@
1
- /**
2
- * YAML Frontmatter 解析器
3
- *
4
- * 解析 Markdown 文件头部的 YAML 元数据(不依赖外部库)。
5
- *
6
- * @module yaml-parser
7
- * @example
8
- * const { parseYaml, parseFile } = require('./scripts/yaml-parser');
9
- * const metadata = parseFile('/path/to/SKILL.md');
10
- */
11
-
12
- const fs = require('fs');
13
-
14
- /**
15
- * 解析 YAML 字符串(简单实现)
16
- *
17
- * 支持的格式:
18
- * - 简单键值对: `key: value`
19
- * - 带引号值: `key: "value"` 或 `key: 'value'`
20
- * - 数组(缩进列表):
21
- * ```
22
- * tags:
23
- * - item1
24
- * - item2
25
- * ```
26
- *
27
- * @param {string} yamlStr - YAML 字符串
28
- * @returns {Object} 解析后的对象
29
- *
30
- * @example
31
- * const yaml = `
32
- * name: my-skill
33
- * version: 1.0.0
34
- * tags:
35
- * - tag1
36
- * - tag2
37
- * `;
38
- * const obj = parseYaml(yaml);
39
- * // { name: 'my-skill', version: '1.0.0', tags: ['tag1', 'tag2'] }
40
- */
41
- const parseYaml = (yamlStr) => {
42
- const metadata = {};
43
- const lines = yamlStr.split('\n');
44
- let arrayKey = null;
45
-
46
- for (const line of lines) {
47
- // 跳过空行和注释
48
- if (line.trim() === '' || line.trim().startsWith('#')) {
49
- continue;
50
- }
51
-
52
- // 检查是否是数组项(以 - 开头,前面有空格)
53
- const arrayMatch = line.match(/^(\s+)-\s+(.+)$/);
54
- if (arrayMatch && arrayKey) {
55
- if (!Array.isArray(metadata[arrayKey])) {
56
- metadata[arrayKey] = [];
57
- }
58
- let value = arrayMatch[2].trim();
59
- // 移除引号
60
- value = value.replace(/^["']|["']$/g, '');
61
- metadata[arrayKey].push(value);
62
- continue;
63
- }
64
-
65
- // 检查是否是键值对
66
- const kvMatch = line.match(/^(\w[\w-]*):\s*(.*)$/);
67
- if (kvMatch) {
68
- const key = kvMatch[1];
69
- let value = kvMatch[2].trim();
70
-
71
- if (value === '') {
72
- // 空值,可能是数组或对象的开始
73
- arrayKey = key;
74
- metadata[key] = [];
75
- } else {
76
- // 移除引号
77
- value = value.replace(/^["']|["']$/g, '');
78
-
79
- // 尝试转换类型
80
- if (value === 'true') {
81
- metadata[key] = true;
82
- } else if (value === 'false') {
83
- metadata[key] = false;
84
- } else if (/^\d+$/.test(value)) {
85
- metadata[key] = parseInt(value, 10);
86
- } else if (/^\d+\.\d+$/.test(value)) {
87
- metadata[key] = parseFloat(value);
88
- } else {
89
- metadata[key] = value;
90
- }
91
-
92
- arrayKey = null;
93
- }
94
- }
95
- }
96
-
97
- return metadata;
98
- };
99
-
100
- /**
101
- * 从文件中解析 YAML Frontmatter
102
- *
103
- * Frontmatter 是文件开头被 `---` 包围的 YAML 内容:
104
- * ```
105
- * ---
106
- * name: my-skill
107
- * version: 1.0.0
108
- * ---
109
- * # 正文内容
110
- * ```
111
- *
112
- * @param {string} filePath - 文件路径
113
- * @returns {Object|null} 解析的元数据,失败返回 null
114
- *
115
- * @example
116
- * const metadata = parseFile('/path/to/SKILL.md');
117
- * if (metadata) {
118
- * console.log(metadata.name);
119
- * console.log(metadata.description);
120
- * }
121
- */
122
- const parseFile = (filePath) => {
123
- try {
124
- if (!fs.existsSync(filePath)) {
125
- return null;
126
- }
127
-
128
- const content = fs.readFileSync(filePath, 'utf8');
129
- return parseContent(content);
130
- } catch (error) {
131
- return null;
132
- }
133
- };
134
-
135
- /**
136
- * 从字符串内容中解析 YAML Frontmatter
137
- *
138
- * @param {string} content - 文件内容
139
- * @returns {Object|null} 解析的元数据
140
- */
141
- const parseContent = (content) => {
142
- const lines = content.split('\n');
143
-
144
- let inYaml = false;
145
- let yamlLines = [];
146
-
147
- for (const line of lines) {
148
- if (line.trim() === '---') {
149
- if (!inYaml) {
150
- inYaml = true;
151
- continue;
152
- } else {
153
- // 结束 YAML 块
154
- break;
155
- }
156
- }
157
- if (inYaml) {
158
- yamlLines.push(line);
159
- }
160
- }
161
-
162
- if (yamlLines.length === 0) {
163
- return null;
164
- }
165
-
166
- return parseYaml(yamlLines.join('\n'));
167
- };
168
-
169
- /**
170
- * 提取 Frontmatter 之后的正文内容
171
- *
172
- * @param {string} content - 完整文件内容
173
- * @returns {string} 正文内容
174
- *
175
- * @example
176
- * const content = fs.readFileSync('file.md', 'utf8');
177
- * const body = extractBody(content);
178
- */
179
- const extractBody = (content) => {
180
- const lines = content.split('\n');
181
- let inYaml = false;
182
- let yamlEnded = false;
183
- let bodyStart = 0;
184
-
185
- for (let i = 0; i < lines.length; i++) {
186
- if (lines[i].trim() === '---') {
187
- if (!inYaml) {
188
- inYaml = true;
189
- } else {
190
- yamlEnded = true;
191
- bodyStart = i + 1;
192
- break;
193
- }
194
- }
195
- }
196
-
197
- if (!yamlEnded) {
198
- return content;
199
- }
200
-
201
- return lines.slice(bodyStart).join('\n').trim();
202
- };
203
-
204
- module.exports = {
205
- parseYaml,
206
- parseFile,
207
- parseContent,
208
- extractBody
209
- };