@velvetmonkey/flywheel-crank 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +169 -36
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -16,6 +16,22 @@ import matter from "gray-matter";
|
|
|
16
16
|
var HEADING_REGEX = /^(#{1,6})\s+(.+)$/;
|
|
17
17
|
|
|
18
18
|
// src/core/writer.ts
|
|
19
|
+
var EMPTY_PLACEHOLDER_PATTERNS = [
|
|
20
|
+
/^\d+\.\s*$/,
|
|
21
|
+
// "1. " or "2. " (numbered list placeholder)
|
|
22
|
+
/^-\s*$/,
|
|
23
|
+
// "- " (bullet placeholder)
|
|
24
|
+
/^-\s*\[\s*\]\s*$/,
|
|
25
|
+
// "- [ ] " (empty task placeholder)
|
|
26
|
+
/^-\s*\[x\]\s*$/i,
|
|
27
|
+
// "- [x] " (completed task placeholder)
|
|
28
|
+
/^\*\s*$/
|
|
29
|
+
// "* " (asterisk bullet placeholder)
|
|
30
|
+
];
|
|
31
|
+
function isEmptyPlaceholder(line) {
|
|
32
|
+
const trimmed = line.trim();
|
|
33
|
+
return EMPTY_PLACEHOLDER_PATTERNS.some((p) => p.test(trimmed));
|
|
34
|
+
}
|
|
19
35
|
function extractHeadings(content) {
|
|
20
36
|
const lines = content.split("\n");
|
|
21
37
|
const headings = [];
|
|
@@ -93,13 +109,24 @@ function insertInSection(content, section, newContent, position) {
|
|
|
93
109
|
if (position === "prepend") {
|
|
94
110
|
lines.splice(section.contentStartLine, 0, formattedContent);
|
|
95
111
|
} else {
|
|
96
|
-
let
|
|
97
|
-
|
|
98
|
-
|
|
112
|
+
let lastContentLineIdx = -1;
|
|
113
|
+
for (let i = section.endLine; i >= section.contentStartLine; i--) {
|
|
114
|
+
if (lines[i].trim() !== "") {
|
|
115
|
+
lastContentLineIdx = i;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (lastContentLineIdx >= section.contentStartLine && isEmptyPlaceholder(lines[lastContentLineIdx])) {
|
|
120
|
+
lines[lastContentLineIdx] = formattedContent;
|
|
99
121
|
} else {
|
|
100
|
-
insertLine = section.
|
|
122
|
+
let insertLine = section.endLine + 1;
|
|
123
|
+
if (section.contentStartLine <= section.endLine) {
|
|
124
|
+
insertLine = section.endLine + 1;
|
|
125
|
+
} else {
|
|
126
|
+
insertLine = section.contentStartLine;
|
|
127
|
+
}
|
|
128
|
+
lines.splice(insertLine, 0, formattedContent);
|
|
101
129
|
}
|
|
102
|
-
lines.splice(insertLine, 0, formattedContent);
|
|
103
130
|
}
|
|
104
131
|
return lines.join("\n");
|
|
105
132
|
}
|
|
@@ -305,9 +332,104 @@ async function undoLastCommit(vaultPath2) {
|
|
|
305
332
|
}
|
|
306
333
|
}
|
|
307
334
|
|
|
335
|
+
// src/core/wikilinks.ts
|
|
336
|
+
import {
|
|
337
|
+
scanVaultEntities,
|
|
338
|
+
getAllEntities,
|
|
339
|
+
applyWikilinks,
|
|
340
|
+
loadEntityCache,
|
|
341
|
+
saveEntityCache
|
|
342
|
+
} from "@velvetmonkey/vault-core";
|
|
343
|
+
import path3 from "path";
|
|
344
|
+
var entityIndex = null;
|
|
345
|
+
var indexReady = false;
|
|
346
|
+
var indexError = null;
|
|
347
|
+
var DEFAULT_EXCLUDE_FOLDERS = [
|
|
348
|
+
"daily-notes",
|
|
349
|
+
"daily",
|
|
350
|
+
"weekly",
|
|
351
|
+
"monthly",
|
|
352
|
+
"quarterly",
|
|
353
|
+
"periodic",
|
|
354
|
+
"journal",
|
|
355
|
+
"inbox",
|
|
356
|
+
"templates"
|
|
357
|
+
];
|
|
358
|
+
async function initializeEntityIndex(vaultPath2) {
|
|
359
|
+
const cacheFile = path3.join(vaultPath2, ".claude", "wikilink-entities.json");
|
|
360
|
+
try {
|
|
361
|
+
const cached = await loadEntityCache(cacheFile);
|
|
362
|
+
if (cached) {
|
|
363
|
+
entityIndex = cached;
|
|
364
|
+
indexReady = true;
|
|
365
|
+
console.error(`[Crank] Loaded ${cached._metadata.total_entities} entities from cache`);
|
|
366
|
+
const cacheAge = Date.now() - new Date(cached._metadata.generated_at).getTime();
|
|
367
|
+
if (cacheAge > 60 * 60 * 1e3) {
|
|
368
|
+
rebuildIndexInBackground(vaultPath2, cacheFile);
|
|
369
|
+
}
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
await rebuildIndex(vaultPath2, cacheFile);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
indexError = error instanceof Error ? error : new Error(String(error));
|
|
375
|
+
console.error(`[Crank] Failed to initialize entity index: ${indexError.message}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async function rebuildIndex(vaultPath2, cacheFile) {
|
|
379
|
+
console.error(`[Crank] Scanning vault for entities...`);
|
|
380
|
+
const startTime = Date.now();
|
|
381
|
+
entityIndex = await scanVaultEntities(vaultPath2, {
|
|
382
|
+
excludeFolders: DEFAULT_EXCLUDE_FOLDERS
|
|
383
|
+
});
|
|
384
|
+
indexReady = true;
|
|
385
|
+
const duration = Date.now() - startTime;
|
|
386
|
+
console.error(`[Crank] Entity index built: ${entityIndex._metadata.total_entities} entities in ${duration}ms`);
|
|
387
|
+
try {
|
|
388
|
+
await saveEntityCache(cacheFile, entityIndex);
|
|
389
|
+
console.error(`[Crank] Entity cache saved`);
|
|
390
|
+
} catch (e) {
|
|
391
|
+
console.error(`[Crank] Failed to save entity cache: ${e}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function rebuildIndexInBackground(vaultPath2, cacheFile) {
|
|
395
|
+
rebuildIndex(vaultPath2, cacheFile).catch((error) => {
|
|
396
|
+
console.error(`[Crank] Background index rebuild failed: ${error}`);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
function isEntityIndexReady() {
|
|
400
|
+
return indexReady && entityIndex !== null;
|
|
401
|
+
}
|
|
402
|
+
function processWikilinks(content) {
|
|
403
|
+
if (!isEntityIndexReady() || !entityIndex) {
|
|
404
|
+
return {
|
|
405
|
+
content,
|
|
406
|
+
linksAdded: 0,
|
|
407
|
+
linkedEntities: []
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
const entities = getAllEntities(entityIndex);
|
|
411
|
+
return applyWikilinks(content, entities, {
|
|
412
|
+
firstOccurrenceOnly: true,
|
|
413
|
+
caseInsensitive: true
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
function maybeApplyWikilinks(content, skipWikilinks) {
|
|
417
|
+
if (skipWikilinks) {
|
|
418
|
+
return { content };
|
|
419
|
+
}
|
|
420
|
+
const result = processWikilinks(content);
|
|
421
|
+
if (result.linksAdded > 0) {
|
|
422
|
+
return {
|
|
423
|
+
content: result.content,
|
|
424
|
+
wikilinkInfo: `Applied ${result.linksAdded} wikilink(s): ${result.linkedEntities.join(", ")}`
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return { content: result.content };
|
|
428
|
+
}
|
|
429
|
+
|
|
308
430
|
// src/tools/mutations.ts
|
|
309
431
|
import fs2 from "fs/promises";
|
|
310
|
-
import
|
|
432
|
+
import path4 from "path";
|
|
311
433
|
function registerMutationTools(server2, vaultPath2) {
|
|
312
434
|
server2.tool(
|
|
313
435
|
"vault_add_to_section",
|
|
@@ -318,11 +440,12 @@ function registerMutationTools(server2, vaultPath2) {
|
|
|
318
440
|
content: z.string().describe("Content to add to the section"),
|
|
319
441
|
position: z.enum(["append", "prepend"]).default("append").describe("Where to insert content"),
|
|
320
442
|
format: z.enum(["plain", "bullet", "task", "numbered", "timestamp-bullet"]).default("plain").describe("How to format the content"),
|
|
321
|
-
commit: z.boolean().default(false).describe("If true, commit this change to git (creates undo point)")
|
|
443
|
+
commit: z.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
|
|
444
|
+
skipWikilinks: z.boolean().default(false).describe("If true, skip auto-wikilink application (wikilinks are applied by default)")
|
|
322
445
|
},
|
|
323
|
-
async ({ path: notePath, section, content, position, format, commit }) => {
|
|
446
|
+
async ({ path: notePath, section, content, position, format, commit, skipWikilinks }) => {
|
|
324
447
|
try {
|
|
325
|
-
const fullPath =
|
|
448
|
+
const fullPath = path4.join(vaultPath2, notePath);
|
|
326
449
|
try {
|
|
327
450
|
await fs2.access(fullPath);
|
|
328
451
|
} catch {
|
|
@@ -343,7 +466,8 @@ function registerMutationTools(server2, vaultPath2) {
|
|
|
343
466
|
};
|
|
344
467
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
345
468
|
}
|
|
346
|
-
const
|
|
469
|
+
const { content: wikilinkedContent, wikilinkInfo } = maybeApplyWikilinks(content, skipWikilinks);
|
|
470
|
+
const formattedContent = formatContent(wikilinkedContent, format);
|
|
347
471
|
const updatedContent = insertInSection(
|
|
348
472
|
fileContent,
|
|
349
473
|
sectionBoundary,
|
|
@@ -361,7 +485,8 @@ function registerMutationTools(server2, vaultPath2) {
|
|
|
361
485
|
gitError = gitResult.error;
|
|
362
486
|
}
|
|
363
487
|
}
|
|
364
|
-
const preview = formattedContent
|
|
488
|
+
const preview = formattedContent + (wikilinkInfo ? `
|
|
489
|
+
(${wikilinkInfo})` : "");
|
|
365
490
|
const result = {
|
|
366
491
|
success: true,
|
|
367
492
|
message: `Added content to section "${sectionBoundary.name}" in ${notePath}`,
|
|
@@ -394,7 +519,7 @@ function registerMutationTools(server2, vaultPath2) {
|
|
|
394
519
|
},
|
|
395
520
|
async ({ path: notePath, section, pattern, mode, useRegex, commit }) => {
|
|
396
521
|
try {
|
|
397
|
-
const fullPath =
|
|
522
|
+
const fullPath = path4.join(vaultPath2, notePath);
|
|
398
523
|
try {
|
|
399
524
|
await fs2.access(fullPath);
|
|
400
525
|
} catch {
|
|
@@ -470,11 +595,12 @@ function registerMutationTools(server2, vaultPath2) {
|
|
|
470
595
|
replacement: z.string().describe("Text to replace with"),
|
|
471
596
|
mode: z.enum(["first", "last", "all"]).default("first").describe("Which matches to replace"),
|
|
472
597
|
useRegex: z.boolean().default(false).describe("Treat search as regex"),
|
|
473
|
-
commit: z.boolean().default(false).describe("If true, commit this change to git (creates undo point)")
|
|
598
|
+
commit: z.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
|
|
599
|
+
skipWikilinks: z.boolean().default(false).describe("If true, skip auto-wikilink application on replacement text")
|
|
474
600
|
},
|
|
475
|
-
async ({ path: notePath, section, search, replacement, mode, useRegex, commit }) => {
|
|
601
|
+
async ({ path: notePath, section, search, replacement, mode, useRegex, commit, skipWikilinks }) => {
|
|
476
602
|
try {
|
|
477
|
-
const fullPath =
|
|
603
|
+
const fullPath = path4.join(vaultPath2, notePath);
|
|
478
604
|
try {
|
|
479
605
|
await fs2.access(fullPath);
|
|
480
606
|
} catch {
|
|
@@ -495,11 +621,12 @@ function registerMutationTools(server2, vaultPath2) {
|
|
|
495
621
|
};
|
|
496
622
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
497
623
|
}
|
|
624
|
+
const { content: wikilinkedReplacement, wikilinkInfo } = maybeApplyWikilinks(replacement, skipWikilinks);
|
|
498
625
|
const replaceResult = replaceInSection(
|
|
499
626
|
fileContent,
|
|
500
627
|
sectionBoundary,
|
|
501
628
|
search,
|
|
502
|
-
|
|
629
|
+
wikilinkedReplacement,
|
|
503
630
|
mode,
|
|
504
631
|
useRegex
|
|
505
632
|
);
|
|
@@ -551,7 +678,7 @@ function registerMutationTools(server2, vaultPath2) {
|
|
|
551
678
|
// src/tools/tasks.ts
|
|
552
679
|
import { z as z2 } from "zod";
|
|
553
680
|
import fs3 from "fs/promises";
|
|
554
|
-
import
|
|
681
|
+
import path5 from "path";
|
|
555
682
|
var TASK_REGEX = /^(\s*)-\s*\[([ xX])\]\s*(.*)$/;
|
|
556
683
|
function findTasks(content, section) {
|
|
557
684
|
const lines = content.split("\n");
|
|
@@ -607,7 +734,7 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
607
734
|
},
|
|
608
735
|
async ({ path: notePath, task, section, commit }) => {
|
|
609
736
|
try {
|
|
610
|
-
const fullPath =
|
|
737
|
+
const fullPath = path5.join(vaultPath2, notePath);
|
|
611
738
|
try {
|
|
612
739
|
await fs3.access(fullPath);
|
|
613
740
|
} catch {
|
|
@@ -695,11 +822,12 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
695
822
|
task: z2.string().describe("Task text (without checkbox)"),
|
|
696
823
|
position: z2.enum(["append", "prepend"]).default("append").describe("Where to add the task"),
|
|
697
824
|
completed: z2.boolean().default(false).describe("Whether the task should start as completed"),
|
|
698
|
-
commit: z2.boolean().default(false).describe("If true, commit this change to git (creates undo point)")
|
|
825
|
+
commit: z2.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
|
|
826
|
+
skipWikilinks: z2.boolean().default(false).describe("If true, skip auto-wikilink application (wikilinks are applied by default)")
|
|
699
827
|
},
|
|
700
|
-
async ({ path: notePath, section, task, position, completed, commit }) => {
|
|
828
|
+
async ({ path: notePath, section, task, position, completed, commit, skipWikilinks }) => {
|
|
701
829
|
try {
|
|
702
|
-
const fullPath =
|
|
830
|
+
const fullPath = path5.join(vaultPath2, notePath);
|
|
703
831
|
try {
|
|
704
832
|
await fs3.access(fullPath);
|
|
705
833
|
} catch {
|
|
@@ -720,8 +848,9 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
720
848
|
};
|
|
721
849
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
722
850
|
}
|
|
851
|
+
const { content: wikilinkedTask, wikilinkInfo } = maybeApplyWikilinks(task.trim(), skipWikilinks);
|
|
723
852
|
const checkbox = completed ? "[x]" : "[ ]";
|
|
724
|
-
const taskLine = `- ${checkbox} ${
|
|
853
|
+
const taskLine = `- ${checkbox} ${wikilinkedTask}`;
|
|
725
854
|
const updatedContent = insertInSection(
|
|
726
855
|
fileContent,
|
|
727
856
|
sectionBoundary,
|
|
@@ -743,7 +872,8 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
743
872
|
success: true,
|
|
744
873
|
message: `Added task to section "${sectionBoundary.name}" in ${notePath}`,
|
|
745
874
|
path: notePath,
|
|
746
|
-
preview: taskLine
|
|
875
|
+
preview: taskLine + (wikilinkInfo ? `
|
|
876
|
+
(${wikilinkInfo})` : ""),
|
|
747
877
|
gitCommit,
|
|
748
878
|
gitError
|
|
749
879
|
};
|
|
@@ -764,7 +894,7 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
764
894
|
// src/tools/frontmatter.ts
|
|
765
895
|
import { z as z3 } from "zod";
|
|
766
896
|
import fs4 from "fs/promises";
|
|
767
|
-
import
|
|
897
|
+
import path6 from "path";
|
|
768
898
|
function registerFrontmatterTools(server2, vaultPath2) {
|
|
769
899
|
server2.tool(
|
|
770
900
|
"vault_update_frontmatter",
|
|
@@ -776,7 +906,7 @@ function registerFrontmatterTools(server2, vaultPath2) {
|
|
|
776
906
|
},
|
|
777
907
|
async ({ path: notePath, frontmatter: updates, commit }) => {
|
|
778
908
|
try {
|
|
779
|
-
const fullPath =
|
|
909
|
+
const fullPath = path6.join(vaultPath2, notePath);
|
|
780
910
|
try {
|
|
781
911
|
await fs4.access(fullPath);
|
|
782
912
|
} catch {
|
|
@@ -832,7 +962,7 @@ function registerFrontmatterTools(server2, vaultPath2) {
|
|
|
832
962
|
},
|
|
833
963
|
async ({ path: notePath, key, value, commit }) => {
|
|
834
964
|
try {
|
|
835
|
-
const fullPath =
|
|
965
|
+
const fullPath = path6.join(vaultPath2, notePath);
|
|
836
966
|
try {
|
|
837
967
|
await fs4.access(fullPath);
|
|
838
968
|
} catch {
|
|
@@ -889,7 +1019,7 @@ function registerFrontmatterTools(server2, vaultPath2) {
|
|
|
889
1019
|
// src/tools/notes.ts
|
|
890
1020
|
import { z as z4 } from "zod";
|
|
891
1021
|
import fs5 from "fs/promises";
|
|
892
|
-
import
|
|
1022
|
+
import path7 from "path";
|
|
893
1023
|
function registerNoteTools(server2, vaultPath2) {
|
|
894
1024
|
server2.tool(
|
|
895
1025
|
"vault_create_note",
|
|
@@ -911,7 +1041,7 @@ function registerNoteTools(server2, vaultPath2) {
|
|
|
911
1041
|
};
|
|
912
1042
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
913
1043
|
}
|
|
914
|
-
const fullPath =
|
|
1044
|
+
const fullPath = path7.join(vaultPath2, notePath);
|
|
915
1045
|
try {
|
|
916
1046
|
await fs5.access(fullPath);
|
|
917
1047
|
if (!overwrite) {
|
|
@@ -924,7 +1054,7 @@ function registerNoteTools(server2, vaultPath2) {
|
|
|
924
1054
|
}
|
|
925
1055
|
} catch {
|
|
926
1056
|
}
|
|
927
|
-
const dir =
|
|
1057
|
+
const dir = path7.dirname(fullPath);
|
|
928
1058
|
await fs5.mkdir(dir, { recursive: true });
|
|
929
1059
|
await writeVaultFile(vaultPath2, notePath, content, frontmatter);
|
|
930
1060
|
let gitCommit;
|
|
@@ -983,7 +1113,7 @@ Content length: ${content.length} chars`,
|
|
|
983
1113
|
};
|
|
984
1114
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
985
1115
|
}
|
|
986
|
-
const fullPath =
|
|
1116
|
+
const fullPath = path7.join(vaultPath2, notePath);
|
|
987
1117
|
try {
|
|
988
1118
|
await fs5.access(fullPath);
|
|
989
1119
|
} catch {
|
|
@@ -1029,7 +1159,7 @@ Content length: ${content.length} chars`,
|
|
|
1029
1159
|
// src/tools/system.ts
|
|
1030
1160
|
import { z as z5 } from "zod";
|
|
1031
1161
|
import fs6 from "fs/promises";
|
|
1032
|
-
import
|
|
1162
|
+
import path8 from "path";
|
|
1033
1163
|
function registerSystemTools(server2, vaultPath2) {
|
|
1034
1164
|
server2.tool(
|
|
1035
1165
|
"vault_list_sections",
|
|
@@ -1049,7 +1179,7 @@ function registerSystemTools(server2, vaultPath2) {
|
|
|
1049
1179
|
};
|
|
1050
1180
|
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
1051
1181
|
}
|
|
1052
|
-
const fullPath =
|
|
1182
|
+
const fullPath = path8.join(vaultPath2, notePath);
|
|
1053
1183
|
try {
|
|
1054
1184
|
await fs6.access(fullPath);
|
|
1055
1185
|
} catch {
|
|
@@ -1157,18 +1287,18 @@ Message: ${undoResult.undoneCommit.message}` : void 0
|
|
|
1157
1287
|
|
|
1158
1288
|
// src/core/vaultRoot.ts
|
|
1159
1289
|
import * as fs7 from "fs";
|
|
1160
|
-
import * as
|
|
1290
|
+
import * as path9 from "path";
|
|
1161
1291
|
var VAULT_MARKERS = [".obsidian", ".claude"];
|
|
1162
1292
|
function findVaultRoot(startPath) {
|
|
1163
|
-
let current =
|
|
1293
|
+
let current = path9.resolve(startPath || process.cwd());
|
|
1164
1294
|
while (true) {
|
|
1165
1295
|
for (const marker of VAULT_MARKERS) {
|
|
1166
|
-
const markerPath =
|
|
1296
|
+
const markerPath = path9.join(current, marker);
|
|
1167
1297
|
if (fs7.existsSync(markerPath) && fs7.statSync(markerPath).isDirectory()) {
|
|
1168
1298
|
return current;
|
|
1169
1299
|
}
|
|
1170
1300
|
}
|
|
1171
|
-
const parent =
|
|
1301
|
+
const parent = path9.dirname(current);
|
|
1172
1302
|
if (parent === current) {
|
|
1173
1303
|
return startPath || process.cwd();
|
|
1174
1304
|
}
|
|
@@ -1184,6 +1314,9 @@ var server = new McpServer({
|
|
|
1184
1314
|
var vaultPath = process.env.PROJECT_PATH || findVaultRoot();
|
|
1185
1315
|
console.error(`[Crank] Starting Flywheel Crank MCP server`);
|
|
1186
1316
|
console.error(`[Crank] Vault path: ${vaultPath}`);
|
|
1317
|
+
initializeEntityIndex(vaultPath).catch((err) => {
|
|
1318
|
+
console.error(`[Crank] Entity index initialization failed: ${err}`);
|
|
1319
|
+
});
|
|
1187
1320
|
registerMutationTools(server, vaultPath);
|
|
1188
1321
|
registerTaskTools(server, vaultPath);
|
|
1189
1322
|
registerFrontmatterTools(server, vaultPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-crank",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Deterministic vault mutations for Obsidian via MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
39
|
+
"@velvetmonkey/vault-core": "^0.1.0",
|
|
39
40
|
"gray-matter": "^4.0.3",
|
|
40
41
|
"zod": "^3.22.4",
|
|
41
42
|
"simple-git": "^3.22.0"
|