apple-notes-mcp 2.9.16 → 2.9.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +80 -2
  2. package/build/index.js +485 -152
  3. package/package.json +1 -1
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, constants9) {
420
+ optimizeNames(names, constants10) {
421
421
  if (!names[this.name.str])
422
422
  return;
423
423
  if (this.rhs)
424
- this.rhs = optimizeExpr(this.rhs, names, constants9);
424
+ this.rhs = optimizeExpr(this.rhs, names, constants10);
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, constants9) {
441
+ optimizeNames(names, constants10) {
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, constants9);
444
+ this.rhs = optimizeExpr(this.rhs, names, constants10);
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, constants9) {
506
- this.code = optimizeExpr(this.code, names, constants9);
505
+ optimizeNames(names, constants10) {
506
+ this.code = optimizeExpr(this.code, names, constants10);
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, constants9) {
535
+ optimizeNames(names, constants10) {
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, constants9))
540
+ if (n.optimizeNames(names, constants10))
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, constants9) {
593
+ optimizeNames(names, constants10) {
594
594
  var _a;
595
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants9);
596
- if (!(super.optimizeNames(names, constants9) || this.else))
595
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants10);
596
+ if (!(super.optimizeNames(names, constants10) || this.else))
597
597
  return;
598
- this.condition = optimizeExpr(this.condition, names, constants9);
598
+ this.condition = optimizeExpr(this.condition, names, constants10);
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, constants9) {
622
- if (!super.optimizeNames(names, constants9))
621
+ optimizeNames(names, constants10) {
622
+ if (!super.optimizeNames(names, constants10))
623
623
  return;
624
- this.iteration = optimizeExpr(this.iteration, names, constants9);
624
+ this.iteration = optimizeExpr(this.iteration, names, constants10);
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, constants9) {
661
- if (!super.optimizeNames(names, constants9))
660
+ optimizeNames(names, constants10) {
661
+ if (!super.optimizeNames(names, constants10))
662
662
  return;
663
- this.iterable = optimizeExpr(this.iterable, names, constants9);
663
+ this.iterable = optimizeExpr(this.iterable, names, constants10);
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, constants9) {
705
+ optimizeNames(names, constants10) {
706
706
  var _a, _b;
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);
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);
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, constants9) {
1010
+ function optimizeExpr(expr, names, constants10) {
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 = constants9[n.str];
1025
+ const c = constants10[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 && constants9[c.str] !== void 0);
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);
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 constants9 = {
7425
+ var constants10 = {
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 constants9) {
7465
- v = { value: constants9[c] };
7464
+ for (c in constants10) {
7465
+ v = { value: constants10[c] };
7466
7466
  Object.defineProperty(DOMException, c, v);
7467
7467
  Object.defineProperty(DOMException.prototype, c, v);
7468
7468
  }
@@ -24465,14 +24465,14 @@ var require_turndown_cjs = __commonJS({
24465
24465
  } else if (node.nodeType === 1) {
24466
24466
  replacement = replacementForNode.call(self, node);
24467
24467
  }
24468
- return join30(output, replacement);
24468
+ return join31(output, replacement);
24469
24469
  }, "");
24470
24470
  }
24471
24471
  function postProcess(output) {
24472
24472
  var self = this;
24473
24473
  this.rules.forEach(function(rule) {
24474
24474
  if (typeof rule.append === "function") {
24475
- output = join30(output, rule.append(self.options));
24475
+ output = join31(output, rule.append(self.options));
24476
24476
  }
24477
24477
  });
24478
24478
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -24484,7 +24484,7 @@ var require_turndown_cjs = __commonJS({
24484
24484
  if (whitespace.leading || whitespace.trailing) content = content.trim();
24485
24485
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
24486
24486
  }
24487
- function join30(output, replacement) {
24487
+ function join31(output, replacement) {
24488
24488
  var s1 = trimTrailingNewlines(output);
24489
24489
  var s2 = trimLeadingNewlines(replacement);
24490
24490
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -42435,7 +42435,7 @@ function readSmartFolderIds(read = readSmartFolders) {
42435
42435
  }
42436
42436
  function smartFolderDestinationError(folderPath) {
42437
42437
  return new CodedError(
42438
- `Refused: "${folderPath}" is a smart folder. Smart folders only gather notes by their rules and cannot hold notes or folders; choose an ordinary folder (list-folders). Nothing was changed`,
42438
+ `Refused: "${folderPath}" is a smart folder. Smart folders only gather notes by their rules and cannot hold notes or folders; choose an ordinary folder (list-folders marks smart folders smartFolder: true). Nothing was changed`,
42439
42439
  {
42440
42440
  code: "unsupported",
42441
42441
  committed: false,
@@ -43881,6 +43881,9 @@ var AppleNotesManager = class {
43881
43881
  /**
43882
43882
  * Lists all folders in an account with full hierarchical paths.
43883
43883
  *
43884
+ * Smart folders are included (AppleScript lists them like ordinary folders)
43885
+ * and carry `smartFolder: true` when the NoteStore database is readable.
43886
+ *
43884
43887
  * Each folder's `name` field contains the full path (e.g., "Work/Clients/Omnia")
43885
43888
  * so that duplicate folder names (e.g., multiple "Archive" folders) are
43886
43889
  * distinguishable and can be used directly in other operations.
@@ -43939,11 +43942,13 @@ var AppleNotesManager = class {
43939
43942
  }
43940
43943
  return safeName;
43941
43944
  };
43945
+ const smart = new Set(entries2.some((entry) => entry.id) ? this.smartFolderIds() : []);
43942
43946
  return entries2.map((entry) => ({
43943
43947
  id: entry.id,
43944
43948
  name: buildPath(entry),
43945
43949
  account: entry.account || targetAccount || "",
43946
- shared: entry.shared
43950
+ shared: entry.shared,
43951
+ ...smart.has(entry.id) ? { smartFolder: true } : {}
43947
43952
  }));
43948
43953
  }
43949
43954
  /**
@@ -48265,6 +48270,7 @@ function comparableVisibleText(html) {
48265
48270
  }
48266
48271
 
48267
48272
  // src/services/backgroundNotes.ts
48273
+ var capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
48268
48274
  var BACKGROUND_SHORTCUT = "Apple Notes MCP - Background Operations v5";
48269
48275
  var backgroundShortcutName = () => process.env.APPLE_NOTES_MCP_BACKGROUND_SHORTCUT || BACKGROUND_SHORTCUT;
48270
48276
  var backgroundStatus = () => nativeTagsStatus(backgroundShortcutName());
@@ -48375,10 +48381,14 @@ function validateAppendContent(content, format, options = {}) {
48375
48381
  );
48376
48382
  }
48377
48383
  }
48384
+ var BRIDGE_REFUSED = "refused the request without running it: its Find Notes step did not find exactly one note with this exact title whose body contains the scope text. Notes search can lag behind a note created or edited moments ago, so wait a minute and retry, or pass a longer, more distinctive scopeText";
48378
48385
  function runBackgroundShortcut(input, status = backgroundStatus()) {
48379
48386
  if (!status.installed)
48380
- throw new Error(
48381
- `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
48387
+ throw Object.assign(
48388
+ new Error(
48389
+ `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
48390
+ ),
48391
+ { shortcut: status.shortcut, notInstalled: true }
48382
48392
  );
48383
48393
  const directory = mkdtempSync4(join17(tmpdir4(), "apple-notes-background-"));
48384
48394
  try {
@@ -48401,16 +48411,31 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
48401
48411
  }),
48402
48412
  { mode: 384 }
48403
48413
  );
48414
+ let output;
48404
48415
  try {
48405
- execFileSync16("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
48406
- encoding: "utf8",
48407
- timeout: callTimeoutMs() ?? 6e4,
48408
- maxBuffer: 1024 * 1024,
48409
- stdio: ["ignore", "pipe", "pipe"]
48410
- });
48416
+ output = execFileSync16(
48417
+ "/usr/bin/shortcuts",
48418
+ ["run", status.identifier, "--input-path", file],
48419
+ {
48420
+ encoding: "utf8",
48421
+ timeout: callTimeoutMs() ?? 6e4,
48422
+ maxBuffer: 1024 * 1024,
48423
+ stdio: ["ignore", "pipe", "pipe"]
48424
+ }
48425
+ );
48411
48426
  } catch (error2) {
48412
48427
  throw Object.assign(error2, { shortcut: status.shortcut });
48413
48428
  }
48429
+ if (/_REFUSED\b/.test(String(output ?? "")))
48430
+ throw Object.assign(
48431
+ new Error(
48432
+ status.shortcut === markdownShortcutName() ? "refused the request without running it" : BRIDGE_REFUSED
48433
+ ),
48434
+ {
48435
+ shortcut: status.shortcut,
48436
+ refused: true
48437
+ }
48438
+ );
48414
48439
  } finally {
48415
48440
  rmSync4(directory, { recursive: true, force: true });
48416
48441
  }
@@ -48480,6 +48505,7 @@ function assertPreserved(before, after, options = {}) {
48480
48505
  function describeTransportFailure(error2) {
48481
48506
  const detail = error2;
48482
48507
  const named = detail?.shortcut ? `the "${detail.shortcut}" Shortcut` : "the background Shortcut";
48508
+ if (detail?.refused) return `${named} ${detail.message}`;
48483
48509
  return detail?.code === "ETIMEDOUT" ? `Shortcuts timed out waiting for ${named}. ${shortcutConsentHint(detail.shortcut)}` : `${named} failed: ${String(detail?.stderr || detail?.message || "no output").trim().slice(0, 400)}`;
48484
48510
  }
48485
48511
  function mutateBackground(request, operation, data, verify, deps) {
@@ -48497,16 +48523,30 @@ function mutateBackground(request, operation, data, verify, deps) {
48497
48523
  throw new Error("Note revision changed during preflight");
48498
48524
  let transportUncertain = false;
48499
48525
  let transportMessage = "";
48526
+ let transportError;
48500
48527
  try {
48501
48528
  deps.run({ ...data, operation, title: before.title, scopeText: request.scopeText });
48502
48529
  } catch (error2) {
48503
48530
  transportUncertain = true;
48531
+ transportError = error2 ?? {};
48504
48532
  transportMessage = describeTransportFailure(error2);
48505
48533
  }
48506
48534
  const after = deps.read(request.id);
48507
48535
  try {
48508
48536
  verify(before, after);
48509
48537
  } catch (error2) {
48538
+ const unchanged = after.hash === before.hash && after.pinned === before.pinned;
48539
+ if (transportError?.notInstalled && unchanged)
48540
+ throw new CodedError(
48541
+ `${transportError.message}. The Shortcut never ran, so nothing was written; run apple-notes-mcp setup, then retry.`,
48542
+ { code: "shortcut_not_installed", committed: false, indeterminate: false }
48543
+ );
48544
+ const exitedWithError = typeof transportError?.status === "number" && transportError.status !== 0 && !transportError.signal && transportError.code !== "ETIMEDOUT";
48545
+ if (transportError && (transportError.refused || exitedWithError) && unchanged)
48546
+ throw new CodedError(
48547
+ `${capitalize(transportMessage)}. The note is unchanged, so nothing was written; it is safe to retry once the cause is fixed.`,
48548
+ { code: "operation_failed", committed: false, indeterminate: false }
48549
+ );
48510
48550
  throw new Error(
48511
48551
  `Operation outcome uncertain; read exact note before any retry: ${error2 instanceof Error ? error2.message : "readback failed"}${transportMessage ? "; " + transportMessage : ""}`
48512
48552
  );
@@ -51666,16 +51706,16 @@ var NOTE_PLACEHOLDERS = [
51666
51706
  "exportStem"
51667
51707
  ];
51668
51708
  var PLACEHOLDER_MODIFIERS = ["raw", "yaml"];
51669
- var wrap2 = (before, after = "", join30) => ({
51709
+ var wrap2 = (before, after = "", join31) => ({
51670
51710
  mode: "wrap",
51671
51711
  before,
51672
51712
  after,
51673
- ...join30 ? { join: join30 } : {}
51713
+ ...join31 ? { join: join31 } : {}
51674
51714
  });
51675
- var pattern = (value, join30) => ({
51715
+ var pattern = (value, join31) => ({
51676
51716
  mode: "pattern",
51677
51717
  value,
51678
- ...join30 ? { join: join30 } : {}
51718
+ ...join31 ? { join: join31 } : {}
51679
51719
  });
51680
51720
  var STANDARD = {
51681
51721
  schemaVersion: 1,
@@ -53056,6 +53096,22 @@ function exportWithTemplate(request, deps, chosen, notes, skipped, { output, ass
53056
53096
 
53057
53097
  // src/tools/directOperations.ts
53058
53098
  import { createHash as createHash3 } from "node:crypto";
53099
+ import {
53100
+ closeSync as closeSync7,
53101
+ constants as constants7,
53102
+ fstatSync as fstatSync7,
53103
+ mkdtempSync as mkdtempSync6,
53104
+ openSync as openSync7,
53105
+ readFileSync as readFileSync4,
53106
+ readSync as readSync5,
53107
+ rmSync as rmSync6,
53108
+ writeFileSync as writeFileSync5
53109
+ } from "node:fs";
53110
+ import { tmpdir as tmpdir6 } from "node:os";
53111
+ import { basename as basename6, extname as extname7, isAbsolute as isAbsolute4, join as join24 } from "node:path";
53112
+
53113
+ // src/utils/pasteboardFreeze.ts
53114
+ import { execFileSync as execFileSync19 } from "node:child_process";
53059
53115
  import {
53060
53116
  closeSync as closeSync6,
53061
53117
  constants as constants6,
@@ -53064,10 +53120,224 @@ import {
53064
53120
  openSync as openSync6,
53065
53121
  readFileSync as readFileSync3,
53066
53122
  rmSync as rmSync5,
53123
+ statSync as statSync4,
53067
53124
  writeFileSync as writeFileSync4
53068
53125
  } from "node:fs";
53069
53126
  import { tmpdir as tmpdir5 } from "node:os";
53070
- import { basename as basename5, extname as extname6, isAbsolute as isAbsolute4, join as join23 } from "node:path";
53127
+ import { basename as basename5, extname as extname6, join as join23 } from "node:path";
53128
+ var PASTEBOARD_NAME_ENV = "APPLE_NOTES_MCP_PASTEBOARD_NAME";
53129
+ var MAX_PASTEBOARD_BYTES = 64 * 1024 * 1024;
53130
+ var PASTEBOARD_DATA_TYPES = [
53131
+ { type: "public.png", ext: "png", label: "image" },
53132
+ { type: "public.jpeg", ext: "jpg", label: "image" },
53133
+ { type: "public.heic", ext: "heic", label: "image" },
53134
+ { type: "com.compuserve.gif", ext: "gif", label: "image" },
53135
+ { type: "public.tiff", ext: "tiff", label: "image" },
53136
+ { type: "com.adobe.pdf", ext: "pdf", label: "document" }
53137
+ ];
53138
+ var PASTEBOARD_ACCESS_BEHAVIORS = {
53139
+ 0: "default",
53140
+ 1: "ask",
53141
+ 2: "alwaysAllow",
53142
+ 3: "alwaysDeny"
53143
+ };
53144
+ var PASTEBOARD_FREEZE_JXA = `
53145
+ ObjC.import("AppKit");
53146
+ function run(argv) {
53147
+ var dir = argv[0], name = argv[1], limit = Number(argv[2]), prefs = JSON.parse(argv[3]);
53148
+ var allowAlert = argv[4] === "1";
53149
+ var pb = name ? $.NSPasteboard.pasteboardWithName(name) : $.NSPasteboard.generalPasteboard;
53150
+ if (!pb || pb.isNil()) return JSON.stringify({ status: "error", code: "pasteboard_unavailable" });
53151
+ if (!name && pb.respondsToSelector("accessBehavior")) {
53152
+ var behavior = Number(pb.accessBehavior);
53153
+ if (behavior === 3 || (behavior !== 2 && !allowAlert))
53154
+ return JSON.stringify({ status: "error", code: "pasteboard_access_denied", accessBehavior: behavior });
53155
+ }
53156
+ var before = pb.changeCount;
53157
+ var types = ObjC.deepUnwrap(pb.types) || [];
53158
+ if (types.length === 0) return JSON.stringify({ status: "error", code: "pasteboard_empty" });
53159
+ var result = null;
53160
+ var items = pb.pasteboardItems;
53161
+ var itemCount = items && !items.isNil() ? Number(items.count) : 0;
53162
+ var fileUrls = [];
53163
+ for (var k = 0; k < itemCount; k++) {
53164
+ var item = items.objectAtIndex(k);
53165
+ var itemTypes = ObjC.deepUnwrap(item.types) || [];
53166
+ if (itemTypes.indexOf("public.file-url") < 0) continue;
53167
+ var value = ObjC.unwrap(item.stringForType("public.file-url"));
53168
+ if (value) fileUrls.push(value);
53169
+ }
53170
+ if (fileUrls.length > 1)
53171
+ return JSON.stringify({ status: "error", code: "multiple_files", count: fileUrls.length });
53172
+ if (fileUrls.length === 1) {
53173
+ var url = $.NSURL.URLWithString(fileUrls[0]);
53174
+ if (url && !url.isNil() && url.isFileURL) {
53175
+ result = { status: "ok", kind: "file", type: "public.file-url", path: ObjC.unwrap(url.path) };
53176
+ }
53177
+ }
53178
+ if (!result) {
53179
+ for (var i = 0; i < prefs.length && !result; i++) {
53180
+ if (types.indexOf(prefs[i][0]) < 0) continue;
53181
+ var data = pb.dataForType(prefs[i][0]);
53182
+ if (!data || data.isNil() || data.length === 0) continue;
53183
+ if (data.length > limit) return JSON.stringify({ status: "error", code: "too_large", bytes: data.length });
53184
+ var path = dir + "/pasteboard." + prefs[i][1];
53185
+ if (!data.writeToFileAtomically(path, true))
53186
+ return JSON.stringify({ status: "error", code: "write_failed" });
53187
+ result = { status: "ok", kind: "data", type: prefs[i][0], path: path, bytes: data.length };
53188
+ }
53189
+ }
53190
+ if (pb.changeCount !== before) return JSON.stringify({ status: "error", code: "pasteboard_changed" });
53191
+ return JSON.stringify(result || { status: "error", code: "unsupported_content", types: types.slice(0, 20) });
53192
+ }
53193
+ `;
53194
+ var ENVELOPE_CODES = {
53195
+ pasteboard_access_denied: "permission_denied",
53196
+ pasteboard_unavailable: "operation_failed",
53197
+ pasteboard_timeout: "operation_failed",
53198
+ pasteboard_empty: "validation_error",
53199
+ pasteboard_changed: "operation_failed",
53200
+ unsupported_content: "validation_error",
53201
+ multiple_files: "validation_error",
53202
+ too_large: "validation_error",
53203
+ write_failed: "operation_failed",
53204
+ file_unreadable: "validation_error"
53205
+ };
53206
+ var PasteboardError = class extends CodedError {
53207
+ constructor(code, message, details = {}) {
53208
+ super(message, {
53209
+ ...details,
53210
+ code: ENVELOPE_CODES[code],
53211
+ pasteboardCode: code,
53212
+ committed: false
53213
+ });
53214
+ this.code = code;
53215
+ this.name = "PasteboardError";
53216
+ }
53217
+ code;
53218
+ };
53219
+ var SUPPORTED = "an image (PNG, JPEG, HEIC, GIF, TIFF), a PDF, or one copied file";
53220
+ var MESSAGES = {
53221
+ pasteboard_access_denied: "Reading the pasteboard now would make macOS ask whether to allow the paste, so nothing was read. Call again with allowPasteAlert: true to let macOS show its paste alert, or allow pasting for the app that runs this server in System Settings, where macOS lists it after its first paste alert.",
53222
+ pasteboard_unavailable: "The pasteboard is not reachable from this process. The MCP host must run in your logged-in GUI session (not over SSH or as a background daemon).",
53223
+ pasteboard_timeout: "Reading the pasteboard timed out; nothing was attached. If macOS showed a paste alert, answer it and try again.",
53224
+ pasteboard_empty: "The pasteboard is empty. Copy an image or a file first.",
53225
+ pasteboard_changed: "The pasteboard changed while it was being read. Try again.",
53226
+ unsupported_content: `The pasteboard holds no supported content: copy ${SUPPORTED}. Text belongs in append-to-note, not in an attachment.`,
53227
+ multiple_files: "The pasteboard holds more than one copied file. Copy exactly one file, or attach each file with add-attachment.",
53228
+ too_large: "The pasteboard contents exceed the 64 MiB attachment limit.",
53229
+ write_failed: "Could not write the pasteboard contents to a temporary file.",
53230
+ file_unreadable: "The copied file could not be read as a regular file of at most 64 MiB."
53231
+ };
53232
+ function replyError(code, reply) {
53233
+ if (code === "pasteboard_access_denied") {
53234
+ const raw = typeof reply.accessBehavior === "number" ? reply.accessBehavior : void 0;
53235
+ const accessBehavior = raw !== void 0 && PASTEBOARD_ACCESS_BEHAVIORS[raw] || "unknown";
53236
+ 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;
53237
+ return new PasteboardError(code, message, { accessBehavior });
53238
+ }
53239
+ if (code === "multiple_files") {
53240
+ const count = typeof reply.count === "number" ? reply.count : void 0;
53241
+ return new PasteboardError(
53242
+ code,
53243
+ count ? `The pasteboard holds ${count} copied files. Copy exactly one file, or attach each file with add-attachment.` : MESSAGES[code],
53244
+ count ? { count } : {}
53245
+ );
53246
+ }
53247
+ if (code === "unsupported_content" && Array.isArray(reply.types)) {
53248
+ const types = reply.types.filter((t) => typeof t === "string").slice(0, 20);
53249
+ if (types.length)
53250
+ return new PasteboardError(
53251
+ code,
53252
+ `${MESSAGES[code]} Pasteboard types found: ${types.join(", ")}.`,
53253
+ { types }
53254
+ );
53255
+ }
53256
+ if (code === "too_large" && typeof reply.bytes === "number")
53257
+ return new PasteboardError(code, MESSAGES[code], { bytes: reply.bytes });
53258
+ return new PasteboardError(code, MESSAGES[code]);
53259
+ }
53260
+ function pasteboardTimeoutMs() {
53261
+ return callTimeoutMs() ?? envPositiveNumber("APPLE_NOTES_MCP_TIMEOUT_MS") ?? 3e4;
53262
+ }
53263
+ function defaultRunJxa(args) {
53264
+ return execFileSync19("osascript", ["-l", "JavaScript", "-e", PASTEBOARD_FREEZE_JXA, ...args], {
53265
+ encoding: "utf8",
53266
+ timeout: pasteboardTimeoutMs(),
53267
+ maxBuffer: 1024 * 1024,
53268
+ stdio: ["pipe", "pipe", "pipe"]
53269
+ });
53270
+ }
53271
+ function pasteboardFilename(requested, frozenFilename) {
53272
+ if (requested === void 0) return void 0;
53273
+ const ext = extname6(frozenFilename);
53274
+ return ext && !extname6(requested) ? requested + ext : requested;
53275
+ }
53276
+ function readRegularFile(path10) {
53277
+ let descriptor;
53278
+ try {
53279
+ descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
53280
+ } catch {
53281
+ throw new PasteboardError("file_unreadable", MESSAGES.file_unreadable);
53282
+ }
53283
+ try {
53284
+ const stat = fstatSync6(descriptor);
53285
+ if (!stat.isFile() || stat.size === 0 || stat.size > MAX_PASTEBOARD_BYTES)
53286
+ throw new PasteboardError("file_unreadable", MESSAGES.file_unreadable);
53287
+ return readFileSync3(descriptor);
53288
+ } finally {
53289
+ closeSync6(descriptor);
53290
+ }
53291
+ }
53292
+ function freezePasteboard(options = {}) {
53293
+ const directory = mkdtempSync5(join23(tmpdir5(), "notes-pasteboard-"));
53294
+ const cleanup = () => rmSync5(directory, { recursive: true, force: true });
53295
+ try {
53296
+ const prefs = JSON.stringify(PASTEBOARD_DATA_TYPES.map((t) => [t.type, t.ext]));
53297
+ let raw;
53298
+ try {
53299
+ raw = (options.runJxa ?? defaultRunJxa)([
53300
+ directory,
53301
+ options.pasteboardName ?? "",
53302
+ String(MAX_PASTEBOARD_BYTES),
53303
+ prefs,
53304
+ options.allowPasteAlert ? "1" : "0"
53305
+ ]);
53306
+ } catch (error2) {
53307
+ throw error2?.code === "ETIMEDOUT" ? new PasteboardError("pasteboard_timeout", MESSAGES.pasteboard_timeout) : new PasteboardError("pasteboard_unavailable", MESSAGES.pasteboard_unavailable);
53308
+ }
53309
+ let reply;
53310
+ try {
53311
+ reply = JSON.parse(raw.trim());
53312
+ } catch {
53313
+ throw new PasteboardError("pasteboard_unavailable", MESSAGES.pasteboard_unavailable);
53314
+ }
53315
+ if (reply.status !== "ok" || !reply.path || !reply.type) {
53316
+ const code = reply.code && reply.code in MESSAGES ? reply.code : "pasteboard_unavailable";
53317
+ throw replyError(code, reply);
53318
+ }
53319
+ if (reply.kind === "file") {
53320
+ const bytes = readRegularFile(reply.path);
53321
+ const filename2 = basename5(reply.path);
53322
+ const path11 = join23(directory, filename2);
53323
+ writeFileSync4(path11, bytes, { mode: 384 });
53324
+ return { kind: "file", type: reply.type, path: path11, filename: filename2, bytes: bytes.length, cleanup };
53325
+ }
53326
+ if (join23(directory, basename5(reply.path)) !== reply.path)
53327
+ throw new PasteboardError("write_failed", MESSAGES.write_failed);
53328
+ const label = PASTEBOARD_DATA_TYPES.find((t) => t.type === reply.type)?.label ?? "item";
53329
+ const filename = `Pasted ${label}${extname6(reply.path)}`;
53330
+ const path10 = join23(directory, filename);
53331
+ writeFileSync4(path10, readFileSync3(reply.path), { mode: 384 });
53332
+ rmSync5(reply.path, { force: true });
53333
+ return { kind: "data", type: reply.type, path: path10, filename, bytes: statSync4(path10).size, cleanup };
53334
+ } catch (error2) {
53335
+ cleanup();
53336
+ throw error2;
53337
+ }
53338
+ }
53339
+
53340
+ // src/tools/directOperations.ts
53071
53341
  var noteId = exactIdInput(
53072
53342
  "ICNote",
53073
53343
  /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i,
@@ -53112,17 +53382,41 @@ function assertExistingContentPreserved(before, after) {
53112
53382
  }
53113
53383
  function localAttachment(path10) {
53114
53384
  if (!isAbsolute4(path10)) throw new Error("An absolute local file path is required");
53115
- const descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
53385
+ const descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
53116
53386
  try {
53117
- const stat = fstatSync6(descriptor);
53118
- if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
53387
+ const stat = fstatSync7(descriptor);
53388
+ if (!stat.isFile() || stat.size === 0 || stat.size > MAX_ADD_ATTACHMENT_BYTES)
53119
53389
  throw new Error("Attachment must be a nonempty regular file of at most 64 MiB");
53120
- const bytes = readFileSync3(descriptor);
53390
+ const bytes = readFileSync4(descriptor);
53121
53391
  if (bytes.length !== stat.size)
53122
53392
  throw new Error("Attachment changed while it was being read; try again");
53123
53393
  return bytes;
53124
53394
  } finally {
53125
- closeSync6(descriptor);
53395
+ closeSync7(descriptor);
53396
+ }
53397
+ }
53398
+ var MAX_ADD_ATTACHMENT_BYTES = 64 * 1024 * 1024;
53399
+ function fileMatches(path10, size, expected) {
53400
+ let descriptor;
53401
+ try {
53402
+ descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
53403
+ const stat = fstatSync7(descriptor);
53404
+ if (!stat.isFile() || stat.size !== size) return false;
53405
+ const hash = createHash3("sha256");
53406
+ const chunk = Buffer.allocUnsafe(1024 * 1024);
53407
+ let total = 0;
53408
+ for (; ; ) {
53409
+ const read = readSync5(descriptor, chunk, 0, chunk.length, null);
53410
+ if (read === 0) break;
53411
+ total += read;
53412
+ if (total > size) return false;
53413
+ hash.update(chunk.subarray(0, read));
53414
+ }
53415
+ return total === size && hash.digest("hex") === expected;
53416
+ } catch {
53417
+ return false;
53418
+ } finally {
53419
+ if (descriptor !== void 0) closeSync7(descriptor);
53126
53420
  }
53127
53421
  }
53128
53422
  function registerDirectOperations(server2, manager) {
@@ -53235,19 +53529,67 @@ function registerDirectOperations(server2, manager) {
53235
53529
  }
53236
53530
  }
53237
53531
  );
53532
+ tool(
53533
+ "add-attachment-from-pasteboard",
53534
+ "Use when: the user copied an image, a PDF, or a file (screenshot, Copy Image, Finder Copy) and wants it attached to an exact note.\nReturns: the add-attachment result (attachment id, bytes, name, content hash) plus source: what was taken from the pasteboard (kind, type, default filename).\nDo not use when: the pasteboard holds text (use append-to-note), several copied files (refused; use add-attachment per file), or you have a file path (use add-attachment).\nSafety: checks the note and revision before reading the pasteboard; reads nothing when macOS would show its paste alert unless allowPasteAlert is true (error pasteboardCode pasteboard_access_denied); reads the pasteboard once and freezes its bytes into a private temporary file before attaching, never writes to the pasteboard, then runs add-attachment's checks: fresh rich revision, at most 64 MiB, no insertion retry, existing content and exact bytes verified. Needs the MCP host to run in the logged-in GUI session.",
53535
+ {
53536
+ id: noteId,
53537
+ expectedContentHash: revision,
53538
+ filename: attachmentInput.filename.describe(
53539
+ `Name the attachment gets in Notes (default: the copied file's name, or "Pasted image.png" / "Pasted document.pdf"). Without an extension, the pasted type's extension is added; with one, it must match the pasted type. Same rules as add-attachment otherwise.`
53540
+ ),
53541
+ allowPasteAlert: external_exports.boolean().optional().describe(
53542
+ "Read the pasteboard even if macOS will show its paste alert (macOS 15.4+ paste privacy). Default false: when pasting is not already always allowed, the tool reads nothing and returns pasteboardCode pasteboard_access_denied. Set true only after the user agrees to answer the alert. Never overrides a Deny setting."
53543
+ )
53544
+ },
53545
+ ({ id: id2, expectedContentHash, filename, allowPasteAlert }) => {
53546
+ if (filename !== void 0) checkAttachmentFilename(filename);
53547
+ const before = readSnapshot(manager, id2);
53548
+ if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
53549
+ const frozen = freezePasteboard({
53550
+ pasteboardName: process.env[PASTEBOARD_NAME_ENV]?.trim() || void 0,
53551
+ allowPasteAlert: allowPasteAlert === true
53552
+ });
53553
+ try {
53554
+ const source = {
53555
+ kind: frozen.kind,
53556
+ type: frozen.type,
53557
+ filename: frozen.filename
53558
+ };
53559
+ return {
53560
+ ...attachFile(
53561
+ manager,
53562
+ {
53563
+ id: id2,
53564
+ expectedContentHash,
53565
+ path: frozen.path,
53566
+ filename: pasteboardFilename(filename, frozen.filename)
53567
+ },
53568
+ before
53569
+ ),
53570
+ source
53571
+ };
53572
+ } finally {
53573
+ frozen.cleanup();
53574
+ }
53575
+ }
53576
+ );
53238
53577
  }
53239
- function attachmentName(path10, filename) {
53240
- const source = basename5(path10);
53241
- if (filename === void 0) return source;
53578
+ function checkAttachmentFilename(filename) {
53242
53579
  if (Buffer.byteLength(filename, "utf8") > 255)
53243
53580
  throw new Error("filename must be at most 255 bytes");
53244
53581
  if (filename !== filename.trim() || filename === "" || filename.startsWith(".") || /[/:\\\p{Cc}]/u.test(filename))
53245
53582
  throw new Error(
53246
53583
  "filename must be one path component with no slash, colon, backslash, control character, leading dot, or surrounding spaces"
53247
53584
  );
53248
- if (extname6(filename).toLowerCase() !== extname6(source).toLowerCase())
53585
+ }
53586
+ function attachmentName(path10, filename) {
53587
+ const source = basename6(path10);
53588
+ if (filename === void 0) return source;
53589
+ checkAttachmentFilename(filename);
53590
+ if (extname7(filename).toLowerCase() !== extname7(source).toLowerCase())
53249
53591
  throw new Error(
53250
- `filename must keep the source file's extension (${extname6(source) || "none"})`
53592
+ `filename must keep the source file's extension (${extname7(source) || "none"})`
53251
53593
  );
53252
53594
  return filename;
53253
53595
  }
@@ -53283,36 +53625,25 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
53283
53625
  if (returnedId && /\/ICAttachment\/p\d+$/.test(returnedId) && returnedId !== attachmentId)
53284
53626
  throw new Error(UNCERTAIN);
53285
53627
  const expected = sha256(bytes);
53286
- const matches = row.assetPaths.some((path10) => {
53287
- let descriptor;
53288
- try {
53289
- descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
53290
- const stat = fstatSync6(descriptor);
53291
- return stat.isFile() && stat.size === bytes.length && sha256(readFileSync3(descriptor)) === expected;
53292
- } catch {
53293
- return false;
53294
- } finally {
53295
- if (descriptor !== void 0) closeSync6(descriptor);
53296
- }
53297
- });
53628
+ const matches = row.assetPaths.some((path10) => fileMatches(path10, bytes.length, expected));
53298
53629
  if (!matches)
53299
53630
  throw new Error(
53300
53631
  `Notes' database shows new attachment ${attachmentId} on this note, but its file bytes could not be verified; read the exact note and do not attach the file again`
53301
53632
  );
53302
53633
  return { attachmentId, name: row.filename };
53303
53634
  }
53304
- function attachFile(manager, args) {
53635
+ function attachFile(manager, args, checked) {
53305
53636
  const { id: id2, expectedContentHash, path: path10 } = args;
53306
53637
  const name = attachmentName(path10, args.filename);
53307
- const before = readSnapshot(manager, id2);
53638
+ const before = checked ?? readSnapshot(manager, id2);
53308
53639
  if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
53309
53640
  const bytes = localAttachment(path10);
53310
53641
  const beforeAttachments = manager.listAttachmentsById(id2);
53311
53642
  const beforeStored = storedAttachmentIds(manager, id2);
53312
- const directory = mkdtempSync5(join23(tmpdir5(), "notes-attachment-add-"));
53313
- const temporaryFile = join23(directory, name);
53643
+ const directory = mkdtempSync6(join24(tmpdir6(), "notes-attachment-add-"));
53644
+ const temporaryFile = join24(directory, name);
53314
53645
  try {
53315
- writeFileSync4(temporaryFile, bytes, { mode: 384 });
53646
+ writeFileSync5(temporaryFile, bytes, { mode: 384 });
53316
53647
  if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
53317
53648
  let returnedId;
53318
53649
  let transportUncertain = false;
@@ -53338,9 +53669,9 @@ function attachFile(manager, args) {
53338
53669
  if (inserted.length === 1 && !(persistentReturnedId && returnedId !== inserted[0].id)) {
53339
53670
  attachmentId = inserted[0].id;
53340
53671
  reportedName = inserted[0].name;
53341
- const fetched = manager.getAttachmentBase64ById(id2, attachmentId);
53342
- const actual = typeof fetched.base64 === "string" ? Buffer.from(fetched.base64, "base64") : null;
53343
- if (!actual || sha256(actual) !== sha256(bytes))
53672
+ const verifyPath = join24(directory, "verify", "attachment.bin");
53673
+ const saved = manager.saveAttachmentById(id2, attachmentId, verifyPath);
53674
+ if (!saved.success || !fileMatches(saved.savedPath ?? verifyPath, bytes.length, sha256(bytes)))
53344
53675
  throw new Error("Attachment bytes were not verified; read the exact note before retrying");
53345
53676
  } else {
53346
53677
  const stored = inserted.length === 0 && beforeStored ? storedInsertion(manager, id2, beforeStored, bytes, returnedId) : null;
@@ -53372,7 +53703,7 @@ function attachFile(manager, args) {
53372
53703
  } : {}
53373
53704
  };
53374
53705
  } finally {
53375
- rmSync5(directory, { recursive: true, force: true });
53706
+ rmSync6(directory, { recursive: true, force: true });
53376
53707
  }
53377
53708
  }
53378
53709
 
@@ -53380,7 +53711,7 @@ function attachFile(manager, args) {
53380
53711
  import { createHash as createHash4 } from "node:crypto";
53381
53712
 
53382
53713
  // src/utils/folderStore.ts
53383
- import { execFileSync as execFileSync19 } from "child_process";
53714
+ import { execFileSync as execFileSync20 } from "child_process";
53384
53715
  import * as os9 from "os";
53385
53716
  import * as path9 from "path";
53386
53717
  var NOTE_STORE_PATH = path9.join(
@@ -53444,7 +53775,7 @@ function runSqlite7(dbPath2, sql, pk) {
53444
53775
  const args = ["-readonly"];
53445
53776
  if (pk !== void 0) args.push("-cmd", `.parameter set @pk ${pk}`);
53446
53777
  args.push(dbPath2, sql);
53447
- return execFileSync19("sqlite3", args, {
53778
+ return execFileSync20("sqlite3", args, {
53448
53779
  encoding: "utf8",
53449
53780
  timeout: 5e3,
53450
53781
  stdio: ["pipe", "pipe", "pipe"]
@@ -53702,21 +54033,21 @@ function registerFolderDelete(server2, manager, deps = defaultDeps) {
53702
54033
  // src/services/templateStore.ts
53703
54034
  import { randomBytes } from "node:crypto";
53704
54035
  import {
53705
- closeSync as closeSync7,
53706
- constants as constants7,
53707
- fstatSync as fstatSync7,
54036
+ closeSync as closeSync8,
54037
+ constants as constants8,
54038
+ fstatSync as fstatSync8,
53708
54039
  linkSync,
53709
54040
  lstatSync as lstatSync5,
53710
54041
  mkdirSync as mkdirSync7,
53711
- openSync as openSync7,
54042
+ openSync as openSync8,
53712
54043
  readdirSync as readdirSync4,
53713
- readSync as readSync5,
54044
+ readSync as readSync6,
53714
54045
  renameSync,
53715
54046
  unlinkSync as unlinkSync3,
53716
54047
  writeSync as writeSync5
53717
54048
  } from "node:fs";
53718
54049
  import { homedir as homedir19 } from "node:os";
53719
- import { isAbsolute as isAbsolute5, join as join25, resolve as resolve5 } from "node:path";
54050
+ import { isAbsolute as isAbsolute5, join as join26, resolve as resolve5 } from "node:path";
53720
54051
  var TEMPLATE_SLUG = /^[a-z0-9][a-z0-9_-]{0,63}$/;
53721
54052
  var TemplateStoreError = class extends Error {
53722
54053
  constructor(code, message) {
@@ -53736,7 +54067,7 @@ function templateDir(env = process.env) {
53736
54067
  );
53737
54068
  return resolve5(override);
53738
54069
  }
53739
- return join25(homedir19(), "Library/Application Support/apple-notes-mcp/templates");
54070
+ return join26(homedir19(), "Library/Application Support/apple-notes-mcp/templates");
53740
54071
  }
53741
54072
  function checkName(name, action) {
53742
54073
  if (isBuiltinTemplate(name) && action !== "read")
@@ -53773,19 +54104,19 @@ var TemplateStore = class {
53773
54104
  return this.dir;
53774
54105
  }
53775
54106
  file(name) {
53776
- return join25(this.dir, `${name}.json`);
54107
+ return join26(this.dir, `${name}.json`);
53777
54108
  }
53778
54109
  /** Read a saved template's text, refusing symlinks and non-regular files. */
53779
54110
  readText(path10) {
53780
54111
  let fd;
53781
54112
  try {
53782
- fd = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
54113
+ fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW);
53783
54114
  } catch (error2) {
53784
54115
  if (error2.code === "ENOENT") return void 0;
53785
54116
  throw new TemplateStoreError("unsafe-path", `Refusing to read ${path10}: not a regular file.`);
53786
54117
  }
53787
54118
  try {
53788
- const stat = fstatSync7(fd);
54119
+ const stat = fstatSync8(fd);
53789
54120
  if (!stat.isFile())
53790
54121
  throw new TemplateStoreError(
53791
54122
  "unsafe-path",
@@ -53801,13 +54132,13 @@ var TemplateStore = class {
53801
54132
  const data = Buffer.alloc(stat.size);
53802
54133
  let read = 0;
53803
54134
  while (read < stat.size) {
53804
- const n = readSync5(fd, data, read, stat.size - read, read);
54135
+ const n = readSync6(fd, data, read, stat.size - read, read);
53805
54136
  if (n <= 0) break;
53806
54137
  read += n;
53807
54138
  }
53808
54139
  return { text: data.subarray(0, read).toString("utf8"), bytes: read, mtime: stat.mtime };
53809
54140
  } finally {
53810
- closeSync7(fd);
54141
+ closeSync8(fd);
53811
54142
  }
53812
54143
  }
53813
54144
  /** Saved templates in name order; `skipped` counts entries that are not usable. */
@@ -53887,10 +54218,10 @@ var TemplateStore = class {
53887
54218
  "template-exists",
53888
54219
  `A template named "${name}" already exists. Pass force: true to replace it.`
53889
54220
  );
53890
- const temp = join25(this.dir, `.${name}.${randomBytes(6).toString("hex")}.tmp`);
53891
- const fd = openSync7(
54221
+ const temp = join26(this.dir, `.${name}.${randomBytes(6).toString("hex")}.tmp`);
54222
+ const fd = openSync8(
53892
54223
  temp,
53893
- constants7.O_WRONLY | constants7.O_CREAT | constants7.O_EXCL | constants7.O_NOFOLLOW,
54224
+ constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
53894
54225
  384
53895
54226
  );
53896
54227
  try {
@@ -53898,7 +54229,7 @@ var TemplateStore = class {
53898
54229
  let written = 0;
53899
54230
  while (written < data.length) written += writeSync5(fd, data, written);
53900
54231
  } finally {
53901
- closeSync7(fd);
54232
+ closeSync8(fd);
53902
54233
  }
53903
54234
  try {
53904
54235
  if (force) renameSync(temp, path10);
@@ -54118,7 +54449,7 @@ function registerMarkdownTemplates(server2, store = () => new TemplateStore()) {
54118
54449
 
54119
54450
  // src/utils/svgAnalyzer.ts
54120
54451
  import { createHash as createHash5 } from "node:crypto";
54121
- import { closeSync as closeSync8, constants as constants8, fstatSync as fstatSync8, openSync as openSync8, readSync as readSync6 } from "node:fs";
54452
+ import { closeSync as closeSync9, constants as constants9, fstatSync as fstatSync9, openSync as openSync9, readSync as readSync7 } from "node:fs";
54122
54453
 
54123
54454
  // src/utils/svgColor.ts
54124
54455
  var NAMED = {
@@ -56087,14 +56418,14 @@ function analyzeSvgBuffer(source) {
56087
56418
  function readSvgSource(path10) {
56088
56419
  let fd;
56089
56420
  try {
56090
- fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
56421
+ fd = openSync9(path10, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
56091
56422
  } catch (error2) {
56092
56423
  if (error2.code === "ELOOP")
56093
56424
  throw new SvgError("svg_file_invalid", "The SVG path is a symbolic link");
56094
56425
  throw new SvgError("svg_file_invalid", "The SVG file does not exist or cannot be read");
56095
56426
  }
56096
56427
  try {
56097
- const info = fstatSync8(fd);
56428
+ const info = fstatSync9(fd);
56098
56429
  if (!info.isFile())
56099
56430
  throw new SvgError("svg_file_invalid", "The SVG path is not a regular file");
56100
56431
  if (info.size > SVG_LIMITS.maxSourceBytes)
@@ -56105,7 +56436,7 @@ function readSvgSource(path10) {
56105
56436
  const buffer = Buffer.alloc(SVG_LIMITS.maxSourceBytes + 1);
56106
56437
  let total = 0;
56107
56438
  for (; ; ) {
56108
- const n = readSync6(fd, buffer, total, buffer.length - total, null);
56439
+ const n = readSync7(fd, buffer, total, buffer.length - total, null);
56109
56440
  if (n === 0) break;
56110
56441
  total += n;
56111
56442
  if (total > SVG_LIMITS.maxSourceBytes)
@@ -56116,7 +56447,7 @@ function readSvgSource(path10) {
56116
56447
  }
56117
56448
  return buffer.subarray(0, total);
56118
56449
  } finally {
56119
- closeSync8(fd);
56450
+ closeSync9(fd);
56120
56451
  }
56121
56452
  }
56122
56453
  function analyzeSvgFile(path10) {
@@ -56356,7 +56687,9 @@ function appendChecklistItems(args, deps, readRich) {
56356
56687
  stoppedAt: {
56357
56688
  index,
56358
56689
  text: text2,
56359
- outcome: wrote ? "uncertain" : "not-written",
56690
+ // A bridge refusal or failure with an unchanged readback is
56691
+ // reported as not written even though verification ran (#248).
56692
+ outcome: wrote && !(error2 instanceof CodedError && error2.envelope.committed === false) ? "uncertain" : "not-written",
56360
56693
  error: error2 instanceof Error ? error2.message : String(error2)
56361
56694
  },
56362
56695
  notAttempted: args.items.slice(index + 1),
@@ -56779,14 +57112,14 @@ import {
56779
57112
  chmodSync,
56780
57113
  existsSync as existsSync14,
56781
57114
  mkdirSync as mkdirSync8,
56782
- mkdtempSync as mkdtempSync6,
56783
- readFileSync as readFileSync4,
57115
+ mkdtempSync as mkdtempSync7,
57116
+ readFileSync as readFileSync5,
56784
57117
  renameSync as renameSync2,
56785
- rmSync as rmSync6,
56786
- writeFileSync as writeFileSync5
57118
+ rmSync as rmSync7,
57119
+ writeFileSync as writeFileSync6
56787
57120
  } from "node:fs";
56788
57121
  import { homedir as homedir20, release as release3 } from "node:os";
56789
- import { dirname as dirname8, join as join26, resolve as resolve7 } from "node:path";
57122
+ import { dirname as dirname8, join as join27, resolve as resolve7 } from "node:path";
56790
57123
  import { fileURLToPath as fileURLToPath2 } from "node:url";
56791
57124
  var PUBLIC_HELPER_PROTOCOL = 1;
56792
57125
  var PUBLIC_HELPER_DIR_ENV = "APPLE_NOTES_MCP_PUBLIC_HELPER_DIR";
@@ -56814,10 +57147,10 @@ var publicManifestSchema = external_exports.object({
56814
57147
  function packageRoot(fromDir = dirname8(fileURLToPath2(import.meta.url))) {
56815
57148
  let dir = fromDir;
56816
57149
  for (; ; ) {
56817
- const candidate = join26(dir, "package.json");
57150
+ const candidate = join27(dir, "package.json");
56818
57151
  if (existsSync14(candidate)) {
56819
57152
  try {
56820
- if (JSON.parse(readFileSync4(candidate, "utf8")).name === "apple-notes-mcp")
57153
+ if (JSON.parse(readFileSync5(candidate, "utf8")).name === "apple-notes-mcp")
56821
57154
  return dir;
56822
57155
  } catch {
56823
57156
  }
@@ -56831,9 +57164,9 @@ function defaultPublicHelperDeps(overrides = {}) {
56831
57164
  return {
56832
57165
  env: process.env,
56833
57166
  platform: process.platform,
56834
- sourcePath: join26(packageRoot(), PUBLIC_HELPER_SOURCE),
57167
+ sourcePath: join27(packageRoot(), PUBLIC_HELPER_SOURCE),
56835
57168
  exists: existsSync14,
56836
- readFile: (path10) => readFileSync4(path10),
57169
+ readFile: (path10) => readFileSync5(path10),
56837
57170
  spawn: spawnSync3,
56838
57171
  spawnAsync: spawn,
56839
57172
  ...overrides
@@ -56842,14 +57175,14 @@ function defaultPublicHelperDeps(overrides = {}) {
56842
57175
  function publicHelperInstallDir(env = process.env) {
56843
57176
  const override = env[PUBLIC_HELPER_DIR_ENV]?.trim();
56844
57177
  if (override) return override;
56845
- return join26(homedir20(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
57178
+ return join27(homedir20(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
56846
57179
  }
56847
57180
  function sha256Hex(data) {
56848
57181
  return createHash6("sha256").update(data).digest("hex");
56849
57182
  }
56850
57183
  function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
56851
57184
  const installDir = publicHelperInstallDir(deps.env);
56852
- const binaryPath = join26(installDir, PUBLIC_HELPER_BINARY);
57185
+ const binaryPath = join27(installDir, PUBLIC_HELPER_BINARY);
56853
57186
  const base = { installDir, binaryPath, sourcePath: deps.sourcePath, manifest: null };
56854
57187
  const fail = (reason, detail) => ({
56855
57188
  ...base,
@@ -56860,7 +57193,7 @@ function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
56860
57193
  if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
56861
57194
  if (!deps.exists(deps.sourcePath))
56862
57195
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
56863
- const manifestPath = join26(installDir, PUBLIC_HELPER_MANIFEST);
57196
+ const manifestPath = join27(installDir, PUBLIC_HELPER_MANIFEST);
56864
57197
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
56865
57198
  return fail(
56866
57199
  "helper_not_installed",
@@ -56900,14 +57233,14 @@ var UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
56900
57233
  "helper_modified",
56901
57234
  "helper_manifest_invalid"
56902
57235
  ]);
56903
- var ENVELOPE_CODES = {
57236
+ var ENVELOPE_CODES2 = {
56904
57237
  invalid_request: "validation_error",
56905
57238
  attachment_not_found: "not_found"
56906
57239
  };
56907
57240
  var PublicHelperError = class extends CodedError {
56908
57241
  constructor(code, message) {
56909
57242
  super(message, {
56910
- code: UNAVAILABLE_CODES.has(code) ? "unsupported" : ENVELOPE_CODES[code] ?? "operation_failed",
57243
+ code: UNAVAILABLE_CODES.has(code) ? "unsupported" : ENVELOPE_CODES2[code] ?? "operation_failed",
56911
57244
  helperCode: code
56912
57245
  });
56913
57246
  this.code = code;
@@ -57137,13 +57470,13 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
57137
57470
  steps.push({ step: "find compiler", ok: true, detail: compiler });
57138
57471
  const installDir = publicHelperInstallDir(deps.env);
57139
57472
  mkdirSync8(installDir, { recursive: true, mode: 448 });
57140
- const staging = mkdtempSync6(join26(installDir, ".staging-"));
57473
+ const staging = mkdtempSync7(join27(installDir, ".staging-"));
57141
57474
  try {
57142
- const stagedBinary = join26(staging, PUBLIC_HELPER_BINARY);
57143
- const digestPath = join26(staging, "source-digest.swift");
57144
- const plistPath = join26(staging, "Info.plist");
57145
- writeFileSync5(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
57146
- writeFileSync5(plistPath, publicHelperInfoPlist(), { mode: 384 });
57475
+ const stagedBinary = join27(staging, PUBLIC_HELPER_BINARY);
57476
+ const digestPath = join27(staging, "source-digest.swift");
57477
+ const plistPath = join27(staging, "Info.plist");
57478
+ writeFileSync6(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
57479
+ writeFileSync6(plistPath, publicHelperInfoPlist(), { mode: 384 });
57147
57480
  const compile = deps.spawn(
57148
57481
  "/usr/bin/xcrun",
57149
57482
  publicHelperCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
@@ -57204,15 +57537,15 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
57204
57537
  compiler
57205
57538
  };
57206
57539
  chmodSync(stagedBinary, 448);
57207
- renameSync2(stagedBinary, join26(installDir, PUBLIC_HELPER_BINARY));
57208
- writeFileSync5(
57209
- join26(installDir, PUBLIC_HELPER_MANIFEST),
57540
+ renameSync2(stagedBinary, join27(installDir, PUBLIC_HELPER_BINARY));
57541
+ writeFileSync6(
57542
+ join27(installDir, PUBLIC_HELPER_MANIFEST),
57210
57543
  JSON.stringify(manifest, null, 2) + "\n",
57211
57544
  { mode: 384 }
57212
57545
  );
57213
57546
  steps.push({ step: "install", ok: true, detail: installDir });
57214
57547
  } finally {
57215
- rmSync6(staging, { recursive: true, force: true });
57548
+ rmSync7(staging, { recursive: true, force: true });
57216
57549
  }
57217
57550
  const installation = inspectPublicHelper(deps);
57218
57551
  steps.push({
@@ -57450,8 +57783,8 @@ function formatNoteDrawings(result) {
57450
57783
  }
57451
57784
 
57452
57785
  // src/utils/noteAudio.ts
57453
- import { readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
57454
- import { join as join27 } from "node:path";
57786
+ import { readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
57787
+ import { join as join28 } from "node:path";
57455
57788
  var EXTRA_AUDIO_UTIS = [
57456
57789
  "public.mp3",
57457
57790
  "public.aiff-audio",
@@ -57476,7 +57809,7 @@ function accountDirsFor(containerDir, accountIdentifier) {
57476
57809
  if (own) return [own];
57477
57810
  let names;
57478
57811
  try {
57479
- names = readdirSync5(join27(containerDir, "Accounts")).sort();
57812
+ names = readdirSync5(join28(containerDir, "Accounts")).sort();
57480
57813
  } catch {
57481
57814
  return [];
57482
57815
  }
@@ -57488,8 +57821,8 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
57488
57821
  if (!id2 || !filename) return null;
57489
57822
  const generation = safeComponent(media?.generation);
57490
57823
  for (const dir of accountDirsFor(containerDir, accountIdentifier)) {
57491
- const base = join27(dir, "Media", id2);
57492
- const candidates = generation ? [join27(base, generation, filename), join27(base, filename)] : [join27(base, filename)];
57824
+ const base = join28(dir, "Media", id2);
57825
+ const candidates = generation ? [join28(base, generation, filename), join28(base, filename)] : [join28(base, filename)];
57493
57826
  for (const candidate of candidates) {
57494
57827
  const real = realInside(candidate, dir);
57495
57828
  if (real && isFile2(real)) return real;
@@ -57499,7 +57832,7 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
57499
57832
  }
57500
57833
  function isFile2(path10) {
57501
57834
  try {
57502
- return statSync4(path10).isFile();
57835
+ return statSync5(path10).isFile();
57503
57836
  } catch {
57504
57837
  return false;
57505
57838
  }
@@ -57762,20 +58095,20 @@ import {
57762
58095
  chmodSync as chmodSync2,
57763
58096
  existsSync as existsSync16,
57764
58097
  mkdirSync as mkdirSync9,
57765
- mkdtempSync as mkdtempSync7,
58098
+ mkdtempSync as mkdtempSync8,
57766
58099
  renameSync as renameSync3,
57767
- rmSync as rmSync7,
57768
- writeFileSync as writeFileSync6
58100
+ rmSync as rmSync8,
58101
+ writeFileSync as writeFileSync7
57769
58102
  } from "node:fs";
57770
58103
  import { release as release4 } from "node:os";
57771
- import { join as join29 } from "node:path";
58104
+ import { join as join30 } from "node:path";
57772
58105
 
57773
58106
  // src/services/privateHelper.ts
57774
58107
  import { spawnSync as spawnSync4 } from "node:child_process";
57775
58108
  import { createHash as createHash7 } from "node:crypto";
57776
- import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
58109
+ import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
57777
58110
  import { homedir as homedir21 } from "node:os";
57778
- import { dirname as dirname9, join as join28, resolve as resolve8 } from "node:path";
58111
+ import { dirname as dirname9, join as join29, resolve as resolve8 } from "node:path";
57779
58112
  import { fileURLToPath as fileURLToPath3 } from "node:url";
57780
58113
  var PRIVATE_HELPER_PROTOCOL = 1;
57781
58114
  var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
@@ -57803,10 +58136,10 @@ var manifestSchema = external_exports.object({
57803
58136
  function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
57804
58137
  let dir = fromDir;
57805
58138
  for (; ; ) {
57806
- const candidate = join28(dir, "package.json");
58139
+ const candidate = join29(dir, "package.json");
57807
58140
  if (existsSync15(candidate)) {
57808
58141
  try {
57809
- const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
58142
+ const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
57810
58143
  if (pkg.name === "apple-notes-mcp") return dir;
57811
58144
  } catch {
57812
58145
  }
@@ -57820,9 +58153,9 @@ function defaultDeps2(overrides = {}) {
57820
58153
  return {
57821
58154
  env: process.env,
57822
58155
  platform: process.platform,
57823
- sourcePath: join28(packageRoot2(), HELPER_SOURCE_RELATIVE),
58156
+ sourcePath: join29(packageRoot2(), HELPER_SOURCE_RELATIVE),
57824
58157
  exists: existsSync15,
57825
- readFile: (path10) => readFileSync5(path10),
58158
+ readFile: (path10) => readFileSync6(path10),
57826
58159
  spawn: spawnSync4,
57827
58160
  ...overrides
57828
58161
  };
@@ -57833,14 +58166,14 @@ function privateHelperEnabled(env = process.env) {
57833
58166
  function helperInstallDir(env = process.env) {
57834
58167
  const override = env[HELPER_DIR_ENV]?.trim();
57835
58168
  if (override) return override;
57836
- return join28(homedir21(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
58169
+ return join29(homedir21(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
57837
58170
  }
57838
58171
  function sha256Hex2(data) {
57839
58172
  return createHash7("sha256").update(data).digest("hex");
57840
58173
  }
57841
58174
  function inspectInstallation(deps = defaultDeps2()) {
57842
58175
  const installDir = helperInstallDir(deps.env);
57843
- const binaryPath = join28(installDir, HELPER_BINARY_NAME);
58176
+ const binaryPath = join29(installDir, HELPER_BINARY_NAME);
57844
58177
  const base = {
57845
58178
  installDir,
57846
58179
  binaryPath,
@@ -57858,7 +58191,7 @@ function inspectInstallation(deps = defaultDeps2()) {
57858
58191
  if (!deps.exists(deps.sourcePath))
57859
58192
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
57860
58193
  base.expectedSourceSha256 = sha256Hex2(deps.readFile(deps.sourcePath));
57861
- const manifestPath = join28(installDir, MANIFEST_NAME);
58194
+ const manifestPath = join29(installDir, MANIFEST_NAME);
57862
58195
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
57863
58196
  return fail(
57864
58197
  "helper_not_installed",
@@ -58173,9 +58506,9 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
58173
58506
  steps.push({ step: "find compiler", ok: true, detail: compiler });
58174
58507
  const installDir = helperInstallDir(deps.env);
58175
58508
  mkdirSync9(installDir, { recursive: true, mode: 448 });
58176
- const staging = mkdtempSync7(join29(installDir, ".staging-"));
58509
+ const staging = mkdtempSync8(join30(installDir, ".staging-"));
58177
58510
  try {
58178
- const stagedBinary = join29(staging, HELPER_BINARY_NAME);
58511
+ const stagedBinary = join30(staging, HELPER_BINARY_NAME);
58179
58512
  const compile = deps.spawn(
58180
58513
  "/usr/bin/xcrun",
58181
58514
  compileArguments(deps.sourcePath, stagedBinary, sourceSha),
@@ -58252,13 +58585,13 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
58252
58585
  compiler
58253
58586
  };
58254
58587
  chmodSync2(stagedBinary, 448);
58255
- renameSync3(stagedBinary, join29(installDir, HELPER_BINARY_NAME));
58256
- writeFileSync6(join29(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
58588
+ renameSync3(stagedBinary, join30(installDir, HELPER_BINARY_NAME));
58589
+ writeFileSync7(join30(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
58257
58590
  mode: 384
58258
58591
  });
58259
58592
  steps.push({ step: "install", ok: true, detail: installDir });
58260
58593
  } finally {
58261
- if (existsSync16(staging)) rmSync7(staging, { recursive: true, force: true });
58594
+ if (existsSync16(staging)) rmSync8(staging, { recursive: true, force: true });
58262
58595
  }
58263
58596
  const installation = inspectInstallation(deps);
58264
58597
  steps.push({
@@ -60304,7 +60637,7 @@ ${noteList}`, {
60304
60637
  registerTool(
60305
60638
  "list-folders",
60306
60639
  {
60307
- description: "Use when: listing all folders, with full nested paths, for an account.\nReturns: folder names/paths.\nDo not use when: listing notes (list-notes).\nNote: warns if iCloud sync is active.",
60640
+ description: "Use when: listing all folders, with full nested paths, for an account.\nReturns: folder names/paths.\nDo not use when: listing notes (list-notes).\nNote: smart folders are listed too (AppleScript lists them like ordinary folders) and are marked smartFolder: true when Full Disk Access lets the server read the Notes database; they cannot hold notes. Warns if iCloud sync is active.",
60308
60641
  inputSchema: {
60309
60642
  account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account to list folders from")
60310
60643
  },
@@ -60334,7 +60667,7 @@ ${syncWarnings.join(" ")}` : "";
60334
60667
  return successResponse(`No folders found${acct}${syncNote}`, { folders: [], count: 0 });
60335
60668
  }
60336
60669
  const resolvedAcct = folders[0]?.account ? ` (${folders[0].account})` : acct;
60337
- const folderList = folders.map((f) => ` - ${f.name}`).join("\n");
60670
+ const folderList = folders.map((f) => ` - ${f.name}${f.smartFolder ? " (smart folder)" : ""}`).join("\n");
60338
60671
  return successResponse(
60339
60672
  `Found ${folders.length} folders${resolvedAcct}:
60340
60673
  ${folderList}${syncNote}`,
@@ -60350,7 +60683,7 @@ registerTool(
60350
60683
  {
60351
60684
  description: `Use when: listing Smart Folders and the rules that define them.
60352
60685
  Returns: each smart folder's name, ids, account, and parent, its rules decoded as match ("all"/"any"/"none") plus filters (each with a readable description), the stored query with the outer deleted wrapper removed, and the raw stored query JSON. With includeMatchingNotes, also the notes Notes.app currently shows in each folder.
60353
- Do not use when: listing ordinary folders (list-folders) or searching notes (search-notes).
60686
+ Do not use when: listing all folders with paths (list-folders, which lists smart folders alongside ordinary ones) or searching notes (search-notes).
60354
60687
  Safety: read-only; reads the NoteStore database and requires Full Disk Access. includeMatchingNotes asks Notes.app (Automation permission) for each folder's current contents rather than re-evaluating the rules. Unrecognized rules are kept as "unknown" filters and set fullyDecoded false.`,
60355
60688
  inputSchema: {
60356
60689
  includeMatchingNotes: external_exports.boolean().optional().describe(