apple-notes-mcp 2.15.4 → 2.15.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/build/index.js +241 -100
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -48713,33 +48713,11 @@ import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as
48713
48713
  import { tmpdir as tmpdir3 } from "node:os";
48714
48714
  import { join as join17 } from "node:path";
48715
48715
 
48716
- // src/services/shortcutConsent.ts
48717
- function shortcutConsentHint(shortcut) {
48718
- const target = shortcut ? `"${shortcut}"` : "the bridge Shortcut";
48719
- return `This may be an unanswered first-run Shortcuts consent prompt, which a background run cannot display: run ${target} once in the foreground in Shortcuts.app and choose Always Allow.`;
48720
- }
48721
-
48722
- // src/services/nativeTags.ts
48723
- var NATIVE_TAGS_SHORTCUT = "Apple Notes MCP - Native Tags";
48724
- function normalizeNativeTags(tags) {
48725
- if (!tags.length || tags.length > 100) throw new Error("Provide between 1 and 100 tags");
48726
- return [
48727
- ...new Set(
48728
- tags.map((value) => {
48729
- const tag = value.normalize("NFC").replace(/^#/, "");
48730
- if (tag.length > 100 || !new RegExp("^(?=.*\\p{L})[\\p{L}\\p{N}_-]+$", "u").test(tag))
48731
- throw new Error(
48732
- "Tags must contain a letter and only letters, digits, hyphens or underscores"
48733
- );
48734
- return tag;
48735
- })
48736
- )
48737
- ];
48738
- }
48716
+ // src/utils/richContentPreservation.ts
48739
48717
  var TAG_OBJECT_TYPE = "com.apple.notes.inlinetextattachment.hashtag";
48740
48718
  function requirePreservationMetadata(rich) {
48741
- const unavailable = () => {
48742
- throw new Error("Native object, checklist or formatting preservation metadata is unavailable");
48719
+ const unavailable = (message = "Native object, checklist or formatting preservation metadata is unavailable") => {
48720
+ throw new Error(message);
48743
48721
  };
48744
48722
  const { objects, objectData, checklistItems, styleRuns, nativeTagObjectIds } = rich;
48745
48723
  if (!objects || !objectData || !checklistItems || !styleRuns || !nativeTagObjectIds || rich.nativeObjectDataComplete !== true)
@@ -48774,16 +48752,173 @@ function requirePreservationMetadata(rich) {
48774
48752
  return unavailable();
48775
48753
  }
48776
48754
  let position = 0;
48755
+ const firstReferences = /* @__PURE__ */ new Map();
48777
48756
  for (const run of styleRuns) {
48778
48757
  if (run.start !== position || !Number.isInteger(run.length) || run.length < 0 || typeof run.signature !== "string" || run.nativeSemantics?.complete !== true || run.length === 0 && (run.nativeSemantics.unknown || run.nativeSemantics.structuredParagraph || run.nativeSemantics.links || run.nativeSemantics.objects.length > 0))
48779
48758
  return unavailable();
48759
+ const references = run.nativeSemantics.objects;
48760
+ if (references.length > 1 || rich.text.slice(run.start, run.start + run.length).includes("\uFFFC") && references.length !== 1)
48761
+ return unavailable("Native object placement preservation metadata is unavailable");
48762
+ for (const reference of references) {
48763
+ if (!objectData.some((data) => data.id === reference.id && data.type === reference.type))
48764
+ return unavailable("Native object placement preservation metadata is unavailable");
48765
+ if (!firstReferences.has(reference.id))
48766
+ firstReferences.set(reference.id, {
48767
+ start: run.start,
48768
+ length: run.length,
48769
+ type: reference.type
48770
+ });
48771
+ }
48780
48772
  position += run.length;
48781
48773
  }
48782
48774
  if (position !== rich.text.length) return unavailable();
48775
+ if (objects.some((object3) => {
48776
+ const reference = firstReferences.get(object3.id);
48777
+ return !reference || reference.start !== object3.start || reference.length !== object3.length || reference.type !== object3.type;
48778
+ }))
48779
+ return unavailable("Native object placement preservation metadata is unavailable");
48783
48780
  for (const link of rich.links)
48784
48781
  if (!Number.isInteger(link.start) || !Number.isInteger(link.length) || link.start < 0 || link.length < 1 || link.start + link.length > rich.text.length || rich.text.slice(link.start, link.start + link.length) !== link.text)
48785
48782
  return unavailable();
48786
48783
  }
48784
+ function assertZeroLengthFormatting(before, after, spans) {
48785
+ const oldEmpty = before.styleRuns.filter((run) => run.length === 0).map((run) => {
48786
+ const span = spans.find(
48787
+ (span2) => run.start >= span2.before && run.start <= span2.before + span2.length
48788
+ );
48789
+ if (!span) throw new Error("Zero-length formatting falls inside the requested removal");
48790
+ return { ...run, start: span.after + run.start - span.before };
48791
+ });
48792
+ const newEmpty = after.styleRuns.filter(
48793
+ (run) => run.length === 0 && spans.some((span) => run.start >= span.after && run.start <= span.after + span.length)
48794
+ );
48795
+ if (JSON.stringify(oldEmpty) !== JSON.stringify(newEmpty))
48796
+ throw new Error("Existing zero-length formatting metadata changed");
48797
+ }
48798
+ function retainedSpans(before, after, change) {
48799
+ if (change.kind !== "remove-tag") {
48800
+ if (change.kind === "append" ? !after.text.startsWith(before.text) : after.text !== before.text)
48801
+ throw new Error("Existing note text or whitespace was not preserved");
48802
+ return {
48803
+ spans: [{ before: 0, after: 0, length: before.text.length }],
48804
+ removed: /* @__PURE__ */ new Set()
48805
+ };
48806
+ }
48807
+ const removed = new Set(before.nativeTagObjectIds[change.tag]);
48808
+ if (!removed.size || after.nativeTags.includes(change.tag))
48809
+ throw new Error("Native tag removal was not verified");
48810
+ const spans = [];
48811
+ let position = 0, output = "";
48812
+ for (const object3 of before.objects.filter((object4) => removed.has(object4.id))) {
48813
+ const text2 = before.text.slice(object3.start, object3.start + object3.length);
48814
+ if (text2 !== "\uFFFC" && text2 !== `#${change.tag}`)
48815
+ throw new Error("Native tag removal range is unsupported");
48816
+ const prefix = before.text.slice(position, object3.start);
48817
+ spans.push({ before: position, after: output.length, length: prefix.length });
48818
+ output += prefix;
48819
+ position = object3.start + object3.length;
48820
+ }
48821
+ spans.push({ before: position, after: output.length, length: before.text.length - position });
48822
+ output += before.text.slice(position);
48823
+ if (after.text !== output)
48824
+ throw new Error("Existing note text or whitespace was not preserved around the removed tag");
48825
+ return { spans, removed };
48826
+ }
48827
+ function assertRetainedRichContent(before, after, change = { kind: "unchanged" }) {
48828
+ requirePreservationMetadata(before);
48829
+ requirePreservationMetadata(after);
48830
+ const { spans, removed } = retainedSpans(before, after, change);
48831
+ const rangeStart = (start, length) => {
48832
+ const span = spans.find(
48833
+ (span2) => start >= span2.before && start + length <= span2.before + span2.length
48834
+ );
48835
+ return span && span.after + start - span.before;
48836
+ };
48837
+ const retainedIds = before.nativeObjectIds.filter((id2) => !removed.has(id2));
48838
+ if (retainedIds.some((id2) => !after.nativeObjectIds.includes(id2)) || [...removed].some((id2) => after.nativeObjectIds.includes(id2)) || change.kind !== "append" && after.nativeObjectIds.length !== retainedIds.length || change.kind === "append" && after.objects.some(
48839
+ (object3) => !retainedIds.includes(object3.id) && object3.start < before.text.length
48840
+ ))
48841
+ throw new Error("Existing native object identity or placement changed");
48842
+ for (const object3 of before.objects.filter((object4) => !removed.has(object4.id))) {
48843
+ const actual = after.objects.find((current) => current.id === object3.id);
48844
+ if (actual.type !== object3.type || actual.length !== object3.length || actual.start !== rangeStart(object3.start, object3.length))
48845
+ throw new Error("Existing native object range changed");
48846
+ const oldData = before.objectData.find((row) => row.id === object3.id), newData = after.objectData.find((row) => row.id === object3.id);
48847
+ if (oldData.pk !== newData.pk || oldData.type !== newData.type || oldData.mergeable !== newData.mergeable || oldData.view !== newData.view || oldData.altText !== newData.altText)
48848
+ throw new Error("Existing native object payload changed");
48849
+ }
48850
+ const tags = before.nativeTags.filter(
48851
+ (tag) => change.kind !== "remove-tag" || tag !== change.tag
48852
+ );
48853
+ if (tags.some(
48854
+ (tag) => JSON.stringify(before.nativeTagObjectIds[tag]) !== JSON.stringify(after.nativeTagObjectIds[tag])
48855
+ ) || change.kind !== "append" && after.nativeTags.length !== tags.length)
48856
+ throw new Error("Existing native tag identities changed");
48857
+ const links = change.kind === "append" ? after.links.filter((link) => link.start < before.text.length) : after.links;
48858
+ if (links.length !== before.links.length || before.links.some((link, index) => {
48859
+ const actual = links[index];
48860
+ return actual.text !== link.text || actual.url !== link.url || actual.length !== link.length || actual.start !== rangeStart(link.start, link.length);
48861
+ }))
48862
+ throw new Error("Existing links or their ranges were not preserved");
48863
+ const items = change.kind === "append" ? after.checklistItems.filter((item) => item.start < before.text.length) : after.checklistItems;
48864
+ if (items.length !== before.checklistItems.length || before.checklistItems.some((item, index) => {
48865
+ const actual = items[index];
48866
+ const retained = spans.flatMap((span) => {
48867
+ const start2 = Math.max(item.start, span.before), end = Math.min(item.start + item.text.length, span.before + span.length);
48868
+ return start2 < end ? [before.text.slice(start2, end)] : [];
48869
+ }).join("");
48870
+ const startSpan = spans.find(
48871
+ (span) => item.start >= span.before && item.start <= span.before + span.length
48872
+ );
48873
+ const start = startSpan && startSpan.after + item.start - startSpan.before;
48874
+ return actual.id !== item.id || actual.text !== retained || actual.done !== item.done || actual.start !== start;
48875
+ }))
48876
+ throw new Error("Existing checklist item identity, state or range changed");
48877
+ for (const span of spans) {
48878
+ let offset = 0, oldIndex = 0, newIndex = 0;
48879
+ while (offset < span.length) {
48880
+ const oldPosition = span.before + offset, newPosition = span.after + offset;
48881
+ while (before.styleRuns[oldIndex].start + before.styleRuns[oldIndex].length <= oldPosition)
48882
+ oldIndex++;
48883
+ while (after.styleRuns[newIndex].start + after.styleRuns[newIndex].length <= newPosition)
48884
+ newIndex++;
48885
+ const oldRun = before.styleRuns[oldIndex], newRun = after.styleRuns[newIndex];
48886
+ if (oldRun.signature !== newRun.signature || oldRun.paragraphStyle !== newRun.paragraphStyle || oldRun.blockQuote !== newRun.blockQuote || oldRun.highlight !== newRun.highlight)
48887
+ throw new Error("Existing rich formatting changed");
48888
+ offset += Math.min(
48889
+ span.length - offset,
48890
+ oldRun.start + oldRun.length - oldPosition,
48891
+ newRun.start + newRun.length - newPosition
48892
+ );
48893
+ }
48894
+ }
48895
+ assertZeroLengthFormatting(before, after, spans);
48896
+ }
48897
+
48898
+ // src/services/shortcutConsent.ts
48899
+ function shortcutConsentHint(shortcut) {
48900
+ const target = shortcut ? `"${shortcut}"` : "the bridge Shortcut";
48901
+ return `This may be an unanswered first-run Shortcuts consent prompt, which a background run cannot display: run ${target} once in the foreground in Shortcuts.app and choose Always Allow.`;
48902
+ }
48903
+
48904
+ // src/services/nativeTags.ts
48905
+ var NATIVE_TAGS_SHORTCUT = "Apple Notes MCP - Native Tags";
48906
+ function normalizeNativeTags(tags) {
48907
+ if (!tags.length || tags.length > 100) throw new Error("Provide between 1 and 100 tags");
48908
+ return [
48909
+ ...new Set(
48910
+ tags.map((value) => {
48911
+ const tag = value.normalize("NFC").replace(/^#/, "");
48912
+ if (tag.length > 100 || !new RegExp("^(?=.*\\p{L})[\\p{L}\\p{N}_-]+$", "u").test(tag))
48913
+ throw new Error(
48914
+ "Tags must contain a letter and only letters, digits, hyphens or underscores"
48915
+ );
48916
+ return tag;
48917
+ })
48918
+ )
48919
+ ];
48920
+ }
48921
+ var TAG_OBJECT_TYPE2 = "com.apple.notes.inlinetextattachment.hashtag";
48787
48922
  function plainNativeRange(rich, start, length) {
48788
48923
  return rich.styleRuns.every((run) => {
48789
48924
  if (run.start >= start + length || run.start + run.length <= start) return true;
@@ -48799,7 +48934,7 @@ function preservedTextSpans(before, after, missing) {
48799
48934
  ([tag, ids]) => ids.map((id2) => [id2, tag])
48800
48935
  )
48801
48936
  );
48802
- if (!added.length || added.some((o) => o.type !== TAG_OBJECT_TYPE || !missing.includes(tagsById.get(o.id))) || missing.some((tag) => !added.some((o) => tagsById.get(o.id) === tag)))
48937
+ if (!added.length || added.some((o) => o.type !== TAG_OBJECT_TYPE2 || !missing.includes(tagsById.get(o.id))) || missing.some((tag) => !added.some((o) => tagsById.get(o.id) === tag)))
48803
48938
  return void 0;
48804
48939
  const spans = [];
48805
48940
  let oldPosition = 0, newPosition = 0, appended = false;
@@ -48856,6 +48991,11 @@ function preservesAddedSemantics(before, after, spans) {
48856
48991
  return true;
48857
48992
  }
48858
48993
  function preservesNativeContent(before, after, spans) {
48994
+ try {
48995
+ assertZeroLengthFormatting(before, after, spans);
48996
+ } catch {
48997
+ return false;
48998
+ }
48859
48999
  const rangeStart = (start, length) => {
48860
49000
  const span = spans.find((s) => start >= s.before && start + length <= s.before + s.length);
48861
49001
  return span && span.after + start - span.before;
@@ -48906,6 +49046,16 @@ function preservesNativeContent(before, after, spans) {
48906
49046
  }
48907
49047
  return true;
48908
49048
  }
49049
+ function nativeTagAdditionPreserved(before, after, tags) {
49050
+ requirePreservationMetadata(before);
49051
+ requirePreservationMetadata(after);
49052
+ const missing = tags.filter((tag) => !before.nativeTags.includes(tag));
49053
+ const allTags = [.../* @__PURE__ */ new Set([...before.nativeTags, ...tags])];
49054
+ const spans = preservedTextSpans(before, after, missing);
49055
+ return after.nativeTags.length === allTags.length && allTags.every((tag) => after.nativeTags.includes(tag)) && before.nativeObjectIds.every((id2) => after.nativeObjectIds.includes(id2)) && before.hasChecklist === after.hasChecklist && Boolean(
49056
+ spans && preservesAddedSemantics(before, after, spans) && preservesNativeContent(before, after, spans)
49057
+ );
49058
+ }
48909
49059
  function addNativeTags(request, deps) {
48910
49060
  if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(request.id))
48911
49061
  throw new Error("An exact CoreData note ID is required");
@@ -48947,9 +49097,7 @@ function addNativeTags(request, deps) {
48947
49097
  } catch (error2) {
48948
49098
  throw verificationFailure(error2 instanceof Error ? error2.message : String(error2));
48949
49099
  }
48950
- const allTags = [.../* @__PURE__ */ new Set([...before.rich.nativeTags, ...tags])];
48951
- const spans = preservedTextSpans(before.rich, after.rich, missing);
48952
- if (after.title !== before.title || after.rich.nativeTags.length !== allTags.length || allTags.some((tag) => !after.rich.nativeTags.includes(tag)) || before.rich.nativeObjectIds.some((id2) => !after.rich.nativeObjectIds.includes(id2)) || before.rich.hasChecklist !== after.rich.hasChecklist || !spans || !preservesAddedSemantics(before.rich, after.rich, spans) || !preservesNativeContent(before.rich, after.rich, spans)) {
49100
+ if (after.title !== before.title || !nativeTagAdditionPreserved(before.rich, after.rich, tags)) {
48953
49101
  throw verificationFailure();
48954
49102
  }
48955
49103
  return {
@@ -49389,53 +49537,25 @@ function backgroundDependencies(manager) {
49389
49537
  function assertPreserved(before, after, options = {}) {
49390
49538
  if (before.id !== after.id || before.title !== after.title)
49391
49539
  throw new Error("Note identity changed");
49392
- const tidy = (s) => s.replace(/\r\n/g, "\n").replace(/[\s\ufffc]+$/gu, "");
49393
- let oldText = tidy(before.rich.text), newText = tidy(after.rich.text);
49394
- if (options.tagChange) {
49395
- const escaped = options.tagChange.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
49396
- const strip = (s) => s.replace(new RegExp(`#${escaped}(?![\\p{L}\\p{N}_-])`, "gu"), "").replace(/[\s\ufffc]/gu, "");
49397
- oldText = strip(oldText);
49398
- newText = strip(newText);
49399
- }
49400
- if (options.append ? !newText.startsWith(oldText) : newText !== oldText)
49401
- throw new Error("Existing note text was not preserved");
49402
- const links = options.append ? after.rich.links.slice(0, before.rich.links.length) : after.rich.links;
49403
- if (linkSignature(before.rich.links) !== linkSignature(links))
49404
- throw new Error("Existing links were not preserved");
49405
- if (!options.tagChange && before.rich.nativeObjectIds.some((id2) => !after.rich.nativeObjectIds.includes(id2)))
49406
- throw new Error("Existing native object was lost");
49407
- if (JSON.stringify(before.checklist) !== JSON.stringify(
49408
- options.append ? after.checklist.slice(0, before.checklist.length) : after.checklist
49409
- ))
49410
- throw new Error("Existing checklist items changed");
49411
- for (const item of before.rich.checklistItems || []) {
49412
- const actual = after.rich.checklistItems?.find((current) => current.id === item.id);
49413
- if (!actual || actual.text !== item.text || actual.done !== item.done)
49414
- throw new Error("Existing checklist item identity or state changed");
49415
- }
49416
- if (before.rich.nativeTags.filter((t) => t !== options.tagChange).some((t) => !after.rich.nativeTags.includes(t)))
49417
- throw new Error("Existing native tag was lost");
49418
- const allowedRemoved = options.tagChange ? before.rich.nativeTagObjectIds?.[options.tagChange] || [] : [];
49419
- for (const object3 of before.rich.objectData || []) {
49420
- if (allowedRemoved.includes(object3.id)) continue;
49421
- const actual = after.rich.objectData?.find((o) => o.id === object3.id);
49422
- if (!actual || actual.mergeable !== object3.mergeable || actual.view !== object3.view)
49423
- throw new Error("Existing native object content or presentation changed");
49424
- }
49425
- if (!options.tagChange && before.rich.styleRuns && after.rich.styleRuns) {
49426
- const end = before.rich.text.trimEnd().length;
49427
- for (const oldRun of before.rich.styleRuns)
49428
- for (const newRun of after.rich.styleRuns) {
49429
- const overlapStart = Math.max(oldRun.start, newRun.start);
49430
- const overlapEnd = Math.min(
49431
- end,
49432
- oldRun.start + oldRun.length,
49433
- newRun.start + newRun.length
49434
- );
49435
- if (overlapStart < overlapEnd && /[^\s\ufffc]/u.test(before.rich.text.slice(overlapStart, overlapEnd)) && oldRun.signature !== newRun.signature)
49436
- throw new Error("Existing rich formatting changed");
49437
- }
49540
+ if (options.tagChange && !options.removeTag) {
49541
+ if (!nativeTagAdditionPreserved(before.rich, after.rich, [options.tagChange]))
49542
+ throw new Error("Native tag addition or retained content was not verified");
49543
+ } else {
49544
+ try {
49545
+ assertRetainedRichContent(
49546
+ before.rich,
49547
+ after.rich,
49548
+ options.tagChange ? { kind: "remove-tag", tag: options.tagChange } : options.append ? { kind: "append" } : { kind: "unchanged" }
49549
+ );
49550
+ } catch (error2) {
49551
+ if (error2 instanceof Error && error2.message === "Existing note text or whitespace was not preserved")
49552
+ throw new Error("Existing note text was not preserved", { cause: error2 });
49553
+ throw error2;
49554
+ }
49438
49555
  }
49556
+ for (const snapshot of [before, after])
49557
+ if (JSON.stringify(snapshot.checklist) !== JSON.stringify(snapshot.rich.checklistItems.map(({ text: text2, done }) => ({ text: text2, done }))))
49558
+ throw new Error("Native checklist readers disagree about item text or state");
49439
49559
  }
49440
49560
  function describeTransportFailure(error2) {
49441
49561
  const detail = error2;
@@ -49449,12 +49569,37 @@ function textBelowTitle(text2, title) {
49449
49569
  if (firstLine.trim() !== title.trim()) return text2;
49450
49570
  return lineEnd < 0 ? "" : text2.slice(lineEnd + 1);
49451
49571
  }
49572
+ function assertChecklistEndAppendAvailable(rich) {
49573
+ requirePreservationMetadata(rich);
49574
+ const items = [...rich.checklistItems].sort((a, b) => a.start - b.start);
49575
+ if (!items.length) return;
49576
+ const last = items.at(-1);
49577
+ if (items.some(
49578
+ (item, index) => item.start > 0 && rich.text[item.start - 1] !== "\n" || index > 0 && item.start !== items[index - 1].start + items[index - 1].text.length + 1
49579
+ ) || !/^\n?$/u.test(rich.text.slice(last.start + last.text.length)))
49580
+ throw new CodedError(
49581
+ "Checklist end-append requires one existing checklist group at the end of the note. Notes may insert into an earlier group when text or native objects follow it, or when groups are separate. Nothing changed",
49582
+ { code: "validation_error", committed: false, indeterminate: false }
49583
+ );
49584
+ }
49585
+ function requireBackgroundPreflightMetadata(rich) {
49586
+ try {
49587
+ requirePreservationMetadata(rich);
49588
+ } catch (error2) {
49589
+ throw new CodedError(error2 instanceof Error ? error2.message : String(error2), {
49590
+ code: "validation_error",
49591
+ committed: false,
49592
+ indeterminate: false
49593
+ });
49594
+ }
49595
+ }
49452
49596
  function mutateBackground(request, operation, data, verify, deps) {
49453
49597
  if (request.scopeText.length < 12 || request.scopeText.length > 500 || /[\r\n\0]/u.test(request.scopeText))
49454
49598
  throw new Error("Use a distinctive existing single-line scope of 12\u2013500 characters");
49455
49599
  const before = deps.read(request.id);
49456
49600
  if (before.hash !== request.expectedContentHash)
49457
49601
  throw new Error("Note revision changed; read it again");
49602
+ requireBackgroundPreflightMetadata(before.rich);
49458
49603
  if (!before.rich.text.includes(request.scopeText))
49459
49604
  throw new Error("Scope is absent from exact note");
49460
49605
  if (!textBelowTitle(before.rich.text, before.title).includes(request.scopeText))
@@ -49465,8 +49610,10 @@ function mutateBackground(request, operation, data, verify, deps) {
49465
49610
  const candidates = deps.candidates(before.title, request.scopeText);
49466
49611
  if (candidates.length !== 1 || candidates[0] !== request.id)
49467
49612
  throw new Error("Ambiguous note selection; nothing changed");
49468
- if (deps.read(request.id).hash !== before.hash)
49469
- throw new Error("Note revision changed during preflight");
49613
+ const current = deps.read(request.id);
49614
+ if (current.hash !== before.hash) throw new Error("Note revision changed during preflight");
49615
+ requireBackgroundPreflightMetadata(current.rich);
49616
+ if (operation === "create-checklist-item") assertChecklistEndAppendAvailable(current.rich);
49470
49617
  let transportUncertain = false;
49471
49618
  let transportMessage = "";
49472
49619
  let transportError;
@@ -49570,7 +49717,7 @@ function setNativeTag(manager, request) {
49570
49717
  request.present ? "add-tag" : "remove-tag",
49571
49718
  { tag },
49572
49719
  (before, after) => {
49573
- assertPreserved(before, after, { tagChange: tag });
49720
+ assertPreserved(before, after, { tagChange: tag, removeTag: !request.present });
49574
49721
  if (after.rich.nativeTags.includes(tag) !== request.present)
49575
49722
  throw new Error("Native tag state not verified");
49576
49723
  const removed = before.rich.nativeObjectIds.filter(
@@ -55509,30 +55656,13 @@ function readSnapshot(manager, id2) {
55509
55656
  const rich = readRichNote(id2);
55510
55657
  if (enriched.revision !== rich.revision)
55511
55658
  throw new Error("Native metadata changed during read; read the note again");
55659
+ requirePreservationMetadata(rich);
55512
55660
  return { id: id2, title: note.title, body, rich, hash: richContentHash(body, enriched) };
55513
55661
  }
55514
55662
  function assertExistingContentPreserved(before, after) {
55515
55663
  if (before.id !== after.id || before.title !== after.title)
55516
55664
  throw new Error("Note identity changed");
55517
- const tidy = (text2) => text2.replace(/\r\n/g, "\n").replace(/[\s\ufffc]+$/gu, "");
55518
- if (!tidy(after.rich.text).startsWith(tidy(before.rich.text)))
55519
- throw new Error("Existing note text was not preserved");
55520
- if (linkSignature(before.rich.links) !== linkSignature(after.rich.links.slice(0, before.rich.links.length)))
55521
- throw new Error("Existing links were not preserved");
55522
- if (before.rich.nativeObjectIds.some((id2) => !after.rich.nativeObjectIds.includes(id2)))
55523
- throw new Error("Existing native object was lost");
55524
- if (before.rich.nativeTags.some((tag) => !after.rich.nativeTags.includes(tag)))
55525
- throw new Error("Existing native tag was lost");
55526
- for (const item of before.rich.checklistItems || []) {
55527
- const actual = after.rich.checklistItems?.find((current) => current.id === item.id);
55528
- if (!actual || actual.text !== item.text || actual.done !== item.done)
55529
- throw new Error("Existing checklist item identity or state changed");
55530
- }
55531
- for (const object3 of before.rich.objectData || []) {
55532
- const actual = after.rich.objectData?.find((current) => current.id === object3.id);
55533
- if (!actual || actual.mergeable !== object3.mergeable || actual.view !== object3.view)
55534
- throw new Error("Existing native object content or presentation changed");
55535
- }
55665
+ assertRetainedRichContent(before.rich, after.rich, { kind: "append" });
55536
55666
  }
55537
55667
  function localAttachment(path10) {
55538
55668
  if (!isAbsolute4(path10)) throw new Error("An absolute local file path is required");
@@ -55909,8 +56039,19 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
55909
56039
  transportWarning: "Transport was uncertain; exact bytes and prior content were verified"
55910
56040
  } : {}
55911
56041
  };
56042
+ } catch (error2) {
56043
+ if (progress.insertionStarted)
56044
+ throw new CodedError(
56045
+ `Attachment outcome uncertain; read the exact note before any retry: ${error2 instanceof Error ? error2.message : String(error2)}`,
56046
+ { code: "verification_failed", indeterminate: true }
56047
+ );
56048
+ throw error2;
55912
56049
  } finally {
55913
- rmSync7(directory, { recursive: true, force: true });
56050
+ try {
56051
+ rmSync7(directory, { recursive: true, force: true });
56052
+ } catch {
56053
+ console.error("Could not remove private temporary attachment files");
56054
+ }
55914
56055
  }
55915
56056
  }
55916
56057
 
@@ -59994,7 +60135,7 @@ function registerNativeOperations(server2, manager) {
59994
60135
  );
59995
60136
  tool(
59996
60137
  "create-checklist-item",
59997
- "Use when: appending one real unchecked Notes checklist item.\nReturns: the created checklist identity after exact-note readback.\nDo not use when: plain text is sufficient or more than one item is requested.\nSafety: preserves existing text, links, native objects, checklist identities, and formatting.",
60138
+ "Use when: appending one real unchecked Notes checklist item.\nReturns: the created checklist identity after exact-note readback.\nDo not use when: plain text is sufficient or more than one item is requested.\nSafety: preserves existing text, links, native objects, checklist identities, and formatting. If a checklist already exists, requires one contiguous group at the end of the note; otherwise refuses before running the bridge.",
59998
60139
  {
59999
60140
  ...common,
60000
60141
  text: external_exports.string().min(1).max(1e4).refine((s) => !/[\r\n\0]/u.test(s), "One line per checklist item")
@@ -60018,7 +60159,7 @@ function registerNativeOperations(server2, manager) {
60018
60159
  );
60019
60160
  tool(
60020
60161
  "create-checklist-items",
60021
- "Use when: appending several real unchecked Notes checklist items to one note, in order.\nReturns: each landed item's native identity and text, the final order check, and the new revision; on a stop, which items landed, the item whose outcome is uncertain, and the items not attempted.\nDo not use when: one item is enough (create-checklist-item) or plain text is sufficient.\nSafety: runs the verified single-item bridge once per item, chaining each verified revision into the next; checks after every item that exactly one new unchecked item with that text and a new identity appeared and that earlier items kept theirs, and stops at the first uncertain result without retrying. Each item takes a few seconds, so a full batch can run about a minute; if the call times out on the client side, read the note before retrying and send only items that are not present.",
60162
+ "Use when: appending several real unchecked Notes checklist items to one note, in order.\nReturns: each landed item's native identity and text, the final order check, and the new revision; on a stop, which items landed, the item whose outcome is uncertain, and the items not attempted.\nDo not use when: one item is enough (create-checklist-item) or plain text is sufficient.\nSafety: runs the verified single-item bridge once per item, chaining each verified revision into the next; requires any existing checklist to be one contiguous group at the end of the note before dispatch. Checks after every item that exactly one new unchecked item with that text and a new identity appeared and that earlier items kept theirs, and stops at the first uncertain result without retrying. Each item takes a few seconds, so a full batch can run about a minute; if the call times out on the client side, read the note before retrying and send only items that are not present.",
60022
60163
  {
60023
60164
  ...common,
60024
60165
  items: external_exports.array(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.15.4",
3
+ "version": "2.15.5",
4
4
  "description": "MCP server for Apple Notes - read, search, create, edit, organize, and export notes on macOS via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",