@noteplanco/noteplan-mcp 1.1.21 → 1.1.24
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/README.md +7 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/noteplan/attachments-paths.d.ts +13 -0
- package/dist/noteplan/attachments-paths.d.ts.map +1 -0
- package/dist/noteplan/attachments-paths.js +27 -0
- package/dist/noteplan/attachments-paths.js.map +1 -0
- package/dist/noteplan/embeddings.js +1 -1
- package/dist/noteplan/embeddings.js.map +1 -1
- package/dist/noteplan/file-reader.d.ts +37 -46
- package/dist/noteplan/file-reader.d.ts.map +1 -1
- package/dist/noteplan/file-reader.js +200 -202
- package/dist/noteplan/file-reader.js.map +1 -1
- package/dist/noteplan/file-reader.test.d.ts +2 -0
- package/dist/noteplan/file-reader.test.d.ts.map +1 -0
- package/dist/noteplan/file-reader.test.js +67 -0
- package/dist/noteplan/file-reader.test.js.map +1 -0
- package/dist/noteplan/file-writer.d.ts +35 -31
- package/dist/noteplan/file-writer.d.ts.map +1 -1
- package/dist/noteplan/file-writer.js +280 -164
- package/dist/noteplan/file-writer.js.map +1 -1
- package/dist/noteplan/file-writer.test.js +704 -191
- package/dist/noteplan/file-writer.test.js.map +1 -1
- package/dist/noteplan/filter-store.d.ts +5 -5
- package/dist/noteplan/filter-store.d.ts.map +1 -1
- package/dist/noteplan/filter-store.js +94 -79
- package/dist/noteplan/filter-store.js.map +1 -1
- package/dist/noteplan/frontmatter-parser.d.ts.map +1 -1
- package/dist/noteplan/frontmatter-parser.js +5 -4
- package/dist/noteplan/frontmatter-parser.js.map +1 -1
- package/dist/noteplan/frontmatter-parser.test.js +44 -0
- package/dist/noteplan/frontmatter-parser.test.js.map +1 -1
- package/dist/noteplan/markdown-parser.js +1 -1
- package/dist/noteplan/markdown-parser.js.map +1 -1
- package/dist/noteplan/markdown-parser.test.js +194 -0
- package/dist/noteplan/markdown-parser.test.js.map +1 -1
- package/dist/noteplan/preferences.d.ts +1 -0
- package/dist/noteplan/preferences.d.ts.map +1 -1
- package/dist/noteplan/preferences.js +1 -0
- package/dist/noteplan/preferences.js.map +1 -1
- package/dist/noteplan/ripgrep-search.d.ts +25 -2
- package/dist/noteplan/ripgrep-search.d.ts.map +1 -1
- package/dist/noteplan/ripgrep-search.js +75 -2
- package/dist/noteplan/ripgrep-search.js.map +1 -1
- package/dist/noteplan/space-row-utils.d.ts +20 -0
- package/dist/noteplan/space-row-utils.d.ts.map +1 -0
- package/dist/noteplan/space-row-utils.js +78 -0
- package/dist/noteplan/space-row-utils.js.map +1 -0
- package/dist/noteplan/space-row-utils.test.d.ts +2 -0
- package/dist/noteplan/space-row-utils.test.d.ts.map +1 -0
- package/dist/noteplan/space-row-utils.test.js +123 -0
- package/dist/noteplan/space-row-utils.test.js.map +1 -0
- package/dist/noteplan/sqlite-reader.d.ts +12 -27
- package/dist/noteplan/sqlite-reader.d.ts.map +1 -1
- package/dist/noteplan/sqlite-reader.js +325 -223
- package/dist/noteplan/sqlite-reader.js.map +1 -1
- package/dist/noteplan/sqlite-writer.d.ts +1 -1
- package/dist/noteplan/sqlite-writer.d.ts.map +1 -1
- package/dist/noteplan/sqlite-writer.js +2 -2
- package/dist/noteplan/sqlite-writer.js.map +1 -1
- package/dist/noteplan/unified-store.d.ts +41 -30
- package/dist/noteplan/unified-store.d.ts.map +1 -1
- package/dist/noteplan/unified-store.js +307 -161
- package/dist/noteplan/unified-store.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +143 -62
- package/dist/server.js.map +1 -1
- package/dist/tools/attachments.d.ts +9 -9
- package/dist/tools/attachments.d.ts.map +1 -1
- package/dist/tools/attachments.js +74 -83
- package/dist/tools/attachments.js.map +1 -1
- package/dist/tools/attachments.test.js +170 -129
- package/dist/tools/attachments.test.js.map +1 -1
- package/dist/tools/calendar.d.ts +36 -13
- package/dist/tools/calendar.d.ts.map +1 -1
- package/dist/tools/calendar.js +44 -17
- package/dist/tools/calendar.js.map +1 -1
- package/dist/tools/embeddings.d.ts +6 -6
- package/dist/tools/embeddings.d.ts.map +1 -1
- package/dist/tools/embeddings.js +6 -6
- package/dist/tools/embeddings.js.map +1 -1
- package/dist/tools/events.d.ts +7 -3
- package/dist/tools/events.d.ts.map +1 -1
- package/dist/tools/events.js +51 -16
- package/dist/tools/events.js.map +1 -1
- package/dist/tools/filters.d.ts +28 -33
- package/dist/tools/filters.d.ts.map +1 -1
- package/dist/tools/filters.js +42 -105
- package/dist/tools/filters.js.map +1 -1
- package/dist/tools/notes.d.ts +80 -218
- package/dist/tools/notes.d.ts.map +1 -1
- package/dist/tools/notes.js +194 -180
- package/dist/tools/notes.js.map +1 -1
- package/dist/tools/notes.test.js +501 -21
- package/dist/tools/notes.test.js.map +1 -1
- package/dist/tools/search.d.ts +4 -3
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +9 -5
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/search.test.d.ts +2 -0
- package/dist/tools/search.test.d.ts.map +1 -0
- package/dist/tools/search.test.js +37 -0
- package/dist/tools/search.test.js.map +1 -0
- package/dist/tools/spaces.d.ts +20 -20
- package/dist/tools/spaces.d.ts.map +1 -1
- package/dist/tools/spaces.js +28 -28
- package/dist/tools/spaces.js.map +1 -1
- package/dist/tools/tasks.d.ts +22 -22
- package/dist/tools/tasks.d.ts.map +1 -1
- package/dist/tools/tasks.js +22 -22
- package/dist/tools/tasks.js.map +1 -1
- package/dist/tools/templates.d.ts +7 -7
- package/dist/tools/templates.d.ts.map +1 -1
- package/dist/tools/templates.js +4 -4
- package/dist/tools/templates.js.map +1 -1
- package/dist/tools/themes.js +1 -1
- package/dist/tools/themes.js.map +1 -1
- package/dist/transport/bridge-availability.d.ts +5 -0
- package/dist/transport/bridge-availability.d.ts.map +1 -0
- package/dist/transport/bridge-availability.js +92 -0
- package/dist/transport/bridge-availability.js.map +1 -0
- package/dist/transport/bridge-cascade.d.ts +18 -0
- package/dist/transport/bridge-cascade.d.ts.map +1 -0
- package/dist/transport/bridge-cascade.js +78 -0
- package/dist/transport/bridge-cascade.js.map +1 -0
- package/dist/transport/bridge-cascade.test.d.ts +2 -0
- package/dist/transport/bridge-cascade.test.d.ts.map +1 -0
- package/dist/transport/bridge-cascade.test.js +160 -0
- package/dist/transport/bridge-cascade.test.js.map +1 -0
- package/dist/transport/bridge-client.d.ts +197 -0
- package/dist/transport/bridge-client.d.ts.map +1 -0
- package/dist/transport/bridge-client.js +288 -0
- package/dist/transport/bridge-client.js.map +1 -0
- package/dist/transport/bridge-client.test.d.ts +2 -0
- package/dist/transport/bridge-client.test.d.ts.map +1 -0
- package/dist/transport/bridge-client.test.js +384 -0
- package/dist/transport/bridge-client.test.js.map +1 -0
- package/dist/transport/bridge-context.d.ts +10 -0
- package/dist/transport/bridge-context.d.ts.map +1 -0
- package/dist/transport/bridge-context.js +18 -0
- package/dist/transport/bridge-context.js.map +1 -0
- package/dist/transport/bridge-fs.d.ts +25 -0
- package/dist/transport/bridge-fs.d.ts.map +1 -0
- package/dist/transport/bridge-fs.js +129 -0
- package/dist/transport/bridge-fs.js.map +1 -0
- package/dist/utils/date-utils.d.ts +24 -0
- package/dist/utils/date-utils.d.ts.map +1 -1
- package/dist/utils/date-utils.js +55 -0
- package/dist/utils/date-utils.js.map +1 -1
- package/dist/utils/date-utils.test.d.ts +2 -0
- package/dist/utils/date-utils.test.d.ts.map +1 -0
- package/dist/utils/date-utils.test.js +109 -0
- package/dist/utils/date-utils.test.js.map +1 -0
- package/dist/utils/folder-access.d.ts +23 -0
- package/dist/utils/folder-access.d.ts.map +1 -0
- package/dist/utils/folder-access.js +131 -0
- package/dist/utils/folder-access.js.map +1 -0
- package/dist/utils/folder-access.test.d.ts +2 -0
- package/dist/utils/folder-access.test.d.ts.map +1 -0
- package/dist/utils/folder-access.test.js +182 -0
- package/dist/utils/folder-access.test.js.map +1 -0
- package/dist/utils/folder-matcher.d.ts.map +1 -1
- package/dist/utils/folder-matcher.js +16 -0
- package/dist/utils/folder-matcher.js.map +1 -1
- package/dist/utils/folder-matcher.test.js +42 -0
- package/dist/utils/folder-matcher.test.js.map +1 -1
- package/dist/utils/server-config.d.ts +10 -2
- package/dist/utils/server-config.d.ts.map +1 -1
- package/dist/utils/server-config.js +16 -2
- package/dist/utils/server-config.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -1
- package/dist/utils/version.js +5 -1
- package/dist/utils/version.js.map +1 -1
- package/package.json +4 -3
- package/scripts/calendar-helper +0 -0
- package/scripts/reminders-helper +0 -0
package/dist/tools/notes.js
CHANGED
|
@@ -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,28 +516,44 @@ export function getNote(params) {
|
|
|
509
516
|
result.limit = lineWindow.limit;
|
|
510
517
|
result.hasMore = lineWindow.hasMore;
|
|
511
518
|
result.nextCursor = lineWindow.nextCursor;
|
|
512
|
-
|
|
513
|
-
|
|
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
|
});
|
|
525
537
|
const allowedTypes = input.types ? new Set(input.types) : null;
|
|
526
538
|
const query = typeof input.query === 'string' ? input.query.trim().toLowerCase() : undefined;
|
|
539
|
+
// Pre-compute query tokens outside the per-note filter loop
|
|
540
|
+
const queryAlternatives = query
|
|
541
|
+
? query.split('|').map((alt) => {
|
|
542
|
+
const words = alt.trim().replace(/_/g, ' ').split(/\s+/).filter(Boolean);
|
|
543
|
+
return words.length > 0 ? words : null;
|
|
544
|
+
}).filter((words) => words !== null)
|
|
545
|
+
: null;
|
|
527
546
|
const filtered = notes.filter((note) => {
|
|
528
547
|
if (allowedTypes && !allowedTypes.has(note.type))
|
|
529
548
|
return false;
|
|
530
|
-
if (!
|
|
549
|
+
if (!queryAlternatives)
|
|
531
550
|
return true;
|
|
532
|
-
|
|
533
|
-
|
|
551
|
+
// Normalize underscores to spaces so "knuth_reviewer" matches "knuth reviewer"
|
|
552
|
+
const haystack = `${note.title} ${note.filename} ${note.folder || ''}`
|
|
553
|
+
.toLowerCase()
|
|
554
|
+
.replace(/_/g, ' ');
|
|
555
|
+
// OR across alternatives; AND within each alternative's words
|
|
556
|
+
return queryAlternatives.some((words) => words.every((word) => haystack.includes(word)));
|
|
534
557
|
});
|
|
535
558
|
const offset = toBoundedInt(input.cursor ?? input.offset, 0, 0, Number.MAX_SAFE_INTEGER);
|
|
536
559
|
const limit = toBoundedInt(input.limit, 50, 1, 500);
|
|
@@ -590,7 +613,7 @@ function noteMatchScore(note, query, queryDateToken) {
|
|
|
590
613
|
return 0.76;
|
|
591
614
|
return 0;
|
|
592
615
|
}
|
|
593
|
-
export function resolveNote(params) {
|
|
616
|
+
export async function resolveNote(params) {
|
|
594
617
|
const query = typeof params?.query === 'string' ? params.query.trim() : '';
|
|
595
618
|
if (!query) {
|
|
596
619
|
return {
|
|
@@ -606,7 +629,7 @@ export function resolveNote(params) {
|
|
|
606
629
|
const includeStageTimings = isDebugTimingsEnabled(params.debugTimings);
|
|
607
630
|
const stageTimings = {};
|
|
608
631
|
const listStart = Date.now();
|
|
609
|
-
const notes = store.listNotes({
|
|
632
|
+
const notes = await store.listNotes({
|
|
610
633
|
folder: params.folder,
|
|
611
634
|
space: params.space,
|
|
612
635
|
});
|
|
@@ -703,17 +726,59 @@ export function resolveNote(params) {
|
|
|
703
726
|
}
|
|
704
727
|
return result;
|
|
705
728
|
}
|
|
706
|
-
export function createNote(params) {
|
|
729
|
+
export async function createNote(params) {
|
|
707
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
|
+
}
|
|
708
772
|
const isTemplate = params.noteType === 'template';
|
|
709
773
|
const folder = isTemplate && !params.folder ? '@Templates' : params.folder;
|
|
710
774
|
const content = isTemplate
|
|
711
|
-
? ensureTemplateFrontmatter(params.title, params.content, params.templateTypes)
|
|
775
|
+
? ensureTemplateFrontmatter(params.title ?? '', params.content, params.templateTypes)
|
|
712
776
|
: params.content;
|
|
713
|
-
const result = store.createNote(params.title, content, {
|
|
777
|
+
const result = await store.createNote(params.title ?? '', content, {
|
|
714
778
|
folder,
|
|
715
779
|
space: params.space,
|
|
716
780
|
createNewFolder: params.create_new_folder,
|
|
781
|
+
filename: params.filename,
|
|
717
782
|
});
|
|
718
783
|
return {
|
|
719
784
|
success: true,
|
|
@@ -742,7 +807,7 @@ export function createNote(params) {
|
|
|
742
807
|
};
|
|
743
808
|
}
|
|
744
809
|
}
|
|
745
|
-
export function updateNote(params) {
|
|
810
|
+
export async function updateNote(params) {
|
|
746
811
|
try {
|
|
747
812
|
if (params.fullReplace !== true) {
|
|
748
813
|
return {
|
|
@@ -750,7 +815,7 @@ export function updateNote(params) {
|
|
|
750
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.',
|
|
751
816
|
};
|
|
752
817
|
}
|
|
753
|
-
const noteRef = resolveWritableNoteReference(params);
|
|
818
|
+
const noteRef = await resolveWritableNoteReference(params);
|
|
754
819
|
if (!noteRef.note) {
|
|
755
820
|
return {
|
|
756
821
|
success: false,
|
|
@@ -765,45 +830,8 @@ export function updateNote(params) {
|
|
|
765
830
|
error: 'Empty content is blocked for noteplan_update_note. Use allowEmptyContent=true to override intentionally.',
|
|
766
831
|
};
|
|
767
832
|
}
|
|
768
|
-
// Use the resolved filename as the confirmation token target for consistency
|
|
769
|
-
const confirmationTarget = existingNote.filename;
|
|
770
|
-
if (isTrueBool(params.dryRun)) {
|
|
771
|
-
const token = issueConfirmationToken({
|
|
772
|
-
tool: 'noteplan_update_note',
|
|
773
|
-
target: confirmationTarget,
|
|
774
|
-
action: 'full_replace',
|
|
775
|
-
});
|
|
776
|
-
return {
|
|
777
|
-
success: true,
|
|
778
|
-
dryRun: true,
|
|
779
|
-
message: `Dry run: note ${existingNote.filename} would be fully replaced`,
|
|
780
|
-
note: {
|
|
781
|
-
id: existingNote.id,
|
|
782
|
-
title: existingNote.title,
|
|
783
|
-
filename: existingNote.filename,
|
|
784
|
-
type: existingNote.type,
|
|
785
|
-
source: existingNote.source,
|
|
786
|
-
folder: existingNote.folder,
|
|
787
|
-
spaceId: existingNote.spaceId,
|
|
788
|
-
},
|
|
789
|
-
currentContentLength: existingNote.content.length,
|
|
790
|
-
newContentLength: params.content.length,
|
|
791
|
-
...token,
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
const confirmation = validateAndConsumeConfirmationToken(params.confirmationToken, {
|
|
795
|
-
tool: 'noteplan_update_note',
|
|
796
|
-
target: confirmationTarget,
|
|
797
|
-
action: 'full_replace',
|
|
798
|
-
});
|
|
799
|
-
if (!confirmation.ok) {
|
|
800
|
-
return {
|
|
801
|
-
success: false,
|
|
802
|
-
error: confirmationFailureMessage('noteplan_update_note', confirmation.reason),
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
833
|
const writeTarget = getWritableIdentifier(existingNote);
|
|
806
|
-
const note = store.updateNote(writeTarget.identifier, params.content, {
|
|
834
|
+
const note = await store.updateNote(writeTarget.identifier, params.content, {
|
|
807
835
|
source: writeTarget.source,
|
|
808
836
|
});
|
|
809
837
|
return {
|
|
@@ -823,9 +851,9 @@ export function updateNote(params) {
|
|
|
823
851
|
};
|
|
824
852
|
}
|
|
825
853
|
}
|
|
826
|
-
export function deleteNote(params) {
|
|
854
|
+
export async function deleteNote(params) {
|
|
827
855
|
try {
|
|
828
|
-
const target = resolveNoteTarget(params.id, params.filename, params.space);
|
|
856
|
+
const target = await resolveNoteTarget(params.id, params.filename, params.space);
|
|
829
857
|
const note = target.note;
|
|
830
858
|
if (!note) {
|
|
831
859
|
return {
|
|
@@ -866,7 +894,7 @@ export function deleteNote(params) {
|
|
|
866
894
|
error: confirmationFailureMessage('noteplan_delete_note', confirmation.reason),
|
|
867
895
|
};
|
|
868
896
|
}
|
|
869
|
-
const deleted = store.deleteNote(target.identifier);
|
|
897
|
+
const deleted = await store.deleteNote(target.identifier);
|
|
870
898
|
return {
|
|
871
899
|
success: true,
|
|
872
900
|
message: deleted.source === 'space'
|
|
@@ -886,9 +914,9 @@ export function deleteNote(params) {
|
|
|
886
914
|
};
|
|
887
915
|
}
|
|
888
916
|
}
|
|
889
|
-
export function moveNote(params) {
|
|
917
|
+
export async function moveNote(params) {
|
|
890
918
|
try {
|
|
891
|
-
const noteRef = resolveWritableNoteReference(params);
|
|
919
|
+
const noteRef = await resolveWritableNoteReference(params);
|
|
892
920
|
if (!noteRef.note) {
|
|
893
921
|
return {
|
|
894
922
|
success: false,
|
|
@@ -897,7 +925,7 @@ export function moveNote(params) {
|
|
|
897
925
|
};
|
|
898
926
|
}
|
|
899
927
|
const writable = getWritableIdentifier(noteRef.note);
|
|
900
|
-
const preview = store.previewMoveNote(writable.identifier, params.destinationFolder);
|
|
928
|
+
const preview = await store.previewMoveNote(writable.identifier, params.destinationFolder);
|
|
901
929
|
const confirmationTarget = `${preview.fromFilename}=>${preview.toFilename}::${preview.destinationParentId ?? preview.destinationFolder}`;
|
|
902
930
|
if (isTrueBool(params.dryRun)) {
|
|
903
931
|
const token = issueConfirmationToken({
|
|
@@ -935,7 +963,7 @@ export function moveNote(params) {
|
|
|
935
963
|
error: confirmationFailureMessage('noteplan_move_note', confirmation.reason),
|
|
936
964
|
};
|
|
937
965
|
}
|
|
938
|
-
const moved = store.moveNote(writable.identifier, params.destinationFolder);
|
|
966
|
+
const moved = await store.moveNote(writable.identifier, params.destinationFolder);
|
|
939
967
|
return {
|
|
940
968
|
success: true,
|
|
941
969
|
message: moved.note.source === 'space'
|
|
@@ -962,16 +990,16 @@ export function moveNote(params) {
|
|
|
962
990
|
};
|
|
963
991
|
}
|
|
964
992
|
}
|
|
965
|
-
export function restoreNote(params) {
|
|
993
|
+
export async function restoreNote(params) {
|
|
966
994
|
try {
|
|
967
|
-
const target = resolveNoteTarget(params.id, params.filename, params.space);
|
|
995
|
+
const target = await resolveNoteTarget(params.id, params.filename, params.space);
|
|
968
996
|
if (!target.note) {
|
|
969
997
|
return {
|
|
970
998
|
success: false,
|
|
971
999
|
error: 'Note not found',
|
|
972
1000
|
};
|
|
973
1001
|
}
|
|
974
|
-
const preview = store.previewRestoreNote(target.identifier, params.destinationFolder);
|
|
1002
|
+
const preview = await store.previewRestoreNote(target.identifier, params.destinationFolder);
|
|
975
1003
|
const confirmationTarget = `${preview.fromIdentifier}=>${preview.toIdentifier}`;
|
|
976
1004
|
if (isTrueBool(params.dryRun)) {
|
|
977
1005
|
const token = issueConfirmationToken({
|
|
@@ -1009,7 +1037,7 @@ export function restoreNote(params) {
|
|
|
1009
1037
|
error: confirmationFailureMessage('noteplan_restore_note', confirmation.reason),
|
|
1010
1038
|
};
|
|
1011
1039
|
}
|
|
1012
|
-
const restored = store.restoreNote(target.identifier, params.destinationFolder);
|
|
1040
|
+
const restored = await store.restoreNote(target.identifier, params.destinationFolder);
|
|
1013
1041
|
return {
|
|
1014
1042
|
success: true,
|
|
1015
1043
|
message: restored.source === 'space'
|
|
@@ -1035,10 +1063,10 @@ export function restoreNote(params) {
|
|
|
1035
1063
|
};
|
|
1036
1064
|
}
|
|
1037
1065
|
}
|
|
1038
|
-
export function renameNoteFile(params) {
|
|
1066
|
+
export async function renameNoteFile(params) {
|
|
1039
1067
|
try {
|
|
1040
1068
|
// Resolve the note — supports id, filename, title, or query
|
|
1041
|
-
const resolved = resolveWritableNoteReference(params);
|
|
1069
|
+
const resolved = await resolveWritableNoteReference(params);
|
|
1042
1070
|
if (!resolved.note) {
|
|
1043
1071
|
return {
|
|
1044
1072
|
success: false,
|
|
@@ -1092,7 +1120,7 @@ export function renameNoteFile(params) {
|
|
|
1092
1120
|
error: confirmationFailureMessage('noteplan_rename_note_file', confirmation.reason),
|
|
1093
1121
|
};
|
|
1094
1122
|
}
|
|
1095
|
-
const renamed = store.renameSpaceNote(writeId, params.newTitle);
|
|
1123
|
+
const renamed = await store.renameSpaceNote(writeId, params.newTitle);
|
|
1096
1124
|
// Also update the # Title heading in the note content if it matches the old title
|
|
1097
1125
|
if (renamed.note.content) {
|
|
1098
1126
|
const lines = renamed.note.content.split('\n');
|
|
@@ -1102,7 +1130,7 @@ export function renameNoteFile(params) {
|
|
|
1102
1130
|
if (oldHeadingTitle === renamed.fromTitle) {
|
|
1103
1131
|
lines[titleLineIndex] = `# ${params.newTitle}`;
|
|
1104
1132
|
const renamedWriteTarget = getWritableIdentifier(renamed.note);
|
|
1105
|
-
store.updateNote(renamedWriteTarget.identifier, lines.join('\n'), {
|
|
1133
|
+
await store.updateNote(renamedWriteTarget.identifier, lines.join('\n'), {
|
|
1106
1134
|
source: renamedWriteTarget.source,
|
|
1107
1135
|
});
|
|
1108
1136
|
}
|
|
@@ -1134,7 +1162,7 @@ export function renameNoteFile(params) {
|
|
|
1134
1162
|
};
|
|
1135
1163
|
}
|
|
1136
1164
|
const keepExtension = params.keepExtension ?? true;
|
|
1137
|
-
const preview = store.previewRenameNoteFile(note.filename, effectiveNewFilename, keepExtension);
|
|
1165
|
+
const preview = await store.previewRenameNoteFile(note.filename, effectiveNewFilename, keepExtension);
|
|
1138
1166
|
const confirmationTarget = `${preview.fromFilename}=>${preview.toFilename}`;
|
|
1139
1167
|
if (isTrueBool(params.dryRun)) {
|
|
1140
1168
|
const token = issueConfirmationToken({
|
|
@@ -1171,27 +1199,51 @@ export function renameNoteFile(params) {
|
|
|
1171
1199
|
error: confirmationFailureMessage('noteplan_rename_note_file', confirmation.reason),
|
|
1172
1200
|
};
|
|
1173
1201
|
}
|
|
1174
|
-
const renamed = store.renameNoteFile(note.filename, effectiveNewFilename, keepExtension);
|
|
1175
|
-
// 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.
|
|
1176
1206
|
const newTitle = params.newTitle || params.newFilename;
|
|
1207
|
+
let headingRewritten = false;
|
|
1208
|
+
let oldHeadingTitle = null;
|
|
1177
1209
|
if (newTitle && renamed.note.content) {
|
|
1178
1210
|
const lines = renamed.note.content.split('\n');
|
|
1179
1211
|
const titleLineIndex = lines.findIndex((l) => /^#\s+/.test(l));
|
|
1180
1212
|
if (titleLineIndex !== -1) {
|
|
1181
|
-
const
|
|
1182
|
-
// Update heading if it matches the old note title (or old filename without extension)
|
|
1213
|
+
const currentHeading = lines[titleLineIndex].replace(/^#\s+/, '');
|
|
1183
1214
|
const oldTitle = note.title || '';
|
|
1184
1215
|
const oldFilenameBase = note.filename.replace(/^.*\//, '').replace(/\.\w+$/, '');
|
|
1185
|
-
if (
|
|
1216
|
+
if (currentHeading === oldTitle || currentHeading === oldFilenameBase) {
|
|
1186
1217
|
lines[titleLineIndex] = `# ${newTitle}`;
|
|
1187
1218
|
const writeTarget = getWritableIdentifier(renamed.note);
|
|
1188
|
-
store.updateNote(writeTarget.identifier, lines.join('\n'), {
|
|
1219
|
+
await store.updateNote(writeTarget.identifier, lines.join('\n'), {
|
|
1189
1220
|
source: writeTarget.source,
|
|
1190
1221
|
});
|
|
1222
|
+
headingRewritten = true;
|
|
1223
|
+
oldHeadingTitle = currentHeading;
|
|
1191
1224
|
}
|
|
1192
1225
|
}
|
|
1193
1226
|
}
|
|
1194
|
-
|
|
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 = {
|
|
1195
1247
|
success: true,
|
|
1196
1248
|
message: `Note renamed to ${renamed.toFilename}`,
|
|
1197
1249
|
fromFilename: renamed.fromFilename,
|
|
@@ -1205,6 +1257,13 @@ export function renameNoteFile(params) {
|
|
|
1205
1257
|
folder: renamed.note.folder,
|
|
1206
1258
|
},
|
|
1207
1259
|
};
|
|
1260
|
+
if (wikilinksUpdatedCount !== undefined) {
|
|
1261
|
+
result.wikilinksUpdatedCount = wikilinksUpdatedCount;
|
|
1262
|
+
}
|
|
1263
|
+
if (wikilinkPropagationWarning) {
|
|
1264
|
+
result.warnings = [wikilinkPropagationWarning];
|
|
1265
|
+
}
|
|
1266
|
+
return result;
|
|
1208
1267
|
}
|
|
1209
1268
|
catch (error) {
|
|
1210
1269
|
return {
|
|
@@ -1304,8 +1363,8 @@ function annotateFromMeta(lineObj, meta) {
|
|
|
1304
1363
|
...(meta.scheduledDate !== undefined && { scheduledDate: meta.scheduledDate }),
|
|
1305
1364
|
};
|
|
1306
1365
|
}
|
|
1307
|
-
export function getParagraphs(params) {
|
|
1308
|
-
const noteRef = resolveWritableNoteReference(params);
|
|
1366
|
+
export async function getParagraphs(params) {
|
|
1367
|
+
const noteRef = await resolveWritableNoteReference(params);
|
|
1309
1368
|
if (!noteRef.note) {
|
|
1310
1369
|
return {
|
|
1311
1370
|
success: false,
|
|
@@ -1401,7 +1460,7 @@ export function getParagraphs(params) {
|
|
|
1401
1460
|
}
|
|
1402
1461
|
return result;
|
|
1403
1462
|
}
|
|
1404
|
-
export function searchParagraphs(params) {
|
|
1463
|
+
export async function searchParagraphs(params) {
|
|
1405
1464
|
const query = typeof params?.query === 'string' ? params.query.trim() : '';
|
|
1406
1465
|
if (!query) {
|
|
1407
1466
|
return {
|
|
@@ -1415,7 +1474,7 @@ export function searchParagraphs(params) {
|
|
|
1415
1474
|
error: 'Provide one note reference: id, title, filename, or date',
|
|
1416
1475
|
};
|
|
1417
1476
|
}
|
|
1418
|
-
const note = store.getNote({
|
|
1477
|
+
const note = await store.getNote({
|
|
1419
1478
|
id: params.id,
|
|
1420
1479
|
title: params.title,
|
|
1421
1480
|
filename: params.filename,
|
|
@@ -1683,30 +1742,22 @@ export const replaceLinesSchema = z.object({
|
|
|
1683
1742
|
.optional()
|
|
1684
1743
|
.default('tabs')
|
|
1685
1744
|
.describe('Indentation normalization for replacement list/task lines. Default: tabs'),
|
|
1686
|
-
dryRun: z
|
|
1687
|
-
.boolean()
|
|
1688
|
-
.optional()
|
|
1689
|
-
.describe('Preview line replacement and get confirmationToken without modifying the note'),
|
|
1690
|
-
confirmationToken: z
|
|
1691
|
-
.string()
|
|
1692
|
-
.optional()
|
|
1693
|
-
.describe('Confirmation token issued by dryRun for replace execution'),
|
|
1694
1745
|
allowEmptyContent: z
|
|
1695
1746
|
.boolean()
|
|
1696
1747
|
.optional()
|
|
1697
1748
|
.describe('Allow replacing selected lines with empty content (default: false). Prefer delete_lines for pure deletion.'),
|
|
1698
1749
|
});
|
|
1699
1750
|
// Granular note operation implementations
|
|
1700
|
-
export function setProperty(params) {
|
|
1751
|
+
export async function setProperty(params) {
|
|
1701
1752
|
try {
|
|
1702
|
-
const noteRef = resolveWritableNoteReference(params);
|
|
1753
|
+
const noteRef = await resolveWritableNoteReference(params);
|
|
1703
1754
|
if (!noteRef.note) {
|
|
1704
1755
|
return { success: false, error: noteRef.error || 'Note not found', candidates: noteRef.candidates };
|
|
1705
1756
|
}
|
|
1706
1757
|
const note = noteRef.note;
|
|
1707
1758
|
const newContent = frontmatter.setFrontmatterProperty(note.content, params.key, params.value);
|
|
1708
1759
|
const writable = getWritableIdentifier(note);
|
|
1709
|
-
store.updateNote(writable.identifier, newContent, { source: writable.source });
|
|
1760
|
+
await store.updateNote(writable.identifier, newContent, { source: writable.source });
|
|
1710
1761
|
return {
|
|
1711
1762
|
success: true,
|
|
1712
1763
|
message: `Property "${params.key}" set to "${params.value}"`,
|
|
@@ -1719,16 +1770,16 @@ export function setProperty(params) {
|
|
|
1719
1770
|
};
|
|
1720
1771
|
}
|
|
1721
1772
|
}
|
|
1722
|
-
export function removeProperty(params) {
|
|
1773
|
+
export async function removeProperty(params) {
|
|
1723
1774
|
try {
|
|
1724
|
-
const noteRef = resolveWritableNoteReference(params);
|
|
1775
|
+
const noteRef = await resolveWritableNoteReference(params);
|
|
1725
1776
|
if (!noteRef.note) {
|
|
1726
1777
|
return { success: false, error: noteRef.error || 'Note not found', candidates: noteRef.candidates };
|
|
1727
1778
|
}
|
|
1728
1779
|
const note = noteRef.note;
|
|
1729
1780
|
const newContent = frontmatter.removeFrontmatterProperty(note.content, params.key);
|
|
1730
1781
|
const writable = getWritableIdentifier(note);
|
|
1731
|
-
store.updateNote(writable.identifier, newContent, { source: writable.source });
|
|
1782
|
+
await store.updateNote(writable.identifier, newContent, { source: writable.source });
|
|
1732
1783
|
return {
|
|
1733
1784
|
success: true,
|
|
1734
1785
|
message: `Property "${params.key}" removed`,
|
|
@@ -1741,9 +1792,9 @@ export function removeProperty(params) {
|
|
|
1741
1792
|
};
|
|
1742
1793
|
}
|
|
1743
1794
|
}
|
|
1744
|
-
export function insertContent(params) {
|
|
1795
|
+
export async function insertContent(params) {
|
|
1745
1796
|
try {
|
|
1746
|
-
const resolved = resolveWritableNoteReference(params);
|
|
1797
|
+
const resolved = await resolveWritableNoteReference(params);
|
|
1747
1798
|
if (!resolved.note) {
|
|
1748
1799
|
return {
|
|
1749
1800
|
success: false,
|
|
@@ -1807,7 +1858,7 @@ export function insertContent(params) {
|
|
|
1807
1858
|
line: params.line,
|
|
1808
1859
|
});
|
|
1809
1860
|
const writeTarget = getWritableIdentifier(note);
|
|
1810
|
-
store.updateNote(writeTarget.identifier, newContent, {
|
|
1861
|
+
await store.updateNote(writeTarget.identifier, newContent, {
|
|
1811
1862
|
source: writeTarget.source,
|
|
1812
1863
|
});
|
|
1813
1864
|
return {
|
|
@@ -1830,9 +1881,9 @@ export function insertContent(params) {
|
|
|
1830
1881
|
};
|
|
1831
1882
|
}
|
|
1832
1883
|
}
|
|
1833
|
-
export function appendContent(params) {
|
|
1884
|
+
export async function appendContent(params) {
|
|
1834
1885
|
try {
|
|
1835
|
-
const resolved = resolveWritableNoteReference(params);
|
|
1886
|
+
const resolved = await resolveWritableNoteReference(params);
|
|
1836
1887
|
if (!resolved.note) {
|
|
1837
1888
|
return {
|
|
1838
1889
|
success: false,
|
|
@@ -1848,7 +1899,7 @@ export function appendContent(params) {
|
|
|
1848
1899
|
heading: params.heading,
|
|
1849
1900
|
});
|
|
1850
1901
|
const writeTarget = getWritableIdentifier(note);
|
|
1851
|
-
store.updateNote(writeTarget.identifier, newContent, {
|
|
1902
|
+
await store.updateNote(writeTarget.identifier, newContent, {
|
|
1852
1903
|
source: writeTarget.source,
|
|
1853
1904
|
});
|
|
1854
1905
|
return {
|
|
@@ -1870,7 +1921,7 @@ export function appendContent(params) {
|
|
|
1870
1921
|
};
|
|
1871
1922
|
}
|
|
1872
1923
|
}
|
|
1873
|
-
export function deleteLines(params) {
|
|
1924
|
+
export async function deleteLines(params) {
|
|
1874
1925
|
try {
|
|
1875
1926
|
// Validate required line params — MCP may deliver them as undefined when omitted
|
|
1876
1927
|
const rawStart = params.startLine !== undefined && params.startLine !== null ? Number(params.startLine) : NaN;
|
|
@@ -1881,24 +1932,23 @@ export function deleteLines(params) {
|
|
|
1881
1932
|
if (!Number.isFinite(rawEnd)) {
|
|
1882
1933
|
return { success: false, error: 'endLine is required (1-indexed). Pass the same value as startLine to delete a single line.' };
|
|
1883
1934
|
}
|
|
1884
|
-
const resolved = resolveWritableNoteReference(params);
|
|
1935
|
+
const resolved = await resolveWritableNoteReference(params);
|
|
1885
1936
|
if (!resolved.note) {
|
|
1886
1937
|
return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };
|
|
1887
1938
|
}
|
|
1888
1939
|
const note = resolved.note;
|
|
1889
1940
|
const allLines = note.content.split('\n');
|
|
1890
|
-
// Line numbers are absolute (1-indexed), matching get_notes/getParagraphs
|
|
1891
1941
|
const totalLineCount = allLines.length;
|
|
1892
1942
|
const fmLineCount = frontmatter.getFrontmatterLineCount(note.content);
|
|
1893
|
-
|
|
1894
|
-
const boundedStartLine = toBoundedInt(params.startLine, minLine, minLine, Math.max(minLine, totalLineCount));
|
|
1895
|
-
const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, totalLineCount));
|
|
1896
|
-
if (boundedStartLine <= fmLineCount) {
|
|
1943
|
+
if (fmLineCount > 0 && rawStart <= fmLineCount) {
|
|
1897
1944
|
return {
|
|
1898
1945
|
success: false,
|
|
1899
|
-
error: `
|
|
1946
|
+
error: `Line ${rawStart} is inside frontmatter (lines 1-${fmLineCount}). Content starts at line ${fmLineCount + 1}.`,
|
|
1900
1947
|
};
|
|
1901
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));
|
|
1902
1952
|
const lineCountToDelete = boundedEndLine - boundedStartLine + 1;
|
|
1903
1953
|
const previewStartIndex = boundedStartLine - 1;
|
|
1904
1954
|
const previewEndIndexExclusive = boundedEndLine;
|
|
@@ -1969,7 +2019,7 @@ export function deleteLines(params) {
|
|
|
1969
2019
|
splicedLines.splice(boundedStartLine - 1, lineCountToDelete);
|
|
1970
2020
|
const newContent = splicedLines.join('\n');
|
|
1971
2021
|
const writeIdentifier = note.source === 'space' ? (note.id || note.filename) : note.filename;
|
|
1972
|
-
store.updateNote(writeIdentifier, newContent, { source: note.source });
|
|
2022
|
+
await store.updateNote(writeIdentifier, newContent, { source: note.source });
|
|
1973
2023
|
return {
|
|
1974
2024
|
success: true,
|
|
1975
2025
|
message: `Lines ${boundedStartLine}-${boundedEndLine} deleted`,
|
|
@@ -1986,7 +2036,7 @@ export function deleteLines(params) {
|
|
|
1986
2036
|
};
|
|
1987
2037
|
}
|
|
1988
2038
|
}
|
|
1989
|
-
export function editLine(params) {
|
|
2039
|
+
export async function editLine(params) {
|
|
1990
2040
|
try {
|
|
1991
2041
|
if (params.allowEmptyContent !== true && params.content.trim().length === 0) {
|
|
1992
2042
|
return {
|
|
@@ -1994,7 +2044,7 @@ export function editLine(params) {
|
|
|
1994
2044
|
error: 'Empty line content is blocked for noteplan_edit_line. Use noteplan_delete_lines or set allowEmptyContent=true.',
|
|
1995
2045
|
};
|
|
1996
2046
|
}
|
|
1997
|
-
const resolved = resolveWritableNoteReference(params);
|
|
2047
|
+
const resolved = await resolveWritableNoteReference(params);
|
|
1998
2048
|
if (!resolved.note) {
|
|
1999
2049
|
return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };
|
|
2000
2050
|
}
|
|
@@ -2033,7 +2083,7 @@ export function editLine(params) {
|
|
|
2033
2083
|
warnings.push(buildAttachmentWarningMessage(removedAttachmentReferences.length));
|
|
2034
2084
|
}
|
|
2035
2085
|
const writeIdentifier = note.source === 'space' ? (note.id || note.filename) : note.filename;
|
|
2036
|
-
store.updateNote(writeIdentifier, newContent, { source: note.source });
|
|
2086
|
+
await store.updateNote(writeIdentifier, newContent, { source: note.source });
|
|
2037
2087
|
return {
|
|
2038
2088
|
success: true,
|
|
2039
2089
|
message: `Line ${params.line} updated`,
|
|
@@ -2057,7 +2107,7 @@ export function editLine(params) {
|
|
|
2057
2107
|
};
|
|
2058
2108
|
}
|
|
2059
2109
|
}
|
|
2060
|
-
export function replaceLines(params) {
|
|
2110
|
+
export async function replaceLines(params) {
|
|
2061
2111
|
try {
|
|
2062
2112
|
// Validate required line params — MCP may deliver them as undefined when omitted
|
|
2063
2113
|
const rawStart = params.startLine !== undefined && params.startLine !== null ? Number(params.startLine) : NaN;
|
|
@@ -2068,24 +2118,23 @@ export function replaceLines(params) {
|
|
|
2068
2118
|
if (!Number.isFinite(rawEnd)) {
|
|
2069
2119
|
return { success: false, error: 'endLine is required (1-indexed). Pass the same value as startLine to replace a single line.' };
|
|
2070
2120
|
}
|
|
2071
|
-
const resolved = resolveWritableNoteReference(params);
|
|
2121
|
+
const resolved = await resolveWritableNoteReference(params);
|
|
2072
2122
|
if (!resolved.note) {
|
|
2073
2123
|
return { success: false, error: resolved.error || 'Note not found', candidates: resolved.candidates };
|
|
2074
2124
|
}
|
|
2075
2125
|
const note = resolved.note;
|
|
2076
2126
|
const allLines = note.content.split('\n');
|
|
2077
2127
|
const originalLineCount = allLines.length;
|
|
2078
|
-
// Line numbers are absolute (1-indexed), matching get_notes/getParagraphs
|
|
2079
2128
|
const fmLineCount = frontmatter.getFrontmatterLineCount(note.content);
|
|
2080
|
-
|
|
2081
|
-
const boundedStartLine = toBoundedInt(params.startLine, minLine, minLine, Math.max(minLine, originalLineCount));
|
|
2082
|
-
const boundedEndLine = toBoundedInt(params.endLine, boundedStartLine, boundedStartLine, Math.max(boundedStartLine, originalLineCount));
|
|
2083
|
-
if (boundedStartLine <= fmLineCount) {
|
|
2129
|
+
if (fmLineCount > 0 && rawStart <= fmLineCount) {
|
|
2084
2130
|
return {
|
|
2085
2131
|
success: false,
|
|
2086
|
-
error: `
|
|
2132
|
+
error: `Line ${rawStart} is inside frontmatter (lines 1-${fmLineCount}). Content starts at line ${fmLineCount + 1}.`,
|
|
2087
2133
|
};
|
|
2088
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));
|
|
2089
2138
|
let startIndex = boundedStartLine - 1;
|
|
2090
2139
|
let lineCountToReplace = boundedEndLine - boundedStartLine + 1;
|
|
2091
2140
|
const replacedText = allLines.slice(startIndex, boundedEndLine).join('\n');
|
|
@@ -2117,44 +2166,9 @@ export function replaceLines(params) {
|
|
|
2117
2166
|
if (lineDelta !== 0) {
|
|
2118
2167
|
warnings.push(`Line numbers shifted by ${lineDelta > 0 ? '+' : ''}${lineDelta} after this replacement. Re-read line numbers before the next mutation.`);
|
|
2119
2168
|
}
|
|
2120
|
-
const target = `${note.filename}:${boundedStartLine}-${boundedEndLine}:${replacementLines.length}:${normalized.content.length}`;
|
|
2121
|
-
if (isTrueBool(params.dryRun)) {
|
|
2122
|
-
const token = issueConfirmationToken({
|
|
2123
|
-
tool: 'noteplan_replace_lines',
|
|
2124
|
-
target,
|
|
2125
|
-
action: 'replace_lines',
|
|
2126
|
-
});
|
|
2127
|
-
return {
|
|
2128
|
-
success: true,
|
|
2129
|
-
dryRun: true,
|
|
2130
|
-
message: `Dry run: lines ${boundedStartLine}-${boundedEndLine} would be replaced`,
|
|
2131
|
-
lineCountToReplace,
|
|
2132
|
-
insertedLineCount: replacementLines.length,
|
|
2133
|
-
lineDelta,
|
|
2134
|
-
originalLineCount,
|
|
2135
|
-
newLineCount,
|
|
2136
|
-
indentationStyle,
|
|
2137
|
-
linesRetabbed: normalized.linesRetabbed,
|
|
2138
|
-
removedAttachmentReferences: removedAttachmentReferences.slice(0, 20),
|
|
2139
|
-
removedAttachmentReferencesTruncated: removedAttachmentReferences.length > 20,
|
|
2140
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
2141
|
-
...token,
|
|
2142
|
-
};
|
|
2143
|
-
}
|
|
2144
|
-
const confirmation = validateAndConsumeConfirmationToken(params.confirmationToken, {
|
|
2145
|
-
tool: 'noteplan_replace_lines',
|
|
2146
|
-
target,
|
|
2147
|
-
action: 'replace_lines',
|
|
2148
|
-
});
|
|
2149
|
-
if (!confirmation.ok) {
|
|
2150
|
-
return {
|
|
2151
|
-
success: false,
|
|
2152
|
-
error: confirmationFailureMessage('noteplan_replace_lines', confirmation.reason),
|
|
2153
|
-
};
|
|
2154
|
-
}
|
|
2155
2169
|
allLines.splice(startIndex, lineCountToReplace, ...replacementLines);
|
|
2156
2170
|
const writeIdentifier = note.source === 'space' ? (note.id || note.filename) : note.filename;
|
|
2157
|
-
store.updateNote(writeIdentifier, allLines.join('\n'), { source: note.source });
|
|
2171
|
+
await store.updateNote(writeIdentifier, allLines.join('\n'), { source: note.source });
|
|
2158
2172
|
return {
|
|
2159
2173
|
success: true,
|
|
2160
2174
|
message: `Lines ${boundedStartLine}-${boundedEndLine} replaced`,
|
|
@@ -2242,7 +2256,7 @@ export const searchParagraphsGlobalSchema = z.object({
|
|
|
2242
2256
|
offset: z.number().min(0).optional().default(0).describe('Pagination offset'),
|
|
2243
2257
|
cursor: z.string().optional().describe('Cursor token from previous page (preferred over offset)'),
|
|
2244
2258
|
});
|
|
2245
|
-
export function searchParagraphsGlobal(params) {
|
|
2259
|
+
export async function searchParagraphsGlobal(params) {
|
|
2246
2260
|
const query = typeof params?.query === 'string' ? params.query.trim() : '';
|
|
2247
2261
|
if (!query) {
|
|
2248
2262
|
return {
|
|
@@ -2264,7 +2278,7 @@ export function searchParagraphsGlobal(params) {
|
|
|
2264
2278
|
const noteTypes = normalizeTypeList(params.noteTypes);
|
|
2265
2279
|
const preferCalendar = params.preferCalendar === true;
|
|
2266
2280
|
const periodicOnly = params.periodicOnly === true;
|
|
2267
|
-
const allNotes = store.listNotes({
|
|
2281
|
+
const allNotes = await store.listNotes({
|
|
2268
2282
|
folder: params.folder,
|
|
2269
2283
|
space: params.space,
|
|
2270
2284
|
});
|