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
@@ -2,15 +2,27 @@ const executeHelp = require('./executeHelp');
2
2
  const executeInit = require('./executeInit');
3
3
  const executeEnv = require('./executeEnv');
4
4
  const executeOpen = require('./executeOpen');
5
- const executeSkills = require('./executeSkills');
6
5
  const executeMcp = require('./executeMcp');
6
+ const executeApp = require('./executeApp');
7
+ const executeApply = require('./executeApply');
8
+ const executeDomain = require('./executeDomain');
9
+ const executeAsk = require('./executeAsk');
10
+ const executeMmr2 = require('./executeMmr2');
11
+ const executeMmr0 = require('./executeMmr0');
12
+ const executeDocs = require('./executeDocs');
7
13
 
8
14
  const exported = {
9
15
  executeHelp,
10
16
  executeInit,
11
17
  executeEnv,
12
18
  executeOpen,
13
- executeSkills,
14
- executeMcp
19
+ executeMcp,
20
+ executeApp,
21
+ executeApply,
22
+ executeDomain,
23
+ executeAsk,
24
+ executeMmr2,
25
+ executeMmr0,
26
+ executeDocs
15
27
  };
16
28
  module.exports = exported;
@@ -0,0 +1,418 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ R2MO Java to Protobuf Converter (V8 - Enhanced Comments & Constraints)
6
+ 更新内容:
7
+ 1. 【新增】从 @Schema(description) 中提取中文注释
8
+ 2. 【新增】约束信息增强:长度、必填、数值范围等
9
+ 3. 【优化】注释优先级:Schema description > JavaDoc > 行尾注释
10
+ 4. 保留所有 V7 特性(自动导入、Enum 排序、BaseEntity 补全、注解提取、路径推导)
11
+ """
12
+
13
+ import os
14
+ import re
15
+ import sys
16
+ import argparse
17
+ import xml.etree.ElementTree as ET
18
+
19
+ # ================= 1. 配置与映射表 =================
20
+
21
+ BASE_ENTITY_FIELDS = [
22
+ {'name': 'key', 'type': 'string', 'label': '', 'comment': '主键'},
23
+ {'name': 'active', 'type': 'bool', 'label': '', 'comment': '是否启用'},
24
+ {'name': 'sigma', 'type': 'string', 'label': '', 'comment': '统一标识'},
25
+ {'name': 'metadata', 'type': 'string', 'label': '', 'comment': '附加配置'},
26
+ {'name': 'language', 'type': 'string', 'label': '', 'comment': '语言'},
27
+ {'name': 'created_at', 'type': 'string', 'label': '', 'comment': '创建时间'},
28
+ {'name': 'created_by', 'type': 'string', 'label': '', 'comment': '创建人'},
29
+ {'name': 'updated_at', 'type': 'string', 'label': '', 'comment': '更新时间'},
30
+ {'name': 'updated_by', 'type': 'string', 'label': '', 'comment': '更新人'},
31
+ ]
32
+
33
+ # 基础类型映射
34
+ TYPE_MAPPING = {
35
+ 'String': 'string', 'Integer': 'int32', 'int': 'int32',
36
+ 'Long': 'int64', 'long': 'int64', 'Boolean': 'bool', 'boolean': 'bool',
37
+ 'Double': 'double', 'double': 'double', 'Float': 'float', 'float': 'float',
38
+ 'BigDecimal': 'string', 'LocalDateTime': 'string', 'LocalDate': 'string',
39
+ 'LocalTime': 'string', 'Date': 'string', 'UUID': 'string',
40
+ 'JsonObject': 'string', 'JsonArray': 'string'
41
+ }
42
+
43
+ CONSTRAINT_MAPPING = {
44
+ 'NotNull': '必填', 'NotBlank': '必填', 'NotEmpty': '必填',
45
+ 'Deprecated': '已废弃', 'Email': '邮箱格式', 'Phone': '手机号格式'
46
+ }
47
+
48
+ # ================= 2. 辅助函数 =================
49
+
50
+ def get_project_name():
51
+ if os.path.exists('pom.xml'):
52
+ try:
53
+ tree = ET.parse('pom.xml')
54
+ root = tree.getroot()
55
+ ns = re.match(r'\{.*\}', root.tag)
56
+ ns_map = { 'mvn': ns.group(0).strip('{}') } if ns else {}
57
+ if ns:
58
+ aid = root.find(f"{{{ns_map['mvn']}}}artifactId")
59
+ else:
60
+ aid = root.find('artifactId')
61
+ if aid is not None: return aid.text
62
+ except Exception: pass
63
+ return os.path.basename(os.getcwd())
64
+
65
+ def camel_to_snake(name):
66
+ s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
67
+ return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
68
+
69
+ def get_package_name(content):
70
+ match = re.search(r'package\s+([\w\.]+);', content)
71
+ return match.group(1) if match else "domain"
72
+
73
+ def extract_constraints(lines):
74
+ """
75
+ 从注解中提取约束信息,包括:
76
+ 1. 长度约束 (@Size, @Length)
77
+ 2. 数值约束 (@Max, @Min)
78
+ 3. 验证约束 (@NotNull, @NotBlank, @Email 等)
79
+ 4. Swagger 描述 (@Schema)
80
+ """
81
+ constraints = []
82
+ block = " ".join(lines)
83
+
84
+ # 提取 @Size / @Length 约束
85
+ size = re.search(r'@(?:Size|Length)\((.*?)\)', block)
86
+ if size:
87
+ size_str = size.group(1).replace(' ','').replace('"','')
88
+ constraints.append(f"长度[{size_str}]")
89
+
90
+ # 提取 @Max / @Min 约束
91
+ for k in ['Max', 'Min']:
92
+ val = re.search(fr'@{k}\(?\s*(?:value\s*=\s*)?(\d+)\s*\)?', block)
93
+ if val: constraints.append(f"{k}:{val.group(1)}")
94
+
95
+ # 提取验证约束
96
+ for k, v in CONSTRAINT_MAPPING.items():
97
+ if f'@{k}' in block: constraints.append(v)
98
+
99
+ # 提取 @Schema 中的 description (中文注释)
100
+ schema_desc = re.search(r'@Schema\([^)]*description\s*=\s*"([^"]+)"', block)
101
+ if schema_desc:
102
+ desc_text = schema_desc.group(1)
103
+ # 如果包含中文,优先使用
104
+ if re.search(r'[\u4e00-\u9fff]', desc_text):
105
+ return constraints, desc_text
106
+
107
+ return constraints, None
108
+
109
+ # ================= 3. 解析逻辑 =================
110
+
111
+ def parse_java_enum(content, class_name):
112
+ start_match = re.search(r'public\s+enum\s+\w+\s*\{', content)
113
+ if not start_match: return []
114
+
115
+ start_idx = start_match.end()
116
+ end_idx = content.find(';', start_idx)
117
+ if end_idx == -1: end_idx = content.rfind('}')
118
+
119
+ body = content[start_idx:end_idx]
120
+ body = re.sub(r'//.*', '', body)
121
+ body = re.sub(r'/\*.*?\*/', '', body, flags=re.DOTALL)
122
+
123
+ raw_items = body.split(',')
124
+ enum_items = []
125
+ auto_idx = 0
126
+
127
+ for item in raw_items:
128
+ item = item.strip()
129
+ if not item: continue
130
+
131
+ name_match = re.match(r'([A-Z0-9_]+)', item)
132
+ if not name_match: continue
133
+ name = name_match.group(1)
134
+
135
+ val_match = re.search(r'\(\s*(\d+)', item)
136
+ if val_match:
137
+ val = int(val_match.group(1))
138
+ else:
139
+ val = auto_idx
140
+ auto_idx += 1
141
+
142
+ enum_items.append({'name': name, 'value': val})
143
+
144
+ enum_items.sort(key=lambda x: x['value'])
145
+ if not enum_items or enum_items[0]['value'] != 0:
146
+ prefix = camel_to_snake(class_name).upper()
147
+ enum_items.insert(0, {'name': f"{prefix}_UNSPECIFIED", 'value': 0})
148
+
149
+ return enum_items
150
+
151
+ def parse_java_class(content):
152
+ """
153
+ 解析 Class,返回 (字段列表, 依赖导入集合)
154
+ 支持两种模式:
155
+ 1. 字段声明上的注解和注释
156
+ 2. Getter 方法中的 JavaDoc 注释
157
+ """
158
+ fields = []
159
+ imports = set() # 收集需要 import 的文件名
160
+
161
+ if 'extends BaseEntity' in content:
162
+ fields.extend(BASE_ENTITY_FIELDS)
163
+
164
+ lines = content.split('\n')
165
+ buf_anno, buf_doc = [], ""
166
+
167
+ field_pat = re.compile(r'private\s+([\w<>?]+)\s+(\w+)\s*;')
168
+ json_pat = re.compile(r'@JsonProperty\("([^"]+)"\)')
169
+ # 匹配 Getter 方法的 JavaDoc: /** Getter for <code>ZDB.TABLE.FIELD</code>. 「fieldName」- 描述 */
170
+ getter_doc_pat = re.compile(r'/\*\*\s*Getter for.*?[「」].*?[\u4e00-\u9fff]+.*?\*/', re.DOTALL)
171
+
172
+ # 首先尝试从字段声明处理(带注解的方式)
173
+ for i, line in enumerate(lines):
174
+ line_stripped = line.strip()
175
+ if not line_stripped: continue
176
+
177
+ if line_stripped.startswith('/**'):
178
+ buf_doc = re.sub(r'/\*\*|\*/|\*', '', line_stripped).strip()
179
+ continue
180
+ if line_stripped.startswith('*'):
181
+ clean = line_stripped.replace('*', '').strip()
182
+ if clean and clean != '/': buf_doc += " " + clean
183
+ continue
184
+ if line_stripped.startswith('@'):
185
+ buf_anno.append(line_stripped)
186
+ continue
187
+ if 'static final' in line_stripped:
188
+ buf_anno, buf_doc = [], ""
189
+ continue
190
+
191
+ match = field_pat.search(line)
192
+ if match:
193
+ j_type, raw_name = match.group(1), match.group(2)
194
+
195
+ # 1. 命名
196
+ final_name = camel_to_snake(raw_name)
197
+ for anno in buf_anno:
198
+ jp = json_pat.search(anno)
199
+ if jp: final_name = jp.group(1); break
200
+
201
+ # 2. 类型映射与依赖分析
202
+ p_type, label, target_type_for_import = _map_java_type(j_type)
203
+
204
+ # 【新增】依赖收集逻辑
205
+ if target_type_for_import:
206
+ import_file = f"{camel_to_snake(target_type_for_import)}.proto"
207
+ imports.add(import_file)
208
+
209
+ # 3. 注释和约束提取
210
+ constraints, schema_desc = extract_constraints(buf_anno)
211
+
212
+ # 提取行尾注释
213
+ eol_cmt = lines[i].split('//')[1].strip() if '//' in lines[i] else ""
214
+
215
+ # 优先级:Schema description (中文) > JavaDoc > 行尾注释
216
+ if schema_desc:
217
+ if constraints:
218
+ final_cmt = f"{schema_desc} [{', '.join(constraints)}]"
219
+ else:
220
+ final_cmt = schema_desc
221
+ else:
222
+ con_str = f"[{', '.join(constraints)}]" if constraints else ""
223
+ parts = [p for p in [con_str, eol_cmt, buf_doc] if p]
224
+ final_cmt = " ".join(parts)
225
+
226
+ fields.append({'name': final_name, 'type': p_type, 'label': label, 'comment': final_cmt})
227
+ buf_anno, buf_doc = [], ""
228
+
229
+ # 如果字段列表为空(可能是 jOOQ 风格),尝试从 Getter 方法提取
230
+ if len(fields) <= len(BASE_ENTITY_FIELDS):
231
+ fields_from_getters = _extract_fields_from_getters(content)
232
+ if fields_from_getters:
233
+ # 清空字段,重新从 getter 提取
234
+ fields = []
235
+ if 'extends BaseEntity' in content or 'implements VertxPojo' in content:
236
+ fields.extend(BASE_ENTITY_FIELDS)
237
+ fields.extend(fields_from_getters)
238
+
239
+ return fields, imports
240
+
241
+ def _map_java_type(j_type):
242
+ """映射 Java 类型到 Proto 类型"""
243
+ p_type = j_type
244
+ label = ""
245
+ target_type_for_import = None
246
+
247
+ if j_type.startswith("List<"):
248
+ label = "repeated "
249
+ inner = re.search(r'List<(\w+)>', j_type)
250
+ if inner:
251
+ inner_type = inner.group(1)
252
+ if inner_type in TYPE_MAPPING:
253
+ p_type = TYPE_MAPPING[inner_type]
254
+ else:
255
+ p_type = inner_type
256
+ target_type_for_import = inner_type
257
+ else:
258
+ if j_type in TYPE_MAPPING:
259
+ p_type = TYPE_MAPPING[j_type]
260
+ else:
261
+ p_type = j_type
262
+ target_type_for_import = j_type
263
+
264
+ return p_type, label, target_type_for_import
265
+
266
+ def _extract_fields_from_getters(content):
267
+ """
268
+ 从 Getter 方法中提取字段信息(用于 jOOQ 生成的代码)
269
+ 匹配格式: /** Getter for <code>ZDB.TABLE.FIELD</code>. 「fieldName」- 描述 */
270
+ """
271
+ fields = []
272
+
273
+ # 匹配 getter 方法及其 JavaDoc
274
+ getter_pattern = re.compile(
275
+ r'/\*\*\s*Getter for[^*]*?\*/' # JavaDoc
276
+ r'\s*@Override\s*' # @Override
277
+ r'public\s+([\w<>?]+)\s+get(\w+)\(\)', # 方法签名
278
+ re.DOTALL
279
+ )
280
+
281
+ for match in getter_pattern.finditer(content):
282
+ javadoc = match.group(0)
283
+ j_type = match.group(1)
284
+ field_name_camel = match.group(2)
285
+
286
+ # 提取中文注释:「fieldName」- 描述
287
+ chinese_match = re.search(r'[「」]([^」]+)[」][\s\-—]*([^*\n]+)', javadoc)
288
+ if chinese_match:
289
+ # field_label = chinese_match.group(1) # 如 "id"
290
+ description = chinese_match.group(2).strip() # 如 "主键"
291
+ else:
292
+ # 回退:提取任何中文内容
293
+ chinese_text = re.findall(r'[\u4e00-\u9fff]+', javadoc)
294
+ description = ''.join(chinese_text) if chinese_text else ''
295
+
296
+ # 字段名转换为 snake_case
297
+ field_name = camel_to_snake(field_name_camel[0].lower() + field_name_camel[1:])
298
+
299
+ # 类型映射
300
+ p_type, label, _ = _map_java_type(j_type)
301
+
302
+ fields.append({
303
+ 'name': field_name,
304
+ 'type': p_type,
305
+ 'label': label,
306
+ 'comment': description
307
+ })
308
+
309
+ return fields
310
+
311
+ def generate_proto(name, pkg, content, ftype):
312
+ lines = [
313
+ 'syntax = "proto3";',
314
+ 'package domain;', ''
315
+ ]
316
+
317
+ # 只有 class 类型才需要 import
318
+ imports = set()
319
+ fields = []
320
+ enum_items = []
321
+
322
+ if ftype == 'enum':
323
+ enum_items = parse_java_enum(content, name)
324
+ if not enum_items: return None
325
+ else:
326
+ # 解析字段并获取依赖
327
+ fields, imports = parse_java_class(content)
328
+ if not fields: return None
329
+
330
+ # 【新增】生成 import 语句
331
+ # 排除自己引用自己
332
+ self_import = f"{camel_to_snake(name)}.proto"
333
+ if self_import in imports:
334
+ imports.remove(self_import)
335
+
336
+ if imports:
337
+ sorted_imports = sorted(list(imports))
338
+ for imp in sorted_imports:
339
+ lines.append(f'import "{imp}";')
340
+ lines.append('') # 空行分隔
341
+
342
+ lines.append(f'// Generated from {pkg}.{name}')
343
+ lines.append(f'option java_package = "{pkg}";')
344
+ lines.append('option java_multiple_files = true;')
345
+ lines.append('')
346
+
347
+ if ftype == 'enum':
348
+ lines.append(f'enum {name} {{')
349
+ for i in enum_items: lines.append(f' {i["name"]} = {i["value"]};')
350
+ lines.append('}')
351
+ else:
352
+ lines.append(f'message {name} {{')
353
+ for idx, f in enumerate(fields, 1):
354
+ suf = f' // {f["comment"]}' if f["comment"] else ''
355
+ lines.append(f' {f["label"]}{f["type"]} {f["name"]} = {idx};{suf}')
356
+ lines.append('}')
357
+
358
+ return "\n".join(lines)
359
+
360
+ # ================= 4. 主程序 =================
361
+
362
+ def main():
363
+ parser = argparse.ArgumentParser(description='R2MO Java-to-Proto (V8)')
364
+ parser.add_argument('-i', '--input', help='指定输入目录')
365
+ parser.add_argument('-o', '--output', help='指定输出目录')
366
+ args = parser.parse_args()
367
+
368
+ cwd = os.getcwd()
369
+ project_name = get_project_name()
370
+
371
+ if args.input: input_dir = os.path.abspath(args.input)
372
+ else: input_dir = os.path.join(cwd, f"{project_name}-domain")
373
+
374
+ if args.output: output_dir = os.path.abspath(args.output)
375
+ else: output_dir = os.path.join(cwd, f"{project_name}-ui", ".r2mo", "domain")
376
+
377
+ if not os.path.exists(input_dir):
378
+ if f"{project_name}-domain" in cwd: input_dir = cwd
379
+ else:
380
+ print(f"❌ 输入目录不存在: {input_dir}"); sys.exit(1)
381
+
382
+ if not os.path.exists(output_dir): os.makedirs(output_dir)
383
+
384
+ print(f"🚀 R2MO Proto Generator (V8)")
385
+ print(f" In: {input_dir}")
386
+ print(f" Out: {output_dir}")
387
+ print("-" * 40)
388
+
389
+ count = 0
390
+ for root, dirs, files in os.walk(input_dir):
391
+ for f in files:
392
+ if f.endswith(".java") and f != "BaseEntity.java":
393
+ path = os.path.join(root, f)
394
+ try:
395
+ with open(path, 'r', encoding='utf-8') as fh: content = fh.read()
396
+
397
+ is_enum = 'public enum' in content
398
+ match = re.search(r'public\s+(class|enum)\s+(\w+)', content)
399
+
400
+ if match:
401
+ name = match.group(2)
402
+ pkg = get_package_name(content)
403
+ ftype = 'enum' if is_enum else 'class'
404
+
405
+ proto = generate_proto(name, pkg, content, ftype)
406
+ if proto:
407
+ out_name = f"{camel_to_snake(name)}.proto"
408
+ with open(os.path.join(output_dir, out_name), 'w') as fh: fh.write(proto)
409
+ count += 1
410
+ print(f"✅ {name} -> {out_name}")
411
+ except Exception as e:
412
+ print(f"⚠️ Skip {f}: {e}")
413
+
414
+ print("-" * 40)
415
+ print(f"🎉 处理完成: {count} 个文件")
416
+
417
+ if __name__ == "__main__":
418
+ main()