apple-notes-mcp 2.9.26 → 2.9.28

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
@@ -417,11 +417,11 @@ var require_codegen = __commonJS({
417
417
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
418
418
  return `${varKind} ${this.name}${rhs};` + _n;
419
419
  }
420
- optimizeNames(names, constants10) {
420
+ optimizeNames(names, constants9) {
421
421
  if (!names[this.name.str])
422
422
  return;
423
423
  if (this.rhs)
424
- this.rhs = optimizeExpr(this.rhs, names, constants10);
424
+ this.rhs = optimizeExpr(this.rhs, names, constants9);
425
425
  return this;
426
426
  }
427
427
  get names() {
@@ -438,10 +438,10 @@ var require_codegen = __commonJS({
438
438
  render({ _n }) {
439
439
  return `${this.lhs} = ${this.rhs};` + _n;
440
440
  }
441
- optimizeNames(names, constants10) {
441
+ optimizeNames(names, constants9) {
442
442
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
443
443
  return;
444
- this.rhs = optimizeExpr(this.rhs, names, constants10);
444
+ this.rhs = optimizeExpr(this.rhs, names, constants9);
445
445
  return this;
446
446
  }
447
447
  get names() {
@@ -502,8 +502,8 @@ var require_codegen = __commonJS({
502
502
  optimizeNodes() {
503
503
  return `${this.code}` ? this : void 0;
504
504
  }
505
- optimizeNames(names, constants10) {
506
- this.code = optimizeExpr(this.code, names, constants10);
505
+ optimizeNames(names, constants9) {
506
+ this.code = optimizeExpr(this.code, names, constants9);
507
507
  return this;
508
508
  }
509
509
  get names() {
@@ -532,12 +532,12 @@ var require_codegen = __commonJS({
532
532
  }
533
533
  return nodes.length > 0 ? this : void 0;
534
534
  }
535
- optimizeNames(names, constants10) {
535
+ optimizeNames(names, constants9) {
536
536
  const { nodes } = this;
537
537
  let i = nodes.length;
538
538
  while (i--) {
539
539
  const n = nodes[i];
540
- if (n.optimizeNames(names, constants10))
540
+ if (n.optimizeNames(names, constants9))
541
541
  continue;
542
542
  subtractNames(names, n.names);
543
543
  nodes.splice(i, 1);
@@ -590,12 +590,12 @@ var require_codegen = __commonJS({
590
590
  return void 0;
591
591
  return this;
592
592
  }
593
- optimizeNames(names, constants10) {
593
+ optimizeNames(names, constants9) {
594
594
  var _a;
595
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants10);
596
- if (!(super.optimizeNames(names, constants10) || this.else))
595
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants9);
596
+ if (!(super.optimizeNames(names, constants9) || this.else))
597
597
  return;
598
- this.condition = optimizeExpr(this.condition, names, constants10);
598
+ this.condition = optimizeExpr(this.condition, names, constants9);
599
599
  return this;
600
600
  }
601
601
  get names() {
@@ -618,10 +618,10 @@ var require_codegen = __commonJS({
618
618
  render(opts) {
619
619
  return `for(${this.iteration})` + super.render(opts);
620
620
  }
621
- optimizeNames(names, constants10) {
622
- if (!super.optimizeNames(names, constants10))
621
+ optimizeNames(names, constants9) {
622
+ if (!super.optimizeNames(names, constants9))
623
623
  return;
624
- this.iteration = optimizeExpr(this.iteration, names, constants10);
624
+ this.iteration = optimizeExpr(this.iteration, names, constants9);
625
625
  return this;
626
626
  }
627
627
  get names() {
@@ -657,10 +657,10 @@ var require_codegen = __commonJS({
657
657
  render(opts) {
658
658
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
659
659
  }
660
- optimizeNames(names, constants10) {
661
- if (!super.optimizeNames(names, constants10))
660
+ optimizeNames(names, constants9) {
661
+ if (!super.optimizeNames(names, constants9))
662
662
  return;
663
- this.iterable = optimizeExpr(this.iterable, names, constants10);
663
+ this.iterable = optimizeExpr(this.iterable, names, constants9);
664
664
  return this;
665
665
  }
666
666
  get names() {
@@ -702,11 +702,11 @@ var require_codegen = __commonJS({
702
702
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
703
703
  return this;
704
704
  }
705
- optimizeNames(names, constants10) {
705
+ optimizeNames(names, constants9) {
706
706
  var _a, _b;
707
- super.optimizeNames(names, constants10);
708
- (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants10);
709
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants10);
707
+ super.optimizeNames(names, constants9);
708
+ (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants9);
709
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants9);
710
710
  return this;
711
711
  }
712
712
  get names() {
@@ -1007,7 +1007,7 @@ var require_codegen = __commonJS({
1007
1007
  function addExprNames(names, from) {
1008
1008
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
1009
1009
  }
1010
- function optimizeExpr(expr, names, constants10) {
1010
+ function optimizeExpr(expr, names, constants9) {
1011
1011
  if (expr instanceof code_1.Name)
1012
1012
  return replaceName(expr);
1013
1013
  if (!canOptimize(expr))
@@ -1022,14 +1022,14 @@ var require_codegen = __commonJS({
1022
1022
  return items;
1023
1023
  }, []));
1024
1024
  function replaceName(n) {
1025
- const c = constants10[n.str];
1025
+ const c = constants9[n.str];
1026
1026
  if (c === void 0 || names[n.str] !== 1)
1027
1027
  return n;
1028
1028
  delete names[n.str];
1029
1029
  return c;
1030
1030
  }
1031
1031
  function canOptimize(e) {
1032
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants10[c.str] !== void 0);
1032
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants9[c.str] !== void 0);
1033
1033
  }
1034
1034
  }
1035
1035
  function subtractNames(names, from) {
@@ -7422,7 +7422,7 @@ var require_DOMException = __commonJS({
7422
7422
  "INVALID_NODE_TYPE_ERR (24): the supplied node is invalid or has an invalid ancestor for this operation",
7423
7423
  "DATA_CLONE_ERR (25): the object can not be cloned."
7424
7424
  ];
7425
- var constants10 = {
7425
+ var constants9 = {
7426
7426
  INDEX_SIZE_ERR,
7427
7427
  DOMSTRING_SIZE_ERR: 2,
7428
7428
  // historical
@@ -7461,8 +7461,8 @@ var require_DOMException = __commonJS({
7461
7461
  this.name = names[code];
7462
7462
  }
7463
7463
  DOMException.prototype.__proto__ = Error.prototype;
7464
- for (c in constants10) {
7465
- v = { value: constants10[c] };
7464
+ for (c in constants9) {
7465
+ v = { value: constants9[c] };
7466
7466
  Object.defineProperty(DOMException, c, v);
7467
7467
  Object.defineProperty(DOMException.prototype, c, v);
7468
7468
  }
@@ -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}`);
@@ -39853,6 +39857,10 @@ var CodedError = class extends Error {
39853
39857
  };
39854
39858
  var RULES = [
39855
39859
  { code: "timeout_indeterminate", pattern: /timed out|\bETIMEDOUT\b/i },
39860
+ // A scope guard is checked before the write runs, so nothing was written:
39861
+ // the note is not where it was reviewed. Its advice to read the note before
39862
+ // retrying must not make it look like an unverified write.
39863
+ { code: "revision_conflict", pattern: /\bScope guard failed:/ },
39856
39864
  {
39857
39865
  code: "verification_failed",
39858
39866
  pattern: /\buncertain\b|may have (?:been|succeeded)|\baccepted (?:the|an) \w+, but|\bnot verified\b|Do not retry automatically|before any retry|read (?:the )?(?:exact )?(?:note|ID)\b[^.]*before retr/i
@@ -39881,7 +39889,7 @@ var RULES = [
39881
39889
  },
39882
39890
  {
39883
39891
  code: "validation_error",
39884
- pattern: /\bis required\b|required\b|\bInvalid\b|\bmust\b|\bProvide\b|No (?:note IDs|reviewed notes) provided|response limit|Refusing to write|cannot be resolved|at most \d|between \d+ and \d+|supports the (?:end|default)|Use a distinctive|Use create-table|too large|equal cell counts|Folder path is empty/i
39892
+ pattern: /\bis required\b|required\b|\bInvalid\b|\bmust\b|\bProvide\b|No (?:note IDs|reviewed notes) provided|response limit|Refusing to (?:read|write)|cannot be resolved|at most \d|between \d+ and \d+|supports the (?:end|default)|Use a distinctive|Use create-table|too large|equal cell counts|Folder path is empty/i
39885
39893
  }
39886
39894
  ];
39887
39895
  var NOTHING_WRITTEN = /nothing (?:was )?(?:changed|created|written)|no replacement started|No content was (?:replaced|appended)|Nothing was created/i;
@@ -40034,7 +40042,7 @@ function styleValue(field) {
40034
40042
  }
40035
40043
  return Buffer.from(field.value).toString("hex");
40036
40044
  }
40037
- function parseRichNote(data, nativeTags = []) {
40045
+ function parseRichNote(data, nativeTags = [], options = {}) {
40038
40046
  const doc = decodeMessage(data);
40039
40047
  const wrapper = embeddedMessage(getField(doc, 2));
40040
40048
  const body = wrapper && embeddedMessage(getField(wrapper, 3));
@@ -40074,7 +40082,7 @@ function parseRichNote(data, nativeTags = []) {
40074
40082
  if (varintValue(getField(fields, 14))) lossy.add("highlight");
40075
40083
  }
40076
40084
  const url = stringValue(getField(fields, 9));
40077
- if (url) {
40085
+ if (url && !(options.skipUnsafeLinks && !safeUrl(url))) {
40078
40086
  if (!safeUrl(url)) throw new Error("Unsupported link scheme in note");
40079
40087
  const previous = links.at(-1);
40080
40088
  if (previous?.url === url && previous.start + previous.length === position) {
@@ -40129,7 +40137,7 @@ function parseRichNote(data, nativeTags = []) {
40129
40137
  ...lossy.size ? { htmlLossyFormatting: HTML_LOSSY_ORDER.filter((f) => lossy.has(f)) } : {}
40130
40138
  };
40131
40139
  }
40132
- function readRichNote(id2) {
40140
+ function readRichNote(id2, options = {}) {
40133
40141
  const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]+)$/i.exec(id2)?.[1];
40134
40142
  if (!pk) throw new Error("Invalid exact note ID");
40135
40143
  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 +40152,9 @@ function readRichNote(id2) {
40144
40152
  if (!tags || Array.isArray(tags) || typeof tags !== "object" || Object.values(tags).some((tag) => typeof tag !== "string"))
40145
40153
  throw new Error("Invalid native tags");
40146
40154
  const rich = parseRichNote(
40147
- gunzipSync2(Buffer.from(rows[0], "hex"), { maxOutputLength: 32 * 1024 * 1024 })
40155
+ gunzipSync2(Buffer.from(rows[0], "hex"), { maxOutputLength: 32 * 1024 * 1024 }),
40156
+ [],
40157
+ options
40148
40158
  );
40149
40159
  const tagMap = tags;
40150
40160
  const objectData = JSON.parse(rows[2] || "[]");
@@ -41660,6 +41670,7 @@ function buildAttachmentRowsSql(notePk, columns) {
41660
41670
  `'identifier', a.ZIDENTIFIER`,
41661
41671
  `'uti', ${col3("a", "ZTYPEUTI")}`,
41662
41672
  `'parentPk', ${parent}`,
41673
+ `'parentDeleted', ${parent !== "NULL" && columns.has("ZMARKEDFORDELETION") ? `(SELECT COALESCE(p.ZMARKEDFORDELETION, 0) FROM ZICCLOUDSYNCINGOBJECT p WHERE p.Z_PK = ${parent})` : "0"}`,
41663
41674
  `'filename', ${col3("a", "ZFILENAME")}`,
41664
41675
  `'mediaIdentifier', m.ZIDENTIFIER`,
41665
41676
  `'mediaFilename', ${col3("m", "ZFILENAME")}`,
@@ -41718,6 +41729,7 @@ function parseAttachmentRows(json2) {
41718
41729
  identifier,
41719
41730
  uti: toStringOrNull(r.uti),
41720
41731
  parentPk: toIntOrNull(r.parentPk),
41732
+ ...toIntOrNull(r.parentDeleted) ? { parentDeleted: true } : {},
41721
41733
  filename: toStringOrNull(r.filename),
41722
41734
  mediaIdentifier: toStringOrNull(r.mediaIdentifier),
41723
41735
  mediaFilename: toStringOrNull(r.mediaFilename),
@@ -41835,7 +41847,7 @@ function generationDirs(base, accountDir) {
41835
41847
  const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
41836
41848
  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
41849
  }
41838
- function fallbackFiles(accountDir, rootName, identifier, generation, names) {
41850
+ function fallbackFiles(accountDir, rootName, identifier, generation, names, onStale) {
41839
41851
  const base = join8(accountDir, rootName, identifier);
41840
41852
  const found = [];
41841
41853
  const add = (candidate) => {
@@ -41844,12 +41856,14 @@ function fallbackFiles(accountDir, rootName, identifier, generation, names) {
41844
41856
  };
41845
41857
  const gen = safeComponent(generation);
41846
41858
  if (gen) for (const name of names) add(join8(base, gen, name));
41859
+ const recorded = found.length;
41847
41860
  if (isDirectory(base)) {
41848
41861
  for (const dir of generationDirs(base, accountDir))
41849
41862
  for (const name of names) add(join8(dir, name));
41850
41863
  for (const name of names) add(join8(base, name));
41851
41864
  }
41852
41865
  for (const name of names) add(join8(accountDir, rootName, `${identifier}${extname(name)}`));
41866
+ if (gen && recorded === 0 && found.length > 0) onStale?.();
41853
41867
  return found;
41854
41868
  }
41855
41869
  function previewPixelArea(name) {
@@ -41895,7 +41909,7 @@ function previewPaths(accountDir, identifier, entries2) {
41895
41909
  }
41896
41910
  return files;
41897
41911
  }
41898
- function assetPathsFor(accountDir, row) {
41912
+ function assetPathsFor(accountDir, row, onStale) {
41899
41913
  const found = [];
41900
41914
  const add = (candidate) => {
41901
41915
  const real = realInside(candidate, accountDir);
@@ -41912,18 +41926,28 @@ function assetPathsFor(accountDir, row) {
41912
41926
  if (!id2) return found;
41913
41927
  const ownName = safeComponent(row.filename);
41914
41928
  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
- ]))
41929
+ for (const file of fallbackFiles(
41930
+ accountDir,
41931
+ "FallbackImages",
41932
+ id2,
41933
+ row.fallbackImageGeneration,
41934
+ ["FallbackImage.png", "FallbackImage.jpg"],
41935
+ onStale
41936
+ ))
41919
41937
  add(file);
41920
- for (const file of fallbackFiles(accountDir, "FallbackPDFs", id2, row.fallbackPdfGeneration, [
41921
- "FallbackPDF.pdf"
41922
- ]))
41938
+ for (const file of fallbackFiles(
41939
+ accountDir,
41940
+ "FallbackPDFs",
41941
+ id2,
41942
+ row.fallbackPdfGeneration,
41943
+ ["FallbackPDF.pdf"],
41944
+ onStale
41945
+ ))
41923
41946
  add(file);
41924
41947
  return found;
41925
41948
  }
41926
- function assembleAttachmentAssets(rows, bodyOrder, containerDir = NOTES_CONTAINER_DIR) {
41949
+ function assembleAttachmentAssets(allRows, bodyOrder, containerDir = NOTES_CONTAINER_DIR) {
41950
+ const rows = allRows.filter((r) => !r.parentDeleted);
41927
41951
  const byPk = new Map(rows.map((r) => [r.pk, r]));
41928
41952
  const roots = rows.filter((r) => r.parentPk === null || !byPk.has(r.parentPk));
41929
41953
  const bodyIndex = /* @__PURE__ */ new Map();
@@ -41946,10 +41970,11 @@ function assembleAttachmentAssets(rows, bodyOrder, containerDir = NOTES_CONTAINE
41946
41970
  const accountDir = accountDirFor(row.accountIdentifier ?? parent?.accountIdentifier ?? null);
41947
41971
  let assetPaths = [];
41948
41972
  let previews = [];
41973
+ let fallbackStale = false;
41949
41974
  if (accountDir) {
41950
41975
  if (!previewEntries.has(accountDir))
41951
41976
  previewEntries.set(accountDir, listPreviewEntries(accountDir));
41952
- assetPaths = assetPathsFor(accountDir, row);
41977
+ assetPaths = assetPathsFor(accountDir, row, () => fallbackStale = true);
41953
41978
  previews = previewPaths(accountDir, row.identifier, previewEntries.get(accountDir));
41954
41979
  }
41955
41980
  const previewPath = previews[0] ?? null;
@@ -41962,6 +41987,7 @@ function assembleAttachmentAssets(rows, bodyOrder, containerDir = NOTES_CONTAINE
41962
41987
  filename: row.filename ?? row.mediaFilename,
41963
41988
  bodyIndex: parent ? null : indexOf(row),
41964
41989
  assetPaths,
41990
+ ...fallbackStale ? { fallbackStale: true } : {},
41965
41991
  previewPath,
41966
41992
  paths: previewPath ? [...assetPaths, previewPath] : [...assetPaths]
41967
41993
  };
@@ -42047,6 +42073,11 @@ function copyFileExclusive(src, dest) {
42047
42073
  function exportFileName(record2, source, kind) {
42048
42074
  const ext = extname(source);
42049
42075
  const id2 = safeComponent(record2.identifier) ?? "attachment";
42076
+ if (kind === "fallback") {
42077
+ const stored2 = safeComponent(record2.filename ? basename(record2.filename) : null);
42078
+ const stem = stored2 && !GENERIC_FILE_NAMES.has(stored2.toLowerCase()) ? basename(stored2, extname(stored2)) : id2;
42079
+ return `${safeComponent(stem) ?? id2}${ext}`;
42080
+ }
42050
42081
  if (kind === "preview") {
42051
42082
  const stored2 = safeComponent(record2.filename ? basename(record2.filename) : null);
42052
42083
  const stem = stored2 ? basename(stored2, extname(stored2)) : id2;
@@ -42075,7 +42106,7 @@ function prepareExportDir(exportDir, containerDir = NOTES_CONTAINER_DIR) {
42075
42106
  }
42076
42107
  return abs;
42077
42108
  }
42078
- function exportOneAttachment(record2, dir, source) {
42109
+ function exportOneAttachment(record2, dir, source, inBody) {
42079
42110
  const base = {
42080
42111
  pk: record2.pk,
42081
42112
  identifier: record2.identifier,
@@ -42083,7 +42114,8 @@ function exportOneAttachment(record2, dir, source) {
42083
42114
  kind: record2.kind,
42084
42115
  parentIdentifier: record2.parentIdentifier,
42085
42116
  exportedTo: null,
42086
- exportedKind: null
42117
+ exportedKind: null,
42118
+ ...inBody === false ? { inBody: false } : {}
42087
42119
  };
42088
42120
  if (!source) return base;
42089
42121
  const name = exportFileName(record2, source.path, source.kind);
@@ -42094,7 +42126,12 @@ function exportOneAttachment(record2, dir, source) {
42094
42126
  throw new Error(`Refusing to write outside the export directory: "${dest}"`);
42095
42127
  assertSafeSavePath(dest);
42096
42128
  copyFileExclusive(source.path, dest);
42097
- return { ...base, exportedTo: dest, exportedKind: source.kind };
42129
+ return {
42130
+ ...base,
42131
+ exportedTo: dest,
42132
+ exportedKind: source.kind,
42133
+ ...source.kind === "fallback" && record2.fallbackStale ? { stale: true } : {}
42134
+ };
42098
42135
  } catch (error2) {
42099
42136
  if (error2.code === "EEXIST") continue;
42100
42137
  return { ...base, error: error2 instanceof Error ? error2.message : String(error2) };
@@ -42102,20 +42139,31 @@ function exportOneAttachment(record2, dir, source) {
42102
42139
  }
42103
42140
  return { ...base, error: "Too many name collisions in the export directory" };
42104
42141
  }
42142
+ function isFallbackRendering(path10) {
42143
+ return /[\\/]Fallback(?:Images|PDFs)[\\/]/.test(path10);
42144
+ }
42105
42145
  function exportSource(record2) {
42106
- if (record2.assetPaths[0]) return { path: record2.assetPaths[0], kind: "asset" };
42146
+ const asset = record2.assetPaths[0];
42147
+ if (asset) return { path: asset, kind: isFallbackRendering(asset) ? "fallback" : "asset" };
42107
42148
  if (record2.previewPath) return { path: record2.previewPath, kind: "preview" };
42108
42149
  return null;
42109
42150
  }
42110
42151
  function exportAttachmentAssets(assets, exportDir, options = {}) {
42111
42152
  const dir = prepareExportDir(exportDir, options.containerDir);
42153
+ const inBody = (record2) => {
42154
+ if (assets.orderSource !== "body") return void 0;
42155
+ const root = record2.parentIdentifier === null ? record2 : assets.attachments.find(
42156
+ (a) => a.parentIdentifier === null && a.identifier === record2.parentIdentifier
42157
+ );
42158
+ return root ? root.bodyIndex !== null : void 0;
42159
+ };
42112
42160
  if (options.firstImageOnly) {
42113
42161
  const first2 = selectFirstImage(assets);
42114
42162
  if (!first2) return { exportDir: dir, results: [], firstImage: null };
42115
42163
  const record2 = assets.attachments.find((a) => a.pk === first2.pk);
42116
42164
  return {
42117
42165
  exportDir: dir,
42118
- results: [exportOneAttachment(record2, dir, exportSource(record2))],
42166
+ results: [exportOneAttachment(record2, dir, exportSource(record2), inBody(record2))],
42119
42167
  firstImage: first2
42120
42168
  };
42121
42169
  }
@@ -42129,7 +42177,7 @@ function exportAttachmentAssets(assets, exportDir, options = {}) {
42129
42177
  } else if (!exportSource(record2) && assets.attachments.some((c) => c.parentIdentifier === record2.identifier)) {
42130
42178
  continue;
42131
42179
  }
42132
- results.push(exportOneAttachment(record2, dir, exportSource(record2)));
42180
+ results.push(exportOneAttachment(record2, dir, exportSource(record2), inBody(record2)));
42133
42181
  }
42134
42182
  return { exportDir: dir, results };
42135
42183
  }
@@ -42298,7 +42346,17 @@ function entries(dir, limit) {
42298
42346
  }
42299
42347
  }
42300
42348
  var byGenerationDesc = (a, b) => generationRank(basename2(b)) - generationRank(basename2(a));
42301
- function findFallbackImage(accountDir, identifier, generation) {
42349
+ function locateFallbackImage(accountDir, identifier, generation) {
42350
+ const gen = safeComponent(generation);
42351
+ const path10 = searchFallbackImage(accountDir, identifier, generation);
42352
+ if (!path10 || !gen) return { path: path10, stale: false };
42353
+ const id2 = safeComponent(identifier);
42354
+ const recorded = ["FallbackImage.png", "FallbackImage.jpg"].map(
42355
+ (n) => id2 ? realInside(join9(accountDir, "FallbackImages", id2, gen, n), accountDir) : null
42356
+ ).filter((p) => p !== null);
42357
+ return { path: path10, stale: !recorded.includes(path10) };
42358
+ }
42359
+ function searchFallbackImage(accountDir, identifier, generation) {
42302
42360
  const id2 = safeComponent(identifier);
42303
42361
  if (!id2) return null;
42304
42362
  const base = join9(accountDir, "FallbackImages", id2);
@@ -42345,7 +42403,8 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
42345
42403
  const accountDir = resolveAccountDir(containerDir, row.accountIdentifier);
42346
42404
  const kind = row.uti === "com.apple.paper" ? "paper" : "drawing";
42347
42405
  const id2 = safeComponent(row.identifier);
42348
- const fallbackImagePath = accountDir ? findFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : null;
42406
+ const fallback = accountDir ? locateFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : { path: null, stale: false };
42407
+ const fallbackImagePath = fallback.path;
42349
42408
  const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
42350
42409
  const bundle = accountDir && id2 && kind === "paper" ? realInside(join9(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
42351
42410
  let raster = null;
@@ -42367,6 +42426,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
42367
42426
  handwritingSummary: row.handwritingSummary,
42368
42427
  bundlePresent: bundle !== null && kindOf(bundle) === "dir",
42369
42428
  fallbackImagePath,
42429
+ fallbackImageStale: fallback.stale,
42370
42430
  previewPath,
42371
42431
  raster
42372
42432
  };
@@ -42507,18 +42567,25 @@ function idList(ids) {
42507
42567
  function buildScopeGuardScript(noteVar, guard, destinationVar) {
42508
42568
  if (!hasScopeGuard(guard)) return "";
42509
42569
  validateScopeGuard(guard);
42510
- const forbidden = guard.forbiddenAncestorFolderIds ?? [];
42570
+ const ifFolderId = guard.ifFolderId && canonicalCoreDataId(guard.ifFolderId);
42571
+ const ifAncestorFolderId = guard.ifAncestorFolderId && canonicalCoreDataId(guard.ifAncestorFolderId);
42572
+ const forbidden = [...new Set((guard.forbiddenAncestorFolderIds ?? []).map(canonicalCoreDataId))];
42511
42573
  let script = `
42512
42574
  set scopeFolder to container of ${noteVar}
42513
42575
  if class of scopeFolder is not folder then return "${SCOPE_MARKER}:the note is not in a folder"`;
42514
- if (guard.ifFolderId)
42576
+ if (forbidden.length > 0)
42515
42577
  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)
42578
+ repeat with forbiddenId in ${idList(forbidden)}
42579
+ if not (exists folder id (contents of forbiddenId)) then return "${SCOPE_MARKER}:a forbidden folder id does not match any folder"
42580
+ end repeat`;
42581
+ if (ifFolderId)
42582
+ script += `
42583
+ if (id of scopeFolder) is not "${ifFolderId}" then return "${SCOPE_MARKER}:the note is not in the expected folder"`;
42584
+ if (ifAncestorFolderId || forbidden.length > 0)
42518
42585
  script += chainScript("scopeChain", "scopeFolder");
42519
- if (guard.ifAncestorFolderId)
42586
+ if (ifAncestorFolderId)
42520
42587
  script += `
42521
- if scopeChain does not contain "${guard.ifAncestorFolderId}" then return "${SCOPE_MARKER}:the note is not inside the expected ancestor folder"`;
42588
+ if scopeChain does not contain "${ifAncestorFolderId}" then return "${SCOPE_MARKER}:the note is not inside the expected ancestor folder"`;
42522
42589
  if (forbidden.length > 0) {
42523
42590
  script += `
42524
42591
  repeat with forbiddenId in ${idList(forbidden)}
@@ -43435,7 +43502,7 @@ var AppleNotesManager = class {
43435
43502
  * @throws Error when the note's rich data cannot be read (e.g. no Full Disk Access)
43436
43503
  */
43437
43504
  getNoteTablesById(id2) {
43438
- return collectNoteTables(readRichNote(id2), id2);
43505
+ return collectNoteTables(readRichNote(id2, { skipUnsafeLinks: true }), id2);
43439
43506
  }
43440
43507
  /**
43441
43508
  * Retrieves detailed metadata for a note by title.
@@ -43568,6 +43635,7 @@ var AppleNotesManager = class {
43568
43635
  return `
43569
43636
  if not (exists note id "${safeGuardId}") then ${inactive("missing")}
43570
43637
  set ${ref} to note id "${safeGuardId}"
43638
+ if (id of ${ref}) is (id of noteRef) then ${inactive("the note being deleted")}
43571
43639
  if password protected of ${ref} then ${inactive("locked")}
43572
43640
  set ${folderVar} to missing value
43573
43641
  try
@@ -46328,7 +46396,7 @@ var HIGHLIGHTS = {
46328
46396
  var KNOWN_RUN_FIELDS = /* @__PURE__ */ new Set([1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 14]);
46329
46397
  var KNOWN_PARAGRAPH_FIELDS = /* @__PURE__ */ new Set([1, 2, 4, 5, 8, 9]);
46330
46398
  var isSafeLink = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
46331
- var utf8 = new TextDecoder();
46399
+ var utf8 = new TextDecoder("utf-8", { ignoreBOM: true });
46332
46400
  var first = (fields, n) => fields.find((f) => f.fieldNumber === n);
46333
46401
  var varintOf = (fields, n) => {
46334
46402
  const f = first(fields, n);
@@ -46477,7 +46545,8 @@ function decodeNoteBlocks(data) {
46477
46545
  const end = newline === -1 ? text2.length : newline;
46478
46546
  while (runIndex < runs.length - 1 && runs[runIndex].start + runs[runIndex].length <= paragraphStart)
46479
46547
  runIndex++;
46480
- const attrs = runs[runIndex]?.paragraph ?? DEFAULT_PARAGRAPH;
46548
+ const covering = runs[runIndex];
46549
+ const attrs = !covering ? DEFAULT_PARAGRAPH : covering.paragraph.styleType === 103 && checklistRunLineStart(text2, covering.start, covering.length) > paragraphStart ? DEFAULT_PARAGRAPH : covering.paragraph;
46481
46550
  const style = attrs.styleType === null ? "body" : STYLE_NAMES[attrs.styleType] ?? "unknown";
46482
46551
  const alignment = ALIGNMENTS[attrs.alignmentValue] ?? "unknown";
46483
46552
  const block = {
@@ -46684,6 +46753,25 @@ function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
46684
46753
  return decodeCompressedNoteBlocks(Buffer.from(row.data, "hex"));
46685
46754
  }
46686
46755
 
46756
+ // src/utils/wordCount.ts
46757
+ 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;
46758
+ var segmenter;
46759
+ function countWords(text2) {
46760
+ let count = 0;
46761
+ for (const run of text2.replace(//gu, " ").split(/\s+/u)) {
46762
+ if (!/[\p{L}\p{N}]/u.test(run)) continue;
46763
+ if (!UNSPACED_SCRIPT.test(run)) {
46764
+ count++;
46765
+ continue;
46766
+ }
46767
+ segmenter ??= new Intl.Segmenter(void 0, { granularity: "word" });
46768
+ let words = 0;
46769
+ for (const segment of segmenter.segment(run)) if (segment.isWordLike) words++;
46770
+ count += Math.max(1, words);
46771
+ }
46772
+ return count;
46773
+ }
46774
+
46687
46775
  // src/utils/noteRecentList.ts
46688
46776
  var RECENT_LIMIT = { DEFAULT: 50, MAX: 1e3 };
46689
46777
  var CHECKPOINT_PREFIX = "cdts1:";
@@ -46738,9 +46826,7 @@ function parseSince(input) {
46738
46826
  }
46739
46827
  function textStats(text2) {
46740
46828
  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 };
46829
+ return { wordCount: countWords(visible2), charCount: [...visible2].length };
46744
46830
  }
46745
46831
  function previewText(text2) {
46746
46832
  const flat = text2.replace(OBJECT_REPLACEMENT, " ").replace(/\s+/gu, " ").trim();
@@ -47362,6 +47448,10 @@ var Parser = class {
47362
47448
  function parseNoteQuery(input) {
47363
47449
  return new Parser(tokenize(input), input.length).parse();
47364
47450
  }
47451
+ function noteBodyText(text2) {
47452
+ const firstBreak = text2.indexOf("\n");
47453
+ return firstBreak === -1 ? "" : text2.slice(firstBreak + 1);
47454
+ }
47365
47455
  function normalizeForMatch(text2) {
47366
47456
  return text2.normalize("NFC").replace(/[\ufffc\u00a0]/gu, " ").toLowerCase();
47367
47457
  }
@@ -47436,20 +47526,36 @@ function cheapFirst(children) {
47436
47526
  }
47437
47527
  return ordered;
47438
47528
  }
47439
- function evaluateNoteQuery(node, note) {
47529
+ function evaluateNoteQueryTruth(node, note) {
47440
47530
  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);
47531
+ case "and": {
47532
+ let unknown2 = false;
47533
+ for (const child2 of cheapFirst(node.children)) {
47534
+ const value = evaluateNoteQueryTruth(child2, note);
47535
+ if (value === false) return false;
47536
+ if (value === null) unknown2 = true;
47537
+ }
47538
+ return unknown2 ? null : true;
47539
+ }
47540
+ case "or": {
47541
+ let unknown2 = false;
47542
+ for (const child2 of cheapFirst(node.children)) {
47543
+ const value = evaluateNoteQueryTruth(child2, note);
47544
+ if (value === true) return true;
47545
+ if (value === null) unknown2 = true;
47546
+ }
47547
+ return unknown2 ? null : false;
47548
+ }
47549
+ case "not": {
47550
+ const value = evaluateNoteQueryTruth(node.child, note);
47551
+ return value === null ? null : !value;
47552
+ }
47447
47553
  case "text": {
47448
47554
  const needle = normalizeForMatch(node.value);
47449
47555
  if (node.field === "title") return note.titleLower.includes(needle);
47450
47556
  if (node.field === "any" && note.titleLower.includes(needle)) return true;
47451
47557
  const content = note.content();
47452
- if (!content) return false;
47558
+ if (!content) return null;
47453
47559
  return (node.field === "body" ? content.bodyLower : content.textLower).includes(needle);
47454
47560
  }
47455
47561
  case "folder":
@@ -47462,21 +47568,27 @@ function evaluateNoteQuery(node, note) {
47462
47568
  return compareDate(node.field === "created" ? note.created : note.modified, node);
47463
47569
  case "tag": {
47464
47570
  const content = note.content();
47465
- return Boolean(content?.tags.includes(normalizeForMatch(node.value)));
47571
+ return content ? content.tags.includes(normalizeForMatch(node.value)) : null;
47572
+ }
47573
+ case "has": {
47574
+ const content = note.content();
47575
+ return content ? content.facets.has(node.facet) : null;
47466
47576
  }
47467
- case "has":
47468
- return Boolean(note.content()?.facets.has(node.facet));
47469
47577
  case "checklist": {
47470
47578
  const content = note.content();
47471
- if (!content || content.checklist.total === 0) return false;
47579
+ if (!content) return null;
47580
+ if (content.checklist.total === 0) return false;
47472
47581
  return node.state === "open" ? content.checklist.open > 0 : content.checklist.open === 0;
47473
47582
  }
47474
47583
  case "words": {
47475
47584
  const content = note.content();
47476
- return content ? compare(content.words, node.op, node.value) : false;
47585
+ return content ? compare(content.words, node.op, node.value) : null;
47477
47586
  }
47478
47587
  }
47479
47588
  }
47589
+ function evaluateNoteQuery(node, note) {
47590
+ return evaluateNoteQueryTruth(node, note) === true;
47591
+ }
47480
47592
  function positiveTextPredicates(node, negated = false) {
47481
47593
  switch (node.type) {
47482
47594
  case "and":
@@ -47499,7 +47611,7 @@ function matchLocations(predicates, title, text2) {
47499
47611
  const firstBreak = text2 === null ? -1 : text2.indexOf("\n");
47500
47612
  const titleLower = normalizeForMatch(title);
47501
47613
  const firstLineLower = text2 === null ? "" : normalizeForMatch(firstBreak === -1 ? text2 : text2.slice(0, firstBreak));
47502
- const bodyLower = text2 === null || firstBreak === -1 ? "" : normalizeForMatch(text2.slice(firstBreak + 1));
47614
+ const bodyLower = text2 === null ? "" : normalizeForMatch(noteBodyText(text2));
47503
47615
  let inTitle = false;
47504
47616
  let inBody = false;
47505
47617
  for (const predicate of predicates) {
@@ -47611,13 +47723,6 @@ function decodeBodyHex(hex3) {
47611
47723
  return null;
47612
47724
  }
47613
47725
  }
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
47726
  var REQUIRED_COLUMNS3 = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
47622
47727
  function col2(available, alias, name) {
47623
47728
  return available.has(name) ? `${alias}.${name}` : "NULL";
@@ -47862,7 +47967,6 @@ function runNoteQuery(ast, options = {}) {
47862
47967
  const body2 = decode2();
47863
47968
  if (!body2) return content = null;
47864
47969
  const text2 = body2.text;
47865
- const firstBreak = text2.indexOf("\n");
47866
47970
  const tags = /* @__PURE__ */ new Set();
47867
47971
  for (const [id2, alt] of row.tags ?? []) {
47868
47972
  if (id2 && alt && body2.objectIds.has(id2))
@@ -47872,7 +47976,7 @@ function runNoteQuery(ast, options = {}) {
47872
47976
  if (tags.size) facets.add("tag");
47873
47977
  content = {
47874
47978
  textLower: normalizeForMatch(text2),
47875
- bodyLower: normalizeForMatch(firstBreak === -1 ? "" : text2.slice(firstBreak + 1)),
47979
+ bodyLower: normalizeForMatch(noteBodyText(text2)),
47876
47980
  words: countWords(text2),
47877
47981
  facets,
47878
47982
  checklist: body2.checklist,
@@ -48365,13 +48469,16 @@ var LEGACY_ENTITIES = {
48365
48469
  gt: ">",
48366
48470
  amp: "&"
48367
48471
  };
48472
+ function codePointText(value) {
48473
+ return Number.isSafeInteger(value) && value <= 1114111 ? String.fromCodePoint(value) : "\uFFFD";
48474
+ }
48368
48475
  function comparableVisibleText(html) {
48369
48476
  return html.replace(/<br\s*\/?\s*>/gi, " ").replace(/<[^>]*>/g, (tag) => INLINE_TAG.test(tag) ? "" : " ").replace(
48370
48477
  /&(?:(nbsp|quot|lt|gt|amp);?|apos;|#(\d+);|#x([0-9a-f]+);)/gi,
48371
48478
  (_match, legacy, dec, hex3) => {
48372
48479
  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));
48480
+ if (dec) return codePointText(Number(dec));
48481
+ if (hex3) return codePointText(Number.parseInt(hex3, 16));
48375
48482
  return "'";
48376
48483
  }
48377
48484
  ).replace(/\s+/g, " ").trim();
@@ -48796,15 +48903,26 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
48796
48903
  const segments = (path10) => JSON.stringify(splitFolderPath(path10).map((part) => part.toLocaleLowerCase()));
48797
48904
  if (request.folder) {
48798
48905
  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
- ))
48906
+ const accounts = manager.listAccounts().filter(
48907
+ (account) => account.defaultFolder && manager.listFolders(account.name).some((folder) => segments(folder.name) === wanted)
48908
+ );
48909
+ if (!accounts.length)
48803
48910
  throw new Error(
48804
48911
  `Folder "${request.folder}" does not exist; create it with create-folder first. Nothing was created`
48805
48912
  );
48806
- for (const account of accounts)
48807
- manager.assertNotSmartFolderDestination(request.folder, account.name);
48913
+ let smartRefusal;
48914
+ const ordinary = accounts.filter((account) => {
48915
+ try {
48916
+ manager.assertNotSmartFolderDestination(request.folder, account.name);
48917
+ return true;
48918
+ } catch (error2) {
48919
+ if (!(error2 instanceof CodedError && error2.envelope.reason === "smart_folder_destination"))
48920
+ throw error2;
48921
+ smartRefusal ??= error2;
48922
+ return false;
48923
+ }
48924
+ });
48925
+ if (!ordinary.length) throw smartRefusal;
48808
48926
  }
48809
48927
  const defaultFolderNotes = () => new Map(
48810
48928
  manager.listAccounts().flatMap(
@@ -49501,7 +49619,63 @@ function withJsonSchema2020_12(transport2) {
49501
49619
 
49502
49620
  // src/utils/shutdown.ts
49503
49621
  var SHUTDOWN_DRAIN_TIMEOUT_MS = 2e3;
49504
- function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
49622
+ var SHUTDOWN_IN_FLIGHT_TIMEOUT_MS = 3e4;
49623
+ var InFlightRequests = class {
49624
+ open = /* @__PURE__ */ new Set();
49625
+ waiters = [];
49626
+ get count() {
49627
+ return this.open.size;
49628
+ }
49629
+ received(id2) {
49630
+ this.open.add(id2);
49631
+ }
49632
+ /** A response was sent, or the client cancelled the request (no response follows). */
49633
+ settled(id2) {
49634
+ if (!this.open.delete(id2) || this.open.size > 0) return;
49635
+ const waiters = this.waiters;
49636
+ this.waiters = [];
49637
+ for (const waiter of waiters) waiter();
49638
+ }
49639
+ onIdle(listener) {
49640
+ if (this.open.size === 0) listener();
49641
+ else this.waiters.push(listener);
49642
+ }
49643
+ };
49644
+ var isObject2 = (value) => typeof value === "object" && value !== null;
49645
+ var isId = (value) => typeof value === "string" || typeof value === "number";
49646
+ function trackRequests(transport2, requests) {
49647
+ if (typeof transport2.send !== "function") return transport2;
49648
+ let handler;
49649
+ Object.defineProperty(transport2, "onmessage", {
49650
+ configurable: true,
49651
+ enumerable: true,
49652
+ get: () => handler,
49653
+ set: (next) => {
49654
+ handler = next ? (message, extra) => {
49655
+ const m = message;
49656
+ if (typeof m.method === "string") {
49657
+ if (isId(m.id)) requests.received(m.id);
49658
+ else if (m.method === "notifications/cancelled" && isObject2(m.params)) {
49659
+ const id2 = m.params.requestId;
49660
+ if (isId(id2)) requests.settled(id2);
49661
+ }
49662
+ }
49663
+ next(message, extra);
49664
+ } : next;
49665
+ }
49666
+ });
49667
+ const send = transport2.send.bind(transport2);
49668
+ transport2.send = async (message, options) => {
49669
+ try {
49670
+ return await send(message, options);
49671
+ } finally {
49672
+ const m = message;
49673
+ if (typeof m.method !== "string" && isId(m.id)) requests.settled(m.id);
49674
+ }
49675
+ };
49676
+ return transport2;
49677
+ }
49678
+ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS, pending, inFlightTimeoutMs = SHUTDOWN_IN_FLIGHT_TIMEOUT_MS) {
49505
49679
  let shuttingDown = false;
49506
49680
  let exited = false;
49507
49681
  const exitOnce = () => {
@@ -49518,8 +49692,11 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
49518
49692
  return () => {
49519
49693
  if (shuttingDown) return;
49520
49694
  shuttingDown = true;
49521
- timer = setTimeout(exitOnce, timeoutMs);
49522
- setImmediate(exitWhenDrained);
49695
+ const busy = (pending?.count ?? 0) > 0;
49696
+ timer = setTimeout(exitOnce, busy ? Math.max(timeoutMs, inFlightTimeoutMs) : timeoutMs);
49697
+ setImmediate(
49698
+ () => pending ? pending.onIdle(() => setImmediate(exitWhenDrained)) : exitWhenDrained()
49699
+ );
49523
49700
  };
49524
49701
  }
49525
49702
 
@@ -49867,7 +50044,7 @@ function lastViewedOf(raw, columnPresent = true, now = Date.now()) {
49867
50044
  return { lastViewed: new Date(ms).toISOString(), lastViewedStatus: "viewed" };
49868
50045
  }
49869
50046
  var visible = (text2) => text2.replace(/\ufffc/g, "");
49870
- var wordCount = (text2) => visible(text2).split(/\s+/u).filter((word) => word !== "").length;
50047
+ var wordCount = (text2) => countWords(text2);
49871
50048
  var charCount = (text2) => Array.from(visible(text2)).length;
49872
50049
  function noteStructureSql(columns) {
49873
50050
  const c = (alias, name) => col(columns, alias, name);
@@ -50155,7 +50332,7 @@ function inventorySql(columns) {
50155
50332
  ORDER BY d.ZNOTE LIMIT ${BODY_BATCH};`
50156
50333
  };
50157
50334
  }
50158
- function decodeBodies(dbPath2, sql, params) {
50335
+ function decodeBodies(dbPath2, sql, params, notes) {
50159
50336
  const docs = /* @__PURE__ */ new Map();
50160
50337
  let after = 0;
50161
50338
  for (; ; ) {
@@ -50164,7 +50341,7 @@ function decodeBodies(dbPath2, sql, params) {
50164
50341
  );
50165
50342
  for (const row of rows) {
50166
50343
  after = Math.max(after, row.note);
50167
- if (row.encrypted || !row.data) continue;
50344
+ if (row.encrypted || !row.data || !notes.has(row.note)) continue;
50168
50345
  try {
50169
50346
  docs.set(row.note, decodeCompressedNoteBlocks(Buffer.from(row.data, "hex")));
50170
50347
  } catch {
@@ -50212,7 +50389,7 @@ function listNoteLinks(options = {}) {
50212
50389
  if (options.id && !notes.length)
50213
50390
  throw new NoteStoreError(`No note found for ID "${options.id}".`, "invalid_input");
50214
50391
  const includeInline = options.includeInline ?? Boolean(options.id);
50215
- const docs = includeInline ? decodeBodies(dbPath2, sql.bodies, params) : /* @__PURE__ */ new Map();
50392
+ const docs = includeInline ? decodeBodies(dbPath2, sql.bodies, params, new Set(notes.map((note) => note.pk))) : /* @__PURE__ */ new Map();
50216
50393
  const noteById = new Map(notes.map((note) => [note.pk, note]));
50217
50394
  const accountById = new Map(context.accounts.map((account) => [account.pk, account]));
50218
50395
  const folderById = new Map(context.folders.map((folder) => [folder.pk, folder]));
@@ -50527,7 +50704,14 @@ var AssetLocator = class {
50527
50704
  ["FallbackImage.png", "FallbackImage.jpg"]
50528
50705
  );
50529
50706
  if (fallback)
50530
- return { primary: { path: fallback, name: `${source.kind}.png`, role: "fallback" } };
50707
+ return {
50708
+ primary: {
50709
+ path: fallback.path,
50710
+ name: `${source.kind}.png`,
50711
+ role: "fallback",
50712
+ ...fallback.stale ? { stale: true } : {}
50713
+ }
50714
+ };
50531
50715
  return preview ? { primary: preview } : {};
50532
50716
  }
50533
50717
  case "scan": {
@@ -50535,7 +50719,14 @@ var AssetLocator = class {
50535
50719
  "FallbackPDF.pdf"
50536
50720
  ]);
50537
50721
  return {
50538
- ...pdf ? { primary: { path: pdf, name: "scan.pdf", role: "fallback" } } : {},
50722
+ ...pdf ? {
50723
+ primary: {
50724
+ path: pdf.path,
50725
+ name: "scan.pdf",
50726
+ role: "fallback",
50727
+ ...pdf.stale ? { stale: true } : {}
50728
+ }
50729
+ } : {},
50539
50730
  ...preview ? { preview } : {}
50540
50731
  };
50541
50732
  }
@@ -50564,7 +50755,11 @@ var AssetLocator = class {
50564
50755
  }
50565
50756
  return void 0;
50566
50757
  }
50567
- /** <dir>/<id>/<generation>/<name>, <dir>/<id>/<name>, then any generation. */
50758
+ /**
50759
+ * <dir>/<id>/<generation>/<name>, <dir>/<id>/<name>, then any generation.
50760
+ * `stale` is set when a generation was recorded but the file came from
50761
+ * somewhere else.
50762
+ */
50568
50763
  fallback(account, dir, id2, generation, names) {
50569
50764
  const base = join19(account, dir, id2);
50570
50765
  const gen = safeComponent2(generation);
@@ -50576,7 +50771,7 @@ var AssetLocator = class {
50576
50771
  for (const g of generations)
50577
50772
  for (const name of names) {
50578
50773
  const path10 = confine(g ? join19(base, g, name) : join19(base, name), account);
50579
- if (path10 && isFile(path10)) return path10;
50774
+ if (path10 && isFile(path10)) return { path: path10, stale: Boolean(gen) && g !== gen };
50580
50775
  }
50581
50776
  return void 0;
50582
50777
  }
@@ -50733,6 +50928,9 @@ function readHead(fd) {
50733
50928
  const n = readSync3(fd, head, 0, 16, 0);
50734
50929
  return head.subarray(0, n);
50735
50930
  }
50931
+ function directoryFailure(dir, error2) {
50932
+ return `Could not create the assets directory ${dir}: ${error2 instanceof Error ? error2.message : String(error2)}`;
50933
+ }
50736
50934
  var SidecarWriter = class {
50737
50935
  constructor(dir, linkBase) {
50738
50936
  this.dir = dir;
@@ -50743,9 +50941,16 @@ var SidecarWriter = class {
50743
50941
  placed = /* @__PURE__ */ new Map();
50744
50942
  created = false;
50745
50943
  count = 0;
50944
+ /**
50945
+ * Set when the directory could not be created. Every later asset fails the
50946
+ * same way without another attempt; the export checks this and fails
50947
+ * instead of reporting each asset as unavailable.
50948
+ */
50949
+ directoryError;
50746
50950
  place(asset) {
50747
50951
  const done = this.placed.get(asset.path);
50748
50952
  if (done) return done;
50953
+ if (this.directoryError) return { error: this.directoryError };
50749
50954
  let source;
50750
50955
  try {
50751
50956
  source = openSource(asset.path);
@@ -50755,8 +50960,13 @@ var SidecarWriter = class {
50755
50960
  try {
50756
50961
  const mime = sniffMime(readHead(source.fd), asset.name);
50757
50962
  if (!this.created) {
50758
- assertExportPath(this.dir);
50759
- mkdirSync4(this.dir, { recursive: true });
50963
+ try {
50964
+ assertExportPath(this.dir);
50965
+ mkdirSync4(this.dir, { recursive: true });
50966
+ } catch (error2) {
50967
+ this.directoryError = directoryFailure(this.dir, error2);
50968
+ return { error: this.directoryError };
50969
+ }
50760
50970
  this.created = true;
50761
50971
  }
50762
50972
  const name = safeAssetName(asset.name, mime);
@@ -50909,7 +51119,10 @@ function titleBlockIndex(note) {
50909
51119
  return first2.text.trim() === note.title.trim() && first2.style !== "monospaced" ? first2.index : -1;
50910
51120
  }
50911
51121
  function place(ctx, asset) {
50912
- return asset && ctx.writer ? ctx.writer.place(asset) : void 0;
51122
+ const placed = asset && ctx.writer ? ctx.writer.place(asset) : void 0;
51123
+ if (asset?.stale && placed && "url" in placed)
51124
+ ctx.stats.staleRenderings = (ctx.stats.staleRenderings ?? 0) + 1;
51125
+ return placed;
50913
51126
  }
50914
51127
  function planAttachment(attachment, ctx) {
50915
51128
  ctx.stats.attachments++;
@@ -51075,6 +51288,7 @@ function inlinePlanHtml(plan) {
51075
51288
  }
51076
51289
  }
51077
51290
  function blockPlanHtml(plan) {
51291
+ if (!isHtmlBlockPlan(plan)) return `<p>${inlinePlanHtml(plan)}</p>`;
51078
51292
  switch (plan.type) {
51079
51293
  case "table":
51080
51294
  return tableHtml(plan.rows);
@@ -51942,7 +52156,7 @@ function child(path10, key) {
51942
52156
  if (key.length > MAX_QUOTED_TOKEN) key = `${key.slice(0, MAX_QUOTED_TOKEN - 3)}...`;
51943
52157
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `${path10}.${key}` : `${path10}[${JSON.stringify(key)}]`;
51944
52158
  }
51945
- var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
52159
+ var isObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
51946
52160
  var describe = (value) => value === null ? "null" : Array.isArray(value) ? "an array" : `a ${typeof value}`;
51947
52161
  var describeVersion = (value) => typeof value === "number" && Number.isFinite(value) ? String(value) : describe(value);
51948
52162
  var listed = (values) => values.map((v) => JSON.stringify(v)).join(", ");
@@ -52023,7 +52237,7 @@ var RULE_FIELDS = {
52023
52237
  omit: []
52024
52238
  };
52025
52239
  function validateRule(c, rule, path10) {
52026
- if (!isObject2(rule)) {
52240
+ if (!isObject3(rule)) {
52027
52241
  c.add(path10, `must be an object, not ${describe(rule)}`);
52028
52242
  return;
52029
52243
  }
@@ -52070,7 +52284,7 @@ function validateInlineOrder(c, value, path10) {
52070
52284
  if (missing.length) c.add(path10, `must list every inline format; missing ${listed(missing)}`);
52071
52285
  }
52072
52286
  function validateOptions(c, value, path10) {
52073
- if (!isObject2(value)) {
52287
+ if (!isObject3(value)) {
52074
52288
  c.add(path10, `must be an object, not ${describe(value)}`);
52075
52289
  return;
52076
52290
  }
@@ -52091,7 +52305,7 @@ function validateOptions(c, value, path10) {
52091
52305
  }
52092
52306
  function templateErrors(input) {
52093
52307
  const c = new Collector();
52094
- if (!isObject2(input)) {
52308
+ if (!isObject3(input)) {
52095
52309
  c.add("$", `a template must be a JSON object, not ${describe(input)}`);
52096
52310
  return c.errors;
52097
52311
  }
@@ -52126,7 +52340,7 @@ function templateErrors(input) {
52126
52340
  c.oneOf(value, path10, BUILTIN_TEMPLATE_NAMES);
52127
52341
  break;
52128
52342
  case "assets":
52129
- if (!isObject2(value)) {
52343
+ if (!isObject3(value)) {
52130
52344
  c.add(path10, `must be an object, not ${describe(value)}`);
52131
52345
  break;
52132
52346
  }
@@ -52142,7 +52356,7 @@ function templateErrors(input) {
52142
52356
  validateInlineOrder(c, value, path10);
52143
52357
  break;
52144
52358
  case "rules":
52145
- if (!isObject2(value)) {
52359
+ if (!isObject3(value)) {
52146
52360
  c.add(path10, `must be an object keyed by rule id, not ${describe(value)}`);
52147
52361
  break;
52148
52362
  }
@@ -52772,16 +52986,26 @@ var HashedSidecarWriter = class {
52772
52986
  /** Absolute paths of files written or reused. */
52773
52987
  files = [];
52774
52988
  count = 0;
52989
+ /** Set when the directory could not be created; see SidecarWriter.directoryError. */
52990
+ directoryError;
52991
+ /** Creates the directory once; returns an error message when it cannot. */
52775
52992
  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");
52993
+ if (this.ready) return void 0;
52994
+ try {
52995
+ assertExportPath(this.dir);
52996
+ mkdirSync5(this.dir, { recursive: true });
52997
+ if (!lstatSync4(this.dir).isDirectory())
52998
+ throw new Error("assets directory is not a directory");
52999
+ } catch (error2) {
53000
+ return this.directoryError = directoryFailure(this.dir, error2);
53001
+ }
52780
53002
  this.ready = true;
53003
+ return void 0;
52781
53004
  }
52782
53005
  place(asset) {
52783
53006
  const done = this.placed.get(asset.path);
52784
53007
  if (done) return done;
53008
+ if (this.directoryError) return { error: this.directoryError };
52785
53009
  let source;
52786
53010
  try {
52787
53011
  source = openRegular(asset.path);
@@ -52791,7 +53015,8 @@ var HashedSidecarWriter = class {
52791
53015
  try {
52792
53016
  const { hash, head } = digest(source);
52793
53017
  const mime = sniffMime(head, asset.name);
52794
- this.prepare();
53018
+ const directoryError = this.prepare();
53019
+ if (directoryError) return { error: directoryError };
52795
53020
  const name = safeAssetName(asset.name, mime);
52796
53021
  const ext = extname4(name);
52797
53022
  const target = join21(
@@ -52920,33 +53145,9 @@ function templateAssetsDir(directory, outputDir, values) {
52920
53145
  return dir;
52921
53146
  }
52922
53147
  function readTemplateFile(path10) {
52923
- const abs = assertExportPath(path10);
52924
- if (extname4(abs).toLowerCase() !== ".json")
52925
- throw new Error(`Template file must have a .json extension: ${abs}`);
52926
- let fd;
52927
- try {
52928
- fd = openRegular(abs);
52929
- } catch (error2) {
52930
- const code = error2.code;
52931
- throw new Error(
52932
- code === "ENOENT" ? `Template file not found: ${abs}` : code === "ELOOP" ? `Refusing to read the symbolic link ${abs}` : `Template file is not a readable regular file: ${abs}`
52933
- );
52934
- }
52935
- try {
52936
- const size = fstatSync5(fd).size;
52937
- if (size > MAX_TEMPLATE_BYTES)
52938
- throw new Error(`Template file is ${size} bytes; the limit is ${MAX_TEMPLATE_BYTES}`);
52939
- const data = Buffer.alloc(size);
52940
- let read = 0;
52941
- while (read < size) {
52942
- const n = readSync4(fd, data, read, size - read, read);
52943
- if (n <= 0) break;
52944
- read += n;
52945
- }
52946
- return data.subarray(0, read).toString("utf8");
52947
- } finally {
52948
- closeSync5(fd);
52949
- }
53148
+ if (extname4(path10).toLowerCase() !== ".json")
53149
+ throw new Error(`Template file must have a .json extension: ${path10}`);
53150
+ return readAllowedFile(path10, MAX_TEMPLATE_BYTES, { label: "Template file" }).toString("utf8");
52950
53151
  }
52951
53152
 
52952
53153
  // src/services/notesExport.ts
@@ -53021,10 +53222,11 @@ function validPath(path10, what) {
53021
53222
  function selectNotes(request, deps) {
53022
53223
  if (!!request.id === !!request.folder)
53023
53224
  throw new NotesExportError("invalid-request", "Provide exactly one of 'id' or 'folder'.");
53024
- if (request.id) return [request.id];
53225
+ if (request.id) return { ids: [request.id], truncated: false };
53025
53226
  const limit = Math.min(request.limit ?? DEFAULT_FOLDER_EXPORT_LIMIT, MAX_FOLDER_EXPORT_LIMIT);
53026
53227
  try {
53027
- return deps.listNoteRefs(request.account, request.folder, void 0, limit).map((ref) => ref.id);
53228
+ const ids = deps.listNoteRefs(request.account, request.folder, void 0, limit + 1).map((ref) => ref.id);
53229
+ return { ids: ids.slice(0, limit), truncated: ids.length > limit };
53028
53230
  } catch (error2) {
53029
53231
  throw new NotesExportError(
53030
53232
  "folder-unavailable",
@@ -53056,9 +53258,13 @@ function openOutput(output) {
53056
53258
  throw error2;
53057
53259
  }
53058
53260
  }
53059
- function renderInto(fd, render) {
53261
+ function renderInto(fd, render, writers = []) {
53060
53262
  try {
53061
- return render();
53263
+ const document = render();
53264
+ const directoryError = writers.find((writer) => writer.directoryError)?.directoryError;
53265
+ if (directoryError)
53266
+ throw new NotesExportError("invalid-path", `${directoryError}. Nothing was exported.`);
53267
+ return document;
53062
53268
  } catch (error2) {
53063
53269
  if (fd !== void 0) writeAllAndClose(fd, "");
53064
53270
  throw error2;
@@ -53075,10 +53281,15 @@ function exportNotesMarkdown(request, deps) {
53075
53281
  "invalid-request",
53076
53282
  `assetsDir has no effect: template "${chosen.info.name}" sets assets.mode to "${chosen.template.assets.mode}".`
53077
53283
  );
53078
- const ids = selectNotes(request, deps);
53284
+ const { ids, truncated } = selectNotes(request, deps);
53079
53285
  const { notes, skipped } = loadNotes(ids, !!request.id, deps.readNote);
53080
- if (chosen)
53081
- return exportWithTemplate(request, deps, chosen, notes, skipped, { output, assetsDir });
53286
+ if (chosen) {
53287
+ const receipt2 = exportWithTemplate(request, deps, chosen, notes, skipped, {
53288
+ output,
53289
+ assetsDir
53290
+ });
53291
+ return truncated ? { ...receipt2, truncated } : receipt2;
53292
+ }
53082
53293
  const fd = output ? openOutput(output) : void 0;
53083
53294
  const writer = assetsDir ? new SidecarWriter(assetsDir, output ? dirname6(output) : void 0) : void 0;
53084
53295
  const ctx = {
@@ -53087,12 +53298,14 @@ function exportNotesMarkdown(request, deps) {
53087
53298
  };
53088
53299
  const markdown = renderInto(
53089
53300
  fd,
53090
- () => renderNotesMarkdown(notes, ctx, { wrap: request.wrap ?? 0 })
53301
+ () => renderNotesMarkdown(notes, ctx, { wrap: request.wrap ?? 0 }),
53302
+ writer ? [writer] : []
53091
53303
  );
53092
53304
  const bytes = Buffer.byteLength(markdown);
53093
53305
  const receipt = {
53094
53306
  format: "markdown",
53095
53307
  count: notes.length,
53308
+ ...truncated ? { truncated } : {},
53096
53309
  bytes,
53097
53310
  stats: ctx.stats,
53098
53311
  skipped,
@@ -53128,7 +53341,7 @@ function exportNotesHtml(request, deps) {
53128
53341
  const assetsDir = embed ? void 0 : validPath(request.assetsDir ?? defaultSidecarDir(output), "assetsDir");
53129
53342
  if (assetsDir === output)
53130
53343
  throw new NotesExportError("invalid-path", "outputPath and assetsDir must differ.");
53131
- const ids = selectNotes(request, deps);
53344
+ const { ids, truncated } = selectNotes(request, deps);
53132
53345
  const { notes, skipped } = loadNotes(ids, !!request.id, deps.readNote);
53133
53346
  const fd = openOutput(output);
53134
53347
  const writer = assetsDir ? new SidecarWriter(assetsDir, dirname6(output)) : new DataUrlWriter();
@@ -53138,11 +53351,16 @@ function exportNotesHtml(request, deps) {
53138
53351
  locator: deps.locator ?? new AssetLocator()
53139
53352
  };
53140
53353
  const title = request.id ? notes[0]?.title || "Note" : request.folder;
53141
- const html = renderInto(fd, () => renderNotesHtml(notes, ctx, { title }));
53354
+ const html = renderInto(
53355
+ fd,
53356
+ () => renderNotesHtml(notes, ctx, { title }),
53357
+ writer instanceof SidecarWriter ? [writer] : []
53358
+ );
53142
53359
  const bytes = writeAllAndClose(fd, html);
53143
53360
  return {
53144
53361
  format: "html",
53145
53362
  count: notes.length,
53363
+ ...truncated ? { truncated } : {},
53146
53364
  bytes,
53147
53365
  output,
53148
53366
  stats: ctx.stats,
@@ -53224,7 +53442,7 @@ function exportWithTemplate(request, deps, chosen, notes, skipped, { output, ass
53224
53442
  });
53225
53443
  warnings = result.warnings;
53226
53444
  return result.markdown;
53227
- });
53445
+ }, [...writers.values()]);
53228
53446
  const bytes = Buffer.byteLength(markdown);
53229
53447
  const used = [...writers.values()].filter((writer) => writer.count > 0);
53230
53448
  const files = used.flatMap((writer) => writer.files);
@@ -53290,12 +53508,12 @@ import { basename as basename5, extname as extname6, join as join23 } from "node
53290
53508
  var PASTEBOARD_NAME_ENV = "APPLE_NOTES_MCP_PASTEBOARD_NAME";
53291
53509
  var MAX_PASTEBOARD_BYTES = 64 * 1024 * 1024;
53292
53510
  var PASTEBOARD_DATA_TYPES = [
53511
+ { type: "com.adobe.pdf", ext: "pdf", label: "document" },
53293
53512
  { type: "public.png", ext: "png", label: "image" },
53294
53513
  { type: "public.jpeg", ext: "jpg", label: "image" },
53295
53514
  { type: "public.heic", ext: "heic", label: "image" },
53296
53515
  { 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" }
53516
+ { type: "public.tiff", ext: "tiff", label: "image" }
53299
53517
  ];
53300
53518
  var PASTEBOARD_ACCESS_BEHAVIORS = {
53301
53519
  0: "default",
@@ -53322,15 +53540,26 @@ function run(argv) {
53322
53540
  var items = pb.pasteboardItems;
53323
53541
  var itemCount = items && !items.isNil() ? Number(items.count) : 0;
53324
53542
  var fileUrls = [];
53543
+ var dataItems = 0;
53325
53544
  for (var k = 0; k < itemCount; k++) {
53326
53545
  var item = items.objectAtIndex(k);
53327
53546
  var itemTypes = ObjC.deepUnwrap(item.types) || [];
53328
- if (itemTypes.indexOf("public.file-url") < 0) continue;
53547
+ if (itemTypes.indexOf("public.file-url") < 0) {
53548
+ for (var j = 0; j < prefs.length; j++) {
53549
+ if (itemTypes.indexOf(prefs[j][0]) >= 0) {
53550
+ dataItems++;
53551
+ break;
53552
+ }
53553
+ }
53554
+ continue;
53555
+ }
53329
53556
  var value = ObjC.unwrap(item.stringForType("public.file-url"));
53330
53557
  if (value) fileUrls.push(value);
53331
53558
  }
53332
53559
  if (fileUrls.length > 1)
53333
53560
  return JSON.stringify({ status: "error", code: "multiple_files", count: fileUrls.length });
53561
+ if (fileUrls.length === 0 && dataItems > 1)
53562
+ return JSON.stringify({ status: "error", code: "multiple_items", count: dataItems });
53334
53563
  if (fileUrls.length === 1) {
53335
53564
  var url = $.NSURL.URLWithString(fileUrls[0]);
53336
53565
  if (url && !url.isNil() && url.isFileURL) {
@@ -53361,6 +53590,7 @@ var ENVELOPE_CODES = {
53361
53590
  pasteboard_changed: "operation_failed",
53362
53591
  unsupported_content: "validation_error",
53363
53592
  multiple_files: "validation_error",
53593
+ multiple_items: "validation_error",
53364
53594
  too_large: "validation_error",
53365
53595
  write_failed: "operation_failed",
53366
53596
  file_unreadable: "validation_error"
@@ -53387,6 +53617,7 @@ var MESSAGES = {
53387
53617
  pasteboard_changed: "The pasteboard changed while it was being read. Try again.",
53388
53618
  unsupported_content: `The pasteboard holds no supported content: copy ${SUPPORTED}. Text belongs in append-to-note, not in an attachment.`,
53389
53619
  multiple_files: "The pasteboard holds more than one copied file. Copy exactly one file, or attach each file with add-attachment.",
53620
+ multiple_items: "The pasteboard holds more than one image or PDF. Copy exactly one, or save each and attach it with add-attachment.",
53390
53621
  too_large: "The pasteboard contents exceed the 64 MiB attachment limit.",
53391
53622
  write_failed: "Could not write the pasteboard contents to a temporary file.",
53392
53623
  file_unreadable: "The copied file could not be read as a regular file of at most 64 MiB."
@@ -53398,13 +53629,10 @@ function replyError(code, reply) {
53398
53629
  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
53630
  return new PasteboardError(code, message, { accessBehavior });
53400
53631
  }
53401
- if (code === "multiple_files") {
53632
+ if (code === "multiple_files" || code === "multiple_items") {
53402
53633
  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
- );
53634
+ 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.`;
53635
+ return new PasteboardError(code, count ? counted : MESSAGES[code], count ? { count } : {});
53408
53636
  }
53409
53637
  if (code === "unsupported_content" && Array.isArray(reply.types)) {
53410
53638
  const types = reply.types.filter((t) => typeof t === "string").slice(0, 20);
@@ -53617,7 +53845,10 @@ function registerDirectOperations(server2, manager) {
53617
53845
  {
53618
53846
  id: folderId,
53619
53847
  expectedName: external_exports.string().max(1e3),
53620
- expectedParentId: external_exports.string().max(2e3),
53848
+ // Same forms as id, so list-folders' parentIdentifier (a folder UUID)
53849
+ // works; a top-level folder's parent is its account, passed as the
53850
+ // account's x-coredata id.
53851
+ expectedParentId: folderId,
53621
53852
  newName: external_exports.string().min(1).max(1e3)
53622
53853
  },
53623
53854
  (args) => ({
@@ -54020,7 +54251,9 @@ function readFolderStoreFacts(pk, dbPath2 = NOTE_STORE_PATH) {
54020
54251
 
54021
54252
  // src/tools/folderDelete.ts
54022
54253
  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");
54254
+ var accountIdSchema = external_exports.string().max(2e3).transform(looseIdTransform("ICAccount")).pipe(
54255
+ external_exports.string().regex(/^x-coredata:\/\/[0-9a-f-]+\/ICAccount\/p\d+$/i, "An exact account ID is required")
54256
+ );
54024
54257
  var revisionSchema = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
54025
54258
  var defaultDeps = {
54026
54259
  readStore: (pk) => readFolderStoreFacts(pk),
@@ -54506,7 +54739,7 @@ function failure(action, error2) {
54506
54739
  var nameInput = external_exports.string().min(1).max(64).describe("Template name: a built-in (standard-markdown, obsidian) or a saved template's name");
54507
54740
  var templateInput = external_exports.union([external_exports.record(external_exports.unknown()), external_exports.string().max(MAX_TEMPLATE_BYTES)]).optional().describe("The template itself, as a JSON object or JSON text (schemaVersion 1)");
54508
54741
  var templateFileInput = external_exports.string().min(1).max(4096).optional().describe(
54509
- "Absolute path of a JSON template file ending in .json (home, a temp dir, or /Volumes; symlinks refused; at most 256 KiB)"
54742
+ "Absolute path of a JSON template file ending in .json (home, a temp dir, or /Volumes; hidden paths and ~/Library outside iCloud Drive and CloudStorage refused unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; symlinks refused; at most 256 KiB)"
54510
54743
  );
54511
54744
  function sourceText(args) {
54512
54745
  if (args.template === void 0 === (args.templateFile === void 0))
@@ -54576,7 +54809,7 @@ function registerMarkdownTemplates(server2, store = () => new TemplateStore()) {
54576
54809
  server2.registerTool(
54577
54810
  "validate-markdown-template",
54578
54811
  {
54579
- description: 'Use when: checking a template before saving it or exporting with it.\nReturns: valid true, or valid false with every problem as {path, message}, where path is a JSON path such as $.rules["inline.bold"].after.\nDo not use when: you want to store it (save-markdown-template validates too).\nSafety: read-only. Pass exactly one of name (a saved or built-in template), template (JSON object or text) or templateFile.',
54812
+ description: 'Use when: checking a template before saving it or exporting with it.\nReturns: valid true, or valid false with every problem as {path, message}, where path is a JSON path such as $.rules["inline.bold"].after.\nDo not use when: you want to store it (save-markdown-template validates too).\nSafety: read-only. Pass exactly one of name (a saved or built-in template), template (JSON object or text) or templateFile. templateFile reads only a regular .json file in home, temp or /Volumes, refusing hidden paths (~/.docker, .env) and ~/Library outside iCloud Drive and CloudStorage unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; errors never quote the file.',
54580
54813
  inputSchema: validateInput,
54581
54814
  outputSchema: loose,
54582
54815
  annotations: { readOnlyHint: true }
@@ -54617,7 +54850,7 @@ function registerMarkdownTemplates(server2, store = () => new TemplateStore()) {
54617
54850
  server2.registerTool(
54618
54851
  "save-markdown-template",
54619
54852
  {
54620
- description: "Use when: storing a template so export-notes-markdown can use it by name.\nReturns: name, path, bytes and whether an existing template was replaced.\nDo not use when: exporting once (pass templateFile to export-notes-markdown instead).\nSafety: writes one file in the template library only (APPLE_NOTES_MCP_TEMPLATE_DIR, default ~/Library/Application Support/apple-notes-mcp/templates, mode 0600). Validates first; an invalid template is refused with every JSON path. Create-only: an existing name is refused with [template-exists] unless force is true. Built-in names are reserved.",
54853
+ description: "Use when: storing a template so export-notes-markdown can use it by name.\nReturns: name, path, bytes and whether an existing template was replaced.\nDo not use when: exporting once (pass templateFile to export-notes-markdown instead).\nSafety: writes one file in the template library only (APPLE_NOTES_MCP_TEMPLATE_DIR, default ~/Library/Application Support/apple-notes-mcp/templates, mode 0600). Validates first; an invalid template is refused with every JSON path. Create-only: an existing name is refused with [template-exists] unless force is true. Built-in names are reserved. templateFile follows the same read scope as validate-markdown-template.",
54621
54854
  inputSchema: saveInput,
54622
54855
  outputSchema: loose,
54623
54856
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
@@ -54660,7 +54893,6 @@ function registerMarkdownTemplates(server2, store = () => new TemplateStore()) {
54660
54893
 
54661
54894
  // src/utils/svgAnalyzer.ts
54662
54895
  import { createHash as createHash5 } from "node:crypto";
54663
- import { closeSync as closeSync9, constants as constants9, fstatSync as fstatSync9, openSync as openSync9, readSync as readSync7 } from "node:fs";
54664
54896
 
54665
54897
  // src/utils/svgColor.ts
54666
54898
  var NAMED = {
@@ -55503,7 +55735,7 @@ function decodeEntities(raw, line) {
55503
55735
  if (body in PREDEFINED) return PREDEFINED[body];
55504
55736
  throw new SvgError(
55505
55737
  "svg_unsafe",
55506
- `Named entity "&${body.slice(0, 40)};" on line ${line}: only the five XML entities are allowed`
55738
+ `A named entity on line ${line} is not allowed: only the five XML entities are`
55507
55739
  );
55508
55740
  });
55509
55741
  }
@@ -55574,6 +55806,8 @@ function parseXml(source, limits) {
55574
55806
  if (!NAME_START.test(source[i] ?? "")) fail("Invalid tag");
55575
55807
  while (i < source.length && NAME_CHAR.test(source[i])) i++;
55576
55808
  const name = source.slice(pos + 1, i);
55809
+ if (!root && !stack.length && name.slice(name.indexOf(":") + 1) !== "svg")
55810
+ fail("The root element is not <svg>");
55577
55811
  const rawAttributes = [];
55578
55812
  const attributeNames = /* @__PURE__ */ new Set();
55579
55813
  let selfClosing = false;
@@ -56691,50 +56925,12 @@ function analyzeSvgBuffer(source) {
56691
56925
  const viewport = analyzer.run();
56692
56926
  return analysisFor(source, analyzer, viewport);
56693
56927
  }
56694
- function readSvgSource(path10) {
56695
- let fd;
56696
- try {
56697
- fd = openSync9(path10, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
56698
- } catch (error2) {
56699
- if (error2.code === "ELOOP")
56700
- throw new SvgError("svg_file_invalid", "The SVG path is a symbolic link");
56701
- throw new SvgError("svg_file_invalid", "The SVG file does not exist or cannot be read");
56702
- }
56703
- try {
56704
- const info = fstatSync9(fd);
56705
- if (!info.isFile())
56706
- throw new SvgError("svg_file_invalid", "The SVG path is not a regular file");
56707
- if (info.size > SVG_LIMITS.maxSourceBytes)
56708
- throw new SvgError(
56709
- "svg_file_invalid",
56710
- `The SVG is larger than ${SVG_LIMITS.maxSourceBytes} bytes`
56711
- );
56712
- const buffer = Buffer.alloc(SVG_LIMITS.maxSourceBytes + 1);
56713
- let total = 0;
56714
- for (; ; ) {
56715
- const n = readSync7(fd, buffer, total, buffer.length - total, null);
56716
- if (n === 0) break;
56717
- total += n;
56718
- if (total > SVG_LIMITS.maxSourceBytes)
56719
- throw new SvgError(
56720
- "svg_file_invalid",
56721
- `The SVG is larger than ${SVG_LIMITS.maxSourceBytes} bytes`
56722
- );
56723
- }
56724
- return buffer.subarray(0, total);
56725
- } finally {
56726
- closeSync9(fd);
56727
- }
56728
- }
56729
- function analyzeSvgFile(path10) {
56730
- return analyzeSvgBuffer(readSvgSource(path10));
56731
- }
56732
56928
 
56733
56929
  // src/tools/svgAnalysis.ts
56734
56930
  function runSvgAnalysis(args, roots = allowedSaveRoots()) {
56735
- let abs;
56931
+ let source;
56736
56932
  try {
56737
- abs = assertReadableInRoots(args.path, roots, "SVG file");
56933
+ source = readAllowedFile(args.path, SVG_LIMITS.maxSourceBytes, { roots, label: "SVG file" });
56738
56934
  } catch (error2) {
56739
56935
  throw new CodedError(error2.message, {
56740
56936
  code: "validation_error",
@@ -56742,7 +56938,7 @@ function runSvgAnalysis(args, roots = allowedSaveRoots()) {
56742
56938
  });
56743
56939
  }
56744
56940
  try {
56745
- const result = analyzeSvgFile(abs);
56941
+ const result = analyzeSvgBuffer(source);
56746
56942
  return {
56747
56943
  ...result.analysis,
56748
56944
  ...args.includeDrawing ? { drawing: result.drawing } : {}
@@ -56760,7 +56956,7 @@ function runSvgAnalysis(args, roots = allowedSaveRoots()) {
56760
56956
  function registerSvgAnalysis(server2) {
56761
56957
  const inputSchema = {
56762
56958
  path: external_exports.string().min(1).max(4096).describe(
56763
- `Absolute path of one .svg file (home, temp, or /Volumes; not a symbolic link; at most ${SVG_LIMITS.maxSourceBytes} bytes of UTF-8)`
56959
+ `Absolute path of one .svg file (home, temp, or /Volumes; not a hidden path or ~/Library outside iCloud Drive and CloudStorage; not a symbolic link; at most ${SVG_LIMITS.maxSourceBytes} bytes of UTF-8)`
56764
56960
  ),
56765
56961
  includeDrawing: external_exports.boolean().optional().describe(
56766
56962
  "Also return the normalized drawing (strokes with sRGB color, width and points). Off by default: it can be large."
@@ -56769,7 +56965,7 @@ function registerSvgAnalysis(server2) {
56769
56965
  server2.registerTool(
56770
56966
  "analyze-svg",
56771
56967
  {
56772
- description: "Use when: checking whether an SVG file can be represented as editable monoline strokes, and what that conversion would approximate or drop.\nReturns: classification (safe, lossy, unsupported), importable, defaultWriteAllowed, requiredLosses (geometry-approximation, paint-approximation, drop-content), issues with element locations, counts and work budgets, the source SHA-256, and analysisDigest, a SHA-256 over the canonical analysis and normalized drawing. includeDrawing adds the drawing.\nDo not use when: you want to attach the SVG file itself to a note (add-attachment).\nSafety: read-only and local. Opens no Notes data and makes no network request. Scripts, event handlers, style elements, animation, DOCTYPE/entities and external resources are refused with svgCode svg_unsafe; malformed files with svg_invalid; oversized work with svg_complexity_limit.",
56968
+ description: "Use when: checking whether an SVG file can be represented as editable monoline strokes, and what that conversion would approximate or drop.\nReturns: classification (safe, lossy, unsupported), importable, defaultWriteAllowed, requiredLosses (geometry-approximation, paint-approximation, drop-content), issues with element locations, counts and work budgets, the source SHA-256, and analysisDigest, a SHA-256 over the canonical analysis and normalized drawing. includeDrawing adds the drawing.\nDo not use when: you want to attach the SVG file itself to a note (add-attachment).\nSafety: read-only and local. Opens no Notes data and makes no network request. 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; a file whose root element is not <svg> is refused without quoting it. Scripts, event handlers, style elements, animation, DOCTYPE/entities and external resources are refused with svgCode svg_unsafe; malformed files with svg_invalid; oversized work with svg_complexity_limit.",
56773
56969
  inputSchema,
56774
56970
  outputSchema: external_exports.object({ classification: external_exports.string().optional() }).passthrough(),
56775
56971
  annotations: { readOnlyHint: true, openWorldHint: false }
@@ -58005,14 +58201,17 @@ var strokeSchema = external_exports.object({
58005
58201
  force: external_exports.number()
58006
58202
  })
58007
58203
  ).optional(),
58008
- transformApplied: external_exports.boolean().optional()
58204
+ transformApplied: external_exports.boolean().optional(),
58205
+ masked: external_exports.boolean().optional(),
58206
+ pointsTruncated: external_exports.boolean().optional()
58009
58207
  }).strip();
58010
58208
  var decodedDrawingSchema = external_exports.object({
58011
58209
  status: external_exports.literal("ok"),
58012
58210
  strokeCount: external_exports.number().int(),
58013
58211
  strokes: external_exports.array(strokeSchema),
58014
58212
  truncated: external_exports.boolean(),
58015
- bounds
58213
+ bounds,
58214
+ hiddenStrokeCount: external_exports.number().int().optional()
58016
58215
  });
58017
58216
  function decodeRow(row, format, includePoints, deps) {
58018
58217
  const base = {
@@ -58045,7 +58244,8 @@ function decodeRow(row, format, includePoints, deps) {
58045
58244
  status: "ok",
58046
58245
  strokeCount: decoded.strokeCount,
58047
58246
  bounds: decoded.bounds,
58048
- truncated: decoded.truncated
58247
+ truncated: decoded.truncated,
58248
+ ...decoded.hiddenStrokeCount ? { hiddenStrokeCount: decoded.hiddenStrokeCount } : {}
58049
58249
  };
58050
58250
  if (format !== "json") result.svg = drawingToSvg(strokes, decoded.bounds);
58051
58251
  if (format !== "svg")
@@ -58070,6 +58270,28 @@ function getNoteDrawings(noteId3, options = {}) {
58070
58270
  drawings
58071
58271
  };
58072
58272
  }
58273
+ function fitNoteDrawings(result, maxBytes, measure = (r) => Buffer.byteLength(JSON.stringify(r))) {
58274
+ let next = result;
58275
+ let pointsOmitted = false;
58276
+ let svgOmitted = false;
58277
+ if (measure(next) > maxBytes && next.drawings.some((d) => d.strokes?.some((s) => s.points))) {
58278
+ next = {
58279
+ ...next,
58280
+ drawings: next.drawings.map(
58281
+ (d) => d.strokes ? { ...d, strokes: d.strokes.map(({ points: _points, ...rest }) => rest) } : d
58282
+ )
58283
+ };
58284
+ pointsOmitted = true;
58285
+ }
58286
+ if (measure(next) > maxBytes && next.drawings.some((d) => d.svg !== void 0)) {
58287
+ next = {
58288
+ ...next,
58289
+ drawings: next.drawings.map(({ svg: _svg, ...rest }) => rest)
58290
+ };
58291
+ svgOmitted = true;
58292
+ }
58293
+ return { result: next, pointsOmitted, svgOmitted, oversized: measure(next) > maxBytes };
58294
+ }
58073
58295
  function formatNoteDrawings(result) {
58074
58296
  if (result.drawingCount === 0) return `No classic PencilKit drawings in note ${result.id}.`;
58075
58297
  const lines = [
@@ -58077,7 +58299,7 @@ function formatNoteDrawings(result) {
58077
58299
  ];
58078
58300
  for (const d of result.drawings)
58079
58301
  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}`
58302
+ 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
58303
  );
58082
58304
  return lines.join("\n");
58083
58305
  }
@@ -58177,9 +58399,6 @@ function readAudioAssets(noteId3, options = {}) {
58177
58399
  };
58178
58400
  });
58179
58401
  }
58180
- function countWords2(text2) {
58181
- return text2.split(/\s+/u).filter((w) => /[\p{L}\p{N}]/u.test(w)).length;
58182
- }
58183
58402
 
58184
58403
  // src/services/noteTranscription.ts
58185
58404
  var DEFAULT_TRANSCRIPTION_LOCALE = "en-US";
@@ -58194,6 +58413,7 @@ var BUDGET_MARGIN_MS = 1e4;
58194
58413
  var MIN_TAKE_SECONDS = 5;
58195
58414
  var CALL_WIDE_CODES = /* @__PURE__ */ new Set([
58196
58415
  "permission_required",
58416
+ "permission_not_requested",
58197
58417
  "asset_unavailable",
58198
58418
  "unsupported_locale",
58199
58419
  "speech_unavailable"
@@ -58272,7 +58492,7 @@ async function transcribeTake(take, ctx) {
58272
58492
  status: complete ? "ok" : text2 ? "partial" : "indeterminate",
58273
58493
  ...complete ? {} : { code: "incomplete", message: stopReason ?? "stopped early" },
58274
58494
  ...durationSeconds !== void 0 ? { durationSeconds } : {},
58275
- wordCount: countWords2(text2),
58495
+ wordCount: countWords(text2),
58276
58496
  ...engine ? { engine } : {}
58277
58497
  },
58278
58498
  text: text2
@@ -58299,7 +58519,7 @@ async function transcribeRecording(asset, includeText, ctx) {
58299
58519
  status,
58300
58520
  ...status !== "ok" && firstProblem?.code ? { code: firstProblem.code, message: firstProblem.message } : {},
58301
58521
  ...asset.durationSeconds !== null ? { durationSeconds: Math.round(asset.durationSeconds) } : {},
58302
- wordCount: countWords2(transcript),
58522
+ wordCount: countWords(transcript),
58303
58523
  ...includeText ? { transcript } : {},
58304
58524
  takes: outcomes.map((o) => o.take)
58305
58525
  };
@@ -58354,24 +58574,29 @@ async function transcribeNoteAudio(noteId3, options = {}) {
58354
58574
  }
58355
58575
  function fitTranscriptions(result, maxBytes, measure = (r) => Buffer.byteLength(JSON.stringify(r))) {
58356
58576
  if (measure(result) <= maxBytes) return result;
58577
+ const shorten = (share) => ({
58578
+ ...result,
58579
+ recordings: result.recordings.map(
58580
+ (r) => r.transcript ? {
58581
+ ...r,
58582
+ transcript: r.transcript.slice(0, Math.floor(r.transcript.length * share)),
58583
+ transcriptTruncated: true
58584
+ } : r
58585
+ )
58586
+ });
58357
58587
  let next = result;
58358
58588
  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
- };
58589
+ next = shorten(share);
58369
58590
  }
58591
+ if (measure(next) > maxBytes) next = { ...shorten(0), responseOversized: true };
58370
58592
  return next;
58371
58593
  }
58372
58594
  function formatTranscription(result) {
58373
58595
  if (result.recordingCount === 0) return `No audio attachments in note ${result.id}.`;
58374
58596
  const lines = [
58597
+ ...result.responseOversized ? [
58598
+ "The transcripts were dropped: even without them the response exceeds APPLE_NOTES_MCP_EXPORT_MAX_BYTES. Transcribe one recording at a time with attachmentId."
58599
+ ] : [],
58375
58600
  `${result.recordingCount} audio attachment${result.recordingCount === 1 ? "" : "s"} in note ${result.id} (${result.status}, locale ${result.locale}):`
58376
58601
  ];
58377
58602
  result.recordings.forEach((r, i) => {
@@ -59243,10 +59468,11 @@ function prepareDeleteGuards(args) {
59243
59468
  if (guardNoteId === void 0 !== (expectedGuardContentHash === void 0)) {
59244
59469
  return { error: "Pass guardNoteId and expectedGuardContentHash together." };
59245
59470
  }
59246
- if (guardNoteId === id2 || requireActiveNoteId === id2) {
59471
+ const same = (a, b) => a !== void 0 && b !== void 0 && canonicalCoreDataId(a) === canonicalCoreDataId(b);
59472
+ if (same(guardNoteId, id2) || same(requireActiveNoteId, id2)) {
59247
59473
  return { error: "A guard note must be a different note from the one being deleted." };
59248
59474
  }
59249
- if (guardNoteId !== void 0 && guardNoteId === requireActiveNoteId) {
59475
+ if (same(guardNoteId, requireActiveNoteId)) {
59250
59476
  return { error: "requireActiveNoteId repeats guardNoteId; pass only guardNoteId." };
59251
59477
  }
59252
59478
  const prepared = { guards: [], labels: [] };
@@ -59593,7 +59819,7 @@ ${noteList}${truncationNote}${scanNote}${wordCountNote}${syncNote}`,
59593
59819
  registerTool(
59594
59820
  "query-notes",
59595
59821
  {
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.',
59822
+ 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
59823
  inputSchema: {
59598
59824
  query: external_exports.string().min(1, "A query expression is required").max(MAX.QUERY).describe(
59599
59825
  'Boolean query expression, e.g. `folder:"Work Projects" has:checklist -checklist:done`'
@@ -59646,7 +59872,7 @@ registerTool(
59646
59872
  }
59647
59873
  if (result.unreadable > 0) {
59648
59874
  notes.push(
59649
- `\u26A0\uFE0F ${result.unreadable} note bodies could not be decoded, so body predicates did not match them.`
59875
+ `\u26A0\uFE0F ${result.unreadable} note bodies could not be decoded, so body predicates (including negated ones) did not match them.`
59650
59876
  );
59651
59877
  }
59652
59878
  const footer = notes.length ? `
@@ -60049,7 +60275,7 @@ registerTool(
60049
60275
  if (!note) return notFoundResponse(`Note with ID "${id2}" not found`);
60050
60276
  const body = notesManager.getNoteContentById(id2);
60051
60277
  if (!body) return errorResponse(`Failed to read content of note "${note.title}"`);
60052
- const rich = readRichNote(id2);
60278
+ const rich = readRichNote(id2, { skipUnsafeLinks: true });
60053
60279
  const tables = (rich.objectData || []).filter((object3) => object3.type?.includes("table")).map((object3) => {
60054
60280
  try {
60055
60281
  return {
@@ -61659,6 +61885,7 @@ registerTool(
61659
61885
  height: external_exports.number().optional(),
61660
61886
  bytes: external_exports.number().optional(),
61661
61887
  source: external_exports.enum(["fallback", "preview"]).optional(),
61888
+ stale: external_exports.boolean().optional(),
61662
61889
  attachmentId: external_exports.string().optional(),
61663
61890
  identifier: external_exports.string().optional(),
61664
61891
  kind: external_exports.enum(["paper", "drawing"]).optional(),
@@ -61667,8 +61894,9 @@ registerTool(
61667
61894
  },
61668
61895
  withErrorHandling(({ noteId: noteId3, savePath, attachmentId }) => {
61669
61896
  const r = notesManager.exportPaperImageById(noteId3, savePath, attachmentId);
61897
+ const stale = r.source === "fallback" && r.drawing.fallbackImageStale;
61670
61898
  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}`,
61899
+ `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
61900
  {
61673
61901
  savedPath: r.savedPath,
61674
61902
  format: r.format,
@@ -61676,6 +61904,7 @@ registerTool(
61676
61904
  height: r.height,
61677
61905
  bytes: r.bytes,
61678
61906
  source: r.source,
61907
+ ...stale ? { stale } : {},
61679
61908
  attachmentId: attachmentCoreDataId(noteId3, r.drawing.pk),
61680
61909
  identifier: r.drawing.identifier,
61681
61910
  kind: r.drawing.kind,
@@ -61688,7 +61917,7 @@ registerTool(
61688
61917
  "export-attachments",
61689
61918
  {
61690
61919
  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.
61920
+ 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
61921
  Do not use when: exporting one attachment to an exact path (save-attachment) or reading bytes inline (fetch-attachment).
61693
61922
  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
61923
  inputSchema: {
@@ -61700,6 +61929,7 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61700
61929
  exportDir: external_exports.string().optional(),
61701
61930
  exported: external_exports.number().optional(),
61702
61931
  previews: external_exports.number().optional(),
61932
+ fallbacks: external_exports.number().optional(),
61703
61933
  skipped: external_exports.number().optional(),
61704
61934
  failed: external_exports.number().optional(),
61705
61935
  results: external_exports.array(external_exports.object({}).passthrough()).optional(),
@@ -61714,12 +61944,14 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61714
61944
  }));
61715
61945
  const exported = results.filter((x) => x.exportedKind !== null).length;
61716
61946
  const previews = results.filter((x) => x.exportedKind === "preview").length;
61947
+ const fallbacks = results.filter((x) => x.exportedKind === "fallback").length;
61717
61948
  const failed = results.filter((x) => x.error).length;
61718
61949
  const skipped = results.length - exported - failed;
61719
61950
  const structured = {
61720
61951
  exportDir: r.exportDir,
61721
61952
  exported,
61722
61953
  previews,
61954
+ fallbacks,
61723
61955
  skipped,
61724
61956
  failed,
61725
61957
  results
@@ -61733,7 +61965,7 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61733
61965
  );
61734
61966
  }
61735
61967
  }
61736
- const summary = `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${skipped} with nothing on disk, ${failed} failed).`;
61968
+ const summary = `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${fallbacks} Notes rendering(s), ${skipped} with nothing on disk, ${failed} failed).`;
61737
61969
  if (exported === 0 && failed > 0) {
61738
61970
  const error2 = errorResponse(summary, new CodedError(summary, { code: "operation_failed" }));
61739
61971
  return { ...error2, structuredContent: { ...structured, ...error2.structuredContent } };
@@ -61914,12 +62146,13 @@ var exportStatsSchema = external_exports.object({
61914
62146
  unavailable: external_exports.number(),
61915
62147
  tables: external_exports.number(),
61916
62148
  unreadableTables: external_exports.number(),
61917
- unreferenced: external_exports.number()
62149
+ unreferenced: external_exports.number(),
62150
+ staleRenderings: external_exports.number().optional()
61918
62151
  });
61919
62152
  registerTool(
61920
62153
  "export-notes-markdown",
61921
62154
  {
61922
- description: "Use when: exporting one note (by exact id) or a folder's notes as one Markdown document rendered from the decoded note body: headings, bulleted/dashed/numbered lists with indent, checklists with state, block quotes, monospaced blocks, bold/italic/strikethrough/underline/highlight, links, tables, and attachments in body order.\nReturns: the Markdown inline (capped by APPLE_NOTES_MCP_EXPORT_MAX_BYTES), or with outputPath a receipt {format, count, bytes, output}; plus attachment counts and skipped notes (for example password-protected ones).\nDo not use when: you need the legacy HTML-converted Markdown of one note (get-note-markdown) or a restorable backup (export-notes-json). A folder document separates notes with '---' and is a presentation format, not something to import back.\nSafety: read-only against Notes; requires Full Disk Access. outputPath is create-only (an existing file is refused with [output_exists]); assetsDir copies attachment files without replacing existing ones (collisions get -2, -3 suffixes). Without assetsDir, attachments render as labeled placeholders.\nTemplates: pass template ('standard-markdown' reproduces the default output; 'obsidian' adds YAML front matter and copies attachments beside outputPath) or templateFile (a JSON template) to control how every block, inline style, attachment, per-note header/footer and separator renders. Templated receipts add template, warnings (e.g. missing_asset) and assetFiles; an invalid template is refused with [invalid-template] and every problem's JSON path.",
62155
+ description: "Use when: exporting one note (by exact id) or a folder's notes as one Markdown document rendered from the decoded note body: headings, bulleted/dashed/numbered lists with indent, checklists with state, block quotes, monospaced blocks, bold/italic/strikethrough/underline/highlight, links, tables, and attachments in body order.\nReturns: the Markdown inline (capped by APPLE_NOTES_MCP_EXPORT_MAX_BYTES), or with outputPath a receipt {format, count, bytes, output}; plus attachment counts and skipped notes (for example password-protected ones).\nDo not use when: you need the legacy HTML-converted Markdown of one note (get-note-markdown) or a restorable backup (export-notes-json). A folder document separates notes with '---' and is a presentation format, not something to import back.\nSafety: read-only against Notes; requires Full Disk Access. outputPath is create-only (an existing file is refused with [output_exists]); assetsDir copies attachment files without replacing existing ones (collisions get -2, -3 suffixes). Without assetsDir, attachments render as labeled placeholders.\nTemplates: pass template ('standard-markdown' reproduces the default output; 'obsidian' adds YAML front matter and copies attachments beside outputPath) or templateFile (a JSON template; hidden paths and ~/Library outside iCloud Drive and CloudStorage are refused unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1) to control how every block, inline style, attachment, per-note header/footer and separator renders. Templated receipts add template, warnings (e.g. missing_asset) and assetFiles; an invalid template is refused with [invalid-template] and every problem's JSON path.",
61923
62156
  inputSchema: {
61924
62157
  id: noteIdInput.optional(),
61925
62158
  folder: external_exports.string().min(1).max(MAX.FOLDER).optional().describe("Folder path to export instead of one note (nested paths use '/')"),
@@ -61934,12 +62167,13 @@ registerTool(
61934
62167
  `Render through this template: built-in ${BUILTIN_TEMPLATE_NAMES.map((n) => `'${n}'`).join(" or ")}, or a saved template's name (list-markdown-templates). Exclusive with templateFile`
61935
62168
  ),
61936
62169
  templateFile: exportPathInput(
61937
- "JSON template file (.json) to render through (exclusive with template; at most 256 KiB)"
62170
+ "JSON template file (.json) to render through (exclusive with template; at most 256 KiB; hidden paths and ~/Library outside iCloud Drive and CloudStorage refused unless APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1)"
61938
62171
  )
61939
62172
  },
61940
62173
  outputSchema: {
61941
62174
  format: external_exports.string().optional(),
61942
62175
  count: external_exports.number().optional(),
62176
+ truncated: external_exports.boolean().optional(),
61943
62177
  bytes: external_exports.number().optional(),
61944
62178
  markdown: external_exports.string().optional(),
61945
62179
  output: external_exports.string().optional(),
@@ -61978,7 +62212,7 @@ registerTool(
61978
62212
  );
61979
62213
  }
61980
62214
  const warned = receipt.warnings?.length ? `; ${receipt.warnings.length + (receipt.warningsOmitted ?? 0)} warning(s)` : "";
61981
- const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + warned;
62215
+ const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + warned + (receipt.truncated ? "; the folder has more notes than limit, pass a higher limit" : "");
61982
62216
  if (receipt.output)
61983
62217
  return successResponse(
61984
62218
  `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 +62243,7 @@ registerTool(
62009
62243
  outputSchema: {
62010
62244
  format: external_exports.string().optional(),
62011
62245
  count: external_exports.number().optional(),
62246
+ truncated: external_exports.boolean().optional(),
62012
62247
  bytes: external_exports.number().optional(),
62013
62248
  output: external_exports.string().optional(),
62014
62249
  assets: external_exports.object({ dir: external_exports.string(), files: external_exports.number() }).optional(),
@@ -62031,7 +62266,7 @@ registerTool(
62031
62266
  return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
62032
62267
  }
62033
62268
  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}` : "";
62269
+ const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + (receipt.truncated ? "; the folder has more notes than limit, pass a higher limit" : "");
62035
62270
  return successResponse(
62036
62271
  `Wrote ${receipt.count} note(s) as HTML (${receipt.bytes} bytes) to ${receipt.output}${assets}${skipped}.`,
62037
62272
  { ...receipt }
@@ -62214,27 +62449,32 @@ registerTool(
62214
62449
  drawingCount: external_exports.number().optional(),
62215
62450
  status: external_exports.string().optional(),
62216
62451
  drawings: external_exports.array(external_exports.object({}).passthrough()).optional(),
62217
- pointsOmitted: external_exports.boolean().optional()
62452
+ pointsOmitted: external_exports.boolean().optional(),
62453
+ svgOmitted: external_exports.boolean().optional()
62218
62454
  }
62219
62455
  },
62220
62456
  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)." : "");
62457
+ const limit = exportMaxResponseBytes();
62458
+ const { result, pointsOmitted, svgOmitted, oversized } = fitNoteDrawings(
62459
+ getNoteDrawings(id2, { format, includePoints }),
62460
+ limit
62461
+ );
62462
+ if (oversized)
62463
+ return errorResponse(
62464
+ `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).`
62465
+ );
62466
+ 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
62467
  return successResponse(text2, {
62229
62468
  ...result,
62230
- ...pointsOmitted ? { pointsOmitted } : {}
62469
+ ...pointsOmitted ? { pointsOmitted } : {},
62470
+ ...svgOmitted ? { svgOmitted } : {}
62231
62471
  });
62232
62472
  }, "Error reading drawings")
62233
62473
  );
62234
62474
  registerTool(
62235
62475
  "transcribe-note-audio",
62236
62476
  {
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.",
62477
+ 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
62478
  inputSchema: {
62239
62479
  id: noteIdInput,
62240
62480
  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 +62492,13 @@ registerTool(
62252
62492
  locale: external_exports.string().optional(),
62253
62493
  status: external_exports.string().optional(),
62254
62494
  recordingCount: external_exports.number().optional(),
62255
- recordings: external_exports.array(external_exports.object({}).passthrough()).optional()
62495
+ recordings: external_exports.array(external_exports.object({}).passthrough()).optional(),
62496
+ responseOversized: external_exports.boolean().optional()
62256
62497
  }
62257
62498
  },
62258
62499
  withAsyncErrorHandling(async (params, signal) => {
62259
62500
  const { id: id2, locale, attachmentId, includeText, downloadAssets, maxSeconds } = params;
62501
+ const toResponse = (r) => successResponse(formatTranscription(r), r);
62260
62502
  const result = fitTranscriptions(
62261
62503
  await transcribeNoteAudio(id2, {
62262
62504
  locale,
@@ -62266,12 +62508,10 @@ registerTool(
62266
62508
  maxSeconds,
62267
62509
  signal
62268
62510
  }),
62269
- exportMaxResponseBytes()
62270
- );
62271
- return successResponse(
62272
- formatTranscription(result),
62273
- result
62511
+ exportMaxResponseBytes(),
62512
+ (r) => Buffer.byteLength(JSON.stringify(toResponse(r)))
62274
62513
  );
62514
+ return toResponse(result);
62275
62515
  }, "Error transcribing audio")
62276
62516
  );
62277
62517
  registerTool(
@@ -62386,13 +62626,14 @@ process.on("uncaughtException", (err) => {
62386
62626
  process.on("unhandledRejection", (reason) => {
62387
62627
  console.error("[unhandledRejection]", reason);
62388
62628
  });
62389
- var shutdown = createShutdown(process.stdout, () => process.exit(0));
62629
+ var inFlight = new InFlightRequests();
62630
+ var shutdown = createShutdown(process.stdout, () => process.exit(0), void 0, inFlight);
62390
62631
  for (const sig of ["SIGINT", "SIGTERM"]) {
62391
62632
  process.on(sig, shutdown);
62392
62633
  }
62393
62634
  process.stdin.on("end", shutdown);
62394
62635
  process.stdin.on("close", shutdown);
62395
- var transport = withJsonSchema2020_12(new StdioServerTransport());
62636
+ var transport = trackRequests(withJsonSchema2020_12(new StdioServerTransport()), inFlight);
62396
62637
  await server.connect(transport);
62397
62638
  /*! Bundled license information:
62398
62639