momo-ai 1.0.21 → 1.0.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/r2mo-rad-lain/SKILL.md +63 -374
- package/.trae/skills/algorithmic-art/LICENSE.txt +202 -0
- package/.trae/skills/algorithmic-art/SKILL.md +405 -0
- package/.trae/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/.trae/skills/algorithmic-art/templates/viewer.html +599 -0
- package/.trae/skills/doc-coauthoring/SKILL.md +375 -0
- package/.trae/skills/frontend-design/LICENSE.txt +177 -0
- package/.trae/skills/frontend-design/SKILL.md +42 -0
- package/.trae/skills/r2mo-rad-lain/SKILL.md +101 -0
- package/README.md +9 -32
- package/docs/images/r2mo-lain.png +0 -0
- package/package.json +11 -11
- package/skills/r2mo-rad-domain/SKILL.md +70 -0
- package/src/_skill/repositories.json +9 -3
- package/src/_template/LAIN/.obsidian/app.json +1 -0
- package/src/_template/LAIN/.obsidian/appearance.json +10 -0
- package/src/_template/LAIN/.obsidian/community-plugins.json +7 -0
- package/src/_template/LAIN/.obsidian/core-plugins.json +33 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/main.js +20876 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/manifest.json +11 -0
- package/src/_template/LAIN/.obsidian/plugins/dataview/styles.css +141 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/data.json +815 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/main.js +10 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/manifest.json +12 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-excalidraw-plugin/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/main.js +153 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/manifest.json +11 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-kanban/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/main.js +7732 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/manifest.json +10 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-plantuml/styles.css +38 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/main.js +504 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/manifest.json +12 -0
- package/src/_template/LAIN/.obsidian/plugins/obsidian-tasks-plugin/styles.css +1 -0
- package/src/_template/LAIN/.obsidian/snippets/body-font.css +27 -0
- package/src/_template/LAIN/.obsidian/themes/Primary/manifest.json +9 -0
- package/src/_template/LAIN/.obsidian/themes/Primary/theme.css +3878 -0
- package/src/_template/LAIN/.obsidian/themes/Retro Windows/manifest.json +7 -0
- package/src/_template/LAIN/.obsidian/themes/Retro Windows/theme.css +582 -0
- package/src/_template/LAIN/.obsidian/themes/RetroOS 98/manifest.json +9 -0
- package/src/_template/LAIN/.obsidian/themes/RetroOS 98/theme.css +2566 -0
- package/src/_template/LAIN/.obsidian/types.json +28 -0
- package/src/_template/LAIN/.obsidian/workspace.json +184 -0
- package/src/_template/LAIN/AGENTS.md +170 -16
- package/src/_template/R2MO/domain-enhance.md +10 -0
- package/src/commander/app.json +13 -0
- package/src/commander/apply.json +13 -0
- package/src/commander/ask.json +6 -0
- package/src/commander/docs.json +13 -0
- package/src/commander/domain.json +19 -0
- package/src/commander/init.json +1 -1
- package/src/commander/mmr0.json +6 -0
- package/src/commander/mmr2.json +6 -0
- package/src/executor/executeApp.js +133 -0
- package/src/executor/{executeSkills.js → executeApply.js} +166 -302
- package/src/executor/executeAsk.js +274 -0
- package/src/executor/executeDocs.js +498 -0
- package/src/executor/executeDomain.js +293 -0
- package/src/executor/executeInit.js +159 -383
- package/src/executor/executeMcp.js +74 -1
- package/src/executor/executeMmr0.js +488 -0
- package/src/executor/executeMmr2.js +880 -0
- package/src/executor/index.js +15 -3
- package/src/python/r2mo_proto.py +418 -0
- package/src/python/r2mo_proto_database.py +369 -0
- package/src/python/r2mo_proto_domain.py +458 -0
- package/src/utils/momo-menu.js +43 -13
- package/.claude/skills/r2mo-rad-lain/PROMPT.md +0 -281
- package/.claude/skills/r2mo-rad-lain/README.md +0 -192
- package/.claude/skills/r2mo-rad-lain/examples/argument-parsing.js +0 -154
- package/.claude/skills/r2mo-rad-lain/examples/file-operations.js +0 -182
- package/.claude/skills/r2mo-rad-lain/file-utils-api.md +0 -281
- package/.claude/skills/r2mo-rad-lain/menu-api.md +0 -187
- package/.claude/skills/r2mo-rad-lain/scripts/file-utils.js +0 -223
- package/.claude/skills/r2mo-rad-lain/scripts/menu.js +0 -289
- package/.claude/skills/r2mo-rad-lain/scripts/yaml-parser.js +0 -209
- package/.claude/skills/r2mo-rad-lain/templates/command.json.template +0 -13
- package/.claude/skills/r2mo-rad-lain/templates/executor.js.template +0 -32
- package/.claude/skills/r2mo-rad-lain/templates/interactive-menu.js.template +0 -221
- package/src/_template/LAIN/.momo/advanced/actor.md +0 -42
- package/src/_template/LAIN/.momo/advanced/refer.json +0 -46
- package/src/_template/LAIN/.momo/scripts/submodule-clean.sh +0 -56
- package/src/_template/LAIN/changes/proposal.md +0 -39
- package/src/_template/LAIN/changes/tasks/task-detail.md +0 -45
- package/src/_template/LAIN/changes/tasks.md +0 -49
- package/src/_template/LAIN/execute/admin-n-f-dashboard.md +0 -53
- package/src/_template/LAIN/execute/admin-n-f-form.md +0 -51
- package/src/_template/LAIN/execute/admin-n-f-home.md +0 -49
- package/src/_template/LAIN/execute/admin-n-f-list.md +0 -52
- package/src/_template/LAIN/execute/admin-n-f-login.md +0 -56
- package/src/_template/LAIN/specification/project-model.md +0 -13
- package/src/_template/LAIN/specification/project.md +0 -73
- package/src/_template/LAIN/specification/requirement.md +0 -25
- package/src/commander/skills.json +0 -20
|
@@ -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
|
+
};
|