momo-ai 1.0.20 → 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 (162) hide show
  1. package/.claude/skills/algorithmic-art/LICENSE.txt +202 -0
  2. package/.claude/skills/algorithmic-art/SKILL.md +405 -0
  3. package/.claude/skills/algorithmic-art/templates/generator_template.js +223 -0
  4. package/.claude/skills/algorithmic-art/templates/viewer.html +599 -0
  5. package/.claude/skills/r2mo-rad-lain/SKILL.md +101 -0
  6. package/.cursor/mcp.json +17 -0
  7. package/.obsidian/app.json +1 -0
  8. package/.obsidian/appearance.json +4 -0
  9. package/.obsidian/community-plugins.json +4 -0
  10. package/.obsidian/core-plugins.json +33 -0
  11. package/.obsidian/plugins/ai-agent/main.js +98495 -0
  12. package/.obsidian/plugins/ai-agent/manifest.json +11 -0
  13. package/.obsidian/plugins/ai-agent/styles.css +806 -0
  14. package/.obsidian/plugins/dataview/main.js +20876 -0
  15. package/.obsidian/plugins/dataview/manifest.json +11 -0
  16. package/.obsidian/plugins/dataview/styles.css +141 -0
  17. package/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
  18. package/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
  19. package/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
  20. package/.obsidian/plugins/templater-obsidian/main.js +45 -0
  21. package/.obsidian/plugins/templater-obsidian/manifest.json +11 -0
  22. package/.obsidian/plugins/templater-obsidian/styles.css +226 -0
  23. package/.obsidian/plugins/terminal/main.js +200 -0
  24. package/.obsidian/plugins/terminal/manifest.json +14 -0
  25. package/.obsidian/plugins/terminal/styles.css +32 -0
  26. package/.obsidian/themes/AnuPpuccin/manifest.json +7 -0
  27. package/.obsidian/themes/AnuPpuccin/theme.css +9080 -0
  28. package/.obsidian/themes/Things/manifest.json +7 -0
  29. package/.obsidian/themes/Things/theme.css +1628 -0
  30. package/.obsidian/workspace.json +196 -0
  31. package/.trae/skills/algorithmic-art/LICENSE.txt +202 -0
  32. package/.trae/skills/algorithmic-art/SKILL.md +405 -0
  33. package/.trae/skills/algorithmic-art/templates/generator_template.js +223 -0
  34. package/.trae/skills/algorithmic-art/templates/viewer.html +599 -0
  35. package/.trae/skills/doc-coauthoring/SKILL.md +375 -0
  36. package/.trae/skills/frontend-design/LICENSE.txt +177 -0
  37. package/.trae/skills/frontend-design/SKILL.md +42 -0
  38. package/.trae/skills/r2mo-rad-lain/SKILL.md +101 -0
  39. package/README.md +12 -148
  40. package/docs/images/logo.jpeg +0 -0
  41. package/docs/images/r2mo-lain.png +0 -0
  42. package/install.sh +1 -0
  43. package/package.json +15 -11
  44. package/skills/r2mo-rad-domain/SKILL.md +70 -0
  45. package/skills/r2mo-rad-lain/SKILL.md +101 -0
  46. package/src/_mcp/skills-server.mjs +70 -0
  47. package/src/_skill/repositories.json +22 -0
  48. package/src/_template/LAIN/.obsidian/app.json +1 -0
  49. package/src/_template/LAIN/.obsidian/appearance.json +10 -0
  50. package/src/_template/LAIN/.obsidian/community-plugins.json +7 -0
  51. package/src/_template/LAIN/.obsidian/core-plugins.json +33 -0
  52. package/src/_template/LAIN/.obsidian/plugins/dataview/main.js +20876 -0
  53. package/src/_template/LAIN/.obsidian/plugins/dataview/manifest.json +11 -0
  54. package/src/_template/LAIN/.obsidian/plugins/dataview/styles.css +141 -0
  55. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/data.json +815 -0
  56. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
  57. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
  58. package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
  59. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/main.js +153 -0
  60. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
  61. package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
  62. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/main.js +7732 -0
  63. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/manifest.json +10 -0
  64. package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/styles.css +38 -0
  65. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/main.js +504 -0
  66. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
  67. package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
  68. package/src/_template/LAIN/.obsidian/snippets/body-font.css +27 -0
  69. package/src/_template/LAIN/.obsidian/themes/Primary/manifest.json +9 -0
  70. package/src/_template/LAIN/.obsidian/themes/Primary/theme.css +3878 -0
  71. package/src/_template/LAIN/.obsidian/themes/Retro Windows/manifest.json +7 -0
  72. package/src/_template/LAIN/.obsidian/themes/Retro Windows/theme.css +582 -0
  73. package/src/_template/LAIN/.obsidian/themes/RetroOS 98/manifest.json +9 -0
  74. package/src/_template/LAIN/.obsidian/themes/RetroOS 98/theme.css +2566 -0
  75. package/src/_template/LAIN/.obsidian/types.json +28 -0
  76. package/src/_template/LAIN/.obsidian/workspace.json +184 -0
  77. package/src/_template/LAIN/AGENTS.md +170 -16
  78. package/src/_template/R2MO/domain-enhance.md +10 -0
  79. package/src/commander/app.json +13 -0
  80. package/src/commander/apply.json +13 -0
  81. package/src/commander/ask.json +6 -0
  82. package/src/commander/docs.json +13 -0
  83. package/src/commander/domain.json +19 -0
  84. package/src/commander/help.json +5 -0
  85. package/src/commander/init.json +1 -1
  86. package/src/commander/mcp.json +13 -0
  87. package/src/commander/mmr0.json +6 -0
  88. package/src/commander/mmr2.json +6 -0
  89. package/src/commander/open.json +8 -2
  90. package/src/executor/executeApp.js +133 -0
  91. package/src/executor/executeApply.js +611 -0
  92. package/src/executor/executeAsk.js +274 -0
  93. package/src/executor/executeDocs.js +498 -0
  94. package/src/executor/executeDomain.js +293 -0
  95. package/src/executor/executeEnv.js +48 -38
  96. package/src/executor/executeHelp.js +77 -16
  97. package/src/executor/executeInit.js +176 -346
  98. package/src/executor/executeMcp.js +363 -0
  99. package/src/executor/executeMmr0.js +488 -0
  100. package/src/executor/executeMmr2.js +880 -0
  101. package/src/executor/executeOpen.js +144 -125
  102. package/src/executor/index.js +17 -39
  103. package/src/momo.js +2 -1
  104. package/src/python/r2mo_proto.py +418 -0
  105. package/src/python/r2mo_proto_database.py +369 -0
  106. package/src/python/r2mo_proto_domain.py +458 -0
  107. package/src/utils/momo-args.js +39 -0
  108. package/src/utils/momo-file-utils.js +75 -0
  109. package/src/utils/momo-menu.js +84 -0
  110. package/src/_template/LAIN/.momo/advanced/actor.md +0 -42
  111. package/src/_template/LAIN/.momo/advanced/refer.json +0 -46
  112. package/src/_template/LAIN/.momo/scripts/submodule-clean.sh +0 -56
  113. package/src/_template/LAIN/changes/proposal.md +0 -39
  114. package/src/_template/LAIN/changes/tasks/task-detail.md +0 -45
  115. package/src/_template/LAIN/changes/tasks.md +0 -49
  116. package/src/_template/LAIN/execute/admin-n-f-dashboard.md +0 -53
  117. package/src/_template/LAIN/execute/admin-n-f-form.md +0 -51
  118. package/src/_template/LAIN/execute/admin-n-f-home.md +0 -49
  119. package/src/_template/LAIN/execute/admin-n-f-list.md +0 -52
  120. package/src/_template/LAIN/execute/admin-n-f-login.md +0 -56
  121. package/src/_template/LAIN/specification/project-model.md +0 -13
  122. package/src/_template/LAIN/specification/project.md +0 -73
  123. package/src/_template/LAIN/specification/requirement.md +0 -25
  124. package/src/commander/actor.json +0 -12
  125. package/src/commander/actors.json +0 -6
  126. package/src/commander/add.json +0 -12
  127. package/src/commander/agent.json +0 -12
  128. package/src/commander/agentcfg.json +0 -5
  129. package/src/commander/archive.json +0 -12
  130. package/src/commander/commit.json +0 -12
  131. package/src/commander/console.json +0 -7
  132. package/src/commander/lain.json +0 -7
  133. package/src/commander/list.json +0 -7
  134. package/src/commander/plan.json +0 -12
  135. package/src/commander/project.json +0 -12
  136. package/src/commander/pull.json +0 -6
  137. package/src/commander/push.json +0 -6
  138. package/src/commander/repo.json +0 -18
  139. package/src/commander/run.json +0 -18
  140. package/src/commander/show.json +0 -12
  141. package/src/commander/tasks.json +0 -18
  142. package/src/commander/unlock.json +0 -6
  143. package/src/commander/validate.json +0 -12
  144. package/src/executor/executeActor.js +0 -133
  145. package/src/executor/executeActors.js +0 -58
  146. package/src/executor/executeAdd.js +0 -307
  147. package/src/executor/executeAgent.js +0 -299
  148. package/src/executor/executeAgentCfg.js +0 -210
  149. package/src/executor/executeArchive.js +0 -124
  150. package/src/executor/executeCommit.js +0 -202
  151. package/src/executor/executeConsole.js +0 -142
  152. package/src/executor/executeList.js +0 -133
  153. package/src/executor/executePlan.js +0 -164
  154. package/src/executor/executeProject.js +0 -313
  155. package/src/executor/executePull.js +0 -127
  156. package/src/executor/executePush.js +0 -243
  157. package/src/executor/executeRepo.js +0 -238
  158. package/src/executor/executeRun.js +0 -644
  159. package/src/executor/executeShow.js +0 -164
  160. package/src/executor/executeTasks.js +0 -384
  161. package/src/executor/executeUnlock.js +0 -110
  162. package/src/executor/executeValidate.js +0 -210
