@noteplanco/noteplan-mcp 1.1.23 → 1.1.25

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 (165) hide show
  1. package/README.md +7 -0
  2. package/dist/index.js +6 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/noteplan/attachments-paths.d.ts +13 -0
  5. package/dist/noteplan/attachments-paths.d.ts.map +1 -0
  6. package/dist/noteplan/attachments-paths.js +27 -0
  7. package/dist/noteplan/attachments-paths.js.map +1 -0
  8. package/dist/noteplan/embeddings.js +1 -1
  9. package/dist/noteplan/embeddings.js.map +1 -1
  10. package/dist/noteplan/file-reader.d.ts +37 -46
  11. package/dist/noteplan/file-reader.d.ts.map +1 -1
  12. package/dist/noteplan/file-reader.js +200 -202
  13. package/dist/noteplan/file-reader.js.map +1 -1
  14. package/dist/noteplan/file-reader.test.d.ts +2 -0
  15. package/dist/noteplan/file-reader.test.d.ts.map +1 -0
  16. package/dist/noteplan/file-reader.test.js +67 -0
  17. package/dist/noteplan/file-reader.test.js.map +1 -0
  18. package/dist/noteplan/file-writer.d.ts +35 -31
  19. package/dist/noteplan/file-writer.d.ts.map +1 -1
  20. package/dist/noteplan/file-writer.js +280 -164
  21. package/dist/noteplan/file-writer.js.map +1 -1
  22. package/dist/noteplan/file-writer.test.js +704 -191
  23. package/dist/noteplan/file-writer.test.js.map +1 -1
  24. package/dist/noteplan/filter-store.d.ts +5 -5
  25. package/dist/noteplan/filter-store.d.ts.map +1 -1
  26. package/dist/noteplan/filter-store.js +94 -79
  27. package/dist/noteplan/filter-store.js.map +1 -1
  28. package/dist/noteplan/ripgrep-search.d.ts +25 -2
  29. package/dist/noteplan/ripgrep-search.d.ts.map +1 -1
  30. package/dist/noteplan/ripgrep-search.js +75 -2
  31. package/dist/noteplan/ripgrep-search.js.map +1 -1
  32. package/dist/noteplan/space-row-utils.d.ts +20 -0
  33. package/dist/noteplan/space-row-utils.d.ts.map +1 -0
  34. package/dist/noteplan/space-row-utils.js +78 -0
  35. package/dist/noteplan/space-row-utils.js.map +1 -0
  36. package/dist/noteplan/space-row-utils.test.d.ts +2 -0
  37. package/dist/noteplan/space-row-utils.test.d.ts.map +1 -0
  38. package/dist/noteplan/space-row-utils.test.js +123 -0
  39. package/dist/noteplan/space-row-utils.test.js.map +1 -0
  40. package/dist/noteplan/sqlite-reader.d.ts +12 -27
  41. package/dist/noteplan/sqlite-reader.d.ts.map +1 -1
  42. package/dist/noteplan/sqlite-reader.js +315 -221
  43. package/dist/noteplan/sqlite-reader.js.map +1 -1
  44. package/dist/noteplan/sqlite-writer.d.ts +1 -1
  45. package/dist/noteplan/sqlite-writer.d.ts.map +1 -1
  46. package/dist/noteplan/sqlite-writer.js +2 -2
  47. package/dist/noteplan/sqlite-writer.js.map +1 -1
  48. package/dist/noteplan/unified-store.d.ts +41 -30
  49. package/dist/noteplan/unified-store.d.ts.map +1 -1
  50. package/dist/noteplan/unified-store.js +257 -159
  51. package/dist/noteplan/unified-store.js.map +1 -1
  52. package/dist/server.d.ts.map +1 -1
  53. package/dist/server.js +142 -61
  54. package/dist/server.js.map +1 -1
  55. package/dist/tools/attachments.d.ts +9 -9
  56. package/dist/tools/attachments.d.ts.map +1 -1
  57. package/dist/tools/attachments.js +74 -83
  58. package/dist/tools/attachments.js.map +1 -1
  59. package/dist/tools/attachments.test.js +170 -129
  60. package/dist/tools/attachments.test.js.map +1 -1
  61. package/dist/tools/calendar.d.ts +16 -13
  62. package/dist/tools/calendar.d.ts.map +1 -1
  63. package/dist/tools/calendar.js +17 -16
  64. package/dist/tools/calendar.js.map +1 -1
  65. package/dist/tools/embeddings.d.ts +6 -6
  66. package/dist/tools/embeddings.d.ts.map +1 -1
  67. package/dist/tools/embeddings.js +6 -6
  68. package/dist/tools/embeddings.js.map +1 -1
  69. package/dist/tools/events.d.ts +7 -3
  70. package/dist/tools/events.d.ts.map +1 -1
  71. package/dist/tools/events.js +51 -16
  72. package/dist/tools/events.js.map +1 -1
  73. package/dist/tools/filters.d.ts +28 -33
  74. package/dist/tools/filters.d.ts.map +1 -1
  75. package/dist/tools/filters.js +42 -105
  76. package/dist/tools/filters.js.map +1 -1
  77. package/dist/tools/notes.d.ts +80 -218
  78. package/dist/tools/notes.d.ts.map +1 -1
  79. package/dist/tools/notes.js +180 -177
  80. package/dist/tools/notes.js.map +1 -1
  81. package/dist/tools/notes.test.js +242 -21
  82. package/dist/tools/notes.test.js.map +1 -1
  83. package/dist/tools/search.d.ts +4 -3
  84. package/dist/tools/search.d.ts.map +1 -1
  85. package/dist/tools/search.js +9 -5
  86. package/dist/tools/search.js.map +1 -1
  87. package/dist/tools/search.test.d.ts +2 -0
  88. package/dist/tools/search.test.d.ts.map +1 -0
  89. package/dist/tools/search.test.js +37 -0
  90. package/dist/tools/search.test.js.map +1 -0
  91. package/dist/tools/spaces.d.ts +20 -20
  92. package/dist/tools/spaces.d.ts.map +1 -1
  93. package/dist/tools/spaces.js +28 -28
  94. package/dist/tools/spaces.js.map +1 -1
  95. package/dist/tools/tasks.d.ts +22 -22
  96. package/dist/tools/tasks.d.ts.map +1 -1
  97. package/dist/tools/tasks.js +22 -22
  98. package/dist/tools/tasks.js.map +1 -1
  99. package/dist/tools/templates.d.ts +7 -7
  100. package/dist/tools/templates.d.ts.map +1 -1
  101. package/dist/tools/templates.js +4 -4
  102. package/dist/tools/templates.js.map +1 -1
  103. package/dist/tools/themes.d.ts.map +1 -1
  104. package/dist/tools/themes.js +26 -35
  105. package/dist/tools/themes.js.map +1 -1
  106. package/dist/transport/bridge-availability.d.ts +5 -0
  107. package/dist/transport/bridge-availability.d.ts.map +1 -0
  108. package/dist/transport/bridge-availability.js +92 -0
  109. package/dist/transport/bridge-availability.js.map +1 -0
  110. package/dist/transport/bridge-cascade.d.ts +18 -0
  111. package/dist/transport/bridge-cascade.d.ts.map +1 -0
  112. package/dist/transport/bridge-cascade.js +78 -0
  113. package/dist/transport/bridge-cascade.js.map +1 -0
  114. package/dist/transport/bridge-cascade.test.d.ts +2 -0
  115. package/dist/transport/bridge-cascade.test.d.ts.map +1 -0
  116. package/dist/transport/bridge-cascade.test.js +160 -0
  117. package/dist/transport/bridge-cascade.test.js.map +1 -0
  118. package/dist/transport/bridge-client.d.ts +197 -0
  119. package/dist/transport/bridge-client.d.ts.map +1 -0
  120. package/dist/transport/bridge-client.js +288 -0
  121. package/dist/transport/bridge-client.js.map +1 -0
  122. package/dist/transport/bridge-client.test.d.ts +2 -0
  123. package/dist/transport/bridge-client.test.d.ts.map +1 -0
  124. package/dist/transport/bridge-client.test.js +384 -0
  125. package/dist/transport/bridge-client.test.js.map +1 -0
  126. package/dist/transport/bridge-context.d.ts +10 -0
  127. package/dist/transport/bridge-context.d.ts.map +1 -0
  128. package/dist/transport/bridge-context.js +18 -0
  129. package/dist/transport/bridge-context.js.map +1 -0
  130. package/dist/transport/bridge-fs.d.ts +25 -0
  131. package/dist/transport/bridge-fs.d.ts.map +1 -0
  132. package/dist/transport/bridge-fs.js +129 -0
  133. package/dist/transport/bridge-fs.js.map +1 -0
  134. package/dist/utils/date-utils.d.ts +24 -0
  135. package/dist/utils/date-utils.d.ts.map +1 -1
  136. package/dist/utils/date-utils.js +55 -0
  137. package/dist/utils/date-utils.js.map +1 -1
  138. package/dist/utils/date-utils.test.d.ts +2 -0
  139. package/dist/utils/date-utils.test.d.ts.map +1 -0
  140. package/dist/utils/date-utils.test.js +109 -0
  141. package/dist/utils/date-utils.test.js.map +1 -0
  142. package/dist/utils/folder-access.d.ts +23 -0
  143. package/dist/utils/folder-access.d.ts.map +1 -0
  144. package/dist/utils/folder-access.js +131 -0
  145. package/dist/utils/folder-access.js.map +1 -0
  146. package/dist/utils/folder-access.test.d.ts +2 -0
  147. package/dist/utils/folder-access.test.d.ts.map +1 -0
  148. package/dist/utils/folder-access.test.js +182 -0
  149. package/dist/utils/folder-access.test.js.map +1 -0
  150. package/dist/utils/folder-matcher.d.ts.map +1 -1
  151. package/dist/utils/folder-matcher.js +16 -0
  152. package/dist/utils/folder-matcher.js.map +1 -1
  153. package/dist/utils/folder-matcher.test.js +42 -0
  154. package/dist/utils/folder-matcher.test.js.map +1 -1
  155. package/dist/utils/server-config.d.ts +10 -2
  156. package/dist/utils/server-config.d.ts.map +1 -1
  157. package/dist/utils/server-config.js +16 -2
  158. package/dist/utils/server-config.js.map +1 -1
  159. package/dist/utils/version.d.ts +2 -0
  160. package/dist/utils/version.d.ts.map +1 -1
  161. package/dist/utils/version.js +5 -1
  162. package/dist/utils/version.js.map +1 -1
  163. package/package.json +4 -3
  164. package/scripts/calendar-helper +0 -0
  165. package/scripts/reminders-helper +0 -0
