@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.
Files changed (2) hide show
  1. package/dist/index.js +73 -43
  2. 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, i) => {
352
- if (line === "" || isInsideCodeBlock(contentLines, i) || isStructuredLine(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, i) => {
377
- if (line === "" || isInsideCodeBlock(contentLines, i) || isStructuredLine(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, i) => {
402
- if (line === "" || isInsideCodeBlock(contentLines, i) || isStructuredLine(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.25",
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.25",
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": "Apache-2.0",
75
+ "license": "AGPL-3.0-only",
76
76
  "files": [
77
77
  "dist",
78
78
  "README.md",