@velvetmonkey/flywheel-memory 2.0.25 → 2.0.27
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 +73 -43
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -168,24 +168,6 @@ function findSection(content, sectionName) {
|
|
|
168
168
|
contentStartLine
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
|
-
function isInsideCodeBlock(lines, currentIndex) {
|
|
172
|
-
let fenceCount = 0;
|
|
173
|
-
for (let i = 0; i < currentIndex; i++) {
|
|
174
|
-
if (lines[i].trim().startsWith("```")) {
|
|
175
|
-
fenceCount++;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return fenceCount % 2 === 1;
|
|
179
|
-
}
|
|
180
|
-
function isStructuredLine(line) {
|
|
181
|
-
const trimmed = line.trimStart();
|
|
182
|
-
return trimmed.startsWith("|") || // Table row
|
|
183
|
-
trimmed.startsWith(">") || // Blockquote
|
|
184
|
-
trimmed.startsWith("```") || // Code fence
|
|
185
|
-
/^-{3,}$/.test(trimmed) || // Horizontal rule (dashes)
|
|
186
|
-
/^\*{3,}$/.test(trimmed) || // Horizontal rule (asterisks)
|
|
187
|
-
/^_{3,}$/.test(trimmed);
|
|
188
|
-
}
|
|
189
171
|
function isPreformattedList(content) {
|
|
190
172
|
const trimmed = content.trim();
|
|
191
173
|
if (!trimmed) return false;
|
|
@@ -223,9 +205,6 @@ function formatContent(content, format) {
|
|
|
223
205
|
return lines.map((line, i) => {
|
|
224
206
|
if (i === 0) return `- ${line}`;
|
|
225
207
|
if (line === "") return "";
|
|
226
|
-
if (isInsideCodeBlock(lines, i) || isStructuredLine(line)) {
|
|
227
|
-
return line;
|
|
228
|
-
}
|
|
229
208
|
return ` ${line}`;
|
|
230
209
|
}).join("\n");
|
|
231
210
|
}
|
|
@@ -237,9 +216,6 @@ function formatContent(content, format) {
|
|
|
237
216
|
return lines.map((line, i) => {
|
|
238
217
|
if (i === 0) return `- [ ] ${line}`;
|
|
239
218
|
if (line === "") return "";
|
|
240
|
-
if (isInsideCodeBlock(lines, i) || isStructuredLine(line)) {
|
|
241
|
-
return line;
|
|
242
|
-
}
|
|
243
219
|
return ` ${line}`;
|
|
244
220
|
}).join("\n");
|
|
245
221
|
}
|
|
@@ -251,9 +227,6 @@ function formatContent(content, format) {
|
|
|
251
227
|
return lines.map((line, i) => {
|
|
252
228
|
if (i === 0) return `1. ${line}`;
|
|
253
229
|
if (line === "") return "";
|
|
254
|
-
if (isInsideCodeBlock(lines, i) || isStructuredLine(line)) {
|
|
255
|
-
return line;
|
|
256
|
-
}
|
|
257
230
|
return ` ${line}`;
|
|
258
231
|
}).join("\n");
|
|
259
232
|
}
|
|
@@ -270,9 +243,6 @@ function formatContent(content, format) {
|
|
|
270
243
|
return lines.map((line, i) => {
|
|
271
244
|
if (i === 0) return `${prefix}${line}`;
|
|
272
245
|
if (line === "") return "";
|
|
273
|
-
if (isInsideCodeBlock(lines, i) || isStructuredLine(line)) {
|
|
274
|
-
return line;
|
|
275
|
-
}
|
|
276
246
|
return `${indent}${line}`;
|
|
277
247
|
}).join("\n");
|
|
278
248
|
}
|
|
@@ -348,10 +318,8 @@ function insertInSection(content, section, newContent, position, options) {
|
|
|
348
318
|
const indent = detectSectionBaseIndentation(lines, section.contentStartLine, section.endLine);
|
|
349
319
|
if (indent) {
|
|
350
320
|
const contentLines = formattedContent.split("\n");
|
|
351
|
-
const indentedContent = contentLines.map((line
|
|
352
|
-
if (line === ""
|
|
353
|
-
return line;
|
|
354
|
-
}
|
|
321
|
+
const indentedContent = contentLines.map((line) => {
|
|
322
|
+
if (line === "") return line;
|
|
355
323
|
return indent + line;
|
|
356
324
|
}).join("\n");
|
|
357
325
|
lines.splice(section.contentStartLine, 0, indentedContent);
|
|
@@ -373,10 +341,8 @@ function insertInSection(content, section, newContent, position, options) {
|
|
|
373
341
|
if (options?.preserveListNesting) {
|
|
374
342
|
const indent = detectSectionBaseIndentation(lines, section.contentStartLine, section.endLine);
|
|
375
343
|
const contentLines = formattedContent.split("\n");
|
|
376
|
-
const indentedContent = contentLines.map((line
|
|
377
|
-
if (line === ""
|
|
378
|
-
return line;
|
|
379
|
-
}
|
|
344
|
+
const indentedContent = contentLines.map((line) => {
|
|
345
|
+
if (line === "") return line;
|
|
380
346
|
return indent + line;
|
|
381
347
|
}).join("\n");
|
|
382
348
|
lines[lastContentLineIdx] = indentedContent;
|
|
@@ -398,10 +364,8 @@ function insertInSection(content, section, newContent, position, options) {
|
|
|
398
364
|
if (options?.preserveListNesting) {
|
|
399
365
|
const indent = detectSectionBaseIndentation(lines, section.contentStartLine, section.endLine);
|
|
400
366
|
const contentLines = formattedContent.split("\n");
|
|
401
|
-
const indentedContent = contentLines.map((line
|
|
402
|
-
if (line === ""
|
|
403
|
-
return line;
|
|
404
|
-
}
|
|
367
|
+
const indentedContent = contentLines.map((line) => {
|
|
368
|
+
if (line === "") return line;
|
|
405
369
|
return indent + line;
|
|
406
370
|
}).join("\n");
|
|
407
371
|
lines.splice(insertLine, 0, indentedContent);
|
|
@@ -7490,7 +7454,7 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
7490
7454
|
import * as fs10 from "fs";
|
|
7491
7455
|
import * as path10 from "path";
|
|
7492
7456
|
import { z as z5 } from "zod";
|
|
7493
|
-
import { scanVaultEntities as scanVaultEntities2 } from "@velvetmonkey/vault-core";
|
|
7457
|
+
import { scanVaultEntities as scanVaultEntities2, getEntityIndexFromDb as getEntityIndexFromDb2 } from "@velvetmonkey/vault-core";
|
|
7494
7458
|
function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfig, getStateDb) {
|
|
7495
7459
|
const RefreshIndexOutputSchema = {
|
|
7496
7460
|
success: z5.boolean().describe("Whether the refresh succeeded"),
|
|
@@ -7934,6 +7898,49 @@ function registerSystemTools(server2, getIndex, setIndex, getVaultPath, setConfi
|
|
|
7934
7898
|
};
|
|
7935
7899
|
}
|
|
7936
7900
|
);
|
|
7901
|
+
server2.registerTool(
|
|
7902
|
+
"list_entities",
|
|
7903
|
+
{
|
|
7904
|
+
title: "List Entities",
|
|
7905
|
+
description: "Get all entities grouped by category with aliases and hub scores. Returns the full EntityIndex from StateDb.",
|
|
7906
|
+
inputSchema: {
|
|
7907
|
+
category: z5.string().optional().describe('Filter to a specific category (e.g. "people", "technologies")'),
|
|
7908
|
+
limit: z5.coerce.number().default(2e3).describe("Maximum entities per category")
|
|
7909
|
+
}
|
|
7910
|
+
},
|
|
7911
|
+
async ({
|
|
7912
|
+
category,
|
|
7913
|
+
limit: perCategoryLimit
|
|
7914
|
+
}) => {
|
|
7915
|
+
const stateDb2 = getStateDb?.();
|
|
7916
|
+
if (!stateDb2) {
|
|
7917
|
+
return {
|
|
7918
|
+
content: [{ type: "text", text: JSON.stringify({ error: "StateDb not available" }) }]
|
|
7919
|
+
};
|
|
7920
|
+
}
|
|
7921
|
+
const entityIndex2 = getEntityIndexFromDb2(stateDb2);
|
|
7922
|
+
if (category) {
|
|
7923
|
+
const allCategories = Object.keys(entityIndex2).filter((k) => k !== "_metadata");
|
|
7924
|
+
for (const cat of allCategories) {
|
|
7925
|
+
if (cat !== category) {
|
|
7926
|
+
entityIndex2[cat] = [];
|
|
7927
|
+
}
|
|
7928
|
+
}
|
|
7929
|
+
}
|
|
7930
|
+
if (perCategoryLimit) {
|
|
7931
|
+
const allCategories = Object.keys(entityIndex2).filter((k) => k !== "_metadata");
|
|
7932
|
+
for (const cat of allCategories) {
|
|
7933
|
+
const arr = entityIndex2[cat];
|
|
7934
|
+
if (Array.isArray(arr) && arr.length > perCategoryLimit) {
|
|
7935
|
+
entityIndex2[cat] = arr.slice(0, perCategoryLimit);
|
|
7936
|
+
}
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
return {
|
|
7940
|
+
content: [{ type: "text", text: JSON.stringify(entityIndex2) }]
|
|
7941
|
+
};
|
|
7942
|
+
}
|
|
7943
|
+
);
|
|
7937
7944
|
}
|
|
7938
7945
|
|
|
7939
7946
|
// src/tools/read/primitives.ts
|
|
@@ -8169,6 +8176,19 @@ async function getAllTasks(index, vaultPath2, options = {}) {
|
|
|
8169
8176
|
(t) => !excludeTags.some((excludeTag) => t.tags.includes(excludeTag))
|
|
8170
8177
|
);
|
|
8171
8178
|
}
|
|
8179
|
+
filteredTasks.sort((a, b) => {
|
|
8180
|
+
if (a.due_date && !b.due_date) return -1;
|
|
8181
|
+
if (!a.due_date && b.due_date) return 1;
|
|
8182
|
+
if (a.due_date && b.due_date) {
|
|
8183
|
+
const cmp = b.due_date.localeCompare(a.due_date);
|
|
8184
|
+
if (cmp !== 0) return cmp;
|
|
8185
|
+
}
|
|
8186
|
+
const noteA = index.notes.get(a.path);
|
|
8187
|
+
const noteB = index.notes.get(b.path);
|
|
8188
|
+
const mtimeA = noteA?.modified?.getTime() ?? 0;
|
|
8189
|
+
const mtimeB = noteB?.modified?.getTime() ?? 0;
|
|
8190
|
+
return mtimeB - mtimeA;
|
|
8191
|
+
});
|
|
8172
8192
|
const openCount = allTasks.filter((t) => t.status === "open").length;
|
|
8173
8193
|
const completedCount = allTasks.filter((t) => t.status === "completed").length;
|
|
8174
8194
|
const cancelledCount = allTasks.filter((t) => t.status === "cancelled").length;
|
|
@@ -10836,6 +10856,12 @@ async function createNoteFromTemplate(vaultPath2, notePath, config) {
|
|
|
10836
10856
|
const now = /* @__PURE__ */ new Date();
|
|
10837
10857
|
const dateStr = now.toISOString().split("T")[0];
|
|
10838
10858
|
templateContent = templateContent.replace(/\{\{date\}\}/g, dateStr).replace(/\{\{title\}\}/g, path19.basename(notePath, ".md"));
|
|
10859
|
+
const matter9 = (await import("gray-matter")).default;
|
|
10860
|
+
const parsed = matter9(templateContent);
|
|
10861
|
+
if (!parsed.data.date) {
|
|
10862
|
+
parsed.data.date = dateStr;
|
|
10863
|
+
}
|
|
10864
|
+
templateContent = matter9.stringify(parsed.content, parsed.data);
|
|
10839
10865
|
await fs20.writeFile(fullPath, templateContent, "utf-8");
|
|
10840
10866
|
return { created: true, templateUsed: templatePath };
|
|
10841
10867
|
}
|
|
@@ -11369,6 +11395,9 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
11369
11395
|
return formatMcpResult(errorResult(notePath, `Template not found: ${template}`));
|
|
11370
11396
|
}
|
|
11371
11397
|
}
|
|
11398
|
+
if (!effectiveFrontmatter.date) {
|
|
11399
|
+
effectiveFrontmatter.date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
11400
|
+
}
|
|
11372
11401
|
const warnings = [];
|
|
11373
11402
|
const noteName = path20.basename(notePath, ".md");
|
|
11374
11403
|
const existingAliases = Array.isArray(effectiveFrontmatter?.aliases) ? effectiveFrontmatter.aliases.filter((a) => typeof a === "string") : [];
|
|
@@ -14697,6 +14726,7 @@ var TOOL_CATEGORY = {
|
|
|
14697
14726
|
refresh_index: "health",
|
|
14698
14727
|
// absorbed rebuild_search_index
|
|
14699
14728
|
get_all_entities: "health",
|
|
14729
|
+
list_entities: "hubs",
|
|
14700
14730
|
get_unlinked_mentions: "health",
|
|
14701
14731
|
// search (unified: metadata + content + entities)
|
|
14702
14732
|
search: "search",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.27",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. 42 tools for search, backlinks, graph queries, mutations, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
53
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
53
|
+
"@velvetmonkey/vault-core": "^2.0.27",
|
|
54
54
|
"better-sqlite3": "^11.0.0",
|
|
55
55
|
"chokidar": "^4.0.0",
|
|
56
56
|
"gray-matter": "^4.0.3",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=18.0.0"
|
|
74
74
|
},
|
|
75
|
-
"license": "
|
|
75
|
+
"license": "AGPL-3.0-only",
|
|
76
76
|
"files": [
|
|
77
77
|
"dist",
|
|
78
78
|
"README.md",
|