momo-ai 1.0.74 → 1.0.75

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