momo-ai 1.0.75 → 1.0.77

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "momo-ai",
3
- "version": "1.0.75",
3
+ "version": "1.0.77",
4
4
  "description": "Rachel Momo ( OpenSpec )",
5
5
  "main": "src/momo.js",
6
6
  "bin": {
@@ -47,6 +47,12 @@
47
47
  "description": "社区精选技能集合",
48
48
  "url": "https://github.com/abubakarsiddik31/claude-skills-collection.git",
49
49
  "skillsPath": "skills"
50
+ },
51
+ {
52
+ "name": "mxt",
53
+ "description": "MXT实战技能库",
54
+ "url": "https://gitee.com/silentbalanceyh/r2mo-ai.git",
55
+ "skillsPath": "skills"
50
56
  }
51
57
  ]
52
58
  }
@@ -1,5 +1,4 @@
1
- const { execSync } = require('child_process');
2
- const crypto = require('crypto');
1
+ const { spawn, execSync } = require('child_process');
3
2
  const fs = require('fs');
4
3
  const fsAsync = require('fs').promises;
5
4
  const path = require('path');
@@ -8,94 +7,123 @@ const Ec = require('../epic');
8
7
  const { parseOptional } = require('../utils/momo-args');
9
8
  const { copyDir } = require('../utils/momo-file-utils');
10
9
 
11
- const _getObsidianConfigPath = () => {
12
- const platform = os.platform();
13
-
14
- if (platform === 'darwin') {
15
- return path.join(os.homedir(), 'Library/Application Support/obsidian/obsidian.json');
16
- }
17
- if (platform === 'win32') {
18
- return path.join(process.env.APPDATA || '', 'obsidian', 'obsidian.json');
19
- }
20
-
21
- const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
22
- return path.join(xdgConfig, 'obsidian', 'obsidian.json');
23
- };
24
-
25
- /**
26
- * 根据绝对路径生成确定性 vault ID(sha256 前 16 位 hex)
27
- * 同一路径永远得到相同 ID,避免随机 ID 导致 Vault Not Found
28
- * @param {string} absPath vault 绝对路径
29
- * @returns {string}
30
- */
31
- const _deriveVaultId = (absPath) => {
32
- return crypto.createHash('sha256').update(absPath).digest('hex').slice(0, 16);
33
- };
34
-
35
10
  /**
36
11
  * 检查 Obsidian 是否已安装
37
- * @returns {boolean}
12
+ * @returns {Promise<boolean>}
38
13
  */
