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 +1 -1
- package/src/_skill/repositories.json +6 -0
- package/src/executor/executeDocs.js +354 -203
package/package.json
CHANGED
|
@@ -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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
25
|
+
const possiblePaths = [
|
|
49
26
|
path.join(localAppData, 'Obsidian', 'Obsidian.exe'),
|
|
50
|
-
path.join(programFiles, 'Obsidian', 'Obsidian.exe')
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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('
|
|
33
|
+
execSync('which obsidian', { stdio: 'pipe' });
|
|
62
34
|
return true;
|
|
63
35
|
} catch {
|
|
64
|
-
|
|
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
|
|
71
|
-
* @param {string}
|
|
52
|
+
* 检查指定 vault 是否正在运行
|
|
53
|
+
* @param {string} vaultPath vault 路径
|
|
72
54
|
* @returns {Promise<boolean>}
|
|
73
55
|
*/
|
|
74
|
-
const _isVaultRunning = async (
|
|
75
|
-
const
|
|
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))
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
88
|
-
*
|
|
89
|
+
* 关闭指定 vault 的 Obsidian 窗口
|
|
90
|
+
* 通过更新配置文件将 vault 的 open 状态设为 false
|
|
91
|
+
* @param {string} vaultPath vault 路径
|
|
89
92
|
* @returns {Promise<void>}
|
|
90
93
|
*/
|
|
91
|
-
const _closeVault = async (
|
|
92
|
-
const
|
|
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))
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
*
|
|
108
|
-
*
|
|
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
|
|
113
|
-
const
|
|
114
|
-
|
|
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
|
-
|
|
171
|
+
const content = await fsAsync.readFile(configPath, 'utf8');
|
|
172
|
+
config = JSON.parse(content);
|
|
120
173
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* @param {string}
|
|
205
|
-
* @
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
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
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
//
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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(
|
|
378
|
-
|
|
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
|
-
|
|
536
|
+
// 短暂延迟后退出,确保进程完成
|
|
537
|
+
setTimeout(() => {
|
|
538
|
+
process.exit(0);
|
|
539
|
+
}, 1000);
|
|
389
540
|
|
|
390
541
|
} catch (error) {
|
|
391
542
|
Ec.error(`❌ 执行失败: ${error.message}`);
|