@@ -0,0 +1,498 @@
1
+ const { spawn, execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const fsAsync = require('fs').promises;
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const Ec = require('../epic');
7
+ const { parseOptional } = require('../utils/momo-args');
8
+ const { copyDir } = require('../utils/momo-file-utils');
9
+
10
+ /**
11
+ * 检查 Obsidian 是否已安装
12
+ * @returns {Promise<boolean>}
13
+ */
14
+ const _isObsidianInstalled = async () => {
15
+ const platform = os.platform();
16
+
17
+ if (platform === 'darwin') {
18
+ // macOS: 检查 Applications 目录
19
+ const obsidianPath = '/Applications/Obsidian.app';
20
+ return fs.existsSync(obsidianPath);
21
+ } else if (platform === 'win32') {
22
+ // Windows: 检查常见安装路径或注册表
23
+ const localAppData = process.env.LOCALAPPDATA || '';
24
+ const programFiles = process.env.PROGRAMFILES || 'C:\\Program Files';
25
+ const possiblePaths = [
26
+ path.join(localAppData, 'Obsidian', 'Obsidian.exe'),
27
+ path.join(programFiles, 'Obsidian', 'Obsidian.exe')
28
+ ];
29
+ return possiblePaths.some(p => fs.existsSync(p));
30
+ } else {
31
+ // Linux: 检查 which obsidian 或常见路径
32
+ try {
33
+ execSync('which obsidian', { stdio: 'pipe' });
34
+ return true;
35
+ } catch {
36
+ // 检查 flatpak 或 snap 安装
37
+ const snapPath = '/snap/bin/obsidian';
38
+ const flatpakCheck = () => {
39
+ try {
40
+ execSync('flatpak list | grep -i obsidian', { stdio: 'pipe' });
41
+ return true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ };
46
+ return fs.existsSync(snapPath) || flatpakCheck();
47
+ }
48
+ }
49
+ };
50
+
51
+ /**
52
+ * 检查 Obsidian 是否正在运行
53
+ * @returns {Promise<boolean>}
54
+ */
55
+ const _isObsidianRunning = async () => {
56
+ const platform = os.platform();
57
+
58
+ try {
59
+ if (platform === 'darwin') {
60
+ const result = execSync('pgrep -f "Obsidian.app"', { stdio: 'pipe' });
61
+ return result.toString().trim().length > 0;
62
+ } else if (platform === 'win32') {
63
+ const result = execSync('tasklist /FI "IMAGENAME eq Obsidian.exe"', { stdio: 'pipe' });
64
+ return result.toString().includes('Obsidian.exe');
65
+ } else {
66
+ const result = execSync('pgrep -f obsidian', { stdio: 'pipe' });
67
+ return result.toString().trim().length > 0;
68
+ }
69
+ } catch {
70
+ return false;
71
+ }
72
+ };
73
+
74
+ /**
75
+ * 关闭 Obsidian 应用
76
+ * @returns {Promise<void>}
77
+ */
78
+ const _closeObsidian = async () => {
79
+ const platform = os.platform();
80
+
81
+ try {
82
+ if (platform === 'darwin') {
83
+ execSync('killall Obsidian', { stdio: 'ignore' });
84
+ // 等待进程完全关闭
85
+ await new Promise(resolve => setTimeout(resolve, 1000));
86
+ } else if (platform === 'win32') {
87
+ execSync('taskkill /F /IM Obsidian.exe', { stdio: 'ignore' });
88
+ await new Promise(resolve => setTimeout(resolve, 1000));
89
+ } else {
90
+ execSync('killall obsidian', { stdio: 'ignore' });
91
+ await new Promise(resolve => setTimeout(resolve, 1000));
92
+ }
93
+ } catch {
94
+ // 如果关闭失败也不报错
95
+ }
96
+ };
97
+
98
+ /**
99
+ * 生成 vault ID(模仿 Obsidian 的 16 位十六进制 ID)
100
+ * @returns {string}
101
+ */
102
+ const _generateVaultId = () => {
103
+ const chars = '0123456789abcdef';
104
+ let id = '';
105
+ for (let i = 0; i < 16; i++) {
106
+ id += chars[Math.floor(Math.random() * chars.length)];
107
+ }
108
+ return id;
109
+ };
110
+
111
+ /**
112
+ * 注册 vault 到 Obsidian 配置
113
+ * @param {string} vaultPath vault 路径
114
+ * @returns {Promise<string|null>} 返回 vault ID,失败返回 null
115
+ */
116
+ const _registerVaultToObsidian = async (vaultPath) => {
117
+ const platform = os.platform();
118
+ let configPath;
119
+
120
+ if (platform === 'darwin') {
121
+ configPath = path.join(os.homedir(), 'Library/Application Support/obsidian/obsidian.json');
122
+ } else if (platform === 'win32') {
123
+ configPath = path.join(process.env.APPDATA || '', 'obsidian', 'obsidian.json');
124
+ } else {
125
+ // Linux
126
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
127
+ configPath = path.join(xdgConfig, 'obsidian', 'obsidian.json');
128
+ }
129
+
130
+ try {
131
+ let config = { vaults: {}, frame: 'custom' };
132
+
133
+ // 读取现有配置
134
+ if (fs.existsSync(configPath)) {
135
+ const content = await fsAsync.readFile(configPath, 'utf8');
136
+ config = JSON.parse(content);
137
+ }
138
+
139
+ // 检查 vault 是否已存在
140
+ const existingVault = Object.entries(config.vaults || {}).find(
141
+ ([id, vault]) => vault.path === vaultPath
142
+ );
143
+
144
+ let vaultId;
145
+ if (existingVault) {
146
+ // 已存在,更新 ts 和 open 状态
147
+ [vaultId] = existingVault;
148
+ config.vaults[vaultId].ts = Date.now();
149
+ config.vaults[vaultId].open = true;
150
+ } else {
151
+ // 新 vault,生成 ID 并添加
152
+ vaultId = _generateVaultId();
153
+ config.vaults[vaultId] = {
154
+ path: vaultPath,
155
+ ts: Date.now(),
156
+ open: true
157
+ };
158
+ }
159
+
160
+ // 确保配置目录存在
161
+ const configDir = path.dirname(configPath);
162
+ if (!fs.existsSync(configDir)) {
163
+ await fsAsync.mkdir(configDir, { recursive: true });
164
+ }
165
+
166
+ // 写入配置
167
+ await fsAsync.writeFile(configPath, JSON.stringify(config), 'utf8');
168
+ return vaultId;
169
+ } catch (error) {
170
+ Ec.warn(`⚠ 无法注册 vault 到配置: ${error.message}`);
171
+ return null;
172
+ }
173
+ };
174
+
175
+ /**
176
+ * 使用 Obsidian 打开目录
177
+ * 直接调用 Obsidian 可执行文件,传递目录路径作为参数
178
+ * @param {string} targetDir 目标目录绝对路径
179
+ * @param {string} vaultId vault ID,用于 URL scheme
180
+ * @returns {Promise<void>}
181
+ */
182
+ const _openWithObsidian = async (targetDir, vaultId = null) => {
183
+ const platform = os.platform();
184
+
185
+ if (platform === 'darwin') {
186
+ // macOS: 优先使用 vault ID 的 URL scheme,更可靠
187
+ if (vaultId) {
188
+ const obsidianUri = `obsidian://open?vault=${vaultId}`;
189
+
190
+ return new Promise((resolve, reject) => {
191
+ const child = spawn('open', [obsidianUri], {
192
+ stdio: 'ignore'
193
+ });
194
+
195
+ child.on('error', (error) => {
196
+ reject(new Error(`启动 Obsidian 失败: ${error.message}`));
197
+ });
198
+
199
+ child.on('close', (code) => {
200
+ if (code === 0) {
201
+ // 给 Obsidian 时间加载
202
+ setTimeout(() => resolve(), 1000);
203
+ } else {
204
+ reject(new Error(`启动 Obsidian 失败,退出码: ${code}`));
205
+ }
206
+ });
207
+ });
208
+ } else {
209
+ // 回退到直接调用可执行文件
210
+ const obsidianBinary = '/Applications/Obsidian.app/Contents/MacOS/Obsidian';
211
+
212
+ if (!fs.existsSync(obsidianBinary)) {
213
+ throw new Error('找不到 Obsidian 可执行文件');
214
+ }
215
+
216
+ return new Promise((resolve, reject) => {
217
+ const child = spawn(obsidianBinary, [targetDir], {
218
+ detached: true,
219
+ stdio: 'ignore'
220
+ });
221
+
222
+ child.on('error', (error) => {
223
+ reject(new Error(`启动 Obsidian 失败: ${error.message}`));
224
+ });
225
+
226
+ child.unref();
227
+ setTimeout(() => resolve(), 500);
228
+ });
229
+ }
230
+ } else if (platform === 'win32') {
231
+ // Windows: 查找 Obsidian.exe 并直接打开目录
232
+ const localAppData = process.env.LOCALAPPDATA || '';
233
+ const programFiles = process.env.PROGRAMFILES || 'C:\\Program Files';
234
+ const possiblePaths = [
235
+ path.join(localAppData, 'Obsidian', 'Obsidian.exe'),
236
+ path.join(programFiles, 'Obsidian', 'Obsidian.exe')
237
+ ];
238
+
239
+ const obsidianExe = possiblePaths.find(p => fs.existsSync(p));
240
+ if (!obsidianExe) {
241
+ throw new Error('找不到 Obsidian 可执行文件');
242
+ }
243
+
244
+ return new Promise((resolve, reject) => {
245
+ const child = spawn(obsidianExe, [targetDir], {
246
+ detached: true,
247
+ stdio: 'ignore'
248
+ });
249
+
250
+ child.on('error', (error) => {
251
+ reject(new Error(`启动 Obsidian 失败: ${error.message}`));
252
+ });
253
+
254
+ child.unref();
255
+
256
+ setTimeout(() => {
257
+ resolve();
258
+ }, 500);
259
+ });
260
+ } else {
261
+ // Linux: 尝试直接启动 obsidian 命令
262
+ const snapPath = '/snap/bin/obsidian';
263
+ const flatpakPath = 'flatpak run md.obsidian.Obsidian';
264
+ let obsidianCmd = 'obsidian';
265
+ let args = [targetDir];
266
+ let useShell = false;
267
+
268
+ // 检查 snap 安装
269
+ if (fs.existsSync(snapPath)) {
270
+ obsidianCmd = snapPath;
271
+ } else {
272
+ // 尝试 flatpak
273
+ try {
274
+ execSync('flatpak list | grep -i obsidian', { stdio: 'pipe' });
275
+ obsidianCmd = flatpakPath;
276
+ useShell = true;
277
+ args = [targetDir];
278
+ } catch {
279
+ // 使用默认命令
280
+ }
281
+ }
282
+
283
+ return new Promise((resolve, reject) => {
284
+ const spawnOptions = {
285
+ detached: true,
286
+ stdio: 'ignore'
287
+ };
288
+
289
+ if (useShell) {
290
+ spawnOptions.shell = true;
291
+ }
292
+
293
+ const child = spawn(obsidianCmd, args, spawnOptions);
294
+
295
+ child.on('error', (error) => {
296
+ reject(new Error(`启动 Obsidian 失败: ${error.message}`));
297
+ });
298
+
299
+ child.unref();
300
+
301
+ setTimeout(() => {
302
+ resolve();
303
+ }, 500);
304
+ });
305
+ }
306
+ };
307
+
308
+ /**
309
+ * 获取操作系统名称
310
+ * @returns {string}
311
+ */
312
+ const _getOsName = () => {
313
+ const platform = os.platform();
314
+ switch (platform) {
315
+ case 'darwin': return 'macOS';
316
+ case 'win32': return 'Windows';
317
+ case 'linux': return 'Linux';
318
+ default: return platform;
319
+ }
320
+ };
321
+
322
+ /**
323
+ * 初始化 .obsidian 目录
324
+ * 从模板目录复制默认配置
325
+ * @param {string} targetDir 目标目录
326
+ * @returns {Promise<boolean>} 是否成功初始化
327
+ */
328
+ const _initObsidianConfig = async (targetDir) => {
329
+ const obsidianSourcePath = path.resolve(__dirname, '../_template/LAIN/.obsidian');
330
+ const obsidianTargetPath = path.join(targetDir, '.obsidian');
331
+
332
+ // 检查模板目录是否存在
333
+ if (!fs.existsSync(obsidianSourcePath)) {
334
+ Ec.warn(`⚠ 模板目录不存在: ${obsidianSourcePath}`);
335
+ return false;
336
+ }
337
+
338
+ try {
339
+ const stat = await fsAsync.stat(obsidianSourcePath);
340
+ if (!stat.isDirectory()) {
341
+ Ec.warn(`⚠ 模板路径不是目录: ${obsidianSourcePath}`);
342
+ return false;
343
+ }
344
+
345
+ Ec.waiting('正在初始化 .obsidian 配置...');
346
+ await copyDir(obsidianSourcePath, obsidianTargetPath);
347
+ Ec.info('✓ 已创建 .obsidian 配置目录');
348
+ return true;
349
+ } catch (error) {
350
+ Ec.error(`初始化 .obsidian 失败: ${error.message}`);
351
+ return false;
352
+ }
353
+ };
354
+
355
+ /**
356
+ * 更新目标目录的 .gitignore 文件
357
+ * 添加 .obsidian/workspace.json 到忽略列表
358
+ * @param {string} targetDir 目标目录
359
+ * @returns {Promise<void>}
360
+ */
361
+ const _updateGitignore = async (targetDir) => {
362
+ const gitignorePath = path.join(targetDir, '.gitignore');
363
+ const ignoreEntry = '.obsidian/workspace.json';
364
+
365
+ try {
366
+ let gitignoreContent = '';
367
+
368
+ // 读取现有的 .gitignore 内容
369
+ if (fs.existsSync(gitignorePath)) {
370
+ gitignoreContent = await fsAsync.readFile(gitignorePath, 'utf8');
371
+ }
372
+
373
+ // 检查是否已经包含该条目
374
+ if (gitignoreContent.includes(ignoreEntry)) {
375
+ Ec.info('✓ .gitignore 已包含 .obsidian/workspace.json');
376
+ return;
377
+ }
378
+
379
+ // 添加条目
380
+ if (gitignoreContent && !gitignoreContent.endsWith('\n')) {
381
+ gitignoreContent += '\n';
382
+ }
383
+
384
+ gitignoreContent += `${ignoreEntry}\n`;
385
+
386
+ // 写入 .gitignore 文件
387
+ await fsAsync.writeFile(gitignorePath, gitignoreContent, 'utf8');
388
+ Ec.info('✓ 已添加 .obsidian/workspace.json 到 .gitignore');
389
+ } catch (error) {
390
+ Ec.warn(`⚠ 更新 .gitignore 失败: ${error.message}`);
391
+ }
392
+ };
393
+
394
+ module.exports = async (options) => {
395
+ try {
396
+ // 1. 解析 -d 参数,获取目标目录
397
+ const dirArg = parseOptional('dir', 'd');
398
+ const targetDir = dirArg.hasFlag && dirArg.value
399
+ ? path.resolve(dirArg.value)
400
+ : process.cwd();
401
+
402
+ Ec.info(`📁 目标目录: ${targetDir.cyan}`);
403
+ Ec.info(`💻 操作系统: ${_getOsName().cyan}`);
404
+
405
+ // 2. 检查目标目录是否存在
406
+ if (!fs.existsSync(targetDir)) {
407
+ Ec.error(`❌ 目录不存在: ${targetDir}`);
408
+ process.exit(1);
409
+ }
410
+
411
+ // 3. 检查目录是否是文件夹
412
+ const stat = fs.statSync(targetDir);
413
+ if (!stat.isDirectory()) {
414
+ Ec.error(`❌ 路径不是目录: ${targetDir}`);
415
+ process.exit(1);
416
+ }
417
+
418
+ // 4. 检查 .obsidian 配置是否存在,不存在则自动初始化
419
+ const obsidianConfigPath = path.join(targetDir, '.obsidian');
420
+ if (!fs.existsSync(obsidianConfigPath)) {
421
+ Ec.warn(`⚠ 目录中不存在 .obsidian 配置,正在自动初始化...`);
422
+
423
+ const initSuccess = await _initObsidianConfig(targetDir);
424
+ if (!initSuccess) {
425
+ Ec.error('❌ 无法初始化 .obsidian 配置');
426
+ console.log('');
427
+ Ec.error('请手动使用 Obsidian 打开该目录以初始化配置');
428
+ console.log(' 下载地址: https://obsidian.md/download'.cyan);
429
+ console.log('');
430
+ process.exit(1);
431
+ }
432
+ } else {
433
+ Ec.info('✓ 检测到 .obsidian 配置');
434
+ }
435
+
436
+ // 4.5. 更新 .gitignore,添加 .obsidian/workspace.json
437
+ await _updateGitignore(targetDir);
438
+
439
+ // 5. 检查 Obsidian 是否已安装
440
+ Ec.waiting('正在检查 Obsidian 安装状态...');
441
+ const obsidianInstalled = await _isObsidianInstalled();
442
+
443
+ if (!obsidianInstalled) {
444
+ Ec.error('❌ 未检测到 Obsidian 安装');
445
+ console.log('');
446
+ Ec.error('请先安装 Obsidian:');
447
+ console.log(' 下载地址: https://obsidian.md/download'.cyan);
448
+ console.log('');
449
+ process.exit(1);
450
+ }
451
+
452
+ Ec.info('✓ Obsidian 已安装');
453
+
454
+ // 6. 检查 Obsidian 是否正在运行
455
+ const isRunning = await _isObsidianRunning();
456
+ if (isRunning) {
457
+ Ec.waiting('检测到 Obsidian 正在运行,正在重启以加载新 vault...');
458
+ await _closeObsidian();
459
+ }
460
+
461
+ // 7. 注册 vault 到 Obsidian 配置
462
+ Ec.waiting('正在注册 vault 到 Obsidian...');
463
+ const vaultId = await _registerVaultToObsidian(targetDir);
464
+ if (vaultId) {
465
+ Ec.info(`✓ Vault 已注册 (ID: ${vaultId.substring(0, 8)}...)`);
466
+ } else {
467
+ Ec.warn('⚠ Vault 注册失败,将尝试直接打开');
468
+ }
469
+
470
+ // 8. 使用 Obsidian 打开目录
471
+ Ec.waiting(`正在使用 Obsidian 打开: ${targetDir.cyan}...`);
472
+
473
+ try {
474
+ await _openWithObsidian(targetDir, vaultId);
475
+ console.log('');
476
+ Ec.info(`✅ 已成功打开 Obsidian`);
477
+ if (vaultId) {
478
+ Ec.info(`💡 提示: 该 vault 现在会出现在 Obsidian 的本地仓库列表中`);
479
+ }
480
+ console.log('');
481
+ } catch (error) {
482
+ console.log('');
483
+ Ec.error(`❌ 打开失败: ${error.message}`);
484
+ console.log('');
485
+ Ec.warn('您可以手动使用 Obsidian 打开该目录');
486
+ console.log('');
487
+ }
488
+
489
+ // 短暂延迟后退出,确保进程完成
490
+ setTimeout(() => {
491
+ process.exit(0);
492
+ }, 1000);
493
+
494
+ } catch (error) {
495
+ Ec.error(`❌ 执行失败: ${error.message}`);
496
+ process.exit(1);
497
+ }
498
+ };