39
- const _isObsidianInstalled = () => {
14
+ const _isObsidianInstalled = async () => {
40
15
  const platform = os.platform();
41
-
16
+
42
17
  if (platform === 'darwin') {
43
- return fs.existsSync('/Applications/Obsidian.app');
44
- }
45
- if (platform === 'win32') {
18
+ // macOS: 检查 Applications 目录
19
+ const obsidianPath = '/Applications/Obsidian.app';
20
+ return fs.existsSync(obsidianPath);
21
+ } else if (platform === 'win32') {
22
+ // Windows: 检查常见安装路径或注册表
46
23
  const localAppData = process.env.LOCALAPPDATA || '';
47
24
  const programFiles = process.env.PROGRAMFILES || 'C:\\Program Files';
48
- return [
25
+ const possiblePaths = [
49
26
  path.join(localAppData, 'Obsidian', 'Obsidian.exe'),
50
- path.join(programFiles, 'Obsidian', 'Obsidian.exe'),
51
- ].some(p => fs.existsSync(p));
52
- }
53
-
54
- // Linux
55
- try {
56
- execSync('which obsidian', { stdio: 'pipe' });
57
- return true;
58
- } catch {
59
- if (fs.existsSync('/snap/bin/obsidian')) return true;
27
+ path.join(programFiles, 'Obsidian', 'Obsidian.exe')
28
+ ];
29
+ return possiblePaths.some(p => fs.existsSync(p));
30
+ } else {
31
+ // Linux: 检查 which obsidian 或常见路径
60
32
  try {
61
- execSync('flatpak list | grep -i obsidian', { stdio: 'pipe' });
33
+ execSync('which obsidian', { stdio: 'pipe' });
62
34
  return true;
63
35
  } catch {
64
- return false;
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();
65
47
  }
66
48
  }
67
49
  };
68
50
 
69
51
  /**
70
- * 检查指定 vault 是否正在运行(open: true)
71
- * @param {string} vaultId 确定性 vault ID
52
+ * 检查指定 vault 是否正在运行
53
+ * @param {string} vaultPath vault 路径
72
54
  * @returns {Promise<boolean>}
73
55
  */
74
- const _isVaultRunning = async (vaultId) => {
75
- const configPath = _getObsidianConfigPath();
56
+ const _isVaultRunning = async (vaultPath) => {
57
+ const platform = os.platform();
58
+ let configPath;
59
+
60
+ if (platform === 'darwin') {
61
+ configPath = path.join(os.homedir(), 'Library/Application Support/obsidian/obsidian.json');
62
+ } else if (platform === 'win32') {
63
+ configPath = path.join(process.env.APPDATA || '', 'obsidian', 'obsidian.json');
64
+ } else {
65
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
66
+ configPath = path.join(xdgConfig, 'obsidian', 'obsidian.json');
67
+ }
68
+
76
69
  try {
77
- if (!fs.existsSync(configPath)) return false;
78
- const config = JSON.parse(await fsAsync.readFile(configPath, 'utf8'));
79
- const vault = (config.vaults || {})[vaultId];
80
- return vault ? vault.open === true : false;
70
+ if (!fs.existsSync(configPath)) {
71
+ return false;
72
+ }
73
+
74
+ const content = await fsAsync.readFile(configPath, 'utf8');
75
+ const config = JSON.parse(content);
76
+
77
+ // 检查是否有 vault 的 open 状态为 true 且路径匹配
78
+ const openVault = Object.values(config.vaults || {}).find(
79
+ vault => vault.path === vaultPath && vault.open === true
80
+ );
81
+
82
+ return !!openVault;
81
83
  } catch {
82
84
  return false;
83
85
  }
84
86
  };
85
87
 
86
88
  /**
87
- * 关闭指定 vault(将 open 设为 false)
88
- * @param {string} vaultId 确定性 vault ID
89
+ * 关闭指定 vault Obsidian 窗口
90
+ * 通过更新配置文件将 vault open 状态设为 false
91
+ * @param {string} vaultPath vault 路径
89
92
  * @returns {Promise<void>}
90
93
  */
91
- const _closeVault = async (vaultId) => {
92
- const configPath = _getObsidianConfigPath();
94
+ const _closeVault = async (vaultPath) => {
95
+ const platform = os.platform();
96
+ let configPath;
97
+
98
+ if (platform === 'darwin') {
99
+ configPath = path.join(os.homedir(), 'Library/Application Support/obsidian/obsidian.json');
100
+ } else if (platform === 'win32') {
101
+ configPath = path.join(process.env.APPDATA || '', 'obsidian', 'obsidian.json');
102
+ } else {
103
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
104
+ configPath = path.join(xdgConfig, 'obsidian', 'obsidian.json');
105
+ }
106
+
93
107
  try {
94
- if (!fs.existsSync(configPath)) return;
95
- const config = JSON.parse(await fsAsync.readFile(configPath, 'utf8'));
96
- if (config.vaults && config.vaults[vaultId] && config.vaults[vaultId].open) {
97
- const updatedVaults = { ...config.vaults, [vaultId]: { ...config.vaults[vaultId], open: false } };
98
- await fsAsync.writeFile(configPath, JSON.stringify({ ...config, vaults: updatedVaults }), 'utf8');
108
+ if (!fs.existsSync(configPath)) {
109
+ return;
110
+ }
111
+
112
+ const content = await fsAsync.readFile(configPath, 'utf8');
113
+ const config = JSON.parse(content);
114
+
115
+ // 找到对应的 vault 并设置 open 为 false
116
+ let updated = false;
117
+ for (const [id, vault] of Object.entries(config.vaults || {})) {
118
+ if (vault.path === vaultPath && vault.open === true) {
119
+ config.vaults[id].open = false;
120
+ updated = true;
121
+ }
122
+ }
123
+
124
+ if (updated) {
125
+ await fsAsync.writeFile(configPath, JSON.stringify(config), 'utf8');
126
+ // 等待配置生效
99
127
  await new Promise(resolve => setTimeout(resolve, 500));
100
128
  }
101
129
  } catch (error) {
@@ -104,43 +132,75 @@ const _closeVault = async (vaultId) => {
104
132
  };
105
133
 
106
134
  /**
107
- * 注册 vault obsidian.json,使用确定性 ID
108
- * 若已存在同路径的旧随机 ID 记录,先删除旧记录再以确定性 ID 写入。
109
- * @param {string} vaultPath vault 绝对路径
110
- * @returns {Promise<string|null>} 确定性 vault ID,失败返回 null
135
+ * 生成 vault ID(模仿 Obsidian 的 16 位十六进制 ID
136
+ * @returns {string}
111
137
  */
112
- const _registerVaultToObsidian = async (vaultPath) => {
113
- const configPath = _getObsidianConfigPath();
114
- const vaultId = _deriveVaultId(vaultPath);
138
+ const _generateVaultId = () => {
139
+ const chars = '0123456789abcdef';
140
+ let id = '';
141
+ for (let i = 0; i < 16; i++) {
142
+ id += chars[Math.floor(Math.random() * chars.length)];
143
+ }
144
+ return id;
145
+ };
115
146
 
147
+ /**
148
+ * 注册 vault 到 Obsidian 配置
149
+ * @param {string} vaultPath vault 路径
150
+ * @returns {Promise<string|null>} 返回 vault ID,失败返回 null
151
+ */
152
+ const _registerVaultToObsidian = async (vaultPath) => {
153
+ const platform = os.platform();
154
+ let configPath;
155
+
156
+ if (platform === 'darwin') {
157
+ configPath = path.join(os.homedir(), 'Library/Application Support/obsidian/obsidian.json');
158
+ } else if (platform === 'win32') {
159
+ configPath = path.join(process.env.APPDATA || '', 'obsidian', 'obsidian.json');
160
+ } else {
161
+ // Linux
162
+ const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
163
+ configPath = path.join(xdgConfig, 'obsidian', 'obsidian.json');
164
+ }
165
+
116
166
  try {
117
167
  let config = { vaults: {}, frame: 'custom' };
168
+
169
+ // 读取现有配置
118
170
  if (fs.existsSync(configPath)) {
119
- config = JSON.parse(await fsAsync.readFile(configPath, 'utf8'));
171
+ const content = await fsAsync.readFile(configPath, 'utf8');
172
+ config = JSON.parse(content);
120
173
  }
121
- if (!config.vaults) config.vaults = {};
122
-
123
- // 删除所有同路径的旧记录(key 可能是随机 ID)
124
- const updatedVaults = {};
125
- for (const [id, vault] of Object.entries(config.vaults)) {
126
- if (vault.path !== vaultPath) {
127
- updatedVaults[id] = vault;
128
- }
174
+
175
+ // 检查 vault 是否已存在
176
+ const existingVault = Object.entries(config.vaults || {}).find(
177
+ ([id, vault]) => vault.path === vaultPath
178
+ );
179
+
180
+ let vaultId;
181
+ if (existingVault) {
182
+ // 已存在,更新 ts 和 open 状态
183
+ [vaultId] = existingVault;
184
+ config.vaults[vaultId].ts = Date.now();
185
+ config.vaults[vaultId].open = true;
186
+ } else {
187
+ // 新 vault,生成 ID 并添加
188
+ vaultId = _generateVaultId();
189
+ config.vaults[vaultId] = {
190
+ path: vaultPath,
191
+ ts: Date.now(),
192
+ open: true
193
+ };
129
194
  }
130
-
131
- // 写入确定性 ID 记录
132
- updatedVaults[vaultId] = {
133
- path: vaultPath,
134
- ts: Date.now(),
135
- open: true,
136
- };
137
-
195
+
196
+ // 确保配置目录存在
138
197
  const configDir = path.dirname(configPath);
139
198
  if (!fs.existsSync(configDir)) {
140
199
  await fsAsync.mkdir(configDir, { recursive: true });
141
200
  }
142
-
143
- await fsAsync.writeFile(configPath, JSON.stringify({ ...config, vaults: updatedVaults }), 'utf8');
201
+
202
+ // 写入配置
203
+ await fsAsync.writeFile(configPath, JSON.stringify(config), 'utf8');
144
204
  return vaultId;
145
205
  } catch (error) {
146
206
  Ec.warn(`⚠ 无法注册 vault 到配置: ${error.message}`);
@@ -148,72 +208,147 @@ const _registerVaultToObsidian = async (vaultPath) => {
148
208
  }
149
209
  };
150
210
 
151
- const _removeVaultRegistration = async (vaultPath) => {
152
- const configPath = _getObsidianConfigPath();
153
- try {
154
- if (!fs.existsSync(configPath)) return false;
155
- const config = JSON.parse(await fsAsync.readFile(configPath, 'utf8'));
156
- let updated = false;
157
- const updatedVaults = {};
158
- for (const [id, vault] of Object.entries(config.vaults || {})) {
159
- if (vault.path === vaultPath) {
160
- updated = true;
161
- } else {
162
- updatedVaults[id] = vault;
163
- }
164
- }
165
- if (!updated) return false;
166
- await fsAsync.writeFile(configPath, JSON.stringify({ ...config, vaults: updatedVaults }), 'utf8');
167
- return true;
168
- } catch (error) {
169
- Ec.warn(`⚠ 清理 vault 注册失败: ${error.message}`);
170
- return false;
171
- }
172
- };
173
-
174
- const _removeLocalObsidianConfig = async (targetDir) => {
175
- const obsidianConfigPath = path.join(targetDir, '.obsidian');
176
- try {
177
- if (!fs.existsSync(obsidianConfigPath)) return false;
178
- await fsAsync.rm(obsidianConfigPath, { recursive: true, force: true });
179
- return true;
180
- } catch (error) {
181
- Ec.warn(`⚠ 清理本地 .obsidian 失败: ${error.message}`);
182
- return false;
183
- }
184
- };
185
-
186
- const _resetVaultConfig = async (targetDir) => {
187
- Ec.waiting(`正在清理 Obsidian 配置: ${targetDir.cyan}...`);
188
-
189
- const vaultId = _deriveVaultId(targetDir);
190
- const isRunning = await _isVaultRunning(vaultId);
191
- if (isRunning) await _closeVault(vaultId);
192
-
193
- const removedVault = await _removeVaultRegistration(targetDir);
194
- const removedConfig = await _removeLocalObsidianConfig(targetDir);
195
-
196
- Ec.info(removedVault ? '✓ 已移除 Obsidian 中的 vault 注册' : '✓ 未发现需要移除的 vault 注册');
197
- Ec.info(removedConfig ? '✓ 已删除目标目录下的 .obsidian 配置' : '✓ 目标目录下不存在 .obsidian 配置');
198
- Ec.info('✅ Obsidian 配置清理完成');
199
- };
200
-
201
211
  /**
202
- * 通过 Obsidian URL scheme 打开 vault
203
- * 使用确定性 ID,保证每次打开的 ID 与注册时一致
204
- * @param {string} vaultId 确定性 vault ID
205
- * @returns {void}
212
+ * 使用 Obsidian 打开目录
213
+ * 直接调用 Obsidian 可执行文件,传递目录路径作为参数
214
+ * @param {string} targetDir 目标目录绝对路径
215
+ * @param {string} vaultId vault ID,用于 URL scheme
216
+ * @returns {Promise<void>}
206
217
  */
207
- const _openWithObsidian = (vaultId) => {
208
- const url = `obsidian://open?vault=${vaultId}`;
218
+ const _openWithObsidian = async (targetDir, vaultId = null) => {
209
219
  const platform = os.platform();
210
220
 
211
221
  if (platform === 'darwin') {
212
- execSync(`open "${url}"`, { stdio: 'ignore' });
222
+ // macOS: 优先使用 vault ID 的 URL scheme,更可靠
223
+ if (vaultId) {
224
+ const obsidianUri = `obsidian://open?vault=${vaultId}`;
225
+
226
+ // 先退出 Obsidian,确保 URL scheme 能正确打开新 vault
227
+ try {
228
+ execSync('osascript -e \'tell application "Obsidian" to quit\'', {
229
+ stdio: 'ignore',
230
+ timeout: 2000
231
+ });
232
+ await new Promise(resolve => setTimeout(resolve, 1000));
233
+ } catch {
234
+ // Obsidian 可能未运行,忽略错误
235
+ }
236
+
237
+ return new Promise((resolve, reject) => {
238
+ const child = spawn('open', [obsidianUri], {
239
+ stdio: 'ignore'
240
+ });
241
+
242
+ child.on('error', (error) => {
243
+ reject(new Error(`启动 Obsidian 失败: ${error.message}`));
244
+ });
245
+
246
+ child.on('close', (code) => {
247
+ if (code === 0) {
248
+ // 给 Obsidian 时间加载
249
+ setTimeout(() => resolve(), 1000);
250
+ } else {
251
+ reject(new Error(`启动 Obsidian 失败,退出码: ${code}`));
252
+ }
253
+ });
254
+ });
255
+ } else {
256
+ // 回退到直接调用可执行文件
257
+ const obsidianBinary = '/Applications/Obsidian.app/Contents/MacOS/Obsidian';
258
+
259
+ if (!fs.existsSync(obsidianBinary)) {
260
+ throw new Error('找不到 Obsidian 可执行文件');
261
+ }
262
+
263
+ return new Promise((resolve, reject) => {
264
+ const child = spawn(obsidianBinary, [targetDir], {
265
+ detached: true,
266
+ stdio: 'ignore'
267
+ });
268
+
269
+ child.on('error', (error) => {
270
+ reject(new Error(`启动 Obsidian 失败: ${error.message}`));
271
+ });
272
+
273
+ child.unref();
274
+ setTimeout(() => resolve(), 500);
275
+ });
276
+ }
213
277
  } else if (platform === 'win32') {
214
- execSync(`start "" "${url}"`, { stdio: 'ignore', shell: true });
278
+ // Windows: 查找 Obsidian.exe 并直接打开目录
279
+ const localAppData = process.env.LOCALAPPDATA || '';
280
+ const programFiles = process.env.PROGRAMFILES || 'C:\\Program Files';
281
+ const possiblePaths = [
282
+ path.join(localAppData, 'Obsidian', 'Obsidian.exe'),
283
+ path.join(programFiles, 'Obsidian', 'Obsidian.exe')
284
+ ];
285
+
286
+ const obsidianExe = possiblePaths.find(p => fs.existsSync(p));
287
+ if (!obsidianExe) {
288
+ throw new Error('找不到 Obsidian 可执行文件');
289
+ }
290
+
291
+ return new Promise((resolve, reject) => {
292
+ const child = spawn(obsidianExe, [targetDir], {
293
+ detached: true,
294
+ stdio: 'ignore'
295
+ });
296
+
297
+ child.on('error', (error) => {
298
+ reject(new Error(`启动 Obsidian 失败: ${error.message}`));
299
+ });
300
+
301
+ child.unref();
302
+
303
+ setTimeout(() => {
304
+ resolve();
305
+ }, 500);
306
+ });
215
307
  } else {
216
- execSync(`xdg-open "${url}"`, { stdio: 'ignore' });
308
+ // Linux: 尝试直接启动 obsidian 命令
309
+ const snapPath = '/snap/bin/obsidian';
310
+ const flatpakPath = 'flatpak run md.obsidian.Obsidian';
311
+ let obsidianCmd = 'obsidian';
312
+ let args = [targetDir];
313
+ let useShell = false;
314
+
315
+ // 检查 snap 安装
316
+ if (fs.existsSync(snapPath)) {
317
+ obsidianCmd = snapPath;
318
+ } else {
319
+ // 尝试 flatpak
320
+ try {
321
+ execSync('flatpak list | grep -i obsidian', { stdio: 'pipe' });
322
+ obsidianCmd = flatpakPath;
323
+ useShell = true;
324
+ args = [targetDir];
325
+ } catch {
326
+ // 使用默认命令
327
+ }
328
+ }
329
+
330
+ return new Promise((resolve, reject) => {
331
+ const spawnOptions = {
332
+ detached: true,
333
+ stdio: 'ignore'
334
+ };
335
+
336
+ if (useShell) {
337
+ spawnOptions.shell = true;
338
+ }
339
+
340
+ const child = spawn(obsidianCmd, args, spawnOptions);
341
+
342
+ child.on('error', (error) => {
343
+ reject(new Error(`启动 Obsidian 失败: ${error.message}`));
344
+ });
345
+
346
+ child.unref();
347
+
348
+ setTimeout(() => {
349
+ resolve();
350
+ }, 500);
351
+ });
217
352
  }
218
353
  };
219
354
 
@@ -222,34 +357,38 @@ const _openWithObsidian = (vaultId) => {
222
357
  * @returns {string}
223
358
  */
224
359
  const _getOsName = () => {
225
- switch (os.platform()) {
360
+ const platform = os.platform();
361
+ switch (platform) {
226
362
  case 'darwin': return 'macOS';
227
363
  case 'win32': return 'Windows';
228
364
  case 'linux': return 'Linux';
229
- default: return os.platform();
365
+ default: return platform;
230
366
  }
231
367
  };
232
368
 
233
369
  /**
234
- * 初始化 .obsidian 目录(从模板复制)
370
+ * 初始化 .obsidian 目录
371
+ * 从模板目录复制默认配置
235
372
  * @param {string} targetDir 目标目录
236
- * @returns {Promise<boolean>}
373
+ * @returns {Promise<boolean>} 是否成功初始化
237
374
  */
238
375
  const _initObsidianConfig = async (targetDir) => {
239
376
  const obsidianSourcePath = path.resolve(__dirname, '../_template/LAIN/.obsidian');
240
377
  const obsidianTargetPath = path.join(targetDir, '.obsidian');
241
-
378
+
379
+ // 检查模板目录是否存在
242
380
  if (!fs.existsSync(obsidianSourcePath)) {
243
381
  Ec.warn(`⚠ 模板目录不存在: ${obsidianSourcePath}`);
244
382
  return false;
245
383
  }
246
-
384
+
247
385
  try {
248
386
  const stat = await fsAsync.stat(obsidianSourcePath);
249
387
  if (!stat.isDirectory()) {
250
388
  Ec.warn(`⚠ 模板路径不是目录: ${obsidianSourcePath}`);
251
389
  return false;
252
390
  }
391
+
253
392
  Ec.waiting('正在初始化 .obsidian 配置...');
254
393
  await copyDir(obsidianSourcePath, obsidianTargetPath);
255
394
  Ec.info('✓ 已创建 .obsidian 配置目录');
@@ -261,73 +400,73 @@ const _initObsidianConfig = async (targetDir) => {
261
400
  };
262
401
 
263
402
  /**
264
- * 更新 .gitignore,添加 .obsidian/workspace.json
403
+ * 更新目标目录的 .gitignore 文件
404
+ * 添加 .obsidian/workspace.json 到忽略列表
265
405
  * @param {string} targetDir 目标目录
266
406
  * @returns {Promise<void>}
267
407
  */
268
408
  const _updateGitignore = async (targetDir) => {
269
409
  const gitignorePath = path.join(targetDir, '.gitignore');
270
410
  const ignoreEntry = '.obsidian/workspace.json';
271
-
411
+
272
412
  try {
273
- let content = fs.existsSync(gitignorePath)
274
- ? await fsAsync.readFile(gitignorePath, 'utf8')
275
- : '';
276
-
277
- if (content.includes(ignoreEntry)) {
413
+ let gitignoreContent = '';
414
+
415
+ // 读取现有的 .gitignore 内容
416
+ if (fs.existsSync(gitignorePath)) {
417
+ gitignoreContent = await fsAsync.readFile(gitignorePath, 'utf8');
418
+ }
419
+
420
+ // 检查是否已经包含该条目
421
+ if (gitignoreContent.includes(ignoreEntry)) {
278
422
  Ec.info('✓ .gitignore 已包含 .obsidian/workspace.json');
279
423
  return;
280
424
  }
281
-
282
- if (content && !content.endsWith('\n')) content += '\n';
283
- content += `${ignoreEntry}\n`;
284
- await fsAsync.writeFile(gitignorePath, content, 'utf8');
425
+
426
+ // 添加条目
427
+ if (gitignoreContent && !gitignoreContent.endsWith('\n')) {
428
+ gitignoreContent += '\n';
429
+ }
430
+
431
+ gitignoreContent += `${ignoreEntry}\n`;
432
+
433
+ // 写入 .gitignore 文件
434
+ await fsAsync.writeFile(gitignorePath, gitignoreContent, 'utf8');
285
435
  Ec.info('✓ 已添加 .obsidian/workspace.json 到 .gitignore');
286
436
  } catch (error) {
287
437
  Ec.warn(`⚠ 更新 .gitignore 失败: ${error.message}`);
288
438
  }
289
439
  };
290
440
 
291
- module.exports = async () => {
441
+ module.exports = async (options) => {
292
442
  try {
443
+ // 1. 解析 -d 参数,获取目标目录
293
444
  const dirArg = parseOptional('dir', 'd');
294
- const removeArg = parseOptional('remove', 'r');
295
-
296
- if (dirArg.hasFlag && removeArg.hasFlag) {
297
- Ec.error('❌ 参数 -d/--dir 与 -r/--remove 不能同时使用');
298
- process.exit(1);
299
- }
300
-
301
- let targetDir = process.cwd();
302
- if (removeArg.hasFlag) {
303
- targetDir = removeArg.value ? path.resolve(removeArg.value) : process.cwd();
304
- } else if (dirArg.hasFlag) {
305
- targetDir = dirArg.value ? path.resolve(dirArg.value) : process.cwd();
306
- }
445
+ const targetDir = dirArg.hasFlag && dirArg.value
446
+ ? path.resolve(dirArg.value)
447
+ : process.cwd();
307
448
 
308
449
  Ec.info(`📁 目标目录: ${targetDir.cyan}`);
309
450
  Ec.info(`💻 操作系统: ${_getOsName().cyan}`);
310
451
 
452
+ // 2. 检查目标目录是否存在
311
453
  if (!fs.existsSync(targetDir)) {
312
454
  Ec.error(`❌ 目录不存在: ${targetDir}`);
313
455
  process.exit(1);
314
456
  }
315
457
 
316
- if (!fs.statSync(targetDir).isDirectory()) {
458
+ // 3. 检查目录是否是文件夹
459
+ const stat = fs.statSync(targetDir);
460
+ if (!stat.isDirectory()) {
317
461
  Ec.error(`❌ 路径不是目录: ${targetDir}`);
318
462
  process.exit(1);
319
463
  }
320
464
 
321
- if (removeArg.hasFlag) {
322
- await _resetVaultConfig(targetDir);
323
- console.log('');
324
- process.exit(0);
325
- }
326
-
327
- // 检查 .obsidian 配置,不存在则初始化
465
+ // 4. 检查 .obsidian 配置是否存在,不存在则自动初始化
328
466
  const obsidianConfigPath = path.join(targetDir, '.obsidian');
329
467
  if (!fs.existsSync(obsidianConfigPath)) {
330
468
  Ec.warn(`⚠ 目录中不存在 .obsidian 配置,正在自动初始化...`);
469
+
331
470
  const initSuccess = await _initObsidianConfig(targetDir);
332
471
  if (!initSuccess) {
333
472
  Ec.error('❌ 无法初始化 .obsidian 配置');
@@ -341,9 +480,14 @@ module.exports = async () => {
341
480
  Ec.info('✓ 检测到 .obsidian 配置');
342
481
  }
343
482
 
483
+ // 4.5. 更新 .gitignore,添加 .obsidian/workspace.json
344
484
  await _updateGitignore(targetDir);
345
485
 
346
- if (!_isObsidianInstalled()) {
486
+ // 5. 检查 Obsidian 是否已安装
487
+ Ec.waiting('正在检查 Obsidian 安装状态...');
488
+ const obsidianInstalled = await _isObsidianInstalled();
489
+
490
+ if (!obsidianInstalled) {
347
491
  Ec.error('❌ 未检测到 Obsidian 安装');
348
492
  console.log('');
349
493
  Ec.error('请先安装 Obsidian:');
@@ -354,28 +498,32 @@ module.exports = async () => {
354
498
 
355
499
  Ec.info('✓ Obsidian 已安装');
356
500
 
357
- // 派生确定性 ID 并注册(自动替换旧随机 ID 记录)
358
- Ec.waiting('正在注册 vault...');
359
- const vaultId = await _registerVaultToObsidian(targetDir);
360
- if (!vaultId) {
361
- Ec.error('❌ Vault 注册失败,无法打开');
362
- process.exit(1);
501
+ // 6. 检查当前 vault 是否正在运行
502
+ const isVaultRunning = await _isVaultRunning(targetDir);
503
+ if (isVaultRunning) {
504
+ Ec.waiting('检测到该 vault 正在运行,正在关闭以重新加载...');
505
+ await _closeVault(targetDir);
363
506
  }
364
- Ec.info(`✓ Vault 已注册 (ID: ${vaultId})`);
365
507
 
366
- // vault 正在运行,先关闭再重新打开
367
- const isRunning = await _isVaultRunning(vaultId);
368
- if (isRunning) {
369
- Ec.waiting('检测到该 vault 正在运行,正在关闭以重新加载...');
370
- await _closeVault(vaultId);
508
+ // 7. 注册 vault 到 Obsidian 配置
509
+ Ec.waiting('正在注册 vault Obsidian...');
510
+ const vaultId = await _registerVaultToObsidian(targetDir);
511
+ if (vaultId) {
512
+ Ec.info(`✓ Vault 已注册 (ID: ${vaultId.substring(0, 8)}...)`);
513
+ } else {
514
+ Ec.warn('⚠ Vault 注册失败,将尝试直接打开');
371
515
  }
372
516
 
517
+ // 8. 使用 Obsidian 打开目录
373
518
  Ec.waiting(`正在使用 Obsidian 打开: ${targetDir.cyan}...`);
519
+
374
520
  try {
375
- _openWithObsidian(vaultId);
521
+ await _openWithObsidian(targetDir, vaultId);
376
522
  console.log('');
377
- Ec.info('✅ 已成功打开 Obsidian');
378
- Ec.info('💡 提示: 该 vault 现在会出现在 Obsidian 的本地仓库列表中');
523
+ Ec.info(`✅ 已成功打开 Obsidian`);
524
+ if (vaultId) {
525
+ Ec.info(`💡 提示: 该 vault 现在会出现在 Obsidian 的本地仓库列表中`);
526
+ }
379
527
  console.log('');
380
528
  } catch (error) {
381
529
  console.log('');
@@ -385,7 +533,10 @@ module.exports = async () => {
385
533
  console.log('');
386
534
  }
387
535
 
388
- setTimeout(() => process.exit(0), 1000);
536
+ // 短暂延迟后退出,确保进程完成
537
+ setTimeout(() => {
538
+ process.exit(0);
539
+ }, 1000);
389
540
 
390
541
  } catch (error) {
391
542
  Ec.error(`❌ 执行失败: ${error.message}`);