apple-notes-mcp 2.9.26 → 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] || "[]");
@@ -41660,6 +41666,7 @@ function buildAttachmentRowsSql(notePk, columns) {
41660
41666
  `'identifier', a.ZIDENTIFIER`,
41661
41667
  `'uti', ${col3("a", "ZTYPEUTI")}`,
41662
41668
  `'parentPk', ${parent}`,
41669
+ `'parentDeleted', ${parent !== "NULL" && columns.has("ZMARKEDFORDELETION") ? `(SELECT COALESCE(p.ZMARKEDFORDELETION, 0) FROM ZICCLOUDSYNCINGOBJECT p WHERE p.Z_PK = ${parent})` : "0"}`,
41663
41670
  `'filename', ${col3("a", "ZFILENAME")}`,
41664
41671
  `'mediaIdentifier', m.ZIDENTIFIER`,
41665
41672
  `'mediaFilename', ${col3("m", "ZFILENAME")}`,
@@ -41718,6 +41725,7 @@ function parseAttachmentRows(json2) {
41718
41725
  identifier,
41719
41726
  uti: toStringOrNull(r.uti),
41720
41727
  parentPk: toIntOrNull(r.parentPk),
41728
+ ...toIntOrNull(r.parentDeleted) ? { parentDeleted: true } : {},
41721
41729
  filename: toStringOrNull(r.filename),
41722
41730
  mediaIdentifier: toStringOrNull(r.mediaIdentifier),
41723
41731
  mediaFilename: toStringOrNull(r.mediaFilename),
@@ -41835,7 +41843,7 @@ function generationDirs(base, accountDir) {
41835
41843
  const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
41836
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)));
41837
41845
  }
