apple-notes-mcp 2.9.25 → 2.9.27

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/build/index.js CHANGED
@@ -30351,7 +30351,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
30351
30351
  return (payload, ctx) => fn(shape, payload, ctx);
30352
30352
  };
30353
30353
  let fastpass;
30354
- const isObject3 = isObject;
30354
+ const isObject4 = isObject;
30355
30355
  const jit = !globalConfig.jitless;
30356
30356
  const allowsEval2 = allowsEval;
30357
30357
  const fastEnabled = jit && allowsEval2.value;
@@ -30360,7 +30360,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
30360
30360
  inst._zod.parse = (payload, ctx) => {
30361
30361
  value ?? (value = _normalized.value);
30362
30362
  const input = payload.value;
30363
- if (!isObject3(input)) {
30363
+ if (!isObject4(input)) {
30364
30364
  payload.issues.push({
30365
30365
  expected: "object",
30366
30366
  code: "invalid_type",
@@ -39361,7 +39361,7 @@ function bytesValue(field) {
39361
39361
  function stringValue(field) {
39362
39362
  const bytes = bytesValue(field);
39363
39363
  if (!bytes) return void 0;
39364
- return new TextDecoder().decode(bytes);
39364
+ return new TextDecoder("utf-8", { ignoreBOM: true }).decode(bytes);
39365
39365
  }
39366
39366
  function embeddedMessage(field) {
39367
39367
  const bytes = bytesValue(field);
@@ -39641,6 +39641,10 @@ function identifierFailureIn(message) {
39641
39641
  function canonicalKey(value) {
39642
39642
  return BigInt(value).toString();
39643
39643
  }
39644
+ function canonicalCoreDataId(id2) {
39645
+ const match = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(IC[A-Za-z]+)\/p(\d+)$/.exec(id2);
39646
+ return match ? `x-coredata://${match[1].toUpperCase()}/${match[2]}/p${canonicalKey(match[3])}` : id2;
39647
+ }
39644
39648
  function assertAll(values, pattern2, label) {
39645
39649
  for (const value of values) {
39646
39650
  if (!pattern2.test(value)) throw new Error(`Refusing to query with an invalid ${label}`);
@@ -40034,7 +40038,7 @@ function styleValue(field) {
40034
40038
  }
40035
40039
  return Buffer.from(field.value).toString("hex");
40036
40040
  }
40037
- function parseRichNote(data, nativeTags = []) {
40041
+ function parseRichNote(data, nativeTags = [], options = {}) {
40038
40042
  const doc = decodeMessage(data);
40039
40043
  const wrapper = embeddedMessage(getField(doc, 2));
40040
40044
  const body = wrapper && embeddedMessage(getField(wrapper, 3));
@@ -40074,7 +40078,7 @@ function parseRichNote(data, nativeTags = []) {
40074
40078
  if (varintValue(getField(fields, 14))) lossy.add("highlight");
40075
40079
  }
40076
40080
  const url = stringValue(getField(fields, 9));
40077
- if (url) {
40081
+ if (url && !(options.skipUnsafeLinks && !safeUrl(url))) {
40078
40082
  if (!safeUrl(url)) throw new Error("Unsupported link scheme in note");
40079
40083
  const previous = links.at(-1);
40080
40084
  if (previous?.url === url && previous.start + previous.length === position) {
@@ -40129,7 +40133,7 @@ function parseRichNote(data, nativeTags = []) {
40129
40133
  ...lossy.size ? { htmlLossyFormatting: HTML_LOSSY_ORDER.filter((f) => lossy.has(f)) } : {}
40130
40134
  };
40131
40135
  }
40132
- function readRichNote(id2) {
40136
+ function readRichNote(id2, options = {}) {
40133
40137
  const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]+)$/i.exec(id2)?.[1];
40134
40138
  if (!pk) throw new Error("Invalid exact note ID");
40135
40139
  const sql = `BEGIN; SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE=${pk}; SELECT json_group_object(ZIDENTIFIER,ZALTTEXT) FROM ZICCLOUDSYNCINGOBJECT WHERE ZNOTE1=${pk} AND ZTYPEUTI1='com.apple.notes.inlinetextattachment.hashtag'; SELECT json_group_array(json_object('id',ZIDENTIFIER,'pk',Z_PK,'type',COALESCE(ZTYPEUTI1,ZTYPEUTI),'mergeable',hex(COALESCE(ZMERGEABLEDATA1,ZMERGEABLEDATA)),'view',ZATTACHMENTVIEWTYPE)) FROM ZICCLOUDSYNCINGOBJECT WHERE ZNOTE1=${pk} OR ZNOTE=${pk}; COMMIT;`;
@@ -40144,7 +40148,9 @@ function readRichNote(id2) {
40144
40148
  if (!tags || Array.isArray(tags) || typeof tags !== "object" || Object.values(tags).some((tag) => typeof tag !== "string"))
40145
40149
  throw new Error("Invalid native tags");
40146
40150
  const rich = parseRichNote(
40147
- gunzipSync2(Buffer.from(rows[0], "hex"), { maxOutputLength: 32 * 1024 * 1024 })
40151
+ gunzipSync2(Buffer.from(rows[0], "hex"), { maxOutputLength: 32 * 1024 * 1024 }),
40152
+ [],
40153
+ options
40148
40154
  );
40149
40155
  const tagMap = tags;
40150
40156
  const objectData = JSON.parse(rows[2] || "[]");
@@ -41474,8 +41480,11 @@ function privateContentReason(p, roots) {
41474
41480
  return isWithinRoots(p, cloudDocuments) ? null : "~/Library";
41475
41481
  }
41476
41482
  var CLOUD_DOCUMENT_DIRS = ["Mobile Documents", "CloudStorage"];
41477
- function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPrivate = process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1") {
41478
- const abs = assertReadableInRoots(p, roots);
41483
+ function readAllowedFile(p, maxBytes, options = {}) {
41484
+ const roots = options.roots ?? allowedSaveRoots();
41485
+ const allowPrivate = options.allowPrivate ?? process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1";
41486
+ const label = options.label ?? "Content file";
41487
+ const abs = assertReadableInRoots(p, roots, label);
41479
41488
  const assertNotPrivate = (candidate, candidateRoots) => {
41480
41489
  if (allowPrivate) return;
41481
41490
  const reason = privateContentReason(candidate, candidateRoots);
@@ -41496,12 +41505,12 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPriva
41496
41505
  }
41497
41506
  try {
41498
41507
  const stat = fstatSync(descriptor);
41499
- if (!stat.isFile()) throw new Error(`Content file is not a regular file: "${abs}"`);
41508
+ if (!stat.isFile()) throw new Error(`${label} is not a regular file: "${abs}"`);
41500
41509
  let after;
41501
41510
  try {
41502
41511
  after = canonicalize(abs);
41503
41512
  } catch {
41504
- throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
41513
+ throw new Error(`${label} changed while it was being opened; try again: "${abs}"`);
41505
41514
  }
41506
41515
  if (!isWithinRoots(after, canonicalRoots(roots)))
41507
41516
  throw new Error(
@@ -41510,22 +41519,26 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPriva
41510
41519
  assertNotPrivate(after, canonicalRoots(roots));
41511
41520
  const named = statSync(after);
41512
41521
  if (named.dev !== stat.dev || named.ino !== stat.ino)
41513
- throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
41514
- if (stat.size === 0) throw new Error(`Content file is empty: "${abs}"`);
41522
+ throw new Error(`${label} changed while it was being opened; try again: "${abs}"`);
41523
+ if (stat.size === 0) throw new Error(`${label} is empty: "${abs}"`);
41515
41524
  if (stat.size > maxBytes)
41516
- throw new Error(`Content file is ${stat.size} bytes, over the ${maxBytes}-byte limit.`);
41525
+ throw new Error(`${label} is ${stat.size} bytes, over the ${maxBytes}-byte limit.`);
41517
41526
  const bytes = readFileSync(descriptor);
41518
41527
  if (bytes.length !== stat.size)
41519
- throw new Error("Content file changed while it was being read; try again");
41520
- try {
41521
- return new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
41522
- } catch {
41523
- throw new Error(`Content file is not valid UTF-8 text: "${abs}"`);
41524
- }
41528
+ throw new Error(`${label} changed while it was being read; try again`);
41529
+ return bytes;
41525
41530
  } finally {
41526
41531
  closeSync(descriptor);
41527
41532
  }
41528
41533
  }
41534
+ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPrivate = process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1") {
41535
+ const bytes = readAllowedFile(p, maxBytes, { roots, allowPrivate });
41536
+ try {
41537
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
41538
+ } catch {
41539
+ throw new Error(`Content file is not valid UTF-8 text: "${resolve(p)}"`);
41540
+ }
41541
+ }
41529
41542
  function cleanupTempDir(dir) {
41530
41543
  try {
41531
41544
  if (existsSync5(dir)) rmSync(dir, { recursive: true, force: true });
@@ -41653,6 +41666,7 @@ function buildAttachmentRowsSql(notePk, columns) {
41653
41666
  `'identifier', a.ZIDENTIFIER`,
41654
41667
  `'uti', ${col3("a", "ZTYPEUTI")}`,
41655
41668
  `'parentPk', ${parent}`,
41669
+ `'parentDeleted', ${parent !== "NULL" && columns.has("ZMARKEDFORDELETION") ? `(SELECT COALESCE(p.ZMARKEDFORDELETION, 0) FROM ZICCLOUDSYNCINGOBJECT p WHERE p.Z_PK = ${parent})` : "0"}`,
41656
41670
  `'filename', ${col3("a", "ZFILENAME")}`,
41657
41671
  `'mediaIdentifier', m.ZIDENTIFIER`,
41658
41672
  `'mediaFilename', ${col3("m", "ZFILENAME")}`,
@@ -41711,6 +41725,7 @@ function parseAttachmentRows(json2) {
41711
41725
  identifier,
41712
41726
  uti: toStringOrNull(r.uti),
41713
41727
  parentPk: toIntOrNull(r.parentPk),
41728
+ ...toIntOrNull(r.parentDeleted) ? { parentDeleted: true } : {},
41714
41729
  filename: toStringOrNull(r.filename),
41715
41730
  mediaIdentifier: toStringOrNull(r.mediaIdentifier),
41716
41731
  mediaFilename: toStringOrNull(r.mediaFilename),
@@ -41828,7 +41843,7 @@ function generationDirs(base, accountDir) {
41828
41843
  const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
41829
41844
  return entries2.map((e) => realInside(join8(base, e), accountDir)).filter((p) => p !== null && isDirectory(p)).sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
41830
41845
  }
41831
- function fallbackFiles(accountDir, rootName, identifier, generation, names) {
41846
+ function fallbackFiles(accountDir, rootName, identifier, generation, names, onStale) {
41832
41847
  const base = join8(accountDir, rootName, identifier);
41833
41848
  const found = [];
41834
41849
  const add = (candidate) => {
@@ -41837,12 +41852,14 @@ function fallbackFiles(accountDir, rootName, identifier, generation, names) {
41837
41852
  };
41838
41853
  const gen = safeComponent(generation);
41839
41854
  if (gen) for (const name of names) add(join8(base, gen, name));
41855
+ const recorded = found.length;
41840
41856
  if (isDirectory(base)) {
41841
41857
  for (const dir of generationDirs(base, accountDir))
41842
41858
  for (const name of names) add(join8(dir, name));
41843
41859
  for (const name of names) add(join8(base, name));
41844
41860
  }
41845
41861
  for (const name of names) add(join8(accountDir, rootName, `${identifier}${extname(name)}`));
41862
+ if (gen && recorded === 0 && found.length > 0) onStale?.();
41846
41863
  return found;
41847
41864
  }
41848
41865
  function previewPixelArea(name) {
@@ -41888,7 +41905,7 @@ function previewPaths(accountDir, identifier, entries2) {
41888
41905
  }
41889
41906
  return files;
41890
41907
  }
41891
- function assetPathsFor(accountDir, row) {
41908
+ function assetPathsFor(accountDir, row, onStale) {
41892
41909
  const found = [];
41893
41910
  const add = (candidate) => {
41894
41911
  const real = realInside(candidate, accountDir);
@@ -41905,18 +41922,28 @@ function assetPathsFor(accountDir, row) {
41905
41922
  if (!id2) return found;
41906
41923
  const ownName = safeComponent(row.filename);
41907
41924
  if (ownName) add(join8(accountDir, "Media", id2, ownName));
41908
- for (const file of fallbackFiles(accountDir, "FallbackImages", id2, row.fallbackImageGeneration, [
41909
- "FallbackImage.png",
41910
- "FallbackImage.jpg"
41911
- ]))
41925
+ for (const file of fallbackFiles(
41926
+ accountDir,
41927
+ "FallbackImages",
41928
+ id2,
41929
+ row.fallbackImageGeneration,
41930
+ ["FallbackImage.png", "FallbackImage.jpg"],
41931
+ onStale
41932
+ ))
41912
41933
  add(file);
41913
- for (const file of fallbackFiles(accountDir, "FallbackPDFs", id2, row.fallbackPdfGeneration, [
41914
- "FallbackPDF.pdf"
41915
- ]))
41934
+ for (const file of fallbackFiles(
41935
+ accountDir,
41936
+ "FallbackPDFs",
41937
+ id2,
41938
+ row.fallbackPdfGeneration,
41939
+ ["FallbackPDF.pdf"],
41940
+ onStale
41941
+ ))
41916
41942
  add(file);
41917
41943
  return found;
41918
41944
  }
41919
- function assembleAttachmentAssets(rows, bodyOrder, containerDir = NOTES_CONTAINER_DIR) {
41945
+ function assembleAttachmentAssets(allRows, bodyOrder, containerDir = NOTES_CONTAINER_DIR) {
41946
+ const rows = allRows.filter((r) => !r.parentDeleted);
41920
41947
  const byPk = new Map(rows.map((r) => [r.pk, r]));
41921
41948
  const roots = rows.filter((r) => r.parentPk === null || !byPk.has(r.parentPk));
41922
41949
  const bodyIndex = /* @__PURE__ */ new Map();
@@ -41939,10 +41966,11 @@ function assembleAttachmentAssets(rows, bodyOrder, containerDir = NOTES_CONTAINE
41939
41966
  const accountDir = accountDirFor(row.accountIdentifier ?? parent?.accountIdentifier ?? null);
41940
41967
  let assetPaths = [];
41941
41968
  let previews = [];
41969
+ let fallbackStale = false;
41942
41970
  if (accountDir) {
41943
41971
  if (!previewEntries.has(accountDir))
41944
41972
  previewEntries.set(accountDir, listPreviewEntries(accountDir));
41945
- assetPaths = assetPathsFor(accountDir, row);
41973
+ assetPaths = assetPathsFor(accountDir, row, () => fallbackStale = true);
41946
41974
  previews = previewPaths(accountDir, row.identifier, previewEntries.get(accountDir));
41947
41975
  }
41948
41976
  const previewPath = previews[0] ?? null;
@@ -41955,6 +41983,7 @@ function assembleAttachmentAssets(rows, bodyOrder, containerDir = NOTES_CONTAINE
41955
41983
  filename: row.filename ?? row.mediaFilename,
41956
41984
  bodyIndex: parent ? null : indexOf(row),
41957
41985
  assetPaths,
41986
+ ...fallbackStale ? { fallbackStale: true } : {},
41958
41987
  previewPath,
41959
41988
  paths: previewPath ? [...assetPaths, previewPath] : [...assetPaths]
41960
41989
  };
@@ -42040,6 +42069,11 @@ function copyFileExclusive(src, dest) {
42040
42069
  function exportFileName(record2, source, kind) {
42041
42070
  const ext = extname(source);
42042
42071
  const id2 = safeComponent(record2.identifier) ?? "attachment";
42072
+ if (kind === "fallback") {
42073
+ const stored2 = safeComponent(record2.filename ? basename(record2.filename) : null);
42074
+ const stem = stored2 && !GENERIC_FILE_NAMES.has(stored2.toLowerCase()) ? basename(stored2, extname(stored2)) : id2;
42075
+ return `${safeComponent(stem) ?? id2}${ext}`;
42076
+ }
42043
42077
  if (kind === "preview") {
42044
42078
  const stored2 = safeComponent(record2.filename ? basename(record2.filename) : null);
42045
42079
  const stem = stored2 ? basename(stored2, extname(stored2)) : id2;
@@ -42068,7 +42102,7 @@ function prepareExportDir(exportDir, containerDir = NOTES_CONTAINER_DIR) {
42068
42102
  }
42069
42103
  return abs;
42070
42104
  }
42071
- function exportOneAttachment(record2, dir, source) {
42105
+ function exportOneAttachment(record2, dir, source, inBody) {
42072
42106
  const base = {
42073
42107
  pk: record2.pk,
42074
42108
  identifier: record2.identifier,
@@ -42076,7 +42110,8 @@ function exportOneAttachment(record2, dir, source) {
42076
42110
  kind: record2.kind,
42077
42111
  parentIdentifier: record2.parentIdentifier,
42078
42112
  exportedTo: null,
42079
- exportedKind: null
42113
+ exportedKind: null,
42114
+ ...inBody === false ? { inBody: false } : {}
42080
42115
  };
42081
42116
  if (!source) return base;
42082
42117
  const name = exportFileName(record2, source.path, source.kind);
@@ -42087,7 +42122,12 @@ function exportOneAttachment(record2, dir, source) {
42087
42122
  throw new Error(`Refusing to write outside the export directory: "${dest}"`);
42088
42123
  assertSafeSavePath(dest);
42089
42124
  copyFileExclusive(source.path, dest);
42090
- return { ...base, exportedTo: dest, exportedKind: source.kind };
42125
+ return {
42126
+ ...base,
42127
+ exportedTo: dest,
42128
+ exportedKind: source.kind,
42129
+ ...source.kind === "fallback" && record2.fallbackStale ? { stale: true } : {}
42130
+ };
42091
42131
  } catch (error2) {
42092
42132
  if (error2.code === "EEXIST") continue;
42093
42133
  return { ...base, error: error2 instanceof Error ? error2.message : String(error2) };
@@ -42095,20 +42135,31 @@ function exportOneAttachment(record2, dir, source) {
42095
42135
  }
42096
42136
  return { ...base, error: "Too many name collisions in the export directory" };
42097
42137
  }
42138
+ function isFallbackRendering(path10) {
42139
+ return /[\\/]Fallback(?:Images|PDFs)[\\/]/.test(path10);
42140
+ }
42098
42141
  function exportSource(record2) {
42099
- if (record2.assetPaths[0]) return { path: record2.assetPaths[0], kind: "asset" };
42142
+ const asset = record2.assetPaths[0];
42143
+ if (asset) return { path: asset, kind: isFallbackRendering(asset) ? "fallback" : "asset" };
42100
42144
  if (record2.previewPath) return { path: record2.previewPath, kind: "preview" };
42101
42145
  return null;
42102
42146
  }
42103
42147
  function exportAttachmentAssets(assets, exportDir, options = {}) {
42104
42148
  const dir = prepareExportDir(exportDir, options.containerDir);
42149
+ const inBody = (record2) => {
42150
+ if (assets.orderSource !== "body") return void 0;
42151
+ const root = record2.parentIdentifier === null ? record2 : assets.attachments.find(
42152
+ (a) => a.parentIdentifier === null && a.identifier === record2.parentIdentifier
42153
+ );
42154
+ return root ? root.bodyIndex !== null : void 0;
42155
+ };
42105
42156
  if (options.firstImageOnly) {
42106
42157
  const first2 = selectFirstImage(assets);
42107
42158
  if (!first2) return { exportDir: dir, results: [], firstImage: null };
42108
42159
  const record2 = assets.attachments.find((a) => a.pk === first2.pk);
42109
42160
  return {
42110
42161
  exportDir: dir,
42111
- results: [exportOneAttachment(record2, dir, exportSource(record2))],
42162
+ results: [exportOneAttachment(record2, dir, exportSource(record2), inBody(record2))],
42112
42163
  firstImage: first2
42113
42164
  };
42114
42165
  }
@@ -42122,7 +42173,7 @@ function exportAttachmentAssets(assets, exportDir, options = {}) {
42122
42173
  } else if (!exportSource(record2) && assets.attachments.some((c) => c.parentIdentifier === record2.identifier)) {
42123
42174
  continue;
42124
42175
  }
42125
- results.push(exportOneAttachment(record2, dir, exportSource(record2)));
42176
+ results.push(exportOneAttachment(record2, dir, exportSource(record2), inBody(record2)));
42126
42177
  }
42127
42178
  return { exportDir: dir, results };
42128
42179
  }
@@ -42291,7 +42342,17 @@ function entries(dir, limit) {
42291
42342
  }
42292
42343
  }
42293
42344
  var byGenerationDesc = (a, b) => generationRank(basename2(b)) - generationRank(basename2(a));
42294
- function findFallbackImage(accountDir, identifier, generation) {
42345
+ function locateFallbackImage(accountDir, identifier, generation) {
42346
+ const gen = safeComponent(generation);
42347
+ const path10 = searchFallbackImage(accountDir, identifier, generation);
42348
+ if (!path10 || !gen) return { path: path10, stale: false };
42349
+ const id2 = safeComponent(identifier);
42350
+ const recorded = ["FallbackImage.png", "FallbackImage.jpg"].map(
42351
+ (n) => id2 ? realInside(join9(accountDir, "FallbackImages", id2, gen, n), accountDir) : null
42352
+ ).filter((p) => p !== null);
42353
+ return { path: path10, stale: !recorded.includes(path10) };
42354
+ }
42355
+ function searchFallbackImage(accountDir, identifier, generation) {
42295
42356
  const id2 = safeComponent(identifier);
42296
42357
  if (!id2) return null;
42297
42358
  const base = join9(accountDir, "FallbackImages", id2);
@@ -42338,7 +42399,8 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
42338
42399
  const accountDir = resolveAccountDir(containerDir, row.accountIdentifier);
42339
42400
  const kind = row.uti === "com.apple.paper" ? "paper" : "drawing";
42340
42401
  const id2 = safeComponent(row.identifier);
42341
- const fallbackImagePath = accountDir ? findFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : null;
42402
+ const fallback = accountDir ? locateFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : { path: null, stale: false };
42403
+ const fallbackImagePath = fallback.path;
42342
42404
  const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
42343
42405
  const bundle = accountDir && id2 && kind === "paper" ? realInside(join9(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
42344
42406
  let raster = null;
@@ -42360,6 +42422,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
42360
42422
  handwritingSummary: row.handwritingSummary,
42361
42423
  bundlePresent: bundle !== null && kindOf(bundle) === "dir",
42362
42424
  fallbackImagePath,
42425
+ fallbackImageStale: fallback.stale,
42363
42426
  previewPath,
42364
42427
  raster
42365
42428
  };
@@ -42500,18 +42563,25 @@ function idList(ids) {
42500
42563
  function buildScopeGuardScript(noteVar, guard, destinationVar) {
42501
42564
  if (!hasScopeGuard(guard)) return "";
42502
42565
  validateScopeGuard(guard);
42503
- const forbidden = guard.forbiddenAncestorFolderIds ?? [];
42566
+ const ifFolderId = guard.ifFolderId && canonicalCoreDataId(guard.ifFolderId);
42567
+ const ifAncestorFolderId = guard.ifAncestorFolderId && canonicalCoreDataId(guard.ifAncestorFolderId);
42568
+ const forbidden = [...new Set((guard.forbiddenAncestorFolderIds ?? []).map(canonicalCoreDataId))];
42504
42569
  let script = `
42505
42570
  set scopeFolder to container of ${noteVar}
42506
42571
  if class of scopeFolder is not folder then return "${SCOPE_MARKER}:the note is not in a folder"`;
42507
- if (guard.ifFolderId)
42572
+ if (forbidden.length > 0)
42508
42573
  script += `
42509
- if (id of scopeFolder) is not "${guard.ifFolderId}" then return "${SCOPE_MARKER}:the note is not in the expected folder"`;
42510
- if (guard.ifAncestorFolderId || forbidden.length > 0)
42574
+ repeat with forbiddenId in ${idList(forbidden)}
42575
+ if not (exists folder id (contents of forbiddenId)) then return "${SCOPE_MARKER}:a forbidden folder id does not match any folder"
42576
+ end repeat`;
42577
+ if (ifFolderId)
42578
+ script += `
42579
+ if (id of scopeFolder) is not "${ifFolderId}" then return "${SCOPE_MARKER}:the note is not in the expected folder"`;
42580
+ if (ifAncestorFolderId || forbidden.length > 0)
42511
42581
  script += chainScript("scopeChain", "scopeFolder");
42512
- if (guard.ifAncestorFolderId)
42582
+ if (ifAncestorFolderId)
42513
42583
  script += `
42514
- if scopeChain does not contain "${guard.ifAncestorFolderId}" then return "${SCOPE_MARKER}:the note is not inside the expected ancestor folder"`;
42584
+ if scopeChain does not contain "${ifAncestorFolderId}" then return "${SCOPE_MARKER}:the note is not inside the expected ancestor folder"`;
42515
42585
  if (forbidden.length > 0) {
42516
42586
  script += `
42517
42587
  repeat with forbiddenId in ${idList(forbidden)}
@@ -43428,7 +43498,7 @@ var AppleNotesManager = class {
43428
43498
  * @throws Error when the note's rich data cannot be read (e.g. no Full Disk Access)
43429
43499
  */
43430
43500
  getNoteTablesById(id2) {
43431
- return collectNoteTables(readRichNote(id2), id2);
43501
+ return collectNoteTables(readRichNote(id2, { skipUnsafeLinks: true }), id2);
43432
43502
  }
43433
43503
  /**
43434
43504
  * Retrieves detailed metadata for a note by title.
@@ -43561,6 +43631,7 @@ var AppleNotesManager = class {
43561
43631
  return `
43562
43632
  if not (exists note id "${safeGuardId}") then ${inactive("missing")}
43563
43633
  set ${ref} to note id "${safeGuardId}"
43634
+ if (id of ${ref}) is (id of noteRef) then ${inactive("the note being deleted")}
43564
43635
  if password protected of ${ref} then ${inactive("locked")}
43565
43636
  set ${folderVar} to missing value
43566
43637
  try
@@ -46321,7 +46392,7 @@ var HIGHLIGHTS = {
46321
46392
  var KNOWN_RUN_FIELDS = /* @__PURE__ */ new Set([1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 14]);
46322
46393
  var KNOWN_PARAGRAPH_FIELDS = /* @__PURE__ */ new Set([1, 2, 4, 5, 8, 9]);
46323
46394
  var isSafeLink = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
46324
- var utf8 = new TextDecoder();
46395
+ var utf8 = new TextDecoder("utf-8", { ignoreBOM: true });
46325
46396
  var first = (fields, n) => fields.find((f) => f.fieldNumber === n);
46326
46397
  var varintOf = (fields, n) => {
46327
46398
  const f = first(fields, n);
@@ -46470,7 +46541,8 @@ function decodeNoteBlocks(data) {
46470
46541
  const end = newline === -1 ? text2.length : newline;
46471
46542
  while (runIndex < runs.length - 1 && runs[runIndex].start + runs[runIndex].length <= paragraphStart)
46472
46543
  runIndex++;
46473
- const attrs = runs[runIndex]?.paragraph ?? DEFAULT_PARAGRAPH;
46544
+ const covering = runs[runIndex];
46545
+ const attrs = !covering ? DEFAULT_PARAGRAPH : covering.paragraph.styleType === 103 && checklistRunLineStart(text2, covering.start, covering.length) > paragraphStart ? DEFAULT_PARAGRAPH : covering.paragraph;
46474
46546
  const style = attrs.styleType === null ? "body" : STYLE_NAMES[attrs.styleType] ?? "unknown";
46475
46547
  const alignment = ALIGNMENTS[attrs.alignmentValue] ?? "unknown";
46476
46548
  const block = {
@@ -46677,6 +46749,25 @@ function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
46677
46749
  return decodeCompressedNoteBlocks(Buffer.from(row.data, "hex"));
46678
46750
  }
46679
46751
 
46752
+ // src/utils/wordCount.ts
46753
+ var UNSPACED_SCRIPT = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Thai}\p{Script=Lao}\p{Script=Khmer}\p{Script=Myanmar}]/u;
46754
+ var segmenter;
46755
+ function countWords(text2) {
46756
+ let count = 0;
46757
+ for (const run of text2.replace(//gu, " ").split(/\s+/u)) {
46758
+ if (!/[\p{L}\p{N}]/u.test(run)) continue;
46759
+ if (!UNSPACED_SCRIPT.test(run)) {
46760
+ count++;
46761
+ continue;
46762
+ }
46763
+ segmenter ??= new Intl.Segmenter(void 0, { granularity: "word" });
46764
+ let words = 0;
46765
+ for (const segment of segmenter.segment(run)) if (segment.isWordLike) words++;
46766
+ count += Math.max(1, words);
46767
+ }
46768
+ return count;
46769
+ }
46770
+
46680
46771
  // src/utils/noteRecentList.ts
46681
46772
  var RECENT_LIMIT = { DEFAULT: 50, MAX: 1e3 };
46682
46773
  var CHECKPOINT_PREFIX = "cdts1:";
@@ -46731,9 +46822,7 @@ function parseSince(input) {
46731
46822
  }
46732
46823
  function textStats(text2) {
46733
46824
  const visible2 = text2.replace(OBJECT_REPLACEMENT, "");
46734
- let wordCount2 = 0;
46735
- for (const token of visible2.split(/\s+/u)) if (/[\p{L}\p{N}]/u.test(token)) wordCount2++;
46736
- return { wordCount: wordCount2, charCount: [...visible2].length };
46825
+ return { wordCount: countWords(visible2), charCount: [...visible2].length };
46737
46826
  }
46738
46827
  function previewText(text2) {
46739
46828
  const flat = text2.replace(OBJECT_REPLACEMENT, " ").replace(/\s+/gu, " ").trim();
@@ -47355,6 +47444,10 @@ var Parser = class {
47355
47444
  function parseNoteQuery(input) {
47356
47445
  return new Parser(tokenize(input), input.length).parse();
47357
47446
  }
47447
+ function noteBodyText(text2) {
47448
+ const firstBreak = text2.indexOf("\n");
47449
+ return firstBreak === -1 ? "" : text2.slice(firstBreak + 1);
47450
+ }
47358
47451
  function normalizeForMatch(text2) {
47359
47452
  return text2.normalize("NFC").replace(/[\ufffc\u00a0]/gu, " ").toLowerCase();
47360
47453
  }
@@ -47429,20 +47522,36 @@ function cheapFirst(children) {
47429
47522
  }
47430
47523
  return ordered;
47431
47524
  }
47432
- function evaluateNoteQuery(node, note) {
47525
+ function evaluateNoteQueryTruth(node, note) {
47433
47526
  switch (node.type) {
47434
- case "and":
47435
- return cheapFirst(node.children).every((child2) => evaluateNoteQuery(child2, note));
47436
- case "or":
47437
- return cheapFirst(node.children).some((child2) => evaluateNoteQuery(child2, note));
47438
- case "not":
47439
- return !evaluateNoteQuery(node.child, note);
47527
+ case "and": {
47528
+ let unknown2 = false;
47529
+ for (const child2 of cheapFirst(node.children)) {
47530
+ const value = evaluateNoteQueryTruth(child2, note);
47531
+ if (value === false) return false;
47532
+ if (value === null) unknown2 = true;
47533
+ }
47534
+ return unknown2 ? null : true;
47535
+ }
47536
+ case "or": {
47537
+ let unknown2 = false;
47538
+ for (const child2 of cheapFirst(node.children)) {
47539
+ const value = evaluateNoteQueryTruth(child2, note);
47540
+ if (value === true) return true;
47541
+ if (value === null) unknown2 = true;
47542
+ }
47543
+ return unknown2 ? null : false;
47544
+ }
47545
+ case "not": {
47546
+ const value = evaluateNoteQueryTruth(node.child, note);
47547
+ return value === null ? null : !value;
47548
+ }
47440
47549
  case "text": {
47441
47550
  const needle = normalizeForMatch(node.value);
47442
47551
  if (node.field === "title") return note.titleLower.includes(needle);
47443
47552
  if (node.field === "any" && note.titleLower.includes(needle)) return true;
47444
47553
  const content = note.content();
47445
- if (!content) return false;
47554
+ if (!content) return null;
47446
47555
  return (node.field === "body" ? content.bodyLower : content.textLower).includes(needle);
47447
47556
  }
47448
47557
  case "folder":
@@ -47455,21 +47564,27 @@ function evaluateNoteQuery(node, note) {
47455
47564
  return compareDate(node.field === "created" ? note.created : note.modified, node);
47456
47565
  case "tag": {
47457
47566
  const content = note.content();
47458
- return Boolean(content?.tags.includes(normalizeForMatch(node.value)));
47567
+ return content ? content.tags.includes(normalizeForMatch(node.value)) : null;
47568
+ }
47569
+ case "has": {
47570
+ const content = note.content();
47571
+ return content ? content.facets.has(node.facet) : null;
47459
47572
  }
47460
- case "has":
47461
- return Boolean(note.content()?.facets.has(node.facet));
47462
47573
  case "checklist": {
47463
47574
  const content = note.content();
47464
- if (!content || content.checklist.total === 0) return false;
47575
+ if (!content) return null;
47576
+ if (content.checklist.total === 0) return false;
47465
47577
  return node.state === "open" ? content.checklist.open > 0 : content.checklist.open === 0;
47466
47578
  }
47467
47579
  case "words": {
47468
47580
  const content = note.content();
47469
- return content ? compare(content.words, node.op, node.value) : false;
47581
+ return content ? compare(content.words, node.op, node.value) : null;
47470
47582
  }
47471
47583
  }
47472
47584
  }
47585
+ function evaluateNoteQuery(node, note) {
47586
+ return evaluateNoteQueryTruth(node, note) === true;
47587
+ }
47473
47588
  function positiveTextPredicates(node, negated = false) {
47474
47589
  switch (node.type) {
47475
47590
  case "and":
@@ -47492,7 +47607,7 @@ function matchLocations(predicates, title, text2) {
47492
47607
  const firstBreak = text2 === null ? -1 : text2.indexOf("\n");
47493
47608
  const titleLower = normalizeForMatch(title);
47494
47609
  const firstLineLower = text2 === null ? "" : normalizeForMatch(firstBreak === -1 ? text2 : text2.slice(0, firstBreak));
47495
- const bodyLower = text2 === null || firstBreak === -1 ? "" : normalizeForMatch(text2.slice(firstBreak + 1));
47610
+ const bodyLower = text2 === null ? "" : normalizeForMatch(noteBodyText(text2));
47496
47611
  let inTitle = false;
47497
47612
  let inBody = false;
47498
47613
  for (const predicate of predicates) {
@@ -47604,13 +47719,6 @@ function decodeBodyHex(hex3) {
47604
47719
  return null;
47605
47720
  }
47606
47721
  }
47607
- function countWords(text2) {
47608
- let count = 0;
47609
- for (const word of text2.replace(/\ufffc/gu, " ").split(/\s+/u)) {
47610
- if (/[\p{L}\p{N}]/u.test(word)) count++;
47611
- }
47612
- return count;
47613
- }
47614
47722
  var REQUIRED_COLUMNS3 = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
47615
47723
  function col2(available, alias, name) {
47616
47724
  return available.has(name) ? `${alias}.${name}` : "NULL";
@@ -47855,7 +47963,6 @@ function runNoteQuery(ast, options = {}) {
47855
47963
  const body2 = decode2();
47856
47964
  if (!body2) return content = null;
47857
47965
  const text2 = body2.text;
47858
- const firstBreak = text2.indexOf("\n");
47859
47966
  const tags = /* @__PURE__ */ new Set();
47860
47967
  for (const [id2, alt] of row.tags ?? []) {
47861
47968
  if (id2 && alt && body2.objectIds.has(id2))
@@ -47865,7 +47972,7 @@ function runNoteQuery(ast, options = {}) {
47865
47972
  if (tags.size) facets.add("tag");
47866
47973
  content = {
47867
47974
  textLower: normalizeForMatch(text2),
47868
- bodyLower: normalizeForMatch(firstBreak === -1 ? "" : text2.slice(firstBreak + 1)),
47975
+ bodyLower: normalizeForMatch(noteBodyText(text2)),
47869
47976
  words: countWords(text2),
47870
47977
  facets,
47871
47978
  checklist: body2.checklist,
@@ -48358,13 +48465,16 @@ var LEGACY_ENTITIES = {
48358
48465
  gt: ">",
48359
48466
  amp: "&"
48360
48467
  };
48468
+ function codePointText(value) {
48469
+ return Number.isSafeInteger(value) && value <= 1114111 ? String.fromCodePoint(value) : "\uFFFD";
48470
+ }
48361
48471
  function comparableVisibleText(html) {
48362
48472
  return html.replace(/<br\s*\/?\s*>/gi, " ").replace(/<[^>]*>/g, (tag) => INLINE_TAG.test(tag) ? "" : " ").replace(
48363
48473
  /&(?:(nbsp|quot|lt|gt|amp);?|apos;|#(\d+);|#x([0-9a-f]+);)/gi,
48364
48474
  (_match, legacy, dec, hex3) => {
48365
48475
  if (legacy) return LEGACY_ENTITIES[legacy.toLowerCase()];
48366
- if (dec) return String.fromCodePoint(Number(dec));
48367
- if (hex3) return String.fromCodePoint(Number.parseInt(hex3, 16));
48476
+ if (dec) return codePointText(Number(dec));
48477
+ if (hex3) return codePointText(Number.parseInt(hex3, 16));
48368
48478
  return "'";
48369
48479
  }
48370
48480
  ).replace(/\s+/g, " ").trim();
@@ -48789,15 +48899,26 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
48789
48899
  const segments = (path10) => JSON.stringify(splitFolderPath(path10).map((part) => part.toLocaleLowerCase()));
48790
48900
  if (request.folder) {
48791
48901
  const wanted = segments(request.folder);
48792
- const accounts = manager.listAccounts().filter((account) => account.defaultFolder);
48793
- if (!accounts.some(
48794
- (account) => manager.listFolders(account.name).some((folder) => segments(folder.name) === wanted)
48795
- ))
48902
+ const accounts = manager.listAccounts().filter(
48903
+ (account) => account.defaultFolder && manager.listFolders(account.name).some((folder) => segments(folder.name) === wanted)
48904
+ );
48905
+ if (!accounts.length)
48796
48906
  throw new Error(
48797
48907
  `Folder "${request.folder}" does not exist; create it with create-folder first. Nothing was created`
48798
48908
  );
48799
- for (const account of accounts)
48800
- manager.assertNotSmartFolderDestination(request.folder, account.name);
48909
+ let smartRefusal;
48910
+ const ordinary = accounts.filter((account) => {
48911
+ try {
48912
+ manager.assertNotSmartFolderDestination(request.folder, account.name);
48913
+ return true;
48914
+ } catch (error2) {
48915
+ if (!(error2 instanceof CodedError && error2.envelope.reason === "smart_folder_destination"))
48916
+ throw error2;
48917
+ smartRefusal ??= error2;
48918
+ return false;
48919
+ }
48920
+ });
48921
+ if (!ordinary.length) throw smartRefusal;
48801
48922
  }
48802
48923
  const defaultFolderNotes = () => new Map(
48803
48924
  manager.listAccounts().flatMap(
@@ -49494,7 +49615,63 @@ function withJsonSchema2020_12(transport2) {
49494
49615
 
49495
49616
  // src/utils/shutdown.ts
49496
49617
  var SHUTDOWN_DRAIN_TIMEOUT_MS = 2e3;
49497
- function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
49618
+ var SHUTDOWN_IN_FLIGHT_TIMEOUT_MS = 3e4;
49619
+ var InFlightRequests = class {
49620
+ open = /* @__PURE__ */ new Set();
49621
+ waiters = [];
49622
+ get count() {
49623
+ return this.open.size;
49624
+ }
49625
+ received(id2) {
49626
+ this.open.add(id2);
49627
+ }
49628
+ /** A response was sent, or the client cancelled the request (no response follows). */
49629
+ settled(id2) {
49630
+ if (!this.open.delete(id2) || this.open.size > 0) return;
49631
+ const waiters = this.waiters;
49632
+ this.waiters = [];
49633
+ for (const waiter of waiters) waiter();
49634
+ }
49635
+ onIdle(listener) {
49636
+ if (this.open.size === 0) listener();
49637
+ else this.waiters.push(listener);
49638
+ }
49639
+ };
49640
+ var isObject2 = (value) => typeof value === "object" && value !== null;
49641
+ var isId = (value) => typeof value === "string" || typeof value === "number";
49642
+ function trackRequests(transport2, requests) {
49643
+ if (typeof transport2.send !== "function") return transport2;
49644
+ let handler;
49645
+ Object.defineProperty(transport2, "onmessage", {
49646
+ configurable: true,
49647
+ enumerable: true,
49648
+ get: () => handler,
49649
+ set: (next) => {
49650
+ handler = next ? (message, extra) => {
49651
+ const m = message;
49652
+ if (typeof m.method === "string") {
49653
+ if (isId(m.id)) requests.received(m.id);
49654
+ else if (m.method === "notifications/cancelled" && isObject2(m.params)) {
49655
+ const id2 = m.params.requestId;
49656
+ if (isId(id2)) requests.settled(id2);
49657
+ }
49658
+ }
49659
+ next(message, extra);
49660
+ } : next;
49661
+ }
49662
+ });
49663
+ const send = transport2.send.bind(transport2);
49664
+ transport2.send = async (message, options) => {
49665
+ try {
49666
+ return await send(message, options);
49667
+ } finally {
49668
+ const m = message;
49669
+ if (typeof m.method !== "string" && isId(m.id)) requests.settled(m.id);
49670
+ }
49671
+ };
49672
+ return transport2;
49673
+ }
49674
+ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS, pending, inFlightTimeoutMs = SHUTDOWN_IN_FLIGHT_TIMEOUT_MS) {
49498
49675
  let shuttingDown = false;
49499
49676
  let exited = false;
49500
49677
  const exitOnce = () => {
@@ -49511,8 +49688,11 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
49511
49688
  return () => {
49512
49689
  if (shuttingDown) return;
49513
49690
  shuttingDown = true;
49514
- timer = setTimeout(exitOnce, timeoutMs);
49515
- setImmediate(exitWhenDrained);
49691
+ const busy = (pending?.count ?? 0) > 0;
49692
+ timer = setTimeout(exitOnce, busy ? Math.max(timeoutMs, inFlightTimeoutMs) : timeoutMs);
49693
+ setImmediate(
49694
+ () => pending ? pending.onIdle(() => setImmediate(exitWhenDrained)) : exitWhenDrained()
49695
+ );
49516
49696
  };
49517
49697
  }
49518
49698
 
@@ -49860,7 +50040,7 @@ function lastViewedOf(raw, columnPresent = true, now = Date.now()) {
49860
50040
  return { lastViewed: new Date(ms).toISOString(), lastViewedStatus: "viewed" };
49861
50041
  }
49862
50042
  var visible = (text2) => text2.replace(/\ufffc/g, "");
49863
- var wordCount = (text2) => visible(text2).split(/\s+/u).filter((word) => word !== "").length;
50043
+ var wordCount = (text2) => countWords(text2);
49864
50044
  var charCount = (text2) => Array.from(visible(text2)).length;
49865
50045
  function noteStructureSql(columns) {
49866
50046
  const c = (alias, name) => col(columns, alias, name);
@@ -50148,7 +50328,7 @@ function inventorySql(columns) {
50148
50328
  ORDER BY d.ZNOTE LIMIT ${BODY_BATCH};`
50149
50329
  };
50150
50330
  }
50151
- function decodeBodies(dbPath2, sql, params) {
50331
+ function decodeBodies(dbPath2, sql, params, notes) {
50152
50332
  const docs = /* @__PURE__ */ new Map();
50153
50333
  let after = 0;
50154
50334
  for (; ; ) {
@@ -50157,7 +50337,7 @@ function decodeBodies(dbPath2, sql, params) {
50157
50337
  );
50158
50338
  for (const row of rows) {
50159
50339
  after = Math.max(after, row.note);
50160
- if (row.encrypted || !row.data) continue;
50340
+ if (row.encrypted || !row.data || !notes.has(row.note)) continue;
50161
50341
  try {
50162
50342
  docs.set(row.note, decodeCompressedNoteBlocks(Buffer.from(row.data, "hex")));
50163
50343
  } catch {
@@ -50205,7 +50385,7 @@ function listNoteLinks(options = {}) {
50205
50385
  if (options.id && !notes.length)
50206
50386
  throw new NoteStoreError(`No note found for ID "${options.id}".`, "invalid_input");
50207
50387
  const includeInline = options.includeInline ?? Boolean(options.id);
50208
- const docs = includeInline ? decodeBodies(dbPath2, sql.bodies, params) : /* @__PURE__ */ new Map();
50388
+ const docs = includeInline ? decodeBodies(dbPath2, sql.bodies, params, new Set(notes.map((note) => note.pk))) : /* @__PURE__ */ new Map();
50209
50389
  const noteById = new Map(notes.map((note) => [note.pk, note]));
50210
50390
  const accountById = new Map(context.accounts.map((account) => [account.pk, account]));
50211
50391
  const folderById = new Map(context.folders.map((folder) => [folder.pk, folder]));
@@ -50520,7 +50700,14 @@ var AssetLocator = class {
50520
50700
  ["FallbackImage.png", "FallbackImage.jpg"]
50521
50701
  );
50522
50702
  if (fallback)
50523
- return { primary: { path: fallback, name: `${source.kind}.png`, role: "fallback" } };
50703
+ return {
50704
+ primary: {
50705
+ path: fallback.path,
50706
+ name: `${source.kind}.png`,
50707
+ role: "fallback",
50708
+ ...fallback.stale ? { stale: true } : {}
50709
+ }
50710
+ };
50524
50711
  return preview ? { primary: preview } : {};
50525
50712
  }
50526
50713
  case "scan": {
@@ -50528,7 +50715,14 @@ var AssetLocator = class {
50528
50715
  "FallbackPDF.pdf"
50529
50716
  ]);
50530
50717
  return {
50531
- ...pdf ? { primary: { path: pdf, name: "scan.pdf", role: "fallback" } } : {},
50718
+ ...pdf ? {
50719
+ primary: {
50720
+ path: pdf.path,
50721
+ name: "scan.pdf",
50722
+ role: "fallback",
50723
+ ...pdf.stale ? { stale: true } : {}
50724
+ }
50725
+ } : {},
50532
50726
  ...preview ? { preview } : {}
50533
50727
  };
50534
50728
  }
@@ -50557,7 +50751,11 @@ var AssetLocator = class {
50557
50751
  }
50558
50752
  return void 0;
50559
50753
  }
50560
- /** <dir>/<id>/<generation>/<name>, <dir>/<id>/<name>, then any generation. */
50754
+ /**
50755
+ * <dir>/<id>/<generation>/<name>, <dir>/<id>/<name>, then any generation.
50756
+ * `stale` is set when a generation was recorded but the file came from
50757
+ * somewhere else.
50758
+ */
50561
50759
  fallback(account, dir, id2, generation, names) {
50562
50760
  const base = join19(account, dir, id2);
50563
50761
  const gen = safeComponent2(generation);
@@ -50569,7 +50767,7 @@ var AssetLocator = class {
50569
50767
  for (const g of generations)
50570
50768
  for (const name of names) {
50571
50769
  const path10 = confine(g ? join19(base, g, name) : join19(base, name), account);
50572
- if (path10 && isFile(path10)) return path10;
50770
+ if (path10 && isFile(path10)) return { path: path10, stale: Boolean(gen) && g !== gen };
50573
50771
  }
50574
50772
  return void 0;
50575
50773
  }
@@ -50726,6 +50924,9 @@ function readHead(fd) {
50726
50924
  const n = readSync3(fd, head, 0, 16, 0);
50727
50925
  return head.subarray(0, n);
50728
50926
  }
50927
+ function directoryFailure(dir, error2) {
50928
+ return `Could not create the assets directory ${dir}: ${error2 instanceof Error ? error2.message : String(error2)}`;
50929
+ }
50729
50930
  var SidecarWriter = class {
50730
50931
  constructor(dir, linkBase) {
50731
50932
  this.dir = dir;
@@ -50736,9 +50937,16 @@ var SidecarWriter = class {
50736
50937
  placed = /* @__PURE__ */ new Map();
50737
50938
  created = false;
50738
50939
  count = 0;
50940
+ /**
50941
+ * Set when the directory could not be created. Every later asset fails the
50942
+ * same way without another attempt; the export checks this and fails
50943
+ * instead of reporting each asset as unavailable.
50944
+ */
50945
+ directoryError;
50739
50946
  place(asset) {
50740
50947
  const done = this.placed.get(asset.path);
50741
50948
  if (done) return done;
50949
+ if (this.directoryError) return { error: this.directoryError };
50742
50950
  let source;
50743
50951
  try {
50744
50952
  source = openSource(asset.path);
@@ -50748,8 +50956,13 @@ var SidecarWriter = class {
50748
50956
  try {
50749
50957
  const mime = sniffMime(readHead(source.fd), asset.name);
50750
50958
  if (!this.created) {
50751
- assertExportPath(this.dir);
50752
- mkdirSync4(this.dir, { recursive: true });
50959
+ try {
50960
+ assertExportPath(this.dir);
50961
+ mkdirSync4(this.dir, { recursive: true });
50962
+ } catch (error2) {
50963
+ this.directoryError = directoryFailure(this.dir, error2);
50964
+ return { error: this.directoryError };
50965
+ }
50753
50966
  this.created = true;
50754
50967
  }
50755
50968
  const name = safeAssetName(asset.name, mime);
@@ -50902,7 +51115,10 @@ function titleBlockIndex(note) {
50902
51115
  return first2.text.trim() === note.title.trim() && first2.style !== "monospaced" ? first2.index : -1;
50903
51116
  }
50904
51117
  function place(ctx, asset) {
50905
- return asset && ctx.writer ? ctx.writer.place(asset) : void 0;
51118
+ const placed = asset && ctx.writer ? ctx.writer.place(asset) : void 0;
51119
+ if (asset?.stale && placed && "url" in placed)
51120
+ ctx.stats.staleRenderings = (ctx.stats.staleRenderings ?? 0) + 1;
51121
+ return placed;
50906
51122
  }
50907
51123
  function planAttachment(attachment, ctx) {
50908
51124
  ctx.stats.attachments++;
@@ -51068,6 +51284,7 @@ function inlinePlanHtml(plan) {
51068
51284
  }
51069
51285
  }
51070
51286
  function blockPlanHtml(plan) {
51287
+ if (!isHtmlBlockPlan(plan)) return `<p>${inlinePlanHtml(plan)}</p>`;
51071
51288
  switch (plan.type) {
51072
51289
  case "table":
51073
51290
  return tableHtml(plan.rows);
@@ -51935,7 +52152,7 @@ function child(path10, key) {
51935
52152
  if (key.length > MAX_QUOTED_TOKEN) key = `${key.slice(0, MAX_QUOTED_TOKEN - 3)}...`;
51936
52153
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path10}.${key}` : `${path10}[${JSON.stringify(key)}]`;
51937
52154
  }
51938
- var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
52155
+ var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
51939
52156
  var describe = (value) => value === null ? "null" : Array.isArray(value) ? "an array" : `a ${typeof value}`;
51940
52157
  var describeVersion = (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : describe(value);
51941
52158
  var listed = (values) => values.map((v) => JSON.stringify(v)).join(", ");
@@ -52016,7 +52233,7 @@ var RULE_FIELDS = {
52016
52233
  omit: []
52017
52234
  };
52018
52235
  function validateRule(c, rule, path10) {
52019
- if (!isObject2(rule)) {
52236
+ if (!isObject3(rule)) {
52020
52237
  c.add(path10, `must be an object, not ${describe(rule)}`);
52021
52238
  return;
52022
52239
  }
@@ -52063,7 +52280,7 @@ function validateInlineOrder(c, value, path10) {
52063
52280
  if (missing.length) c.add(path10, `must list every inline format; missing ${listed(missing)}`);
52064
52281
  }
52065
52282
  function validateOptions(c, value, path10) {
52066
- if (!isObject2(value)) {
52283
+ if (!isObject3(value)) {
52067
52284
  c.add(path10, `must be an object, not ${describe(value)}`);
52068
52285
  return;
52069
52286
  }
@@ -52084,7 +52301,7 @@ function validateOptions(c, value, path10) {
52084
52301
  }
52085
52302
  function templateErrors(input) {
52086
52303
  const c = new Collector();
52087
- if (!isObject2(input)) {
52304
+ if (!isObject3(input)) {
52088
52305
  c.add("$", `a template must be a JSON object, not ${describe(input)}`);
52089
52306
  return c.errors;
52090
52307
  }
@@ -52119,7 +52336,7 @@ function templateErrors(input) {
52119
52336
  c.oneOf(value, path10, BUILTIN_TEMPLATE_NAMES);
52120
52337
  break;
52121
52338
  case "assets":
52122
- if (!isObject2(value)) {
52339
+ if (!isObject3(value)) {
52123
52340
  c.add(path10, `must be an object, not ${describe(value)}`);
52124
52341
  break;
52125
52342
  }
@@ -52135,7 +52352,7 @@ function templateErrors(input) {
52135
52352
  validateInlineOrder(c, value, path10);
52136
52353
  break;
52137
52354
  case "rules":
52138
- if (!isObject2(value)) {
52355
+ if (!isObject3(value)) {
52139
52356
  c.add(path10, `must be an object keyed by rule id, not ${describe(value)}`);
52140
52357
  break;
52141
52358
  }
@@ -52765,16 +52982,26 @@ var HashedSidecarWriter = class {
52765
52982
  /** Absolute paths of files written or reused. */
52766
52983
  files = [];
52767
52984
  count = 0;
52985
+ /** Set when the directory could not be created; see SidecarWriter.directoryError. */
52986
+ directoryError;
52987
+ /** Creates the directory once; returns an error message when it cannot. */
52768
52988
  prepare() {
52769
- if (this.ready) return;
52770
- assertExportPath(this.dir);
52771
- mkdirSync5(this.dir, { recursive: true });
52772
- if (!lstatSync4(this.dir).isDirectory()) throw new Error("assets directory is not a directory");
52989
+ if (this.ready) return void 0;
52990
+ try {
52991
+ assertExportPath(this.dir);
52992
+ mkdirSync5(this.dir, { recursive: true });
52993
+ if (!lstatSync4(this.dir).isDirectory())
52994
+ throw new Error("assets directory is not a directory");
52995
+ } catch (error2) {
52996
+ return this.directoryError = directoryFailure(this.dir, error2);
52997
+ }
52773
52998
  this.ready = true;
52999
+ return void 0;
52774
53000
  }
52775
53001
  place(asset) {
52776
53002
  const done = this.placed.get(asset.path);
52777
53003
  if (done) return done;
53004
+ if (this.directoryError) return { error: this.directoryError };
52778
53005
  let source;
52779
53006
  try {
52780
53007
  source = openRegular(asset.path);
@@ -52784,7 +53011,8 @@ var HashedSidecarWriter = class {
52784
53011
  try {
52785
53012
  const { hash, head } = digest(source);
52786
53013
  const mime = sniffMime(head, asset.name);
52787
- this.prepare();
53014
+ const directoryError = this.prepare();
53015
+ if (directoryError) return { error: directoryError };
52788
53016
  const name = safeAssetName(asset.name, mime);
52789
53017
  const ext = extname4(name);
52790
53018
  const target = join21(
@@ -53014,10 +53242,11 @@ function validPath(path10, what) {
53014
53242
  function selectNotes(request, deps) {
53015
53243
  if (!!request.id === !!request.folder)
53016
53244
  throw new NotesExportError("invalid-request", "Provide exactly one of 'id' or 'folder'.");
53017
- if (request.id) return [request.id];
53245
+ if (request.id) return { ids: [request.id], truncated: false };
53018
53246
  const limit = Math.min(request.limit ?? DEFAULT_FOLDER_EXPORT_LIMIT, MAX_FOLDER_EXPORT_LIMIT);
53019
53247
  try {
53020
- return deps.listNoteRefs(request.account, request.folder, void 0, limit).map((ref) => ref.id);
53248
+ const ids = deps.listNoteRefs(request.account, request.folder, void 0, limit + 1).map((ref) => ref.id);
53249
+ return { ids: ids.slice(0, limit), truncated: ids.length > limit };
53021
53250
  } catch (error2) {
53022
53251
  throw new NotesExportError(
53023
53252
  "folder-unavailable",
@@ -53049,9 +53278,13 @@ function openOutput(output) {
53049
53278
  throw error2;
53050
53279
  }
53051
53280
  }
53052
- function renderInto(fd, render) {
53281
+ function renderInto(fd, render, writers = []) {
53053
53282
  try {
53054
- return render();
53283
+ const document = render();
53284
+ const directoryError = writers.find((writer) => writer.directoryError)?.directoryError;
53285
+ if (directoryError)
53286
+ throw new NotesExportError("invalid-path", `${directoryError}. Nothing was exported.`);
53287
+ return document;
53055
53288
  } catch (error2) {
53056
53289
  if (fd !== void 0) writeAllAndClose(fd, "");
53057
53290
  throw error2;
@@ -53068,10 +53301,15 @@ function exportNotesMarkdown(request, deps) {
53068
53301
  "invalid-request",
53069
53302
  `assetsDir has no effect: template "${chosen.info.name}" sets assets.mode to "${chosen.template.assets.mode}".`
53070
53303
  );
53071
- const ids = selectNotes(request, deps);
53304
+ const { ids, truncated } = selectNotes(request, deps);
53072
53305
  const { notes, skipped } = loadNotes(ids, !!request.id, deps.readNote);
53073
- if (chosen)
53074
- return exportWithTemplate(request, deps, chosen, notes, skipped, { output, assetsDir });
53306
+ if (chosen) {
53307
+ const receipt2 = exportWithTemplate(request, deps, chosen, notes, skipped, {
53308
+ output,
53309
+ assetsDir
53310
+ });
53311
+ return truncated ? { ...receipt2, truncated } : receipt2;
53312
+ }
53075
53313
  const fd = output ? openOutput(output) : void 0;
53076
53314
  const writer = assetsDir ? new SidecarWriter(assetsDir, output ? dirname6(output) : void 0) : void 0;
53077
53315
  const ctx = {
@@ -53080,12 +53318,14 @@ function exportNotesMarkdown(request, deps) {
53080
53318
  };
53081
53319
  const markdown = renderInto(
53082
53320
  fd,
53083
- () => renderNotesMarkdown(notes, ctx, { wrap: request.wrap ?? 0 })
53321
+ () => renderNotesMarkdown(notes, ctx, { wrap: request.wrap ?? 0 }),
53322
+ writer ? [writer] : []
53084
53323
  );
53085
53324
  const bytes = Buffer.byteLength(markdown);
53086
53325
  const receipt = {
53087
53326
  format: "markdown",
53088
53327
  count: notes.length,
53328
+ ...truncated ? { truncated } : {},
53089
53329
  bytes,
53090
53330
  stats: ctx.stats,
53091
53331
  skipped,
@@ -53121,7 +53361,7 @@ function exportNotesHtml(request, deps) {
53121
53361
  const assetsDir = embed ? void 0 : validPath(request.assetsDir ?? defaultSidecarDir(output), "assetsDir");
53122
53362
  if (assetsDir === output)
53123
53363
  throw new NotesExportError("invalid-path", "outputPath and assetsDir must differ.");
53124
- const ids = selectNotes(request, deps);
53364
+ const { ids, truncated } = selectNotes(request, deps);
53125
53365
  const { notes, skipped } = loadNotes(ids, !!request.id, deps.readNote);
53126
53366
  const fd = openOutput(output);
53127
53367
  const writer = assetsDir ? new SidecarWriter(assetsDir, dirname6(output)) : new DataUrlWriter();
@@ -53131,11 +53371,16 @@ function exportNotesHtml(request, deps) {
53131
53371
  locator: deps.locator ?? new AssetLocator()
53132
53372
  };
53133
53373
  const title = request.id ? notes[0]?.title || "Note" : request.folder;
53134
- const html = renderInto(fd, () => renderNotesHtml(notes, ctx, { title }));
53374
+ const html = renderInto(
53375
+ fd,
53376
+ () => renderNotesHtml(notes, ctx, { title }),
53377
+ writer instanceof SidecarWriter ? [writer] : []
53378
+ );
53135
53379
  const bytes = writeAllAndClose(fd, html);
53136
53380
  return {
53137
53381
  format: "html",
53138
53382
  count: notes.length,
53383
+ ...truncated ? { truncated } : {},
53139
53384
  bytes,
53140
53385
  output,
53141
53386
  stats: ctx.stats,
@@ -53217,7 +53462,7 @@ function exportWithTemplate(request, deps, chosen, notes, skipped, { output, ass
53217
53462
  });
53218
53463
  warnings = result.warnings;
53219
53464
  return result.markdown;
53220
- });
53465
+ }, [...writers.values()]);
53221
53466
  const bytes = Buffer.byteLength(markdown);
53222
53467
  const used = [...writers.values()].filter((writer) => writer.count > 0);
53223
53468
  const files = used.flatMap((writer) => writer.files);
@@ -53258,7 +53503,6 @@ import {
53258
53503
  fstatSync as fstatSync7,
53259
53504
  mkdtempSync as mkdtempSync6,
53260
53505
  openSync as openSync7,
53261
- readFileSync as readFileSync4,
53262
53506
  readSync as readSync5,
53263
53507
  rmSync as rmSync6,
53264
53508
  writeFileSync as writeFileSync5
@@ -53284,12 +53528,12 @@ import { basename as basename5, extname as extname6, join as join23 } from "node
53284
53528
  var PASTEBOARD_NAME_ENV = "APPLE_NOTES_MCP_PASTEBOARD_NAME";
53285
53529
  var MAX_PASTEBOARD_BYTES = 64 * 1024 * 1024;
53286
53530
  var PASTEBOARD_DATA_TYPES = [
53531
+ { type: "com.adobe.pdf", ext: "pdf", label: "document" },
53287
53532
  { type: "public.png", ext: "png", label: "image" },
53288
53533
  { type: "public.jpeg", ext: "jpg", label: "image" },
53289
53534
  { type: "public.heic", ext: "heic", label: "image" },
53290
53535
  { type: "com.compuserve.gif", ext: "gif", label: "image" },
53291
- { type: "public.tiff", ext: "tiff", label: "image" },
53292
- { type: "com.adobe.pdf", ext: "pdf", label: "document" }
53536
+ { type: "public.tiff", ext: "tiff", label: "image" }
53293
53537
  ];
53294
53538
  var PASTEBOARD_ACCESS_BEHAVIORS = {
53295
53539
  0: "default",
@@ -53316,15 +53560,26 @@ function run(argv) {
53316
53560
  var items = pb.pasteboardItems;
53317
53561
  var itemCount = items && !items.isNil() ? Number(items.count) : 0;
53318
53562
  var fileUrls = [];
53563
+ var dataItems = 0;
53319
53564
  for (var k = 0; k < itemCount; k++) {
53320
53565
  var item = items.objectAtIndex(k);
53321
53566
  var itemTypes = ObjC.deepUnwrap(item.types) || [];
53322
- if (itemTypes.indexOf("public.file-url") < 0) continue;
53567
+ if (itemTypes.indexOf("public.file-url") < 0) {
53568
+ for (var j = 0; j < prefs.length; j++) {
53569
+ if (itemTypes.indexOf(prefs[j][0]) >= 0) {
53570
+ dataItems++;
53571
+ break;
53572
+ }
53573
+ }
53574
+ continue;
53575
+ }
53323
53576
  var value = ObjC.unwrap(item.stringForType("public.file-url"));
53324
53577
  if (value) fileUrls.push(value);
53325
53578
  }
53326
53579
  if (fileUrls.length > 1)
53327
53580
  return JSON.stringify({ status: "error", code: "multiple_files", count: fileUrls.length });
53581
+ if (fileUrls.length === 0 && dataItems > 1)
53582
+ return JSON.stringify({ status: "error", code: "multiple_items", count: dataItems });
53328
53583
  if (fileUrls.length === 1) {
53329
53584
  var url = $.NSURL.URLWithString(fileUrls[0]);
53330
53585
  if (url && !url.isNil() && url.isFileURL) {
@@ -53355,6 +53610,7 @@ var ENVELOPE_CODES = {
53355
53610
  pasteboard_changed: "operation_failed",
53356
53611
  unsupported_content: "validation_error",
53357
53612
  multiple_files: "validation_error",
53613
+ multiple_items: "validation_error",
53358
53614
  too_large: "validation_error",
53359
53615
  write_failed: "operation_failed",
53360
53616
  file_unreadable: "validation_error"
@@ -53381,6 +53637,7 @@ var MESSAGES = {
53381
53637
  pasteboard_changed: "The pasteboard changed while it was being read. Try again.",
53382
53638
  unsupported_content: `The pasteboard holds no supported content: copy ${SUPPORTED}. Text belongs in append-to-note, not in an attachment.`,
53383
53639
  multiple_files: "The pasteboard holds more than one copied file. Copy exactly one file, or attach each file with add-attachment.",
53640
+ multiple_items: "The pasteboard holds more than one image or PDF. Copy exactly one, or save each and attach it with add-attachment.",
53384
53641
  too_large: "The pasteboard contents exceed the 64 MiB attachment limit.",
53385
53642
  write_failed: "Could not write the pasteboard contents to a temporary file.",
53386
53643
  file_unreadable: "The copied file could not be read as a regular file of at most 64 MiB."
@@ -53392,13 +53649,10 @@ function replyError(code, reply) {
53392
53649
  const message = accessBehavior === "alwaysDeny" ? "macOS is set to deny pasteboard access to the app that runs this server, so nothing was read. Change it to allow in System Settings, then try again." : MESSAGES.pasteboard_access_denied;
53393
53650
  return new PasteboardError(code, message, { accessBehavior });
53394
53651
  }
53395
- if (code === "multiple_files") {
53652
+ if (code === "multiple_files" || code === "multiple_items") {
53396
53653
  const count = typeof reply.count === "number" ? reply.count : void 0;
53397
- return new PasteboardError(
53398
- code,
53399
- count ? `The pasteboard holds ${count} copied files. Copy exactly one file, or attach each file with add-attachment.` : MESSAGES[code],
53400
- count ? { count } : {}
53401
- );
53654
+ const counted = code === "multiple_files" ? `The pasteboard holds ${count} copied files. Copy exactly one file, or attach each file with add-attachment.` : `The pasteboard holds ${count} images or PDFs. Copy exactly one, or save each and attach it with add-attachment.`;
53655
+ return new PasteboardError(code, count ? counted : MESSAGES[code], count ? { count } : {});
53402
53656
  }
53403
53657
  if (code === "unsupported_content" && Array.isArray(reply.types)) {
53404
53658
  const types = reply.types.filter((t) => typeof t === "string").slice(0, 20);
@@ -53538,17 +53792,14 @@ function assertExistingContentPreserved(before, after) {
53538
53792
  }
53539
53793
  function localAttachment(path10) {
53540
53794
  if (!isAbsolute4(path10)) throw new Error("An absolute local file path is required");
53541
- const descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
53542
53795
  try {
53543
- const stat = fstatSync7(descriptor);
53544
- if (!stat.isFile() || stat.size === 0 || stat.size > MAX_ADD_ATTACHMENT_BYTES)
53545
- throw new Error("Attachment must be a nonempty regular file of at most 64 MiB");
53546
- const bytes = readFileSync4(descriptor);
53547
- if (bytes.length !== stat.size)
53548
- throw new Error("Attachment changed while it was being read; try again");
53549
- return bytes;
53550
- } finally {
53551
- closeSync7(descriptor);
53796
+ return readAllowedFile(path10, MAX_ADD_ATTACHMENT_BYTES, { label: "Attachment" });
53797
+ } catch (error2) {
53798
+ throw new CodedError(error2 instanceof Error ? error2.message : String(error2), {
53799
+ code: "validation_error",
53800
+ committed: false,
53801
+ indeterminate: false
53802
+ });
53552
53803
  }
53553
53804
  }
53554
53805
  var MAX_ADD_ATTACHMENT_BYTES = 64 * 1024 * 1024;
@@ -53614,7 +53865,10 @@ function registerDirectOperations(server2, manager) {
53614
53865
  {
53615
53866
  id: folderId,
53616
53867
  expectedName: external_exports.string().max(1e3),
53617
- expectedParentId: external_exports.string().max(2e3),
53868
+ // Same forms as id, so list-folders' parentIdentifier (a folder UUID)
53869
+ // works; a top-level folder's parent is its account, passed as the
53870
+ // account's x-coredata id.
53871
+ expectedParentId: folderId,
53618
53872
  newName: external_exports.string().min(1).max(1e3)
53619
53873
  },
53620
53874
  (args) => ({
@@ -53623,20 +53877,22 @@ function registerDirectOperations(server2, manager) {
53623
53877
  })
53624
53878
  );
53625
53879
  const attachmentInput = {
53626
- path: external_exports.string().min(1).max(4096).describe("Absolute path of the local file to attach"),
53880
+ path: external_exports.string().min(1).max(4096).describe(
53881
+ "Absolute path of the local regular file to attach, in home, temp or /Volumes; hidden paths and ~/Library (except iCloud Drive and CloudStorage) are refused"
53882
+ ),
53627
53883
  filename: external_exports.string().min(1).max(255).optional().describe(
53628
53884
  "Name the attachment gets in Notes instead of the source file's name. One path component with the source file's extension; no slash, colon, or control characters, and no leading dot."
53629
53885
  )
53630
53886
  };
53631
53887
  tool(
53632
53888
  "add-attachment",
53633
- "Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, attachment name, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes. On macOS 27 Notes' AppleScript does not list PDF attachments; with Full Disk Access the new attachment is verified through the read-only NoteStore database instead (verifiedBy: database), otherwise the outcome is reported as uncertain.",
53889
+ "Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, attachment name, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: reads only a regular file in home, temp or /Volumes, refusing hidden paths (~/.ssh, .env) and ~/Library outside iCloud Drive and CloudStorage unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes. On macOS 27 Notes' AppleScript does not list PDF attachments; with Full Disk Access the new attachment is verified through the read-only NoteStore database instead (verifiedBy: database), otherwise the outcome is reported as uncertain.",
53634
53890
  { id: noteId, expectedContentHash: revision, ...attachmentInput },
53635
53891
  (args) => attachFile(manager, args)
53636
53892
  );
53637
53893
  tool(
53638
53894
  "create-note-with-attachment",
53639
- "Use when: creating a new note that holds one local file, in one call.\nReturns: the new note id plus the add-attachment result (attachment id, bytes, name, content hash).\nDo not use when: the note already exists (add-attachment).\nSafety: checks the file and filename before creating anything, creates the note through Notes.app like create-note, then attaches with the same byte verification as add-attachment. If the attachment step fails before the file is inserted, the error names the new note's id; attach to it with add-attachment instead of creating another note. If insertion started but could not be verified, the outcome is uncertain: read the note with list-attachments before attaching again.",
53895
+ "Use when: creating a new note that holds one local file, in one call.\nReturns: the new note id plus the add-attachment result (attachment id, bytes, name, content hash).\nDo not use when: the note already exists (add-attachment).\nSafety: checks the file and filename before creating anything, with the same read scope as add-attachment (home, temp or /Volumes; no hidden paths or ~/Library outside iCloud Drive and CloudStorage), creates the note through Notes.app like create-note, then attaches with the same byte verification as add-attachment. If the attachment step fails before the file is inserted, the error names the new note's id; attach to it with add-attachment instead of creating another note. If insertion started but could not be verified, the outcome is uncertain: read the note with list-attachments before attaching again.",
53640
53896
  {
53641
53897
  title: external_exports.string().min(1).max(2e3).refine((s) => !/[\r\n\0]/u.test(s), "One-line title"),
53642
53898
  content: external_exports.string().min(1).max(1024 * 1024).optional().describe("Optional plain-text body placed above the attachment"),
@@ -54015,7 +54271,9 @@ function readFolderStoreFacts(pk, dbPath2 = NOTE_STORE_PATH) {
54015
54271
 
54016
54272
  // src/tools/folderDelete.ts
54017
54273
  var folderIdSchema = external_exports.string().max(2e3).transform(looseIdTransform("ICFolder"));
54018
- var accountIdSchema = external_exports.string().max(2e3).regex(/^x-coredata:\/\/[0-9a-f-]+\/ICAccount\/p\d+$/i, "An exact account ID is required");
54274
+ var accountIdSchema = external_exports.string().max(2e3).transform(looseIdTransform("ICAccount")).pipe(
54275
+ external_exports.string().regex(/^x-coredata:\/\/[0-9a-f-]+\/ICAccount\/p\d+$/i, "An exact account ID is required")
54276
+ );
54019
54277
  var revisionSchema = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
54020
54278
  var defaultDeps = {
54021
54279
  readStore: (pk) => readFolderStoreFacts(pk),
@@ -57408,7 +57666,7 @@ import {
57408
57666
  existsSync as existsSync14,
57409
57667
  mkdirSync as mkdirSync8,
57410
57668
  mkdtempSync as mkdtempSync7,
57411
- readFileSync as readFileSync5,
57669
+ readFileSync as readFileSync4,
57412
57670
  renameSync as renameSync2,
57413
57671
  rmSync as rmSync7,
57414
57672
  writeFileSync as writeFileSync6
@@ -57445,7 +57703,7 @@ function packageRoot(fromDir = dirname8(fileURLToPath2(import.meta.url))) {
57445
57703
  const candidate = join27(dir, "package.json");
57446
57704
  if (existsSync14(candidate)) {
57447
57705
  try {
57448
- if (JSON.parse(readFileSync5(candidate, "utf8")).name === "apple-notes-mcp")
57706
+ if (JSON.parse(readFileSync4(candidate, "utf8")).name === "apple-notes-mcp")
57449
57707
  return dir;
57450
57708
  } catch {
57451
57709
  }
@@ -57461,7 +57719,7 @@ function defaultPublicHelperDeps(overrides = {}) {
57461
57719
  platform: process.platform,
57462
57720
  sourcePath: join27(packageRoot(), PUBLIC_HELPER_SOURCE),
57463
57721
  exists: existsSync14,
57464
- readFile: (path10) => readFileSync5(path10),
57722
+ readFile: (path10) => readFileSync4(path10),
57465
57723
  spawn: spawnSync3,
57466
57724
  spawnAsync: spawn,
57467
57725
  ...overrides
@@ -58000,14 +58258,17 @@ var strokeSchema = external_exports.object({
58000
58258
  force: external_exports.number()
58001
58259
  })
58002
58260
  ).optional(),
58003
- transformApplied: external_exports.boolean().optional()
58261
+ transformApplied: external_exports.boolean().optional(),
58262
+ masked: external_exports.boolean().optional(),
58263
+ pointsTruncated: external_exports.boolean().optional()
58004
58264
  }).strip();
58005
58265
  var decodedDrawingSchema = external_exports.object({
58006
58266
  status: external_exports.literal("ok"),
58007
58267
  strokeCount: external_exports.number().int(),
58008
58268
  strokes: external_exports.array(strokeSchema),
58009
58269
  truncated: external_exports.boolean(),
58010
- bounds
58270
+ bounds,
58271
+ hiddenStrokeCount: external_exports.number().int().optional()
58011
58272
  });
58012
58273
  function decodeRow(row, format, includePoints, deps) {
58013
58274
  const base = {
@@ -58040,7 +58301,8 @@ function decodeRow(row, format, includePoints, deps) {
58040
58301
  status: "ok",
58041
58302
  strokeCount: decoded.strokeCount,
58042
58303
  bounds: decoded.bounds,
58043
- truncated: decoded.truncated
58304
+ truncated: decoded.truncated,
58305
+ ...decoded.hiddenStrokeCount ? { hiddenStrokeCount: decoded.hiddenStrokeCount } : {}
58044
58306
  };
58045
58307
  if (format !== "json") result.svg = drawingToSvg(strokes, decoded.bounds);
58046
58308
  if (format !== "svg")
@@ -58065,6 +58327,28 @@ function getNoteDrawings(noteId3, options = {}) {
58065
58327
  drawings
58066
58328
  };
58067
58329
  }
58330
+ function fitNoteDrawings(result, maxBytes, measure = (r) => Buffer.byteLength(JSON.stringify(r))) {
58331
+ let next = result;
58332
+ let pointsOmitted = false;
58333
+ let svgOmitted = false;
58334
+ if (measure(next) > maxBytes && next.drawings.some((d) => d.strokes?.some((s) => s.points))) {
58335
+ next = {
58336
+ ...next,
58337
+ drawings: next.drawings.map(
58338
+ (d) => d.strokes ? { ...d, strokes: d.strokes.map(({ points: _points, ...rest }) => rest) } : d
58339
+ )
58340
+ };
58341
+ pointsOmitted = true;
58342
+ }
58343
+ if (measure(next) > maxBytes && next.drawings.some((d) => d.svg !== void 0)) {
58344
+ next = {
58345
+ ...next,
58346
+ drawings: next.drawings.map(({ svg: _svg, ...rest }) => rest)
58347
+ };
58348
+ svgOmitted = true;
58349
+ }
58350
+ return { result: next, pointsOmitted, svgOmitted, oversized: measure(next) > maxBytes };
58351
+ }
58068
58352
  function formatNoteDrawings(result) {
58069
58353
  if (result.drawingCount === 0) return `No classic PencilKit drawings in note ${result.id}.`;
58070
58354
  const lines = [
@@ -58072,7 +58356,7 @@ function formatNoteDrawings(result) {
58072
58356
  ];
58073
58357
  for (const d of result.drawings)
58074
58358
  lines.push(
58075
- d.status === "ok" ? `- ${d.attachmentId}: ${d.strokeCount} stroke${d.strokeCount === 1 ? "" : "s"}${d.truncated ? " (truncated)" : ""}` : `- ${d.attachmentId}: error ${d.code}: ${d.message}`
58359
+ d.status === "ok" ? `- ${d.attachmentId}: ${d.strokeCount} stroke${d.strokeCount === 1 ? "" : "s"}${d.hiddenStrokeCount ? ` (${d.hiddenStrokeCount} fully erased)` : ""}${d.truncated ? " (truncated)" : ""}` : `- ${d.attachmentId}: error ${d.code}: ${d.message}`
58076
58360
  );
58077
58361
  return lines.join("\n");
58078
58362
  }
@@ -58172,9 +58456,6 @@ function readAudioAssets(noteId3, options = {}) {
58172
58456
  };
58173
58457
  });
58174
58458
  }
58175
- function countWords2(text2) {
58176
- return text2.split(/\s+/u).filter((w) => /[\p{L}\p{N}]/u.test(w)).length;
58177
- }
58178
58459
 
58179
58460
  // src/services/noteTranscription.ts
58180
58461
  var DEFAULT_TRANSCRIPTION_LOCALE = "en-US";
@@ -58189,6 +58470,7 @@ var BUDGET_MARGIN_MS = 1e4;
58189
58470
  var MIN_TAKE_SECONDS = 5;
58190
58471
  var CALL_WIDE_CODES = /* @__PURE__ */ new Set([
58191
58472
  "permission_required",
58473
+ "permission_not_requested",
58192
58474
  "asset_unavailable",
58193
58475
  "unsupported_locale",
58194
58476
  "speech_unavailable"
@@ -58267,7 +58549,7 @@ async function transcribeTake(take, ctx) {
58267
58549
  status: complete ? "ok" : text2 ? "partial" : "indeterminate",
58268
58550
  ...complete ? {} : { code: "incomplete", message: stopReason ?? "stopped early" },
58269
58551
  ...durationSeconds !== void 0 ? { durationSeconds } : {},
58270
- wordCount: countWords2(text2),
58552
+ wordCount: countWords(text2),
58271
58553
  ...engine ? { engine } : {}
58272
58554
  },
58273
58555
  text: text2
@@ -58294,7 +58576,7 @@ async function transcribeRecording(asset, includeText, ctx) {
58294
58576
  status,
58295
58577
  ...status !== "ok" && firstProblem?.code ? { code: firstProblem.code, message: firstProblem.message } : {},
58296
58578
  ...asset.durationSeconds !== null ? { durationSeconds: Math.round(asset.durationSeconds) } : {},
58297
- wordCount: countWords2(transcript),
58579
+ wordCount: countWords(transcript),
58298
58580
  ...includeText ? { transcript } : {},
58299
58581
  takes: outcomes.map((o) => o.take)
58300
58582
  };
@@ -58349,24 +58631,29 @@ async function transcribeNoteAudio(noteId3, options = {}) {
58349
58631
  }
58350
58632
  function fitTranscriptions(result, maxBytes, measure = (r) => Buffer.byteLength(JSON.stringify(r))) {
58351
58633
  if (measure(result) <= maxBytes) return result;
58634
+ const shorten = (share) => ({
58635
+ ...result,
58636
+ recordings: result.recordings.map(
58637
+ (r) => r.transcript ? {
58638
+ ...r,
58639
+ transcript: r.transcript.slice(0, Math.floor(r.transcript.length * share)),
58640
+ transcriptTruncated: true
58641
+ } : r
58642
+ )
58643
+ });
58352
58644
  let next = result;
58353
58645
  for (let share = 0.5; measure(next) > maxBytes && share > 1e-4; share /= 2) {
58354
- next = {
58355
- ...result,
58356
- recordings: result.recordings.map(
58357
- (r) => r.transcript ? {
58358
- ...r,
58359
- transcript: r.transcript.slice(0, Math.floor(r.transcript.length * share)),
58360
- transcriptTruncated: true
58361
- } : r
58362
- )
58363
- };
58646
+ next = shorten(share);
58364
58647
  }
58648
+ if (measure(next) > maxBytes) next = { ...shorten(0), responseOversized: true };
58365
58649
  return next;
58366
58650
  }
58367
58651
  function formatTranscription(result) {
58368
58652
  if (result.recordingCount === 0) return `No audio attachments in note ${result.id}.`;
58369
58653
  const lines = [
58654
+ ...result.responseOversized ? [
58655
+ "The transcripts were dropped: even without them the response exceeds APPLE_NOTES_MCP_EXPORT_MAX_BYTES. Transcribe one recording at a time with attachmentId."
58656
+ ] : [],
58370
58657
  `${result.recordingCount} audio attachment${result.recordingCount === 1 ? "" : "s"} in note ${result.id} (${result.status}, locale ${result.locale}):`
58371
58658
  ];
58372
58659
  result.recordings.forEach((r, i) => {
@@ -58401,7 +58688,7 @@ import { join as join30 } from "node:path";
58401
58688
  // src/services/privateHelper.ts
58402
58689
  import { spawnSync as spawnSync4 } from "node:child_process";
58403
58690
  import { createHash as createHash7 } from "node:crypto";
58404
- import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
58691
+ import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
58405
58692
  import { homedir as homedir21 } from "node:os";
58406
58693
  import { dirname as dirname9, join as join29, resolve as resolve8 } from "node:path";
58407
58694
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -58434,7 +58721,7 @@ function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
58434
58721
  const candidate = join29(dir, "package.json");
58435
58722
  if (existsSync15(candidate)) {
58436
58723
  try {
58437
- const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
58724
+ const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
58438
58725
  if (pkg.name === "apple-notes-mcp") return dir;
58439
58726
  } catch {
58440
58727
  }
@@ -58450,7 +58737,7 @@ function defaultDeps2(overrides = {}) {
58450
58737
  platform: process.platform,
58451
58738
  sourcePath: join29(packageRoot2(), HELPER_SOURCE_RELATIVE),
58452
58739
  exists: existsSync15,
58453
- readFile: (path10) => readFileSync6(path10),
58740
+ readFile: (path10) => readFileSync5(path10),
58454
58741
  spawn: spawnSync4,
58455
58742
  ...overrides
58456
58743
  };
@@ -59238,10 +59525,11 @@ function prepareDeleteGuards(args) {
59238
59525
  if (guardNoteId === void 0 !== (expectedGuardContentHash === void 0)) {
59239
59526
  return { error: "Pass guardNoteId and expectedGuardContentHash together." };
59240
59527
  }
59241
- if (guardNoteId === id2 || requireActiveNoteId === id2) {
59528
+ const same = (a, b) => a !== void 0 && b !== void 0 && canonicalCoreDataId(a) === canonicalCoreDataId(b);
59529
+ if (same(guardNoteId, id2) || same(requireActiveNoteId, id2)) {
59242
59530
  return { error: "A guard note must be a different note from the one being deleted." };
59243
59531
  }
59244
- if (guardNoteId !== void 0 && guardNoteId === requireActiveNoteId) {
59532
+ if (same(guardNoteId, requireActiveNoteId)) {
59245
59533
  return { error: "requireActiveNoteId repeats guardNoteId; pass only guardNoteId." };
59246
59534
  }
59247
59535
  const prepared = { guards: [], labels: [] };
@@ -59588,7 +59876,7 @@ ${noteList}${truncationNote}${scanNote}${wordCountNote}${syncNote}`,
59588
59876
  registerTool(
59589
59877
  "query-notes",
59590
59878
  {
59591
- description: 'Use when: finding notes with a boolean expression over text and metadata \u2014 e.g. `folder:Work has:checklist -checklist:done`, `(title:invoice OR tag:finance) modified:>=2026-07-01`, `pinned words:>250`. Reads the Notes database directly, so it is fast and can match title OR body in one call.\nSyntax: bare words and "quoted phrases" match title or body (case-insensitive substring); fields title:, body:, text:, folder:, account:, tag: (values may be quoted, e.g. folder:"Work Projects"); facets has:link|attachment|checklist|drawing|image|video|audio|pdf|table|scan|tag; checklist:open|done; flags pinned, locked, shared (or is:pinned); words:>250 and created:/modified: with =, >, >=, <, <= and YYYY-MM-DD local dates. AND is implicit; OR, NOT, leading -, and parentheses are supported; operators are case-insensitive and a quoted "and" searches the literal word.\nReturns: matching notes (most recently modified first) with id, title, folder, account, modified date, snippet, and matchedIn (where the positive text terms occur: title, body, or both; absent when the body is unreadable or the query has no text term), plus scan/match counts; includeWordCount adds wordCount. Ids work with get-note-content and every other id-based tool.\nDo not use when: Full Disk Access is unavailable (use search-notes). Scans the most recent scanLimit notes (default 500); raise it for older notes.\nSafety: read-only; never writes the database. Excludes Recently Deleted and folderless notes unless includeDeleted is true. Locked notes match on title and metadata only; body predicates never match them.',
59879
+ description: 'Use when: finding notes with a boolean expression over text and metadata \u2014 e.g. `folder:Work has:checklist -checklist:done`, `(title:invoice OR tag:finance) modified:>=2026-07-01`, `pinned words:>250`. Reads the Notes database directly, so it is fast and can match title OR body in one call.\nSyntax: bare words and "quoted phrases" match title or body (case-insensitive substring); fields title:, body:, text:, folder:, account:, tag: (values may be quoted, e.g. folder:"Work Projects"); facets has:link|attachment|checklist|drawing|image|video|audio|pdf|table|scan|tag; checklist:open|done; flags pinned, locked, shared (or is:pinned); words:>250 and created:/modified: with =, >, >=, <, <= and YYYY-MM-DD local dates. AND is implicit; OR, NOT, leading -, and parentheses are supported; operators are case-insensitive and a quoted "and" searches the literal word.\nReturns: matching notes (most recently modified first) with id, title, folder, account, modified date, snippet, and matchedIn (where the positive text terms occur: title, body, or both; absent when the body is unreadable or the query has no text term), plus scan/match counts; includeWordCount adds wordCount. Ids work with get-note-content and every other id-based tool.\nDo not use when: Full Disk Access is unavailable (use search-notes). Scans the most recent scanLimit notes (default 500); raise it for older notes.\nSafety: read-only; never writes the database. Excludes Recently Deleted and folderless notes unless includeDeleted is true. Locked notes match on title and metadata only; body predicates (including negated ones such as -body:x) never match them.',
59592
59880
  inputSchema: {
59593
59881
  query: external_exports.string().min(1, "A query expression is required").max(MAX.QUERY).describe(
59594
59882
  'Boolean query expression, e.g. `folder:"Work Projects" has:checklist -checklist:done`'
@@ -59641,7 +59929,7 @@ registerTool(
59641
59929
  }
59642
59930
  if (result.unreadable > 0) {
59643
59931
  notes.push(
59644
- `\u26A0\uFE0F ${result.unreadable} note bodies could not be decoded, so body predicates did not match them.`
59932
+ `\u26A0\uFE0F ${result.unreadable} note bodies could not be decoded, so body predicates (including negated ones) did not match them.`
59645
59933
  );
59646
59934
  }
59647
59935
  const footer = notes.length ? `
@@ -60044,7 +60332,7 @@ registerTool(
60044
60332
  if (!note) return notFoundResponse(`Note with ID "${id2}" not found`);
60045
60333
  const body = notesManager.getNoteContentById(id2);
60046
60334
  if (!body) return errorResponse(`Failed to read content of note "${note.title}"`);
60047
- const rich = readRichNote(id2);
60335
+ const rich = readRichNote(id2, { skipUnsafeLinks: true });
60048
60336
  const tables = (rich.objectData || []).filter((object3) => object3.type?.includes("table")).map((object3) => {
60049
60337
  try {
60050
60338
  return {
@@ -61654,6 +61942,7 @@ registerTool(
61654
61942
  height: external_exports.number().optional(),
61655
61943
  bytes: external_exports.number().optional(),
61656
61944
  source: external_exports.enum(["fallback", "preview"]).optional(),
61945
+ stale: external_exports.boolean().optional(),
61657
61946
  attachmentId: external_exports.string().optional(),
61658
61947
  identifier: external_exports.string().optional(),
61659
61948
  kind: external_exports.enum(["paper", "drawing"]).optional(),
@@ -61662,8 +61951,9 @@ registerTool(
61662
61951
  },
61663
61952
  withErrorHandling(({ noteId: noteId3, savePath, attachmentId }) => {
61664
61953
  const r = notesManager.exportPaperImageById(noteId3, savePath, attachmentId);
61954
+ const stale = r.source === "fallback" && r.drawing.fallbackImageStale;
61665
61955
  return successResponse(
61666
- `Saved ${r.format.toUpperCase()} ${r.width}x${r.height} (${r.bytes} bytes, ${r.source === "fallback" ? "Notes' full rendering" : "largest preview"}) to ${r.savedPath}`,
61956
+ `Saved ${r.format.toUpperCase()} ${r.width}x${r.height} (${r.bytes} bytes, ${r.source === "fallback" ? "Notes' full rendering" : "largest preview"}) to ${r.savedPath}` + (stale ? ". This is an older rendering: the one Notes recorded is not on disk, so it may not show the latest strokes." : ""),
61667
61957
  {
61668
61958
  savedPath: r.savedPath,
61669
61959
  format: r.format,
@@ -61671,6 +61961,7 @@ registerTool(
61671
61961
  height: r.height,
61672
61962
  bytes: r.bytes,
61673
61963
  source: r.source,
61964
+ ...stale ? { stale } : {},
61674
61965
  attachmentId: attachmentCoreDataId(noteId3, r.drawing.pk),
61675
61966
  identifier: r.drawing.identifier,
61676
61967
  kind: r.drawing.kind,
@@ -61683,7 +61974,7 @@ registerTool(
61683
61974
  "export-attachments",
61684
61975
  {
61685
61976
  description: `Use when: copying every file attachment of one note (or only its lead visual) into a directory on disk.
61686
- Returns: per attachment its id, kind, exportedTo, and exportedKind: "asset" for the real file, "preview" when the asset never downloaded and only Notes' rendered thumbnail was available, or null when nothing was on disk.
61977
+ Returns: per attachment its id, kind, exportedTo, and exportedKind: "asset" for the real file, "fallback" for Notes' own full rendering of it (a drawing's PNG or a scan's PDF, named with that format's extension), "preview" when the asset never downloaded and only Notes' rendered thumbnail was available, or null when nothing was on disk; inBody: false marks an attachment no longer shown in the note body.
61687
61978
  Do not use when: exporting one attachment to an exact path (save-attachment) or reading bytes inline (fetch-attachment).
61688
61979
  Safety: writes files; exportDir must be absolute and under the home directory, a temp dir, or /Volumes, and not inside the Notes data folder. Existing files are never replaced: name collisions get -2, -3, ... suffixes. Reads NoteStore and the Notes data folder read-only; requires Full Disk Access. Notes.app is not opened.`,
61689
61980
  inputSchema: {
@@ -61695,6 +61986,7 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61695
61986
  exportDir: external_exports.string().optional(),
61696
61987
  exported: external_exports.number().optional(),
61697
61988
  previews: external_exports.number().optional(),
61989
+ fallbacks: external_exports.number().optional(),
61698
61990
  skipped: external_exports.number().optional(),
61699
61991
  failed: external_exports.number().optional(),
61700
61992
  results: external_exports.array(external_exports.object({}).passthrough()).optional(),
@@ -61709,12 +62001,14 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61709
62001
  }));
61710
62002
  const exported = results.filter((x) => x.exportedKind !== null).length;
61711
62003
  const previews = results.filter((x) => x.exportedKind === "preview").length;
62004
+ const fallbacks = results.filter((x) => x.exportedKind === "fallback").length;
61712
62005
  const failed = results.filter((x) => x.error).length;
61713
62006
  const skipped = results.length - exported - failed;
61714
62007
  const structured = {
61715
62008
  exportDir: r.exportDir,
61716
62009
  exported,
61717
62010
  previews,
62011
+ fallbacks,
61718
62012
  skipped,
61719
62013
  failed,
61720
62014
  results
@@ -61728,7 +62022,7 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61728
62022
  );
61729
62023
  }
61730
62024
  }
61731
- const summary = `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${skipped} with nothing on disk, ${failed} failed).`;
62025
+ const summary = `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${fallbacks} Notes rendering(s), ${skipped} with nothing on disk, ${failed} failed).`;
61732
62026
  if (exported === 0 && failed > 0) {
61733
62027
  const error2 = errorResponse(summary, new CodedError(summary, { code: "operation_failed" }));
61734
62028
  return { ...error2, structuredContent: { ...structured, ...error2.structuredContent } };
@@ -61909,7 +62203,8 @@ var exportStatsSchema = external_exports.object({
61909
62203
  unavailable: external_exports.number(),
61910
62204
  tables: external_exports.number(),
61911
62205
  unreadableTables: external_exports.number(),
61912
- unreferenced: external_exports.number()
62206
+ unreferenced: external_exports.number(),
62207
+ staleRenderings: external_exports.number().optional()
61913
62208
  });
61914
62209
  registerTool(
61915
62210
  "export-notes-markdown",
@@ -61935,6 +62230,7 @@ registerTool(
61935
62230
  outputSchema: {
61936
62231
  format: external_exports.string().optional(),
61937
62232
  count: external_exports.number().optional(),
62233
+ truncated: external_exports.boolean().optional(),
61938
62234
  bytes: external_exports.number().optional(),
61939
62235
  markdown: external_exports.string().optional(),
61940
62236
  output: external_exports.string().optional(),
@@ -61973,7 +62269,7 @@ registerTool(
61973
62269
  );
61974
62270
  }
61975
62271
  const warned = receipt.warnings?.length ? `; ${receipt.warnings.length + (receipt.warningsOmitted ?? 0)} warning(s)` : "";
61976
- const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + warned;
62272
+ const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + warned + (receipt.truncated ? "; the folder has more notes than limit, pass a higher limit" : "");
61977
62273
  if (receipt.output)
61978
62274
  return successResponse(
61979
62275
  `Wrote ${receipt.count} note(s) as Markdown (${receipt.bytes} bytes) to ${receipt.output}` + (receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : "") + `${skipped}.`,
@@ -62004,6 +62300,7 @@ registerTool(
62004
62300
  outputSchema: {
62005
62301
  format: external_exports.string().optional(),
62006
62302
  count: external_exports.number().optional(),
62303
+ truncated: external_exports.boolean().optional(),
62007
62304
  bytes: external_exports.number().optional(),
62008
62305
  output: external_exports.string().optional(),
62009
62306
  assets: external_exports.object({ dir: external_exports.string(), files: external_exports.number() }).optional(),
@@ -62026,7 +62323,7 @@ registerTool(
62026
62323
  return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
62027
62324
  }
62028
62325
  const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;
62029
- const skipped = receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "";
62326
+ const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + (receipt.truncated ? "; the folder has more notes than limit, pass a higher limit" : "");
62030
62327
  return successResponse(
62031
62328
  `Wrote ${receipt.count} note(s) as HTML (${receipt.bytes} bytes) to ${receipt.output}${assets}${skipped}.`,
62032
62329
  { ...receipt }
@@ -62209,27 +62506,32 @@ registerTool(
62209
62506
  drawingCount: external_exports.number().optional(),
62210
62507
  status: external_exports.string().optional(),
62211
62508
  drawings: external_exports.array(external_exports.object({}).passthrough()).optional(),
62212
- pointsOmitted: external_exports.boolean().optional()
62509
+ pointsOmitted: external_exports.boolean().optional(),
62510
+ svgOmitted: external_exports.boolean().optional()
62213
62511
  }
62214
62512
  },
62215
62513
  withErrorHandling(({ id: id2, format, includePoints }) => {
62216
- let result = getNoteDrawings(id2, { format, includePoints });
62217
- let pointsOmitted = false;
62218
- if (Buffer.byteLength(JSON.stringify(result)) > exportMaxResponseBytes() && includePoints !== false && format !== "svg") {
62219
- result = getNoteDrawings(id2, { format, includePoints: false });
62220
- pointsOmitted = true;
62221
- }
62222
- const text2 = formatNoteDrawings(result) + (pointsOmitted ? "\nStroke points were omitted to stay under the response size limit (APPLE_NOTES_MCP_EXPORT_MAX_BYTES)." : "");
62514
+ const limit = exportMaxResponseBytes();
62515
+ const { result, pointsOmitted, svgOmitted, oversized } = fitNoteDrawings(
62516
+ getNoteDrawings(id2, { format, includePoints }),
62517
+ limit
62518
+ );
62519
+ if (oversized)
62520
+ return errorResponse(
62521
+ `The drawings in note "${id2}" are too large to return even without stroke points and SVG (limit ${limit} bytes, APPLE_NOTES_MCP_EXPORT_MAX_BYTES).`
62522
+ );
62523
+ const text2 = formatNoteDrawings(result) + (pointsOmitted ? "\nStroke points were omitted to stay under the response size limit (APPLE_NOTES_MCP_EXPORT_MAX_BYTES)." : "") + (svgOmitted ? "\nSVG documents were omitted to stay under the response size limit (APPLE_NOTES_MCP_EXPORT_MAX_BYTES)." : "");
62223
62524
  return successResponse(text2, {
62224
62525
  ...result,
62225
- ...pointsOmitted ? { pointsOmitted } : {}
62526
+ ...pointsOmitted ? { pointsOmitted } : {},
62527
+ ...svgOmitted ? { svgOmitted } : {}
62226
62528
  });
62227
62529
  }, "Error reading drawings")
62228
62530
  );
62229
62531
  registerTool(
62230
62532
  "transcribe-note-audio",
62231
62533
  {
62232
- description: "Use when: you need the words spoken in a note's voice recordings or audio attachments, transcribed now on this Mac, by note id.\nReturns: per audio attachment a status (ok / partial / error / indeterminate), duration, word count, per-take results, and the transcript; overall status ok / partial / error / indeterminate / none.\nDo not use when: you only need the audio file (save-attachment) or a note has no audio.\nNote: read-only; recognition runs entirely on-device (never sent to a server). Needs Full Disk Access and the public native helper built once with `apple-notes-mcp setup --public-helper`. Long recordings take time: pass attachmentId to transcribe one at a time. An indeterminate result means the helper timed out; retrying may succeed. Never prompts: code permission_required means the user must allow the host app under System Settings > Privacy & Security > Speech Recognition. asset_unavailable can mean the language's speech model is not installed; pass downloadAssets: true only if the user agrees to the download.",
62534
+ description: "Use when: you need the words spoken in a note's voice recordings or audio attachments, transcribed now on this Mac, by note id.\nReturns: per audio attachment a status (ok / partial / error / indeterminate), duration, word count, per-take results, and the transcript; overall status ok / partial / error / indeterminate / none.\nDo not use when: you only need the audio file (save-attachment) or a note has no audio.\nNote: read-only; recognition runs entirely on-device (never sent to a server). Needs Full Disk Access and the public native helper built once with `apple-notes-mcp setup --public-helper`. Long recordings take time: pass attachmentId to transcribe one at a time. An indeterminate result means the helper timed out; retrying may succeed. Never prompts: code permission_required means the user must allow the host app under System Settings > Privacy & Security > Speech Recognition; permission_not_requested (before macOS 26) means the host app has never asked for that access, so it is not listed there yet. asset_unavailable can mean the language's speech model is not installed; pass downloadAssets: true only if the user agrees to the download.",
62233
62535
  inputSchema: {
62234
62536
  id: noteIdInput,
62235
62537
  locale: external_exports.string().max(35).optional().describe('BCP-47 language of the speech, e.g. "en-US" (default), "it-IT", "fr-FR"'),
@@ -62247,11 +62549,13 @@ registerTool(
62247
62549
  locale: external_exports.string().optional(),
62248
62550
  status: external_exports.string().optional(),
62249
62551
  recordingCount: external_exports.number().optional(),
62250
- recordings: external_exports.array(external_exports.object({}).passthrough()).optional()
62552
+ recordings: external_exports.array(external_exports.object({}).passthrough()).optional(),
62553
+ responseOversized: external_exports.boolean().optional()
62251
62554
  }
62252
62555
  },
62253
62556
  withAsyncErrorHandling(async (params, signal) => {
62254
62557
  const { id: id2, locale, attachmentId, includeText, downloadAssets, maxSeconds } = params;
62558
+ const toResponse = (r) => successResponse(formatTranscription(r), r);
62255
62559
  const result = fitTranscriptions(
62256
62560
  await transcribeNoteAudio(id2, {
62257
62561
  locale,
@@ -62261,12 +62565,10 @@ registerTool(
62261
62565
  maxSeconds,
62262
62566
  signal
62263
62567
  }),
62264
- exportMaxResponseBytes()
62265
- );
62266
- return successResponse(
62267
- formatTranscription(result),
62268
- result
62568
+ exportMaxResponseBytes(),
62569
+ (r) => Buffer.byteLength(JSON.stringify(toResponse(r)))
62269
62570
  );
62571
+ return toResponse(result);
62270
62572
  }, "Error transcribing audio")
62271
62573
  );
62272
62574
  registerTool(
@@ -62381,13 +62683,14 @@ process.on("uncaughtException", (err) => {
62381
62683
  process.on("unhandledRejection", (reason) => {
62382
62684
  console.error("[unhandledRejection]", reason);
62383
62685
  });
62384
- var shutdown = createShutdown(process.stdout, () => process.exit(0));
62686
+ var inFlight = new InFlightRequests();
62687
+ var shutdown = createShutdown(process.stdout, () => process.exit(0), void 0, inFlight);
62385
62688
  for (const sig of ["SIGINT", "SIGTERM"]) {
62386
62689
  process.on(sig, shutdown);
62387
62690
  }
62388
62691
  process.stdin.on("end", shutdown);
62389
62692
  process.stdin.on("close", shutdown);
62390
- var transport = withJsonSchema2020_12(new StdioServerTransport());
62693
+ var transport = trackRequests(withJsonSchema2020_12(new StdioServerTransport()), inFlight);
62391
62694
  await server.connect(transport);
62392
62695
  /*! Bundled license information:
62393
62696