@@ -7,6 +7,8 @@ import { ensureTemplateFrontmatter } from './templates.js';
7
7
  import { issueConfirmationToken, validateAndConsumeConfirmationToken, } from '../utils/confirmation-tokens.js';
8
8
  import { parseParagraphLine, parseAllParagraphLines, buildParagraphLine } from '../noteplan/markdown-parser.js';
9
9
  import { normalizeFilename } from '../utils/filename-normalize.js';
10
+ import { normalizePeriodicTitle, isCanonicalPeriodicTitle, parseFlexibleDate } from '../utils/date-utils.js';
11
+ import { getBridgeClient } from '../transport/bridge-availability.js';
10
12
  function toBoundedInt(value, defaultValue, min, max) {
11
13
  const numeric = typeof value === 'number' ? value : Number(value);
12
14
  if (!Number.isFinite(numeric))
@@ -57,37 +59,37 @@ function confirmationFailureMessage(toolName, reason) {
57
59
  }
58
60
  return `Confirmation token is invalid for ${toolName}. ${refreshHint}`;
59
61
  }
60
- function resolveNoteTarget(id, filename, space) {
62
+ async function resolveNoteTarget(id, filename, space) {
61
63
  const raw = (id && id.trim().length > 0 ? id : filename)?.trim();
62
64
  if (!raw) {
63
65
  return { identifier: '', note: null };
64
66
  }
65
67
  const identifier = normalizeFilename(raw);
66
68
  const note = id
67
- ? store.getNote({ id: identifier, space }) ?? store.getNote({ filename: identifier, space })
68
- : store.getNote({ filename: identifier, space });
69
+ ? (await await store.getNote({ id: identifier, space })) ?? (await await store.getNote({ filename: identifier, space }))
70
+ : await await store.getNote({ filename: identifier, space });
69
71
  return {
70
72
  identifier,
71
73
  note,
72
74
  };
73
75
  }
74
- export function resolveWritableNoteReference(input) {
76
+ export async function resolveWritableNoteReference(input) {
75
77
  if (input.id && input.id.trim().length > 0) {
76
78
  const normalizedId = normalizeFilename(input.id.trim());
77
- const note = store.getNote({ id: normalizedId, space: input.space?.trim() });
79
+ const note = await await store.getNote({ id: normalizedId, space: input.space?.trim() });
78
80
  return { note, error: note ? undefined : 'Note not found' };
79
81
  }
80
82
  if (input.filename && input.filename.trim().length > 0) {
81
83
  const normalizedFn = normalizeFilename(input.filename.trim());
82
- const note = store.getNote({ filename: normalizedFn, space: input.space?.trim() });
84
+ const note = await await store.getNote({ filename: normalizedFn, space: input.space?.trim() });
83
85
  return { note, error: note ? undefined : 'Note not found' };
84
86
  }
85
87
  if (input.date && input.date.trim().length > 0) {
86
- let note = store.getNote({ date: input.date.trim(), space: input.space?.trim() });
88
+ let note = await await store.getNote({ date: input.date.trim(), space: input.space?.trim() });
87
89
  if (!note) {
88
90
  // Auto-create calendar notes on the fly (matches NotePlan native behavior)
89
91
  try {
90
- note = store.ensureCalendarNote(input.date.trim(), input.space?.trim());
92
+ note = await await store.ensureCalendarNote(input.date.trim(), input.space?.trim());
91
93
  }
92
94
  catch {
93
95
  return { note: null, error: 'Failed to create calendar note for date' };
@@ -97,14 +99,14 @@ export function resolveWritableNoteReference(input) {
97
99
  }
98
100
  const textQuery = input.query?.trim() || input.title?.trim();
99
101
  if (textQuery) {
100
- const resolved = resolveNote({
102
+ const resolved = (await resolveNote({
101
103
  query: textQuery,
102
104
  space: input.space?.trim(),
103
105
  types: ['note', 'calendar'],
104
106
  limit: 5,
105
107
  minScore: 0.88,
106
108
  ambiguityDelta: 0.06,
107
- });
109
+ }));
108
110
  if (resolved.success !== true) {
109
111
  return { note: null, error: 'Could not resolve note query' };
110
112
  }
@@ -120,7 +122,7 @@ export function resolveWritableNoteReference(input) {
120
122
  if (!identifier) {
121
123
  return { note: null, error: 'Could not resolve note target' };
122
124
  }
123
- const note = store.getNote({ id: identifier, space: input.space?.trim() }) ?? store.getNote({ filename: identifier, space: input.space?.trim() });
125
+ const note = (await await store.getNote({ id: identifier, space: input.space?.trim() })) ?? (await await store.getNote({ filename: identifier, space: input.space?.trim() }));
124
126
  return { note, error: note ? undefined : 'Resolved note no longer exists' };
125
127
  }
126
128
  return {
@@ -262,6 +264,11 @@ export const getNoteSchema = z.object({
262
264
  .optional()
263
265
  .default(280)
264
266
  .describe('Preview length when includeContent=false (default: 280)'),
267
+ format: z
268
+ .enum(['flat', 'lines', 'both'])
269
+ .optional()
270
+ .default('flat')
271
+ .describe('Response shape when includeContent=true. "flat" (default): only the joined "content" string — token-efficient, fine for reading. "lines": only the per-line "lines" array — use when you need line numbers for follow-up edits. "both": both fields (highest token cost; previous default).'),
265
272
  });
266
273
  export const listNotesSchema = z.object({
267
274
  folder: z
@@ -297,9 +304,17 @@ export const resolveNoteSchema = z.object({
297
304
  .describe('If top scores are within this delta, treat as ambiguous'),
298
305
  });
299
306
  export const createNoteSchema = z.object({
300
- title: z.string().describe('Title for the new note'),
307
+ title: z.string().optional().describe('Title for the new note. Required for project notes. Ignored for calendar notes (their identifier comes from `date` or a periodic title).'),
308
+ date: z
309
+ .string()
310
+ .optional()
311
+ .describe('Periodic-note identifier — set this to create a calendar note at `Calendar/{date}.{ext}`. Accepts daily (`YYYY-MM-DD`, `YYYYMMDD`, `today`/`tomorrow`/`yesterday`), weekly (`YYYY-Www` — sloppy `2026-W4` is normalized to `2026-W04`), monthly (`YYYY-MM`), quarterly (`YYYY-Qn`), and yearly (`YYYY`). Title and folder are ignored when date is present.'),
312
+ filename: z
313
+ .string()
314
+ .optional()
315
+ .describe('Optional on-disk basename for the note (e.g. "_context.md"). Independent of title — the title still controls the H1 inside the file. Path separators are rejected; pass folder via the "folder" parameter. Extension is preserved if .md/.txt, otherwise the configured default extension is appended. Project notes only — space and calendar notes derive filenames automatically.'),
301
316
  content: z.string().optional().describe('Initial content for the note. Can include YAML frontmatter between --- delimiters for styling (icon, icon-color, bg-color, bg-color-dark, bg-pattern, status, priority, summary, type, domain)'),
302
- folder: z.string().optional().describe('Folder to create the note in. Supports smart matching (e.g., "projects" matches "10 - Projects")'),
317
+ folder: z.string().optional().describe('Folder to create the note in. Supports smart matching (e.g., "projects" matches "10 - Projects"). Setting this to "Calendar" together with a periodic title (e.g. `2026-W16`) is treated as creating a calendar note.'),
303
318
  create_new_folder: z.boolean().optional().describe('Set to true to create a new folder instead of matching existing ones'),
304
319
  space: z.string().optional().describe('Space name or ID to create in (e.g., "My Team" or a UUID)'),
305
320
  noteType: z.enum(['note', 'template']).optional().default('note').describe('Type of note to create. Use "template" to create in @Templates with proper frontmatter'),
@@ -319,14 +334,6 @@ export const updateNoteSchema = z.object({
319
334
  .boolean()
320
335
  .optional()
321
336
  .describe('Required safety confirmation for whole-note rewrite. Must be true to proceed.'),
322
- dryRun: z
323
- .boolean()
324
- .optional()
325
- .describe('Preview full-rewrite impact and get confirmationToken without modifying the note'),
326
- confirmationToken: z
327
- .string()
328
- .optional()
329
- .describe('Confirmation token issued by dryRun for full note rewrite'),
330
337
  allowEmptyContent: z
331
338
  .boolean()
332
339
  .optional()
@@ -450,8 +457,8 @@ export const restoreNoteSchema = z.object({
450
457
  }
451
458
  });
452
459
  // Tool implementations
453
- export function getNote(params) {
454
- const note = store.getNote(params);
460
+ export async function getNote(params) {
461
+ const note = await store.getNote(params);
455
462
  if (!note) {
456
463
  return {
457
464
  success: false,
@@ -509,16 +516,21 @@ export function getNote(params) {
509
516
  result.limit = lineWindow.limit;
510
517
  result.hasMore = lineWindow.hasMore;
511
518
  result.nextCursor = lineWindow.nextCursor;
512
- result.content = lineWindow.content;
513
- result.lines = lineWindow.lines;
519
+ const format = params.format ?? 'flat';
520
+ if (format !== 'lines') {
521
+ result.content = lineWindow.content;
522
+ }
523
+ if (format !== 'flat') {
524
+ result.lines = lineWindow.lines;
525
+ }
514
526
  if (lineWindow.hasMore) {
515
527
  result.performanceHints = [NEXT_CURSOR_HINT];
516
528
  }
517
529
  return result;
518
530
  }
519
- export function listNotes(params) {
531
+ export async function listNotes(params) {
520
532
  const input = params ?? {};
521
- const notes = store.listNotes({
533
+ const notes = await store.listNotes({
522
534
  folder: input.folder,
523
535
  space: input.space,
524
536
  });
@@ -601,7 +613,7 @@ function noteMatchScore(note, query, queryDateToken) {
601
613
  return 0.76;
602
614
  return 0;
603
615
  }
604
- export function resolveNote(params) {
616
+ export async function resolveNote(params) {
605
617
  const query = typeof params?.query === 'string' ? params.query.trim() : '';
606
618
  if (!query) {
607
619
  return {
@@ -617,7 +629,7 @@ export function resolveNote(params) {
617
629
  const includeStageTimings = isDebugTimingsEnabled(params.debugTimings);
618
630
  const stageTimings = {};
619
631
  const listStart = Date.now();
620
- const notes = store.listNotes({
632
+ const notes = await store.listNotes({
621
633
  folder: params.folder,
622
634
  space: params.space,
623
635
  });
@@ -714,17 +726,59 @@ export function resolveNote(params) {
714
726
  }
715
727
  return result;
716
728
  }
717
- export function createNote(params) {
729
+ export async function createNote(params) {
718
730
  try {
731
+ // Auto-route to calendar only when intent is unambiguous. A project
732
+ // note titled "2026" would otherwise be hijacked, so sloppy variants
733
+ // (e.g. "2026-W4") require an explicit folder=Calendar or date.
734
+ const titlePeriodic = params.title ? normalizePeriodicTitle(params.title) : null;
735
+ const folderHintsCalendar = (params.folder ?? '').trim().toLowerCase() === 'calendar';
736
+ let calendarDate = null;
737
+ if (params.date) {
738
+ const normalizedDate = normalizePeriodicTitle(parseFlexibleDate(params.date));
739
+ if (!normalizedDate) {
740
+ throw new Error(`Invalid calendar date "${params.date}". Use a periodic identifier like "today", "2026-05-07", "20260507", "2026-W16", "2026-05", "2026-Q2", or "2026".`);
741
+ }
742
+ calendarDate = normalizedDate;
743
+ }
744
+ else if (folderHintsCalendar) {
745
+ if (!titlePeriodic) {
746
+ throw new Error('folder="Calendar" requires a periodic title (e.g. "2026-W16", "2026-05", "2026-Q2", "20260507") or pass `date` directly. Got title: ' +
747
+ JSON.stringify(params.title ?? null));
748
+ }
749
+ calendarDate = titlePeriodic;
750
+ }
751
+ else if (params.title && isCanonicalPeriodicTitle(params.title)) {
752
+ calendarDate = params.title;
753
+ }
754
+ if (calendarDate) {
755
+ const result = await store.createNote(params.title ?? calendarDate, params.content, {
756
+ space: params.space,
757
+ filename: params.filename,
758
+ calendarDate,
759
+ });
760
+ return {
761
+ success: true,
762
+ tip: 'Calendar notes are auto-created when you write to them via noteplan_edit_content / noteplan_paragraphs with a `date` parameter — explicit create is rarely needed.',
763
+ note: {
764
+ title: result.note.title,
765
+ filename: result.note.filename,
766
+ type: result.note.type,
767
+ source: result.note.source,
768
+ folder: result.note.folder,
769
+ },
770
+ };
771
+ }
719
772
  const isTemplate = params.noteType === 'template';
720
773
  const folder = isTemplate && !params.folder ? '@Templates' : params.folder;
721
774
  const content = isTemplate
722
- ? ensureTemplateFrontmatter(params.title, params.content, params.templateTypes)
775
+ ? ensureTemplateFrontmatter(params.title ?? '', params.content, params.templateTypes)
723
776
  : params.content;
724
- const result = store.createNote(params.title, content, {
777
+ const result = await store.createNote(params.title ?? '', content, {
725
778
  folder,
726
779
  space: params.space,
727
780
  createNewFolder: params.create_new_folder,
781
+ filename: params.filename,
728
782
  });
729
783
  return {
730
784
  success: true,
@@ -753,7 +807,7 @@ export function createNote(params) {
753
807
  };
754
808
  }
755
809
  }
756
- export function updateNote(params) {
810
+ export async function updateNote(params) {
757
811
  try {
758
812
  if (params.fullReplace !== true) {
759
813
  return {
@@ -761,7 +815,7 @@ export function updateNote(params) {
761
815
  error: 'Full note replacement is blocked for noteplan_update_note unless fullReplace=true. Prefer noteplan_search_paragraphs + noteplan_edit_line/insert_content/delete_lines for targeted edits.',
762
816
  };
763
817
  }
764
- const noteRef = resolveWritableNoteReference(params);
818
+ const noteRef = await resolveWritableNoteReference(params);
765
819
  if (!noteRef.note) {
766
820
  return {
767
821
  success: false,
@@ -776,45 +830,8 @@ export function updateNote(params) {
776
830
  error: 'Empty content is blocked for noteplan_update_note. Use allowEmptyContent=true to override intentionally.',
777
831
  };
778
832
  }
779
- // Use the resolved filename as the confirmation token target for consistency
780
- const confirmationTarget = existingNote.filename;
781
- if (isTrueBool(params.dryRun)) {
782
- const token = issueConfirmationToken({
783
- tool: 'noteplan_update_note',
784
- target: confirmationTarget,
785
- action: 'full_replace',
786
- });
787
- return {
788
- success: true,
789
- dryRun: true,
790
- message: `Dry run: note ${existingNote.filename} would be fully replaced`,
791
- note: {
792
- id: existingNote.id,
793
- title: existingNote.title,
794
- filename: existingNote.filename,
795
- type: existingNote.type,
796
- source: existingNote.source,
797
- folder: existingNote.folder,
798
- spaceId: existingNote.spaceId,
799
- },
800
- currentContentLength: existingNote.content.length,
801
- newContentLength: params.content.length,
802
- ...token,
803
- };
804
- }
805
- const confirmation = validateAndConsumeConfirmationToken(params.confirmationToken, {
806
- tool: 'noteplan_update_note',
807
- target: confirmationTarget,
808
- action: 'full_replace',
809
- });
810
- if (!confirmation.ok) {
811
- return {
812
- success: false,
813
- error: confirmationFailureMessage('noteplan_update_note', confirmation.reason),
814
- };
815
- }
816
833
  const writeTarget = getWritableIdentifier(existingNote);
817
- const note = store.updateNote(writeTarget.identifier, params.content, {
834
+ const note = await store.updateNote(writeTarget.identifier, params.content, {
818
835
  source: writeTarget.source,
819
836
  });
820
837
  return {
@@ -834,9 +851,9 @@ export function updateNote(params) {
834
851
  };
835
852
  }
836
853
  }
837
- export function deleteNote(params) {
854
+ export async function deleteNote(params) {
838
855
  try {
839
- const target = resolveNoteTarget(params.id, params.filename, params.space);
856
+ const target = await resolveNoteTarget(params.id, params.filename, params.space);
840
857
  const note = target.note;
841
858
  if (!note) {
842
859
  return {
@@ -877,7 +894,7 @@ export function deleteNote(params) {
877
894
  error: confirmationFailureMessage('noteplan_delete_note', confirmation.reason),
878
895
  };
879
896
  }
880
- const deleted = store.deleteNote(target.identifier);
897
+ const deleted = await store.deleteNote(target.identifier);
881
898
  return {
882
899
  success: true,
883
900
  message: deleted.source === 'space'
@@ -897,9 +914,9 @@ export function deleteNote(params) {
897
914
  };
898
915
  }
899
916
  }
900
- export function moveNote(params) {
917
+ export async function moveNote(params) {
901
918
  try {
902
- const noteRef = resolveWritableNoteReference(params);
919
+ const noteRef = await resolveWritableNoteReference(params);
903
920
  if (!noteRef.note) {
904
921
  return {
905
922
  success: false,
@@ -908,7 +925,7 @@ export function moveNote(params) {
908
925
  };
909
926
  }
910
927
  const writable = getWritableIdentifier(noteRef.note);
911
- const preview = store.previewMoveNote(writable.identifier, params.destinationFolder);
928
+ const preview = await store.previewMoveNote(writable.identifier, params.destinationFolder);
912
929
  const confirmationTarget = `${preview.fromFilename}=>${preview.toFilename}::${preview.destinationParentId ?? preview.destinationFolder}`;
913
930
  if (isTrueBool(params.dryRun)) {
914
931
  const token = issueConfirmationToken({
@@ -946,7 +963,7 @@ export function moveNote(params) {
946
963
  error: confirmationFailureMessage('noteplan_move_note', confirmation.reason),
947
964
  };
948
965
  }
949
- const moved = store.moveNote(writable.identifier, params.destinationFolder);
966
+ const moved = await store.moveNote(writable.identifier, params.destinationFolder);
950
967
  return {
951
968
  success: true,
952
969
  message: moved.note.source === 'space'
@@ -973,16 +990,16 @@ export function moveNote(params) {
973
990
  };
974
991
  }
975
992
  }
976
- export function restoreNote(params) {
993
+ export async function restoreNote(params) {
977
994
  try {
978
- const target = resolveNoteTarget(params.id, params.filename, params.space);
995
+ const target = await resolveNoteTarget(params.id, params.filename, params.space);
979
996
  if (!target.note) {
980
997
  return {
981
998
  success: false,
982
999
  error: 'Note not found',
983
1000
  };
984
1001
  }
985
- const preview = store.previewRestoreNote(target.identifier, params.destinationFolder);
1002
+ const preview = await store.previewRestoreNote(target.identifier, params.destinationFolder);
986
1003
  const confirmationTarget = `${preview.fromIdentifier}=>${preview.toIdentifier}`;
987
1004
  if (isTrueBool(params.dryRun)) {
988
1005
  const token = issueConfirmationToken({
@@ -1020,7 +1037,7 @@ export function restoreNote(params) {
1020
1037
  error: confirmationFailureMessage('noteplan_restore_note', confirmation.reason),
1021
1038
  };
1022
1039
  }
1023
- const restored = store.restoreNote(target.identifier, params.destinationFolder);
1040
+ const restored = await store.restoreNote(target.identifier, params.destinationFolder);
1024
1041
  return {
1025
1042
  success: true,
1026
1043
  message: restored.source === 'space'
@@ -1046,10 +1063,10 @@ export function restoreNote(params) {
1046
1063
  };
1047
1064
  }
1048
1065
  }
1049
- export function renameNoteFile(params) {
1066
+ export async function renameNoteFile(params) {
1050
1067
  try {
1051
1068
  // Resolve the note — supports id, filename, title, or query
1052
- const resolved = resolveWritableNoteReference(params);
1069
+ const resolved = await resolveWritableNoteReference(params);
1053
1070
  if (!resolved.note) {
1054
1071
  return {
1055
1072
  success: false,
@@ -1103,7 +1120,7 @@ export function renameNoteFile(params) {
1103
1120
  error: confirmationFailureMessage('noteplan_rename_note_file', confirmation.reason),
1104
1121
  };
1105
1122
  }
1106
- const renamed = store.renameSpaceNote(writeId, params.newTitle);
1123
+ const renamed = await store.renameSpaceNote(writeId, params.newTitle);
1107
1124
  // Also update the # Title heading in the note content if it matches the old title
1108
1125
  if (renamed.note.content) {
1109
1126
  const lines = renamed.note.content.split('\n');
@@ -1113,7 +1130,7 @@ export function renameNoteFile(params) {
1113
1130
  if (oldHeadingTitle === renamed.fromTitle) {
1114
1131
  lines[titleLineIndex] = `# ${params.newTitle}`;
1115
1132
  const renamedWriteTarget = getWritableIdentifier(renamed.note);
1116
- store.updateNote(renamedWriteTarget.identifier, lines.join('\n'), {
1133
+ await store.updateNote(renamedWriteTarget.identifier, lines.join('\n'), {
1117
1134
  source: renamedWriteTarget.source,
1118
1135
  });
1119
1136
  }
@@ -1145,7 +1162,7 @@ export function renameNoteFile(params) {
1145
1162
  };
1146
1163
  }
1147
1164
  const keepExtension = params.keepExtension ?? true;
1148
- const preview = store.previewRenameNoteFile(note.filename, effectiveNewFilename, keepExtension);
1165
+ const preview = await store.previewRenameNoteFile(note.filename, effectiveNewFilename, keepExtension);
1149
1166
  const confirmationTarget = `${preview.fromFilename}=>${preview.toFilename}`;
1150
1167
  if (isTrueBool(params.dryRun)) {
1151
1168
  const token = issueConfirmationToken({
@@ -1182,27 +1199,51 @@ export function renameNoteFile(params) {
1182
1199
  error: confirmationFailureMessage('noteplan_rename_note_file', confirmation.reason),
1183
1200
  };
1184
1201
  }
1185
- const renamed = store.renameNoteFile(note.filename, effectiveNewFilename, keepExtension);
1186
- // Also update the # Title heading in the note content if it matches the old title
1202
+ const renamed = await store.renameNoteFile(note.filename, effectiveNewFilename, keepExtension);
1203
+ // Also update the # Title heading in the note content if it matches the old title.
1204
+ // When the heading actually changes, propagate the rename to wikilinks across the
1205
+ // vault via the bridge — matches what the NotePlan UI rename does.
1187
1206
  const newTitle = params.newTitle || params.newFilename;
1207
+ let headingRewritten = false;
1208
+ let oldHeadingTitle = null;
1188
1209
  if (newTitle && renamed.note.content) {
1189
1210
  const lines = renamed.note.content.split('\n');
1190
1211
  const titleLineIndex = lines.findIndex((l) => /^#\s+/.test(l));
1191
1212
  if (titleLineIndex !== -1) {
1192
- const oldHeadingTitle = lines[titleLineIndex].replace(/^#\s+/, '');
1193
- // Update heading if it matches the old note title (or old filename without extension)
1213
+ const currentHeading = lines[titleLineIndex].replace(/^#\s+/, '');
1194
1214
  const oldTitle = note.title || '';
1195
1215
  const oldFilenameBase = note.filename.replace(/^.*\//, '').replace(/\.\w+$/, '');
1196
- if (oldHeadingTitle === oldTitle || oldHeadingTitle === oldFilenameBase) {
1216
+ if (currentHeading === oldTitle || currentHeading === oldFilenameBase) {
1197
1217
  lines[titleLineIndex] = `# ${newTitle}`;
1198
1218
  const writeTarget = getWritableIdentifier(renamed.note);
1199
- store.updateNote(writeTarget.identifier, lines.join('\n'), {
1219
+ await store.updateNote(writeTarget.identifier, lines.join('\n'), {
1200
1220
  source: writeTarget.source,
1201
1221
  });
1222
+ headingRewritten = true;
1223
+ oldHeadingTitle = currentHeading;
1202
1224
  }
1203
1225
  }
1204
1226
  }
1205
- return {
1227
+ let wikilinksUpdatedCount;
1228
+ let wikilinkPropagationWarning;
1229
+ if (headingRewritten && oldHeadingTitle && newTitle && oldHeadingTitle !== newTitle) {
1230
+ const bridge = await getBridgeClient();
1231
+ if (bridge) {
1232
+ try {
1233
+ const res = await bridge.rewriteWikilinks(oldHeadingTitle, newTitle);
1234
+ wikilinksUpdatedCount = res.updatedCount;
1235
+ }
1236
+ catch (err) {
1237
+ wikilinkPropagationWarning =
1238
+ err instanceof Error ? err.message : 'Failed to update wikilinks';
1239
+ }
1240
+ }
1241
+ else {
1242
+ wikilinkPropagationWarning =
1243
+ 'Wikilinks pointing to the old title were NOT updated because NotePlan is not running. Open NotePlan and re-run rename, or fix references manually.';
1244
+ }
1245
+ }
1246
+ const result = {
1206
1247
  success: true,
1207
1248
  message: `Note renamed to ${renamed.toFilename}`,
1208
1249
  fromFilename: renamed.fromFilename,
@@ -1216,6 +1257,13 @@ export function renameNoteFile(params) {
1216
1257
  folder: renamed.note.folder,
1217
1258
  },
1218
1259
  };
1260
+ if (wikilinksUpdatedCount !== undefined) {
1261
+ result.wikilinksUpdatedCount = wikilinksUpdatedCount;
1262
+ }
1263
+ if (wikilinkPropagationWarning) {
1264
+ result.warnings = [wikilinkPropagationWarning];
1265
+ }
1266
+ return result;
1219
1267
  }
1220
1268
  catch (error) {
1221
1269
  return {
@@ -1315,8 +1363,8 @@ function annotateFromMeta(lineObj, meta) {
1315
1363
  ...(meta.scheduledDate !== undefined && { scheduledDate: meta.scheduledDate }),
1316
1364
  };
1317
1365
  }
1318
- export function getParagraphs(params) {
1319
- const noteRef = resolveWritableNoteReference(params);
1366
+ export async function getParagraphs(params) {
1367
+ const noteRef = await resolveWritableNoteReference(params);
1320
1368
  if (!noteRef.note) {
1321
1369
  return {
1322
1370
  success: false,
@@ -1412,7 +1460,7 @@ export function getParagraphs(params) {
1412
1460
  }
1413
1461
  return result;
1414
1462
  }
1415
- export function searchParagraphs(params) {
1463
+ export async function searchParagraphs(params) {
1416
1464
  const query = typeof params?.query === 'string' ? params.query.trim() : '';
1417
1465
  if (!query) {
1418
1466
  return {
@@ -1426,7 +1474,7 @@ export function searchParagraphs(params) {
1426
1474
  error: 'Provide one note reference: id, title, filename, or date',
1427
1475
  };
1428
1476
  }
1429
- const note = store.getNote({
1477
+ const note = await store.getNote({
1430
1478
  id: params.id,
1431
1479
  title: params.title,
1432
1480
  filename: params.filename,
@@ -1694,30 +1742,22 @@ export const replaceLinesSchema = z.object({
1694
1742
  .optional()
1695
1743
  .default('tabs')
1696
1744
  .describe('Indentation normalization for replacement list/task lines. Default: tabs'),
1697
- dryRun: z
1698
- .boolean()
1699
- .optional()
1700
- .describe('Preview line replacement and get confirmationToken without modifying the note'),
1701
- confirmationToken: z
1702
- .string()
1703
- .optional()
1704
- .describe('Confirmation token issued by dryRun for replace execution'),
1705
1745
  allowEmptyContent: z
1706
1746
  .boolean()
1707
1747
  .optional()
1708
1748
  .describe('Allow replacing selected lines with empty content (default: false). Prefer delete_lines for pure deletion.'),
1709
1749
  });
1710
1750
  // Granular note operation implementations
1711
- export function setProperty(params) {
1751
+ export async function setProperty(params) {
1712
1752
  try {
1713
- const noteRef = resolveWritableNoteReference(params);
1753
+ const noteRef = await resolveWritableNoteReference(params);
1714
1754
  if (!noteRef.note) {
1715
1755
  return { success: false, error: noteRef.error || 'Note not found', candidates: noteRef.candidates };
1716
1756
  }
1717
1757
  const note = noteRef.note;
1718
1758
  const newContent = frontmatter.setFrontmatterProperty(note.content, params.key, params.value);
1719
1759
  const writable = getWritableIdentifier(note);
1720
- store.updateNote(writable.identifier, newContent, { source: writable.source });
1760
+ await store.updateNote(writable.identifier, newContent, { source: writable.source });
1721
1761
  return {
1722
1762
  success: true,
1723
1763
  message: `Property "${params.key}" set to "${params.value}"`,
@@ -1730,16 +1770,16 @@ export function setProperty(params) {
1730
1770
  };
1731
1771
  }
1732
1772
  }
1733
- export function removeProperty(params) {
1773
+ export async function removeProperty(params) {
1734
1774
  try {
1735
- const noteRef = resolveWritableNoteReference(params);
1775
+ const noteRef = await resolveWritableNoteReference(params);
1736
1776
  if (!noteRef.note) {
1737
1777
  return { success: false, error: noteRef.error || 'Note not found', candidates: noteRef.candidates };
1738
1778
  }
1739
1779
  const note = noteRef.note;
1740
1780
  const newContent = frontmatter.removeFrontmatterProperty(note.content, params.key);
1741
1781
  const writable = getWritableIdentifier(note);
1742
- store.updateNote(writable.identifier, newContent, { source: writable.source });
1782
+ await store.updateNote(writable.identifier, newContent, { source: writable.source });
1743
1783
  return {
1744
1784
  success: true,
1745
1785
  message: `Property "${params.key}" removed`,
@@ -1752,9 +1792,9 @@ export function removeProperty(params) {
1752
1792
  };
1753
1793
  }
1754
1794
  }
1755
- export function insertContent(params) {
1795
+ export async function insertContent(params) {
1756
1796
  try {
1757
- const resolved = resolveWritableNoteReference(params);
1797
+ const resolved = await resolveWritableNoteReference(params);
1758
1798
  if (!resolved.note) {
1759
1799
  return {
1760
1800
  success: false,
@@ -1818,7 +1858,7 @@ export function insertContent(params) {
1818
1858
  line: params.line,
1819
1859
  });
1820
1860
  const writeTarget = getWritableIdentifier(note);
1821
- store.updateNote(writeTarget.identifier, newContent, {
1861
+ await store.updateNote(writeTarget.identifier, newContent, {
1822
1862
  source: writeTarget.source,
1823
1863
  });
1824
1864
  return {
@@ -1841,9 +1881,9 @@ export function insertContent(params) {
1841
1881
  };
1842
1882
  }
1843
1883
  }
1844
- export function appendContent(params) {
1884
+ export async function appendContent(params) {
1845
1885
  try {
1846
- const resolved = resolveWritableNoteReference(params);
1886
+ const resolved = await resolveWritableNoteReference(params);
1847
1887
  if (!resolved.note) {
1848
1888
  return {
1849
1889
  success: false,
@@ -1859,7 +1899,7 @@ export function appendContent(params) {
1859
1899
  heading: params.heading,
1860
1900
  });
1861
1901
  const writeTarget = getWritableIdentifier(note);
1862
- store.updateNote(writeTarget.identifier, newContent, {
1902
+ await store.updateNote(writeTarget.identifier, newContent, {
1863
1903
  source: writeTarget.source,
1864
1904
  });
1865
1905
  return {
@@ -1881,7 +1921,7 @@ export function appendContent(params) {
1881
1921
  };
1882
1922
  }
1883
1923
  }
1884
- export function deleteLines(params) {
1924
+ export async function deleteLines(params) {
1885
1925
  try {
1886
1926
  // Validate required line params — MCP may deliver them as undefined when omitted
1887
1927
  const rawStart = params.startLine !== undefined && params.startLine !== null ? Number(params.startLine) : NaN;
@@ -1892,24 +1932,23 @@ export function deleteLines(params) {
1892
1932
  if (!Number.isFinite(rawEnd)) {
1893
1933
  return { success: false, error: 'endLine is required (1-indexed). Pass the same value as startLine to delete a single line.' };
1894
1934
  }
1895
- const resolved = resolveWritableNoteReference(params);
1935
+ const resolved = await resolveWritableNoteReference(params);
1896
1936
  if (!resolved.note) {
1897
1937
  return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };
1898
1938
  }
1899
1939
  const note = resolved.note;
1900
1940
  const allLines = note.content.split('\n');
1901
- // Line numbers are absolute (1-indexed), matching get_notes/getParagraphs
1902
1941
  const totalLineCount = allLines.length;
1903
1942
  const fmLineCount = frontmatter.getFrontmatterLineCount(note.content);
1904
- const minLine = fmLineCount > 0 ? fmLineCount + 1 : 1;
1905
- const boundedStartLine = toBoundedInt(params.startLine, minLine, minLine, Math.max(minLine, totalLineCount));
1906
- const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, totalLineCount));
1907
- if (boundedStartLine <= fmLineCount) {
1943
+ if (fmLineCount > 0 && rawStart <= fmLineCount) {
1908
1944
  return {
1909
1945
  success: false,
1910
- error: `Lines 1-${fmLineCount} are frontmatter and cannot be deleted. Content starts at line ${fmLineCount + 1}.`,
1946
+ error: `Line ${rawStart} is inside frontmatter (lines 1-${fmLineCount}). Content starts at line ${fmLineCount + 1}.`,
1911
1947
  };
1912
1948
  }
1949
+ const minLine = fmLineCount > 0 ? fmLineCount + 1 : 1;
1950
+ const boundedStartLine = toBoundedInt(params.startLine, minLine, minLine, Math.max(minLine, totalLineCount));
1951
+ const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, totalLineCount));
1913
1952
  const lineCountToDelete = boundedEndLine - boundedStartLine + 1;
1914
1953
  const previewStartIndex = boundedStartLine - 1;
1915
1954
  const previewEndIndexExclusive = boundedEndLine;
@@ -1980,7 +2019,7 @@ export function deleteLines(params) {
1980
2019
  splicedLines.splice(boundedStartLine - 1, lineCountToDelete);
1981
2020
  const newContent = splicedLines.join('\n');
1982
2021
  const writeIdentifier = note.source === 'space' ? (note.id || note.filename) : note.filename;
1983
- store.updateNote(writeIdentifier, newContent, { source: note.source });
2022
+ await store.updateNote(writeIdentifier, newContent, { source: note.source });
1984
2023
  return {
1985
2024
  success: true,
1986
2025
  message: `Lines ${boundedStartLine}-${boundedEndLine} deleted`,
@@ -1997,7 +2036,7 @@ export function deleteLines(params) {
1997
2036
  };
1998
2037
  }
1999
2038
  }
2000
- export function editLine(params) {
2039
+ export async function editLine(params) {
2001
2040
  try {
2002
2041
  if (params.allowEmptyContent !== true && params.content.trim().length === 0) {
2003
2042
  return {
@@ -2005,7 +2044,7 @@ export function editLine(params) {
2005
2044
  error: 'Empty line content is blocked for noteplan_edit_line. Use noteplan_delete_lines or set allowEmptyContent=true.',
2006
2045
  };
2007
2046
  }
2008
- const resolved = resolveWritableNoteReference(params);
2047
+ const resolved = await resolveWritableNoteReference(params);
2009
2048
  if (!resolved.note) {
2010
2049
  return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };
2011
2050
  }
@@ -2044,7 +2083,7 @@ export function editLine(params) {
2044
2083
  warnings.push(buildAttachmentWarningMessage(removedAttachmentReferences.length));
2045
2084
  }
2046
2085
  const writeIdentifier = note.source === 'space' ? (note.id || note.filename) : note.filename;
2047
- store.updateNote(writeIdentifier, newContent, { source: note.source });
2086
+ await store.updateNote(writeIdentifier, newContent, { source: note.source });
2048
2087
  return {
2049
2088
  success: true,
2050
2089
  message: `Line ${params.line} updated`,
@@ -2068,7 +2107,7 @@ export function editLine(params) {
2068
2107
  };
2069
2108
  }
2070
2109
  }
2071
- export function replaceLines(params) {
2110
+ export async function replaceLines(params) {
2072
2111
  try {
2073
2112
  // Validate required line params — MCP may deliver them as undefined when omitted
2074
2113
  const rawStart = params.startLine !== undefined && params.startLine !== null ? Number(params.startLine) : NaN;
@@ -2079,24 +2118,23 @@ export function replaceLines(params) {
2079
2118
  if (!Number.isFinite(rawEnd)) {
2080
2119
  return { success: false, error: 'endLine is required (1-indexed). Pass the same value as startLine to replace a single line.' };
2081
2120
  }
2082
- const resolved = resolveWritableNoteReference(params);
2121
+ const resolved = await resolveWritableNoteReference(params);
2083
2122
  if (!resolved.note) {
2084
2123
  return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };
2085
2124
  }
2086
2125
  const note = resolved.note;
2087
2126
  const allLines = note.content.split('\n');
2088
2127
  const originalLineCount = allLines.length;
2089
- // Line numbers are absolute (1-indexed), matching get_notes/getParagraphs
2090
2128
  const fmLineCount = frontmatter.getFrontmatterLineCount(note.content);
2091
- const minLine = fmLineCount > 0 ? fmLineCount + 1 : 1;
2092
- const boundedStartLine = toBoundedInt(params.startLine, minLine, minLine, Math.max(minLine, originalLineCount));
2093
- const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, originalLineCount));
2094
- if (boundedStartLine <= fmLineCount) {
2129
+ if (fmLineCount > 0 && rawStart <= fmLineCount) {
2095
2130
  return {
2096
2131
  success: false,
2097
- error: `Lines 1-${fmLineCount} are frontmatter and cannot be replaced directly. Content starts at line ${fmLineCount + 1}.`,
2132
+ error: `Line ${rawStart} is inside frontmatter (lines 1-${fmLineCount}). Content starts at line ${fmLineCount + 1}.`,
2098
2133
  };
2099
2134
  }
2135
+ const minLine = fmLineCount > 0 ? fmLineCount + 1 : 1;
2136
+ const boundedStartLine = toBoundedInt(params.startLine, minLine, minLine, Math.max(minLine, originalLineCount));
2137
+ const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, originalLineCount));
2100
2138
  let startIndex = boundedStartLine - 1;
2101
2139
  let lineCountToReplace = boundedEndLine - boundedStartLine + 1;
2102
2140
  const replacedText = allLines.slice(startIndex, boundedEndLine).join('\n');
@@ -2128,44 +2166,9 @@ export function replaceLines(params) {
2128
2166
  if (lineDelta !== 0) {
2129
2167
  warnings.push(`Line numbers shifted by ${lineDelta > 0 ? '+' : ''}${lineDelta} after this replacement. Re-read line numbers before the next mutation.`);
2130
2168
  }
2131
- const target = `${note.filename}:${boundedStartLine}-${boundedEndLine}:${replacementLines.length}:${normalized.content.length}`;
2132
- if (isTrueBool(params.dryRun)) {
2133
- const token = issueConfirmationToken({
2134
- tool: 'noteplan_replace_lines',
2135
- target,
2136
- action: 'replace_lines',
2137
- });
2138
- return {
2139
- success: true,
2140
- dryRun: true,
2141
- message: `Dry run: lines ${boundedStartLine}-${boundedEndLine} would be replaced`,
2142
- lineCountToReplace,
2143
- insertedLineCount: replacementLines.length,
2144
- lineDelta,
2145
- originalLineCount,
2146
- newLineCount,
2147
- indentationStyle,
2148
- linesRetabbed: normalized.linesRetabbed,
2149
- removedAttachmentReferences: removedAttachmentReferences.slice(0, 20),
2150
- removedAttachmentReferencesTruncated: removedAttachmentReferences.length > 20,
2151
- warnings: warnings.length > 0 ? warnings : undefined,
2152
- ...token,
2153
- };
2154
- }
2155
- const confirmation = validateAndConsumeConfirmationToken(params.confirmationToken, {
2156
- tool: 'noteplan_replace_lines',
2157
- target,
2158
- action: 'replace_lines',
2159
- });
2160
- if (!confirmation.ok) {
2161
- return {
2162
- success: false,
2163
- error: confirmationFailureMessage('noteplan_replace_lines', confirmation.reason),
2164
- };
2165
- }
2166
2169
  allLines.splice(startIndex, lineCountToReplace, ...replacementLines);
2167
2170
  const writeIdentifier = note.source === 'space' ? (note.id || note.filename) : note.filename;
2168
- store.updateNote(writeIdentifier, allLines.join('\n'), { source: note.source });
2171
+ await store.updateNote(writeIdentifier, allLines.join('\n'), { source: note.source });
2169
2172
  return {
2170
2173
  success: true,
2171
2174
  message: `Lines ${boundedStartLine}-${boundedEndLine} replaced`,
@@ -2253,7 +2256,7 @@ export const searchParagraphsGlobalSchema = z.object({
2253
2256
  offset: z.number().min(0).optional().default(0).describe('Pagination offset'),
2254
2257
  cursor: z.string().optional().describe('Cursor token from previous page (preferred over offset)'),
2255
2258
  });
2256
- export function searchParagraphsGlobal(params) {
2259
+ export async function searchParagraphsGlobal(params) {
2257
2260
  const query = typeof params?.query === 'string' ? params.query.trim() : '';
2258
2261
  if (!query) {
2259
2262
  return {
@@ -2275,7 +2278,7 @@ export function searchParagraphsGlobal(params) {
2275
2278
  const noteTypes = normalizeTypeList(params.noteTypes);
2276
2279
  const preferCalendar = params.preferCalendar === true;
2277
2280
  const periodicOnly = params.periodicOnly === true;
2278
- const allNotes = store.listNotes({
2281
+ const allNotes = await store.listNotes({
2279
2282
  folder: params.folder,
2280
2283
  space: params.space,
2281
2284
  });