@noteplanco/noteplan-mcp 1.1.8 → 1.1.9

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 (39) hide show
  1. package/dist/noteplan/frontmatter-parser.d.ts +2 -2
  2. package/dist/noteplan/frontmatter-parser.js +2 -2
  3. package/dist/noteplan/markdown-parser.test.js +92 -0
  4. package/dist/noteplan/markdown-parser.test.js.map +1 -1
  5. package/dist/noteplan/sqlite-loader.d.ts +11 -2
  6. package/dist/noteplan/sqlite-loader.d.ts.map +1 -1
  7. package/dist/noteplan/sqlite-loader.js +143 -15
  8. package/dist/noteplan/sqlite-loader.js.map +1 -1
  9. package/dist/noteplan/sqlite-writer.d.ts.map +1 -1
  10. package/dist/noteplan/sqlite-writer.js +3 -0
  11. package/dist/noteplan/sqlite-writer.js.map +1 -1
  12. package/dist/noteplan/unified-store.d.ts.map +1 -1
  13. package/dist/noteplan/unified-store.js +8 -1
  14. package/dist/noteplan/unified-store.js.map +1 -1
  15. package/dist/server.d.ts.map +1 -1
  16. package/dist/server.js +32 -10
  17. package/dist/server.js.map +1 -1
  18. package/dist/tools/events.d.ts +16 -16
  19. package/dist/tools/events.d.ts.map +1 -1
  20. package/dist/tools/events.js +17 -2
  21. package/dist/tools/events.js.map +1 -1
  22. package/dist/tools/notes.d.ts +253 -30
  23. package/dist/tools/notes.d.ts.map +1 -1
  24. package/dist/tools/notes.js +124 -30
  25. package/dist/tools/notes.js.map +1 -1
  26. package/dist/tools/notes.test.js +757 -1
  27. package/dist/tools/notes.test.js.map +1 -1
  28. package/dist/tools/tasks.d.ts +86 -10
  29. package/dist/tools/tasks.d.ts.map +1 -1
  30. package/dist/tools/tasks.js +84 -25
  31. package/dist/tools/tasks.js.map +1 -1
  32. package/dist/tools/ui-automation.d.ts +74 -0
  33. package/dist/tools/ui-automation.d.ts.map +1 -0
  34. package/dist/tools/ui-automation.js +209 -0
  35. package/dist/tools/ui-automation.js.map +1 -0
  36. package/dist/utils/version.d.ts.map +1 -1
  37. package/dist/utils/version.js +23 -5
  38. package/dist/utils/version.js.map +1 -1
  39. package/package.json +1 -1
@@ -69,7 +69,7 @@ function resolveNoteTarget(id, filename, space) {
69
69
  note,
70
70
  };
71
71
  }
