@velvetmonkey/flywheel-memory 2.0.84 → 2.0.86
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 +77 -33
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
+
}) : x)(function(x) {
|
|
7
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
+
});
|
|
4
10
|
var __esm = (fn, res) => function __init() {
|
|
5
11
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
12
|
};
|
|
@@ -1575,7 +1581,7 @@ var init_taskHelpers = __esm({
|
|
|
1575
1581
|
import * as path32 from "path";
|
|
1576
1582
|
import { readFileSync as readFileSync5, realpathSync } from "fs";
|
|
1577
1583
|
import { fileURLToPath } from "url";
|
|
1578
|
-
import { dirname as
|
|
1584
|
+
import { dirname as dirname5, join as join17 } from "path";
|
|
1579
1585
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1580
1586
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1581
1587
|
|
|
@@ -1891,12 +1897,41 @@ function setEmbeddingsBuildState(state2) {
|
|
|
1891
1897
|
function setEmbeddingsDatabase(database) {
|
|
1892
1898
|
db = database;
|
|
1893
1899
|
}
|
|
1900
|
+
function clearModelCache(modelId) {
|
|
1901
|
+
try {
|
|
1902
|
+
const candidates = [];
|
|
1903
|
+
try {
|
|
1904
|
+
const transformersDir = path2.dirname(__require.resolve("@huggingface/transformers/package.json"));
|
|
1905
|
+
candidates.push(path2.join(transformersDir, ".cache", ...modelId.split("/")));
|
|
1906
|
+
} catch {
|
|
1907
|
+
}
|
|
1908
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
1909
|
+
if (home) {
|
|
1910
|
+
const npxDir = path2.join(home, ".npm", "_npx");
|
|
1911
|
+
if (fs3.existsSync(npxDir)) {
|
|
1912
|
+
for (const hash of fs3.readdirSync(npxDir)) {
|
|
1913
|
+
const candidate = path2.join(npxDir, hash, "node_modules", "@huggingface", "transformers", ".cache", ...modelId.split("/"));
|
|
1914
|
+
if (fs3.existsSync(candidate)) candidates.push(candidate);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
for (const cacheDir of candidates) {
|
|
1919
|
+
if (fs3.existsSync(cacheDir)) {
|
|
1920
|
+
fs3.rmSync(cacheDir, { recursive: true, force: true });
|
|
1921
|
+
console.error(`[Semantic] Deleted corrupted model cache: ${cacheDir}`);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
} catch (e) {
|
|
1925
|
+
console.error(`[Semantic] Could not clear model cache: ${e instanceof Error ? e.message : e}`);
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1894
1928
|
async function initEmbeddings() {
|
|
1895
1929
|
if (pipeline) return;
|
|
1896
1930
|
if (initPromise) return initPromise;
|
|
1897
1931
|
initPromise = (async () => {
|
|
1898
1932
|
const MAX_RETRIES = 3;
|
|
1899
1933
|
const RETRY_DELAYS = [2e3, 5e3, 1e4];
|
|
1934
|
+
let cacheCleared = false;
|
|
1900
1935
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
1901
1936
|
try {
|
|
1902
1937
|
const transformers = await Function("specifier", "return import(specifier)")("@huggingface/transformers");
|
|
@@ -1918,9 +1953,17 @@ async function initEmbeddings() {
|
|
|
1918
1953
|
"Semantic search requires @huggingface/transformers. Install it with: npm install @huggingface/transformers"
|
|
1919
1954
|
);
|
|
1920
1955
|
}
|
|
1956
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1957
|
+
if (!cacheCleared && (errMsg.includes("Protobuf parsing failed") || errMsg.includes("onnx"))) {
|
|
1958
|
+
console.error(`[Semantic] Corrupted model cache detected: ${errMsg}`);
|
|
1959
|
+
clearModelCache(activeModelConfig.id);
|
|
1960
|
+
cacheCleared = true;
|
|
1961
|
+
pipeline = null;
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1921
1964
|
if (attempt < MAX_RETRIES) {
|
|
1922
1965
|
const delay = RETRY_DELAYS[attempt - 1];
|
|
1923
|
-
console.error(`[Semantic] Model load failed (attempt ${attempt}/${MAX_RETRIES}): ${
|
|
1966
|
+
console.error(`[Semantic] Model load failed (attempt ${attempt}/${MAX_RETRIES}): ${errMsg}`);
|
|
1924
1967
|
console.error(`[Semantic] Retrying in ${delay / 1e3}s...`);
|
|
1925
1968
|
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
1926
1969
|
pipeline = null;
|
|
@@ -7551,10 +7594,10 @@ function queryTasksFromCache(options) {
|
|
|
7551
7594
|
if (!db3) {
|
|
7552
7595
|
throw new Error("Task cache database not initialized.");
|
|
7553
7596
|
}
|
|
7554
|
-
const { status
|
|
7597
|
+
const { status, folder, tag, excludeTags = [], has_due_date, limit, offset = 0 } = options;
|
|
7555
7598
|
const conditions = [];
|
|
7556
7599
|
const params = [];
|
|
7557
|
-
if (status
|
|
7600
|
+
if (status) {
|
|
7558
7601
|
conditions.push("status = ?");
|
|
7559
7602
|
params.push(status);
|
|
7560
7603
|
}
|
|
@@ -9731,11 +9774,11 @@ function sortNotes(notes, sortBy, order) {
|
|
|
9731
9774
|
function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
9732
9775
|
server2.tool(
|
|
9733
9776
|
"search",
|
|
9734
|
-
'Search the vault \u2014 always try this before reading files. Returns frontmatter, backlinks (with lines), outlinks (with lines + exists), headings, content snippet or preview, entity metadata, and timestamps for every hit.\n\
|
|
9777
|
+
'Search the vault \u2014 always try this before reading files. Returns frontmatter, backlinks (with lines), outlinks (with lines + exists), headings, content snippet or preview, entity metadata, and timestamps for every hit.\n\nSearches across metadata (frontmatter/tags/folders), content (FTS5 full-text + hybrid semantic), and entities (people/projects/technologies). Uses filters to narrow by frontmatter fields, tags, folders, or dates. Hybrid semantic results are automatically included when embeddings have been built (via init_semantic).\n\nExample: search({ query: "quarterly review", limit: 5 })\nExample: search({ where: { type: "project", status: "active" } })',
|
|
9735
9778
|
{
|
|
9736
|
-
query: z4.string().optional().describe(
|
|
9737
|
-
scope: z4.enum(["metadata", "content", "entities", "all"]).
|
|
9738
|
-
// Metadata filters
|
|
9779
|
+
query: z4.string().optional().describe("Search query text. Required unless using metadata filters (where, has_tag, folder, etc.)"),
|
|
9780
|
+
scope: z4.enum(["metadata", "content", "entities", "all"]).optional().describe('Narrow to a specific search type. Omit for best results (searches everything). Use "metadata" for frontmatter-only queries, "entities" for entity lookup.'),
|
|
9781
|
+
// Metadata filters
|
|
9739
9782
|
where: z4.record(z4.unknown()).optional().describe('Frontmatter filters as key-value pairs. Example: { "type": "project", "status": "active" }'),
|
|
9740
9783
|
has_tag: z4.string().optional().describe("Filter to notes with this tag"),
|
|
9741
9784
|
has_any_tag: z4.array(z4.string()).optional().describe("Filter to notes with any of these tags"),
|
|
@@ -9756,7 +9799,8 @@ function registerQueryTools(server2, getIndex, getVaultPath, getStateDb) {
|
|
|
9756
9799
|
// Context boost (edge weights)
|
|
9757
9800
|
context_note: z4.string().optional().describe("Path of the note providing context. When set, results connected to this note via weighted edges get an RRF boost.")
|
|
9758
9801
|
},
|
|
9759
|
-
async ({ query, scope, where, has_tag, has_any_tag, has_all_tags, include_children, folder, title_contains, modified_after, modified_before, sort_by, order, prefix, limit: requestedLimit, context_note }) => {
|
|
9802
|
+
async ({ query, scope: rawScope, where, has_tag, has_any_tag, has_all_tags, include_children, folder, title_contains, modified_after, modified_before, sort_by, order, prefix, limit: requestedLimit, context_note }) => {
|
|
9803
|
+
const scope = rawScope || "all";
|
|
9760
9804
|
const limit = Math.min(requestedLimit ?? 20, MAX_LIMIT);
|
|
9761
9805
|
const index = getIndex();
|
|
9762
9806
|
const vaultPath2 = getVaultPath();
|
|
@@ -10678,7 +10722,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
|
|
|
10678
10722
|
description: "Read the structure of a specific note. Use after search identifies a note you need more detail on. Returns headings, frontmatter, tags, word count. Set include_content: true to get the full markdown.",
|
|
10679
10723
|
inputSchema: {
|
|
10680
10724
|
path: z6.string().describe("Path to the note"),
|
|
10681
|
-
include_content: z6.boolean().default(
|
|
10725
|
+
include_content: z6.boolean().default(true).describe("Include the text content under each top-level section. Set false to get structure only.")
|
|
10682
10726
|
}
|
|
10683
10727
|
},
|
|
10684
10728
|
async ({ path: path33, include_content }) => {
|
|
@@ -10791,7 +10835,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
|
|
|
10791
10835
|
description: 'Query tasks from the vault. Use path to scope to a single note. Use status to filter (default: "open"). Use has_due_date to find tasks with due dates.',
|
|
10792
10836
|
inputSchema: {
|
|
10793
10837
|
path: z6.string().optional().describe("Scope to tasks from this specific note path"),
|
|
10794
|
-
status: z6.enum(["open", "completed", "cancelled"
|
|
10838
|
+
status: z6.enum(["open", "completed", "cancelled"]).default("open").describe("Filter by task status"),
|
|
10795
10839
|
has_due_date: z6.boolean().optional().describe("If true, only return tasks with due dates (sorted by date)"),
|
|
10796
10840
|
folder: z6.string().optional().describe("Limit to tasks in notes within this folder"),
|
|
10797
10841
|
tag: z6.string().optional().describe("Filter to tasks with this tag"),
|
|
@@ -10812,7 +10856,7 @@ function registerPrimitiveTools(server2, getIndex, getVaultPath, getConfig = ()
|
|
|
10812
10856
|
};
|
|
10813
10857
|
}
|
|
10814
10858
|
let filtered = result2;
|
|
10815
|
-
if (status
|
|
10859
|
+
if (status) {
|
|
10816
10860
|
filtered = result2.filter((t) => t.status === status);
|
|
10817
10861
|
}
|
|
10818
10862
|
const paged2 = filtered.slice(offset, offset + limit);
|
|
@@ -13465,19 +13509,19 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13465
13509
|
format: z11.enum(["plain", "bullet", "task", "numbered", "timestamp-bullet"]).default("plain").describe("How to format the content"),
|
|
13466
13510
|
commit: z11.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
|
|
13467
13511
|
skipWikilinks: z11.boolean().default(false).describe("If true, skip auto-wikilink application (wikilinks are applied by default)"),
|
|
13468
|
-
|
|
13469
|
-
|
|
13470
|
-
suggestOutgoingLinks: z11.boolean().default(true).describe('Append suggested outgoing wikilinks based on content (e.g., "\u2192 [[AI]], [[Philosophy]]"). Set false to disable.'),
|
|
13471
|
-
maxSuggestions: z11.number().min(1).max(10).default(5).describe("Maximum number of suggested wikilinks to append (1-10, default: 5)"),
|
|
13472
|
-
validate: z11.boolean().default(true).describe("Check input for common issues (double timestamps, non-markdown bullets, etc.)"),
|
|
13473
|
-
normalize: z11.boolean().default(true).describe("Auto-fix common issues before formatting (replace \u2022 with -, trim excessive whitespace, etc.)"),
|
|
13474
|
-
guardrails: z11.enum(["warn", "strict", "off"]).default("warn").describe('Output validation mode: "warn" returns issues but proceeds, "strict" blocks on errors, "off" disables'),
|
|
13512
|
+
suggestOutgoingLinks: z11.boolean().default(true).describe("Suggest related outgoing wikilinks based on content. Set false to disable."),
|
|
13513
|
+
maxSuggestions: z11.number().min(1).max(10).default(5).describe("Maximum number of suggested wikilinks (1-10, default: 5)"),
|
|
13475
13514
|
linkedEntities: z11.array(z11.string()).optional().describe("Entity names already linked in the content. When skipWikilinks=true, these are tracked for feedback without re-processing the content."),
|
|
13476
13515
|
dry_run: z11.boolean().optional().default(false).describe("Preview changes without writing to disk"),
|
|
13477
13516
|
agent_id: z11.string().optional().describe('Agent identifier for multi-agent scoping (e.g., "claude-opus", "planning-agent")'),
|
|
13478
13517
|
session_id: z11.string().optional().describe('Session identifier for conversation scoping (e.g., "sess-abc123")')
|
|
13479
13518
|
},
|
|
13480
|
-
async ({ path: notePath, section, content, create_if_missing, position, format, commit, skipWikilinks,
|
|
13519
|
+
async ({ path: notePath, section, content, create_if_missing, position, format, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, linkedEntities, dry_run, agent_id, session_id }) => {
|
|
13520
|
+
const preserveListNesting = true;
|
|
13521
|
+
const bumpHeadings = true;
|
|
13522
|
+
const validate = true;
|
|
13523
|
+
const normalize = true;
|
|
13524
|
+
const guardrails = "warn";
|
|
13481
13525
|
let noteCreated = false;
|
|
13482
13526
|
let templateUsed;
|
|
13483
13527
|
if (create_if_missing) {
|
|
@@ -13609,16 +13653,16 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13609
13653
|
useRegex: z11.boolean().default(false).describe("Treat search as regex"),
|
|
13610
13654
|
commit: z11.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
|
|
13611
13655
|
skipWikilinks: z11.boolean().default(false).describe("If true, skip auto-wikilink application on replacement text"),
|
|
13612
|
-
suggestOutgoingLinks: z11.boolean().default(true).describe(
|
|
13613
|
-
maxSuggestions: z11.number().min(1).max(10).default(5).describe("Maximum number of suggested wikilinks
|
|
13614
|
-
validate: z11.boolean().default(true).describe("Check input for common issues (double timestamps, non-markdown bullets, etc.)"),
|
|
13615
|
-
normalize: z11.boolean().default(true).describe("Auto-fix common issues before formatting (replace \u2022 with -, trim excessive whitespace, etc.)"),
|
|
13616
|
-
guardrails: z11.enum(["warn", "strict", "off"]).default("warn").describe('Output validation mode: "warn" returns issues but proceeds, "strict" blocks on errors, "off" disables'),
|
|
13656
|
+
suggestOutgoingLinks: z11.boolean().default(true).describe("Suggest related outgoing wikilinks based on content. Set false to disable."),
|
|
13657
|
+
maxSuggestions: z11.number().min(1).max(10).default(5).describe("Maximum number of suggested wikilinks (1-10, default: 5)"),
|
|
13617
13658
|
dry_run: z11.boolean().optional().default(false).describe("Preview changes without writing to disk"),
|
|
13618
13659
|
agent_id: z11.string().optional().describe("Agent identifier for multi-agent scoping"),
|
|
13619
13660
|
session_id: z11.string().optional().describe("Session identifier for conversation scoping")
|
|
13620
13661
|
},
|
|
13621
|
-
async ({ path: notePath, section, search, replacement, mode, useRegex, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions,
|
|
13662
|
+
async ({ path: notePath, section, search, replacement, mode, useRegex, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, dry_run, agent_id, session_id }) => {
|
|
13663
|
+
const validate = true;
|
|
13664
|
+
const normalize = true;
|
|
13665
|
+
const guardrails = "warn";
|
|
13622
13666
|
return withVaultFile(
|
|
13623
13667
|
{
|
|
13624
13668
|
vaultPath: vaultPath2,
|
|
@@ -17527,7 +17571,7 @@ function getEdgeWeightBoost(entityName, edgeWeightMap) {
|
|
|
17527
17571
|
async function performRecall(stateDb2, query, options = {}) {
|
|
17528
17572
|
const {
|
|
17529
17573
|
max_results = 20,
|
|
17530
|
-
focus
|
|
17574
|
+
focus,
|
|
17531
17575
|
entity,
|
|
17532
17576
|
max_tokens,
|
|
17533
17577
|
diversity = 0.7,
|
|
@@ -17537,7 +17581,7 @@ async function performRecall(stateDb2, query, options = {}) {
|
|
|
17537
17581
|
const recencyIndex2 = loadRecencyFromStateDb();
|
|
17538
17582
|
const edgeWeightMap = getEntityEdgeWeightMap(stateDb2);
|
|
17539
17583
|
const feedbackBoosts = getAllFeedbackBoosts(stateDb2);
|
|
17540
|
-
if (focus
|
|
17584
|
+
if (!focus || focus === "entities") {
|
|
17541
17585
|
try {
|
|
17542
17586
|
const entityResults = searchEntitiesDb2(stateDb2, query, max_results);
|
|
17543
17587
|
for (const e of entityResults) {
|
|
@@ -17566,7 +17610,7 @@ async function performRecall(stateDb2, query, options = {}) {
|
|
|
17566
17610
|
} catch {
|
|
17567
17611
|
}
|
|
17568
17612
|
}
|
|
17569
|
-
if (focus
|
|
17613
|
+
if (!focus || focus === "notes") {
|
|
17570
17614
|
try {
|
|
17571
17615
|
const noteResults = searchFTS5("", query, max_results);
|
|
17572
17616
|
for (const n of noteResults) {
|
|
@@ -17589,7 +17633,7 @@ async function performRecall(stateDb2, query, options = {}) {
|
|
|
17589
17633
|
} catch {
|
|
17590
17634
|
}
|
|
17591
17635
|
}
|
|
17592
|
-
if (focus
|
|
17636
|
+
if (!focus || focus === "memories") {
|
|
17593
17637
|
try {
|
|
17594
17638
|
const memResults = searchMemories(stateDb2, {
|
|
17595
17639
|
query,
|
|
@@ -17617,7 +17661,7 @@ async function performRecall(stateDb2, query, options = {}) {
|
|
|
17617
17661
|
} catch {
|
|
17618
17662
|
}
|
|
17619
17663
|
}
|
|
17620
|
-
if ((focus
|
|
17664
|
+
if ((!focus || focus === "entities") && query.length >= 20 && hasEntityEmbeddingsIndex()) {
|
|
17621
17665
|
try {
|
|
17622
17666
|
const embedding = await embedTextCached(query);
|
|
17623
17667
|
const semanticMatches = findSemanticallySimilarEntities(embedding, max_results);
|
|
@@ -17726,7 +17770,7 @@ function registerRecallTools(server2, getStateDb, getVaultPath) {
|
|
|
17726
17770
|
{
|
|
17727
17771
|
query: z24.string().describe('What to recall (e.g., "Project X", "meetings about auth")'),
|
|
17728
17772
|
max_results: z24.number().min(1).max(100).optional().describe("Max results (default: 20)"),
|
|
17729
|
-
focus: z24.enum(["entities", "notes", "memories"
|
|
17773
|
+
focus: z24.enum(["entities", "notes", "memories"]).optional().describe("Limit to a specific result type. Omit for best results (searches everything)."),
|
|
17730
17774
|
entity: z24.string().optional().describe("Filter memories by entity association"),
|
|
17731
17775
|
max_tokens: z24.number().optional().describe("Token budget for response (truncates lower-ranked results)"),
|
|
17732
17776
|
diversity: z24.number().min(0).max(1).optional().describe("Relevance vs diversity tradeoff (0=max diversity, 1=pure relevance, default: 0.7)")
|
|
@@ -19309,7 +19353,7 @@ function registerVaultResources(server2, getIndex) {
|
|
|
19309
19353
|
|
|
19310
19354
|
// src/index.ts
|
|
19311
19355
|
var __filename = fileURLToPath(import.meta.url);
|
|
19312
|
-
var __dirname =
|
|
19356
|
+
var __dirname = dirname5(__filename);
|
|
19313
19357
|
var pkg = JSON.parse(readFileSync5(join17(__dirname, "../package.json"), "utf-8"));
|
|
19314
19358
|
var vaultPath = process.env.PROJECT_PATH || process.env.VAULT_PATH || findVaultRoot();
|
|
19315
19359
|
var resolvedVaultPath;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.86",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 51 tools for search, backlinks, graph queries, mutations, agent memory, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
56
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
56
|
+
"@velvetmonkey/vault-core": "^2.0.86",
|
|
57
57
|
"better-sqlite3": "^11.0.0",
|
|
58
58
|
"chokidar": "^4.0.0",
|
|
59
59
|
"gray-matter": "^4.0.3",
|