@cubocompany/opengem 0.1.0
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/README.md +136 -0
- package/dist/index.js +1636 -0
- package/package.json +43 -0
- package/skills/README.md +4 -0
- package/skills/manifest.json +11 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1636 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { tool } from "@opencode-ai/plugin";
|
|
3
|
+
|
|
4
|
+
// src/lib/shell.ts
|
|
5
|
+
function makeBunSpawnShell() {
|
|
6
|
+
return async (cmd) => {
|
|
7
|
+
try {
|
|
8
|
+
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
9
|
+
const stdout = await new Response(proc.stdout).text();
|
|
10
|
+
const stderr = await new Response(proc.stderr).text();
|
|
11
|
+
const exitCode = await proc.exited;
|
|
12
|
+
return { exitCode, stdout, stderr };
|
|
13
|
+
} catch (err) {
|
|
14
|
+
return { exitCode: 127, stdout: "", stderr: String(err) };
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/lib/commands.ts
|
|
20
|
+
var TOOL_MANIFEST = {
|
|
21
|
+
obsidian_read: {
|
|
22
|
+
label: "Read Obsidian Note",
|
|
23
|
+
description: "Read an Obsidian note by file name or path",
|
|
24
|
+
futureSlashCommand: "obsidian.read"
|
|
25
|
+
},
|
|
26
|
+
obsidian_search: {
|
|
27
|
+
label: "Search Obsidian Vault",
|
|
28
|
+
description: "Search vault content by query string",
|
|
29
|
+
futureSlashCommand: "obsidian.search"
|
|
30
|
+
},
|
|
31
|
+
obsidian_create_note: {
|
|
32
|
+
label: "Create Obsidian Note",
|
|
33
|
+
description: "Create a new note in a vault",
|
|
34
|
+
futureSlashCommand: "obsidian.create"
|
|
35
|
+
},
|
|
36
|
+
obsidian_append_note: {
|
|
37
|
+
label: "Append to Obsidian Note",
|
|
38
|
+
description: "Append text to an existing note",
|
|
39
|
+
futureSlashCommand: "obsidian.append"
|
|
40
|
+
},
|
|
41
|
+
obsidian_set_property: {
|
|
42
|
+
label: "Set Obsidian Note Property",
|
|
43
|
+
description: "Set a frontmatter property on a note",
|
|
44
|
+
futureSlashCommand: "obsidian.property.set"
|
|
45
|
+
},
|
|
46
|
+
obsidian_skills_check: {
|
|
47
|
+
label: "Check Obsidian Skills",
|
|
48
|
+
description: "Validate that required Obsidian skills are discoverable by OpenCode",
|
|
49
|
+
futureSlashCommand: "obsidian.skills.check"
|
|
50
|
+
},
|
|
51
|
+
obsidian_env_doctor: {
|
|
52
|
+
label: "Obsidian Environment Doctor",
|
|
53
|
+
description: "Run environment diagnostics: CLI installed, app running, skills synced",
|
|
54
|
+
futureSlashCommand: "obsidian.env.doctor"
|
|
55
|
+
},
|
|
56
|
+
obsidian_backlinks: {
|
|
57
|
+
label: "List Backlinks",
|
|
58
|
+
description: "List all notes that link to a given note path",
|
|
59
|
+
futureSlashCommand: "obsidian.backlinks"
|
|
60
|
+
},
|
|
61
|
+
obsidian_tags: {
|
|
62
|
+
label: "List Tags",
|
|
63
|
+
description: "List all tags in the vault, or tags on a specific note",
|
|
64
|
+
futureSlashCommand: "obsidian.tags"
|
|
65
|
+
},
|
|
66
|
+
obsidian_tag_notes: {
|
|
67
|
+
label: "Notes by Tag",
|
|
68
|
+
description: "List all notes that use a specific tag",
|
|
69
|
+
futureSlashCommand: "obsidian.tag.notes"
|
|
70
|
+
},
|
|
71
|
+
obsidian_plugins: {
|
|
72
|
+
label: "List Plugins",
|
|
73
|
+
description: "List all installed Obsidian plugins",
|
|
74
|
+
futureSlashCommand: "obsidian.plugins"
|
|
75
|
+
},
|
|
76
|
+
obsidian_plugin_reload: {
|
|
77
|
+
label: "Reload Plugin",
|
|
78
|
+
description: "Reload an Obsidian plugin by ID without restarting the app",
|
|
79
|
+
futureSlashCommand: "obsidian.plugin.reload"
|
|
80
|
+
},
|
|
81
|
+
obsidian_dev_errors: {
|
|
82
|
+
label: "Dev: Recent Errors",
|
|
83
|
+
description: "Get recent JavaScript errors from the running Obsidian app",
|
|
84
|
+
futureSlashCommand: "obsidian.dev.errors"
|
|
85
|
+
},
|
|
86
|
+
obsidian_dev_console: {
|
|
87
|
+
label: "Dev: Console Capture",
|
|
88
|
+
description: "Start/stop debug capture or retrieve console output from the running app",
|
|
89
|
+
futureSlashCommand: "obsidian.dev.console"
|
|
90
|
+
},
|
|
91
|
+
obsidian_dev_screenshot: {
|
|
92
|
+
label: "Dev: Screenshot",
|
|
93
|
+
description: "Capture a screenshot of the running Obsidian app to a vault path (PNG)",
|
|
94
|
+
futureSlashCommand: "obsidian.dev.screenshot"
|
|
95
|
+
},
|
|
96
|
+
obsidian_dev_dom: {
|
|
97
|
+
label: "Dev: DOM Inspector",
|
|
98
|
+
description: "Inspect DOM elements by CSS selector (text, count, attribute, or computed CSS)",
|
|
99
|
+
futureSlashCommand: "obsidian.dev.dom"
|
|
100
|
+
},
|
|
101
|
+
obsidian_dev_css: {
|
|
102
|
+
label: "Dev: CSS Inspector",
|
|
103
|
+
description: "Inspect computed CSS properties by CSS selector",
|
|
104
|
+
futureSlashCommand: "obsidian.dev.css"
|
|
105
|
+
},
|
|
106
|
+
obsidian_wiki_init: {
|
|
107
|
+
label: "Wiki: Initialize",
|
|
108
|
+
description: "Set up the wiki folder structure (raw/, wiki/, schema/) with SCHEMA.md, INDEX.md, and LOG.md. Run once per vault to get started.",
|
|
109
|
+
futureSlashCommand: "obsidian.wiki.init"
|
|
110
|
+
},
|
|
111
|
+
obsidian_wiki_ingest: {
|
|
112
|
+
label: "Wiki: Ingest Source",
|
|
113
|
+
description: "Copy a source document into raw/ and create or update its wiki/ page",
|
|
114
|
+
futureSlashCommand: "obsidian.wiki.ingest"
|
|
115
|
+
},
|
|
116
|
+
obsidian_wiki_update: {
|
|
117
|
+
label: "Wiki: Update Page",
|
|
118
|
+
description: "Create or overwrite a wiki/ knowledge page",
|
|
119
|
+
futureSlashCommand: "obsidian.wiki.update"
|
|
120
|
+
},
|
|
121
|
+
obsidian_wiki_refresh_index: {
|
|
122
|
+
label: "Wiki: Refresh Index",
|
|
123
|
+
description: "Rebuild wiki/INDEX.md with links to all wiki pages",
|
|
124
|
+
futureSlashCommand: "obsidian.wiki.refresh-index"
|
|
125
|
+
},
|
|
126
|
+
obsidian_wiki_search_cited: {
|
|
127
|
+
label: "Wiki: Search with Citations",
|
|
128
|
+
description: "Search wiki/ content and return results with source citations",
|
|
129
|
+
futureSlashCommand: "obsidian.wiki.search-cited"
|
|
130
|
+
},
|
|
131
|
+
obsidian_wiki_save_answer: {
|
|
132
|
+
label: "Wiki: Save Answer",
|
|
133
|
+
description: "Save a Q&A answer as a note in wiki/answers/",
|
|
134
|
+
futureSlashCommand: "obsidian.wiki.save-answer"
|
|
135
|
+
},
|
|
136
|
+
obsidian_wiki_lint: {
|
|
137
|
+
label: "Wiki: Health Check",
|
|
138
|
+
description: "Detect broken wikilinks, orphan pages, and missing index entries in wiki/",
|
|
139
|
+
futureSlashCommand: "obsidian.wiki.lint"
|
|
140
|
+
},
|
|
141
|
+
obsidian_eval: {
|
|
142
|
+
label: "Eval (opt-in)",
|
|
143
|
+
description: "Execute JavaScript in the Obsidian app context. Requires evalEnabled in plugin config.",
|
|
144
|
+
futureSlashCommand: "obsidian.eval"
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/lib/config.ts
|
|
149
|
+
function resolvePluginConfig(input) {
|
|
150
|
+
return {
|
|
151
|
+
defaultVault: input.defaultVault ?? null,
|
|
152
|
+
skills: {
|
|
153
|
+
mode: input.skills?.mode ?? "external",
|
|
154
|
+
externalPath: input.skills?.externalPath ?? null,
|
|
155
|
+
syncDirName: input.skills?.syncDirName ?? "obsidian-opencode-plugin-bundled"
|
|
156
|
+
},
|
|
157
|
+
wiki: {
|
|
158
|
+
rawDir: input.wiki?.rawDir ?? "raw",
|
|
159
|
+
wikiDir: input.wiki?.wikiDir ?? "wiki",
|
|
160
|
+
schemaDir: input.wiki?.schemaDir ?? "schema"
|
|
161
|
+
},
|
|
162
|
+
evalEnabled: input.evalEnabled ?? false
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function resolveVault(args) {
|
|
166
|
+
if (args.inputVault)
|
|
167
|
+
return args.inputVault;
|
|
168
|
+
if (args.config.defaultVault)
|
|
169
|
+
return args.config.defaultVault;
|
|
170
|
+
if (args.action === "read")
|
|
171
|
+
return args.activeVault;
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/lib/capabilities.ts
|
|
176
|
+
async function detectCli(which) {
|
|
177
|
+
return which();
|
|
178
|
+
}
|
|
179
|
+
async function detectApp(ping) {
|
|
180
|
+
return ping();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/lib/skills.ts
|
|
184
|
+
var {existsSync} = (() => ({}));
|
|
185
|
+
|
|
186
|
+
// node:path
|
|
187
|
+
function assertPath(path) {
|
|
188
|
+
if (typeof path !== "string")
|
|
189
|
+
throw TypeError("Path must be a string. Received " + JSON.stringify(path));
|
|
190
|
+
}
|
|
191
|
+
function normalizeStringPosix(path, allowAboveRoot) {
|
|
192
|
+
var res = "", lastSegmentLength = 0, lastSlash = -1, dots = 0, code;
|
|
193
|
+
for (var i = 0;i <= path.length; ++i) {
|
|
194
|
+
if (i < path.length)
|
|
195
|
+
code = path.charCodeAt(i);
|
|
196
|
+
else if (code === 47)
|
|
197
|
+
break;
|
|
198
|
+
else
|
|
199
|
+
code = 47;
|
|
200
|
+
if (code === 47) {
|
|
201
|
+
if (lastSlash === i - 1 || dots === 1)
|
|
202
|
+
;
|
|
203
|
+
else if (lastSlash !== i - 1 && dots === 2) {
|
|
204
|
+
if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
|
|
205
|
+
if (res.length > 2) {
|
|
206
|
+
var lastSlashIndex = res.lastIndexOf("/");
|
|
207
|
+
if (lastSlashIndex !== res.length - 1) {
|
|
208
|
+
if (lastSlashIndex === -1)
|
|
209
|
+
res = "", lastSegmentLength = 0;
|
|
210
|
+
else
|
|
211
|
+
res = res.slice(0, lastSlashIndex), lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
|
|
212
|
+
lastSlash = i, dots = 0;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
} else if (res.length === 2 || res.length === 1) {
|
|
216
|
+
res = "", lastSegmentLength = 0, lastSlash = i, dots = 0;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (allowAboveRoot) {
|
|
221
|
+
if (res.length > 0)
|
|
222
|
+
res += "/..";
|
|
223
|
+
else
|
|
224
|
+
res = "..";
|
|
225
|
+
lastSegmentLength = 2;
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
if (res.length > 0)
|
|
229
|
+
res += "/" + path.slice(lastSlash + 1, i);
|
|
230
|
+
else
|
|
231
|
+
res = path.slice(lastSlash + 1, i);
|
|
232
|
+
lastSegmentLength = i - lastSlash - 1;
|
|
233
|
+
}
|
|
234
|
+
lastSlash = i, dots = 0;
|
|
235
|
+
} else if (code === 46 && dots !== -1)
|
|
236
|
+
++dots;
|
|
237
|
+
else
|
|
238
|
+
dots = -1;
|
|
239
|
+
}
|
|
240
|
+
return res;
|
|
241
|
+
}
|
|
242
|
+
function _format(sep, pathObject) {
|
|
243
|
+
var dir = pathObject.dir || pathObject.root, base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
|
|
244
|
+
if (!dir)
|
|
245
|
+
return base;
|
|
246
|
+
if (dir === pathObject.root)
|
|
247
|
+
return dir + base;
|
|
248
|
+
return dir + sep + base;
|
|
249
|
+
}
|
|
250
|
+
function resolve() {
|
|
251
|
+
var resolvedPath = "", resolvedAbsolute = false, cwd;
|
|
252
|
+
for (var i = arguments.length - 1;i >= -1 && !resolvedAbsolute; i--) {
|
|
253
|
+
var path;
|
|
254
|
+
if (i >= 0)
|
|
255
|
+
path = arguments[i];
|
|
256
|
+
else {
|
|
257
|
+
if (cwd === undefined)
|
|
258
|
+
cwd = process.cwd();
|
|
259
|
+
path = cwd;
|
|
260
|
+
}
|
|
261
|
+
if (assertPath(path), path.length === 0)
|
|
262
|
+
continue;
|
|
263
|
+
resolvedPath = path + "/" + resolvedPath, resolvedAbsolute = path.charCodeAt(0) === 47;
|
|
264
|
+
}
|
|
265
|
+
if (resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute), resolvedAbsolute)
|
|
266
|
+
if (resolvedPath.length > 0)
|
|
267
|
+
return "/" + resolvedPath;
|
|
268
|
+
else
|
|
269
|
+
return "/";
|
|
270
|
+
else if (resolvedPath.length > 0)
|
|
271
|
+
return resolvedPath;
|
|
272
|
+
else
|
|
273
|
+
return ".";
|
|
274
|
+
}
|
|
275
|
+
function normalize(path) {
|
|
276
|
+
if (assertPath(path), path.length === 0)
|
|
277
|
+
return ".";
|
|
278
|
+
var isAbsolute = path.charCodeAt(0) === 47, trailingSeparator = path.charCodeAt(path.length - 1) === 47;
|
|
279
|
+
if (path = normalizeStringPosix(path, !isAbsolute), path.length === 0 && !isAbsolute)
|
|
280
|
+
path = ".";
|
|
281
|
+
if (path.length > 0 && trailingSeparator)
|
|
282
|
+
path += "/";
|
|
283
|
+
if (isAbsolute)
|
|
284
|
+
return "/" + path;
|
|
285
|
+
return path;
|
|
286
|
+
}
|
|
287
|
+
function isAbsolute(path) {
|
|
288
|
+
return assertPath(path), path.length > 0 && path.charCodeAt(0) === 47;
|
|
289
|
+
}
|
|
290
|
+
function join() {
|
|
291
|
+
if (arguments.length === 0)
|
|
292
|
+
return ".";
|
|
293
|
+
var joined;
|
|
294
|
+
for (var i = 0;i < arguments.length; ++i) {
|
|
295
|
+
var arg = arguments[i];
|
|
296
|
+
if (assertPath(arg), arg.length > 0)
|
|
297
|
+
if (joined === undefined)
|
|
298
|
+
joined = arg;
|
|
299
|
+
else
|
|
300
|
+
joined += "/" + arg;
|
|
301
|
+
}
|
|
302
|
+
if (joined === undefined)
|
|
303
|
+
return ".";
|
|
304
|
+
return normalize(joined);
|
|
305
|
+
}
|
|
306
|
+
function relative(from, to) {
|
|
307
|
+
if (assertPath(from), assertPath(to), from === to)
|
|
308
|
+
return "";
|
|
309
|
+
if (from = resolve(from), to = resolve(to), from === to)
|
|
310
|
+
return "";
|
|
311
|
+
var fromStart = 1;
|
|
312
|
+
for (;fromStart < from.length; ++fromStart)
|
|
313
|
+
if (from.charCodeAt(fromStart) !== 47)
|
|
314
|
+
break;
|
|
315
|
+
var fromEnd = from.length, fromLen = fromEnd - fromStart, toStart = 1;
|
|
316
|
+
for (;toStart < to.length; ++toStart)
|
|
317
|
+
if (to.charCodeAt(toStart) !== 47)
|
|
318
|
+
break;
|
|
319
|
+
var toEnd = to.length, toLen = toEnd - toStart, length = fromLen < toLen ? fromLen : toLen, lastCommonSep = -1, i = 0;
|
|
320
|
+
for (;i <= length; ++i) {
|
|
321
|
+
if (i === length) {
|
|
322
|
+
if (toLen > length) {
|
|
323
|
+
if (to.charCodeAt(toStart + i) === 47)
|
|
324
|
+
return to.slice(toStart + i + 1);
|
|
325
|
+
else if (i === 0)
|
|
326
|
+
return to.slice(toStart + i);
|
|
327
|
+
} else if (fromLen > length) {
|
|
328
|
+
if (from.charCodeAt(fromStart + i) === 47)
|
|
329
|
+
lastCommonSep = i;
|
|
330
|
+
else if (i === 0)
|
|
331
|
+
lastCommonSep = 0;
|
|
332
|
+
}
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
var fromCode = from.charCodeAt(fromStart + i), toCode = to.charCodeAt(toStart + i);
|
|
336
|
+
if (fromCode !== toCode)
|
|
337
|
+
break;
|
|
338
|
+
else if (fromCode === 47)
|
|
339
|
+
lastCommonSep = i;
|
|
340
|
+
}
|
|
341
|
+
var out = "";
|
|
342
|
+
for (i = fromStart + lastCommonSep + 1;i <= fromEnd; ++i)
|
|
343
|
+
if (i === fromEnd || from.charCodeAt(i) === 47)
|
|
344
|
+
if (out.length === 0)
|
|
345
|
+
out += "..";
|
|
346
|
+
else
|
|
347
|
+
out += "/..";
|
|
348
|
+
if (out.length > 0)
|
|
349
|
+
return out + to.slice(toStart + lastCommonSep);
|
|
350
|
+
else {
|
|
351
|
+
if (toStart += lastCommonSep, to.charCodeAt(toStart) === 47)
|
|
352
|
+
++toStart;
|
|
353
|
+
return to.slice(toStart);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function _makeLong(path) {
|
|
357
|
+
return path;
|
|
358
|
+
}
|
|
359
|
+
function dirname(path) {
|
|
360
|
+
if (assertPath(path), path.length === 0)
|
|
361
|
+
return ".";
|
|
362
|
+
var code = path.charCodeAt(0), hasRoot = code === 47, end = -1, matchedSlash = true;
|
|
363
|
+
for (var i = path.length - 1;i >= 1; --i)
|
|
364
|
+
if (code = path.charCodeAt(i), code === 47) {
|
|
365
|
+
if (!matchedSlash) {
|
|
366
|
+
end = i;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
} else
|
|
370
|
+
matchedSlash = false;
|
|
371
|
+
if (end === -1)
|
|
372
|
+
return hasRoot ? "/" : ".";
|
|
373
|
+
if (hasRoot && end === 1)
|
|
374
|
+
return "//";
|
|
375
|
+
return path.slice(0, end);
|
|
376
|
+
}
|
|
377
|
+
function basename(path, ext) {
|
|
378
|
+
if (ext !== undefined && typeof ext !== "string")
|
|
379
|
+
throw TypeError('"ext" argument must be a string');
|
|
380
|
+
assertPath(path);
|
|
381
|
+
var start = 0, end = -1, matchedSlash = true, i;
|
|
382
|
+
if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
|
|
383
|
+
if (ext.length === path.length && ext === path)
|
|
384
|
+
return "";
|
|
385
|
+
var extIdx = ext.length - 1, firstNonSlashEnd = -1;
|
|
386
|
+
for (i = path.length - 1;i >= 0; --i) {
|
|
387
|
+
var code = path.charCodeAt(i);
|
|
388
|
+
if (code === 47) {
|
|
389
|
+
if (!matchedSlash) {
|
|
390
|
+
start = i + 1;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
if (firstNonSlashEnd === -1)
|
|
395
|
+
matchedSlash = false, firstNonSlashEnd = i + 1;
|
|
396
|
+
if (extIdx >= 0)
|
|
397
|
+
if (code === ext.charCodeAt(extIdx)) {
|
|
398
|
+
if (--extIdx === -1)
|
|
399
|
+
end = i;
|
|
400
|
+
} else
|
|
401
|
+
extIdx = -1, end = firstNonSlashEnd;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (start === end)
|
|
405
|
+
end = firstNonSlashEnd;
|
|
406
|
+
else if (end === -1)
|
|
407
|
+
end = path.length;
|
|
408
|
+
return path.slice(start, end);
|
|
409
|
+
} else {
|
|
410
|
+
for (i = path.length - 1;i >= 0; --i)
|
|
411
|
+
if (path.charCodeAt(i) === 47) {
|
|
412
|
+
if (!matchedSlash) {
|
|
413
|
+
start = i + 1;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
} else if (end === -1)
|
|
417
|
+
matchedSlash = false, end = i + 1;
|
|
418
|
+
if (end === -1)
|
|
419
|
+
return "";
|
|
420
|
+
return path.slice(start, end);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function extname(path) {
|
|
424
|
+
assertPath(path);
|
|
425
|
+
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, preDotState = 0;
|
|
426
|
+
for (var i = path.length - 1;i >= 0; --i) {
|
|
427
|
+
var code = path.charCodeAt(i);
|
|
428
|
+
if (code === 47) {
|
|
429
|
+
if (!matchedSlash) {
|
|
430
|
+
startPart = i + 1;
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (end === -1)
|
|
436
|
+
matchedSlash = false, end = i + 1;
|
|
437
|
+
if (code === 46) {
|
|
438
|
+
if (startDot === -1)
|
|
439
|
+
startDot = i;
|
|
440
|
+
else if (preDotState !== 1)
|
|
441
|
+
preDotState = 1;
|
|
442
|
+
} else if (startDot !== -1)
|
|
443
|
+
preDotState = -1;
|
|
444
|
+
}
|
|
445
|
+
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
|
|
446
|
+
return "";
|
|
447
|
+
return path.slice(startDot, end);
|
|
448
|
+
}
|
|
449
|
+
function format(pathObject) {
|
|
450
|
+
if (pathObject === null || typeof pathObject !== "object")
|
|
451
|
+
throw TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject);
|
|
452
|
+
return _format("/", pathObject);
|
|
453
|
+
}
|
|
454
|
+
function parse(path) {
|
|
455
|
+
assertPath(path);
|
|
456
|
+
var ret = { root: "", dir: "", base: "", ext: "", name: "" };
|
|
457
|
+
if (path.length === 0)
|
|
458
|
+
return ret;
|
|
459
|
+
var code = path.charCodeAt(0), isAbsolute2 = code === 47, start;
|
|
460
|
+
if (isAbsolute2)
|
|
461
|
+
ret.root = "/", start = 1;
|
|
462
|
+
else
|
|
463
|
+
start = 0;
|
|
464
|
+
var startDot = -1, startPart = 0, end = -1, matchedSlash = true, i = path.length - 1, preDotState = 0;
|
|
465
|
+
for (;i >= start; --i) {
|
|
466
|
+
if (code = path.charCodeAt(i), code === 47) {
|
|
467
|
+
if (!matchedSlash) {
|
|
468
|
+
startPart = i + 1;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
if (end === -1)
|
|
474
|
+
matchedSlash = false, end = i + 1;
|
|
475
|
+
if (code === 46) {
|
|
476
|
+
if (startDot === -1)
|
|
477
|
+
startDot = i;
|
|
478
|
+
else if (preDotState !== 1)
|
|
479
|
+
preDotState = 1;
|
|
480
|
+
} else if (startDot !== -1)
|
|
481
|
+
preDotState = -1;
|
|
482
|
+
}
|
|
483
|
+
if (startDot === -1 || end === -1 || preDotState === 0 || preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
|
|
484
|
+
if (end !== -1)
|
|
485
|
+
if (startPart === 0 && isAbsolute2)
|
|
486
|
+
ret.base = ret.name = path.slice(1, end);
|
|
487
|
+
else
|
|
488
|
+
ret.base = ret.name = path.slice(startPart, end);
|
|
489
|
+
} else {
|
|
490
|
+
if (startPart === 0 && isAbsolute2)
|
|
491
|
+
ret.name = path.slice(1, startDot), ret.base = path.slice(1, end);
|
|
492
|
+
else
|
|
493
|
+
ret.name = path.slice(startPart, startDot), ret.base = path.slice(startPart, end);
|
|
494
|
+
ret.ext = path.slice(startDot, end);
|
|
495
|
+
}
|
|
496
|
+
if (startPart > 0)
|
|
497
|
+
ret.dir = path.slice(0, startPart - 1);
|
|
498
|
+
else if (isAbsolute2)
|
|
499
|
+
ret.dir = "/";
|
|
500
|
+
return ret;
|
|
501
|
+
}
|
|
502
|
+
var sep = "/";
|
|
503
|
+
var delimiter = ":";
|
|
504
|
+
var posix = ((p) => (p.posix = p, p))({ resolve, normalize, isAbsolute, join, relative, _makeLong, dirname, basename, extname, format, parse, sep, delimiter, win32: null, posix: null });
|
|
505
|
+
|
|
506
|
+
// src/lib/skills.ts
|
|
507
|
+
async function detectSkillsState(args) {
|
|
508
|
+
const exists = args.exists ?? existsSync;
|
|
509
|
+
if (args.explicitPath) {
|
|
510
|
+
return { mode: "external", path: args.explicitPath };
|
|
511
|
+
}
|
|
512
|
+
const bundledPath = join(args.homeDir, ".opencode", "skills", args.syncDirName);
|
|
513
|
+
if (args.mode === "bundled") {
|
|
514
|
+
return { mode: "bundled", path: bundledPath };
|
|
515
|
+
}
|
|
516
|
+
const externalDefault = join(args.homeDir, ".opencode", "skills", "obsidian-skills");
|
|
517
|
+
return {
|
|
518
|
+
mode: args.mode,
|
|
519
|
+
path: exists(externalDefault) ? externalDefault : bundledPath
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/lib/wiki.ts
|
|
524
|
+
function resolveWikiPaths(args) {
|
|
525
|
+
const raw = args.rawDir ?? "raw";
|
|
526
|
+
const wiki = args.wikiDir ?? "wiki";
|
|
527
|
+
const schema = args.schemaDir ?? "schema";
|
|
528
|
+
return {
|
|
529
|
+
vault: args.vault,
|
|
530
|
+
raw,
|
|
531
|
+
wiki,
|
|
532
|
+
schema,
|
|
533
|
+
answers: `${wiki}/answers`,
|
|
534
|
+
index: `${wiki}/INDEX.md`
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function buildIndexMarkdown(pages) {
|
|
538
|
+
const sorted = [...pages].sort();
|
|
539
|
+
const now = new Date().toISOString();
|
|
540
|
+
const links = sorted.map((p) => {
|
|
541
|
+
const name = p.replace(/^wiki\//, "").replace(/\.md$/, "");
|
|
542
|
+
return `- [[${name}]]`;
|
|
543
|
+
}).join(`
|
|
544
|
+
`);
|
|
545
|
+
return `# Wiki Index
|
|
546
|
+
|
|
547
|
+
> generated: ${now}
|
|
548
|
+
|
|
549
|
+
${links}
|
|
550
|
+
`;
|
|
551
|
+
}
|
|
552
|
+
function buildLogEntry(operation, args) {
|
|
553
|
+
const now = new Date().toISOString().replace("T", " ").slice(0, 16);
|
|
554
|
+
const lines = [`
|
|
555
|
+
## ${now} — ${operation}`];
|
|
556
|
+
if (args.source)
|
|
557
|
+
lines.push(`- Source: ${args.source}`);
|
|
558
|
+
if (args.files?.length)
|
|
559
|
+
lines.push(`- Files touched: ${args.files.map((f) => `[[${f}]]`).join(", ")}`);
|
|
560
|
+
if (args.notes)
|
|
561
|
+
lines.push(`- Notes: ${args.notes}`);
|
|
562
|
+
return lines.join(`
|
|
563
|
+
`) + `
|
|
564
|
+
`;
|
|
565
|
+
}
|
|
566
|
+
function detectBrokenLinks(pages, links) {
|
|
567
|
+
const pageNames = new Set(pages.map((p) => p.replace(/^wiki\//, "").replace(/\.md$/, "")));
|
|
568
|
+
const broken = [];
|
|
569
|
+
for (const [source, sourceLinks] of Object.entries(links)) {
|
|
570
|
+
for (const link of sourceLinks) {
|
|
571
|
+
const name = link.replace(/^\[\[/, "").replace(/\]\]$/, "").split("|")[0].trim();
|
|
572
|
+
if (!pageNames.has(name)) {
|
|
573
|
+
broken.push({ source, link });
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return broken;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// node:os
|
|
581
|
+
var homedir = function() {
|
|
582
|
+
return "/";
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
// src/lib/cli.ts
|
|
586
|
+
function buildObsidianArgs(command, args) {
|
|
587
|
+
if (args.file && args.path)
|
|
588
|
+
throw new Error("MUTUALLY_EXCLUSIVE_TARGET");
|
|
589
|
+
const flags = Object.entries(args).filter(([, value]) => value !== undefined && value !== null).flatMap(([key, value]) => {
|
|
590
|
+
if (typeof value === "boolean")
|
|
591
|
+
return value ? [key] : [];
|
|
592
|
+
return [`${key}=${String(value)}`];
|
|
593
|
+
});
|
|
594
|
+
return [command, ...flags];
|
|
595
|
+
}
|
|
596
|
+
function errorResult(code, message, hint, command = "", args = {}, requiredCapabilities = [], checkedCapabilities = []) {
|
|
597
|
+
return {
|
|
598
|
+
schemaVersion: "1.0",
|
|
599
|
+
ok: false,
|
|
600
|
+
command,
|
|
601
|
+
args,
|
|
602
|
+
requiredCapabilities,
|
|
603
|
+
checkedCapabilities,
|
|
604
|
+
data: null,
|
|
605
|
+
stdout: "",
|
|
606
|
+
stderr: "",
|
|
607
|
+
exitCode: 1,
|
|
608
|
+
hint,
|
|
609
|
+
error: { code, kind: "capability", message }
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
async function executeObsidianCli(shell, command, args, meta) {
|
|
613
|
+
const built = buildObsidianArgs(command, args);
|
|
614
|
+
const result = await shell(["obsidian", ...built]);
|
|
615
|
+
const mappedError = result.exitCode === 0 ? null : meta.mapError?.({ exitCode: result.exitCode, stderr: result.stderr }) ?? (result.exitCode === 127 ? { code: "CLI_NOT_FOUND", kind: "capability", message: "obsidian CLI is not installed or not on PATH" } : { code: "COMMAND_NOT_ENABLED", kind: "runtime", message: result.stderr || "CLI command failed" });
|
|
616
|
+
return {
|
|
617
|
+
schemaVersion: "1.0",
|
|
618
|
+
ok: result.exitCode === 0,
|
|
619
|
+
command: `obsidian_${command.replace(":", "_")}`,
|
|
620
|
+
args,
|
|
621
|
+
requiredCapabilities: meta.requiredCapabilities,
|
|
622
|
+
checkedCapabilities: meta.checkedCapabilities,
|
|
623
|
+
data: null,
|
|
624
|
+
stdout: result.stdout,
|
|
625
|
+
stderr: result.stderr,
|
|
626
|
+
exitCode: result.exitCode,
|
|
627
|
+
hint: result.exitCode === 0 ? null : "Ask OpenCode to use obsidian_env_doctor",
|
|
628
|
+
error: mappedError
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/lib/tool-inputs.ts
|
|
633
|
+
function ensureSingleTarget(input) {
|
|
634
|
+
if (!input.file && !input.path)
|
|
635
|
+
throw new Error("FILE_OR_PATH_REQUIRED");
|
|
636
|
+
if (input.file && input.path)
|
|
637
|
+
throw new Error("MUTUALLY_EXCLUSIVE_TARGET");
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/tools/read.ts
|
|
641
|
+
async function runReadTool(args) {
|
|
642
|
+
try {
|
|
643
|
+
ensureSingleTarget(args.input);
|
|
644
|
+
} catch (err) {
|
|
645
|
+
const code = err.message;
|
|
646
|
+
return errorResult(code, err.message, "Provide exactly one of file or path", "obsidian_read", args.input, ["cli", "app"], ["cli", "app"]);
|
|
647
|
+
}
|
|
648
|
+
const result = await executeObsidianCli(args.shell, "read", args.input, {
|
|
649
|
+
requiredCapabilities: ["cli", "app"],
|
|
650
|
+
checkedCapabilities: ["cli", "app", "vault"]
|
|
651
|
+
});
|
|
652
|
+
return {
|
|
653
|
+
...result,
|
|
654
|
+
data: {
|
|
655
|
+
target: {
|
|
656
|
+
file: args.input.file ?? null,
|
|
657
|
+
path: args.input.path ?? null,
|
|
658
|
+
vault: args.input.vault ?? null
|
|
659
|
+
},
|
|
660
|
+
content: result.stdout
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/tools/search.ts
|
|
666
|
+
async function runSearchTool(args) {
|
|
667
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
668
|
+
const vault = resolveVault({
|
|
669
|
+
action: "read",
|
|
670
|
+
inputVault: args.input.vault ?? null,
|
|
671
|
+
activeVault: args.activeVault,
|
|
672
|
+
config
|
|
673
|
+
});
|
|
674
|
+
const cliArgs = { ...args.input, ...vault ? { vault } : {} };
|
|
675
|
+
const result = await executeObsidianCli(args.shell, "search", cliArgs, {
|
|
676
|
+
requiredCapabilities: ["cli", "app"],
|
|
677
|
+
checkedCapabilities: ["cli", "app", "vault"]
|
|
678
|
+
});
|
|
679
|
+
return {
|
|
680
|
+
...result,
|
|
681
|
+
data: result.ok ? { query: args.input.query, results: result.stdout } : null
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// src/tools/create-note.ts
|
|
686
|
+
async function runCreateNoteTool(args) {
|
|
687
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
688
|
+
const vault = resolveVault({
|
|
689
|
+
action: "write",
|
|
690
|
+
inputVault: args.input.vault ?? null,
|
|
691
|
+
activeVault: args.activeVault,
|
|
692
|
+
config
|
|
693
|
+
});
|
|
694
|
+
if (!vault) {
|
|
695
|
+
return errorResult("VAULT_REQUIRED", "Write commands require a vault", "Pass vault or configure defaultVault", "obsidian_create_note", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
696
|
+
}
|
|
697
|
+
const result = await executeObsidianCli(args.shell, "create", { ...args.input, vault }, {
|
|
698
|
+
requiredCapabilities: ["cli", "app", "vault"],
|
|
699
|
+
checkedCapabilities: ["cli", "app", "vault"]
|
|
700
|
+
});
|
|
701
|
+
return {
|
|
702
|
+
...result,
|
|
703
|
+
data: result.ok ? { name: args.input.name, created: true, opened: !args.input.silent } : null
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/tools/append-note.ts
|
|
708
|
+
async function runAppendNoteTool(args) {
|
|
709
|
+
try {
|
|
710
|
+
ensureSingleTarget(args.input);
|
|
711
|
+
} catch (err) {
|
|
712
|
+
const code = err.message;
|
|
713
|
+
return errorResult(code, err.message, "Provide exactly one of file or path", "obsidian_append_note", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
714
|
+
}
|
|
715
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
716
|
+
const vault = resolveVault({
|
|
717
|
+
action: "write",
|
|
718
|
+
inputVault: args.input.vault ?? null,
|
|
719
|
+
activeVault: args.activeVault,
|
|
720
|
+
config
|
|
721
|
+
});
|
|
722
|
+
if (!vault) {
|
|
723
|
+
return errorResult("VAULT_REQUIRED", "Write commands require a vault", "Pass vault or configure defaultVault", "obsidian_append_note", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
724
|
+
}
|
|
725
|
+
const result = await executeObsidianCli(args.shell, "append", { ...args.input, vault }, {
|
|
726
|
+
requiredCapabilities: ["cli", "app", "vault"],
|
|
727
|
+
checkedCapabilities: ["cli", "app", "vault"]
|
|
728
|
+
});
|
|
729
|
+
return {
|
|
730
|
+
...result,
|
|
731
|
+
data: result.ok ? { target: { file: args.input.file ?? null, path: args.input.path ?? null }, appended: true } : null
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// src/tools/set-property.ts
|
|
736
|
+
async function runSetPropertyTool(args) {
|
|
737
|
+
try {
|
|
738
|
+
ensureSingleTarget(args.input);
|
|
739
|
+
} catch (err) {
|
|
740
|
+
const code = err.message;
|
|
741
|
+
return errorResult(code, err.message, "Provide exactly one of file or path", "obsidian_set_property", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
742
|
+
}
|
|
743
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
744
|
+
const vault = resolveVault({
|
|
745
|
+
action: "write",
|
|
746
|
+
inputVault: args.input.vault ?? null,
|
|
747
|
+
activeVault: args.activeVault,
|
|
748
|
+
config
|
|
749
|
+
});
|
|
750
|
+
if (!vault) {
|
|
751
|
+
return errorResult("VAULT_REQUIRED", "Write commands require a vault", "Pass vault or configure defaultVault", "obsidian_set_property", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
752
|
+
}
|
|
753
|
+
const result = await executeObsidianCli(args.shell, "property:set", { ...args.input, vault }, {
|
|
754
|
+
requiredCapabilities: ["cli", "app", "vault"],
|
|
755
|
+
checkedCapabilities: ["cli", "app", "vault"]
|
|
756
|
+
});
|
|
757
|
+
return {
|
|
758
|
+
...result,
|
|
759
|
+
data: result.ok ? { property: args.input.name, value: args.input.value, updated: true } : null
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/tools/skills-check.ts
|
|
764
|
+
var {existsSync: existsSync2} = (() => ({}));
|
|
765
|
+
async function runSkillsCheck(args) {
|
|
766
|
+
const exists = args.exists ?? existsSync2;
|
|
767
|
+
const found = [];
|
|
768
|
+
const missing = [];
|
|
769
|
+
for (const skill of args.requiredSkills) {
|
|
770
|
+
if (exists(join(args.skillsPath, skill, "SKILL.md"))) {
|
|
771
|
+
found.push(skill);
|
|
772
|
+
} else {
|
|
773
|
+
missing.push(skill);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const ok = missing.length === 0;
|
|
777
|
+
return {
|
|
778
|
+
schemaVersion: "1.0",
|
|
779
|
+
ok,
|
|
780
|
+
command: "obsidian_skills_check",
|
|
781
|
+
args: { mode: args.mode },
|
|
782
|
+
requiredCapabilities: [],
|
|
783
|
+
checkedCapabilities: ["skills"],
|
|
784
|
+
data: {
|
|
785
|
+
mode: args.mode,
|
|
786
|
+
skillsPath: args.skillsPath,
|
|
787
|
+
found,
|
|
788
|
+
missing
|
|
789
|
+
},
|
|
790
|
+
stdout: "",
|
|
791
|
+
stderr: "",
|
|
792
|
+
exitCode: ok ? 0 : 1,
|
|
793
|
+
hint: ok ? null : "Run obsidian_env_doctor or bun run sync:skills to fix missing skills",
|
|
794
|
+
error: ok ? null : {
|
|
795
|
+
code: "BUNDLED_SKILLS_OUT_OF_SYNC",
|
|
796
|
+
kind: "capability",
|
|
797
|
+
message: `Missing skills: ${missing.join(", ")}`
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/tools/env-doctor.ts
|
|
803
|
+
async function runEnvDoctor(args) {
|
|
804
|
+
const [cliInstalled, appRunning, skills] = await Promise.all([
|
|
805
|
+
args.detectCli(),
|
|
806
|
+
args.detectApp(),
|
|
807
|
+
args.detectSkills()
|
|
808
|
+
]);
|
|
809
|
+
return {
|
|
810
|
+
schemaVersion: "1.0",
|
|
811
|
+
ok: true,
|
|
812
|
+
command: "obsidian_env_doctor",
|
|
813
|
+
args: {},
|
|
814
|
+
requiredCapabilities: [],
|
|
815
|
+
checkedCapabilities: ["cli", "app", "skills"],
|
|
816
|
+
data: {
|
|
817
|
+
cliInstalled,
|
|
818
|
+
appRunning,
|
|
819
|
+
defaultVault: args.defaultVault,
|
|
820
|
+
skills
|
|
821
|
+
},
|
|
822
|
+
stdout: "",
|
|
823
|
+
stderr: "",
|
|
824
|
+
exitCode: 0,
|
|
825
|
+
hint: null,
|
|
826
|
+
error: null
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/tools/backlinks.ts
|
|
831
|
+
async function runBacklinksTool(args) {
|
|
832
|
+
if (!args.input.path) {
|
|
833
|
+
return errorResult("INVALID_ARGS", "path is required", "Provide the note path", "obsidian_backlinks", args.input, ["cli", "app"], ["cli", "app"]);
|
|
834
|
+
}
|
|
835
|
+
const result = await executeObsidianCli(args.shell, "backlinks", args.input, {
|
|
836
|
+
requiredCapabilities: ["cli", "app"],
|
|
837
|
+
checkedCapabilities: ["cli", "app"]
|
|
838
|
+
});
|
|
839
|
+
return {
|
|
840
|
+
...result,
|
|
841
|
+
data: result.ok ? { path: args.input.path, backlinks: result.stdout.split(`
|
|
842
|
+
`).map((l) => l.trim()).filter(Boolean) } : null
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/tools/tags.ts
|
|
847
|
+
async function runTagsTool(args) {
|
|
848
|
+
const result = await executeObsidianCli(args.shell, "tags", args.input, {
|
|
849
|
+
requiredCapabilities: ["cli", "app"],
|
|
850
|
+
checkedCapabilities: ["cli", "app"]
|
|
851
|
+
});
|
|
852
|
+
return {
|
|
853
|
+
...result,
|
|
854
|
+
data: result.ok ? { tags: result.stdout.split(`
|
|
855
|
+
`).map((l) => l.trim()).filter(Boolean) } : null
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// src/tools/tag-notes.ts
|
|
860
|
+
async function runTagNotesTool(args) {
|
|
861
|
+
if (!args.input.name) {
|
|
862
|
+
return errorResult("INVALID_ARGS", "name is required", "Provide a tag name", "obsidian_tag_notes", args.input, ["cli", "app"], ["cli", "app"]);
|
|
863
|
+
}
|
|
864
|
+
const result = await executeObsidianCli(args.shell, "tag", { ...args.input, verbose: true }, {
|
|
865
|
+
requiredCapabilities: ["cli", "app"],
|
|
866
|
+
checkedCapabilities: ["cli", "app"]
|
|
867
|
+
});
|
|
868
|
+
return {
|
|
869
|
+
...result,
|
|
870
|
+
data: result.ok ? { tag: args.input.name, notes: result.stdout.split(`
|
|
871
|
+
`).map((l) => l.trim()).filter(Boolean) } : null
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/tools/plugins.ts
|
|
876
|
+
async function runPluginsTool(args) {
|
|
877
|
+
const result = await executeObsidianCli(args.shell, "plugins", args.input, {
|
|
878
|
+
requiredCapabilities: ["cli", "app"],
|
|
879
|
+
checkedCapabilities: ["cli", "app"]
|
|
880
|
+
});
|
|
881
|
+
return {
|
|
882
|
+
...result,
|
|
883
|
+
data: result.ok ? { plugins: result.stdout.split(`
|
|
884
|
+
`).map((l) => l.trim()).filter(Boolean) } : null
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// src/tools/plugin-reload.ts
|
|
889
|
+
async function runPluginReloadTool(args) {
|
|
890
|
+
if (!args.input.id) {
|
|
891
|
+
return errorResult("INVALID_ARGS", "id is required", "Provide a plugin id", "obsidian_plugin_reload", args.input, ["cli", "app"], ["cli", "app"]);
|
|
892
|
+
}
|
|
893
|
+
const result = await executeObsidianCli(args.shell, "plugin:reload", args.input, {
|
|
894
|
+
requiredCapabilities: ["cli", "app"],
|
|
895
|
+
checkedCapabilities: ["cli", "app"]
|
|
896
|
+
});
|
|
897
|
+
return {
|
|
898
|
+
...result,
|
|
899
|
+
data: result.ok ? { id: args.input.id, reloaded: true } : null
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/tools/dev-errors.ts
|
|
904
|
+
async function runDevErrorsTool(args) {
|
|
905
|
+
const result = await executeObsidianCli(args.shell, "dev:errors", args.input, {
|
|
906
|
+
requiredCapabilities: ["cli", "app"],
|
|
907
|
+
checkedCapabilities: ["cli", "app"]
|
|
908
|
+
});
|
|
909
|
+
return {
|
|
910
|
+
...result,
|
|
911
|
+
data: result.ok ? { errors: result.stdout.split(`
|
|
912
|
+
`).map((l) => l.trim()).filter(Boolean) } : null
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/tools/dev-console.ts
|
|
917
|
+
async function runDevConsoleTool(args) {
|
|
918
|
+
const { action, limit } = args.input;
|
|
919
|
+
if (action !== "start" && action !== "stop" && action !== "get") {
|
|
920
|
+
return errorResult("INVALID_ARGS", "action must be start, stop, or get", "Use start, stop, or get", "obsidian_dev_console", args.input, ["cli", "app"], ["cli", "app"]);
|
|
921
|
+
}
|
|
922
|
+
if (action === "get") {
|
|
923
|
+
const cliArgs = limit !== undefined ? { limit } : {};
|
|
924
|
+
const result2 = await executeObsidianCli(args.shell, "dev:console", cliArgs, {
|
|
925
|
+
requiredCapabilities: ["cli", "app"],
|
|
926
|
+
checkedCapabilities: ["cli", "app"]
|
|
927
|
+
});
|
|
928
|
+
return { ...result2, data: result2.ok ? { action: "get", output: result2.stdout } : null };
|
|
929
|
+
}
|
|
930
|
+
const onOff = action === "start" ? "on" : "off";
|
|
931
|
+
const shell = args.shell;
|
|
932
|
+
const result = await executeObsidianCli(async (cmd) => shell([...cmd, onOff]), "dev:debug", {}, { requiredCapabilities: ["cli", "app"], checkedCapabilities: ["cli", "app"] });
|
|
933
|
+
return { ...result, data: result.ok ? { action } : null };
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/tools/dev-screenshot.ts
|
|
937
|
+
async function runDevScreenshotTool(args) {
|
|
938
|
+
if (!args.input.path) {
|
|
939
|
+
return errorResult("INVALID_ARGS", "path is required", "Provide output path inside the vault", "obsidian_dev_screenshot", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
940
|
+
}
|
|
941
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
942
|
+
const vault = resolveVault({ action: "write", inputVault: args.input.vault ?? null, activeVault: args.activeVault, config });
|
|
943
|
+
if (!vault) {
|
|
944
|
+
return errorResult("VAULT_REQUIRED", "Write commands require a vault", "Pass vault or configure defaultVault", "obsidian_dev_screenshot", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
945
|
+
}
|
|
946
|
+
const result = await executeObsidianCli(args.shell, "dev:screenshot", { path: args.input.path, vault }, {
|
|
947
|
+
requiredCapabilities: ["cli", "app", "vault"],
|
|
948
|
+
checkedCapabilities: ["cli", "app", "vault"]
|
|
949
|
+
});
|
|
950
|
+
return { ...result, data: result.ok ? { path: args.input.path, vault } : null };
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/tools/dev-dom.ts
|
|
954
|
+
async function runDevDomTool(args) {
|
|
955
|
+
if (!args.input.selector) {
|
|
956
|
+
return errorResult("INVALID_ARGS", "selector is required", "Provide a CSS selector", "obsidian_dev_dom", args.input, ["cli", "app"], ["cli", "app"]);
|
|
957
|
+
}
|
|
958
|
+
const { selector, mode, attr, css } = args.input;
|
|
959
|
+
const cliArgs = { selector };
|
|
960
|
+
if (mode && ["text", "all", "total"].includes(mode))
|
|
961
|
+
cliArgs[mode] = true;
|
|
962
|
+
if (attr)
|
|
963
|
+
cliArgs["attr"] = attr;
|
|
964
|
+
if (css)
|
|
965
|
+
cliArgs["css"] = css;
|
|
966
|
+
const result = await executeObsidianCli(args.shell, "dev:dom", cliArgs, {
|
|
967
|
+
requiredCapabilities: ["cli", "app"],
|
|
968
|
+
checkedCapabilities: ["cli", "app"]
|
|
969
|
+
});
|
|
970
|
+
return { ...result, data: result.ok ? { selector, output: result.stdout } : null };
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// src/tools/dev-css.ts
|
|
974
|
+
async function runDevCssTool(args) {
|
|
975
|
+
if (!args.input.selector) {
|
|
976
|
+
return errorResult("INVALID_ARGS", "selector is required", "Provide a CSS selector", "obsidian_dev_css", args.input, ["cli", "app"], ["cli", "app"]);
|
|
977
|
+
}
|
|
978
|
+
const result = await executeObsidianCli(args.shell, "dev:css", args.input, {
|
|
979
|
+
requiredCapabilities: ["cli", "app"],
|
|
980
|
+
checkedCapabilities: ["cli", "app"]
|
|
981
|
+
});
|
|
982
|
+
return { ...result, data: result.ok ? { selector: args.input.selector, prop: args.input.prop, output: result.stdout } : null };
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// src/lib/vault.ts
|
|
986
|
+
async function detectActiveVault(shell) {
|
|
987
|
+
const result = await shell(["obsidian", "vault"]);
|
|
988
|
+
if (result.exitCode !== 0 || !result.stdout.trim())
|
|
989
|
+
return null;
|
|
990
|
+
const lines = result.stdout.trim().split(`
|
|
991
|
+
`);
|
|
992
|
+
const name = lines.find((l) => l.startsWith("name\t"))?.replace("name\t", "").trim();
|
|
993
|
+
const path = lines.find((l) => l.startsWith("path\t"))?.replace("path\t", "").trim();
|
|
994
|
+
if (!name)
|
|
995
|
+
return null;
|
|
996
|
+
return { name, path: path ?? "" };
|
|
997
|
+
}
|
|
998
|
+
async function listVaults(shell) {
|
|
999
|
+
const result = await shell(["obsidian", "vaults"]);
|
|
1000
|
+
if (result.exitCode !== 0 || !result.stdout.trim())
|
|
1001
|
+
return [];
|
|
1002
|
+
return result.stdout.trim().split(`
|
|
1003
|
+
`).flatMap((line) => {
|
|
1004
|
+
const parts = line.split("\t");
|
|
1005
|
+
const name = parts[0]?.trim();
|
|
1006
|
+
const path = parts[1]?.trim();
|
|
1007
|
+
return name ? [{ name, path: path ?? "" }] : [];
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/tools/wiki-init.ts
|
|
1012
|
+
var SCHEMA_MD = `# Wiki Schema
|
|
1013
|
+
|
|
1014
|
+
This wiki follows the LLM Wiki pattern: a persistent, compounding knowledge base
|
|
1015
|
+
maintained by an LLM agent across three layers.
|
|
1016
|
+
|
|
1017
|
+
## Folder structure
|
|
1018
|
+
|
|
1019
|
+
\`\`\`
|
|
1020
|
+
raw/ — Immutable source documents (articles, papers, notes)
|
|
1021
|
+
wiki/ — Synthesized knowledge pages (one topic per file)
|
|
1022
|
+
wiki/answers/ — Filed answers to past queries
|
|
1023
|
+
schema/ — This file and other structural conventions
|
|
1024
|
+
\`\`\`
|
|
1025
|
+
|
|
1026
|
+
## Special files
|
|
1027
|
+
|
|
1028
|
+
- \`wiki/INDEX.md\` — Content catalog, auto-generated by obsidian_wiki_refresh_index
|
|
1029
|
+
- \`wiki/LOG.md\` — Append-only record of all ingests and queries
|
|
1030
|
+
|
|
1031
|
+
## Wiki page conventions
|
|
1032
|
+
|
|
1033
|
+
- One topic per file, named clearly (e.g. \`Transformers.md\`, \`OpenAI.md\`)
|
|
1034
|
+
- Start with a one-paragraph summary
|
|
1035
|
+
- Use \`## Sections\` for subtopics
|
|
1036
|
+
- Cross-reference with \`[[Page Name]]\` wikilinks
|
|
1037
|
+
- Cite sources with \`> Source: [[raw/filename]]\`
|
|
1038
|
+
- Prefer updating existing pages over creating new ones
|
|
1039
|
+
|
|
1040
|
+
## Operations
|
|
1041
|
+
|
|
1042
|
+
**Ingest** (obsidian_wiki_ingest)
|
|
1043
|
+
1. Save raw source to \`raw/\`
|
|
1044
|
+
2. Read existing related wiki pages
|
|
1045
|
+
3. Update or create wiki pages — typically 2-10 files per ingest
|
|
1046
|
+
4. Append entry to \`wiki/LOG.md\`
|
|
1047
|
+
|
|
1048
|
+
**Query** (obsidian_wiki_search_cited + obsidian_wiki_save_answer)
|
|
1049
|
+
1. Search wiki for relevant pages
|
|
1050
|
+
2. Synthesize answer citing wiki sources
|
|
1051
|
+
3. File the answer to \`wiki/answers/\` if it adds new knowledge
|
|
1052
|
+
|
|
1053
|
+
**Lint** (obsidian_wiki_lint + obsidian_wiki_refresh_index)
|
|
1054
|
+
1. Find broken links, orphan pages, index gaps
|
|
1055
|
+
2. Refresh INDEX.md
|
|
1056
|
+
3. Fix issues found
|
|
1057
|
+
|
|
1058
|
+
## Log format
|
|
1059
|
+
|
|
1060
|
+
Each LOG.md entry:
|
|
1061
|
+
\`\`\`
|
|
1062
|
+
## YYYY-MM-DD HH:MM — <operation>
|
|
1063
|
+
- Source: <name or query>
|
|
1064
|
+
- Files touched: [[page1]], [[page2]]
|
|
1065
|
+
- Notes: <optional>
|
|
1066
|
+
\`\`\`
|
|
1067
|
+
`;
|
|
1068
|
+
function logEntry(operation, vault) {
|
|
1069
|
+
const now = new Date().toISOString().replace("T", " ").slice(0, 16);
|
|
1070
|
+
return `
|
|
1071
|
+
## ${now} — ${operation}
|
|
1072
|
+
- Vault: ${vault}
|
|
1073
|
+
- Notes: Wiki initialized
|
|
1074
|
+
`;
|
|
1075
|
+
}
|
|
1076
|
+
async function runWikiInitTool(args) {
|
|
1077
|
+
let vault = null;
|
|
1078
|
+
let vaultPath = "";
|
|
1079
|
+
let detectedFrom = "explicit";
|
|
1080
|
+
if (args.input.vault) {
|
|
1081
|
+
vault = args.input.vault;
|
|
1082
|
+
detectedFrom = "explicit";
|
|
1083
|
+
} else if (args.defaultVault) {
|
|
1084
|
+
vault = args.defaultVault;
|
|
1085
|
+
detectedFrom = "config";
|
|
1086
|
+
} else {
|
|
1087
|
+
const active = await detectActiveVault(args.shell);
|
|
1088
|
+
if (active) {
|
|
1089
|
+
vault = active.name;
|
|
1090
|
+
vaultPath = active.path;
|
|
1091
|
+
detectedFrom = "active";
|
|
1092
|
+
} else {
|
|
1093
|
+
const all = await listVaults(args.shell);
|
|
1094
|
+
if (all.length === 1) {
|
|
1095
|
+
vault = all[0].name;
|
|
1096
|
+
vaultPath = all[0].path;
|
|
1097
|
+
detectedFrom = "only-vault";
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (!vault) {
|
|
1102
|
+
return errorResult("VAULT_REQUIRED", "Could not detect a vault automatically. Please provide vault name.", "Pass vault='My Vault' or set defaultVault in plugin config", "obsidian_wiki_init", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
1103
|
+
}
|
|
1104
|
+
const force = args.input.force ?? false;
|
|
1105
|
+
const created = [];
|
|
1106
|
+
const skipped = [];
|
|
1107
|
+
const files = [
|
|
1108
|
+
{
|
|
1109
|
+
name: `${args.wikiPaths.schema}/SCHEMA.md`,
|
|
1110
|
+
content: SCHEMA_MD,
|
|
1111
|
+
overwrite: force
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
name: args.wikiPaths.index,
|
|
1115
|
+
content: `# Wiki Index
|
|
1116
|
+
|
|
1117
|
+
> Run obsidian_wiki_refresh_index to populate this file.
|
|
1118
|
+
`,
|
|
1119
|
+
overwrite: force
|
|
1120
|
+
},
|
|
1121
|
+
{
|
|
1122
|
+
name: `${args.wikiPaths.wiki}/LOG.md`,
|
|
1123
|
+
content: `# Wiki Log
|
|
1124
|
+
|
|
1125
|
+
Append-only record of all ingests, queries, and maintenance.
|
|
1126
|
+
${logEntry("init", vault)}`,
|
|
1127
|
+
overwrite: false
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
name: `${args.wikiPaths.raw}/.keep`,
|
|
1131
|
+
content: `# Raw sources folder — do not edit files here manually
|
|
1132
|
+
`,
|
|
1133
|
+
overwrite: false
|
|
1134
|
+
}
|
|
1135
|
+
];
|
|
1136
|
+
for (const file of files) {
|
|
1137
|
+
const result = await executeObsidianCli(args.shell, "create", { name: file.name, content: file.content, vault, overwrite: file.overwrite }, { requiredCapabilities: ["cli", "app", "vault"], checkedCapabilities: ["cli", "app", "vault"] });
|
|
1138
|
+
if (result.ok) {
|
|
1139
|
+
created.push(file.name);
|
|
1140
|
+
} else if (result.exitCode !== 0 && !file.overwrite) {
|
|
1141
|
+
skipped.push(file.name);
|
|
1142
|
+
} else {
|
|
1143
|
+
return { ...result, data: null };
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
schemaVersion: "1.0",
|
|
1148
|
+
ok: true,
|
|
1149
|
+
command: "obsidian_wiki_init",
|
|
1150
|
+
args: args.input,
|
|
1151
|
+
requiredCapabilities: ["cli", "app", "vault"],
|
|
1152
|
+
checkedCapabilities: ["cli", "app", "vault"],
|
|
1153
|
+
data: { vault, vaultPath, created, skipped, detectedFrom },
|
|
1154
|
+
stdout: "",
|
|
1155
|
+
stderr: "",
|
|
1156
|
+
exitCode: 0,
|
|
1157
|
+
hint: skipped.length > 0 ? "Some files already existed and were skipped. Use force:true to overwrite." : null,
|
|
1158
|
+
error: null
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// src/tools/wiki-ingest.ts
|
|
1163
|
+
async function runWikiIngestTool(args) {
|
|
1164
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
1165
|
+
const vault = resolveVault({ action: "write", inputVault: args.input.vault ?? null, activeVault: args.activeVault, config });
|
|
1166
|
+
if (!vault) {
|
|
1167
|
+
return errorResult("VAULT_REQUIRED", "Write commands require a vault", "Pass vault or configure defaultVault", "obsidian_wiki_ingest", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
1168
|
+
}
|
|
1169
|
+
const rawName = `${args.wikiPaths.raw}/${args.input.sourceName}`;
|
|
1170
|
+
const wikiName = `${args.wikiPaths.wiki}/${args.input.sourceName}`;
|
|
1171
|
+
const rawResult = await executeObsidianCli(args.shell, "create", { name: rawName, content: args.input.sourceContent, vault, overwrite: false }, { requiredCapabilities: ["cli", "app", "vault"], checkedCapabilities: ["cli", "app", "vault"] });
|
|
1172
|
+
if (!rawResult.ok)
|
|
1173
|
+
return { ...rawResult, data: null };
|
|
1174
|
+
const wikiResult = await executeObsidianCli(args.shell, "create", { name: wikiName, content: args.input.wikiContent, vault, overwrite: true }, { requiredCapabilities: ["cli", "app", "vault"], checkedCapabilities: ["cli", "app", "vault"] });
|
|
1175
|
+
if (!wikiResult.ok)
|
|
1176
|
+
return { ...wikiResult, data: null };
|
|
1177
|
+
const logContent = buildLogEntry("ingest", {
|
|
1178
|
+
source: args.input.sourceName,
|
|
1179
|
+
files: [rawName, wikiName]
|
|
1180
|
+
});
|
|
1181
|
+
await executeObsidianCli(args.shell, "append", { path: `${args.wikiPaths.wiki}/LOG.md`, content: logContent, vault }, { requiredCapabilities: ["cli", "app", "vault"], checkedCapabilities: ["cli", "app", "vault"] }).catch(() => {});
|
|
1182
|
+
return { ...wikiResult, data: { rawPath: rawName, wikiPath: wikiName, vault } };
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/tools/wiki-update.ts
|
|
1186
|
+
async function runWikiUpdateTool(args) {
|
|
1187
|
+
if (!args.input.pageName) {
|
|
1188
|
+
return errorResult("INVALID_ARGS", "pageName is required", "Provide a wiki page name", "obsidian_wiki_update", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
1189
|
+
}
|
|
1190
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
1191
|
+
const vault = resolveVault({ action: "write", inputVault: args.input.vault ?? null, activeVault: args.activeVault, config });
|
|
1192
|
+
if (!vault) {
|
|
1193
|
+
return errorResult("VAULT_REQUIRED", "Write commands require a vault", "Pass vault or configure defaultVault", "obsidian_wiki_update", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
1194
|
+
}
|
|
1195
|
+
const path = `${args.wikiPaths.wiki}/${args.input.pageName}`;
|
|
1196
|
+
const result = await executeObsidianCli(args.shell, "create", { name: path, content: args.input.content, vault, overwrite: true }, { requiredCapabilities: ["cli", "app", "vault"], checkedCapabilities: ["cli", "app", "vault"] });
|
|
1197
|
+
return { ...result, data: result.ok ? { path, vault, updated: true } : null };
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/tools/wiki-refresh-index.ts
|
|
1201
|
+
async function runWikiRefreshIndexTool(args) {
|
|
1202
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
1203
|
+
const vault = resolveVault({ action: "write", inputVault: args.input.vault ?? null, activeVault: args.activeVault, config });
|
|
1204
|
+
if (!vault) {
|
|
1205
|
+
return errorResult("VAULT_REQUIRED", "Write commands require a vault", "Pass vault or configure defaultVault", "obsidian_wiki_refresh_index", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
1206
|
+
}
|
|
1207
|
+
const searchResult = await executeObsidianCli(args.shell, "files", { folder: args.wikiPaths.wiki, ext: "md", vault }, { requiredCapabilities: ["cli", "app", "vault"], checkedCapabilities: ["cli", "app", "vault"] });
|
|
1208
|
+
if (!searchResult.ok)
|
|
1209
|
+
return { ...searchResult, data: null };
|
|
1210
|
+
const pages = searchResult.stdout.split(`
|
|
1211
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
1212
|
+
const indexContent = buildIndexMarkdown(pages);
|
|
1213
|
+
const writeResult = await executeObsidianCli(args.shell, "create", { name: args.wikiPaths.index, content: indexContent, vault, overwrite: true }, { requiredCapabilities: ["cli", "app", "vault"], checkedCapabilities: ["cli", "app", "vault"] });
|
|
1214
|
+
return { ...writeResult, data: writeResult.ok ? { indexPath: args.wikiPaths.index, pageCount: pages.length, vault } : null };
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// src/tools/wiki-search-cited.ts
|
|
1218
|
+
async function runWikiSearchCitedTool(args) {
|
|
1219
|
+
if (!args.input.query) {
|
|
1220
|
+
return errorResult("INVALID_ARGS", "query is required", "Provide a search query", "obsidian_wiki_search_cited", args.input, ["cli", "app"], ["cli", "app"]);
|
|
1221
|
+
}
|
|
1222
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
1223
|
+
const vault = resolveVault({ action: "read", inputVault: args.input.vault ?? null, activeVault: args.activeVault, config });
|
|
1224
|
+
const cliArgs = { query: args.input.query, path: args.wikiPaths.wiki };
|
|
1225
|
+
if (args.input.limit)
|
|
1226
|
+
cliArgs["limit"] = args.input.limit;
|
|
1227
|
+
if (vault)
|
|
1228
|
+
cliArgs["vault"] = vault;
|
|
1229
|
+
const result = await executeObsidianCli(args.shell, "search", cliArgs, {
|
|
1230
|
+
requiredCapabilities: ["cli", "app"],
|
|
1231
|
+
checkedCapabilities: ["cli", "app"]
|
|
1232
|
+
});
|
|
1233
|
+
return { ...result, data: result.ok ? { query: args.input.query, results: result.stdout } : null };
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/tools/wiki-save-answer.ts
|
|
1237
|
+
function slugify(text) {
|
|
1238
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
1239
|
+
}
|
|
1240
|
+
async function runWikiSaveAnswerTool(args) {
|
|
1241
|
+
if (!args.input.question) {
|
|
1242
|
+
return errorResult("INVALID_ARGS", "question is required", "Provide a question", "obsidian_wiki_save_answer", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
1243
|
+
}
|
|
1244
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
1245
|
+
const vault = resolveVault({ action: "write", inputVault: args.input.vault ?? null, activeVault: args.activeVault, config });
|
|
1246
|
+
if (!vault) {
|
|
1247
|
+
return errorResult("VAULT_REQUIRED", "Write commands require a vault", "Pass vault or configure defaultVault", "obsidian_wiki_save_answer", args.input, ["cli", "app", "vault"], ["cli", "app", "vault"]);
|
|
1248
|
+
}
|
|
1249
|
+
const slug = slugify(args.input.question);
|
|
1250
|
+
const path = `${args.wikiPaths.answers}/${slug}`;
|
|
1251
|
+
const tags = args.input.tags?.length ? `
|
|
1252
|
+
tags: [${args.input.tags.join(", ")}]` : "";
|
|
1253
|
+
const content = `---
|
|
1254
|
+
question: "${args.input.question}"${tags}
|
|
1255
|
+
---
|
|
1256
|
+
|
|
1257
|
+
${args.input.answer}
|
|
1258
|
+
`;
|
|
1259
|
+
const result = await executeObsidianCli(args.shell, "create", { name: path, content, vault, overwrite: false }, { requiredCapabilities: ["cli", "app", "vault"], checkedCapabilities: ["cli", "app", "vault"] });
|
|
1260
|
+
if (!result.ok)
|
|
1261
|
+
return { ...result, data: null };
|
|
1262
|
+
const logContent = buildLogEntry("query", {
|
|
1263
|
+
source: args.input.question,
|
|
1264
|
+
files: [path]
|
|
1265
|
+
});
|
|
1266
|
+
await executeObsidianCli(args.shell, "append", { path: `${args.wikiPaths.wiki}/LOG.md`, content: logContent, vault }, { requiredCapabilities: ["cli", "app", "vault"], checkedCapabilities: ["cli", "app", "vault"] }).catch(() => {});
|
|
1267
|
+
return { ...result, data: { path, vault } };
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// src/tools/wiki-lint.ts
|
|
1271
|
+
async function runWikiLintTool(args) {
|
|
1272
|
+
const config = resolvePluginConfig({ defaultVault: args.defaultVault });
|
|
1273
|
+
const vault = resolveVault({ action: "read", inputVault: args.input.vault ?? null, activeVault: args.activeVault, config });
|
|
1274
|
+
const searchResult = await executeObsidianCli(args.shell, "files", { folder: args.wikiPaths.wiki, ext: "md", ...vault ? { vault } : {} }, { requiredCapabilities: ["cli", "app"], checkedCapabilities: ["cli", "app"] });
|
|
1275
|
+
const pages = searchResult.ok ? searchResult.stdout.split(`
|
|
1276
|
+
`).map((l) => l.trim()).filter(Boolean) : [];
|
|
1277
|
+
const links = args.readLinks ? await args.readLinks() : {};
|
|
1278
|
+
const indexContent = args.readIndex ? await args.readIndex() : "";
|
|
1279
|
+
const brokenLinks = detectBrokenLinks(pages, links);
|
|
1280
|
+
const indexedNames = new Set((indexContent.match(/\[\[([^\]]+)\]\]/g) ?? []).map((l) => l.replace(/^\[\[/, "").replace(/\]\]$/, "").split("|")[0].trim()));
|
|
1281
|
+
const orphanPages = pages.filter((p) => {
|
|
1282
|
+
const name = p.replace(/^wiki\//, "").replace(/\.md$/, "");
|
|
1283
|
+
return !indexedNames.has(name);
|
|
1284
|
+
});
|
|
1285
|
+
const pageNames = new Set(pages.map((p) => p.replace(/^wiki\//, "").replace(/\.md$/, "")));
|
|
1286
|
+
const missingIndexEntries = [...indexedNames].filter((n) => !pageNames.has(n));
|
|
1287
|
+
return {
|
|
1288
|
+
schemaVersion: "1.0",
|
|
1289
|
+
ok: true,
|
|
1290
|
+
command: "obsidian_wiki_lint",
|
|
1291
|
+
args: args.input,
|
|
1292
|
+
requiredCapabilities: ["cli", "app"],
|
|
1293
|
+
checkedCapabilities: ["cli", "app"],
|
|
1294
|
+
data: { pageCount: pages.length, brokenLinks, orphanPages, missingIndexEntries },
|
|
1295
|
+
stdout: "",
|
|
1296
|
+
stderr: "",
|
|
1297
|
+
exitCode: 0,
|
|
1298
|
+
hint: brokenLinks.length > 0 || orphanPages.length > 0 ? "Run obsidian_wiki_refresh_index to fix index issues" : null,
|
|
1299
|
+
error: null
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// src/tools/eval.ts
|
|
1304
|
+
async function runEvalTool(args) {
|
|
1305
|
+
if (!args.evalEnabled) {
|
|
1306
|
+
return errorResult("EVAL_DISABLED", "obsidian_eval is disabled", "Set evalEnabled: true in plugin options to opt in", "obsidian_eval", args.input, ["cli", "app"], []);
|
|
1307
|
+
}
|
|
1308
|
+
if (!args.input.code) {
|
|
1309
|
+
return errorResult("INVALID_ARGS", "code is required", "Provide JavaScript code to evaluate", "obsidian_eval", args.input, ["cli", "app"], ["cli", "app"]);
|
|
1310
|
+
}
|
|
1311
|
+
const result = await executeObsidianCli(args.shell, "eval", { code: args.input.code }, {
|
|
1312
|
+
requiredCapabilities: ["cli", "app"],
|
|
1313
|
+
checkedCapabilities: ["cli", "app"]
|
|
1314
|
+
});
|
|
1315
|
+
return { ...result, data: result.ok ? { result: result.stdout } : null };
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// src/index.ts
|
|
1319
|
+
var {
|
|
1320
|
+
obsidian_read,
|
|
1321
|
+
obsidian_search,
|
|
1322
|
+
obsidian_create_note,
|
|
1323
|
+
obsidian_append_note,
|
|
1324
|
+
obsidian_set_property,
|
|
1325
|
+
obsidian_skills_check,
|
|
1326
|
+
obsidian_env_doctor,
|
|
1327
|
+
obsidian_backlinks,
|
|
1328
|
+
obsidian_tags,
|
|
1329
|
+
obsidian_tag_notes,
|
|
1330
|
+
obsidian_plugins,
|
|
1331
|
+
obsidian_plugin_reload,
|
|
1332
|
+
obsidian_dev_errors,
|
|
1333
|
+
obsidian_dev_console,
|
|
1334
|
+
obsidian_dev_screenshot,
|
|
1335
|
+
obsidian_dev_dom,
|
|
1336
|
+
obsidian_dev_css,
|
|
1337
|
+
obsidian_wiki_init,
|
|
1338
|
+
obsidian_wiki_ingest,
|
|
1339
|
+
obsidian_wiki_update,
|
|
1340
|
+
obsidian_wiki_refresh_index,
|
|
1341
|
+
obsidian_wiki_search_cited,
|
|
1342
|
+
obsidian_wiki_save_answer,
|
|
1343
|
+
obsidian_wiki_lint,
|
|
1344
|
+
obsidian_eval
|
|
1345
|
+
} = TOOL_MANIFEST;
|
|
1346
|
+
var OpenGemPlugin = async (_input, options) => {
|
|
1347
|
+
const shell = makeBunSpawnShell();
|
|
1348
|
+
const config = resolvePluginConfig({
|
|
1349
|
+
defaultVault: options?.defaultVault ?? null,
|
|
1350
|
+
wiki: {
|
|
1351
|
+
rawDir: options?.wikiRawDir ?? "raw",
|
|
1352
|
+
wikiDir: options?.wikiDir ?? "wiki",
|
|
1353
|
+
schemaDir: options?.wikiSchemaDir ?? "schema"
|
|
1354
|
+
},
|
|
1355
|
+
evalEnabled: options?.evalEnabled ?? false
|
|
1356
|
+
});
|
|
1357
|
+
const wikiPaths = resolveWikiPaths({
|
|
1358
|
+
vault: config.defaultVault ?? "",
|
|
1359
|
+
rawDir: config.wiki.rawDir,
|
|
1360
|
+
wikiDir: config.wiki.wikiDir,
|
|
1361
|
+
schemaDir: config.wiki.schemaDir
|
|
1362
|
+
});
|
|
1363
|
+
return {
|
|
1364
|
+
tool: {
|
|
1365
|
+
obsidian_read: tool({
|
|
1366
|
+
description: obsidian_read.description,
|
|
1367
|
+
args: {
|
|
1368
|
+
file: tool.schema.string().optional().describe("Note title or Obsidian file name (without path)"),
|
|
1369
|
+
path: tool.schema.string().optional().describe("Exact path relative to vault root"),
|
|
1370
|
+
vault: tool.schema.string().optional().describe("Vault name (optional for read operations)")
|
|
1371
|
+
},
|
|
1372
|
+
async execute(args) {
|
|
1373
|
+
return JSON.stringify(await runReadTool({ shell, input: args }));
|
|
1374
|
+
}
|
|
1375
|
+
}),
|
|
1376
|
+
obsidian_search: tool({
|
|
1377
|
+
description: obsidian_search.description,
|
|
1378
|
+
args: {
|
|
1379
|
+
query: tool.schema.string().describe("Search query"),
|
|
1380
|
+
limit: tool.schema.number().optional().describe("Maximum number of results (default 10)"),
|
|
1381
|
+
vault: tool.schema.string().optional().describe("Vault name (optional, falls back to active vault)")
|
|
1382
|
+
},
|
|
1383
|
+
async execute(args) {
|
|
1384
|
+
return JSON.stringify(await runSearchTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null }));
|
|
1385
|
+
}
|
|
1386
|
+
}),
|
|
1387
|
+
obsidian_create_note: tool({
|
|
1388
|
+
description: obsidian_create_note.description,
|
|
1389
|
+
args: {
|
|
1390
|
+
name: tool.schema.string().describe("Note title"),
|
|
1391
|
+
content: tool.schema.string().describe("Note content in Markdown"),
|
|
1392
|
+
vault: tool.schema.string().optional().describe("Vault name (required if defaultVault is not configured)"),
|
|
1393
|
+
template: tool.schema.string().optional().describe("Template name to use"),
|
|
1394
|
+
silent: tool.schema.boolean().optional().describe("Skip opening the note after creation"),
|
|
1395
|
+
overwrite: tool.schema.boolean().optional().describe("Overwrite if note already exists")
|
|
1396
|
+
},
|
|
1397
|
+
async execute(args) {
|
|
1398
|
+
return JSON.stringify(await runCreateNoteTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null }));
|
|
1399
|
+
}
|
|
1400
|
+
}),
|
|
1401
|
+
obsidian_append_note: tool({
|
|
1402
|
+
description: obsidian_append_note.description,
|
|
1403
|
+
args: {
|
|
1404
|
+
content: tool.schema.string().describe("Text to append"),
|
|
1405
|
+
file: tool.schema.string().optional().describe("Note title or Obsidian file name"),
|
|
1406
|
+
path: tool.schema.string().optional().describe("Exact path relative to vault root"),
|
|
1407
|
+
vault: tool.schema.string().optional().describe("Vault name (required for write operations)")
|
|
1408
|
+
},
|
|
1409
|
+
async execute(args) {
|
|
1410
|
+
return JSON.stringify(await runAppendNoteTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null }));
|
|
1411
|
+
}
|
|
1412
|
+
}),
|
|
1413
|
+
obsidian_set_property: tool({
|
|
1414
|
+
description: obsidian_set_property.description,
|
|
1415
|
+
args: {
|
|
1416
|
+
name: tool.schema.string().describe("Property (frontmatter field) name"),
|
|
1417
|
+
value: tool.schema.string().describe("Value to set"),
|
|
1418
|
+
file: tool.schema.string().optional().describe("Note title or Obsidian file name"),
|
|
1419
|
+
path: tool.schema.string().optional().describe("Exact path relative to vault root"),
|
|
1420
|
+
vault: tool.schema.string().optional().describe("Vault name (required for write operations)")
|
|
1421
|
+
},
|
|
1422
|
+
async execute(args) {
|
|
1423
|
+
return JSON.stringify(await runSetPropertyTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null }));
|
|
1424
|
+
}
|
|
1425
|
+
}),
|
|
1426
|
+
obsidian_skills_check: tool({
|
|
1427
|
+
description: obsidian_skills_check.description,
|
|
1428
|
+
args: {
|
|
1429
|
+
mode: tool.schema.enum(["external", "bundled"]).optional().describe("Skills mode to check")
|
|
1430
|
+
},
|
|
1431
|
+
async execute(args) {
|
|
1432
|
+
const skillsState = await detectSkillsState({ mode: config.skills.mode, explicitPath: config.skills.externalPath, homeDir: homedir(), syncDirName: config.skills.syncDirName });
|
|
1433
|
+
return JSON.stringify(await runSkillsCheck({ mode: args.mode ?? skillsState.mode, skillsPath: skillsState.path, requiredSkills: ["obsidian-markdown", "obsidian-bases", "json-canvas", "obsidian-cli", "defuddle"] }));
|
|
1434
|
+
}
|
|
1435
|
+
}),
|
|
1436
|
+
obsidian_env_doctor: tool({
|
|
1437
|
+
description: obsidian_env_doctor.description,
|
|
1438
|
+
args: {},
|
|
1439
|
+
async execute() {
|
|
1440
|
+
return JSON.stringify(await runEnvDoctor({
|
|
1441
|
+
detectCli: () => detectCli(async () => (await makeBunSpawnShell()(["obsidian", "--version"])).exitCode === 0),
|
|
1442
|
+
detectApp: () => detectApp(async () => (await makeBunSpawnShell()(["obsidian", "vault"])).exitCode === 0),
|
|
1443
|
+
detectSkills: async () => {
|
|
1444
|
+
const state = await detectSkillsState({ mode: config.skills.mode, explicitPath: config.skills.externalPath, homeDir: homedir(), syncDirName: config.skills.syncDirName });
|
|
1445
|
+
const check = await runSkillsCheck({ mode: state.mode, skillsPath: state.path, requiredSkills: ["obsidian-markdown", "obsidian-bases", "json-canvas", "obsidian-cli", "defuddle"] });
|
|
1446
|
+
return { mode: state.mode, path: state.path, inSync: check.ok };
|
|
1447
|
+
},
|
|
1448
|
+
defaultVault: config.defaultVault
|
|
1449
|
+
}));
|
|
1450
|
+
}
|
|
1451
|
+
}),
|
|
1452
|
+
obsidian_backlinks: tool({
|
|
1453
|
+
description: obsidian_backlinks.description,
|
|
1454
|
+
args: {
|
|
1455
|
+
path: tool.schema.string().describe("Note path relative to vault root"),
|
|
1456
|
+
counts: tool.schema.boolean().optional().describe("Include link counts"),
|
|
1457
|
+
vault: tool.schema.string().optional()
|
|
1458
|
+
},
|
|
1459
|
+
async execute(args) {
|
|
1460
|
+
return JSON.stringify(await runBacklinksTool({ shell, input: args }));
|
|
1461
|
+
}
|
|
1462
|
+
}),
|
|
1463
|
+
obsidian_tags: tool({
|
|
1464
|
+
description: obsidian_tags.description,
|
|
1465
|
+
args: {
|
|
1466
|
+
path: tool.schema.string().optional().describe("Scope tags to a single note path"),
|
|
1467
|
+
counts: tool.schema.boolean().optional().describe("Include usage counts"),
|
|
1468
|
+
sort: tool.schema.string().optional().describe("Sort order: count or name"),
|
|
1469
|
+
vault: tool.schema.string().optional()
|
|
1470
|
+
},
|
|
1471
|
+
async execute(args) {
|
|
1472
|
+
return JSON.stringify(await runTagsTool({ shell, input: args }));
|
|
1473
|
+
}
|
|
1474
|
+
}),
|
|
1475
|
+
obsidian_tag_notes: tool({
|
|
1476
|
+
description: obsidian_tag_notes.description,
|
|
1477
|
+
args: {
|
|
1478
|
+
name: tool.schema.string().describe("Tag name (without leading #)"),
|
|
1479
|
+
vault: tool.schema.string().optional()
|
|
1480
|
+
},
|
|
1481
|
+
async execute(args) {
|
|
1482
|
+
return JSON.stringify(await runTagNotesTool({ shell, input: args }));
|
|
1483
|
+
}
|
|
1484
|
+
}),
|
|
1485
|
+
obsidian_plugins: tool({
|
|
1486
|
+
description: obsidian_plugins.description,
|
|
1487
|
+
args: { vault: tool.schema.string().optional() },
|
|
1488
|
+
async execute(args) {
|
|
1489
|
+
return JSON.stringify(await runPluginsTool({ shell, input: args }));
|
|
1490
|
+
}
|
|
1491
|
+
}),
|
|
1492
|
+
obsidian_plugin_reload: tool({
|
|
1493
|
+
description: obsidian_plugin_reload.description,
|
|
1494
|
+
args: {
|
|
1495
|
+
id: tool.schema.string().describe("Plugin ID as shown in community plugin settings")
|
|
1496
|
+
},
|
|
1497
|
+
async execute(args) {
|
|
1498
|
+
return JSON.stringify(await runPluginReloadTool({ shell, input: args }));
|
|
1499
|
+
}
|
|
1500
|
+
}),
|
|
1501
|
+
obsidian_dev_errors: tool({
|
|
1502
|
+
description: obsidian_dev_errors.description,
|
|
1503
|
+
args: { vault: tool.schema.string().optional() },
|
|
1504
|
+
async execute(args) {
|
|
1505
|
+
return JSON.stringify(await runDevErrorsTool({ shell, input: args }));
|
|
1506
|
+
}
|
|
1507
|
+
}),
|
|
1508
|
+
obsidian_dev_console: tool({
|
|
1509
|
+
description: obsidian_dev_console.description,
|
|
1510
|
+
args: {
|
|
1511
|
+
action: tool.schema.enum(["start", "stop", "get"]).describe("start/stop capture or get output"),
|
|
1512
|
+
limit: tool.schema.number().optional().describe("Max lines to return (get action only)")
|
|
1513
|
+
},
|
|
1514
|
+
async execute(args) {
|
|
1515
|
+
return JSON.stringify(await runDevConsoleTool({ shell, input: args }));
|
|
1516
|
+
}
|
|
1517
|
+
}),
|
|
1518
|
+
obsidian_dev_screenshot: tool({
|
|
1519
|
+
description: obsidian_dev_screenshot.description,
|
|
1520
|
+
args: {
|
|
1521
|
+
path: tool.schema.string().describe("Output path inside the vault (e.g. shots/home.png)"),
|
|
1522
|
+
vault: tool.schema.string().optional()
|
|
1523
|
+
},
|
|
1524
|
+
async execute(args) {
|
|
1525
|
+
return JSON.stringify(await runDevScreenshotTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null }));
|
|
1526
|
+
}
|
|
1527
|
+
}),
|
|
1528
|
+
obsidian_dev_dom: tool({
|
|
1529
|
+
description: obsidian_dev_dom.description,
|
|
1530
|
+
args: {
|
|
1531
|
+
selector: tool.schema.string().describe("CSS selector to target"),
|
|
1532
|
+
mode: tool.schema.enum(["text", "all", "total"]).optional(),
|
|
1533
|
+
attr: tool.schema.string().optional().describe("Attribute name to read"),
|
|
1534
|
+
css: tool.schema.string().optional().describe("CSS property to read")
|
|
1535
|
+
},
|
|
1536
|
+
async execute(args) {
|
|
1537
|
+
return JSON.stringify(await runDevDomTool({ shell, input: args }));
|
|
1538
|
+
}
|
|
1539
|
+
}),
|
|
1540
|
+
obsidian_dev_css: tool({
|
|
1541
|
+
description: obsidian_dev_css.description,
|
|
1542
|
+
args: {
|
|
1543
|
+
selector: tool.schema.string().describe("CSS selector"),
|
|
1544
|
+
prop: tool.schema.string().optional().describe("Specific CSS property name")
|
|
1545
|
+
},
|
|
1546
|
+
async execute(args) {
|
|
1547
|
+
return JSON.stringify(await runDevCssTool({ shell, input: args }));
|
|
1548
|
+
}
|
|
1549
|
+
}),
|
|
1550
|
+
obsidian_wiki_init: tool({
|
|
1551
|
+
description: obsidian_wiki_init.description,
|
|
1552
|
+
args: {
|
|
1553
|
+
vault: tool.schema.string().optional().describe("Target vault (required if defaultVault is not configured)"),
|
|
1554
|
+
force: tool.schema.boolean().optional().describe("Overwrite SCHEMA.md and INDEX.md if they already exist")
|
|
1555
|
+
},
|
|
1556
|
+
async execute(args) {
|
|
1557
|
+
return JSON.stringify(await runWikiInitTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null, wikiPaths }));
|
|
1558
|
+
}
|
|
1559
|
+
}),
|
|
1560
|
+
obsidian_wiki_ingest: tool({
|
|
1561
|
+
description: obsidian_wiki_ingest.description,
|
|
1562
|
+
args: {
|
|
1563
|
+
sourceName: tool.schema.string().describe("Source document name (becomes raw/<name> and wiki/<name>)"),
|
|
1564
|
+
sourceContent: tool.schema.string().describe("Raw source content (immutable)"),
|
|
1565
|
+
wikiContent: tool.schema.string().describe("Wiki summary/knowledge content"),
|
|
1566
|
+
vault: tool.schema.string().optional()
|
|
1567
|
+
},
|
|
1568
|
+
async execute(args) {
|
|
1569
|
+
return JSON.stringify(await runWikiIngestTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null, wikiPaths }));
|
|
1570
|
+
}
|
|
1571
|
+
}),
|
|
1572
|
+
obsidian_wiki_update: tool({
|
|
1573
|
+
description: obsidian_wiki_update.description,
|
|
1574
|
+
args: {
|
|
1575
|
+
pageName: tool.schema.string().describe("Wiki page name (stored under wiki/)"),
|
|
1576
|
+
content: tool.schema.string().describe("Full page content in Markdown"),
|
|
1577
|
+
vault: tool.schema.string().optional()
|
|
1578
|
+
},
|
|
1579
|
+
async execute(args) {
|
|
1580
|
+
return JSON.stringify(await runWikiUpdateTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null, wikiPaths }));
|
|
1581
|
+
}
|
|
1582
|
+
}),
|
|
1583
|
+
obsidian_wiki_refresh_index: tool({
|
|
1584
|
+
description: obsidian_wiki_refresh_index.description,
|
|
1585
|
+
args: { vault: tool.schema.string().optional() },
|
|
1586
|
+
async execute(args) {
|
|
1587
|
+
return JSON.stringify(await runWikiRefreshIndexTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null, wikiPaths }));
|
|
1588
|
+
}
|
|
1589
|
+
}),
|
|
1590
|
+
obsidian_wiki_search_cited: tool({
|
|
1591
|
+
description: obsidian_wiki_search_cited.description,
|
|
1592
|
+
args: {
|
|
1593
|
+
query: tool.schema.string().describe("Search query scoped to wiki/"),
|
|
1594
|
+
limit: tool.schema.number().optional(),
|
|
1595
|
+
vault: tool.schema.string().optional()
|
|
1596
|
+
},
|
|
1597
|
+
async execute(args) {
|
|
1598
|
+
return JSON.stringify(await runWikiSearchCitedTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null, wikiPaths }));
|
|
1599
|
+
}
|
|
1600
|
+
}),
|
|
1601
|
+
obsidian_wiki_save_answer: tool({
|
|
1602
|
+
description: obsidian_wiki_save_answer.description,
|
|
1603
|
+
args: {
|
|
1604
|
+
question: tool.schema.string().describe("The question being answered"),
|
|
1605
|
+
answer: tool.schema.string().describe("The answer content in Markdown"),
|
|
1606
|
+
tags: tool.schema.array(tool.schema.string()).optional().describe("Optional tags for the answer note"),
|
|
1607
|
+
vault: tool.schema.string().optional()
|
|
1608
|
+
},
|
|
1609
|
+
async execute(args) {
|
|
1610
|
+
return JSON.stringify(await runWikiSaveAnswerTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null, wikiPaths }));
|
|
1611
|
+
}
|
|
1612
|
+
}),
|
|
1613
|
+
obsidian_wiki_lint: tool({
|
|
1614
|
+
description: obsidian_wiki_lint.description,
|
|
1615
|
+
args: { vault: tool.schema.string().optional() },
|
|
1616
|
+
async execute(args) {
|
|
1617
|
+
return JSON.stringify(await runWikiLintTool({ shell, input: args, defaultVault: config.defaultVault, activeVault: null, wikiPaths }));
|
|
1618
|
+
}
|
|
1619
|
+
}),
|
|
1620
|
+
obsidian_eval: tool({
|
|
1621
|
+
description: obsidian_eval.description,
|
|
1622
|
+
args: {
|
|
1623
|
+
code: tool.schema.string().describe("JavaScript code to execute in the Obsidian app context")
|
|
1624
|
+
},
|
|
1625
|
+
async execute(args) {
|
|
1626
|
+
return JSON.stringify(await runEvalTool({ shell, input: args, evalEnabled: config.evalEnabled }));
|
|
1627
|
+
}
|
|
1628
|
+
})
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
};
|
|
1632
|
+
var src_default = OpenGemPlugin;
|
|
1633
|
+
export {
|
|
1634
|
+
src_default as default,
|
|
1635
|
+
OpenGemPlugin
|
|
1636
|
+
};
|