@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/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
+ };