@velvetmonkey/flywheel-memory 2.0.59 → 2.0.60
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 +264 -134
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -13184,7 +13184,7 @@ function ensureSectionExists(content, section, notePath) {
|
|
|
13184
13184
|
return { error: errorResult(notePath, message) };
|
|
13185
13185
|
}
|
|
13186
13186
|
async function withVaultFile(options, operation) {
|
|
13187
|
-
const { vaultPath: vaultPath2, notePath, commit, commitPrefix, section, actionDescription, scoping } = options;
|
|
13187
|
+
const { vaultPath: vaultPath2, notePath, commit, commitPrefix, section, actionDescription, scoping, dryRun } = options;
|
|
13188
13188
|
try {
|
|
13189
13189
|
const existsError = await ensureFileExists(vaultPath2, notePath);
|
|
13190
13190
|
if (existsError) {
|
|
@@ -13219,6 +13219,17 @@ async function withVaultFile(options, operation) {
|
|
|
13219
13219
|
if ("error" in result) {
|
|
13220
13220
|
return formatMcpResult(result.error);
|
|
13221
13221
|
}
|
|
13222
|
+
const { opResult, frontmatter, lineEnding } = result;
|
|
13223
|
+
if (dryRun) {
|
|
13224
|
+
const dryResult = successResult(notePath, `[dry run] ${opResult.message}`, {}, {
|
|
13225
|
+
preview: opResult.preview,
|
|
13226
|
+
warnings: opResult.warnings,
|
|
13227
|
+
outputIssues: opResult.outputIssues,
|
|
13228
|
+
normalizationChanges: opResult.normalizationChanges,
|
|
13229
|
+
dryRun: true
|
|
13230
|
+
});
|
|
13231
|
+
return formatMcpResult(dryResult);
|
|
13232
|
+
}
|
|
13222
13233
|
const fullPath = path21.join(vaultPath2, notePath);
|
|
13223
13234
|
const statBefore = await fs19.stat(fullPath);
|
|
13224
13235
|
if (statBefore.mtimeMs !== result.mtimeMs) {
|
|
@@ -13228,7 +13239,6 @@ async function withVaultFile(options, operation) {
|
|
|
13228
13239
|
return formatMcpResult(result.error);
|
|
13229
13240
|
}
|
|
13230
13241
|
}
|
|
13231
|
-
const { opResult, frontmatter, lineEnding } = result;
|
|
13232
13242
|
let finalFrontmatter = opResult.updatedFrontmatter ?? frontmatter;
|
|
13233
13243
|
if (scoping && (scoping.agent_id || scoping.session_id)) {
|
|
13234
13244
|
finalFrontmatter = injectMutationMetadata(finalFrontmatter, scoping);
|
|
@@ -13256,7 +13266,7 @@ async function withVaultFile(options, operation) {
|
|
|
13256
13266
|
}
|
|
13257
13267
|
}
|
|
13258
13268
|
async function withVaultFrontmatter(options, operation) {
|
|
13259
|
-
const { vaultPath: vaultPath2, notePath, commit, commitPrefix, actionDescription } = options;
|
|
13269
|
+
const { vaultPath: vaultPath2, notePath, commit, commitPrefix, actionDescription, dryRun } = options;
|
|
13260
13270
|
try {
|
|
13261
13271
|
const existsError = await ensureFileExists(vaultPath2, notePath);
|
|
13262
13272
|
if (existsError) {
|
|
@@ -13265,6 +13275,13 @@ async function withVaultFrontmatter(options, operation) {
|
|
|
13265
13275
|
const { content, frontmatter, lineEnding } = await readVaultFile(vaultPath2, notePath);
|
|
13266
13276
|
const ctx = { content, frontmatter, lineEnding, vaultPath: vaultPath2, notePath };
|
|
13267
13277
|
const opResult = await operation(ctx);
|
|
13278
|
+
if (dryRun) {
|
|
13279
|
+
const result2 = successResult(notePath, `[dry run] ${opResult.message}`, {}, {
|
|
13280
|
+
preview: opResult.preview,
|
|
13281
|
+
dryRun: true
|
|
13282
|
+
});
|
|
13283
|
+
return formatMcpResult(result2);
|
|
13284
|
+
}
|
|
13268
13285
|
await writeVaultFile(vaultPath2, notePath, content, opResult.updatedFrontmatter, lineEnding);
|
|
13269
13286
|
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, commitPrefix);
|
|
13270
13287
|
const result = successResult(notePath, opResult.message, gitInfo, {
|
|
@@ -13360,10 +13377,11 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13360
13377
|
normalize: z11.boolean().default(true).describe("Auto-fix common issues before formatting (replace \u2022 with -, trim excessive whitespace, etc.)"),
|
|
13361
13378
|
guardrails: z11.enum(["warn", "strict", "off"]).default("warn").describe('Output validation mode: "warn" returns issues but proceeds, "strict" blocks on errors, "off" disables'),
|
|
13362
13379
|
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."),
|
|
13380
|
+
dry_run: z11.boolean().optional().default(false).describe("Preview changes without writing to disk"),
|
|
13363
13381
|
agent_id: z11.string().optional().describe('Agent identifier for multi-agent scoping (e.g., "claude-opus", "planning-agent")'),
|
|
13364
13382
|
session_id: z11.string().optional().describe('Session identifier for conversation scoping (e.g., "sess-abc123")')
|
|
13365
13383
|
},
|
|
13366
|
-
async ({ path: notePath, section, content, create_if_missing, position, format, commit, skipWikilinks, preserveListNesting, bumpHeadings, suggestOutgoingLinks, maxSuggestions, validate, normalize, guardrails, linkedEntities, agent_id, session_id }) => {
|
|
13384
|
+
async ({ path: notePath, section, content, create_if_missing, position, format, commit, skipWikilinks, preserveListNesting, bumpHeadings, suggestOutgoingLinks, maxSuggestions, validate, normalize, guardrails, linkedEntities, dry_run, agent_id, session_id }) => {
|
|
13367
13385
|
let noteCreated = false;
|
|
13368
13386
|
let templateUsed;
|
|
13369
13387
|
if (create_if_missing) {
|
|
@@ -13385,7 +13403,8 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13385
13403
|
commitPrefix: "[Flywheel:Add]",
|
|
13386
13404
|
section,
|
|
13387
13405
|
actionDescription: "add content",
|
|
13388
|
-
scoping: agent_id || session_id ? { agent_id, session_id } : void 0
|
|
13406
|
+
scoping: agent_id || session_id ? { agent_id, session_id } : void 0,
|
|
13407
|
+
dryRun: dry_run
|
|
13389
13408
|
},
|
|
13390
13409
|
async (ctx) => {
|
|
13391
13410
|
const validationResult = runValidationPipeline(content, format, {
|
|
@@ -13454,10 +13473,11 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13454
13473
|
mode: z11.enum(["first", "last", "all"]).default("first").describe("Which matches to remove"),
|
|
13455
13474
|
useRegex: z11.boolean().default(false).describe("Treat pattern as regex"),
|
|
13456
13475
|
commit: z11.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
|
|
13476
|
+
dry_run: z11.boolean().optional().default(false).describe("Preview changes without writing to disk"),
|
|
13457
13477
|
agent_id: z11.string().optional().describe("Agent identifier for multi-agent scoping"),
|
|
13458
13478
|
session_id: z11.string().optional().describe("Session identifier for conversation scoping")
|
|
13459
13479
|
},
|
|
13460
|
-
async ({ path: notePath, section, pattern, mode, useRegex, commit, agent_id, session_id }) => {
|
|
13480
|
+
async ({ path: notePath, section, pattern, mode, useRegex, commit, dry_run, agent_id, session_id }) => {
|
|
13461
13481
|
return withVaultFile(
|
|
13462
13482
|
{
|
|
13463
13483
|
vaultPath: vaultPath2,
|
|
@@ -13466,7 +13486,8 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13466
13486
|
commitPrefix: "[Flywheel:Remove]",
|
|
13467
13487
|
section,
|
|
13468
13488
|
actionDescription: "remove content",
|
|
13469
|
-
scoping: agent_id || session_id ? { agent_id, session_id } : void 0
|
|
13489
|
+
scoping: agent_id || session_id ? { agent_id, session_id } : void 0,
|
|
13490
|
+
dryRun: dry_run
|
|
13470
13491
|
},
|
|
13471
13492
|
async (ctx) => {
|
|
13472
13493
|
const removeResult = removeFromSection(
|
|
@@ -13505,10 +13526,11 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13505
13526
|
validate: z11.boolean().default(true).describe("Check input for common issues (double timestamps, non-markdown bullets, etc.)"),
|
|
13506
13527
|
normalize: z11.boolean().default(true).describe("Auto-fix common issues before formatting (replace \u2022 with -, trim excessive whitespace, etc.)"),
|
|
13507
13528
|
guardrails: z11.enum(["warn", "strict", "off"]).default("warn").describe('Output validation mode: "warn" returns issues but proceeds, "strict" blocks on errors, "off" disables'),
|
|
13529
|
+
dry_run: z11.boolean().optional().default(false).describe("Preview changes without writing to disk"),
|
|
13508
13530
|
agent_id: z11.string().optional().describe("Agent identifier for multi-agent scoping"),
|
|
13509
13531
|
session_id: z11.string().optional().describe("Session identifier for conversation scoping")
|
|
13510
13532
|
},
|
|
13511
|
-
async ({ path: notePath, section, search, replacement, mode, useRegex, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, validate, normalize, guardrails, agent_id, session_id }) => {
|
|
13533
|
+
async ({ path: notePath, section, search, replacement, mode, useRegex, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, validate, normalize, guardrails, dry_run, agent_id, session_id }) => {
|
|
13512
13534
|
return withVaultFile(
|
|
13513
13535
|
{
|
|
13514
13536
|
vaultPath: vaultPath2,
|
|
@@ -13517,7 +13539,8 @@ Example: vault_add_to_section({ path: "daily/2026-02-15.md", section: "Log", con
|
|
|
13517
13539
|
commitPrefix: "[Flywheel:Replace]",
|
|
13518
13540
|
section,
|
|
13519
13541
|
actionDescription: "replace content",
|
|
13520
|
-
scoping: agent_id || session_id ? { agent_id, session_id } : void 0
|
|
13542
|
+
scoping: agent_id || session_id ? { agent_id, session_id } : void 0,
|
|
13543
|
+
dryRun: dry_run
|
|
13521
13544
|
},
|
|
13522
13545
|
async (ctx) => {
|
|
13523
13546
|
const validationResult = runValidationPipeline(replacement, "plain", {
|
|
@@ -13633,10 +13656,11 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
13633
13656
|
task: z12.string().describe("Task text to find (partial match supported)"),
|
|
13634
13657
|
section: z12.string().optional().describe("Optional: limit search to this section"),
|
|
13635
13658
|
commit: z12.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
|
|
13659
|
+
dry_run: z12.boolean().optional().default(false).describe("Preview changes without writing to disk"),
|
|
13636
13660
|
agent_id: z12.string().optional().describe("Agent identifier for multi-agent scoping"),
|
|
13637
13661
|
session_id: z12.string().optional().describe("Session identifier for conversation scoping")
|
|
13638
13662
|
},
|
|
13639
|
-
async ({ path: notePath, task, section, commit, agent_id, session_id }) => {
|
|
13663
|
+
async ({ path: notePath, task, section, commit, dry_run, agent_id, session_id }) => {
|
|
13640
13664
|
try {
|
|
13641
13665
|
const existsError = await ensureFileExists(vaultPath2, notePath);
|
|
13642
13666
|
if (existsError) {
|
|
@@ -13665,6 +13689,16 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
13665
13689
|
if (!toggleResult) {
|
|
13666
13690
|
return formatMcpResult(errorResult(notePath, "Failed to toggle task"));
|
|
13667
13691
|
}
|
|
13692
|
+
const newStatus = toggleResult.newState ? "completed" : "incomplete";
|
|
13693
|
+
const checkbox = toggleResult.newState ? "[x]" : "[ ]";
|
|
13694
|
+
if (dry_run) {
|
|
13695
|
+
return formatMcpResult(
|
|
13696
|
+
successResult(notePath, `[dry run] Toggled task to ${newStatus} in ${notePath}`, {}, {
|
|
13697
|
+
preview: `${checkbox} ${matchingTask.text}`,
|
|
13698
|
+
dryRun: true
|
|
13699
|
+
})
|
|
13700
|
+
);
|
|
13701
|
+
}
|
|
13668
13702
|
let finalFrontmatter = frontmatter;
|
|
13669
13703
|
if (agent_id || session_id) {
|
|
13670
13704
|
finalFrontmatter = injectMutationMetadata(frontmatter, { agent_id, session_id });
|
|
@@ -13673,8 +13707,6 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
13673
13707
|
await updateTaskCacheForFile(vaultPath2, notePath).catch(() => {
|
|
13674
13708
|
});
|
|
13675
13709
|
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Task]");
|
|
13676
|
-
const newStatus = toggleResult.newState ? "completed" : "incomplete";
|
|
13677
|
-
const checkbox = toggleResult.newState ? "[x]" : "[ ]";
|
|
13678
13710
|
return formatMcpResult(
|
|
13679
13711
|
successResult(notePath, `Toggled task to ${newStatus} in ${notePath}`, gitInfo, {
|
|
13680
13712
|
preview: `${checkbox} ${matchingTask.text}`
|
|
@@ -13704,10 +13736,11 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
13704
13736
|
validate: z12.boolean().default(true).describe("Check input for common issues"),
|
|
13705
13737
|
normalize: z12.boolean().default(true).describe("Auto-fix common issues before formatting"),
|
|
13706
13738
|
guardrails: z12.enum(["warn", "strict", "off"]).default("warn").describe("Output validation mode"),
|
|
13739
|
+
dry_run: z12.boolean().optional().default(false).describe("Preview changes without writing to disk"),
|
|
13707
13740
|
agent_id: z12.string().optional().describe("Agent identifier for multi-agent scoping"),
|
|
13708
13741
|
session_id: z12.string().optional().describe("Session identifier for conversation scoping")
|
|
13709
13742
|
},
|
|
13710
|
-
async ({ path: notePath, section, task, position, completed, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, preserveListNesting, validate, normalize, guardrails, agent_id, session_id }) => {
|
|
13743
|
+
async ({ path: notePath, section, task, position, completed, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, preserveListNesting, validate, normalize, guardrails, dry_run, agent_id, session_id }) => {
|
|
13711
13744
|
return withVaultFile(
|
|
13712
13745
|
{
|
|
13713
13746
|
vaultPath: vaultPath2,
|
|
@@ -13716,7 +13749,8 @@ function registerTaskTools(server2, vaultPath2) {
|
|
|
13716
13749
|
commitPrefix: "[Flywheel:Task]",
|
|
13717
13750
|
section,
|
|
13718
13751
|
actionDescription: "add task",
|
|
13719
|
-
scoping: agent_id || session_id ? { agent_id, session_id } : void 0
|
|
13752
|
+
scoping: agent_id || session_id ? { agent_id, session_id } : void 0,
|
|
13753
|
+
dryRun: dry_run
|
|
13720
13754
|
},
|
|
13721
13755
|
async (ctx) => {
|
|
13722
13756
|
const validationResult = runValidationPipeline(task.trim(), "task", {
|
|
@@ -13774,16 +13808,18 @@ Example: vault_update_frontmatter({ path: "projects/alpha.md", frontmatter: { st
|
|
|
13774
13808
|
path: z13.string().describe("Vault-relative path to the note"),
|
|
13775
13809
|
frontmatter: z13.record(z13.any()).describe("Frontmatter fields to update (JSON object)"),
|
|
13776
13810
|
only_if_missing: z13.boolean().default(false).describe("If true, only add fields that don't already exist in the frontmatter (skip existing keys)"),
|
|
13777
|
-
commit: z13.boolean().default(false).describe("If true, commit this change to git (creates undo point)")
|
|
13811
|
+
commit: z13.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
|
|
13812
|
+
dry_run: z13.boolean().optional().default(false).describe("Preview changes without writing to disk")
|
|
13778
13813
|
},
|
|
13779
|
-
async ({ path: notePath, frontmatter: updates, only_if_missing, commit }) => {
|
|
13814
|
+
async ({ path: notePath, frontmatter: updates, only_if_missing, commit, dry_run }) => {
|
|
13780
13815
|
return withVaultFrontmatter(
|
|
13781
13816
|
{
|
|
13782
13817
|
vaultPath: vaultPath2,
|
|
13783
13818
|
notePath,
|
|
13784
13819
|
commit,
|
|
13785
13820
|
commitPrefix: "[Flywheel:FM]",
|
|
13786
|
-
actionDescription: "update frontmatter"
|
|
13821
|
+
actionDescription: "update frontmatter",
|
|
13822
|
+
dryRun: dry_run
|
|
13787
13823
|
},
|
|
13788
13824
|
async (ctx) => {
|
|
13789
13825
|
let effectiveUpdates;
|
|
@@ -13841,10 +13877,11 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
13841
13877
|
skipWikilinks: z14.boolean().default(false).describe("If true, skip auto-wikilink application (wikilinks are applied by default)"),
|
|
13842
13878
|
suggestOutgoingLinks: z14.boolean().default(true).describe('Append suggested outgoing wikilinks based on content (e.g., "\u2192 [[AI]], [[Philosophy]]").'),
|
|
13843
13879
|
maxSuggestions: z14.number().min(1).max(10).default(5).describe("Maximum number of suggested wikilinks to append (1-10, default: 5)"),
|
|
13880
|
+
dry_run: z14.boolean().optional().default(false).describe("Preview changes without writing to disk"),
|
|
13844
13881
|
agent_id: z14.string().optional().describe("Agent identifier for multi-agent scoping"),
|
|
13845
13882
|
session_id: z14.string().optional().describe("Session identifier for conversation scoping")
|
|
13846
13883
|
},
|
|
13847
|
-
async ({ path: notePath, content, template, frontmatter, overwrite, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, agent_id, session_id }) => {
|
|
13884
|
+
async ({ path: notePath, content, template, frontmatter, overwrite, commit, skipWikilinks, suggestOutgoingLinks, maxSuggestions, dry_run, agent_id, session_id }) => {
|
|
13848
13885
|
try {
|
|
13849
13886
|
if (!validatePath(vaultPath2, notePath)) {
|
|
13850
13887
|
return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
|
|
@@ -13922,8 +13959,6 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
13922
13959
|
if (agent_id || session_id) {
|
|
13923
13960
|
finalFrontmatter = injectMutationMetadata(effectiveFrontmatter, { agent_id, session_id });
|
|
13924
13961
|
}
|
|
13925
|
-
await writeVaultFile(vaultPath2, notePath, processedContent, finalFrontmatter);
|
|
13926
|
-
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Create]");
|
|
13927
13962
|
const infoLines = [wikilinkInfo, suggestInfo].filter(Boolean);
|
|
13928
13963
|
const previewLines = [
|
|
13929
13964
|
`Frontmatter fields: ${Object.keys(effectiveFrontmatter).join(", ") || "none"}`,
|
|
@@ -13946,6 +13981,17 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
13946
13981
|
previewLines.push('Tip: Add aliases to frontmatter for flexible wikilink matching (e.g., aliases: ["Short Name"])');
|
|
13947
13982
|
}
|
|
13948
13983
|
}
|
|
13984
|
+
if (dry_run) {
|
|
13985
|
+
return formatMcpResult(
|
|
13986
|
+
successResult(notePath, `[dry run] Would create note: ${notePath}`, {}, {
|
|
13987
|
+
preview: previewLines.join("\n"),
|
|
13988
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
13989
|
+
dryRun: true
|
|
13990
|
+
})
|
|
13991
|
+
);
|
|
13992
|
+
}
|
|
13993
|
+
await writeVaultFile(vaultPath2, notePath, processedContent, finalFrontmatter);
|
|
13994
|
+
const gitInfo = await handleGitCommit(vaultPath2, notePath, commit, "[Flywheel:Create]");
|
|
13949
13995
|
return formatMcpResult(
|
|
13950
13996
|
successResult(notePath, `Created note: ${notePath}`, gitInfo, {
|
|
13951
13997
|
preview: previewLines.join("\n"),
|
|
@@ -13965,9 +14011,10 @@ function registerNoteTools(server2, vaultPath2, getIndex) {
|
|
|
13965
14011
|
{
|
|
13966
14012
|
path: z14.string().describe("Vault-relative path to the note to delete"),
|
|
13967
14013
|
confirm: z14.boolean().default(false).describe("Must be true to confirm deletion"),
|
|
13968
|
-
commit: z14.boolean().default(false).describe("If true, commit this change to git (creates undo point)")
|
|
14014
|
+
commit: z14.boolean().default(false).describe("If true, commit this change to git (creates undo point)"),
|
|
14015
|
+
dry_run: z14.boolean().optional().default(false).describe("Preview what would be deleted without actually deleting")
|
|
13969
14016
|
},
|
|
13970
|
-
async ({ path: notePath, confirm, commit }) => {
|
|
14017
|
+
async ({ path: notePath, confirm, commit, dry_run }) => {
|
|
13971
14018
|
try {
|
|
13972
14019
|
if (!validatePath(vaultPath2, notePath)) {
|
|
13973
14020
|
return formatMcpResult(errorResult(notePath, "Invalid path: path traversal not allowed"));
|
|
@@ -13993,6 +14040,19 @@ ${sources}`;
|
|
|
13993
14040
|
} catch {
|
|
13994
14041
|
}
|
|
13995
14042
|
}
|
|
14043
|
+
if (dry_run) {
|
|
14044
|
+
const previewLines = [`Would delete: ${notePath}`];
|
|
14045
|
+
if (backlinkWarning) {
|
|
14046
|
+
previewLines.push("");
|
|
14047
|
+
previewLines.push("Warning: " + backlinkWarning);
|
|
14048
|
+
}
|
|
14049
|
+
return formatMcpResult(
|
|
14050
|
+
successResult(notePath, `[dry run] Would delete note: ${notePath}`, {}, {
|
|
14051
|
+
preview: previewLines.join("\n"),
|
|
14052
|
+
dryRun: true
|
|
14053
|
+
})
|
|
14054
|
+
);
|
|
14055
|
+
}
|
|
13996
14056
|
if (!confirm) {
|
|
13997
14057
|
const previewLines = ["Deletion requires explicit confirmation (confirm=true)"];
|
|
13998
14058
|
if (backlinkWarning) {
|
|
@@ -14127,9 +14187,10 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
14127
14187
|
oldPath: z15.string().describe('Vault-relative path to move from (e.g., "inbox/note.md")'),
|
|
14128
14188
|
newPath: z15.string().describe('Vault-relative path to move to (e.g., "projects/note.md")'),
|
|
14129
14189
|
updateBacklinks: z15.boolean().default(true).describe("If true (default), updates all backlinks pointing to this note"),
|
|
14130
|
-
commit: z15.boolean().default(false).describe("If true, commit all changes to git")
|
|
14190
|
+
commit: z15.boolean().default(false).describe("If true, commit all changes to git"),
|
|
14191
|
+
dry_run: z15.boolean().optional().default(false).describe("Preview what would change without moving any files")
|
|
14131
14192
|
},
|
|
14132
|
-
async ({ oldPath, newPath, updateBacklinks, commit }) => {
|
|
14193
|
+
async ({ oldPath, newPath, updateBacklinks, commit, dry_run }) => {
|
|
14133
14194
|
try {
|
|
14134
14195
|
if (!validatePath(vaultPath2, oldPath)) {
|
|
14135
14196
|
const result2 = {
|
|
@@ -14175,29 +14236,60 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
14175
14236
|
const oldTitle = getTitleFromPath(oldPath);
|
|
14176
14237
|
const newTitle = getTitleFromPath(newPath);
|
|
14177
14238
|
let backlinkCount = 0;
|
|
14178
|
-
let updatedBacklinks = 0;
|
|
14179
14239
|
const backlinkUpdates = [];
|
|
14180
14240
|
if (updateBacklinks && oldTitle.toLowerCase() !== newTitle.toLowerCase()) {
|
|
14181
|
-
const allOldTitles = [oldTitle, ...aliases];
|
|
14182
14241
|
const backlinks = await findBacklinks(vaultPath2, oldTitle, aliases);
|
|
14183
14242
|
backlinkCount = backlinks.reduce((sum, b) => sum + b.links.length, 0);
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
vaultPath2,
|
|
14188
|
-
backlink.path,
|
|
14189
|
-
allOldTitles,
|
|
14190
|
-
newTitle
|
|
14191
|
-
);
|
|
14192
|
-
if (updateResult.updated) {
|
|
14193
|
-
updatedBacklinks += updateResult.linksUpdated;
|
|
14243
|
+
if (dry_run) {
|
|
14244
|
+
for (const backlink of backlinks) {
|
|
14245
|
+
if (backlink.path === oldPath) continue;
|
|
14194
14246
|
backlinkUpdates.push({
|
|
14195
14247
|
path: backlink.path,
|
|
14196
|
-
linksUpdated:
|
|
14248
|
+
linksUpdated: backlink.links.length
|
|
14197
14249
|
});
|
|
14198
14250
|
}
|
|
14251
|
+
} else {
|
|
14252
|
+
const allOldTitles = [oldTitle, ...aliases];
|
|
14253
|
+
for (const backlink of backlinks) {
|
|
14254
|
+
if (backlink.path === oldPath) continue;
|
|
14255
|
+
const updateResult = await updateBacklinksInFile(
|
|
14256
|
+
vaultPath2,
|
|
14257
|
+
backlink.path,
|
|
14258
|
+
allOldTitles,
|
|
14259
|
+
newTitle
|
|
14260
|
+
);
|
|
14261
|
+
if (updateResult.updated) {
|
|
14262
|
+
backlinkUpdates.push({
|
|
14263
|
+
path: backlink.path,
|
|
14264
|
+
linksUpdated: updateResult.linksUpdated
|
|
14265
|
+
});
|
|
14266
|
+
}
|
|
14267
|
+
}
|
|
14199
14268
|
}
|
|
14200
14269
|
}
|
|
14270
|
+
const updatedBacklinks = backlinkUpdates.reduce((sum, b) => sum + b.linksUpdated, 0);
|
|
14271
|
+
const previewLines = [
|
|
14272
|
+
`${dry_run ? "Would move" : "Moved"}: ${oldPath} \u2192 ${newPath}`
|
|
14273
|
+
];
|
|
14274
|
+
if (backlinkCount > 0) {
|
|
14275
|
+
previewLines.push(`Backlinks found: ${backlinkCount}`);
|
|
14276
|
+
previewLines.push(`Backlinks ${dry_run ? "to update" : "updated"}: ${updatedBacklinks}`);
|
|
14277
|
+
if (backlinkUpdates.length > 0) {
|
|
14278
|
+
previewLines.push(`Files ${dry_run ? "to modify" : "modified"}: ${backlinkUpdates.map((b) => b.path).join(", ")}`);
|
|
14279
|
+
}
|
|
14280
|
+
} else {
|
|
14281
|
+
previewLines.push("No backlinks found");
|
|
14282
|
+
}
|
|
14283
|
+
if (dry_run) {
|
|
14284
|
+
const result2 = {
|
|
14285
|
+
success: true,
|
|
14286
|
+
message: `[dry run] Would move note: ${oldPath} \u2192 ${newPath}`,
|
|
14287
|
+
path: newPath,
|
|
14288
|
+
preview: previewLines.join("\n"),
|
|
14289
|
+
dryRun: true
|
|
14290
|
+
};
|
|
14291
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
14292
|
+
}
|
|
14201
14293
|
const destDir = path24.dirname(newFullPath);
|
|
14202
14294
|
await fs22.mkdir(destDir, { recursive: true });
|
|
14203
14295
|
await fs22.rename(oldFullPath, newFullPath);
|
|
@@ -14220,18 +14312,6 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
14220
14312
|
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
14221
14313
|
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
14222
14314
|
});
|
|
14223
|
-
const previewLines = [
|
|
14224
|
-
`Moved: ${oldPath} \u2192 ${newPath}`
|
|
14225
|
-
];
|
|
14226
|
-
if (backlinkCount > 0) {
|
|
14227
|
-
previewLines.push(`Backlinks found: ${backlinkCount}`);
|
|
14228
|
-
previewLines.push(`Backlinks updated: ${updatedBacklinks}`);
|
|
14229
|
-
if (backlinkUpdates.length > 0) {
|
|
14230
|
-
previewLines.push(`Files modified: ${backlinkUpdates.map((b) => b.path).join(", ")}`);
|
|
14231
|
-
}
|
|
14232
|
-
} else {
|
|
14233
|
-
previewLines.push("No backlinks found");
|
|
14234
|
-
}
|
|
14235
14315
|
const result = {
|
|
14236
14316
|
success: true,
|
|
14237
14317
|
message: `Moved note: ${oldPath} \u2192 ${newPath}`,
|
|
@@ -14260,9 +14340,10 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
14260
14340
|
path: z15.string().describe("Vault-relative path to the note to rename"),
|
|
14261
14341
|
newTitle: z15.string().describe("New title for the note (without .md extension)"),
|
|
14262
14342
|
updateBacklinks: z15.boolean().default(true).describe("If true (default), updates all backlinks pointing to this note"),
|
|
14263
|
-
commit: z15.boolean().default(false).describe("If true, commit all changes to git")
|
|
14343
|
+
commit: z15.boolean().default(false).describe("If true, commit all changes to git"),
|
|
14344
|
+
dry_run: z15.boolean().optional().default(false).describe("Preview what would change without renaming any files")
|
|
14264
14345
|
},
|
|
14265
|
-
async ({ path: notePath, newTitle, updateBacklinks, commit }) => {
|
|
14346
|
+
async ({ path: notePath, newTitle, updateBacklinks, commit, dry_run }) => {
|
|
14266
14347
|
try {
|
|
14267
14348
|
if (!validatePath(vaultPath2, notePath)) {
|
|
14268
14349
|
const result2 = {
|
|
@@ -14315,29 +14396,60 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
14315
14396
|
const aliases = extractAliases2(parsed.data);
|
|
14316
14397
|
const oldTitle = getTitleFromPath(notePath);
|
|
14317
14398
|
let backlinkCount = 0;
|
|
14318
|
-
let updatedBacklinks = 0;
|
|
14319
14399
|
const backlinkUpdates = [];
|
|
14320
14400
|
if (updateBacklinks && oldTitle.toLowerCase() !== sanitizedTitle.toLowerCase()) {
|
|
14321
|
-
const allOldTitles = [oldTitle, ...aliases];
|
|
14322
14401
|
const backlinks = await findBacklinks(vaultPath2, oldTitle, aliases);
|
|
14323
14402
|
backlinkCount = backlinks.reduce((sum, b) => sum + b.links.length, 0);
|
|
14324
|
-
|
|
14325
|
-
|
|
14326
|
-
|
|
14327
|
-
vaultPath2,
|
|
14328
|
-
backlink.path,
|
|
14329
|
-
allOldTitles,
|
|
14330
|
-
sanitizedTitle
|
|
14331
|
-
);
|
|
14332
|
-
if (updateResult.updated) {
|
|
14333
|
-
updatedBacklinks += updateResult.linksUpdated;
|
|
14403
|
+
if (dry_run) {
|
|
14404
|
+
for (const backlink of backlinks) {
|
|
14405
|
+
if (backlink.path === notePath) continue;
|
|
14334
14406
|
backlinkUpdates.push({
|
|
14335
14407
|
path: backlink.path,
|
|
14336
|
-
linksUpdated:
|
|
14408
|
+
linksUpdated: backlink.links.length
|
|
14337
14409
|
});
|
|
14338
14410
|
}
|
|
14411
|
+
} else {
|
|
14412
|
+
const allOldTitles = [oldTitle, ...aliases];
|
|
14413
|
+
for (const backlink of backlinks) {
|
|
14414
|
+
if (backlink.path === notePath) continue;
|
|
14415
|
+
const updateResult = await updateBacklinksInFile(
|
|
14416
|
+
vaultPath2,
|
|
14417
|
+
backlink.path,
|
|
14418
|
+
allOldTitles,
|
|
14419
|
+
sanitizedTitle
|
|
14420
|
+
);
|
|
14421
|
+
if (updateResult.updated) {
|
|
14422
|
+
backlinkUpdates.push({
|
|
14423
|
+
path: backlink.path,
|
|
14424
|
+
linksUpdated: updateResult.linksUpdated
|
|
14425
|
+
});
|
|
14426
|
+
}
|
|
14427
|
+
}
|
|
14339
14428
|
}
|
|
14340
14429
|
}
|
|
14430
|
+
const updatedBacklinks = backlinkUpdates.reduce((sum, b) => sum + b.linksUpdated, 0);
|
|
14431
|
+
const previewLines = [
|
|
14432
|
+
`${dry_run ? "Would rename" : "Renamed"}: "${oldTitle}" \u2192 "${sanitizedTitle}"`
|
|
14433
|
+
];
|
|
14434
|
+
if (backlinkCount > 0) {
|
|
14435
|
+
previewLines.push(`Backlinks found: ${backlinkCount}`);
|
|
14436
|
+
previewLines.push(`Backlinks ${dry_run ? "to update" : "updated"}: ${updatedBacklinks}`);
|
|
14437
|
+
if (backlinkUpdates.length > 0) {
|
|
14438
|
+
previewLines.push(`Files ${dry_run ? "to modify" : "modified"}: ${backlinkUpdates.map((b) => b.path).join(", ")}`);
|
|
14439
|
+
}
|
|
14440
|
+
} else {
|
|
14441
|
+
previewLines.push("No backlinks found");
|
|
14442
|
+
}
|
|
14443
|
+
if (dry_run) {
|
|
14444
|
+
const result2 = {
|
|
14445
|
+
success: true,
|
|
14446
|
+
message: `[dry run] Would rename note: ${oldTitle} \u2192 ${sanitizedTitle}`,
|
|
14447
|
+
path: newPath,
|
|
14448
|
+
preview: previewLines.join("\n"),
|
|
14449
|
+
dryRun: true
|
|
14450
|
+
};
|
|
14451
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
14452
|
+
}
|
|
14341
14453
|
if (fullPath !== newFullPath) {
|
|
14342
14454
|
await fs22.rename(fullPath, newFullPath);
|
|
14343
14455
|
}
|
|
@@ -14360,18 +14472,6 @@ function registerMoveNoteTools(server2, vaultPath2) {
|
|
|
14360
14472
|
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
14361
14473
|
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
14362
14474
|
});
|
|
14363
|
-
const previewLines = [
|
|
14364
|
-
`Renamed: "${oldTitle}" \u2192 "${sanitizedTitle}"`
|
|
14365
|
-
];
|
|
14366
|
-
if (backlinkCount > 0) {
|
|
14367
|
-
previewLines.push(`Backlinks found: ${backlinkCount}`);
|
|
14368
|
-
previewLines.push(`Backlinks updated: ${updatedBacklinks}`);
|
|
14369
|
-
if (backlinkUpdates.length > 0) {
|
|
14370
|
-
previewLines.push(`Files modified: ${backlinkUpdates.map((b) => b.path).join(", ")}`);
|
|
14371
|
-
}
|
|
14372
|
-
} else {
|
|
14373
|
-
previewLines.push("No backlinks found");
|
|
14374
|
-
}
|
|
14375
14475
|
const result = {
|
|
14376
14476
|
success: true,
|
|
14377
14477
|
message: `Renamed note: ${oldTitle} \u2192 ${sanitizedTitle}`,
|
|
@@ -14405,9 +14505,10 @@ function registerMergeTools(server2, vaultPath2) {
|
|
|
14405
14505
|
"Merge a source entity note into a target entity note: adds alias, appends content, updates wikilinks, deletes source",
|
|
14406
14506
|
{
|
|
14407
14507
|
source_path: z16.string().describe("Vault-relative path of the note to merge FROM (will be deleted)"),
|
|
14408
|
-
target_path: z16.string().describe("Vault-relative path of the note to merge INTO (receives alias + content)")
|
|
14508
|
+
target_path: z16.string().describe("Vault-relative path of the note to merge INTO (receives alias + content)"),
|
|
14509
|
+
dry_run: z16.boolean().optional().default(false).describe("Preview merge plan without modifying any files")
|
|
14409
14510
|
},
|
|
14410
|
-
async ({ source_path, target_path }) => {
|
|
14511
|
+
async ({ source_path, target_path, dry_run }) => {
|
|
14411
14512
|
try {
|
|
14412
14513
|
if (!validatePath(vaultPath2, source_path)) {
|
|
14413
14514
|
const result2 = {
|
|
@@ -14478,18 +14579,46 @@ ${trimmedSource}`;
|
|
|
14478
14579
|
const backlinks = await findBacklinks(vaultPath2, sourceTitle, sourceAliases);
|
|
14479
14580
|
let totalBacklinksUpdated = 0;
|
|
14480
14581
|
const modifiedFiles = [];
|
|
14481
|
-
|
|
14482
|
-
|
|
14483
|
-
|
|
14484
|
-
|
|
14485
|
-
backlink.path,
|
|
14486
|
-
allSourceTitles,
|
|
14487
|
-
targetTitle
|
|
14488
|
-
);
|
|
14489
|
-
if (updateResult.updated) {
|
|
14490
|
-
totalBacklinksUpdated += updateResult.linksUpdated;
|
|
14582
|
+
if (dry_run) {
|
|
14583
|
+
for (const backlink of backlinks) {
|
|
14584
|
+
if (backlink.path === source_path || backlink.path === target_path) continue;
|
|
14585
|
+
totalBacklinksUpdated += backlink.links.length;
|
|
14491
14586
|
modifiedFiles.push(backlink.path);
|
|
14492
14587
|
}
|
|
14588
|
+
} else {
|
|
14589
|
+
for (const backlink of backlinks) {
|
|
14590
|
+
if (backlink.path === source_path || backlink.path === target_path) continue;
|
|
14591
|
+
const updateResult = await updateBacklinksInFile(
|
|
14592
|
+
vaultPath2,
|
|
14593
|
+
backlink.path,
|
|
14594
|
+
allSourceTitles,
|
|
14595
|
+
targetTitle
|
|
14596
|
+
);
|
|
14597
|
+
if (updateResult.updated) {
|
|
14598
|
+
totalBacklinksUpdated += updateResult.linksUpdated;
|
|
14599
|
+
modifiedFiles.push(backlink.path);
|
|
14600
|
+
}
|
|
14601
|
+
}
|
|
14602
|
+
}
|
|
14603
|
+
const previewLines = [
|
|
14604
|
+
`${dry_run ? "Would merge" : "Merged"}: "${sourceTitle}" \u2192 "${targetTitle}"`,
|
|
14605
|
+
`Aliases ${dry_run ? "to add" : "added"}: ${allNewAliases.join(", ")}`,
|
|
14606
|
+
`Source content ${dry_run ? "to append" : "appended"}: ${trimmedSource.length > 10 ? "yes" : "no"}`,
|
|
14607
|
+
`Backlinks ${dry_run ? "to update" : "updated"}: ${totalBacklinksUpdated}`
|
|
14608
|
+
];
|
|
14609
|
+
if (modifiedFiles.length > 0) {
|
|
14610
|
+
previewLines.push(`Files ${dry_run ? "to modify" : "modified"}: ${modifiedFiles.join(", ")}`);
|
|
14611
|
+
}
|
|
14612
|
+
if (dry_run) {
|
|
14613
|
+
const result2 = {
|
|
14614
|
+
success: true,
|
|
14615
|
+
message: `[dry run] Would merge "${sourceTitle}" into "${targetTitle}"`,
|
|
14616
|
+
path: target_path,
|
|
14617
|
+
preview: previewLines.join("\n"),
|
|
14618
|
+
backlinks_updated: totalBacklinksUpdated,
|
|
14619
|
+
dryRun: true
|
|
14620
|
+
};
|
|
14621
|
+
return { content: [{ type: "text", text: JSON.stringify(result2, null, 2) }] };
|
|
14493
14622
|
}
|
|
14494
14623
|
await writeVaultFile(vaultPath2, target_path, targetContent, targetFrontmatter);
|
|
14495
14624
|
const fullSourcePath = `${vaultPath2}/${source_path}`;
|
|
@@ -14497,15 +14626,6 @@ ${trimmedSource}`;
|
|
|
14497
14626
|
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
14498
14627
|
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
14499
14628
|
});
|
|
14500
|
-
const previewLines = [
|
|
14501
|
-
`Merged: "${sourceTitle}" \u2192 "${targetTitle}"`,
|
|
14502
|
-
`Aliases added: ${allNewAliases.join(", ")}`,
|
|
14503
|
-
`Source content appended: ${trimmedSource.length > 10 ? "yes" : "no"}`,
|
|
14504
|
-
`Backlinks updated: ${totalBacklinksUpdated}`
|
|
14505
|
-
];
|
|
14506
|
-
if (modifiedFiles.length > 0) {
|
|
14507
|
-
previewLines.push(`Files modified: ${modifiedFiles.join(", ")}`);
|
|
14508
|
-
}
|
|
14509
14629
|
const result = {
|
|
14510
14630
|
success: true,
|
|
14511
14631
|
message: `Merged "${sourceTitle}" into "${targetTitle}"`,
|
|
@@ -14529,9 +14649,10 @@ ${trimmedSource}`;
|
|
|
14529
14649
|
"Absorb an entity name as an alias of a target note: adds alias to target frontmatter and rewrites all [[source]] links to [[target|source]]. Lighter than merge_entities \u2014 no source note required, no content append, no deletion.",
|
|
14530
14650
|
{
|
|
14531
14651
|
source_name: z16.string().describe('The entity name to absorb (e.g. "Foo")'),
|
|
14532
|
-
target_path: z16.string().describe('Vault-relative path of the target entity note (e.g. "entities/Bar.md")')
|
|
14652
|
+
target_path: z16.string().describe('Vault-relative path of the target entity note (e.g. "entities/Bar.md")'),
|
|
14653
|
+
dry_run: z16.boolean().optional().default(false).describe("Preview what would change without modifying any files")
|
|
14533
14654
|
},
|
|
14534
|
-
async ({ source_name, target_path }) => {
|
|
14655
|
+
async ({ source_name, target_path, dry_run }) => {
|
|
14535
14656
|
try {
|
|
14536
14657
|
if (!validatePath(vaultPath2, target_path)) {
|
|
14537
14658
|
const result2 = {
|
|
@@ -14562,58 +14683,67 @@ ${trimmedSource}`;
|
|
|
14562
14683
|
deduped.add(source_name);
|
|
14563
14684
|
}
|
|
14564
14685
|
targetFrontmatter.aliases = Array.from(deduped);
|
|
14565
|
-
await writeVaultFile(vaultPath2, target_path, targetContent, targetFrontmatter);
|
|
14566
14686
|
const backlinks = await findBacklinks(vaultPath2, source_name, []);
|
|
14567
14687
|
let totalBacklinksUpdated = 0;
|
|
14568
14688
|
const modifiedFiles = [];
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
|
|
14573
|
-
|
|
14574
|
-
} catch {
|
|
14575
|
-
continue;
|
|
14689
|
+
if (dry_run) {
|
|
14690
|
+
for (const backlink of backlinks) {
|
|
14691
|
+
if (backlink.path === target_path) continue;
|
|
14692
|
+
totalBacklinksUpdated += backlink.links.length;
|
|
14693
|
+
modifiedFiles.push(backlink.path);
|
|
14576
14694
|
}
|
|
14577
|
-
|
|
14578
|
-
|
|
14579
|
-
const
|
|
14580
|
-
|
|
14581
|
-
|
|
14582
|
-
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
14586
|
-
return `[[${targetTitle}${displayPart}]]`;
|
|
14695
|
+
} else {
|
|
14696
|
+
await writeVaultFile(vaultPath2, target_path, targetContent, targetFrontmatter);
|
|
14697
|
+
for (const backlink of backlinks) {
|
|
14698
|
+
if (backlink.path === target_path) continue;
|
|
14699
|
+
let fileData;
|
|
14700
|
+
try {
|
|
14701
|
+
fileData = await readVaultFile(vaultPath2, backlink.path);
|
|
14702
|
+
} catch {
|
|
14703
|
+
continue;
|
|
14587
14704
|
}
|
|
14588
|
-
|
|
14589
|
-
|
|
14705
|
+
let content = fileData.content;
|
|
14706
|
+
let linksUpdated = 0;
|
|
14707
|
+
const pattern = new RegExp(
|
|
14708
|
+
`\\[\\[${escapeRegex(source_name)}(\\|[^\\]]+)?\\]\\]`,
|
|
14709
|
+
"gi"
|
|
14710
|
+
);
|
|
14711
|
+
content = content.replace(pattern, (_match, displayPart) => {
|
|
14712
|
+
linksUpdated++;
|
|
14713
|
+
if (displayPart) {
|
|
14714
|
+
return `[[${targetTitle}${displayPart}]]`;
|
|
14715
|
+
}
|
|
14716
|
+
if (source_name.toLowerCase() === targetTitle.toLowerCase()) {
|
|
14717
|
+
return `[[${targetTitle}]]`;
|
|
14718
|
+
}
|
|
14719
|
+
return `[[${targetTitle}|${source_name}]]`;
|
|
14720
|
+
});
|
|
14721
|
+
if (linksUpdated > 0) {
|
|
14722
|
+
await writeVaultFile(vaultPath2, backlink.path, content, fileData.frontmatter);
|
|
14723
|
+
totalBacklinksUpdated += linksUpdated;
|
|
14724
|
+
modifiedFiles.push(backlink.path);
|
|
14590
14725
|
}
|
|
14591
|
-
return `[[${targetTitle}|${source_name}]]`;
|
|
14592
|
-
});
|
|
14593
|
-
if (linksUpdated > 0) {
|
|
14594
|
-
await writeVaultFile(vaultPath2, backlink.path, content, fileData.frontmatter);
|
|
14595
|
-
totalBacklinksUpdated += linksUpdated;
|
|
14596
|
-
modifiedFiles.push(backlink.path);
|
|
14597
14726
|
}
|
|
14727
|
+
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
14728
|
+
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
14729
|
+
});
|
|
14598
14730
|
}
|
|
14599
|
-
initializeEntityIndex(vaultPath2).catch((err) => {
|
|
14600
|
-
console.error(`[Flywheel] Entity cache rebuild failed: ${err}`);
|
|
14601
|
-
});
|
|
14602
14731
|
const aliasAdded = source_name.toLowerCase() !== targetTitle.toLowerCase();
|
|
14603
14732
|
const previewLines = [
|
|
14604
|
-
|
|
14605
|
-
`Alias added: ${aliasAdded ? source_name : "no (matches target title)"}`,
|
|
14606
|
-
`Backlinks updated: ${totalBacklinksUpdated}`
|
|
14733
|
+
`${dry_run ? "Would absorb" : "Absorbed"}: "${source_name}" \u2192 "${targetTitle}"`,
|
|
14734
|
+
`Alias ${dry_run ? "to add" : "added"}: ${aliasAdded ? source_name : "no (matches target title)"}`,
|
|
14735
|
+
`Backlinks ${dry_run ? "to update" : "updated"}: ${totalBacklinksUpdated}`
|
|
14607
14736
|
];
|
|
14608
14737
|
if (modifiedFiles.length > 0) {
|
|
14609
|
-
previewLines.push(`Files modified: ${modifiedFiles.join(", ")}`);
|
|
14738
|
+
previewLines.push(`Files ${dry_run ? "to modify" : "modified"}: ${modifiedFiles.join(", ")}`);
|
|
14610
14739
|
}
|
|
14611
14740
|
const result = {
|
|
14612
14741
|
success: true,
|
|
14613
|
-
message: `Absorbed "${source_name}" as alias of "${targetTitle}"`,
|
|
14742
|
+
message: dry_run ? `[dry run] Would absorb "${source_name}" as alias of "${targetTitle}"` : `Absorbed "${source_name}" as alias of "${targetTitle}"`,
|
|
14614
14743
|
path: target_path,
|
|
14615
14744
|
preview: previewLines.join("\n"),
|
|
14616
|
-
backlinks_updated: totalBacklinksUpdated
|
|
14745
|
+
backlinks_updated: totalBacklinksUpdated,
|
|
14746
|
+
...dry_run ? { dryRun: true } : {}
|
|
14617
14747
|
};
|
|
14618
14748
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
14619
14749
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velvetmonkey/flywheel-memory",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.60",
|
|
4
4
|
"description": "MCP server that gives Claude full read/write access to your Obsidian vault. Select from 42 tools for search, backlinks, graph queries, mutations, and hybrid semantic search.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
55
|
-
"@velvetmonkey/vault-core": "^2.0.
|
|
55
|
+
"@velvetmonkey/vault-core": "^2.0.60",
|
|
56
56
|
"better-sqlite3": "^11.0.0",
|
|
57
57
|
"chokidar": "^4.0.0",
|
|
58
58
|
"gray-matter": "^4.0.3",
|