@quanta-intellect/vessel-browser 0.1.136 → 0.1.137

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/out/main/index.js CHANGED
@@ -22147,492 +22147,135 @@ function registerAgentRuntimeHandlers(runtime2, chromeView, sidebarView, sendToR
22147
22147
  }
22148
22148
  );
22149
22149
  }
22150
- const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
22151
- const DEFAULT_NOTE_FOLDER = "Vessel/Research";
22152
- const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
22153
- const PAGE_CONTENT_LIMIT = 6e3;
22154
- const DEFAULT_LIST_LIMIT = 50;
22155
- const DEFAULT_SEARCH_LIMIT = 20;
22156
- function getVaultRoot() {
22157
- const configured = loadSettings().obsidianVaultPath.trim();
22158
- if (!configured) {
22159
- throw new Error(
22160
- "Obsidian not configured. Set vault path in Vessel settings to use memory capture."
22161
- );
22162
- }
22163
- return path$1.resolve(configured);
22150
+ function asTextResponse$1(text) {
22151
+ return { content: [{ type: "text", text }] };
22164
22152
  }
22165
- function assertInsideVault(targetPath, vaultRoot) {
22166
- const resolved = path$1.resolve(targetPath);
22167
- const relative = path$1.relative(vaultRoot, resolved);
22168
- if (relative.startsWith("..") || path$1.isAbsolute(relative)) {
22169
- throw new Error("Resolved note path is outside the configured vault.");
22170
- }
22171
- return resolved;
22153
+ const DANGEROUS_DEVTOOLS_ACTIONS = /* @__PURE__ */ new Set([
22154
+ "devtools_execute_js",
22155
+ "devtools_modify_dom",
22156
+ "devtools_set_storage"
22157
+ ]);
22158
+ let stateListener = null;
22159
+ const activityLog = [];
22160
+ const MAX_ACTIVITY_ENTRIES = 100;
22161
+ let activityCounter = 0;
22162
+ function setDevToolsPanelListener(listener) {
22163
+ stateListener = listener;
22172
22164
  }
22173
- function normalizeFolder(folder, fallback) {
22174
- const raw = (folder?.trim() || fallback).replace(/\\/g, "/");
22175
- if (!raw) return fallback;
22176
- if (path$1.isAbsolute(raw)) {
22177
- throw new Error("Vault note folders must be relative to the vault root.");
22178
- }
22179
- const segments = raw.split("/").filter(Boolean);
22180
- if (segments.some((segment) => segment === "." || segment === "..")) {
22181
- throw new Error("Vault note folders cannot traverse outside the vault.");
22182
- }
22183
- return segments.join(path$1.sep);
22165
+ function getDevToolsPanelState(tabId) {
22166
+ const session = tabId ? getSession(tabId) : void 0;
22167
+ return {
22168
+ console: session?.getConsoleLogs() ?? [],
22169
+ network: session?.getNetworkLog() ?? [],
22170
+ errors: session?.getErrors() ?? [],
22171
+ activity: activityLog
22172
+ };
22184
22173
  }
22185
- function normalizeNotePath(notePath) {
22186
- const raw = notePath.trim().replace(/\\/g, "/");
22187
- if (!raw) {
22188
- throw new Error("A note path is required.");
22189
- }
22190
- if (path$1.isAbsolute(raw)) {
22191
- throw new Error("Note paths must be relative to the vault root.");
22174
+ function broadcastState(tabManager) {
22175
+ if (!stateListener) return;
22176
+ const tabId = tabManager.getActiveTabId();
22177
+ stateListener(getDevToolsPanelState(tabId));
22178
+ }
22179
+ async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
22180
+ const activityEntry = {
22181
+ id: ++activityCounter,
22182
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22183
+ tool: name,
22184
+ args: JSON.stringify(args).slice(0, 200),
22185
+ result: "",
22186
+ durationMs: 0,
22187
+ status: "running"
22188
+ };
22189
+ activityLog.push(activityEntry);
22190
+ if (activityLog.length > MAX_ACTIVITY_ENTRIES) {
22191
+ activityLog.splice(0, activityLog.length - MAX_ACTIVITY_ENTRIES);
22192
22192
  }
22193
- const segments = raw.split("/").filter(Boolean);
22194
- if (segments.some((segment) => segment === "." || segment === "..")) {
22195
- throw new Error("Note paths cannot traverse outside the vault.");
22193
+ broadcastState(tabManager);
22194
+ const startTime = Date.now();
22195
+ try {
22196
+ const result = await runtime2.runControlledAction({
22197
+ source: "mcp",
22198
+ name,
22199
+ args,
22200
+ tabId: tabManager.getActiveTabId(),
22201
+ dangerous: DANGEROUS_DEVTOOLS_ACTIONS.has(name),
22202
+ executor
22203
+ });
22204
+ activityEntry.status = "completed";
22205
+ activityEntry.result = result.slice(0, 200);
22206
+ activityEntry.durationMs = Date.now() - startTime;
22207
+ broadcastState(tabManager);
22208
+ return asTextResponse$1(result);
22209
+ } catch (error) {
22210
+ const message = error instanceof Error ? error.message : "Unknown error";
22211
+ activityEntry.status = "failed";
22212
+ activityEntry.result = message.slice(0, 200);
22213
+ activityEntry.durationMs = Date.now() - startTime;
22214
+ broadcastState(tabManager);
22215
+ return asTextResponse$1(`Error: ${message}`);
22196
22216
  }
22197
- const normalized = segments.join(path$1.sep);
22198
- return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
22199
- }
22200
- function escapeYaml(value) {
22201
- return JSON.stringify(value);
22202
22217
  }
22203
- function renderFrontmatter(data) {
22204
- const lines = ["---"];
22205
- for (const [key2, value] of Object.entries(data)) {
22206
- if (value == null) continue;
22207
- if (Array.isArray(value)) {
22208
- if (value.length === 0) continue;
22209
- lines.push(`${key2}:`);
22210
- for (const item of value) {
22211
- lines.push(` - ${escapeYaml(item)}`);
22218
+ function registerDevTools(server, tabManager, runtime2) {
22219
+ server.registerTool(
22220
+ "vessel_devtools_console_logs",
22221
+ {
22222
+ title: "DevTools: Get Console Logs",
22223
+ description: "Get console log entries captured from the active tab. Returns a rolling buffer of recent console.log, console.warn, console.error, etc. calls. Automatically starts capturing on first use.",
22224
+ inputSchema: {
22225
+ level: zod.z.enum(["log", "warning", "error", "info", "debug", "verbose"]).optional().describe("Filter by log level"),
22226
+ limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)"),
22227
+ search: zod.z.string().optional().describe("Filter entries containing this text (case-insensitive)")
22212
22228
  }
22213
- continue;
22229
+ },
22230
+ async ({ level, limit, search: search2 }) => {
22231
+ return withDevToolsAction(
22232
+ runtime2,
22233
+ tabManager,
22234
+ "devtools_console_logs",
22235
+ { level, limit, search: search2 },
22236
+ async () => {
22237
+ const session = getOrCreateSession(tabManager);
22238
+ await session.ensureConsoleDomain();
22239
+ const entries = session.getConsoleLogs({ level, limit, search: search2 });
22240
+ if (entries.length === 0) {
22241
+ return "No console entries captured yet. Console monitoring is now active — new entries will be captured as they occur.";
22242
+ }
22243
+ return JSON.stringify(entries, null, 2);
22244
+ }
22245
+ );
22214
22246
  }
22215
- lines.push(`${key2}: ${escapeYaml(value)}`);
22216
- }
22217
- lines.push("---", "");
22218
- return lines.join("\n");
22219
- }
22220
- function slugify(value) {
22221
- const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
22222
- return normalized || "note";
22223
- }
22224
- function buildUniqueNotePath(dir, title) {
22225
- const datePrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22226
- const slug = slugify(title);
22227
- const base = `${datePrefix}-${slug}`;
22228
- let candidate = `${base}.md`;
22229
- let counter = 2;
22230
- while (fs$1.existsSync(path$1.join(dir, candidate))) {
22231
- candidate = `${base}-${counter}.md`;
22232
- counter += 1;
22233
- }
22234
- return path$1.join(dir, candidate);
22235
- }
22236
- function trimContent(content, limit = PAGE_CONTENT_LIMIT) {
22237
- const cleaned = content.trim();
22238
- if (cleaned.length <= limit) return cleaned;
22239
- return `${cleaned.slice(0, limit)}
22240
-
22241
- [Truncated]`;
22242
- }
22243
- function parseFrontmatter(content) {
22244
- if (!content.startsWith("---\n")) {
22245
- return { body: content, tags: [] };
22246
- }
22247
- const closingIndex = content.indexOf("\n---\n", 4);
22248
- if (closingIndex === -1) {
22249
- return { body: content, tags: [] };
22250
- }
22251
- const raw = content.slice(4, closingIndex);
22252
- const body = content.slice(closingIndex + 5);
22253
- const lines = raw.split("\n");
22254
- const result = { tags: [] };
22255
- let activeArrayKey = "";
22256
- for (const line of lines) {
22257
- const trimmed = line.trim();
22258
- if (!trimmed) continue;
22259
- if (trimmed.startsWith("- ") && activeArrayKey === "tags") {
22260
- result.tags.push(
22261
- trimmed.slice(2).trim().replace(/^["']|["']$/g, "")
22247
+ );
22248
+ server.registerTool(
22249
+ "vessel_devtools_console_clear",
22250
+ {
22251
+ title: "DevTools: Clear Console Logs",
22252
+ description: "Clear the captured console log buffer for the active tab."
22253
+ },
22254
+ async () => {
22255
+ return withDevToolsAction(
22256
+ runtime2,
22257
+ tabManager,
22258
+ "devtools_console_clear",
22259
+ {},
22260
+ async () => {
22261
+ const session = getOrCreateSession(tabManager);
22262
+ const count = session.clearConsoleLogs();
22263
+ return `Cleared ${count} console entries.`;
22264
+ }
22262
22265
  );
22263
- continue;
22264
22266
  }
22265
- activeArrayKey = "";
22266
- const separatorIndex = trimmed.indexOf(":");
22267
- if (separatorIndex === -1) continue;
22268
- const key2 = trimmed.slice(0, separatorIndex).trim();
22269
- const value = trimmed.slice(separatorIndex + 1).trim();
22270
- if (key2 === "title" && value) {
22271
- result.title = value.replace(/^["']|["']$/g, "");
22272
- } else if (key2 === "tags") {
22273
- activeArrayKey = "tags";
22274
- if (value.startsWith("[") && value.endsWith("]")) {
22275
- const inline = value.slice(1, -1).split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
22276
- result.tags.push(...inline);
22277
- activeArrayKey = "";
22278
- }
22279
- }
22280
- }
22281
- return { body, title: result.title, tags: result.tags };
22282
- }
22283
- function collectMarkdownFiles(dir) {
22284
- const entries = fs$1.readdirSync(dir, { withFileTypes: true });
22285
- const files = [];
22286
- for (const entry of entries) {
22287
- const absolutePath = path$1.join(dir, entry.name);
22288
- if (entry.isDirectory()) {
22289
- files.push(...collectMarkdownFiles(absolutePath));
22290
- continue;
22291
- }
22292
- if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
22293
- files.push(absolutePath);
22294
- }
22295
- }
22296
- return files;
22297
- }
22298
- function toSummary(absolutePath, vaultRoot) {
22299
- const stats = fs$1.statSync(absolutePath);
22300
- const relativePath = path$1.relative(vaultRoot, absolutePath).split(path$1.sep).join("/");
22301
- const raw = fs$1.readFileSync(absolutePath, "utf-8");
22302
- const parsed = parseFrontmatter(raw);
22303
- const headingMatch = parsed.body.match(/^#\s+(.+)$/m);
22304
- const title = parsed.title || headingMatch?.[1]?.trim() || path$1.basename(absolutePath, ".md");
22305
- return {
22306
- title,
22307
- absolutePath,
22308
- relativePath,
22309
- modifiedAt: stats.mtime.toISOString(),
22310
- tags: parsed.tags
22311
- };
22312
- }
22313
- function renderBookmarkLinkBlock(bookmark, note) {
22314
- const lines = [
22315
- "## Linked Bookmark",
22316
- "",
22317
- `- Bookmark ID: \`${bookmark.id}\``,
22318
- `- Title: ${bookmark.title || bookmark.url}`,
22319
- `- URL: [${bookmark.url}](${bookmark.url})`,
22320
- `- Saved At: ${bookmark.savedAt}`
22321
- ];
22322
- if (note?.trim()) {
22323
- lines.push("", "### Context", "", note.trim());
22324
- }
22325
- return `${lines.join("\n")}
22326
- `;
22327
- }
22328
- function writeMemoryNote({
22329
- title,
22330
- body,
22331
- folder,
22332
- tags = [],
22333
- frontmatter = {}
22334
- }) {
22335
- const vaultRoot = getVaultRoot();
22336
- const relativeFolder = normalizeFolder(folder, DEFAULT_NOTE_FOLDER);
22337
- const targetDir = path$1.join(vaultRoot, relativeFolder);
22338
- fs$1.mkdirSync(targetDir, { recursive: true });
22339
- const absolutePath = buildUniqueNotePath(targetDir, title);
22340
- const relativePath = path$1.relative(vaultRoot, absolutePath);
22341
- const content = [
22342
- renderFrontmatter({
22343
- title,
22344
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
22345
- tags,
22346
- ...frontmatter
22347
- }),
22348
- body.trim(),
22349
- ""
22350
- ].join("\n");
22351
- fs$1.writeFileSync(absolutePath, content, "utf-8");
22352
- return {
22353
- title,
22354
- absolutePath,
22355
- relativePath: relativePath.split(path$1.sep).join("/")
22356
- };
22357
- }
22358
- function appendToMemoryNote({
22359
- notePath,
22360
- content,
22361
- heading
22362
- }) {
22363
- const vaultRoot = getVaultRoot();
22364
- const relativePath = normalizeNotePath(notePath);
22365
- const absolutePath = assertInsideVault(
22366
- path$1.join(vaultRoot, relativePath),
22367
- vaultRoot
22368
- );
22369
- if (!fs$1.existsSync(absolutePath)) {
22370
- throw new Error(
22371
- `Memory note not found: ${relativePath.split(path$1.sep).join("/")}`
22372
- );
22373
- }
22374
- const current = fs$1.readFileSync(absolutePath, "utf-8").trimEnd();
22375
- const nextParts = [current, ""];
22376
- if (heading?.trim()) {
22377
- nextParts.push(`## ${heading.trim()}`, "");
22378
- }
22379
- nextParts.push(content.trim(), "");
22380
- fs$1.writeFileSync(absolutePath, nextParts.join("\n"), "utf-8");
22381
- return {
22382
- title: path$1.basename(absolutePath, ".md"),
22383
- absolutePath,
22384
- relativePath: relativePath.split(path$1.sep).join("/")
22385
- };
22386
- }
22387
- function listMemoryNotes({
22388
- folder,
22389
- limit = DEFAULT_LIST_LIMIT
22390
- } = {}) {
22391
- const vaultRoot = getVaultRoot();
22392
- const relativeFolder = normalizeFolder(folder, "");
22393
- const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
22394
- if (!fs$1.existsSync(targetDir)) {
22395
- return [];
22396
- }
22397
- return collectMarkdownFiles(targetDir).map((absolutePath) => toSummary(absolutePath, vaultRoot)).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
22398
- }
22399
- function searchMemoryNotes({
22400
- query,
22401
- folder,
22402
- tags = [],
22403
- limit = DEFAULT_SEARCH_LIMIT
22404
- }) {
22405
- const loweredQuery = query.trim().toLowerCase();
22406
- if (!loweredQuery) {
22407
- throw new Error("A non-empty memory search query is required.");
22408
- }
22409
- const vaultRoot = getVaultRoot();
22410
- const relativeFolder = normalizeFolder(folder, "");
22411
- const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
22412
- if (!fs$1.existsSync(targetDir)) {
22413
- return [];
22414
- }
22415
- const loweredTags = tags.map((tag) => tag.trim().toLowerCase()).filter(Boolean);
22416
- return collectMarkdownFiles(targetDir).map((absolutePath) => {
22417
- const raw = fs$1.readFileSync(absolutePath, "utf-8");
22418
- const parsed = parseFrontmatter(raw);
22419
- const summary = toSummary(absolutePath, vaultRoot);
22420
- const haystack = `${summary.title}
22421
- ${summary.relativePath}
22422
- ${parsed.body}`.toLowerCase();
22423
- const hasQuery = haystack.includes(loweredQuery);
22424
- const hasTags = loweredTags.length === 0 || loweredTags.every(
22425
- (tag) => summary.tags.some((noteTag) => noteTag.toLowerCase() === tag)
22426
- );
22427
- return hasQuery && hasTags ? summary : null;
22428
- }).filter((item) => item !== null).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
22429
- }
22430
- function capturePageToVault({
22431
- page,
22432
- title,
22433
- folder,
22434
- summary,
22435
- note,
22436
- tags = []
22437
- }) {
22438
- const noteTitle = title?.trim() || page.title.trim() || page.url;
22439
- const bodyLines = [
22440
- `# ${noteTitle}`,
22441
- "",
22442
- `Source: [${page.title || page.url}](${page.url})`,
22443
- `Captured: ${(/* @__PURE__ */ new Date()).toISOString()}`
22444
- ];
22445
- if (page.byline) {
22446
- bodyLines.push(`Byline: ${page.byline}`);
22447
- }
22448
- bodyLines.push("");
22449
- if (summary?.trim()) {
22450
- bodyLines.push("## Summary", "", summary.trim(), "");
22451
- }
22452
- if (note?.trim()) {
22453
- bodyLines.push("## Research Note", "", note.trim(), "");
22454
- }
22455
- if (page.excerpt.trim()) {
22456
- bodyLines.push("## Excerpt", "", page.excerpt.trim(), "");
22457
- }
22458
- const snapshot2 = trimContent(page.content);
22459
- if (snapshot2) {
22460
- bodyLines.push("## Page Snapshot", "", snapshot2, "");
22461
- }
22462
- return writeMemoryNote({
22463
- title: noteTitle,
22464
- body: bodyLines.join("\n"),
22465
- folder: folder || DEFAULT_PAGE_FOLDER,
22466
- tags,
22467
- frontmatter: {
22468
- source_url: page.url,
22469
- source_title: page.title || page.url
22470
- }
22471
- });
22472
- }
22473
- function linkBookmarkToMemory({
22474
- bookmark,
22475
- notePath,
22476
- title,
22477
- folder,
22478
- note,
22479
- tags = []
22480
- }) {
22481
- if (notePath?.trim()) {
22482
- return appendToMemoryNote({
22483
- notePath,
22484
- heading: "Linked Bookmark",
22485
- content: [
22486
- `- Bookmark ID: \`${bookmark.id}\``,
22487
- `- Title: ${bookmark.title || bookmark.url}`,
22488
- `- URL: [${bookmark.url}](${bookmark.url})`,
22489
- `- Saved At: ${bookmark.savedAt}`,
22490
- note?.trim() ? `- Note: ${note.trim()}` : ""
22491
- ].filter(Boolean).join("\n")
22492
- });
22493
- }
22494
- const noteTitle = title?.trim() || bookmark.title || bookmark.url;
22495
- return writeMemoryNote({
22496
- title: noteTitle,
22497
- body: renderBookmarkLinkBlock(bookmark, note),
22498
- folder: folder || DEFAULT_BOOKMARK_FOLDER,
22499
- tags,
22500
- frontmatter: {
22501
- bookmark_id: bookmark.id,
22502
- source_url: bookmark.url,
22503
- source_title: bookmark.title || bookmark.url
22504
- }
22505
- });
22506
- }
22507
- function asTextResponse$1(text) {
22508
- return { content: [{ type: "text", text }] };
22509
- }
22510
- const DANGEROUS_DEVTOOLS_ACTIONS = /* @__PURE__ */ new Set([
22511
- "devtools_execute_js",
22512
- "devtools_modify_dom",
22513
- "devtools_set_storage"
22514
- ]);
22515
- let stateListener = null;
22516
- const activityLog = [];
22517
- const MAX_ACTIVITY_ENTRIES = 100;
22518
- let activityCounter = 0;
22519
- function setDevToolsPanelListener(listener) {
22520
- stateListener = listener;
22521
- }
22522
- function getDevToolsPanelState(tabId) {
22523
- const session = tabId ? getSession(tabId) : void 0;
22524
- return {
22525
- console: session?.getConsoleLogs() ?? [],
22526
- network: session?.getNetworkLog() ?? [],
22527
- errors: session?.getErrors() ?? [],
22528
- activity: activityLog
22529
- };
22530
- }
22531
- function broadcastState(tabManager) {
22532
- if (!stateListener) return;
22533
- const tabId = tabManager.getActiveTabId();
22534
- stateListener(getDevToolsPanelState(tabId));
22535
- }
22536
- async function withDevToolsAction(runtime2, tabManager, name, args, executor) {
22537
- const activityEntry = {
22538
- id: ++activityCounter,
22539
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
22540
- tool: name,
22541
- args: JSON.stringify(args).slice(0, 200),
22542
- result: "",
22543
- durationMs: 0,
22544
- status: "running"
22545
- };
22546
- activityLog.push(activityEntry);
22547
- if (activityLog.length > MAX_ACTIVITY_ENTRIES) {
22548
- activityLog.splice(0, activityLog.length - MAX_ACTIVITY_ENTRIES);
22549
- }
22550
- broadcastState(tabManager);
22551
- const startTime = Date.now();
22552
- try {
22553
- const result = await runtime2.runControlledAction({
22554
- source: "mcp",
22555
- name,
22556
- args,
22557
- tabId: tabManager.getActiveTabId(),
22558
- dangerous: DANGEROUS_DEVTOOLS_ACTIONS.has(name),
22559
- executor
22560
- });
22561
- activityEntry.status = "completed";
22562
- activityEntry.result = result.slice(0, 200);
22563
- activityEntry.durationMs = Date.now() - startTime;
22564
- broadcastState(tabManager);
22565
- return asTextResponse$1(result);
22566
- } catch (error) {
22567
- const message = error instanceof Error ? error.message : "Unknown error";
22568
- activityEntry.status = "failed";
22569
- activityEntry.result = message.slice(0, 200);
22570
- activityEntry.durationMs = Date.now() - startTime;
22571
- broadcastState(tabManager);
22572
- return asTextResponse$1(`Error: ${message}`);
22573
- }
22574
- }
22575
- function registerDevTools(server, tabManager, runtime2) {
22576
- server.registerTool(
22577
- "vessel_devtools_console_logs",
22578
- {
22579
- title: "DevTools: Get Console Logs",
22580
- description: "Get console log entries captured from the active tab. Returns a rolling buffer of recent console.log, console.warn, console.error, etc. calls. Automatically starts capturing on first use.",
22581
- inputSchema: {
22582
- level: zod.z.enum(["log", "warning", "error", "info", "debug", "verbose"]).optional().describe("Filter by log level"),
22583
- limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)"),
22584
- search: zod.z.string().optional().describe("Filter entries containing this text (case-insensitive)")
22585
- }
22586
- },
22587
- async ({ level, limit, search: search2 }) => {
22588
- return withDevToolsAction(
22589
- runtime2,
22590
- tabManager,
22591
- "devtools_console_logs",
22592
- { level, limit, search: search2 },
22593
- async () => {
22594
- const session = getOrCreateSession(tabManager);
22595
- await session.ensureConsoleDomain();
22596
- const entries = session.getConsoleLogs({ level, limit, search: search2 });
22597
- if (entries.length === 0) {
22598
- return "No console entries captured yet. Console monitoring is now active — new entries will be captured as they occur.";
22599
- }
22600
- return JSON.stringify(entries, null, 2);
22601
- }
22602
- );
22603
- }
22604
- );
22605
- server.registerTool(
22606
- "vessel_devtools_console_clear",
22607
- {
22608
- title: "DevTools: Clear Console Logs",
22609
- description: "Clear the captured console log buffer for the active tab."
22610
- },
22611
- async () => {
22612
- return withDevToolsAction(
22613
- runtime2,
22614
- tabManager,
22615
- "devtools_console_clear",
22616
- {},
22617
- async () => {
22618
- const session = getOrCreateSession(tabManager);
22619
- const count = session.clearConsoleLogs();
22620
- return `Cleared ${count} console entries.`;
22621
- }
22622
- );
22623
- }
22624
- );
22625
- server.registerTool(
22626
- "vessel_devtools_network_log",
22627
- {
22628
- title: "DevTools: Get Network Log",
22629
- description: "Get captured network requests/responses from the active tab. Returns method, URL, status, timing, headers, and size. Automatically starts capturing on first use.",
22630
- inputSchema: {
22631
- url_pattern: zod.z.string().optional().describe("Filter by URL pattern (regex or substring match)"),
22632
- method: zod.z.string().optional().describe("Filter by HTTP method (GET, POST, etc.)"),
22633
- status_min: zod.z.number().optional().describe("Minimum HTTP status code (e.g., 400 for errors)"),
22634
- status_max: zod.z.number().optional().describe("Maximum HTTP status code"),
22635
- limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)")
22267
+ );
22268
+ server.registerTool(
22269
+ "vessel_devtools_network_log",
22270
+ {
22271
+ title: "DevTools: Get Network Log",
22272
+ description: "Get captured network requests/responses from the active tab. Returns method, URL, status, timing, headers, and size. Automatically starts capturing on first use.",
22273
+ inputSchema: {
22274
+ url_pattern: zod.z.string().optional().describe("Filter by URL pattern (regex or substring match)"),
22275
+ method: zod.z.string().optional().describe("Filter by HTTP method (GET, POST, etc.)"),
22276
+ status_min: zod.z.number().optional().describe("Minimum HTTP status code (e.g., 400 for errors)"),
22277
+ status_max: zod.z.number().optional().describe("Maximum HTTP status code"),
22278
+ limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)")
22636
22279
  }
22637
22280
  },
22638
22281
  async ({ url_pattern, method, status_min, status_max, limit }) => {
@@ -22788,154 +22431,529 @@ function registerDevTools(server, tabManager, runtime2) {
22788
22431
  }
22789
22432
  );
22790
22433
  }
22791
- );
22792
- server.registerTool(
22793
- "vessel_devtools_execute_js",
22794
- {
22795
- title: "DevTools: Execute JavaScript",
22796
- description: "Execute a JavaScript expression in the context of the active tab's page via the Runtime.evaluate CDP method. Supports async/await. This is a dangerous action — it can modify page state.",
22797
- inputSchema: {
22798
- expression: zod.z.string().describe("JavaScript expression to evaluate in the page context")
22434
+ );
22435
+ server.registerTool(
22436
+ "vessel_devtools_execute_js",
22437
+ {
22438
+ title: "DevTools: Execute JavaScript",
22439
+ description: "Execute a JavaScript expression in the context of the active tab's page via the Runtime.evaluate CDP method. Supports async/await. This is a dangerous action — it can modify page state.",
22440
+ inputSchema: {
22441
+ expression: zod.z.string().describe("JavaScript expression to evaluate in the page context")
22442
+ }
22443
+ },
22444
+ async ({ expression }) => {
22445
+ return withDevToolsAction(
22446
+ runtime2,
22447
+ tabManager,
22448
+ "devtools_execute_js",
22449
+ { expression: expression.slice(0, 200) },
22450
+ async () => {
22451
+ const session = getOrCreateSession(tabManager);
22452
+ const result = await session.executeJs(expression);
22453
+ const parts = [`[${result.type}] ${result.result}`];
22454
+ if (result.exceptionDetails) {
22455
+ parts.push(`
22456
+ Exception: ${result.exceptionDetails}`);
22457
+ }
22458
+ return parts.join("");
22459
+ }
22460
+ );
22461
+ }
22462
+ );
22463
+ server.registerTool(
22464
+ "vessel_devtools_get_storage",
22465
+ {
22466
+ title: "DevTools: Get Storage",
22467
+ description: "Read browser storage for the active tab's origin. Supports localStorage, sessionStorage, cookies, and IndexedDB database listing.",
22468
+ inputSchema: {
22469
+ type: zod.z.enum(["localStorage", "sessionStorage", "cookie", "indexedDB"]).describe("Storage type to read")
22470
+ }
22471
+ },
22472
+ async ({ type }) => {
22473
+ return withDevToolsAction(
22474
+ runtime2,
22475
+ tabManager,
22476
+ "devtools_get_storage",
22477
+ { type },
22478
+ async () => {
22479
+ const session = getOrCreateSession(tabManager);
22480
+ const data = await session.getStorage(type);
22481
+ const count = Object.keys(data.entries).length;
22482
+ if (count === 0) {
22483
+ return `No ${type} entries found for ${data.origin}`;
22484
+ }
22485
+ return JSON.stringify(data, null, 2);
22486
+ }
22487
+ );
22488
+ }
22489
+ );
22490
+ server.registerTool(
22491
+ "vessel_devtools_set_storage",
22492
+ {
22493
+ title: "DevTools: Set Storage",
22494
+ description: "Set or remove a key in localStorage or sessionStorage for the active tab. This is a dangerous action that modifies page state.",
22495
+ inputSchema: {
22496
+ type: zod.z.enum(["localStorage", "sessionStorage"]).describe("Storage type to modify"),
22497
+ key: zod.z.string().describe("Storage key"),
22498
+ value: zod.z.string().nullable().describe("Value to set, or null to remove the key")
22499
+ }
22500
+ },
22501
+ async ({ type, key: key2, value }) => {
22502
+ return withDevToolsAction(
22503
+ runtime2,
22504
+ tabManager,
22505
+ "devtools_set_storage",
22506
+ { type, key: key2, value: value ? value.slice(0, 100) : null },
22507
+ async () => {
22508
+ const session = getOrCreateSession(tabManager);
22509
+ return session.setStorage(type, key2, value);
22510
+ }
22511
+ );
22512
+ }
22513
+ );
22514
+ server.registerTool(
22515
+ "vessel_devtools_performance",
22516
+ {
22517
+ title: "DevTools: Performance Snapshot",
22518
+ description: "Get a performance snapshot for the active tab including navigation timing, paint metrics, memory usage, and resource loading statistics."
22519
+ },
22520
+ async () => {
22521
+ return withDevToolsAction(
22522
+ runtime2,
22523
+ tabManager,
22524
+ "devtools_performance",
22525
+ {},
22526
+ async () => {
22527
+ const session = getOrCreateSession(tabManager);
22528
+ const snapshot2 = await session.getPerformanceSnapshot();
22529
+ return JSON.stringify(snapshot2, null, 2);
22530
+ }
22531
+ );
22532
+ }
22533
+ );
22534
+ server.registerTool(
22535
+ "vessel_devtools_get_errors",
22536
+ {
22537
+ title: "DevTools: Get Errors",
22538
+ description: "Get captured JavaScript errors and unhandled promise rejections from the active tab. Automatically starts capturing on first use.",
22539
+ inputSchema: {
22540
+ type: zod.z.enum(["exception", "unhandled-rejection"]).optional().describe("Filter by error type"),
22541
+ limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)")
22542
+ }
22543
+ },
22544
+ async ({ type, limit }) => {
22545
+ return withDevToolsAction(
22546
+ runtime2,
22547
+ tabManager,
22548
+ "devtools_get_errors",
22549
+ { type, limit },
22550
+ async () => {
22551
+ const session = getOrCreateSession(tabManager);
22552
+ await session.ensureErrorCapture();
22553
+ const entries = session.getErrors({ type, limit });
22554
+ if (entries.length === 0) {
22555
+ return "No errors captured yet. Error monitoring is now active — exceptions and unhandled rejections will be captured as they occur.";
22556
+ }
22557
+ return JSON.stringify(entries, null, 2);
22558
+ }
22559
+ );
22560
+ }
22561
+ );
22562
+ server.registerTool(
22563
+ "vessel_devtools_clear_errors",
22564
+ {
22565
+ title: "DevTools: Clear Errors",
22566
+ description: "Clear the captured error buffer for the active tab."
22567
+ },
22568
+ async () => {
22569
+ return withDevToolsAction(
22570
+ runtime2,
22571
+ tabManager,
22572
+ "devtools_clear_errors",
22573
+ {},
22574
+ async () => {
22575
+ const session = getOrCreateSession(tabManager);
22576
+ const count = session.clearErrors();
22577
+ return `Cleared ${count} error entries.`;
22578
+ }
22579
+ );
22580
+ }
22581
+ );
22582
+ }
22583
+ const DEFAULT_PAGE_FOLDER = "Vessel/Pages";
22584
+ const DEFAULT_NOTE_FOLDER = "Vessel/Research";
22585
+ const DEFAULT_BOOKMARK_FOLDER = "Vessel/Bookmarks";
22586
+ const PAGE_CONTENT_LIMIT = 6e3;
22587
+ const DEFAULT_LIST_LIMIT = 50;
22588
+ const DEFAULT_SEARCH_LIMIT = 20;
22589
+ const { access: access$1, mkdir: mkdir$1, readFile: readFile$1, readdir: readdir$1, stat, writeFile: writeFile$1 } = fs$1.promises;
22590
+ function getVaultRoot() {
22591
+ const configured = loadSettings().obsidianVaultPath.trim();
22592
+ if (!configured) {
22593
+ throw new Error(
22594
+ "Obsidian not configured. Set vault path in Vessel settings to use memory capture."
22595
+ );
22596
+ }
22597
+ return path$1.resolve(configured);
22598
+ }
22599
+ function assertInsideVault(targetPath, vaultRoot) {
22600
+ const resolved = path$1.resolve(targetPath);
22601
+ const relative = path$1.relative(vaultRoot, resolved);
22602
+ if (relative.startsWith("..") || path$1.isAbsolute(relative)) {
22603
+ throw new Error("Resolved note path is outside the configured vault.");
22604
+ }
22605
+ return resolved;
22606
+ }
22607
+ function normalizeFolder(folder, fallback) {
22608
+ const raw = (folder?.trim() || fallback).replace(/\\/g, "/");
22609
+ if (!raw) return fallback;
22610
+ if (path$1.isAbsolute(raw)) {
22611
+ throw new Error("Vault note folders must be relative to the vault root.");
22612
+ }
22613
+ const segments = raw.split("/").filter(Boolean);
22614
+ if (segments.some((segment) => segment === "." || segment === "..")) {
22615
+ throw new Error("Vault note folders cannot traverse outside the vault.");
22616
+ }
22617
+ return segments.join(path$1.sep);
22618
+ }
22619
+ function normalizeNotePath(notePath) {
22620
+ const raw = notePath.trim().replace(/\\/g, "/");
22621
+ if (!raw) {
22622
+ throw new Error("A note path is required.");
22623
+ }
22624
+ if (path$1.isAbsolute(raw)) {
22625
+ throw new Error("Note paths must be relative to the vault root.");
22626
+ }
22627
+ const segments = raw.split("/").filter(Boolean);
22628
+ if (segments.some((segment) => segment === "." || segment === "..")) {
22629
+ throw new Error("Note paths cannot traverse outside the vault.");
22630
+ }
22631
+ const normalized = segments.join(path$1.sep);
22632
+ return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
22633
+ }
22634
+ function escapeYaml(value) {
22635
+ return JSON.stringify(value);
22636
+ }
22637
+ function renderFrontmatter(data) {
22638
+ const lines = ["---"];
22639
+ for (const [key2, value] of Object.entries(data)) {
22640
+ if (value == null) continue;
22641
+ if (Array.isArray(value)) {
22642
+ if (value.length === 0) continue;
22643
+ lines.push(`${key2}:`);
22644
+ for (const item of value) {
22645
+ lines.push(` - ${escapeYaml(item)}`);
22646
+ }
22647
+ continue;
22648
+ }
22649
+ lines.push(`${key2}: ${escapeYaml(value)}`);
22650
+ }
22651
+ lines.push("---", "");
22652
+ return lines.join("\n");
22653
+ }
22654
+ function slugify(value) {
22655
+ const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
22656
+ return normalized || "note";
22657
+ }
22658
+ async function pathExists$1(filePath2) {
22659
+ try {
22660
+ await access$1(filePath2);
22661
+ return true;
22662
+ } catch {
22663
+ return false;
22664
+ }
22665
+ }
22666
+ async function buildUniqueNotePath(dir, title) {
22667
+ const datePrefix = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22668
+ const slug = slugify(title);
22669
+ const base = `${datePrefix}-${slug}`;
22670
+ let candidate = `${base}.md`;
22671
+ let counter = 2;
22672
+ while (await pathExists$1(path$1.join(dir, candidate))) {
22673
+ candidate = `${base}-${counter}.md`;
22674
+ counter += 1;
22675
+ }
22676
+ return path$1.join(dir, candidate);
22677
+ }
22678
+ function trimContent(content, limit = PAGE_CONTENT_LIMIT) {
22679
+ const cleaned = content.trim();
22680
+ if (cleaned.length <= limit) return cleaned;
22681
+ return `${cleaned.slice(0, limit)}
22682
+
22683
+ [Truncated]`;
22684
+ }
22685
+ function parseFrontmatter(content) {
22686
+ if (!content.startsWith("---\n")) {
22687
+ return { body: content, tags: [] };
22688
+ }
22689
+ const closingIndex = content.indexOf("\n---\n", 4);
22690
+ if (closingIndex === -1) {
22691
+ return { body: content, tags: [] };
22692
+ }
22693
+ const raw = content.slice(4, closingIndex);
22694
+ const body = content.slice(closingIndex + 5);
22695
+ const lines = raw.split("\n");
22696
+ const result = { tags: [] };
22697
+ let activeArrayKey = "";
22698
+ for (const line of lines) {
22699
+ const trimmed = line.trim();
22700
+ if (!trimmed) continue;
22701
+ if (trimmed.startsWith("- ") && activeArrayKey === "tags") {
22702
+ result.tags.push(
22703
+ trimmed.slice(2).trim().replace(/^["']|["']$/g, "")
22704
+ );
22705
+ continue;
22706
+ }
22707
+ activeArrayKey = "";
22708
+ const separatorIndex = trimmed.indexOf(":");
22709
+ if (separatorIndex === -1) continue;
22710
+ const key2 = trimmed.slice(0, separatorIndex).trim();
22711
+ const value = trimmed.slice(separatorIndex + 1).trim();
22712
+ if (key2 === "title" && value) {
22713
+ result.title = value.replace(/^["']|["']$/g, "");
22714
+ } else if (key2 === "tags") {
22715
+ activeArrayKey = "tags";
22716
+ if (value.startsWith("[") && value.endsWith("]")) {
22717
+ const inline = value.slice(1, -1).split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
22718
+ result.tags.push(...inline);
22719
+ activeArrayKey = "";
22799
22720
  }
22800
- },
22801
- async ({ expression }) => {
22802
- return withDevToolsAction(
22803
- runtime2,
22804
- tabManager,
22805
- "devtools_execute_js",
22806
- { expression: expression.slice(0, 200) },
22807
- async () => {
22808
- const session = getOrCreateSession(tabManager);
22809
- const result = await session.executeJs(expression);
22810
- const parts = [`[${result.type}] ${result.result}`];
22811
- if (result.exceptionDetails) {
22812
- parts.push(`
22813
- Exception: ${result.exceptionDetails}`);
22814
- }
22815
- return parts.join("");
22816
- }
22817
- );
22818
22721
  }
22819
- );
22820
- server.registerTool(
22821
- "vessel_devtools_get_storage",
22822
- {
22823
- title: "DevTools: Get Storage",
22824
- description: "Read browser storage for the active tab's origin. Supports localStorage, sessionStorage, cookies, and IndexedDB database listing.",
22825
- inputSchema: {
22826
- type: zod.z.enum(["localStorage", "sessionStorage", "cookie", "indexedDB"]).describe("Storage type to read")
22722
+ }
22723
+ return { body, title: result.title, tags: result.tags };
22724
+ }
22725
+ async function collectMarkdownFiles(dir) {
22726
+ const entries = await readdir$1(dir, { withFileTypes: true });
22727
+ const nestedFiles = await Promise.all(
22728
+ entries.map(async (entry) => {
22729
+ const absolutePath = path$1.join(dir, entry.name);
22730
+ if (entry.isDirectory()) {
22731
+ return collectMarkdownFiles(absolutePath);
22827
22732
  }
22828
- },
22829
- async ({ type }) => {
22830
- return withDevToolsAction(
22831
- runtime2,
22832
- tabManager,
22833
- "devtools_get_storage",
22834
- { type },
22835
- async () => {
22836
- const session = getOrCreateSession(tabManager);
22837
- const data = await session.getStorage(type);
22838
- const count = Object.keys(data.entries).length;
22839
- if (count === 0) {
22840
- return `No ${type} entries found for ${data.origin}`;
22841
- }
22842
- return JSON.stringify(data, null, 2);
22843
- }
22844
- );
22845
- }
22846
- );
22847
- server.registerTool(
22848
- "vessel_devtools_set_storage",
22849
- {
22850
- title: "DevTools: Set Storage",
22851
- description: "Set or remove a key in localStorage or sessionStorage for the active tab. This is a dangerous action that modifies page state.",
22852
- inputSchema: {
22853
- type: zod.z.enum(["localStorage", "sessionStorage"]).describe("Storage type to modify"),
22854
- key: zod.z.string().describe("Storage key"),
22855
- value: zod.z.string().nullable().describe("Value to set, or null to remove the key")
22733
+ if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
22734
+ return [absolutePath];
22856
22735
  }
22857
- },
22858
- async ({ type, key: key2, value }) => {
22859
- return withDevToolsAction(
22860
- runtime2,
22861
- tabManager,
22862
- "devtools_set_storage",
22863
- { type, key: key2, value: value ? value.slice(0, 100) : null },
22864
- async () => {
22865
- const session = getOrCreateSession(tabManager);
22866
- return session.setStorage(type, key2, value);
22867
- }
22868
- );
22869
- }
22736
+ return [];
22737
+ })
22870
22738
  );
22871
- server.registerTool(
22872
- "vessel_devtools_performance",
22873
- {
22874
- title: "DevTools: Performance Snapshot",
22875
- description: "Get a performance snapshot for the active tab including navigation timing, paint metrics, memory usage, and resource loading statistics."
22876
- },
22877
- async () => {
22878
- return withDevToolsAction(
22879
- runtime2,
22880
- tabManager,
22881
- "devtools_performance",
22882
- {},
22883
- async () => {
22884
- const session = getOrCreateSession(tabManager);
22885
- const snapshot2 = await session.getPerformanceSnapshot();
22886
- return JSON.stringify(snapshot2, null, 2);
22887
- }
22888
- );
22889
- }
22739
+ return nestedFiles.flat();
22740
+ }
22741
+ async function toSummary(absolutePath, vaultRoot, raw) {
22742
+ const stats = await stat(absolutePath);
22743
+ const relativePath = path$1.relative(vaultRoot, absolutePath).split(path$1.sep).join("/");
22744
+ const noteContent = raw ?? await readFile$1(absolutePath, "utf-8");
22745
+ const parsed = parseFrontmatter(noteContent);
22746
+ const headingMatch = parsed.body.match(/^#\s+(.+)$/m);
22747
+ const title = parsed.title || headingMatch?.[1]?.trim() || path$1.basename(absolutePath, ".md");
22748
+ return {
22749
+ title,
22750
+ absolutePath,
22751
+ relativePath,
22752
+ modifiedAt: stats.mtime.toISOString(),
22753
+ tags: parsed.tags
22754
+ };
22755
+ }
22756
+ function renderBookmarkLinkBlock(bookmark, note) {
22757
+ const lines = [
22758
+ "## Linked Bookmark",
22759
+ "",
22760
+ `- Bookmark ID: \`${bookmark.id}\``,
22761
+ `- Title: ${bookmark.title || bookmark.url}`,
22762
+ `- URL: [${bookmark.url}](${bookmark.url})`,
22763
+ `- Saved At: ${bookmark.savedAt}`
22764
+ ];
22765
+ if (note?.trim()) {
22766
+ lines.push("", "### Context", "", note.trim());
22767
+ }
22768
+ return `${lines.join("\n")}
22769
+ `;
22770
+ }
22771
+ async function writeMemoryNote({
22772
+ title,
22773
+ body,
22774
+ folder,
22775
+ tags = [],
22776
+ frontmatter = {}
22777
+ }) {
22778
+ const vaultRoot = getVaultRoot();
22779
+ const relativeFolder = normalizeFolder(folder, DEFAULT_NOTE_FOLDER);
22780
+ const targetDir = path$1.join(vaultRoot, relativeFolder);
22781
+ await mkdir$1(targetDir, { recursive: true });
22782
+ const absolutePath = await buildUniqueNotePath(targetDir, title);
22783
+ const relativePath = path$1.relative(vaultRoot, absolutePath);
22784
+ const content = [
22785
+ renderFrontmatter({
22786
+ title,
22787
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
22788
+ tags,
22789
+ ...frontmatter
22790
+ }),
22791
+ body.trim(),
22792
+ ""
22793
+ ].join("\n");
22794
+ await writeFile$1(absolutePath, content, "utf-8");
22795
+ return {
22796
+ title,
22797
+ absolutePath,
22798
+ relativePath: relativePath.split(path$1.sep).join("/")
22799
+ };
22800
+ }
22801
+ async function appendToMemoryNote({
22802
+ notePath,
22803
+ content,
22804
+ heading
22805
+ }) {
22806
+ const vaultRoot = getVaultRoot();
22807
+ const relativePath = normalizeNotePath(notePath);
22808
+ const absolutePath = assertInsideVault(
22809
+ path$1.join(vaultRoot, relativePath),
22810
+ vaultRoot
22890
22811
  );
22891
- server.registerTool(
22892
- "vessel_devtools_get_errors",
22893
- {
22894
- title: "DevTools: Get Errors",
22895
- description: "Get captured JavaScript errors and unhandled promise rejections from the active tab. Automatically starts capturing on first use.",
22896
- inputSchema: {
22897
- type: zod.z.enum(["exception", "unhandled-rejection"]).optional().describe("Filter by error type"),
22898
- limit: zod.z.number().optional().describe("Maximum number of entries to return (default: all)")
22899
- }
22900
- },
22901
- async ({ type, limit }) => {
22902
- return withDevToolsAction(
22903
- runtime2,
22904
- tabManager,
22905
- "devtools_get_errors",
22906
- { type, limit },
22907
- async () => {
22908
- const session = getOrCreateSession(tabManager);
22909
- await session.ensureErrorCapture();
22910
- const entries = session.getErrors({ type, limit });
22911
- if (entries.length === 0) {
22912
- return "No errors captured yet. Error monitoring is now active — exceptions and unhandled rejections will be captured as they occur.";
22913
- }
22914
- return JSON.stringify(entries, null, 2);
22915
- }
22812
+ if (!await pathExists$1(absolutePath)) {
22813
+ throw new Error(
22814
+ `Memory note not found: ${relativePath.split(path$1.sep).join("/")}`
22815
+ );
22816
+ }
22817
+ const current = (await readFile$1(absolutePath, "utf-8")).trimEnd();
22818
+ const nextParts = [current, ""];
22819
+ if (heading?.trim()) {
22820
+ nextParts.push(`## ${heading.trim()}`, "");
22821
+ }
22822
+ nextParts.push(content.trim(), "");
22823
+ await writeFile$1(absolutePath, nextParts.join("\n"), "utf-8");
22824
+ return {
22825
+ title: path$1.basename(absolutePath, ".md"),
22826
+ absolutePath,
22827
+ relativePath: relativePath.split(path$1.sep).join("/")
22828
+ };
22829
+ }
22830
+ async function listMemoryNotes({
22831
+ folder,
22832
+ limit = DEFAULT_LIST_LIMIT
22833
+ } = {}) {
22834
+ const vaultRoot = getVaultRoot();
22835
+ const relativeFolder = normalizeFolder(folder, "");
22836
+ const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
22837
+ if (!await pathExists$1(targetDir)) {
22838
+ return [];
22839
+ }
22840
+ const notes = await Promise.all(
22841
+ (await collectMarkdownFiles(targetDir)).map(
22842
+ (absolutePath) => toSummary(absolutePath, vaultRoot)
22843
+ )
22844
+ );
22845
+ return notes.sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
22846
+ }
22847
+ async function searchMemoryNotes({
22848
+ query,
22849
+ folder,
22850
+ tags = [],
22851
+ limit = DEFAULT_SEARCH_LIMIT
22852
+ }) {
22853
+ const loweredQuery = query.trim().toLowerCase();
22854
+ if (!loweredQuery) {
22855
+ throw new Error("A non-empty memory search query is required.");
22856
+ }
22857
+ const vaultRoot = getVaultRoot();
22858
+ const relativeFolder = normalizeFolder(folder, "");
22859
+ const targetDir = relativeFolder ? path$1.join(vaultRoot, relativeFolder) : vaultRoot;
22860
+ if (!await pathExists$1(targetDir)) {
22861
+ return [];
22862
+ }
22863
+ const loweredTags = tags.map((tag) => tag.trim().toLowerCase()).filter(Boolean);
22864
+ const matches = await Promise.all(
22865
+ (await collectMarkdownFiles(targetDir)).map(async (absolutePath) => {
22866
+ const raw = await readFile$1(absolutePath, "utf-8");
22867
+ const parsed = parseFrontmatter(raw);
22868
+ const summary = await toSummary(absolutePath, vaultRoot, raw);
22869
+ const haystack = `${summary.title}
22870
+ ${summary.relativePath}
22871
+ ${parsed.body}`.toLowerCase();
22872
+ const hasQuery = haystack.includes(loweredQuery);
22873
+ const hasTags = loweredTags.length === 0 || loweredTags.every(
22874
+ (tag) => summary.tags.some((noteTag) => noteTag.toLowerCase() === tag)
22916
22875
  );
22917
- }
22876
+ return hasQuery && hasTags ? summary : null;
22877
+ })
22918
22878
  );
22919
- server.registerTool(
22920
- "vessel_devtools_clear_errors",
22921
- {
22922
- title: "DevTools: Clear Errors",
22923
- description: "Clear the captured error buffer for the active tab."
22924
- },
22925
- async () => {
22926
- return withDevToolsAction(
22927
- runtime2,
22928
- tabManager,
22929
- "devtools_clear_errors",
22930
- {},
22931
- async () => {
22932
- const session = getOrCreateSession(tabManager);
22933
- const count = session.clearErrors();
22934
- return `Cleared ${count} error entries.`;
22935
- }
22936
- );
22879
+ return matches.filter((item) => item !== null).sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt)).slice(0, Math.max(1, limit));
22880
+ }
22881
+ async function capturePageToVault({
22882
+ page,
22883
+ title,
22884
+ folder,
22885
+ summary,
22886
+ note,
22887
+ tags = []
22888
+ }) {
22889
+ const noteTitle = title?.trim() || page.title.trim() || page.url;
22890
+ const bodyLines = [
22891
+ `# ${noteTitle}`,
22892
+ "",
22893
+ `Source: [${page.title || page.url}](${page.url})`,
22894
+ `Captured: ${(/* @__PURE__ */ new Date()).toISOString()}`
22895
+ ];
22896
+ if (page.byline) {
22897
+ bodyLines.push(`Byline: ${page.byline}`);
22898
+ }
22899
+ bodyLines.push("");
22900
+ if (summary?.trim()) {
22901
+ bodyLines.push("## Summary", "", summary.trim(), "");
22902
+ }
22903
+ if (note?.trim()) {
22904
+ bodyLines.push("## Research Note", "", note.trim(), "");
22905
+ }
22906
+ if (page.excerpt.trim()) {
22907
+ bodyLines.push("## Excerpt", "", page.excerpt.trim(), "");
22908
+ }
22909
+ const snapshot2 = trimContent(page.content);
22910
+ if (snapshot2) {
22911
+ bodyLines.push("## Page Snapshot", "", snapshot2, "");
22912
+ }
22913
+ return await writeMemoryNote({
22914
+ title: noteTitle,
22915
+ body: bodyLines.join("\n"),
22916
+ folder: folder || DEFAULT_PAGE_FOLDER,
22917
+ tags,
22918
+ frontmatter: {
22919
+ source_url: page.url,
22920
+ source_title: page.title || page.url
22937
22921
  }
22938
- );
22922
+ });
22923
+ }
22924
+ async function linkBookmarkToMemory({
22925
+ bookmark,
22926
+ notePath,
22927
+ title,
22928
+ folder,
22929
+ note,
22930
+ tags = []
22931
+ }) {
22932
+ if (notePath?.trim()) {
22933
+ return await appendToMemoryNote({
22934
+ notePath,
22935
+ heading: "Linked Bookmark",
22936
+ content: [
22937
+ `- Bookmark ID: \`${bookmark.id}\``,
22938
+ `- Title: ${bookmark.title || bookmark.url}`,
22939
+ `- URL: [${bookmark.url}](${bookmark.url})`,
22940
+ `- Saved At: ${bookmark.savedAt}`,
22941
+ note?.trim() ? `- Note: ${note.trim()}` : ""
22942
+ ].filter(Boolean).join("\n")
22943
+ });
22944
+ }
22945
+ const noteTitle = title?.trim() || bookmark.title || bookmark.url;
22946
+ return await writeMemoryNote({
22947
+ title: noteTitle,
22948
+ body: renderBookmarkLinkBlock(bookmark, note),
22949
+ folder: folder || DEFAULT_BOOKMARK_FOLDER,
22950
+ tags,
22951
+ frontmatter: {
22952
+ bookmark_id: bookmark.id,
22953
+ source_url: bookmark.url,
22954
+ source_title: bookmark.title || bookmark.url
22955
+ }
22956
+ });
22939
22957
  }
22940
22958
  const logger$f = createLogger("MCP");
22941
22959
  function asTextResponse(text) {
@@ -23641,106 +23659,257 @@ function registerBookmarkTools(server, tabManager, runtime2) {
23641
23659
  }
23642
23660
  );
23643
23661
  server.registerTool(
23644
- "folder_remove",
23662
+ "folder_remove",
23663
+ {
23664
+ title: "Remove Bookmark Folder",
23665
+ description: "Remove a folder. By default bookmarks in it are moved to Unsorted. Set delete_contents to true to delete them with the folder.",
23666
+ inputSchema: {
23667
+ folder_id: zod.z.string().describe("ID of the folder to remove"),
23668
+ delete_contents: zod.z.boolean().optional().default(false).describe(
23669
+ "If true, delete all bookmarks in the folder. If false (default), move them to Unsorted."
23670
+ )
23671
+ }
23672
+ },
23673
+ async ({ folder_id, delete_contents }) => {
23674
+ return withAction(
23675
+ runtime2,
23676
+ tabManager,
23677
+ "remove_bookmark_folder",
23678
+ { folder_id, delete_contents },
23679
+ async () => {
23680
+ const removed = removeFolder(
23681
+ folder_id,
23682
+ delete_contents
23683
+ );
23684
+ if (!removed) return `Folder ${folder_id} not found`;
23685
+ return composeFolderAwareResponse$1(
23686
+ delete_contents ? `Removed folder ${folder_id} and deleted its bookmarks.` : `Removed folder ${folder_id}. Bookmarks moved to Unsorted.`
23687
+ );
23688
+ }
23689
+ );
23690
+ }
23691
+ );
23692
+ server.registerTool(
23693
+ "folder_rename",
23694
+ {
23695
+ title: "Rename Bookmark Folder",
23696
+ description: "Rename an existing bookmark folder.",
23697
+ inputSchema: {
23698
+ folder_id: zod.z.string().describe("ID of the folder to rename"),
23699
+ new_name: zod.z.string().describe("New name for the folder"),
23700
+ summary: zod.z.string().optional().describe("Optional one-sentence summary for the folder")
23701
+ }
23702
+ },
23703
+ async ({ folder_id, new_name, summary }) => {
23704
+ return withAction(
23705
+ runtime2,
23706
+ tabManager,
23707
+ "rename_bookmark_folder",
23708
+ { folder_id, new_name, summary },
23709
+ async () => {
23710
+ const existing = findFolderByName(new_name);
23711
+ if (existing && existing.id !== folder_id) {
23712
+ return composeFolderAwareResponse$1(
23713
+ `Folder "${existing.name}" already exists (id=${existing.id})`
23714
+ );
23715
+ }
23716
+ const folder = renameFolder(
23717
+ folder_id,
23718
+ new_name,
23719
+ summary
23720
+ );
23721
+ return folder ? composeFolderAwareResponse$1(`Renamed folder to "${folder.name}"`) : `Folder ${folder_id} not found`;
23722
+ }
23723
+ );
23724
+ }
23725
+ );
23726
+ server.registerTool(
23727
+ "memory_link_bookmark",
23728
+ {
23729
+ title: "Link Bookmark To Memory",
23730
+ description: "Create a note for a bookmark or append bookmark details into an existing memory note.",
23731
+ inputSchema: {
23732
+ bookmark_id: zod.z.string().describe("Bookmark ID to link"),
23733
+ note_path: zod.z.string().optional().describe("Existing relative note path to append into"),
23734
+ title: zod.z.string().optional().describe("Optional title when creating a new note"),
23735
+ folder: zod.z.string().optional().describe("Relative folder when creating a new note"),
23736
+ note: zod.z.string().optional().describe(
23737
+ "Optional rationale or breadcrumb to store with the bookmark"
23738
+ ),
23739
+ tags: zod.z.array(zod.z.string()).optional().describe("Optional tags when creating a new note")
23740
+ }
23741
+ },
23742
+ async ({ bookmark_id, note_path, title, folder, note, tags }) => {
23743
+ return withAction(
23744
+ runtime2,
23745
+ tabManager,
23746
+ "memory_link_bookmark",
23747
+ { bookmark_id, note_path, title, folder, tags },
23748
+ async () => {
23749
+ const bookmark = getBookmark(bookmark_id);
23750
+ if (!bookmark) {
23751
+ return `Bookmark ${bookmark_id} not found`;
23752
+ }
23753
+ const saved = await linkBookmarkToMemory({
23754
+ bookmark,
23755
+ notePath: note_path,
23756
+ title,
23757
+ folder,
23758
+ note,
23759
+ tags
23760
+ });
23761
+ return `Linked bookmark "${bookmark.title}" to memory note ${saved.relativePath}`;
23762
+ }
23763
+ );
23764
+ }
23765
+ );
23766
+ }
23767
+ function registerMemoryTools(server, tabManager, runtime2) {
23768
+ server.registerTool(
23769
+ "memory_note_create",
23770
+ {
23771
+ title: "Create Memory Note",
23772
+ description: "Write a markdown note into the configured Obsidian vault for research notes, breadcrumbs, or synthesis.",
23773
+ inputSchema: {
23774
+ title: zod.z.string().describe("Title of the note"),
23775
+ body: zod.z.string().describe("Markdown body for the note"),
23776
+ folder: zod.z.string().optional().describe(
23777
+ "Relative folder inside the vault (default: Vessel/Research)"
23778
+ ),
23779
+ tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
23780
+ }
23781
+ },
23782
+ async ({ title, body, folder, tags }) => {
23783
+ return withAction(
23784
+ runtime2,
23785
+ tabManager,
23786
+ "memory_note_create",
23787
+ { title, folder, tags },
23788
+ async () => {
23789
+ const saved = await writeMemoryNote({ title, body, folder, tags });
23790
+ return `Saved memory note "${saved.title}" to ${saved.relativePath}`;
23791
+ }
23792
+ );
23793
+ }
23794
+ );
23795
+ server.registerTool(
23796
+ "memory_append",
23797
+ {
23798
+ title: "Append Memory Note",
23799
+ description: "Append markdown content to an existing note in the configured Obsidian vault.",
23800
+ inputSchema: {
23801
+ note_path: zod.z.string().describe("Relative path to an existing note inside the vault"),
23802
+ content: zod.z.string().describe("Markdown content to append"),
23803
+ heading: zod.z.string().optional().describe("Optional section heading to add before the content")
23804
+ }
23805
+ },
23806
+ async ({ note_path, content, heading }) => {
23807
+ return withAction(
23808
+ runtime2,
23809
+ tabManager,
23810
+ "memory_note_append",
23811
+ { note_path, heading },
23812
+ async () => {
23813
+ const saved = await appendToMemoryNote({
23814
+ notePath: note_path,
23815
+ content,
23816
+ heading
23817
+ });
23818
+ return `Appended memory note at ${saved.relativePath}`;
23819
+ }
23820
+ );
23821
+ }
23822
+ );
23823
+ server.registerTool(
23824
+ "memory_list",
23645
23825
  {
23646
- title: "Remove Bookmark Folder",
23647
- description: "Remove a folder. By default bookmarks in it are moved to Unsorted. Set delete_contents to true to delete them with the folder.",
23826
+ title: "List Memory Notes",
23827
+ description: "List recent markdown notes in the configured Obsidian vault.",
23648
23828
  inputSchema: {
23649
- folder_id: zod.z.string().describe("ID of the folder to remove"),
23650
- delete_contents: zod.z.boolean().optional().default(false).describe(
23651
- "If true, delete all bookmarks in the folder. If false (default), move them to Unsorted."
23652
- )
23829
+ folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
23830
+ limit: zod.z.number().int().positive().max(200).optional().describe("Maximum number of notes to return")
23653
23831
  }
23654
23832
  },
23655
- async ({ folder_id, delete_contents }) => {
23833
+ async ({ folder, limit }) => {
23656
23834
  return withAction(
23657
23835
  runtime2,
23658
23836
  tabManager,
23659
- "remove_bookmark_folder",
23660
- { folder_id, delete_contents },
23837
+ "memory_note_list",
23838
+ { folder, limit },
23661
23839
  async () => {
23662
- const removed = removeFolder(
23663
- folder_id,
23664
- delete_contents
23665
- );
23666
- if (!removed) return `Folder ${folder_id} not found`;
23667
- return composeFolderAwareResponse$1(
23668
- delete_contents ? `Removed folder ${folder_id} and deleted its bookmarks.` : `Removed folder ${folder_id}. Bookmarks moved to Unsorted.`
23669
- );
23840
+ const notes = await listMemoryNotes({ folder, limit });
23841
+ if (notes.length === 0) {
23842
+ return "No memory notes found.";
23843
+ }
23844
+ return notes.map(
23845
+ (note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
23846
+ ).join("\n");
23670
23847
  }
23671
23848
  );
23672
23849
  }
23673
23850
  );
23674
23851
  server.registerTool(
23675
- "folder_rename",
23852
+ "memory_search",
23676
23853
  {
23677
- title: "Rename Bookmark Folder",
23678
- description: "Rename an existing bookmark folder.",
23854
+ title: "Search Memory Notes",
23855
+ description: "Search markdown notes in the configured Obsidian vault by title, path, body, and optional tags.",
23679
23856
  inputSchema: {
23680
- folder_id: zod.z.string().describe("ID of the folder to rename"),
23681
- new_name: zod.z.string().describe("New name for the folder"),
23682
- summary: zod.z.string().optional().describe("Optional one-sentence summary for the folder")
23857
+ query: zod.z.string().describe("Search query"),
23858
+ folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
23859
+ tags: zod.z.array(zod.z.string()).optional().describe("Optional tags that matching notes must contain"),
23860
+ limit: zod.z.number().int().positive().max(100).optional().describe("Maximum number of matching notes to return")
23683
23861
  }
23684
23862
  },
23685
- async ({ folder_id, new_name, summary }) => {
23863
+ async ({ query, folder, tags, limit }) => {
23686
23864
  return withAction(
23687
23865
  runtime2,
23688
23866
  tabManager,
23689
- "rename_bookmark_folder",
23690
- { folder_id, new_name, summary },
23867
+ "memory_note_search",
23868
+ { query, folder, tags, limit },
23691
23869
  async () => {
23692
- const existing = findFolderByName(new_name);
23693
- if (existing && existing.id !== folder_id) {
23694
- return composeFolderAwareResponse$1(
23695
- `Folder "${existing.name}" already exists (id=${existing.id})`
23696
- );
23870
+ const notes = await searchMemoryNotes({ query, folder, tags, limit });
23871
+ if (notes.length === 0) {
23872
+ return `No memory notes matched "${query}".`;
23697
23873
  }
23698
- const folder = renameFolder(
23699
- folder_id,
23700
- new_name,
23701
- summary
23702
- );
23703
- return folder ? composeFolderAwareResponse$1(`Renamed folder to "${folder.name}"`) : `Folder ${folder_id} not found`;
23874
+ return notes.map(
23875
+ (note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
23876
+ ).join("\n");
23704
23877
  }
23705
23878
  );
23706
23879
  }
23707
23880
  );
23708
23881
  server.registerTool(
23709
- "memory_link_bookmark",
23882
+ "memory_page_capture",
23710
23883
  {
23711
- title: "Link Bookmark To Memory",
23712
- description: "Create a note for a bookmark or append bookmark details into an existing memory note.",
23884
+ title: "Capture Page To Memory",
23885
+ description: "Capture the current page into the configured Obsidian vault as a markdown note with URL, excerpt, and content snapshot.",
23713
23886
  inputSchema: {
23714
- bookmark_id: zod.z.string().describe("Bookmark ID to link"),
23715
- note_path: zod.z.string().optional().describe("Existing relative note path to append into"),
23716
- title: zod.z.string().optional().describe("Optional title when creating a new note"),
23717
- folder: zod.z.string().optional().describe("Relative folder when creating a new note"),
23718
- note: zod.z.string().optional().describe(
23719
- "Optional rationale or breadcrumb to store with the bookmark"
23720
- ),
23721
- tags: zod.z.array(zod.z.string()).optional().describe("Optional tags when creating a new note")
23887
+ title: zod.z.string().optional().describe("Optional note title override"),
23888
+ folder: zod.z.string().optional().describe("Relative folder inside the vault (default: Vessel/Pages)"),
23889
+ summary: zod.z.string().optional().describe("Optional summary written into the note"),
23890
+ note: zod.z.string().optional().describe("Optional research note or breadcrumb"),
23891
+ tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
23722
23892
  }
23723
23893
  },
23724
- async ({ bookmark_id, note_path, title, folder, note, tags }) => {
23894
+ async ({ title, folder, summary, note, tags }) => {
23895
+ const tab = tabManager.getActiveTab();
23896
+ if (!tab) return asNoActiveTabResponse();
23725
23897
  return withAction(
23726
23898
  runtime2,
23727
23899
  tabManager,
23728
- "memory_link_bookmark",
23729
- { bookmark_id, note_path, title, folder, tags },
23900
+ "memory_page_capture",
23901
+ { title, folder, tags },
23730
23902
  async () => {
23731
- const bookmark = getBookmark(bookmark_id);
23732
- if (!bookmark) {
23733
- return `Bookmark ${bookmark_id} not found`;
23734
- }
23735
- const saved = linkBookmarkToMemory({
23736
- bookmark,
23737
- notePath: note_path,
23903
+ const page = await extractContent$1(tab.view.webContents);
23904
+ const saved = await capturePageToVault({
23905
+ page,
23738
23906
  title,
23739
23907
  folder,
23908
+ summary,
23740
23909
  note,
23741
23910
  tags
23742
23911
  });
23743
- return `Linked bookmark "${bookmark.title}" to memory note ${saved.relativePath}`;
23912
+ return `Captured page "${saved.title}" to ${saved.relativePath}`;
23744
23913
  }
23745
23914
  );
23746
23915
  }
@@ -25805,155 +25974,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
25805
25974
  );
25806
25975
  registerBookmarkTools(server, tabManager, runtime2);
25807
25976
  registerSessionTools(server, tabManager, runtime2);
25808
- server.registerTool(
25809
- "memory_note_create",
25810
- {
25811
- title: "Create Memory Note",
25812
- description: "Write a markdown note into the configured Obsidian vault for research notes, breadcrumbs, or synthesis.",
25813
- inputSchema: {
25814
- title: zod.z.string().describe("Title of the note"),
25815
- body: zod.z.string().describe("Markdown body for the note"),
25816
- folder: zod.z.string().optional().describe(
25817
- "Relative folder inside the vault (default: Vessel/Research)"
25818
- ),
25819
- tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
25820
- }
25821
- },
25822
- async ({ title, body, folder, tags }) => {
25823
- return withAction(
25824
- runtime2,
25825
- tabManager,
25826
- "memory_note_create",
25827
- { title, folder, tags },
25828
- async () => {
25829
- const saved = writeMemoryNote({ title, body, folder, tags });
25830
- return `Saved memory note "${saved.title}" to ${saved.relativePath}`;
25831
- }
25832
- );
25833
- }
25834
- );
25835
- server.registerTool(
25836
- "memory_append",
25837
- {
25838
- title: "Append Memory Note",
25839
- description: "Append markdown content to an existing note in the configured Obsidian vault.",
25840
- inputSchema: {
25841
- note_path: zod.z.string().describe("Relative path to an existing note inside the vault"),
25842
- content: zod.z.string().describe("Markdown content to append"),
25843
- heading: zod.z.string().optional().describe("Optional section heading to add before the content")
25844
- }
25845
- },
25846
- async ({ note_path, content, heading }) => {
25847
- return withAction(
25848
- runtime2,
25849
- tabManager,
25850
- "memory_note_append",
25851
- { note_path, heading },
25852
- async () => {
25853
- const saved = appendToMemoryNote({
25854
- notePath: note_path,
25855
- content,
25856
- heading
25857
- });
25858
- return `Appended memory note at ${saved.relativePath}`;
25859
- }
25860
- );
25861
- }
25862
- );
25863
- server.registerTool(
25864
- "memory_list",
25865
- {
25866
- title: "List Memory Notes",
25867
- description: "List recent markdown notes in the configured Obsidian vault.",
25868
- inputSchema: {
25869
- folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
25870
- limit: zod.z.number().int().positive().max(200).optional().describe("Maximum number of notes to return")
25871
- }
25872
- },
25873
- async ({ folder, limit }) => {
25874
- return withAction(
25875
- runtime2,
25876
- tabManager,
25877
- "memory_note_list",
25878
- { folder, limit },
25879
- async () => {
25880
- const notes = listMemoryNotes({ folder, limit });
25881
- if (notes.length === 0) {
25882
- return "No memory notes found.";
25883
- }
25884
- return notes.map(
25885
- (note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
25886
- ).join("\n");
25887
- }
25888
- );
25889
- }
25890
- );
25891
- server.registerTool(
25892
- "memory_search",
25893
- {
25894
- title: "Search Memory Notes",
25895
- description: "Search markdown notes in the configured Obsidian vault by title, path, body, and optional tags.",
25896
- inputSchema: {
25897
- query: zod.z.string().describe("Search query"),
25898
- folder: zod.z.string().optional().describe("Optional relative folder inside the vault"),
25899
- tags: zod.z.array(zod.z.string()).optional().describe("Optional tags that matching notes must contain"),
25900
- limit: zod.z.number().int().positive().max(100).optional().describe("Maximum number of matching notes to return")
25901
- }
25902
- },
25903
- async ({ query, folder, tags, limit }) => {
25904
- return withAction(
25905
- runtime2,
25906
- tabManager,
25907
- "memory_note_search",
25908
- { query, folder, tags, limit },
25909
- async () => {
25910
- const notes = searchMemoryNotes({ query, folder, tags, limit });
25911
- if (notes.length === 0) {
25912
- return `No memory notes matched "${query}".`;
25913
- }
25914
- return notes.map(
25915
- (note) => `- ${note.title} | path=${note.relativePath} | modified=${note.modifiedAt}${note.tags.length ? ` | tags=${note.tags.join(",")}` : ""}`
25916
- ).join("\n");
25917
- }
25918
- );
25919
- }
25920
- );
25921
- server.registerTool(
25922
- "memory_page_capture",
25923
- {
25924
- title: "Capture Page To Memory",
25925
- description: "Capture the current page into the configured Obsidian vault as a markdown note with URL, excerpt, and content snapshot.",
25926
- inputSchema: {
25927
- title: zod.z.string().optional().describe("Optional note title override"),
25928
- folder: zod.z.string().optional().describe("Relative folder inside the vault (default: Vessel/Pages)"),
25929
- summary: zod.z.string().optional().describe("Optional summary written into the note"),
25930
- note: zod.z.string().optional().describe("Optional research note or breadcrumb"),
25931
- tags: zod.z.array(zod.z.string()).optional().describe("Optional tags to store in frontmatter")
25932
- }
25933
- },
25934
- async ({ title, folder, summary, note, tags }) => {
25935
- const tab = tabManager.getActiveTab();
25936
- if (!tab) return asNoActiveTabResponse();
25937
- return withAction(
25938
- runtime2,
25939
- tabManager,
25940
- "memory_page_capture",
25941
- { title, folder, tags },
25942
- async () => {
25943
- const page = await extractContent$1(tab.view.webContents);
25944
- const saved = capturePageToVault({
25945
- page,
25946
- title,
25947
- folder,
25948
- summary,
25949
- note,
25950
- tags
25951
- });
25952
- return `Captured page "${saved.title}" to ${saved.relativePath}`;
25953
- }
25954
- );
25955
- }
25956
- );
25977
+ registerMemoryTools(server, tabManager, runtime2);
25957
25978
  server.registerTool(
25958
25979
  "flow_start",
25959
25980
  {
@@ -27632,15 +27653,21 @@ function isSafeAutomationKitId(id) {
27632
27653
  return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
27633
27654
  }
27634
27655
  const logger$9 = createLogger("KitRegistry");
27656
+ const { access, mkdir, readFile, readdir, unlink, writeFile } = fs$1.promises;
27635
27657
  function getUserKitsDir() {
27636
27658
  return path$1.join(electron.app.getPath("userData"), "kits");
27637
27659
  }
27638
- function ensureKitsDir() {
27639
- const dir = getUserKitsDir();
27640
- if (!fs$1.existsSync(dir)) {
27641
- fs$1.mkdirSync(dir, { recursive: true });
27660
+ async function pathExists(filePath2) {
27661
+ try {
27662
+ await access(filePath2);
27663
+ return true;
27664
+ } catch {
27665
+ return false;
27642
27666
  }
27643
27667
  }
27668
+ async function ensureKitsDir() {
27669
+ await mkdir(getUserKitsDir(), { recursive: true });
27670
+ }
27644
27671
  function getKitFilePath(id) {
27645
27672
  if (!isSafeAutomationKitId(id)) return null;
27646
27673
  const kitsDir = path$1.resolve(getUserKitsDir());
@@ -27652,12 +27679,12 @@ function isValidKit(value) {
27652
27679
  const k = value;
27653
27680
  return typeof k.id === "string" && isSafeAutomationKitId(k.id) && typeof k.name === "string" && k.name.length > 0 && typeof k.description === "string" && typeof k.category === "string" && VALID_KIT_CATEGORIES.has(k.category) && typeof k.icon === "string" && typeof k.promptTemplate === "string" && k.promptTemplate.length > 0 && Array.isArray(k.inputs);
27654
27681
  }
27655
- function getInstalledKits() {
27656
- ensureKitsDir();
27682
+ async function getInstalledKits() {
27683
+ await ensureKitsDir();
27657
27684
  const dir = getUserKitsDir();
27658
27685
  let files;
27659
27686
  try {
27660
- files = fs$1.readdirSync(dir).filter((f) => f.endsWith(".kit.json"));
27687
+ files = (await readdir(dir)).filter((f) => f.endsWith(".kit.json"));
27661
27688
  } catch (err) {
27662
27689
  logger$9.warn("Failed to read kit directory:", err);
27663
27690
  return [];
@@ -27665,7 +27692,7 @@ function getInstalledKits() {
27665
27692
  const kits = [];
27666
27693
  for (const file of files) {
27667
27694
  try {
27668
- const raw = fs$1.readFileSync(path$1.join(dir, file), "utf-8");
27695
+ const raw = await readFile(path$1.join(dir, file), "utf-8");
27669
27696
  const parsed = JSON.parse(raw);
27670
27697
  if (isValidKit(parsed)) {
27671
27698
  kits.push(parsed);
@@ -27689,7 +27716,7 @@ async function installKitFromFile() {
27689
27716
  }
27690
27717
  let raw;
27691
27718
  try {
27692
- raw = fs$1.readFileSync(filePaths[0], "utf-8");
27719
+ raw = await readFile(filePaths[0], "utf-8");
27693
27720
  } catch (err) {
27694
27721
  logger$9.warn("Failed to read selected kit file:", err);
27695
27722
  return errorResult("Could not read the selected file.");
@@ -27711,20 +27738,20 @@ async function installKitFromFile() {
27711
27738
  `Kit id "${parsed.id}" conflicts with a built-in kit and cannot be overwritten.`
27712
27739
  );
27713
27740
  }
27714
- ensureKitsDir();
27741
+ await ensureKitsDir();
27715
27742
  const dest = getKitFilePath(parsed.id);
27716
27743
  if (!dest) {
27717
27744
  return errorResult("Kit id contains unsupported characters.");
27718
27745
  }
27719
27746
  try {
27720
- fs$1.writeFileSync(dest, JSON.stringify(parsed, null, 2), "utf-8");
27747
+ await writeFile(dest, JSON.stringify(parsed, null, 2), "utf-8");
27721
27748
  } catch (err) {
27722
27749
  logger$9.warn("Failed to save kit file:", err);
27723
27750
  return errorResult("Failed to save the kit file.");
27724
27751
  }
27725
27752
  return okResult({ kit: parsed });
27726
27753
  }
27727
- function uninstallKit(id, scheduledKitIds) {
27754
+ async function uninstallKit(id, scheduledKitIds) {
27728
27755
  if (BUNDLED_KIT_IDS.has(id)) {
27729
27756
  return errorResult("Built-in kits cannot be removed.");
27730
27757
  }
@@ -27733,16 +27760,16 @@ function uninstallKit(id, scheduledKitIds) {
27733
27760
  "This kit has active scheduled jobs. Delete or reassign them first."
27734
27761
  );
27735
27762
  }
27736
- ensureKitsDir();
27763
+ await ensureKitsDir();
27737
27764
  const target = getKitFilePath(id);
27738
27765
  if (!target) {
27739
27766
  return errorResult("Kit id contains unsupported characters.");
27740
27767
  }
27741
- if (!fs$1.existsSync(target)) {
27768
+ if (!await pathExists(target)) {
27742
27769
  return errorResult("Kit not found.");
27743
27770
  }
27744
27771
  try {
27745
- fs$1.unlinkSync(target);
27772
+ await unlink(target);
27746
27773
  return okResult();
27747
27774
  } catch (err) {
27748
27775
  logger$9.warn("Failed to remove kit file:", err);
@@ -28078,17 +28105,20 @@ function registerSystemHandlers(windowState2, sendToRendererViews) {
28078
28105
  layoutViews(windowState2);
28079
28106
  return clamped;
28080
28107
  });
28081
- electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, (event) => {
28108
+ electron.ipcMain.handle(Channels.AUTOMATION_GET_INSTALLED, async (event) => {
28082
28109
  assertTrustedIpcSender(event);
28083
- return getInstalledKits();
28110
+ return await getInstalledKits();
28084
28111
  });
28085
28112
  electron.ipcMain.handle(Channels.AUTOMATION_INSTALL_FROM_FILE, async (event) => {
28086
28113
  assertTrustedIpcSender(event);
28087
28114
  return await installKitFromFile();
28088
28115
  });
28089
- electron.ipcMain.handle(Channels.AUTOMATION_UNINSTALL, (event, id) => {
28116
+ electron.ipcMain.handle(Channels.AUTOMATION_UNINSTALL, async (event, id) => {
28090
28117
  assertTrustedIpcSender(event);
28091
- return uninstallKit(parseIpc(KitIdSchema, id, "id"), getScheduledKitIds());
28118
+ return await uninstallKit(
28119
+ parseIpc(KitIdSchema, id, "id"),
28120
+ getScheduledKitIds()
28121
+ );
28092
28122
  });
28093
28123
  electron.ipcMain.handle(Channels.CLEAR_BROWSING_DATA, async (event, options) => {
28094
28124
  assertTrustedIpcSender(event);