41838
- function fallbackFiles(accountDir, rootName, identifier, generation, names) {
41846
+ function fallbackFiles(accountDir, rootName, identifier, generation, names, onStale) {
41839
41847
  const base = join8(accountDir, rootName, identifier);
41840
41848
  const found = [];
41841
41849
  const add = (candidate) => {
@@ -41844,12 +41852,14 @@ function fallbackFiles(accountDir, rootName, identifier, generation, names) {
41844
41852
  };
41845
41853
  const gen = safeComponent(generation);
41846
41854
  if (gen) for (const name of names) add(join8(base, gen, name));
41855
+ const recorded = found.length;
41847
41856
  if (isDirectory(base)) {
41848
41857
  for (const dir of generationDirs(base, accountDir))
41849
41858
  for (const name of names) add(join8(dir, name));
41850
41859
  for (const name of names) add(join8(base, name));
41851
41860
  }
41852
41861
  for (const name of names) add(join8(accountDir, rootName, `${identifier}${extname(name)}`));
41862
+ if (gen && recorded === 0 && found.length > 0) onStale?.();
41853
41863
  return found;
41854
41864
  }
41855
41865
  function previewPixelArea(name) {
@@ -41895,7 +41905,7 @@ function previewPaths(accountDir, identifier, entries2) {
41895
41905
  }
41896
41906
  return files;
41897
41907
  }
41898
- function assetPathsFor(accountDir, row) {
41908
+ function assetPathsFor(accountDir, row, onStale) {
41899
41909
  const found = [];
41900
41910
  const add = (candidate) => {
41901
41911
  const real = realInside(candidate, accountDir);
@@ -41912,18 +41922,28 @@ function assetPathsFor(accountDir, row) {
41912
41922
  if (!id2) return found;
41913
41923
  const ownName = safeComponent(row.filename);
41914
41924
  if (ownName) add(join8(accountDir, "Media", id2, ownName));
41915
- for (const file of fallbackFiles(accountDir, "FallbackImages", id2, row.fallbackImageGeneration, [
41916
- "FallbackImage.png",
41917
- "FallbackImage.jpg"
41918
- ]))
41925
+ for (const file of fallbackFiles(
41926
+ accountDir,
41927
+ "FallbackImages",
41928
+ id2,
41929
+ row.fallbackImageGeneration,
41930
+ ["FallbackImage.png", "FallbackImage.jpg"],
41931
+ onStale
41932
+ ))
41919
41933
  add(file);
41920
- for (const file of fallbackFiles(accountDir, "FallbackPDFs", id2, row.fallbackPdfGeneration, [
41921
- "FallbackPDF.pdf"
41922
- ]))
41934
+ for (const file of fallbackFiles(
41935
+ accountDir,
41936
+ "FallbackPDFs",
41937
+ id2,
41938
+ row.fallbackPdfGeneration,
41939
+ ["FallbackPDF.pdf"],
41940
+ onStale
41941
+ ))
41923
41942
  add(file);
41924
41943
  return found;
41925
41944
  }
41926
- 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);
41927
41947
  const byPk = new Map(rows.map((r) => [r.pk, r]));
41928
41948
  const roots = rows.filter((r) => r.parentPk === null || !byPk.has(r.parentPk));
41929
41949
  const bodyIndex = /* @__PURE__ */ new Map();
@@ -41946,10 +41966,11 @@ function assembleAttachmentAssets(rows, bodyOrder, containerDir = NOTES_CONTAINE
41946
41966
  const accountDir = accountDirFor(row.accountIdentifier ?? parent?.accountIdentifier ?? null);
41947
41967
  let assetPaths = [];
41948
41968
  let previews = [];
41969
+ let fallbackStale = false;
41949
41970
  if (accountDir) {
41950
41971
  if (!previewEntries.has(accountDir))
41951
41972
  previewEntries.set(accountDir, listPreviewEntries(accountDir));
41952
- assetPaths = assetPathsFor(accountDir, row);
41973
+ assetPaths = assetPathsFor(accountDir, row, () => fallbackStale = true);
41953
41974
  previews = previewPaths(accountDir, row.identifier, previewEntries.get(accountDir));
41954
41975
  }
41955
41976
  const previewPath = previews[0] ?? null;
@@ -41962,6 +41983,7 @@ function assembleAttachmentAssets(rows, bodyOrder, containerDir = NOTES_CONTAINE
41962
41983
  filename: row.filename ?? row.mediaFilename,
41963
41984
  bodyIndex: parent ? null : indexOf(row),
41964
41985
  assetPaths,
41986
+ ...fallbackStale ? { fallbackStale: true } : {},
41965
41987
  previewPath,
41966
41988
  paths: previewPath ? [...assetPaths, previewPath] : [...assetPaths]
41967
41989
  };
@@ -42047,6 +42069,11 @@ function copyFileExclusive(src, dest) {
42047
42069
  function exportFileName(record2, source, kind) {
42048
42070
  const ext = extname(source);
42049
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
+ }
42050
42077
  if (kind === "preview") {
42051
42078
  const stored2 = safeComponent(record2.filename ? basename(record2.filename) : null);
42052
42079
  const stem = stored2 ? basename(stored2, extname(stored2)) : id2;
@@ -42075,7 +42102,7 @@ function prepareExportDir(exportDir, containerDir = NOTES_CONTAINER_DIR) {
42075
42102
  }
42076
42103
  return abs;
42077
42104
  }
42078
- function exportOneAttachment(record2, dir, source) {
42105
+ function exportOneAttachment(record2, dir, source, inBody) {
42079
42106
  const base = {
42080
42107
  pk: record2.pk,
42081
42108
  identifier: record2.identifier,
@@ -42083,7 +42110,8 @@ function exportOneAttachment(record2, dir, source) {
42083
42110
  kind: record2.kind,
42084
42111
  parentIdentifier: record2.parentIdentifier,
42085
42112
  exportedTo: null,
42086
- exportedKind: null
42113
+ exportedKind: null,
42114
+ ...inBody === false ? { inBody: false } : {}
42087
42115
  };
42088
42116
  if (!source) return base;
42089
42117
  const name = exportFileName(record2, source.path, source.kind);
@@ -42094,7 +42122,12 @@ function exportOneAttachment(record2, dir, source) {
42094
42122
  throw new Error(`Refusing to write outside the export directory: "${dest}"`);
42095
42123
  assertSafeSavePath(dest);
42096
42124
  copyFileExclusive(source.path, dest);
42097
- 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
+ };
42098
42131
  } catch (error2) {
42099
42132
  if (error2.code === "EEXIST") continue;
42100
42133
  return { ...base, error: error2 instanceof Error ? error2.message : String(error2) };
@@ -42102,20 +42135,31 @@ function exportOneAttachment(record2, dir, source) {
42102
42135
  }
42103
42136
  return { ...base, error: "Too many name collisions in the export directory" };
42104
42137
  }
42138
+ function isFallbackRendering(path10) {
42139
+ return /[\\/]Fallback(?:Images|PDFs)[\\/]/.test(path10);
42140
+ }
42105
42141
  function exportSource(record2) {
42106
- 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" };
42107
42144
  if (record2.previewPath) return { path: record2.previewPath, kind: "preview" };
42108
42145
  return null;
42109
42146
  }
42110
42147
  function exportAttachmentAssets(assets, exportDir, options = {}) {
42111
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
+ };
42112
42156
  if (options.firstImageOnly) {
42113
42157
  const first2 = selectFirstImage(assets);
42114
42158
  if (!first2) return { exportDir: dir, results: [], firstImage: null };
42115
42159
  const record2 = assets.attachments.find((a) => a.pk === first2.pk);
42116
42160
  return {
42117
42161
  exportDir: dir,
42118
- results: [exportOneAttachment(record2, dir, exportSource(record2))],
42162
+ results: [exportOneAttachment(record2, dir, exportSource(record2), inBody(record2))],
42119
42163
  firstImage: first2
42120
42164
  };
42121
42165
  }
@@ -42129,7 +42173,7 @@ function exportAttachmentAssets(assets, exportDir, options = {}) {
42129
42173
  } else if (!exportSource(record2) && assets.attachments.some((c) => c.parentIdentifier === record2.identifier)) {
42130
42174
  continue;
42131
42175
  }
42132
- results.push(exportOneAttachment(record2, dir, exportSource(record2)));
42176
+ results.push(exportOneAttachment(record2, dir, exportSource(record2), inBody(record2)));
42133
42177
  }
42134
42178
  return { exportDir: dir, results };
42135
42179
  }
@@ -42298,7 +42342,17 @@ function entries(dir, limit) {
42298
42342
  }
42299
42343
  }
42300
42344
  var byGenerationDesc = (a, b) => generationRank(basename2(b)) - generationRank(basename2(a));
42301
- 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) {
42302
42356
  const id2 = safeComponent(identifier);
42303
42357
  if (!id2) return null;
42304
42358
  const base = join9(accountDir, "FallbackImages", id2);
@@ -42345,7 +42399,8 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
42345
42399
  const accountDir = resolveAccountDir(containerDir, row.accountIdentifier);
42346
42400
  const kind = row.uti === "com.apple.paper" ? "paper" : "drawing";
42347
42401
  const id2 = safeComponent(row.identifier);
42348
- 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;
42349
42404
  const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
42350
42405
  const bundle = accountDir && id2 && kind === "paper" ? realInside(join9(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
42351
42406
  let raster = null;
@@ -42367,6 +42422,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
42367
42422
  handwritingSummary: row.handwritingSummary,
42368
42423
  bundlePresent: bundle !== null && kindOf(bundle) === "dir",
42369
42424
  fallbackImagePath,
42425
+ fallbackImageStale: fallback.stale,
42370
42426
  previewPath,
42371
42427
  raster
42372
42428
  };
@@ -42507,18 +42563,25 @@ function idList(ids) {
42507
42563
  function buildScopeGuardScript(noteVar, guard, destinationVar) {
42508
42564
  if (!hasScopeGuard(guard)) return "";
42509
42565
  validateScopeGuard(guard);
42510
- 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))];
42511
42569
  let script = `
42512
42570
  set scopeFolder to container of ${noteVar}
42513
42571
  if class of scopeFolder is not folder then return "${SCOPE_MARKER}:the note is not in a folder"`;
42514
- if (guard.ifFolderId)
42572
+ if (forbidden.length > 0)
42515
42573
  script += `
42516
- if (id of scopeFolder) is not "${guard.ifFolderId}" then return "${SCOPE_MARKER}:the note is not in the expected folder"`;
42517
- 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)
42518
42581
  script += chainScript("scopeChain", "scopeFolder");
42519
- if (guard.ifAncestorFolderId)
42582
+ if (ifAncestorFolderId)
42520
42583
  script += `
42521
- 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"`;
42522
42585
  if (forbidden.length > 0) {
42523
42586
  script += `
42524
42587
  repeat with forbiddenId in ${idList(forbidden)}
@@ -43435,7 +43498,7 @@ var AppleNotesManager = class {
43435
43498
  * @throws Error when the note's rich data cannot be read (e.g. no Full Disk Access)
43436
43499
  */
43437
43500
  getNoteTablesById(id2) {
43438
- return collectNoteTables(readRichNote(id2), id2);
43501
+ return collectNoteTables(readRichNote(id2, { skipUnsafeLinks: true }), id2);
43439
43502
  }
43440
43503
  /**
43441
43504
  * Retrieves detailed metadata for a note by title.
@@ -43568,6 +43631,7 @@ var AppleNotesManager = class {
43568
43631
  return `
43569
43632
  if not (exists note id "${safeGuardId}") then ${inactive("missing")}
43570
43633
  set ${ref} to note id "${safeGuardId}"
43634
+ if (id of ${ref}) is (id of noteRef) then ${inactive("the note being deleted")}
43571
43635
  if password protected of ${ref} then ${inactive("locked")}
43572
43636
  set ${folderVar} to missing value
43573
43637
  try
@@ -46328,7 +46392,7 @@ var HIGHLIGHTS = {
46328
46392
  var KNOWN_RUN_FIELDS = /* @__PURE__ */ new Set([1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 14]);
46329
46393
  var KNOWN_PARAGRAPH_FIELDS = /* @__PURE__ */ new Set([1, 2, 4, 5, 8, 9]);
46330
46394
  var isSafeLink = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
46331
- var utf8 = new TextDecoder();
46395
+ var utf8 = new TextDecoder("utf-8", { ignoreBOM: true });
46332
46396
  var first = (fields, n) => fields.find((f) => f.fieldNumber === n);
46333
46397
  var varintOf = (fields, n) => {
46334
46398
  const f = first(fields, n);
@@ -46477,7 +46541,8 @@ function decodeNoteBlocks(data) {
46477
46541
  const end = newline === -1 ? text2.length : newline;
46478
46542
  while (runIndex < runs.length - 1 && runs[runIndex].start + runs[runIndex].length <= paragraphStart)
46479
46543
  runIndex++;
46480
- 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;
46481
46546
  const style = attrs.styleType === null ? "body" : STYLE_NAMES[attrs.styleType] ?? "unknown";
46482
46547
  const alignment = ALIGNMENTS[attrs.alignmentValue] ?? "unknown";
46483
46548
  const block = {
@@ -46684,6 +46749,25 @@ function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
46684
46749
  return decodeCompressedNoteBlocks(Buffer.from(row.data, "hex"));
46685
46750
  }
46686
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
+
46687
46771
  // src/utils/noteRecentList.ts
46688
46772
  var RECENT_LIMIT = { DEFAULT: 50, MAX: 1e3 };
46689
46773
  var CHECKPOINT_PREFIX = "cdts1:";
@@ -46738,9 +46822,7 @@ function parseSince(input) {
46738
46822
  }
46739
46823
  function textStats(text2) {
46740
46824
  const visible2 = text2.replace(OBJECT_REPLACEMENT, "");
46741
- let wordCount2 = 0;
46742
- for (const token of visible2.split(/\s+/u)) if (/[\p{L}\p{N}]/u.test(token)) wordCount2++;
46743
- return { wordCount: wordCount2, charCount: [...visible2].length };
46825
+ return { wordCount: countWords(visible2), charCount: [...visible2].length };
46744
46826
  }
46745
46827
  function previewText(text2) {
46746
46828
  const flat = text2.replace(OBJECT_REPLACEMENT, " ").replace(/\s+/gu, " ").trim();
@@ -47362,6 +47444,10 @@ var Parser = class {
47362
47444
  function parseNoteQuery(input) {
47363
47445
  return new Parser(tokenize(input), input.length).parse();
47364
47446
  }
47447
+ function noteBodyText(text2) {
47448
+ const firstBreak = text2.indexOf("\n");
47449
+ return firstBreak === -1 ? "" : text2.slice(firstBreak + 1);
47450
+ }
47365
47451
  function normalizeForMatch(text2) {
47366
47452
  return text2.normalize("NFC").replace(/[\ufffc\u00a0]/gu, " ").toLowerCase();
47367
47453
  }
@@ -47436,20 +47522,36 @@ function cheapFirst(children) {
47436
47522
  }
47437
47523
  return ordered;
47438
47524
  }
47439
- function evaluateNoteQuery(node, note) {
47525
+ function evaluateNoteQueryTruth(node, note) {
47440
47526
  switch (node.type) {
47441
- case "and":
47442
- return cheapFirst(node.children).every((child2) => evaluateNoteQuery(child2, note));
47443
- case "or":
47444
- return cheapFirst(node.children).some((child2) => evaluateNoteQuery(child2, note));
47445
- case "not":
47446
- 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
+ }
47447
47549
  case "text": {
47448
47550
  const needle = normalizeForMatch(node.value);
47449
47551
  if (node.field === "title") return note.titleLower.includes(needle);
47450
47552
  if (node.field === "any" && note.titleLower.includes(needle)) return true;
47451
47553
  const content = note.content();
47452
- if (!content) return false;
47554
+ if (!content) return null;
47453
47555
  return (node.field === "body" ? content.bodyLower : content.textLower).includes(needle);
47454
47556
  }
47455
47557
  case "folder":
@@ -47462,21 +47564,27 @@ function evaluateNoteQuery(node, note) {
47462
47564
  return compareDate(node.field === "created" ? note.created : note.modified, node);
47463
47565
  case "tag": {
47464
47566
  const content = note.content();
47465
- 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;
47466
47572
  }
47467
- case "has":
47468
- return Boolean(note.content()?.facets.has(node.facet));
47469
47573
  case "checklist": {
47470
47574
  const content = note.content();
47471
- if (!content || content.checklist.total === 0) return false;
47575
+ if (!content) return null;
47576
+ if (content.checklist.total === 0) return false;
47472
47577
  return node.state === "open" ? content.checklist.open > 0 : content.checklist.open === 0;
47473
47578
  }
47474
47579
  case "words": {
47475
47580
  const content = note.content();
47476
- return content ? compare(content.words, node.op, node.value) : false;
47581
+ return content ? compare(content.words, node.op, node.value) : null;
47477
47582
  }
47478
47583
  }
47479
47584
  }
47585
+ function evaluateNoteQuery(node, note) {
47586
+ return evaluateNoteQueryTruth(node, note) === true;
47587
+ }
47480
47588
  function positiveTextPredicates(node, negated = false) {
47481
47589
  switch (node.type) {
47482
47590
  case "and":
@@ -47499,7 +47607,7 @@ function matchLocations(predicates, title, text2) {
47499
47607
  const firstBreak = text2 === null ? -1 : text2.indexOf("\n");
47500
47608
  const titleLower = normalizeForMatch(title);
47501
47609
  const firstLineLower = text2 === null ? "" : normalizeForMatch(firstBreak === -1 ? text2 : text2.slice(0, firstBreak));
47502
- const bodyLower = text2 === null || firstBreak === -1 ? "" : normalizeForMatch(text2.slice(firstBreak + 1));
47610
+ const bodyLower = text2 === null ? "" : normalizeForMatch(noteBodyText(text2));
47503
47611
  let inTitle = false;
47504
47612
  let inBody = false;
47505
47613
  for (const predicate of predicates) {
@@ -47611,13 +47719,6 @@ function decodeBodyHex(hex3) {
47611
47719
  return null;
47612
47720
  }
47613
47721
  }
47614
- function countWords(text2) {
47615
- let count = 0;
47616
- for (const word of text2.replace(/\ufffc/gu, " ").split(/\s+/u)) {
47617
- if (/[\p{L}\p{N}]/u.test(word)) count++;
47618
- }
47619
- return count;
47620
- }
47621
47722
  var REQUIRED_COLUMNS3 = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
47622
47723
  function col2(available, alias, name) {
47623
47724
  return available.has(name) ? `${alias}.${name}` : "NULL";
@@ -47862,7 +47963,6 @@ function runNoteQuery(ast, options = {}) {
47862
47963
  const body2 = decode2();
47863
47964
  if (!body2) return content = null;
47864
47965
  const text2 = body2.text;
47865
- const firstBreak = text2.indexOf("\n");
47866
47966
  const tags = /* @__PURE__ */ new Set();
47867
47967
  for (const [id2, alt] of row.tags ?? []) {
47868
47968
  if (id2 && alt && body2.objectIds.has(id2))
@@ -47872,7 +47972,7 @@ function runNoteQuery(ast, options = {}) {
47872
47972
  if (tags.size) facets.add("tag");
47873
47973
  content = {
47874
47974
  textLower: normalizeForMatch(text2),
47875
- bodyLower: normalizeForMatch(firstBreak === -1 ? "" : text2.slice(firstBreak + 1)),
47975
+ bodyLower: normalizeForMatch(noteBodyText(text2)),
47876
47976
  words: countWords(text2),
47877
47977
  facets,
47878
47978
  checklist: body2.checklist,
@@ -48365,13 +48465,16 @@ var LEGACY_ENTITIES = {
48365
48465
  gt: ">",
48366
48466
  amp: "&"
48367
48467
  };
48468
+ function codePointText(value) {
48469
+ return Number.isSafeInteger(value) && value <= 1114111 ? String.fromCodePoint(value) : "\uFFFD";
48470
+ }
48368
48471
  function comparableVisibleText(html) {
48369
48472
  return html.replace(/<br\s*\/?\s*>/gi, " ").replace(/<[^>]*>/g, (tag) => INLINE_TAG.test(tag) ? "" : " ").replace(
48370
48473
  /&(?:(nbsp|quot|lt|gt|amp);?|apos;|#(\d+);|#x([0-9a-f]+);)/gi,
48371
48474
  (_match, legacy, dec, hex3) => {
48372
48475
  if (legacy) return LEGACY_ENTITIES[legacy.toLowerCase()];
48373
- if (dec) return String.fromCodePoint(Number(dec));
48374
- 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));
48375
48478
  return "'";
48376
48479
  }
48377
48480
  ).replace(/\s+/g, " ").trim();
@@ -48796,15 +48899,26 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
48796
48899
  const segments = (path10) => JSON.stringify(splitFolderPath(path10).map((part) => part.toLocaleLowerCase()));
48797
48900
  if (request.folder) {
48798
48901
  const wanted = segments(request.folder);
48799
- const accounts = manager.listAccounts().filter((account) => account.defaultFolder);
48800
- if (!accounts.some(
48801
- (account) => manager.listFolders(account.name).some((folder) => segments(folder.name) === wanted)
48802
- ))
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)
48803
48906
  throw new Error(
48804
48907
  `Folder "${request.folder}" does not exist; create it with create-folder first. Nothing was created`
48805
48908
  );
48806
- for (const account of accounts)
48807
- 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;
48808
48922
  }
48809
48923
  const defaultFolderNotes = () => new Map(
48810
48924
  manager.listAccounts().flatMap(
@@ -49501,7 +49615,63 @@ function withJsonSchema2020_12(transport2) {
49501
49615
 
49502
49616
  // src/utils/shutdown.ts
49503
49617
  var SHUTDOWN_DRAIN_TIMEOUT_MS = 2e3;
49504
- 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) {
49505
49675
  let shuttingDown = false;
49506
49676
  let exited = false;
49507
49677
  const exitOnce = () => {
@@ -49518,8 +49688,11 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
49518
49688
  return () => {
49519
49689
  if (shuttingDown) return;
49520
49690
  shuttingDown = true;
49521
- timer = setTimeout(exitOnce, timeoutMs);
49522
- 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
+ );
49523
49696
  };
49524
49697
  }
49525
49698
 
@@ -49867,7 +50040,7 @@ function lastViewedOf(raw, columnPresent = true, now = Date.now()) {
49867
50040
  return { lastViewed: new Date(ms).toISOString(), lastViewedStatus: "viewed" };
49868
50041
  }
49869
50042
  var visible = (text2) => text2.replace(/\ufffc/g, "");
49870
- var wordCount = (text2) => visible(text2).split(/\s+/u).filter((word) => word !== "").length;
50043
+ var wordCount = (text2) => countWords(text2);
49871
50044
  var charCount = (text2) => Array.from(visible(text2)).length;
49872
50045
  function noteStructureSql(columns) {
49873
50046
  const c = (alias, name) => col(columns, alias, name);
@@ -50155,7 +50328,7 @@ function inventorySql(columns) {
50155
50328
  ORDER BY d.ZNOTE LIMIT ${BODY_BATCH};`
50156
50329
  };
50157
50330
  }
50158
- function decodeBodies(dbPath2, sql, params) {
50331
+ function decodeBodies(dbPath2, sql, params, notes) {
50159
50332
  const docs = /* @__PURE__ */ new Map();
50160
50333
  let after = 0;
50161
50334
  for (; ; ) {
@@ -50164,7 +50337,7 @@ function decodeBodies(dbPath2, sql, params) {
50164
50337
  );
50165
50338
  for (const row of rows) {
50166
50339
  after = Math.max(after, row.note);
50167
- if (row.encrypted || !row.data) continue;
50340
+ if (row.encrypted || !row.data || !notes.has(row.note)) continue;
50168
50341
  try {
50169
50342
  docs.set(row.note, decodeCompressedNoteBlocks(Buffer.from(row.data, "hex")));
50170
50343
  } catch {
@@ -50212,7 +50385,7 @@ function listNoteLinks(options = {}) {
50212
50385
  if (options.id && !notes.length)
50213
50386
  throw new NoteStoreError(`No note found for ID "${options.id}".`, "invalid_input");
50214
50387
  const includeInline = options.includeInline ?? Boolean(options.id);
50215
- 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();
50216
50389
  const noteById = new Map(notes.map((note) => [note.pk, note]));
50217
50390
  const accountById = new Map(context.accounts.map((account) => [account.pk, account]));
50218
50391
  const folderById = new Map(context.folders.map((folder) => [folder.pk, folder]));
@@ -50527,7 +50700,14 @@ var AssetLocator = class {
50527
50700
  ["FallbackImage.png", "FallbackImage.jpg"]
50528
50701
  );
50529
50702
  if (fallback)
50530
- 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
+ };
50531
50711
  return preview ? { primary: preview } : {};
50532
50712
  }
50533
50713
  case "scan": {
@@ -50535,7 +50715,14 @@ var AssetLocator = class {
50535
50715
  "FallbackPDF.pdf"
50536
50716
  ]);
50537
50717
  return {
50538
- ...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
+ } : {},
50539
50726
  ...preview ? { preview } : {}
50540
50727
  };
50541
50728
  }
@@ -50564,7 +50751,11 @@ var AssetLocator = class {
50564
50751
  }
50565
50752
  return void 0;
50566
50753
  }
50567
- /** <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
+ */
50568
50759
  fallback(account, dir, id2, generation, names) {
50569
50760
  const base = join19(account, dir, id2);
50570
50761
  const gen = safeComponent2(generation);
@@ -50576,7 +50767,7 @@ var AssetLocator = class {
50576
50767
  for (const g of generations)
50577
50768
  for (const name of names) {
50578
50769
  const path10 = confine(g ? join19(base, g, name) : join19(base, name), account);
50579
- if (path10 && isFile(path10)) return path10;
50770
+ if (path10 && isFile(path10)) return { path: path10, stale: Boolean(gen) && g !== gen };
50580
50771
  }
50581
50772
  return void 0;
50582
50773
  }
@@ -50733,6 +50924,9 @@ function readHead(fd) {
50733
50924
  const n = readSync3(fd, head, 0, 16, 0);
50734
50925
  return head.subarray(0, n);
50735
50926
  }
50927
+ function directoryFailure(dir, error2) {
50928
+ return `Could not create the assets directory ${dir}: ${error2 instanceof Error ? error2.message : String(error2)}`;
50929
+ }
50736
50930
  var SidecarWriter = class {
50737
50931
  constructor(dir, linkBase) {
50738
50932
  this.dir = dir;
@@ -50743,9 +50937,16 @@ var SidecarWriter = class {
50743
50937
  placed = /* @__PURE__ */ new Map();
50744
50938
  created = false;
50745
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;
50746
50946
  place(asset) {
50747
50947
  const done = this.placed.get(asset.path);
50748
50948
  if (done) return done;
50949
+ if (this.directoryError) return { error: this.directoryError };
50749
50950
  let source;
50750
50951
  try {
50751
50952
  source = openSource(asset.path);
@@ -50755,8 +50956,13 @@ var SidecarWriter = class {
50755
50956
  try {
50756
50957
  const mime = sniffMime(readHead(source.fd), asset.name);
50757
50958
  if (!this.created) {
50758
- assertExportPath(this.dir);
50759
- 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
+ }
50760
50966
  this.created = true;
50761
50967
  }
50762
50968
  const name = safeAssetName(asset.name, mime);
@@ -50909,7 +51115,10 @@ function titleBlockIndex(note) {
50909
51115
  return first2.text.trim() === note.title.trim() && first2.style !== "monospaced" ? first2.index : -1;
50910
51116
  }
50911
51117
  function place(ctx, asset) {
50912
- 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;
50913
51122
  }
50914
51123
  function planAttachment(attachment, ctx) {
50915
51124
  ctx.stats.attachments++;
@@ -51075,6 +51284,7 @@ function inlinePlanHtml(plan) {
51075
51284
  }
51076
51285
  }
51077
51286
  function blockPlanHtml(plan) {
51287
+ if (!isHtmlBlockPlan(plan)) return `<p>${inlinePlanHtml(plan)}</p>`;
51078
51288
  switch (plan.type) {
51079
51289
  case "table":
51080
51290
  return tableHtml(plan.rows);
@@ -51942,7 +52152,7 @@ function child(path10, key) {
51942
52152
  if (key.length > MAX_QUOTED_TOKEN) key = `${key.slice(0, MAX_QUOTED_TOKEN - 3)}...`;
51943
52153
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path10}.${key}` : `${path10}[${JSON.stringify(key)}]`;
51944
52154
  }
51945
- var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
52155
+ var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
51946
52156
  var describe = (value) => value === null ? "null" : Array.isArray(value) ? "an array" : `a ${typeof value}`;
51947
52157
  var describeVersion = (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : describe(value);
51948
52158
  var listed = (values) => values.map((v) => JSON.stringify(v)).join(", ");
@@ -52023,7 +52233,7 @@ var RULE_FIELDS = {
52023
52233
  omit: []
52024
52234
  };
52025
52235
  function validateRule(c, rule, path10) {
52026
- if (!isObject2(rule)) {
52236
+ if (!isObject3(rule)) {
52027
52237
  c.add(path10, `must be an object, not ${describe(rule)}`);
52028
52238
  return;
52029
52239
  }
@@ -52070,7 +52280,7 @@ function validateInlineOrder(c, value, path10) {
52070
52280
  if (missing.length) c.add(path10, `must list every inline format; missing ${listed(missing)}`);
52071
52281
  }
52072
52282
  function validateOptions(c, value, path10) {
52073
- if (!isObject2(value)) {
52283
+ if (!isObject3(value)) {
52074
52284
  c.add(path10, `must be an object, not ${describe(value)}`);
52075
52285
  return;
52076
52286
  }
@@ -52091,7 +52301,7 @@ function validateOptions(c, value, path10) {
52091
52301
  }
52092
52302
  function templateErrors(input) {
52093
52303
  const c = new Collector();
52094
- if (!isObject2(input)) {
52304
+ if (!isObject3(input)) {
52095
52305
  c.add("$", `a template must be a JSON object, not ${describe(input)}`);
52096
52306
  return c.errors;
52097
52307
  }
@@ -52126,7 +52336,7 @@ function templateErrors(input) {
52126
52336
  c.oneOf(value, path10, BUILTIN_TEMPLATE_NAMES);
52127
52337
  break;
52128
52338
  case "assets":
52129
- if (!isObject2(value)) {
52339
+ if (!isObject3(value)) {
52130
52340
  c.add(path10, `must be an object, not ${describe(value)}`);
52131
52341
  break;
52132
52342
  }
@@ -52142,7 +52352,7 @@ function templateErrors(input) {
52142
52352
  validateInlineOrder(c, value, path10);
52143
52353
  break;
52144
52354
  case "rules":
52145
- if (!isObject2(value)) {
52355
+ if (!isObject3(value)) {
52146
52356
  c.add(path10, `must be an object keyed by rule id, not ${describe(value)}`);
52147
52357
  break;
52148
52358
  }
@@ -52772,16 +52982,26 @@ var HashedSidecarWriter = class {
52772
52982
  /** Absolute paths of files written or reused. */
52773
52983
  files = [];
52774
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. */
52775
52988
  prepare() {
52776
- if (this.ready) return;
52777
- assertExportPath(this.dir);
52778
- mkdirSync5(this.dir, { recursive: true });
52779
- 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
+ }
52780
52998
  this.ready = true;
52999
+ return void 0;
52781
53000
  }
52782
53001
  place(asset) {
52783
53002
  const done = this.placed.get(asset.path);
52784
53003
  if (done) return done;
53004
+ if (this.directoryError) return { error: this.directoryError };
52785
53005
  let source;
52786
53006
  try {
52787
53007
  source = openRegular(asset.path);
@@ -52791,7 +53011,8 @@ var HashedSidecarWriter = class {
52791
53011
  try {
52792
53012
  const { hash, head } = digest(source);
52793
53013
  const mime = sniffMime(head, asset.name);
52794
- this.prepare();
53014
+ const directoryError = this.prepare();
53015
+ if (directoryError) return { error: directoryError };
52795
53016
  const name = safeAssetName(asset.name, mime);
52796
53017
  const ext = extname4(name);
52797
53018
  const target = join21(
@@ -53021,10 +53242,11 @@ function validPath(path10, what) {
53021
53242
  function selectNotes(request, deps) {
53022
53243
  if (!!request.id === !!request.folder)
53023
53244
  throw new NotesExportError("invalid-request", "Provide exactly one of 'id' or 'folder'.");
53024
- if (request.id) return [request.id];
53245
+ if (request.id) return { ids: [request.id], truncated: false };
53025
53246
  const limit = Math.min(request.limit ?? DEFAULT_FOLDER_EXPORT_LIMIT, MAX_FOLDER_EXPORT_LIMIT);
53026
53247
  try {
53027
- 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 };
53028
53250
  } catch (error2) {
53029
53251
  throw new NotesExportError(
53030
53252
  "folder-unavailable",
@@ -53056,9 +53278,13 @@ function openOutput(output) {
53056
53278
  throw error2;
53057
53279
  }
53058
53280
  }
53059
- function renderInto(fd, render) {
53281
+ function renderInto(fd, render, writers = []) {
53060
53282
  try {
53061
- 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;
53062
53288
  } catch (error2) {
53063
53289
  if (fd !== void 0) writeAllAndClose(fd, "");
53064
53290
  throw error2;
@@ -53075,10 +53301,15 @@ function exportNotesMarkdown(request, deps) {
53075
53301
  "invalid-request",
53076
53302
  `assetsDir has no effect: template "${chosen.info.name}" sets assets.mode to "${chosen.template.assets.mode}".`
53077
53303
  );
53078
- const ids = selectNotes(request, deps);
53304
+ const { ids, truncated } = selectNotes(request, deps);
53079
53305
  const { notes, skipped } = loadNotes(ids, !!request.id, deps.readNote);
53080
- if (chosen)
53081
- 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
+ }
53082
53313
  const fd = output ? openOutput(output) : void 0;
53083
53314
  const writer = assetsDir ? new SidecarWriter(assetsDir, output ? dirname6(output) : void 0) : void 0;
53084
53315
  const ctx = {
@@ -53087,12 +53318,14 @@ function exportNotesMarkdown(request, deps) {
53087
53318
  };
53088
53319
  const markdown = renderInto(
53089
53320
  fd,
53090
- () => renderNotesMarkdown(notes, ctx, { wrap: request.wrap ?? 0 })
53321
+ () => renderNotesMarkdown(notes, ctx, { wrap: request.wrap ?? 0 }),
53322
+ writer ? [writer] : []
53091
53323
  );
53092
53324
  const bytes = Buffer.byteLength(markdown);
53093
53325
  const receipt = {
53094
53326
  format: "markdown",
53095
53327
  count: notes.length,
53328
+ ...truncated ? { truncated } : {},
53096
53329
  bytes,
53097
53330
  stats: ctx.stats,
53098
53331
  skipped,
@@ -53128,7 +53361,7 @@ function exportNotesHtml(request, deps) {
53128
53361
  const assetsDir = embed ? void 0 : validPath(request.assetsDir ?? defaultSidecarDir(output), "assetsDir");
53129
53362
  if (assetsDir === output)
53130
53363
  throw new NotesExportError("invalid-path", "outputPath and assetsDir must differ.");
53131
- const ids = selectNotes(request, deps);
53364
+ const { ids, truncated } = selectNotes(request, deps);
53132
53365
  const { notes, skipped } = loadNotes(ids, !!request.id, deps.readNote);
53133
53366
  const fd = openOutput(output);
53134
53367
  const writer = assetsDir ? new SidecarWriter(assetsDir, dirname6(output)) : new DataUrlWriter();
@@ -53138,11 +53371,16 @@ function exportNotesHtml(request, deps) {
53138
53371
  locator: deps.locator ?? new AssetLocator()
53139
53372
  };
53140
53373
  const title = request.id ? notes[0]?.title || "Note" : request.folder;
53141
- 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
+ );
53142
53379
  const bytes = writeAllAndClose(fd, html);
53143
53380
  return {
53144
53381
  format: "html",
53145
53382
  count: notes.length,
53383
+ ...truncated ? { truncated } : {},
53146
53384
  bytes,
53147
53385
  output,
53148
53386
  stats: ctx.stats,
@@ -53224,7 +53462,7 @@ function exportWithTemplate(request, deps, chosen, notes, skipped, { output, ass
53224
53462
  });
53225
53463
  warnings = result.warnings;
53226
53464
  return result.markdown;
53227
- });
53465
+ }, [...writers.values()]);
53228
53466
  const bytes = Buffer.byteLength(markdown);
53229
53467
  const used = [...writers.values()].filter((writer) => writer.count > 0);
53230
53468
  const files = used.flatMap((writer) => writer.files);
@@ -53290,12 +53528,12 @@ import { basename as basename5, extname as extname6, join as join23 } from "node
53290
53528
  var PASTEBOARD_NAME_ENV = "APPLE_NOTES_MCP_PASTEBOARD_NAME";
53291
53529
  var MAX_PASTEBOARD_BYTES = 64 * 1024 * 1024;
53292
53530
  var PASTEBOARD_DATA_TYPES = [
53531
+ { type: "com.adobe.pdf", ext: "pdf", label: "document" },
53293
53532
  { type: "public.png", ext: "png", label: "image" },
53294
53533
  { type: "public.jpeg", ext: "jpg", label: "image" },
53295
53534
  { type: "public.heic", ext: "heic", label: "image" },
53296
53535
  { type: "com.compuserve.gif", ext: "gif", label: "image" },
53297
- { type: "public.tiff", ext: "tiff", label: "image" },
53298
- { type: "com.adobe.pdf", ext: "pdf", label: "document" }
53536
+ { type: "public.tiff", ext: "tiff", label: "image" }
53299
53537
  ];
53300
53538
  var PASTEBOARD_ACCESS_BEHAVIORS = {
53301
53539
  0: "default",
@@ -53322,15 +53560,26 @@ function run(argv) {
53322
53560
  var items = pb.pasteboardItems;
53323
53561
  var itemCount = items && !items.isNil() ? Number(items.count) : 0;
53324
53562
  var fileUrls = [];
53563
+ var dataItems = 0;
53325
53564
  for (var k = 0; k < itemCount; k++) {
53326
53565
  var item = items.objectAtIndex(k);
53327
53566
  var itemTypes = ObjC.deepUnwrap(item.types) || [];
53328
- 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
+ }
53329
53576
  var value = ObjC.unwrap(item.stringForType("public.file-url"));
53330
53577
  if (value) fileUrls.push(value);
53331
53578
  }
53332
53579
  if (fileUrls.length > 1)
53333
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 });
53334
53583
  if (fileUrls.length === 1) {
53335
53584
  var url = $.NSURL.URLWithString(fileUrls[0]);
53336
53585
  if (url && !url.isNil() && url.isFileURL) {
@@ -53361,6 +53610,7 @@ var ENVELOPE_CODES = {
53361
53610
  pasteboard_changed: "operation_failed",
53362
53611
  unsupported_content: "validation_error",
53363
53612
  multiple_files: "validation_error",
53613
+ multiple_items: "validation_error",
53364
53614
  too_large: "validation_error",
53365
53615
  write_failed: "operation_failed",
53366
53616
  file_unreadable: "validation_error"
@@ -53387,6 +53637,7 @@ var MESSAGES = {
53387
53637
  pasteboard_changed: "The pasteboard changed while it was being read. Try again.",
53388
53638
  unsupported_content: `The pasteboard holds no supported content: copy ${SUPPORTED}. Text belongs in append-to-note, not in an attachment.`,
53389
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.",
53390
53641
  too_large: "The pasteboard contents exceed the 64 MiB attachment limit.",
53391
53642
  write_failed: "Could not write the pasteboard contents to a temporary file.",
53392
53643
  file_unreadable: "The copied file could not be read as a regular file of at most 64 MiB."
@@ -53398,13 +53649,10 @@ function replyError(code, reply) {
53398
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;
53399
53650
  return new PasteboardError(code, message, { accessBehavior });
53400
53651
  }
53401
- if (code === "multiple_files") {
53652
+ if (code === "multiple_files" || code === "multiple_items") {
53402
53653
  const count = typeof reply.count === "number" ? reply.count : void 0;
53403
- return new PasteboardError(
53404
- code,
53405
- count ? `The pasteboard holds ${count} copied files. Copy exactly one file, or attach each file with add-attachment.` : MESSAGES[code],
53406
- count ? { count } : {}
53407
- );
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 } : {});
53408
53656
  }
53409
53657
  if (code === "unsupported_content" && Array.isArray(reply.types)) {
53410
53658
  const types = reply.types.filter((t) => typeof t === "string").slice(0, 20);
@@ -53617,7 +53865,10 @@ function registerDirectOperations(server2, manager) {
53617
53865
  {
53618
53866
  id: folderId,
53619
53867
  expectedName: external_exports.string().max(1e3),
53620
- 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,
53621
53872
  newName: external_exports.string().min(1).max(1e3)
53622
53873
  },
53623
53874
  (args) => ({
@@ -54020,7 +54271,9 @@ function readFolderStoreFacts(pk, dbPath2 = NOTE_STORE_PATH) {
54020
54271
 
54021
54272
  // src/tools/folderDelete.ts
54022
54273
  var folderIdSchema = external_exports.string().max(2e3).transform(looseIdTransform("ICFolder"));
54023
- 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
+ );
54024
54277
  var revisionSchema = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
54025
54278
  var defaultDeps = {
54026
54279
  readStore: (pk) => readFolderStoreFacts(pk),
@@ -58005,14 +58258,17 @@ var strokeSchema = external_exports.object({
58005
58258
  force: external_exports.number()
58006
58259
  })
58007
58260
  ).optional(),
58008
- transformApplied: external_exports.boolean().optional()
58261
+ transformApplied: external_exports.boolean().optional(),
58262
+ masked: external_exports.boolean().optional(),
58263
+ pointsTruncated: external_exports.boolean().optional()
58009
58264
  }).strip();
58010
58265
  var decodedDrawingSchema = external_exports.object({
58011
58266
  status: external_exports.literal("ok"),
58012
58267
  strokeCount: external_exports.number().int(),
58013
58268
  strokes: external_exports.array(strokeSchema),
58014
58269
  truncated: external_exports.boolean(),
58015
- bounds
58270
+ bounds,
58271
+ hiddenStrokeCount: external_exports.number().int().optional()
58016
58272
  });
58017
58273
  function decodeRow(row, format, includePoints, deps) {
58018
58274
  const base = {
@@ -58045,7 +58301,8 @@ function decodeRow(row, format, includePoints, deps) {
58045
58301
  status: "ok",
58046
58302
  strokeCount: decoded.strokeCount,
58047
58303
  bounds: decoded.bounds,
58048
- truncated: decoded.truncated
58304
+ truncated: decoded.truncated,
58305
+ ...decoded.hiddenStrokeCount ? { hiddenStrokeCount: decoded.hiddenStrokeCount } : {}
58049
58306
  };
58050
58307
  if (format !== "json") result.svg = drawingToSvg(strokes, decoded.bounds);
58051
58308
  if (format !== "svg")
@@ -58070,6 +58327,28 @@ function getNoteDrawings(noteId3, options = {}) {
58070
58327
  drawings
58071
58328
  };
58072
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
+ }
58073
58352
  function formatNoteDrawings(result) {
58074
58353
  if (result.drawingCount === 0) return `No classic PencilKit drawings in note ${result.id}.`;
58075
58354
  const lines = [
@@ -58077,7 +58356,7 @@ function formatNoteDrawings(result) {
58077
58356
  ];
58078
58357
  for (const d of result.drawings)
58079
58358
  lines.push(
58080
- 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}`
58081
58360
  );
58082
58361
  return lines.join("\n");
58083
58362
  }
@@ -58177,9 +58456,6 @@ function readAudioAssets(noteId3, options = {}) {
58177
58456
  };
58178
58457
  });
58179
58458
  }
58180
- function countWords2(text2) {
58181
- return text2.split(/\s+/u).filter((w) => /[\p{L}\p{N}]/u.test(w)).length;
58182
- }
58183
58459
 
58184
58460
  // src/services/noteTranscription.ts
58185
58461
  var DEFAULT_TRANSCRIPTION_LOCALE = "en-US";
@@ -58194,6 +58470,7 @@ var BUDGET_MARGIN_MS = 1e4;
58194
58470
  var MIN_TAKE_SECONDS = 5;
58195
58471
  var CALL_WIDE_CODES = /* @__PURE__ */ new Set([
58196
58472
  "permission_required",
58473
+ "permission_not_requested",
58197
58474
  "asset_unavailable",
58198
58475
  "unsupported_locale",
58199
58476
  "speech_unavailable"
@@ -58272,7 +58549,7 @@ async function transcribeTake(take, ctx) {
58272
58549
  status: complete ? "ok" : text2 ? "partial" : "indeterminate",
58273
58550
  ...complete ? {} : { code: "incomplete", message: stopReason ?? "stopped early" },
58274
58551
  ...durationSeconds !== void 0 ? { durationSeconds } : {},
58275
- wordCount: countWords2(text2),
58552
+ wordCount: countWords(text2),
58276
58553
  ...engine ? { engine } : {}
58277
58554
  },
58278
58555
  text: text2
@@ -58299,7 +58576,7 @@ async function transcribeRecording(asset, includeText, ctx) {
58299
58576
  status,
58300
58577
  ...status !== "ok" && firstProblem?.code ? { code: firstProblem.code, message: firstProblem.message } : {},
58301
58578
  ...asset.durationSeconds !== null ? { durationSeconds: Math.round(asset.durationSeconds) } : {},
58302
- wordCount: countWords2(transcript),
58579
+ wordCount: countWords(transcript),
58303
58580
  ...includeText ? { transcript } : {},
58304
58581
  takes: outcomes.map((o) => o.take)
58305
58582
  };
@@ -58354,24 +58631,29 @@ async function transcribeNoteAudio(noteId3, options = {}) {
58354
58631
  }
58355
58632
  function fitTranscriptions(result, maxBytes, measure = (r) => Buffer.byteLength(JSON.stringify(r))) {
58356
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
+ });
58357
58644
  let next = result;
58358
58645
  for (let share = 0.5; measure(next) > maxBytes && share > 1e-4; share /= 2) {
58359
- next = {
58360
- ...result,
58361
- recordings: result.recordings.map(
58362
- (r) => r.transcript ? {
58363
- ...r,
58364
- transcript: r.transcript.slice(0, Math.floor(r.transcript.length * share)),
58365
- transcriptTruncated: true
58366
- } : r
58367
- )
58368
- };
58646
+ next = shorten(share);
58369
58647
  }
58648
+ if (measure(next) > maxBytes) next = { ...shorten(0), responseOversized: true };
58370
58649
  return next;
58371
58650
  }
58372
58651
  function formatTranscription(result) {
58373
58652
  if (result.recordingCount === 0) return `No audio attachments in note ${result.id}.`;
58374
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
+ ] : [],
58375
58657
  `${result.recordingCount} audio attachment${result.recordingCount === 1 ? "" : "s"} in note ${result.id} (${result.status}, locale ${result.locale}):`
58376
58658
  ];
58377
58659
  result.recordings.forEach((r, i) => {
@@ -59243,10 +59525,11 @@ function prepareDeleteGuards(args) {
59243
59525
  if (guardNoteId === void 0 !== (expectedGuardContentHash === void 0)) {
59244
59526
  return { error: "Pass guardNoteId and expectedGuardContentHash together." };
59245
59527
  }
59246
- 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)) {
59247
59530
  return { error: "A guard note must be a different note from the one being deleted." };
59248
59531
  }
59249
- if (guardNoteId !== void 0 && guardNoteId === requireActiveNoteId) {
59532
+ if (same(guardNoteId, requireActiveNoteId)) {
59250
59533
  return { error: "requireActiveNoteId repeats guardNoteId; pass only guardNoteId." };
59251
59534
  }
59252
59535
  const prepared = { guards: [], labels: [] };
@@ -59593,7 +59876,7 @@ ${noteList}${truncationNote}${scanNote}${wordCountNote}${syncNote}`,
59593
59876
  registerTool(
59594
59877
  "query-notes",
59595
59878
  {
59596
- 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.',
59597
59880
  inputSchema: {
59598
59881
  query: external_exports.string().min(1, "A query expression is required").max(MAX.QUERY).describe(
59599
59882
  'Boolean query expression, e.g. `folder:"Work Projects" has:checklist -checklist:done`'
@@ -59646,7 +59929,7 @@ registerTool(
59646
59929
  }
59647
59930
  if (result.unreadable > 0) {
59648
59931
  notes.push(
59649
- `\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.`
59650
59933
  );
59651
59934
  }
59652
59935
  const footer = notes.length ? `
@@ -60049,7 +60332,7 @@ registerTool(
60049
60332
  if (!note) return notFoundResponse(`Note with ID "${id2}" not found`);
60050
60333
  const body = notesManager.getNoteContentById(id2);
60051
60334
  if (!body) return errorResponse(`Failed to read content of note "${note.title}"`);
60052
- const rich = readRichNote(id2);
60335
+ const rich = readRichNote(id2, { skipUnsafeLinks: true });
60053
60336
  const tables = (rich.objectData || []).filter((object3) => object3.type?.includes("table")).map((object3) => {
60054
60337
  try {
60055
60338
  return {
@@ -61659,6 +61942,7 @@ registerTool(
61659
61942
  height: external_exports.number().optional(),
61660
61943
  bytes: external_exports.number().optional(),
61661
61944
  source: external_exports.enum(["fallback", "preview"]).optional(),
61945
+ stale: external_exports.boolean().optional(),
61662
61946
  attachmentId: external_exports.string().optional(),
61663
61947
  identifier: external_exports.string().optional(),
61664
61948
  kind: external_exports.enum(["paper", "drawing"]).optional(),
@@ -61667,8 +61951,9 @@ registerTool(
61667
61951
  },
61668
61952
  withErrorHandling(({ noteId: noteId3, savePath, attachmentId }) => {
61669
61953
  const r = notesManager.exportPaperImageById(noteId3, savePath, attachmentId);
61954
+ const stale = r.source === "fallback" && r.drawing.fallbackImageStale;
61670
61955
  return successResponse(
61671
- `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." : ""),
61672
61957
  {
61673
61958
  savedPath: r.savedPath,
61674
61959
  format: r.format,
@@ -61676,6 +61961,7 @@ registerTool(
61676
61961
  height: r.height,
61677
61962
  bytes: r.bytes,
61678
61963
  source: r.source,
61964
+ ...stale ? { stale } : {},
61679
61965
  attachmentId: attachmentCoreDataId(noteId3, r.drawing.pk),
61680
61966
  identifier: r.drawing.identifier,
61681
61967
  kind: r.drawing.kind,
@@ -61688,7 +61974,7 @@ registerTool(
61688
61974
  "export-attachments",
61689
61975
  {
61690
61976
  description: `Use when: copying every file attachment of one note (or only its lead visual) into a directory on disk.
61691
- 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.
61692
61978
  Do not use when: exporting one attachment to an exact path (save-attachment) or reading bytes inline (fetch-attachment).
61693
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.`,
61694
61980
  inputSchema: {
@@ -61700,6 +61986,7 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61700
61986
  exportDir: external_exports.string().optional(),
61701
61987
  exported: external_exports.number().optional(),
61702
61988
  previews: external_exports.number().optional(),
61989
+ fallbacks: external_exports.number().optional(),
61703
61990
  skipped: external_exports.number().optional(),
61704
61991
  failed: external_exports.number().optional(),
61705
61992
  results: external_exports.array(external_exports.object({}).passthrough()).optional(),
@@ -61714,12 +62001,14 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61714
62001
  }));
61715
62002
  const exported = results.filter((x) => x.exportedKind !== null).length;
61716
62003
  const previews = results.filter((x) => x.exportedKind === "preview").length;
62004
+ const fallbacks = results.filter((x) => x.exportedKind === "fallback").length;
61717
62005
  const failed = results.filter((x) => x.error).length;
61718
62006
  const skipped = results.length - exported - failed;
61719
62007
  const structured = {
61720
62008
  exportDir: r.exportDir,
61721
62009
  exported,
61722
62010
  previews,
62011
+ fallbacks,
61723
62012
  skipped,
61724
62013
  failed,
61725
62014
  results
@@ -61733,7 +62022,7 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61733
62022
  );
61734
62023
  }
61735
62024
  }
61736
- 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).`;
61737
62026
  if (exported === 0 && failed > 0) {
61738
62027
  const error2 = errorResponse(summary, new CodedError(summary, { code: "operation_failed" }));
61739
62028
  return { ...error2, structuredContent: { ...structured, ...error2.structuredContent } };
@@ -61914,7 +62203,8 @@ var exportStatsSchema = external_exports.object({
61914
62203
  unavailable: external_exports.number(),
61915
62204
  tables: external_exports.number(),
61916
62205
  unreadableTables: external_exports.number(),
61917
- unreferenced: external_exports.number()
62206
+ unreferenced: external_exports.number(),
62207
+ staleRenderings: external_exports.number().optional()
61918
62208
  });
61919
62209
  registerTool(
61920
62210
  "export-notes-markdown",
@@ -61940,6 +62230,7 @@ registerTool(
61940
62230
  outputSchema: {
61941
62231
  format: external_exports.string().optional(),
61942
62232
  count: external_exports.number().optional(),
62233
+ truncated: external_exports.boolean().optional(),
61943
62234
  bytes: external_exports.number().optional(),
61944
62235
  markdown: external_exports.string().optional(),
61945
62236
  output: external_exports.string().optional(),
@@ -61978,7 +62269,7 @@ registerTool(
61978
62269
  );
61979
62270
  }
61980
62271
  const warned = receipt.warnings?.length ? `; ${receipt.warnings.length + (receipt.warningsOmitted ?? 0)} warning(s)` : "";
61981
- 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" : "");
61982
62273
  if (receipt.output)
61983
62274
  return successResponse(
61984
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}.`,
@@ -62009,6 +62300,7 @@ registerTool(
62009
62300
  outputSchema: {
62010
62301
  format: external_exports.string().optional(),
62011
62302
  count: external_exports.number().optional(),
62303
+ truncated: external_exports.boolean().optional(),
62012
62304
  bytes: external_exports.number().optional(),
62013
62305
  output: external_exports.string().optional(),
62014
62306
  assets: external_exports.object({ dir: external_exports.string(), files: external_exports.number() }).optional(),
@@ -62031,7 +62323,7 @@ registerTool(
62031
62323
  return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
62032
62324
  }
62033
62325
  const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;
62034
- 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" : "");
62035
62327
  return successResponse(
62036
62328
  `Wrote ${receipt.count} note(s) as HTML (${receipt.bytes} bytes) to ${receipt.output}${assets}${skipped}.`,
62037
62329
  { ...receipt }
@@ -62214,27 +62506,32 @@ registerTool(
62214
62506
  drawingCount: external_exports.number().optional(),
62215
62507
  status: external_exports.string().optional(),
62216
62508
  drawings: external_exports.array(external_exports.object({}).passthrough()).optional(),
62217
- pointsOmitted: external_exports.boolean().optional()
62509
+ pointsOmitted: external_exports.boolean().optional(),
62510
+ svgOmitted: external_exports.boolean().optional()
62218
62511
  }
62219
62512
  },
62220
62513
  withErrorHandling(({ id: id2, format, includePoints }) => {
62221
- let result = getNoteDrawings(id2, { format, includePoints });
62222
- let pointsOmitted = false;
62223
- if (Buffer.byteLength(JSON.stringify(result)) > exportMaxResponseBytes() && includePoints !== false && format !== "svg") {
62224
- result = getNoteDrawings(id2, { format, includePoints: false });
62225
- pointsOmitted = true;
62226
- }
62227
- 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)." : "");
62228
62524
  return successResponse(text2, {
62229
62525
  ...result,
62230
- ...pointsOmitted ? { pointsOmitted } : {}
62526
+ ...pointsOmitted ? { pointsOmitted } : {},
62527
+ ...svgOmitted ? { svgOmitted } : {}
62231
62528
  });
62232
62529
  }, "Error reading drawings")
62233
62530
  );
62234
62531
  registerTool(
62235
62532
  "transcribe-note-audio",
62236
62533
  {
62237
- 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.",
62238
62535
  inputSchema: {
62239
62536
  id: noteIdInput,
62240
62537
  locale: external_exports.string().max(35).optional().describe('BCP-47 language of the speech, e.g. "en-US" (default), "it-IT", "fr-FR"'),
@@ -62252,11 +62549,13 @@ registerTool(
62252
62549
  locale: external_exports.string().optional(),
62253
62550
  status: external_exports.string().optional(),
62254
62551
  recordingCount: external_exports.number().optional(),
62255
- 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()
62256
62554
  }
62257
62555
  },
62258
62556
  withAsyncErrorHandling(async (params, signal) => {
62259
62557
  const { id: id2, locale, attachmentId, includeText, downloadAssets, maxSeconds } = params;
62558
+ const toResponse = (r) => successResponse(formatTranscription(r), r);
62260
62559
  const result = fitTranscriptions(
62261
62560
  await transcribeNoteAudio(id2, {
62262
62561
  locale,
@@ -62266,12 +62565,10 @@ registerTool(
62266
62565
  maxSeconds,
62267
62566
  signal
62268
62567
  }),
62269
- exportMaxResponseBytes()
62270
- );
62271
- return successResponse(
62272
- formatTranscription(result),
62273
- result
62568
+ exportMaxResponseBytes(),
62569
+ (r) => Buffer.byteLength(JSON.stringify(toResponse(r)))
62274
62570
  );
62571
+ return toResponse(result);
62275
62572
  }, "Error transcribing audio")
62276
62573
  );
62277
62574
  registerTool(
@@ -62386,13 +62683,14 @@ process.on("uncaughtException", (err) => {
62386
62683
  process.on("unhandledRejection", (reason) => {
62387
62684
  console.error("[unhandledRejection]", reason);
62388
62685
  });
62389
- 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);
62390
62688
  for (const sig of ["SIGINT", "SIGTERM"]) {
62391
62689
  process.on(sig, shutdown);
62392
62690
  }
62393
62691
  process.stdin.on("end", shutdown);
62394
62692
  process.stdin.on("close", shutdown);
62395
- var transport = withJsonSchema2020_12(new StdioServerTransport());
62693
+ var transport = trackRequests(withJsonSchema2020_12(new StdioServerTransport()), inFlight);
62396
62694
  await server.connect(transport);
62397
62695
  /*! Bundled license information:
62398
62696