@noteplanco/noteplan-mcp 1.1.6 → 1.1.7
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/file-writer.d.ts.map +1 -1
- package/dist/noteplan/file-writer.js +73 -16
- package/dist/noteplan/file-writer.js.map +1 -1
- package/dist/noteplan/file-writer.test.d.ts +2 -0
- package/dist/noteplan/file-writer.test.d.ts.map +1 -0
- package/dist/noteplan/file-writer.test.js +892 -0
- package/dist/noteplan/file-writer.test.js.map +1 -0
- package/dist/noteplan/filter-store.d.ts.map +1 -1
- package/dist/noteplan/filter-store.js +13 -1
- package/dist/noteplan/filter-store.js.map +1 -1
- package/dist/noteplan/frontmatter-parser.d.ts +10 -1
- package/dist/noteplan/frontmatter-parser.d.ts.map +1 -1
- package/dist/noteplan/frontmatter-parser.js +66 -10
- package/dist/noteplan/frontmatter-parser.js.map +1 -1
- package/dist/noteplan/frontmatter-parser.test.js +484 -1
- package/dist/noteplan/frontmatter-parser.test.js.map +1 -1
- package/dist/noteplan/markdown-parser.d.ts +6 -1
- package/dist/noteplan/markdown-parser.d.ts.map +1 -1
- package/dist/noteplan/markdown-parser.js +21 -44
- package/dist/noteplan/markdown-parser.js.map +1 -1
- package/dist/noteplan/markdown-parser.test.d.ts +2 -0
- package/dist/noteplan/markdown-parser.test.d.ts.map +1 -0
- package/dist/noteplan/markdown-parser.test.js +653 -0
- package/dist/noteplan/markdown-parser.test.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +405 -205
- package/dist/server.js.map +1 -1
- package/dist/tools/attachments.d.ts +151 -0
- package/dist/tools/attachments.d.ts.map +1 -0
- package/dist/tools/attachments.js +421 -0
- package/dist/tools/attachments.js.map +1 -0
- package/dist/tools/attachments.test.d.ts +2 -0
- package/dist/tools/attachments.test.d.ts.map +1 -0
- package/dist/tools/attachments.test.js +561 -0
- package/dist/tools/attachments.test.js.map +1 -0
- package/dist/tools/calendar.d.ts +5 -5
- package/dist/tools/notes.d.ts +67 -26
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +124 -33
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/notes.test.d.ts +2 -0
- package/dist/tools/notes.test.d.ts.map +1 -0
- package/dist/tools/notes.test.js +598 -0
- package/dist/tools/notes.test.js.map +1 -0
- package/dist/tools/reminders.d.ts +4 -4
- package/dist/tools/tasks.d.ts +10 -10
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +14 -27
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/templates.d.ts +69 -0
- package/dist/tools/templates.d.ts.map +1 -0
- package/dist/tools/templates.js +145 -0
- package/dist/tools/templates.js.map +1 -0
- package/dist/tools/templates.test.d.ts +2 -0
- package/dist/tools/templates.test.d.ts.map +1 -0
- package/dist/tools/templates.test.js +48 -0
- package/dist/tools/templates.test.js.map +1 -0
- package/dist/tools/ui.d.ts +2 -0
- package/dist/tools/ui.d.ts.map +1 -1
- package/dist/tools/ui.js +24 -0
- package/dist/tools/ui.js.map +1 -1
- package/dist/utils/applescript.d.ts.map +1 -1
- package/dist/utils/applescript.js +21 -0
- package/dist/utils/applescript.js.map +1 -1
- package/dist/utils/confirmation-tokens.test.d.ts +2 -0
- package/dist/utils/confirmation-tokens.test.d.ts.map +1 -0
- package/dist/utils/confirmation-tokens.test.js +159 -0
- package/dist/utils/confirmation-tokens.test.js.map +1 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -1
- package/dist/utils/version.js +4 -0
- package/dist/utils/version.js.map +1 -1
- package/package.json +1 -1
package/dist/tools/notes.js
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import * as store from '../noteplan/unified-store.js';
|
|
5
5
|
import * as frontmatter from '../noteplan/frontmatter-parser.js';
|
|
6
|
+
import { ensureTemplateFrontmatter } from './templates.js';
|
|
6
7
|
import { issueConfirmationToken, validateAndConsumeConfirmationToken, } from '../utils/confirmation-tokens.js';
|
|
7
8
|
import { parseParagraphLine, buildParagraphLine } from '../noteplan/markdown-parser.js';
|
|
8
9
|
function toBoundedInt(value, defaultValue, min, max) {
|
|
@@ -34,6 +35,17 @@ function toOptionalBoolean(value) {
|
|
|
34
35
|
}
|
|
35
36
|
return undefined;
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Coerce a value to boolean — handles MCP delivering boolean params as strings.
|
|
40
|
+
* Returns true for boolean true or string "true".
|
|
41
|
+
*/
|
|
42
|
+
function isTrueBool(value) {
|
|
43
|
+
if (typeof value === 'boolean')
|
|
44
|
+
return value;
|
|
45
|
+
if (typeof value === 'string')
|
|
46
|
+
return value.trim().toLowerCase() === 'true';
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
37
49
|
function confirmationFailureMessage(toolName, reason) {
|
|
38
50
|
const refreshHint = `Call ${toolName} with dryRun=true to get a new confirmationToken.`;
|
|
39
51
|
if (reason === 'missing') {
|
|
@@ -286,6 +298,8 @@ export const createNoteSchema = z.object({
|
|
|
286
298
|
folder: z.string().optional().describe('Folder to create the note in. Supports smart matching (e.g., "projects" matches "10 - Projects")'),
|
|
287
299
|
create_new_folder: z.boolean().optional().describe('Set to true to create a new folder instead of matching existing ones'),
|
|
288
300
|
space: z.string().optional().describe('Space name or ID to create in (e.g., "My Team" or a UUID)'),
|
|
301
|
+
noteType: z.enum(['note', 'template']).optional().default('note').describe('Type of note to create. Use "template" to create in @Templates with proper frontmatter'),
|
|
302
|
+
templateTypes: z.array(z.enum(['empty-note', 'meeting-note', 'project-note', 'calendar-note'])).optional().describe('Template type tags — used when noteType="template"'),
|
|
289
303
|
});
|
|
290
304
|
export const updateNoteSchema = z.object({
|
|
291
305
|
filename: z.string().describe('Filename/path of the note to update'),
|
|
@@ -358,6 +372,8 @@ export const moveNoteSchema = z.object({
|
|
|
358
372
|
export const renameNoteFileSchema = z.object({
|
|
359
373
|
id: z.string().optional().describe('Note ID (preferred for TeamSpace notes)'),
|
|
360
374
|
filename: z.string().optional().describe('Filename/path of the note to rename'),
|
|
375
|
+
title: z.string().optional().describe('Note title to find and rename (fuzzy matched)'),
|
|
376
|
+
query: z.string().optional().describe('Fuzzy search query to find the note'),
|
|
361
377
|
space: z.string().optional().describe('Space name or ID to search in'),
|
|
362
378
|
newFilename: z
|
|
363
379
|
.string()
|
|
@@ -381,11 +397,11 @@ export const renameNoteFileSchema = z.object({
|
|
|
381
397
|
.optional()
|
|
382
398
|
.describe('Confirmation token issued by dryRun for rename execution'),
|
|
383
399
|
}).superRefine((input, ctx) => {
|
|
384
|
-
if (!input.id && !input.filename) {
|
|
400
|
+
if (!input.id && !input.filename && !input.title && !input.query) {
|
|
385
401
|
ctx.addIssue({
|
|
386
402
|
code: z.ZodIssueCode.custom,
|
|
387
|
-
message: 'Provide one note reference: id or
|
|
388
|
-
path: ['
|
|
403
|
+
message: 'Provide one note reference: id, filename, title, or query',
|
|
404
|
+
path: ['title'],
|
|
389
405
|
});
|
|
390
406
|
}
|
|
391
407
|
});
|
|
@@ -670,8 +686,13 @@ export function resolveNote(params) {
|
|
|
670
686
|
}
|
|
671
687
|
export function createNote(params) {
|
|
672
688
|
try {
|
|
673
|
-
const
|
|
674
|
-
|
|
689
|
+
const isTemplate = params.noteType === 'template';
|
|
690
|
+
const folder = isTemplate && !params.folder ? '@Templates' : params.folder;
|
|
691
|
+
const content = isTemplate
|
|
692
|
+
? ensureTemplateFrontmatter(params.title, params.content, params.templateTypes)
|
|
693
|
+
: params.content;
|
|
694
|
+
const result = store.createNote(params.title, content, {
|
|
695
|
+
folder,
|
|
675
696
|
space: params.space,
|
|
676
697
|
createNewFolder: params.create_new_folder,
|
|
677
698
|
});
|
|
@@ -722,7 +743,7 @@ export function updateNote(params) {
|
|
|
722
743
|
error: 'Empty content is blocked for noteplan_update_note. Use allowEmptyContent=true to override intentionally.',
|
|
723
744
|
};
|
|
724
745
|
}
|
|
725
|
-
if (params.dryRun
|
|
746
|
+
if (isTrueBool(params.dryRun)) {
|
|
726
747
|
const token = issueConfirmationToken({
|
|
727
748
|
tool: 'noteplan_update_note',
|
|
728
749
|
target: params.filename,
|
|
@@ -788,7 +809,7 @@ export function deleteNote(params) {
|
|
|
788
809
|
error: 'Note not found',
|
|
789
810
|
};
|
|
790
811
|
}
|
|
791
|
-
if (params.dryRun
|
|
812
|
+
if (isTrueBool(params.dryRun)) {
|
|
792
813
|
const token = issueConfirmationToken({
|
|
793
814
|
tool: 'noteplan_delete_note',
|
|
794
815
|
target: target.identifier,
|
|
@@ -852,7 +873,7 @@ export function moveNote(params) {
|
|
|
852
873
|
}
|
|
853
874
|
const preview = store.previewMoveNote(target.identifier, params.destinationFolder);
|
|
854
875
|
const confirmationTarget = `${preview.fromFilename}=>${preview.toFilename}::${preview.destinationParentId ?? preview.destinationFolder}`;
|
|
855
|
-
if (params.dryRun
|
|
876
|
+
if (isTrueBool(params.dryRun)) {
|
|
856
877
|
const token = issueConfirmationToken({
|
|
857
878
|
tool: 'noteplan_move_note',
|
|
858
879
|
target: confirmationTarget,
|
|
@@ -926,7 +947,7 @@ export function restoreNote(params) {
|
|
|
926
947
|
}
|
|
927
948
|
const preview = store.previewRestoreNote(target.identifier, params.destinationFolder);
|
|
928
949
|
const confirmationTarget = `${preview.fromIdentifier}=>${preview.toIdentifier}`;
|
|
929
|
-
if (params.dryRun
|
|
950
|
+
if (isTrueBool(params.dryRun)) {
|
|
930
951
|
const token = issueConfirmationToken({
|
|
931
952
|
tool: 'noteplan_restore_note',
|
|
932
953
|
target: confirmationTarget,
|
|
@@ -990,12 +1011,16 @@ export function restoreNote(params) {
|
|
|
990
1011
|
}
|
|
991
1012
|
export function renameNoteFile(params) {
|
|
992
1013
|
try {
|
|
993
|
-
// Resolve the note
|
|
994
|
-
const
|
|
995
|
-
if (!
|
|
996
|
-
return {
|
|
1014
|
+
// Resolve the note — supports id, filename, title, or query
|
|
1015
|
+
const resolved = resolveWritableNoteReference(params);
|
|
1016
|
+
if (!resolved.note) {
|
|
1017
|
+
return {
|
|
1018
|
+
success: false,
|
|
1019
|
+
error: resolved.error || 'Note not found',
|
|
1020
|
+
candidates: resolved.candidates,
|
|
1021
|
+
};
|
|
997
1022
|
}
|
|
998
|
-
const note =
|
|
1023
|
+
const note = resolved.note;
|
|
999
1024
|
// Space note: rename title
|
|
1000
1025
|
if (note.source === 'space') {
|
|
1001
1026
|
if (!params.newTitle) {
|
|
@@ -1006,7 +1031,7 @@ export function renameNoteFile(params) {
|
|
|
1006
1031
|
}
|
|
1007
1032
|
const writeId = note.id || note.filename;
|
|
1008
1033
|
const confirmationTarget = `${note.title}=>${params.newTitle}`;
|
|
1009
|
-
if (params.dryRun
|
|
1034
|
+
if (isTrueBool(params.dryRun)) {
|
|
1010
1035
|
const token = issueConfirmationToken({
|
|
1011
1036
|
tool: 'noteplan_rename_note_file',
|
|
1012
1037
|
target: confirmationTarget,
|
|
@@ -1059,16 +1084,18 @@ export function renameNoteFile(params) {
|
|
|
1059
1084
|
};
|
|
1060
1085
|
}
|
|
1061
1086
|
// Local note: rename file
|
|
1062
|
-
|
|
1087
|
+
// Accept newTitle as an alias for newFilename — "rename the note" typically means changing the title
|
|
1088
|
+
const effectiveNewFilename = params.newFilename || params.newTitle;
|
|
1089
|
+
if (!effectiveNewFilename) {
|
|
1063
1090
|
return {
|
|
1064
1091
|
success: false,
|
|
1065
|
-
error: 'newFilename is required for local notes
|
|
1092
|
+
error: 'newFilename or newTitle is required for local notes',
|
|
1066
1093
|
};
|
|
1067
1094
|
}
|
|
1068
1095
|
const keepExtension = params.keepExtension ?? true;
|
|
1069
|
-
const preview = store.previewRenameNoteFile(note.filename,
|
|
1096
|
+
const preview = store.previewRenameNoteFile(note.filename, effectiveNewFilename, keepExtension);
|
|
1070
1097
|
const confirmationTarget = `${preview.fromFilename}=>${preview.toFilename}`;
|
|
1071
|
-
if (params.dryRun
|
|
1098
|
+
if (isTrueBool(params.dryRun)) {
|
|
1072
1099
|
const token = issueConfirmationToken({
|
|
1073
1100
|
tool: 'noteplan_rename_note_file',
|
|
1074
1101
|
target: confirmationTarget,
|
|
@@ -1103,7 +1130,26 @@ export function renameNoteFile(params) {
|
|
|
1103
1130
|
error: confirmationFailureMessage('noteplan_rename_note_file', confirmation.reason),
|
|
1104
1131
|
};
|
|
1105
1132
|
}
|
|
1106
|
-
const renamed = store.renameNoteFile(note.filename,
|
|
1133
|
+
const renamed = store.renameNoteFile(note.filename, effectiveNewFilename, keepExtension);
|
|
1134
|
+
// Also update the # Title heading in the note content if it matches the old title
|
|
1135
|
+
const newTitle = params.newTitle || params.newFilename;
|
|
1136
|
+
if (newTitle && renamed.note.content) {
|
|
1137
|
+
const lines = renamed.note.content.split('\n');
|
|
1138
|
+
const titleLineIndex = lines.findIndex((l) => /^#\s+/.test(l));
|
|
1139
|
+
if (titleLineIndex !== -1) {
|
|
1140
|
+
const oldHeadingTitle = lines[titleLineIndex].replace(/^#\s+/, '');
|
|
1141
|
+
// Update heading if it matches the old note title (or old filename without extension)
|
|
1142
|
+
const oldTitle = note.title || '';
|
|
1143
|
+
const oldFilenameBase = note.filename.replace(/^.*\//, '').replace(/\.\w+$/, '');
|
|
1144
|
+
if (oldHeadingTitle === oldTitle || oldHeadingTitle === oldFilenameBase) {
|
|
1145
|
+
lines[titleLineIndex] = `# ${newTitle}`;
|
|
1146
|
+
const writeTarget = getWritableIdentifier(renamed.note);
|
|
1147
|
+
store.updateNote(writeTarget.identifier, lines.join('\n'), {
|
|
1148
|
+
source: writeTarget.source,
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1107
1153
|
return {
|
|
1108
1154
|
success: true,
|
|
1109
1155
|
message: `Note renamed to ${renamed.toFilename}`,
|
|
@@ -1427,6 +1473,7 @@ export const appendContentSchema = z.object({
|
|
|
1427
1473
|
query: z.string().optional().describe('Resolvable note query (fuzzy note lookup before append)'),
|
|
1428
1474
|
space: z.string().optional().describe('Space name or ID scope for title/date/query resolution'),
|
|
1429
1475
|
content: z.string().describe('Content to append'),
|
|
1476
|
+
heading: z.string().optional().describe('Heading or section marker text — when provided, appends at end of that section instead of end of note'),
|
|
1430
1477
|
indentationStyle: z
|
|
1431
1478
|
.enum(['tabs', 'preserve'])
|
|
1432
1479
|
.optional()
|
|
@@ -1555,6 +1602,41 @@ export function insertContent(params) {
|
|
|
1555
1602
|
const note = resolved.note;
|
|
1556
1603
|
const indentationStyle = normalizeIndentationStyle(params.indentationStyle);
|
|
1557
1604
|
let contentToInsert = params.content;
|
|
1605
|
+
// Auto-correct position when line number is provided but position is wrong
|
|
1606
|
+
// Catches LLMs sending { position: "start", line: 5 } instead of { position: "at-line", line: 5 }
|
|
1607
|
+
if (params.line !== undefined && params.position !== 'at-line') {
|
|
1608
|
+
params.position = 'at-line';
|
|
1609
|
+
}
|
|
1610
|
+
// Auto-detect raw task/checklist markdown when type is not explicitly set
|
|
1611
|
+
// Catches LLMs sending "- [ ] Buy groceries", "* [x] Done", "* Buy groceries", "+ Item" without proper type
|
|
1612
|
+
if (!params.type && /^[\t ]*[*+\-]\s+/.test(contentToInsert)) {
|
|
1613
|
+
// Determine type from the marker character
|
|
1614
|
+
const markerMatch = contentToInsert.match(/^[\t ]*([*+\-])\s+/);
|
|
1615
|
+
const markerChar = markerMatch?.[1];
|
|
1616
|
+
if (markerChar === '+') {
|
|
1617
|
+
params.type = 'checklist';
|
|
1618
|
+
}
|
|
1619
|
+
else if (markerChar === '*') {
|
|
1620
|
+
params.type = 'task';
|
|
1621
|
+
}
|
|
1622
|
+
else if (markerChar === '-' && /^[\t ]*-\s+\[[ x\->]\]\s+/.test(contentToInsert)) {
|
|
1623
|
+
// Dash with checkbox is clearly a task (plain "- text" could be a bullet, so only match with checkbox)
|
|
1624
|
+
params.type = 'task';
|
|
1625
|
+
}
|
|
1626
|
+
// Detect status from the checkbox marker if present
|
|
1627
|
+
if (params.type) {
|
|
1628
|
+
const statusMatch = contentToInsert.match(/\[(.)\]/);
|
|
1629
|
+
if (statusMatch) {
|
|
1630
|
+
const marker = statusMatch[1];
|
|
1631
|
+
if (marker === 'x')
|
|
1632
|
+
params.taskStatus = 'done';
|
|
1633
|
+
else if (marker === '-')
|
|
1634
|
+
params.taskStatus = 'cancelled';
|
|
1635
|
+
else if (marker === '>')
|
|
1636
|
+
params.taskStatus = 'scheduled';
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1558
1640
|
if (params.type) {
|
|
1559
1641
|
contentToInsert = contentToInsert
|
|
1560
1642
|
.split('\n')
|
|
@@ -1610,6 +1692,7 @@ export function appendContent(params) {
|
|
|
1610
1692
|
const normalized = normalizeContentIndentation(params.content, indentationStyle);
|
|
1611
1693
|
const newContent = frontmatter.insertContentAtPosition(note.content, normalized.content, {
|
|
1612
1694
|
position: 'end',
|
|
1695
|
+
heading: params.heading,
|
|
1613
1696
|
});
|
|
1614
1697
|
const writeTarget = getWritableIdentifier(note);
|
|
1615
1698
|
store.updateNote(writeTarget.identifier, newContent, {
|
|
@@ -1642,11 +1725,14 @@ export function deleteLines(params) {
|
|
|
1642
1725
|
}
|
|
1643
1726
|
const note = resolved.note;
|
|
1644
1727
|
const allLines = note.content.split('\n');
|
|
1645
|
-
|
|
1646
|
-
const
|
|
1728
|
+
// Line numbers are relative to content after frontmatter
|
|
1729
|
+
const fmOffset = frontmatter.getFrontmatterLineCount(note.content);
|
|
1730
|
+
const contentLineCount = allLines.length - fmOffset;
|
|
1731
|
+
const boundedStartLine = toBoundedInt(params.startLine, 1, 1, Math.max(1, contentLineCount));
|
|
1732
|
+
const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, contentLineCount));
|
|
1647
1733
|
const lineCountToDelete = boundedEndLine - boundedStartLine + 1;
|
|
1648
|
-
const previewStartIndex = boundedStartLine - 1;
|
|
1649
|
-
const previewEndIndexExclusive = boundedEndLine;
|
|
1734
|
+
const previewStartIndex = fmOffset + boundedStartLine - 1;
|
|
1735
|
+
const previewEndIndexExclusive = fmOffset + boundedEndLine;
|
|
1650
1736
|
const deletedLinesPreview = allLines
|
|
1651
1737
|
.slice(previewStartIndex, previewEndIndexExclusive)
|
|
1652
1738
|
.slice(0, 20)
|
|
@@ -1660,7 +1746,7 @@ export function deleteLines(params) {
|
|
|
1660
1746
|
? buildAttachmentWarningMessage(removedAttachmentReferences.length)
|
|
1661
1747
|
: undefined;
|
|
1662
1748
|
const confirmTarget = `${note.filename}:${boundedStartLine}-${boundedEndLine}`;
|
|
1663
|
-
if (params.dryRun
|
|
1749
|
+
if (isTrueBool(params.dryRun)) {
|
|
1664
1750
|
const token = issueConfirmationToken({
|
|
1665
1751
|
tool: 'noteplan_delete_lines',
|
|
1666
1752
|
target: confirmTarget,
|
|
@@ -1724,11 +1810,13 @@ export function editLine(params) {
|
|
|
1724
1810
|
const note = resolved.note;
|
|
1725
1811
|
const lines = note.content.split('\n');
|
|
1726
1812
|
const originalLineCount = lines.length;
|
|
1727
|
-
|
|
1728
|
-
|
|
1813
|
+
// Offset past frontmatter so line 1 = first content line
|
|
1814
|
+
const fmOffset = frontmatter.getFrontmatterLineCount(note.content);
|
|
1815
|
+
const lineIndex = fmOffset + Number(params.line) - 1; // Convert to 0-indexed, skip FM
|
|
1816
|
+
if (lineIndex < fmOffset || lineIndex >= lines.length) {
|
|
1729
1817
|
return {
|
|
1730
1818
|
success: false,
|
|
1731
|
-
error: `Line ${params.line} does not exist (note has ${lines.length} lines)`,
|
|
1819
|
+
error: `Line ${params.line} does not exist (note has ${lines.length - fmOffset} content lines)`,
|
|
1732
1820
|
};
|
|
1733
1821
|
}
|
|
1734
1822
|
const originalLine = lines[lineIndex];
|
|
@@ -1781,11 +1869,14 @@ export function replaceLines(params) {
|
|
|
1781
1869
|
const note = resolved.note;
|
|
1782
1870
|
const allLines = note.content.split('\n');
|
|
1783
1871
|
const originalLineCount = allLines.length;
|
|
1784
|
-
|
|
1785
|
-
const
|
|
1786
|
-
const
|
|
1872
|
+
// Line numbers are relative to content after frontmatter
|
|
1873
|
+
const fmOffset = frontmatter.getFrontmatterLineCount(note.content);
|
|
1874
|
+
const contentLineCount = originalLineCount - fmOffset;
|
|
1875
|
+
const boundedStartLine = toBoundedInt(params.startLine, 1, 1, Math.max(1, contentLineCount));
|
|
1876
|
+
const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, contentLineCount));
|
|
1877
|
+
const startIndex = fmOffset + boundedStartLine - 1;
|
|
1787
1878
|
const lineCountToReplace = boundedEndLine - boundedStartLine + 1;
|
|
1788
|
-
const replacedText = allLines.slice(startIndex, boundedEndLine).join('\n');
|
|
1879
|
+
const replacedText = allLines.slice(startIndex, fmOffset + boundedEndLine).join('\n');
|
|
1789
1880
|
const indentationStyle = normalizeIndentationStyle(params.indentationStyle);
|
|
1790
1881
|
const normalized = normalizeContentIndentation(params.content, indentationStyle);
|
|
1791
1882
|
if (params.allowEmptyContent !== true && normalized.content.trim().length === 0) {
|
|
@@ -1806,7 +1897,7 @@ export function replaceLines(params) {
|
|
|
1806
1897
|
warnings.push(`Line numbers shifted by ${lineDelta > 0 ? '+' : ''}${lineDelta} after this replacement. Re-read line numbers before the next mutation.`);
|
|
1807
1898
|
}
|
|
1808
1899
|
const target = `${note.filename}:${boundedStartLine}-${boundedEndLine}:${replacementLines.length}:${normalized.content.length}`;
|
|
1809
|
-
if (params.dryRun
|
|
1900
|
+
if (isTrueBool(params.dryRun)) {
|
|
1810
1901
|
const token = issueConfirmationToken({
|
|
1811
1902
|
tool: 'noteplan_replace_lines',
|
|
1812
1903
|
target,
|