@noteplanco/noteplan-mcp 1.1.7 → 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.
- package/dist/noteplan/embeddings.d.ts +8 -0
- package/dist/noteplan/embeddings.d.ts.map +1 -1
- package/dist/noteplan/embeddings.js +3 -3
- package/dist/noteplan/embeddings.js.map +1 -1
- package/dist/noteplan/file-reader.d.ts +6 -0
- package/dist/noteplan/file-reader.d.ts.map +1 -1
- package/dist/noteplan/file-reader.js +15 -0
- package/dist/noteplan/file-reader.js.map +1 -1
- package/dist/noteplan/file-writer.d.ts.map +1 -1
- package/dist/noteplan/file-writer.js +13 -1
- package/dist/noteplan/file-writer.js.map +1 -1
- package/dist/noteplan/file-writer.test.js +4 -0
- package/dist/noteplan/file-writer.test.js.map +1 -1
- package/dist/noteplan/frontmatter-parser.d.ts +4 -4
- package/dist/noteplan/frontmatter-parser.d.ts.map +1 -1
- package/dist/noteplan/frontmatter-parser.js +11 -14
- package/dist/noteplan/frontmatter-parser.js.map +1 -1
- package/dist/noteplan/frontmatter-parser.test.js +92 -0
- package/dist/noteplan/frontmatter-parser.test.js.map +1 -1
- package/dist/noteplan/markdown-parser.d.ts.map +1 -1
- package/dist/noteplan/markdown-parser.js +4 -2
- package/dist/noteplan/markdown-parser.js.map +1 -1
- package/dist/noteplan/markdown-parser.test.js +129 -0
- package/dist/noteplan/markdown-parser.test.js.map +1 -1
- package/dist/noteplan/sqlite-loader.d.ts +11 -2
- package/dist/noteplan/sqlite-loader.d.ts.map +1 -1
- package/dist/noteplan/sqlite-loader.js +143 -15
- package/dist/noteplan/sqlite-loader.js.map +1 -1
- package/dist/noteplan/sqlite-writer.d.ts.map +1 -1
- package/dist/noteplan/sqlite-writer.js +3 -0
- package/dist/noteplan/sqlite-writer.js.map +1 -1
- package/dist/noteplan/template-docs.d.ts +35 -0
- package/dist/noteplan/template-docs.d.ts.map +1 -0
- package/dist/noteplan/template-docs.js +184 -0
- package/dist/noteplan/template-docs.js.map +1 -0
- package/dist/noteplan/unified-store.d.ts +2 -0
- package/dist/noteplan/unified-store.d.ts.map +1 -1
- package/dist/noteplan/unified-store.js +30 -7
- package/dist/noteplan/unified-store.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +307 -65
- package/dist/server.js.map +1 -1
- package/dist/tools/calendar.d.ts +2 -2
- package/dist/tools/events.d.ts +16 -16
- package/dist/tools/events.d.ts.map +1 -1
- package/dist/tools/events.js +17 -2
- package/dist/tools/events.js.map +1 -1
- package/dist/tools/notes.d.ts +327 -45
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +391 -51
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/notes.test.js +959 -1
- package/dist/tools/notes.test.js.map +1 -1
- package/dist/tools/plugins.d.ts.map +1 -1
- package/dist/tools/plugins.js +1 -0
- package/dist/tools/plugins.js.map +1 -1
- package/dist/tools/search.d.ts +2 -2
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +32 -4
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/tasks.d.ts +86 -10
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +84 -25
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/templates.d.ts +64 -3
- package/dist/tools/templates.d.ts.map +1 -1
- package/dist/tools/templates.js +73 -1
- package/dist/tools/templates.js.map +1 -1
- package/dist/tools/ui-automation.d.ts +74 -0
- package/dist/tools/ui-automation.d.ts.map +1 -0
- package/dist/tools/ui-automation.js +209 -0
- package/dist/tools/ui-automation.js.map +1 -0
- package/dist/utils/version.d.ts +1 -0
- package/dist/utils/version.d.ts.map +1 -1
- package/dist/utils/version.js +89 -27
- package/dist/utils/version.js.map +1 -1
- package/docs/templates.db.gz +0 -0
- package/docs/x-callback-url.md +318 -0
- package/package.json +1 -1
package/dist/tools/notes.js
CHANGED
|
@@ -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
|
-
|
|
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()
|
|
@@ -698,6 +713,7 @@ export function createNote(params) {
|
|
|
698
713
|
});
|
|
699
714
|
return {
|
|
700
715
|
success: true,
|
|
716
|
+
tip: 'Use action "set_property" to add frontmatter fields (e.g. type, tags) or "remove_property" to delete them.',
|
|
701
717
|
note: {
|
|
702
718
|
title: result.note.title,
|
|
703
719
|
filename: result.note.filename,
|
|
@@ -730,29 +746,33 @@ export function updateNote(params) {
|
|
|
730
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.',
|
|
731
747
|
};
|
|
732
748
|
}
|
|
733
|
-
const
|
|
734
|
-
if (!
|
|
749
|
+
const noteRef = resolveWritableNoteReference(params);
|
|
750
|
+
if (!noteRef.note) {
|
|
735
751
|
return {
|
|
736
752
|
success: false,
|
|
737
|
-
error: 'Note not found',
|
|
753
|
+
error: noteRef.error || 'Note not found',
|
|
754
|
+
candidates: noteRef.candidates,
|
|
738
755
|
};
|
|
739
756
|
}
|
|
757
|
+
const existingNote = noteRef.note;
|
|
740
758
|
if (params.allowEmptyContent !== true && params.content.trim().length === 0) {
|
|
741
759
|
return {
|
|
742
760
|
success: false,
|
|
743
761
|
error: 'Empty content is blocked for noteplan_update_note. Use allowEmptyContent=true to override intentionally.',
|
|
744
762
|
};
|
|
745
763
|
}
|
|
764
|
+
// Use the resolved filename as the confirmation token target for consistency
|
|
765
|
+
const confirmationTarget = existingNote.filename;
|
|
746
766
|
if (isTrueBool(params.dryRun)) {
|
|
747
767
|
const token = issueConfirmationToken({
|
|
748
768
|
tool: 'noteplan_update_note',
|
|
749
|
-
target:
|
|
769
|
+
target: confirmationTarget,
|
|
750
770
|
action: 'full_replace',
|
|
751
771
|
});
|
|
752
772
|
return {
|
|
753
773
|
success: true,
|
|
754
774
|
dryRun: true,
|
|
755
|
-
message: `Dry run: note ${
|
|
775
|
+
message: `Dry run: note ${existingNote.filename} would be fully replaced`,
|
|
756
776
|
note: {
|
|
757
777
|
id: existingNote.id,
|
|
758
778
|
title: existingNote.title,
|
|
@@ -769,7 +789,7 @@ export function updateNote(params) {
|
|
|
769
789
|
}
|
|
770
790
|
const confirmation = validateAndConsumeConfirmationToken(params.confirmationToken, {
|
|
771
791
|
tool: 'noteplan_update_note',
|
|
772
|
-
target:
|
|
792
|
+
target: confirmationTarget,
|
|
773
793
|
action: 'full_replace',
|
|
774
794
|
});
|
|
775
795
|
if (!confirmation.ok) {
|
|
@@ -864,14 +884,16 @@ export function deleteNote(params) {
|
|
|
864
884
|
}
|
|
865
885
|
export function moveNote(params) {
|
|
866
886
|
try {
|
|
867
|
-
const
|
|
868
|
-
if (!
|
|
887
|
+
const noteRef = resolveWritableNoteReference(params);
|
|
888
|
+
if (!noteRef.note) {
|
|
869
889
|
return {
|
|
870
890
|
success: false,
|
|
871
|
-
error: 'Note not found',
|
|
891
|
+
error: noteRef.error || 'Note not found',
|
|
892
|
+
candidates: noteRef.candidates,
|
|
872
893
|
};
|
|
873
894
|
}
|
|
874
|
-
const
|
|
895
|
+
const writable = getWritableIdentifier(noteRef.note);
|
|
896
|
+
const preview = store.previewMoveNote(writable.identifier, params.destinationFolder);
|
|
875
897
|
const confirmationTarget = `${preview.fromFilename}=>${preview.toFilename}::${preview.destinationParentId ?? preview.destinationFolder}`;
|
|
876
898
|
if (isTrueBool(params.dryRun)) {
|
|
877
899
|
const token = issueConfirmationToken({
|
|
@@ -909,7 +931,7 @@ export function moveNote(params) {
|
|
|
909
931
|
error: confirmationFailureMessage('noteplan_move_note', confirmation.reason),
|
|
910
932
|
};
|
|
911
933
|
}
|
|
912
|
-
const moved = store.moveNote(
|
|
934
|
+
const moved = store.moveNote(writable.identifier, params.destinationFolder);
|
|
913
935
|
return {
|
|
914
936
|
success: true,
|
|
915
937
|
message: moved.note.source === 'space'
|
|
@@ -1067,6 +1089,21 @@ export function renameNoteFile(params) {
|
|
|
1067
1089
|
};
|
|
1068
1090
|
}
|
|
1069
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
|
+
}
|
|
1070
1107
|
return {
|
|
1071
1108
|
success: true,
|
|
1072
1109
|
message: `TeamSpace note renamed from "${renamed.fromTitle}" to "${renamed.toTitle}"`,
|
|
@@ -1174,13 +1211,25 @@ export function renameNoteFile(params) {
|
|
|
1174
1211
|
}
|
|
1175
1212
|
// Get note with line numbers
|
|
1176
1213
|
export const getParagraphsSchema = z.object({
|
|
1177
|
-
|
|
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'),
|
|
1178
1219
|
space: z.string().optional().describe('Space name or ID to search in'),
|
|
1179
1220
|
startLine: z.number().min(1).optional().describe('First line to include (1-indexed, inclusive)'),
|
|
1180
1221
|
endLine: z.number().min(1).optional().describe('Last line to include (1-indexed, inclusive)'),
|
|
1181
1222
|
limit: z.number().min(1).max(1000).optional().default(200).describe('Maximum lines to return'),
|
|
1182
1223
|
offset: z.number().min(0).optional().default(0).describe('Pagination offset within selected range'),
|
|
1183
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
|
+
}
|
|
1184
1233
|
});
|
|
1185
1234
|
export const searchParagraphsSchema = z.object({
|
|
1186
1235
|
id: z.string().optional().describe('Note ID (preferred for space notes)'),
|
|
@@ -1214,13 +1263,15 @@ export const searchParagraphsSchema = z.object({
|
|
|
1214
1263
|
}
|
|
1215
1264
|
});
|
|
1216
1265
|
export function getParagraphs(params) {
|
|
1217
|
-
const
|
|
1218
|
-
if (!note) {
|
|
1266
|
+
const noteRef = resolveWritableNoteReference(params);
|
|
1267
|
+
if (!noteRef.note) {
|
|
1219
1268
|
return {
|
|
1220
1269
|
success: false,
|
|
1221
|
-
error: 'Note not found',
|
|
1270
|
+
error: noteRef.error || 'Note not found',
|
|
1271
|
+
candidates: noteRef.candidates,
|
|
1222
1272
|
};
|
|
1223
1273
|
}
|
|
1274
|
+
const note = noteRef.note;
|
|
1224
1275
|
const allLines = note.content.split('\n');
|
|
1225
1276
|
const lineWindow = buildLineWindow(allLines, {
|
|
1226
1277
|
startLine: params.startLine,
|
|
@@ -1365,6 +1416,7 @@ export function searchParagraphs(params) {
|
|
|
1365
1416
|
const nextCursor = hasMore ? String(offset + page.length) : null;
|
|
1366
1417
|
const result = {
|
|
1367
1418
|
success: true,
|
|
1419
|
+
tip: 'To search across ALL notes at once, use action "search_global" instead. It supports query "*" to match all tasks.',
|
|
1368
1420
|
query,
|
|
1369
1421
|
count: page.length,
|
|
1370
1422
|
totalCount: allMatches.length,
|
|
@@ -1399,15 +1451,39 @@ export function searchParagraphs(params) {
|
|
|
1399
1451
|
}
|
|
1400
1452
|
// Granular note operation schemas
|
|
1401
1453
|
export const setPropertySchema = z.object({
|
|
1402
|
-
|
|
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'),
|
|
1403
1459
|
space: z.string().optional().describe('Space name or ID to search in'),
|
|
1404
1460
|
key: z.string().describe('Property key (e.g., "icon", "bg-color", "status")'),
|
|
1405
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
|
+
}
|
|
1406
1470
|
});
|
|
1407
1471
|
export const removePropertySchema = z.object({
|
|
1408
|
-
|
|
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'),
|
|
1409
1477
|
space: z.string().optional().describe('Space name or ID to search in'),
|
|
1410
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
|
+
}
|
|
1411
1487
|
});
|
|
1412
1488
|
export const insertContentSchema = z.object({
|
|
1413
1489
|
id: z.string().optional().describe('Note ID (preferred for space notes)'),
|
|
@@ -1549,13 +1625,14 @@ export const replaceLinesSchema = z.object({
|
|
|
1549
1625
|
// Granular note operation implementations
|
|
1550
1626
|
export function setProperty(params) {
|
|
1551
1627
|
try {
|
|
1552
|
-
const
|
|
1553
|
-
if (!note) {
|
|
1554
|
-
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 };
|
|
1555
1631
|
}
|
|
1632
|
+
const note = noteRef.note;
|
|
1556
1633
|
const newContent = frontmatter.setFrontmatterProperty(note.content, params.key, params.value);
|
|
1557
|
-
const
|
|
1558
|
-
store.updateNote(
|
|
1634
|
+
const writable = getWritableIdentifier(note);
|
|
1635
|
+
store.updateNote(writable.identifier, newContent, { source: writable.source });
|
|
1559
1636
|
return {
|
|
1560
1637
|
success: true,
|
|
1561
1638
|
message: `Property "${params.key}" set to "${params.value}"`,
|
|
@@ -1570,13 +1647,14 @@ export function setProperty(params) {
|
|
|
1570
1647
|
}
|
|
1571
1648
|
export function removeProperty(params) {
|
|
1572
1649
|
try {
|
|
1573
|
-
const
|
|
1574
|
-
if (!note) {
|
|
1575
|
-
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 };
|
|
1576
1653
|
}
|
|
1654
|
+
const note = noteRef.note;
|
|
1577
1655
|
const newContent = frontmatter.removeFrontmatterProperty(note.content, params.key);
|
|
1578
|
-
const
|
|
1579
|
-
store.updateNote(
|
|
1656
|
+
const writable = getWritableIdentifier(note);
|
|
1657
|
+
store.updateNote(writable.identifier, newContent, { source: writable.source });
|
|
1580
1658
|
return {
|
|
1581
1659
|
success: true,
|
|
1582
1660
|
message: `Property "${params.key}" removed`,
|
|
@@ -1660,6 +1738,7 @@ export function insertContent(params) {
|
|
|
1660
1738
|
});
|
|
1661
1739
|
return {
|
|
1662
1740
|
success: true,
|
|
1741
|
+
tip: 'Use noteplan_paragraphs(action: "get") to inspect line numbers and content before making further edits.',
|
|
1663
1742
|
message: `Content inserted at ${params.position}`,
|
|
1664
1743
|
note: {
|
|
1665
1744
|
id: note.id,
|
|
@@ -1719,20 +1798,36 @@ export function appendContent(params) {
|
|
|
1719
1798
|
}
|
|
1720
1799
|
export function deleteLines(params) {
|
|
1721
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
|
+
}
|
|
1722
1810
|
const resolved = resolveWritableNoteReference(params);
|
|
1723
1811
|
if (!resolved.note) {
|
|
1724
1812
|
return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };
|
|
1725
1813
|
}
|
|
1726
1814
|
const note = resolved.note;
|
|
1727
1815
|
const allLines = note.content.split('\n');
|
|
1728
|
-
// Line numbers are
|
|
1729
|
-
const
|
|
1730
|
-
const
|
|
1731
|
-
const
|
|
1732
|
-
const
|
|
1816
|
+
// Line numbers are absolute (1-indexed), matching get_notes/getParagraphs
|
|
1817
|
+
const totalLineCount = allLines.length;
|
|
1818
|
+
const fmLineCount = frontmatter.getFrontmatterLineCount(note.content);
|
|
1819
|
+
const minLine = fmLineCount > 0 ? fmLineCount + 1 : 1;
|
|
1820
|
+
const boundedStartLine = toBoundedInt(params.startLine, minLine, minLine, Math.max(minLine, totalLineCount));
|
|
1821
|
+
const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, totalLineCount));
|
|
1822
|
+
if (boundedStartLine <= fmLineCount) {
|
|
1823
|
+
return {
|
|
1824
|
+
success: false,
|
|
1825
|
+
error: `Lines 1-${fmLineCount} are frontmatter and cannot be deleted. Content starts at line ${fmLineCount + 1}.`,
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1733
1828
|
const lineCountToDelete = boundedEndLine - boundedStartLine + 1;
|
|
1734
|
-
const previewStartIndex =
|
|
1735
|
-
const previewEndIndexExclusive =
|
|
1829
|
+
const previewStartIndex = boundedStartLine - 1;
|
|
1830
|
+
const previewEndIndexExclusive = boundedEndLine;
|
|
1736
1831
|
const deletedLinesPreview = allLines
|
|
1737
1832
|
.slice(previewStartIndex, previewEndIndexExclusive)
|
|
1738
1833
|
.slice(0, 20)
|
|
@@ -1776,7 +1871,10 @@ export function deleteLines(params) {
|
|
|
1776
1871
|
error: confirmationFailureMessage('noteplan_delete_lines', confirmation.reason),
|
|
1777
1872
|
};
|
|
1778
1873
|
}
|
|
1779
|
-
|
|
1874
|
+
// Splice out lines using absolute indices (no frontmatter offset needed)
|
|
1875
|
+
const splicedLines = [...allLines];
|
|
1876
|
+
splicedLines.splice(boundedStartLine - 1, lineCountToDelete);
|
|
1877
|
+
const newContent = splicedLines.join('\n');
|
|
1780
1878
|
const writeIdentifier = note.source === 'space' ? (note.id || note.filename) : note.filename;
|
|
1781
1879
|
store.updateNote(writeIdentifier, newContent, { source: note.source });
|
|
1782
1880
|
return {
|
|
@@ -1810,13 +1908,19 @@ export function editLine(params) {
|
|
|
1810
1908
|
const note = resolved.note;
|
|
1811
1909
|
const lines = note.content.split('\n');
|
|
1812
1910
|
const originalLineCount = lines.length;
|
|
1813
|
-
//
|
|
1814
|
-
const
|
|
1815
|
-
const lineIndex =
|
|
1816
|
-
if (lineIndex <
|
|
1911
|
+
// Line numbers are absolute (1-indexed), matching get_notes/getParagraphs
|
|
1912
|
+
const fmLineCount = frontmatter.getFrontmatterLineCount(note.content);
|
|
1913
|
+
const lineIndex = Number(params.line) - 1; // Convert to 0-indexed
|
|
1914
|
+
if (lineIndex < 0 || lineIndex >= lines.length) {
|
|
1915
|
+
return {
|
|
1916
|
+
success: false,
|
|
1917
|
+
error: `Line ${params.line} does not exist (note has ${lines.length} lines)`,
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
if (fmLineCount > 0 && lineIndex < fmLineCount) {
|
|
1817
1921
|
return {
|
|
1818
1922
|
success: false,
|
|
1819
|
-
error: `Line ${params.line}
|
|
1923
|
+
error: `Line ${params.line} is inside frontmatter (lines 1-${fmLineCount}). Content starts at line ${fmLineCount + 1}.`,
|
|
1820
1924
|
};
|
|
1821
1925
|
}
|
|
1822
1926
|
const originalLine = lines[lineIndex];
|
|
@@ -1862,6 +1966,15 @@ export function editLine(params) {
|
|
|
1862
1966
|
}
|
|
1863
1967
|
export function replaceLines(params) {
|
|
1864
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
|
+
}
|
|
1865
1978
|
const resolved = resolveWritableNoteReference(params);
|
|
1866
1979
|
if (!resolved.note) {
|
|
1867
1980
|
return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };
|
|
@@ -1869,16 +1982,31 @@ export function replaceLines(params) {
|
|
|
1869
1982
|
const note = resolved.note;
|
|
1870
1983
|
const allLines = note.content.split('\n');
|
|
1871
1984
|
const originalLineCount = allLines.length;
|
|
1872
|
-
// Line numbers are
|
|
1873
|
-
const
|
|
1874
|
-
const
|
|
1875
|
-
const boundedStartLine = toBoundedInt(params.startLine,
|
|
1876
|
-
const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine,
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1985
|
+
// Line numbers are absolute (1-indexed), matching get_notes/getParagraphs
|
|
1986
|
+
const fmLineCount = frontmatter.getFrontmatterLineCount(note.content);
|
|
1987
|
+
const minLine = fmLineCount > 0 ? fmLineCount + 1 : 1;
|
|
1988
|
+
const boundedStartLine = toBoundedInt(params.startLine, minLine, minLine, Math.max(minLine, originalLineCount));
|
|
1989
|
+
const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, originalLineCount));
|
|
1990
|
+
if (boundedStartLine <= fmLineCount) {
|
|
1991
|
+
return {
|
|
1992
|
+
success: false,
|
|
1993
|
+
error: `Lines 1-${fmLineCount} are frontmatter and cannot be replaced directly. Content starts at line ${fmLineCount + 1}.`,
|
|
1994
|
+
};
|
|
1995
|
+
}
|
|
1996
|
+
let startIndex = boundedStartLine - 1;
|
|
1997
|
+
let lineCountToReplace = boundedEndLine - boundedStartLine + 1;
|
|
1998
|
+
const replacedText = allLines.slice(startIndex, boundedEndLine).join('\n');
|
|
1880
1999
|
const indentationStyle = normalizeIndentationStyle(params.indentationStyle);
|
|
1881
2000
|
const normalized = normalizeContentIndentation(params.content, indentationStyle);
|
|
2001
|
+
// If replacement content includes frontmatter and the note already has
|
|
2002
|
+
// frontmatter, extend the splice to replace the old frontmatter too.
|
|
2003
|
+
// The agent's frontmatter is the intended update.
|
|
2004
|
+
const replacementHasFm = fmLineCount > 0
|
|
2005
|
+
&& frontmatter.parseNoteContent(normalized.content).hasFrontmatter;
|
|
2006
|
+
if (replacementHasFm) {
|
|
2007
|
+
lineCountToReplace += startIndex; // extend to also cover frontmatter lines
|
|
2008
|
+
startIndex = 0;
|
|
2009
|
+
}
|
|
1882
2010
|
if (params.allowEmptyContent !== true && normalized.content.trim().length === 0) {
|
|
1883
2011
|
return {
|
|
1884
2012
|
success: false,
|
|
@@ -1956,4 +2084,216 @@ export function replaceLines(params) {
|
|
|
1956
2084
|
};
|
|
1957
2085
|
}
|
|
1958
2086
|
}
|
|
2087
|
+
// ---------------------------------------------------------------------------
|
|
2088
|
+
// searchParagraphsGlobal — search ALL lines (including frontmatter) across notes
|
|
2089
|
+
// ---------------------------------------------------------------------------
|
|
2090
|
+
function normalizeType(value) {
|
|
2091
|
+
if (typeof value !== 'string')
|
|
2092
|
+
return undefined;
|
|
2093
|
+
const lower = value.trim().toLowerCase();
|
|
2094
|
+
if (lower === 'calendar' || lower === 'note' || lower === 'trash')
|
|
2095
|
+
return lower;
|
|
2096
|
+
return undefined;
|
|
2097
|
+
}
|
|
2098
|
+
function normalizeTypeList(values) {
|
|
2099
|
+
if (!Array.isArray(values))
|
|
2100
|
+
return undefined;
|
|
2101
|
+
const unique = new Set();
|
|
2102
|
+
for (const entry of values) {
|
|
2103
|
+
const normalized = normalizeType(entry);
|
|
2104
|
+
if (normalized)
|
|
2105
|
+
unique.add(normalized);
|
|
2106
|
+
}
|
|
2107
|
+
return unique.size > 0 ? Array.from(unique) : undefined;
|
|
2108
|
+
}
|
|
2109
|
+
function isPeriodicCalendarNote(note) {
|
|
2110
|
+
if (note.type !== 'calendar' || !note.date)
|
|
2111
|
+
return false;
|
|
2112
|
+
return note.date.includes('-');
|
|
2113
|
+
}
|
|
2114
|
+
export const searchParagraphsGlobalSchema = z.object({
|
|
2115
|
+
query: z.string().describe('Text to find across all notes (searches ALL lines including frontmatter)'),
|
|
2116
|
+
caseSensitive: z.boolean().optional().default(false).describe('Case-sensitive match (default: false)'),
|
|
2117
|
+
wholeWord: z.boolean().optional().default(false).describe('Require whole-word matches (default: false)'),
|
|
2118
|
+
status: z
|
|
2119
|
+
.enum(['open', 'done', 'cancelled', 'scheduled'])
|
|
2120
|
+
.optional()
|
|
2121
|
+
.describe('Filter results to only lines with this task status'),
|
|
2122
|
+
contextLines: z.number().min(0).max(5).optional().default(1).describe('Context lines before/after each match'),
|
|
2123
|
+
paragraphMaxChars: z
|
|
2124
|
+
.number()
|
|
2125
|
+
.min(50)
|
|
2126
|
+
.max(5000)
|
|
2127
|
+
.optional()
|
|
2128
|
+
.default(600)
|
|
2129
|
+
.describe('Maximum paragraph text chars per match'),
|
|
2130
|
+
folder: z.string().optional().describe('Restrict to a specific folder path'),
|
|
2131
|
+
space: z.string().optional().describe('Restrict to a specific space name or ID'),
|
|
2132
|
+
noteQuery: z.string().optional().describe('Filter notes by title/filename/folder substring'),
|
|
2133
|
+
noteTypes: z
|
|
2134
|
+
.array(z.enum(['calendar', 'note', 'trash']))
|
|
2135
|
+
.optional()
|
|
2136
|
+
.describe('Restrict scanned notes by type'),
|
|
2137
|
+
preferCalendar: z
|
|
2138
|
+
.boolean()
|
|
2139
|
+
.optional()
|
|
2140
|
+
.default(false)
|
|
2141
|
+
.describe('Prioritize calendar notes before maxNotes truncation'),
|
|
2142
|
+
periodicOnly: z
|
|
2143
|
+
.boolean()
|
|
2144
|
+
.optional()
|
|
2145
|
+
.default(false)
|
|
2146
|
+
.describe('When true, only scan periodic calendar notes (weekly/monthly/quarterly/yearly)'),
|
|
2147
|
+
maxNotes: z.number().min(1).max(2000).optional().default(500).describe('Maximum notes to scan'),
|
|
2148
|
+
limit: z.number().min(1).max(300).optional().default(30).describe('Maximum matches to return'),
|
|
2149
|
+
offset: z.number().min(0).optional().default(0).describe('Pagination offset'),
|
|
2150
|
+
cursor: z.string().optional().describe('Cursor token from previous page (preferred over offset)'),
|
|
2151
|
+
});
|
|
2152
|
+
export function searchParagraphsGlobal(params) {
|
|
2153
|
+
const query = typeof params?.query === 'string' ? params.query.trim() : '';
|
|
2154
|
+
if (!query) {
|
|
2155
|
+
return {
|
|
2156
|
+
success: false,
|
|
2157
|
+
error: 'query is required',
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
const caseSensitive = params.caseSensitive ?? false;
|
|
2161
|
+
const wholeWord = params.wholeWord ?? false;
|
|
2162
|
+
const contextLines = toBoundedInt(params.contextLines, 1, 0, 5);
|
|
2163
|
+
const paragraphMaxChars = toBoundedInt(params.paragraphMaxChars, 600, 50, 5000);
|
|
2164
|
+
const normalizedQuery = caseSensitive ? query : query.toLowerCase();
|
|
2165
|
+
const wildcardQuery = query === '*';
|
|
2166
|
+
const matcher = wholeWord
|
|
2167
|
+
? new RegExp(`\\b${escapeRegExp(query)}\\b`, caseSensitive ? '' : 'i')
|
|
2168
|
+
: null;
|
|
2169
|
+
const maxNotes = toBoundedInt(params.maxNotes, 500, 1, 2000);
|
|
2170
|
+
const noteQuery = typeof params.noteQuery === 'string' ? params.noteQuery.trim().toLowerCase() : '';
|
|
2171
|
+
const noteTypes = normalizeTypeList(params.noteTypes);
|
|
2172
|
+
const preferCalendar = params.preferCalendar === true;
|
|
2173
|
+
const periodicOnly = params.periodicOnly === true;
|
|
2174
|
+
const allNotes = store.listNotes({
|
|
2175
|
+
folder: params.folder,
|
|
2176
|
+
space: params.space,
|
|
2177
|
+
});
|
|
2178
|
+
let filteredNotes = noteQuery
|
|
2179
|
+
? allNotes.filter((note) => {
|
|
2180
|
+
const haystack = `${note.title} ${note.filename} ${note.folder || ''}`.toLowerCase();
|
|
2181
|
+
return haystack.includes(noteQuery);
|
|
2182
|
+
})
|
|
2183
|
+
: allNotes;
|
|
2184
|
+
if (noteTypes && noteTypes.length > 0) {
|
|
2185
|
+
filteredNotes = filteredNotes.filter((note) => noteTypes.includes(note.type));
|
|
2186
|
+
}
|
|
2187
|
+
if (periodicOnly) {
|
|
2188
|
+
filteredNotes = filteredNotes.filter((note) => isPeriodicCalendarNote(note));
|
|
2189
|
+
}
|
|
2190
|
+
if (preferCalendar) {
|
|
2191
|
+
filteredNotes = [...filteredNotes].sort((a, b) => {
|
|
2192
|
+
const aCalendar = a.type === 'calendar' ? 1 : 0;
|
|
2193
|
+
const bCalendar = b.type === 'calendar' ? 1 : 0;
|
|
2194
|
+
if (aCalendar !== bCalendar)
|
|
2195
|
+
return bCalendar - aCalendar;
|
|
2196
|
+
const aModified = a.modifiedAt?.getTime() ?? 0;
|
|
2197
|
+
const bModified = b.modifiedAt?.getTime() ?? 0;
|
|
2198
|
+
return bModified - aModified;
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
const scannedNotes = filteredNotes.slice(0, maxNotes);
|
|
2202
|
+
const truncatedByMaxNotes = filteredNotes.length > scannedNotes.length;
|
|
2203
|
+
const allMatches = [];
|
|
2204
|
+
for (const note of scannedNotes) {
|
|
2205
|
+
const allLines = note.content.split('\n');
|
|
2206
|
+
for (let lineIndex = 0; lineIndex < allLines.length; lineIndex++) {
|
|
2207
|
+
const lineContent = allLines[lineIndex];
|
|
2208
|
+
const haystack = caseSensitive ? lineContent : lineContent.toLowerCase();
|
|
2209
|
+
const isMatch = wildcardQuery
|
|
2210
|
+
? true
|
|
2211
|
+
: matcher
|
|
2212
|
+
? matcher.test(lineContent)
|
|
2213
|
+
: haystack.includes(normalizedQuery);
|
|
2214
|
+
if (!isMatch)
|
|
2215
|
+
continue;
|
|
2216
|
+
const paragraphBounds = findParagraphBounds(allLines, lineIndex);
|
|
2217
|
+
const paragraphRaw = allLines
|
|
2218
|
+
.slice(paragraphBounds.startIndex, paragraphBounds.endIndex + 1)
|
|
2219
|
+
.join('\n');
|
|
2220
|
+
const paragraphTruncated = paragraphRaw.length > paragraphMaxChars;
|
|
2221
|
+
const paragraph = paragraphTruncated
|
|
2222
|
+
? `${paragraphRaw.slice(0, Math.max(0, paragraphMaxChars - 3))}...`
|
|
2223
|
+
: paragraphRaw;
|
|
2224
|
+
const contextStart = Math.max(0, lineIndex - contextLines);
|
|
2225
|
+
const contextEnd = Math.min(allLines.length - 1, lineIndex + contextLines);
|
|
2226
|
+
const meta = parseParagraphLine(lineContent, lineIndex, lineIndex === 0);
|
|
2227
|
+
// Apply status filter (backward compat with searchTasksGlobal)
|
|
2228
|
+
if (params.status && meta.taskStatus !== params.status)
|
|
2229
|
+
continue;
|
|
2230
|
+
allMatches.push({
|
|
2231
|
+
note: {
|
|
2232
|
+
id: note.id,
|
|
2233
|
+
title: note.title,
|
|
2234
|
+
filename: note.filename,
|
|
2235
|
+
type: note.type,
|
|
2236
|
+
source: note.source,
|
|
2237
|
+
folder: note.folder,
|
|
2238
|
+
spaceId: note.spaceId,
|
|
2239
|
+
date: note.date,
|
|
2240
|
+
},
|
|
2241
|
+
lineIndex,
|
|
2242
|
+
line: lineIndex + 1,
|
|
2243
|
+
content: lineContent,
|
|
2244
|
+
// Backward-compat alias: `status` mirrors `taskStatus` for old consumers
|
|
2245
|
+
...(meta.taskStatus !== undefined && { status: meta.taskStatus }),
|
|
2246
|
+
type: meta.type,
|
|
2247
|
+
indentLevel: meta.indentLevel,
|
|
2248
|
+
...(meta.headingLevel !== undefined && { headingLevel: meta.headingLevel }),
|
|
2249
|
+
...(meta.taskStatus !== undefined && { taskStatus: meta.taskStatus }),
|
|
2250
|
+
...(meta.priority !== undefined && { priority: meta.priority }),
|
|
2251
|
+
...(meta.marker !== undefined && { marker: meta.marker }),
|
|
2252
|
+
...(meta.hasCheckbox !== undefined && { hasCheckbox: meta.hasCheckbox }),
|
|
2253
|
+
tags: meta.tags,
|
|
2254
|
+
mentions: meta.mentions,
|
|
2255
|
+
...(meta.scheduledDate !== undefined && { scheduledDate: meta.scheduledDate }),
|
|
2256
|
+
paragraphStartLine: paragraphBounds.startIndex + 1,
|
|
2257
|
+
paragraphEndLine: paragraphBounds.endIndex + 1,
|
|
2258
|
+
paragraph,
|
|
2259
|
+
paragraphTruncated,
|
|
2260
|
+
contextBefore: allLines.slice(contextStart, lineIndex),
|
|
2261
|
+
contextAfter: allLines.slice(lineIndex + 1, contextEnd + 1),
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
const offset = toBoundedInt(params.cursor ?? params.offset, 0, 0, Number.MAX_SAFE_INTEGER);
|
|
2266
|
+
const limit = toBoundedInt(params.limit, 30, 1, 300);
|
|
2267
|
+
const page = allMatches.slice(offset, offset + limit);
|
|
2268
|
+
const hasMore = offset + page.length < allMatches.length;
|
|
2269
|
+
const nextCursor = hasMore ? String(offset + page.length) : null;
|
|
2270
|
+
const result = {
|
|
2271
|
+
success: true,
|
|
2272
|
+
query,
|
|
2273
|
+
count: page.length,
|
|
2274
|
+
totalCount: allMatches.length,
|
|
2275
|
+
offset,
|
|
2276
|
+
limit,
|
|
2277
|
+
hasMore,
|
|
2278
|
+
nextCursor,
|
|
2279
|
+
scannedNoteCount: scannedNotes.length,
|
|
2280
|
+
totalNotes: filteredNotes.length,
|
|
2281
|
+
truncatedByMaxNotes,
|
|
2282
|
+
maxNotes,
|
|
2283
|
+
noteTypes,
|
|
2284
|
+
preferCalendar,
|
|
2285
|
+
periodicOnly,
|
|
2286
|
+
matches: page,
|
|
2287
|
+
};
|
|
2288
|
+
if (hasMore) {
|
|
2289
|
+
result.performanceHints = ['Continue with nextCursor to fetch the next global paragraph match page.'];
|
|
2290
|
+
}
|
|
2291
|
+
if (truncatedByMaxNotes) {
|
|
2292
|
+
result.performanceHints = [
|
|
2293
|
+
...(result.performanceHints ?? []),
|
|
2294
|
+
'Increase maxNotes or narrow folder/space/noteQuery to reduce truncation.',
|
|
2295
|
+
];
|
|
2296
|
+
}
|
|
2297
|
+
return result;
|
|
2298
|
+
}
|
|
1959
2299
|
//# sourceMappingURL=notes.js.map
|