72
- function resolveWritableNoteReference(input) {
72
+ export function resolveWritableNoteReference(input) {
73
73
  if (input.id && input.id.trim().length > 0) {
74
74
  const note = store.getNote({ id: input.id.trim(), space: input.space?.trim() });
75
75
  return { note, error: note ? undefined : 'Note not found' };
@@ -124,7 +124,7 @@ function resolveWritableNoteReference(input) {
124
124
  error: 'Provide one note reference: id, filename, title, date, or query',
125
125
  };
126
126
  }
127
- function getWritableIdentifier(note) {
127
+ export function getWritableIdentifier(note) {
128
128
  if (note.source === 'space') {
129
129
  return {
130
130
  identifier: note.id || note.filename,
@@ -302,7 +302,11 @@ export const createNoteSchema = z.object({
302
302
  templateTypes: z.array(z.enum(['empty-note', 'meeting-note', 'project-note', 'calendar-note'])).optional().describe('Template type tags — used when noteType="template"'),
303
303
  });
304
304
  export const updateNoteSchema = z.object({
305
- filename: z.string().describe('Filename/path of the note to update'),
305
+ id: z.string().optional().describe('Note ID (preferred for space notes)'),
306
+ filename: z.string().optional().describe('Filename/path of the note to update'),
307
+ title: z.string().optional().describe('Note title to search for'),
308
+ date: z.string().optional().describe('Date for calendar notes (YYYYMMDD, YYYY-MM-DD, today, tomorrow, yesterday)'),
309
+ query: z.string().optional().describe('Fuzzy note query'),
306
310
  space: z.string().optional().describe('Space name or ID to search in'),
307
311
  content: z
308
312
  .string()
@@ -323,6 +327,14 @@ export const updateNoteSchema = z.object({
323
327
  .boolean()
324
328
  .optional()
325
329
  .describe('Allow replacing note content with empty/blank text (default: false)'),
330
+ }).superRefine((input, ctx) => {
331
+ if (!input.id && !input.filename && !input.title && !input.date && !input.query) {
332
+ ctx.addIssue({
333
+ code: z.ZodIssueCode.custom,
334
+ message: 'Provide one note reference: id, filename, title, date, or query',
335
+ path: ['filename'],
336
+ });
337
+ }
326
338
  });
327
339
  export const deleteNoteSchema = z.object({
328
340
  id: z.string().optional().describe('Note ID (preferred for TeamSpace notes)'),
@@ -348,6 +360,9 @@ export const deleteNoteSchema = z.object({
348
360
  export const moveNoteSchema = z.object({
349
361
  id: z.string().optional().describe('Note ID (preferred for TeamSpace notes)'),
350
362
  filename: z.string().optional().describe('Filename/path of the note to move'),
363
+ title: z.string().optional().describe('Note title to search for'),
364
+ date: z.string().optional().describe('Date for calendar notes (YYYYMMDD, YYYY-MM-DD, today, tomorrow, yesterday)'),
365
+ query: z.string().optional().describe('Fuzzy note query'),
351
366
  space: z.string().optional().describe('Space name or ID to search in'),
352
367
  destinationFolder: z
353
368
  .string()
@@ -731,29 +746,33 @@ export function updateNote(params) {
731
746
  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.',
732
747
  };
733
748
  }
734
- const existingNote = store.getNote({ filename: params.filename, space: params.space });
735
- if (!existingNote) {
749
+ const noteRef = resolveWritableNoteReference(params);
750
+ if (!noteRef.note) {
736
751
  return {
737
752
  success: false,
738
- error: 'Note not found',
753
+ error: noteRef.error || 'Note not found',
754
+ candidates: noteRef.candidates,
739
755
  };
740
756
  }
757
+ const existingNote = noteRef.note;
741
758
  if (params.allowEmptyContent !== true && params.content.trim().length === 0) {
742
759
  return {
743
760
  success: false,
744
761
  error: 'Empty content is blocked for noteplan_update_note. Use allowEmptyContent=true to override intentionally.',
745
762
  };
746
763
  }
764
+ // Use the resolved filename as the confirmation token target for consistency
765
+ const confirmationTarget = existingNote.filename;
747
766
  if (isTrueBool(params.dryRun)) {
748
767
  const token = issueConfirmationToken({
749
768
  tool: 'noteplan_update_note',
750
- target: params.filename,
769
+ target: confirmationTarget,
751
770
  action: 'full_replace',
752
771
  });
753
772
  return {
754
773
  success: true,
755
774
  dryRun: true,
756
- message: `Dry run: note ${params.filename} would be fully replaced`,
775
+ message: `Dry run: note ${existingNote.filename} would be fully replaced`,
757
776
  note: {
758
777
  id: existingNote.id,
759
778
  title: existingNote.title,
@@ -770,7 +789,7 @@ export function updateNote(params) {
770
789
  }
771
790
  const confirmation = validateAndConsumeConfirmationToken(params.confirmationToken, {
772
791
  tool: 'noteplan_update_note',
773
- target: params.filename,
792
+ target: confirmationTarget,
774
793
  action: 'full_replace',
775
794
  });
776
795
  if (!confirmation.ok) {
@@ -865,14 +884,16 @@ export function deleteNote(params) {
865
884
  }
866
885
  export function moveNote(params) {
867
886
  try {
868
- const target = resolveNoteTarget(params.id, params.filename, params.space);
869
- if (!target.note) {
887
+ const noteRef = resolveWritableNoteReference(params);
888
+ if (!noteRef.note) {
870
889
  return {
871
890
  success: false,
872
- error: 'Note not found',
891
+ error: noteRef.error || 'Note not found',
892
+ candidates: noteRef.candidates,
873
893
  };
874
894
  }
875
- const preview = store.previewMoveNote(target.identifier, params.destinationFolder);
895
+ const writable = getWritableIdentifier(noteRef.note);
896
+ const preview = store.previewMoveNote(writable.identifier, params.destinationFolder);
876
897
  const confirmationTarget = `${preview.fromFilename}=>${preview.toFilename}::${preview.destinationParentId ?? preview.destinationFolder}`;
877
898
  if (isTrueBool(params.dryRun)) {
878
899
  const token = issueConfirmationToken({
@@ -910,7 +931,7 @@ export function moveNote(params) {
910
931
  error: confirmationFailureMessage('noteplan_move_note', confirmation.reason),
911
932
  };
912
933
  }
913
- const moved = store.moveNote(target.identifier, params.destinationFolder);
934
+ const moved = store.moveNote(writable.identifier, params.destinationFolder);
914
935
  return {
915
936
  success: true,
916
937
  message: moved.note.source === 'space'
@@ -1068,6 +1089,21 @@ export function renameNoteFile(params) {
1068
1089
  };
1069
1090
  }
1070
1091
  const renamed = store.renameSpaceNote(writeId, params.newTitle);
1092
+ // Also update the # Title heading in the note content if it matches the old title
1093
+ if (renamed.note.content) {
1094
+ const lines = renamed.note.content.split('\n');
1095
+ const titleLineIndex = lines.findIndex((l) => /^#\s+/.test(l));
1096
+ if (titleLineIndex !== -1) {
1097
+ const oldHeadingTitle = lines[titleLineIndex].replace(/^#\s+/, '');
1098
+ if (oldHeadingTitle === renamed.fromTitle) {
1099
+ lines[titleLineIndex] = `# ${params.newTitle}`;
1100
+ const renamedWriteTarget = getWritableIdentifier(renamed.note);
1101
+ store.updateNote(renamedWriteTarget.identifier, lines.join('\n'), {
1102
+ source: renamedWriteTarget.source,
1103
+ });
1104
+ }
1105
+ }
1106
+ }
1071
1107
  return {
1072
1108
  success: true,
1073
1109
  message: `TeamSpace note renamed from "${renamed.fromTitle}" to "${renamed.toTitle}"`,
@@ -1175,13 +1211,25 @@ export function renameNoteFile(params) {
1175
1211
  }
1176
1212
  // Get note with line numbers
1177
1213
  export const getParagraphsSchema = z.object({
1178
- filename: z.string().describe('Filename/path of the note'),
1214
+ id: z.string().optional().describe('Note ID (preferred for space notes)'),
1215
+ filename: z.string().optional().describe('Filename/path of the note'),
1216
+ title: z.string().optional().describe('Note title to search for'),
1217
+ date: z.string().optional().describe('Date for calendar notes (YYYYMMDD, YYYY-MM-DD, today, tomorrow, yesterday)'),
1218
+ query: z.string().optional().describe('Fuzzy note query'),
1179
1219
  space: z.string().optional().describe('Space name or ID to search in'),
1180
1220
  startLine: z.number().min(1).optional().describe('First line to include (1-indexed, inclusive)'),
1181
1221
  endLine: z.number().min(1).optional().describe('Last line to include (1-indexed, inclusive)'),
1182
1222
  limit: z.number().min(1).max(1000).optional().default(200).describe('Maximum lines to return'),
1183
1223
  offset: z.number().min(0).optional().default(0).describe('Pagination offset within selected range'),
1184
1224
  cursor: z.string().optional().describe('Cursor token from previous page (preferred over offset)'),
1225
+ }).superRefine((input, ctx) => {
1226
+ if (!input.id && !input.filename && !input.title && !input.date && !input.query) {
1227
+ ctx.addIssue({
1228
+ code: z.ZodIssueCode.custom,
1229
+ message: 'Provide one note reference: id, filename, title, date, or query',
1230
+ path: ['filename'],
1231
+ });
1232
+ }
1185
1233
  });
1186
1234
  export const searchParagraphsSchema = z.object({
1187
1235
  id: z.string().optional().describe('Note ID (preferred for space notes)'),
@@ -1215,13 +1263,15 @@ export const searchParagraphsSchema = z.object({
1215
1263
  }
1216
1264
  });
1217
1265
  export function getParagraphs(params) {
1218
- const note = store.getNote({ filename: params.filename, space: params.space });
1219
- if (!note) {
1266
+ const noteRef = resolveWritableNoteReference(params);
1267
+ if (!noteRef.note) {
1220
1268
  return {
1221
1269
  success: false,
1222
- error: 'Note not found',
1270
+ error: noteRef.error || 'Note not found',
1271
+ candidates: noteRef.candidates,
1223
1272
  };
1224
1273
  }
1274
+ const note = noteRef.note;
1225
1275
  const allLines = note.content.split('\n');
1226
1276
  const lineWindow = buildLineWindow(allLines, {
1227
1277
  startLine: params.startLine,
@@ -1401,15 +1451,39 @@ export function searchParagraphs(params) {
1401
1451
  }
1402
1452
  // Granular note operation schemas
1403
1453
  export const setPropertySchema = z.object({
1404
- filename: z.string().describe('Filename/path of the note'),
1454
+ id: z.string().optional().describe('Note ID (preferred for space notes)'),
1455
+ filename: z.string().optional().describe('Filename/path of the note'),
1456
+ title: z.string().optional().describe('Note title to search for'),
1457
+ date: z.string().optional().describe('Date for calendar notes (YYYYMMDD, YYYY-MM-DD, today, tomorrow, yesterday)'),
1458
+ query: z.string().optional().describe('Fuzzy note query'),
1405
1459
  space: z.string().optional().describe('Space name or ID to search in'),
1406
1460
  key: z.string().describe('Property key (e.g., "icon", "bg-color", "status")'),
1407
1461
  value: z.string().describe('Property value'),
1462
+ }).superRefine((input, ctx) => {
1463
+ if (!input.id && !input.filename && !input.title && !input.date && !input.query) {
1464
+ ctx.addIssue({
1465
+ code: z.ZodIssueCode.custom,
1466
+ message: 'Provide one note reference: id, filename, title, date, or query',
1467
+ path: ['filename'],
1468
+ });
1469
+ }
1408
1470
  });
1409
1471
  export const removePropertySchema = z.object({
1410
- filename: z.string().describe('Filename/path of the note'),
1472
+ id: z.string().optional().describe('Note ID (preferred for space notes)'),
1473
+ filename: z.string().optional().describe('Filename/path of the note'),
1474
+ title: z.string().optional().describe('Note title to search for'),
1475
+ date: z.string().optional().describe('Date for calendar notes (YYYYMMDD, YYYY-MM-DD, today, tomorrow, yesterday)'),
1476
+ query: z.string().optional().describe('Fuzzy note query'),
1411
1477
  space: z.string().optional().describe('Space name or ID to search in'),
1412
1478
  key: z.string().describe('Property key to remove'),
1479
+ }).superRefine((input, ctx) => {
1480
+ if (!input.id && !input.filename && !input.title && !input.date && !input.query) {
1481
+ ctx.addIssue({
1482
+ code: z.ZodIssueCode.custom,
1483
+ message: 'Provide one note reference: id, filename, title, date, or query',
1484
+ path: ['filename'],
1485
+ });
1486
+ }
1413
1487
  });
1414
1488
  export const insertContentSchema = z.object({
1415
1489
  id: z.string().optional().describe('Note ID (preferred for space notes)'),
@@ -1551,13 +1625,14 @@ export const replaceLinesSchema = z.object({
1551
1625
  // Granular note operation implementations
1552
1626
  export function setProperty(params) {
1553
1627
  try {
1554
- const note = store.getNote({ filename: params.filename, space: params.space });
1555
- if (!note) {
1556
- return { success: false, error: 'Note not found' };
1628
+ const noteRef = resolveWritableNoteReference(params);
1629
+ if (!noteRef.note) {
1630
+ return { success: false, error: noteRef.error || 'Note not found', candidates: noteRef.candidates };
1557
1631
  }
1632
+ const note = noteRef.note;
1558
1633
  const newContent = frontmatter.setFrontmatterProperty(note.content, params.key, params.value);
1559
- const writeIdentifier = note.source === 'space' ? (note.id || note.filename) : note.filename;
1560
- store.updateNote(writeIdentifier, newContent, { source: note.source });
1634
+ const writable = getWritableIdentifier(note);
1635
+ store.updateNote(writable.identifier, newContent, { source: writable.source });
1561
1636
  return {
1562
1637
  success: true,
1563
1638
  message: `Property "${params.key}" set to "${params.value}"`,
@@ -1572,13 +1647,14 @@ export function setProperty(params) {
1572
1647
  }
1573
1648
  export function removeProperty(params) {
1574
1649
  try {
1575
- const note = store.getNote({ filename: params.filename, space: params.space });
1576
- if (!note) {
1577
- return { success: false, error: 'Note not found' };
1650
+ const noteRef = resolveWritableNoteReference(params);
1651
+ if (!noteRef.note) {
1652
+ return { success: false, error: noteRef.error || 'Note not found', candidates: noteRef.candidates };
1578
1653
  }
1654
+ const note = noteRef.note;
1579
1655
  const newContent = frontmatter.removeFrontmatterProperty(note.content, params.key);
1580
- const writeIdentifier = note.source === 'space' ? (note.id || note.filename) : note.filename;
1581
- store.updateNote(writeIdentifier, newContent, { source: note.source });
1656
+ const writable = getWritableIdentifier(note);
1657
+ store.updateNote(writable.identifier, newContent, { source: writable.source });
1582
1658
  return {
1583
1659
  success: true,
1584
1660
  message: `Property "${params.key}" removed`,
@@ -1722,6 +1798,15 @@ export function appendContent(params) {
1722
1798
  }
1723
1799
  export function deleteLines(params) {
1724
1800
  try {
1801
+ // Validate required line params — MCP may deliver them as undefined when omitted
1802
+ const rawStart = params.startLine !== undefined && params.startLine !== null ? Number(params.startLine) : NaN;
1803
+ const rawEnd = params.endLine !== undefined && params.endLine !== null ? Number(params.endLine) : NaN;
1804
+ if (!Number.isFinite(rawStart)) {
1805
+ return { success: false, error: 'startLine is required (1-indexed).' };
1806
+ }
1807
+ if (!Number.isFinite(rawEnd)) {
1808
+ return { success: false, error: 'endLine is required (1-indexed). Pass the same value as startLine to delete a single line.' };
1809
+ }
1725
1810
  const resolved = resolveWritableNoteReference(params);
1726
1811
  if (!resolved.note) {
1727
1812
  return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };
@@ -1881,6 +1966,15 @@ export function editLine(params) {
1881
1966
  }
1882
1967
  export function replaceLines(params) {
1883
1968
  try {
1969
+ // Validate required line params — MCP may deliver them as undefined when omitted
1970
+ const rawStart = params.startLine !== undefined && params.startLine !== null ? Number(params.startLine) : NaN;
1971
+ const rawEnd = params.endLine !== undefined && params.endLine !== null ? Number(params.endLine) : NaN;
1972
+ if (!Number.isFinite(rawStart)) {
1973
+ return { success: false, error: 'startLine is required (1-indexed).' };
1974
+ }
1975
+ if (!Number.isFinite(rawEnd)) {
1976
+ return { success: false, error: 'endLine is required (1-indexed). Pass the same value as startLine to replace a single line.' };
1977
+ }
1884
1978
  const resolved = resolveWritableNoteReference(params);
1885
1979
  if (!resolved.note) {
1886
1980
  return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };