apple-notes-mcp 2.9.16 → 2.9.17

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 +66 -0
  2. package/build/index.js +400 -123
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2072,6 +2072,72 @@ calling this tool again, which would create a second note.
2072
2072
 
2073
2073
  ---
2074
2074
 
2075
+ #### `add-attachment-from-pasteboard`
2076
+
2077
+ Attaches whatever image, PDF, or file is on the pasteboard (a screenshot, "Copy
2078
+ Image", or a file copied in Finder) to an exact note. The pasteboard is read once
2079
+ through AppKit's public `NSPasteboard` API (via JXA, no native build) and its
2080
+ bytes are frozen into a private temporary file before anything touches the note.
2081
+ The pasteboard itself is never written. The note and `expectedContentHash` are
2082
+ checked before the pasteboard is read, so a request with a wrong id, a stale
2083
+ revision, or a malformed `filename` never captures your clipboard. The frozen
2084
+ file then goes through [`add-attachment`](#add-attachment)'s checks.
2085
+
2086
+ | Parameter | Type | Required | Description |
2087
+ |-----------|------|----------|-------------|
2088
+ | `id` | string | Yes | Note ID |
2089
+ | `expectedContentHash` | string | Yes | The note's current `contentHash` (from `get-note-content`) |
2090
+ | `filename` | string | No | Attachment name. Defaults to the copied file's name, or `Pasted image.png` / `Pasted document.pdf` for image or PDF data. Without an extension, the pasted type's extension is added; with one, it must match the pasted type. Otherwise the same rules as `add-attachment` |
2091
+ | `allowPasteAlert` | boolean | No | Default `false`. Read the pasteboard even when macOS will show its paste alert (see **Paste privacy** below). Never overrides a Deny setting |
2092
+
2093
+ **Returns:** the `add-attachment` result plus `source` (`kind`: `file` or
2094
+ `data`, the pasteboard `type`, and the default `filename`). A copied file wins
2095
+ over image data; among data types PNG is preferred, then JPEG, HEIC, GIF, TIFF,
2096
+ and PDF.
2097
+
2098
+ **Paste privacy (macOS 15.4 and later):** macOS can show an alert asking
2099
+ whether to allow a paste when a process reads the general pasteboard without a
2100
+ user paste. Before reading anything, the tool checks AppKit's
2101
+ [`NSPasteboard.accessBehavior`](https://developer.apple.com/documentation/appkit/nspasteboard/accessbehavior-swift.enum).
2102
+ It reads only when the value is `alwaysAllow`, or when you pass
2103
+ `allowPasteAlert: true` and the value is `default` or `ask` (macOS then shows
2104
+ its alert, and the call waits for your answer up to the automation timeout).
2105
+ Otherwise it reads nothing and returns `code: "permission_denied"` with
2106
+ `pasteboardCode: "pasteboard_access_denied"` and `accessBehavior` (`default`,
2107
+ `ask`, `alwaysDeny`, or `unknown`). `alwaysDeny` is always refused; change it in
2108
+ System Settings, where macOS lists an app after its first paste alert. On macOS
2109
+ before 15.4 the property does not exist and the pasteboard is read as before.
2110
+
2111
+ **Errors** carry a `pasteboardCode` next to the shared `code`, and
2112
+ `committed: false` because nothing was written:
2113
+
2114
+ | `pasteboardCode` | `code` | Cause |
2115
+ |------------------|--------|-------|
2116
+ | `pasteboard_access_denied` | `permission_denied` | Reading would show the macOS paste alert, or pasting is denied (see above) |
2117
+ | `pasteboard_empty` | `validation_error` | Nothing is on the pasteboard |
2118
+ | `unsupported_content` | `validation_error` | No PNG, JPEG, HEIC, GIF, TIFF, PDF, or copied file (text belongs in `append-to-note`); the message and `types` list the pasteboard types found |
2119
+ | `multiple_files` | `validation_error` | More than one copied file; `count` says how many. Copy one file, or use `add-attachment` per file |
2120
+ | `too_large` | `validation_error` | More than 64 MiB |
2121
+ | `file_unreadable` | `validation_error` | The copied file is a symlink, empty, over 64 MiB, or unreadable |
2122
+ | `pasteboard_changed` | `operation_failed` | The pasteboard changed while it was being read; try again |
2123
+ | `pasteboard_timeout` | `operation_failed` | The read timed out (for example an unanswered paste alert) |
2124
+ | `pasteboard_unavailable` | `operation_failed` | The MCP host is not running in your logged-in GUI session (for example over SSH) |
2125
+ | `write_failed` | `operation_failed` | The temporary copy could not be written |
2126
+
2127
+ The pasteboard read uses the same timeout as other automation steps
2128
+ (`APPLE_NOTES_MCP_TIMEOUT_MS`, 30 seconds by default).
2129
+
2130
+ **Known limitation ([#236](https://github.com/sweetrb/apple-notes-mcp/issues/236)):** on macOS 27, Notes' AppleScript dictionary does not list
2131
+ PDF attachments, so a PDF (pasted or from `add-attachment`) is inserted but
2132
+ cannot be verified and the call reports "insertion outcome uncertain". Read the
2133
+ note before retrying.
2134
+
2135
+ For testing, `APPLE_NOTES_MCP_PASTEBOARD_NAME` makes the tool read a private
2136
+ named pasteboard instead of the general one, so a test never touches your
2137
+ clipboard.
2138
+
2139
+ ---
2140
+
2075
2141
  #### `list-attachments`
2076
2142
 
2077
2143
  Lists attachments in a note.
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);
@@ -51666,16 +51666,16 @@ var NOTE_PLACEHOLDERS = [
51666
51666
  "exportStem"
51667
51667
  ];
51668
51668
  var PLACEHOLDER_MODIFIERS = ["raw", "yaml"];
51669
- var wrap2 = (before, after = "", join30) => ({
51669
+ var wrap2 = (before, after = "", join31) => ({
51670
51670
  mode: "wrap",
51671
51671
  before,
51672
51672
  after,
51673
- ...join30 ? { join: join30 } : {}
51673
+ ...join31 ? { join: join31 } : {}
51674
51674
  });
51675
- var pattern = (value, join30) => ({
51675
+ var pattern = (value, join31) => ({
51676
51676
  mode: "pattern",
51677
51677
  value,
51678
- ...join30 ? { join: join30 } : {}
51678
+ ...join31 ? { join: join31 } : {}
51679
51679
  });
51680
51680
  var STANDARD = {
51681
51681
  schemaVersion: 1,
@@ -53056,6 +53056,21 @@ function exportWithTemplate(request, deps, chosen, notes, skipped, { output, ass
53056
53056
 
53057
53057
  // src/tools/directOperations.ts
53058
53058
  import { createHash as createHash3 } from "node:crypto";
53059
+ import {
53060
+ closeSync as closeSync7,
53061
+ constants as constants7,
53062
+ fstatSync as fstatSync7,
53063
+ mkdtempSync as mkdtempSync6,
53064
+ openSync as openSync7,
53065
+ readFileSync as readFileSync4,
53066
+ rmSync as rmSync6,
53067
+ writeFileSync as writeFileSync5
53068
+ } from "node:fs";
53069
+ import { tmpdir as tmpdir6 } from "node:os";
53070
+ import { basename as basename6, extname as extname7, isAbsolute as isAbsolute4, join as join24 } from "node:path";
53071
+
53072
+ // src/utils/pasteboardFreeze.ts
53073
+ import { execFileSync as execFileSync19 } from "node:child_process";
53059
53074
  import {
53060
53075
  closeSync as closeSync6,
53061
53076
  constants as constants6,
@@ -53064,10 +53079,224 @@ import {
53064
53079
  openSync as openSync6,
53065
53080
  readFileSync as readFileSync3,
53066
53081
  rmSync as rmSync5,
53082
+ statSync as statSync4,
53067
53083
  writeFileSync as writeFileSync4
53068
53084
  } from "node:fs";
53069
53085
  import { tmpdir as tmpdir5 } from "node:os";
53070
- import { basename as basename5, extname as extname6, isAbsolute as isAbsolute4, join as join23 } from "node:path";
53086
+ import { basename as basename5, extname as extname6, join as join23 } from "node:path";
53087
+ var PASTEBOARD_NAME_ENV = "APPLE_NOTES_MCP_PASTEBOARD_NAME";
53088
+ var MAX_PASTEBOARD_BYTES = 64 * 1024 * 1024;
53089
+ var PASTEBOARD_DATA_TYPES = [
53090
+ { type: "public.png", ext: "png", label: "image" },
53091
+ { type: "public.jpeg", ext: "jpg", label: "image" },
53092
+ { type: "public.heic", ext: "heic", label: "image" },
53093
+ { type: "com.compuserve.gif", ext: "gif", label: "image" },
53094
+ { type: "public.tiff", ext: "tiff", label: "image" },
53095
+ { type: "com.adobe.pdf", ext: "pdf", label: "document" }
53096
+ ];
53097
+ var PASTEBOARD_ACCESS_BEHAVIORS = {
53098
+ 0: "default",
53099
+ 1: "ask",
53100
+ 2: "alwaysAllow",
53101
+ 3: "alwaysDeny"
53102
+ };
53103
+ var PASTEBOARD_FREEZE_JXA = `
53104
+ ObjC.import("AppKit");
53105
+ function run(argv) {
53106
+ var dir = argv[0], name = argv[1], limit = Number(argv[2]), prefs = JSON.parse(argv[3]);
53107
+ var allowAlert = argv[4] === "1";
53108
+ var pb = name ? $.NSPasteboard.pasteboardWithName(name) : $.NSPasteboard.generalPasteboard;
53109
+ if (!pb || pb.isNil()) return JSON.stringify({ status: "error", code: "pasteboard_unavailable" });
53110
+ if (!name && pb.respondsToSelector("accessBehavior")) {
53111
+ var behavior = Number(pb.accessBehavior);
53112
+ if (behavior === 3 || (behavior !== 2 && !allowAlert))
53113
+ return JSON.stringify({ status: "error", code: "pasteboard_access_denied", accessBehavior: behavior });
53114
+ }
53115
+ var before = pb.changeCount;
53116
+ var types = ObjC.deepUnwrap(pb.types) || [];
53117
+ if (types.length === 0) return JSON.stringify({ status: "error", code: "pasteboard_empty" });
53118
+ var result = null;
53119
+ var items = pb.pasteboardItems;
53120
+ var itemCount = items && !items.isNil() ? Number(items.count) : 0;
53121
+ var fileUrls = [];
53122
+ for (var k = 0; k < itemCount; k++) {
53123
+ var item = items.objectAtIndex(k);
53124
+ var itemTypes = ObjC.deepUnwrap(item.types) || [];
53125
+ if (itemTypes.indexOf("public.file-url") < 0) continue;
53126
+ var value = ObjC.unwrap(item.stringForType("public.file-url"));
53127
+ if (value) fileUrls.push(value);
53128
+ }
53129
+ if (fileUrls.length > 1)
53130
+ return JSON.stringify({ status: "error", code: "multiple_files", count: fileUrls.length });
53131
+ if (fileUrls.length === 1) {
53132
+ var url = $.NSURL.URLWithString(fileUrls[0]);
53133
+ if (url && !url.isNil() && url.isFileURL) {
53134
+ result = { status: "ok", kind: "file", type: "public.file-url", path: ObjC.unwrap(url.path) };
53135
+ }
53136
+ }
53137
+ if (!result) {
53138
+ for (var i = 0; i < prefs.length && !result; i++) {
53139
+ if (types.indexOf(prefs[i][0]) < 0) continue;
53140
+ var data = pb.dataForType(prefs[i][0]);
53141
+ if (!data || data.isNil() || data.length === 0) continue;
53142
+ if (data.length > limit) return JSON.stringify({ status: "error", code: "too_large", bytes: data.length });
53143
+ var path = dir + "/pasteboard." + prefs[i][1];
53144
+ if (!data.writeToFileAtomically(path, true))
53145
+ return JSON.stringify({ status: "error", code: "write_failed" });
53146
+ result = { status: "ok", kind: "data", type: prefs[i][0], path: path, bytes: data.length };
53147
+ }
53148
+ }
53149
+ if (pb.changeCount !== before) return JSON.stringify({ status: "error", code: "pasteboard_changed" });
53150
+ return JSON.stringify(result || { status: "error", code: "unsupported_content", types: types.slice(0, 20) });
53151
+ }
53152
+ `;
53153
+ var ENVELOPE_CODES = {
53154
+ pasteboard_access_denied: "permission_denied",
53155
+ pasteboard_unavailable: "operation_failed",
53156
+ pasteboard_timeout: "operation_failed",
53157
+ pasteboard_empty: "validation_error",
53158
+ pasteboard_changed: "operation_failed",
53159
+ unsupported_content: "validation_error",
53160
+ multiple_files: "validation_error",
53161
+ too_large: "validation_error",
53162
+ write_failed: "operation_failed",
53163
+ file_unreadable: "validation_error"
53164
+ };
53165
+ var PasteboardError = class extends CodedError {
53166
+ constructor(code, message, details = {}) {
53167
+ super(message, {
53168
+ ...details,
53169
+ code: ENVELOPE_CODES[code],
53170
+ pasteboardCode: code,
53171
+ committed: false
53172
+ });
53173
+ this.code = code;
53174
+ this.name = "PasteboardError";
53175
+ }
53176
+ code;
53177
+ };
53178
+ var SUPPORTED = "an image (PNG, JPEG, HEIC, GIF, TIFF), a PDF, or one copied file";
53179
+ var MESSAGES = {
53180
+ 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.",
53181
+ 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).",
53182
+ pasteboard_timeout: "Reading the pasteboard timed out; nothing was attached. If macOS showed a paste alert, answer it and try again.",
53183
+ pasteboard_empty: "The pasteboard is empty. Copy an image or a file first.",
53184
+ pasteboard_changed: "The pasteboard changed while it was being read. Try again.",
53185
+ unsupported_content: `The pasteboard holds no supported content: copy ${SUPPORTED}. Text belongs in append-to-note, not in an attachment.`,
53186
+ multiple_files: "The pasteboard holds more than one copied file. Copy exactly one file, or attach each file with add-attachment.",
53187
+ too_large: "The pasteboard contents exceed the 64 MiB attachment limit.",
53188
+ write_failed: "Could not write the pasteboard contents to a temporary file.",
53189
+ file_unreadable: "The copied file could not be read as a regular file of at most 64 MiB."
53190
+ };
53191
+ function replyError(code, reply) {
53192
+ if (code === "pasteboard_access_denied") {
53193
+ const raw = typeof reply.accessBehavior === "number" ? reply.accessBehavior : void 0;
53194
+ const accessBehavior = raw !== void 0 && PASTEBOARD_ACCESS_BEHAVIORS[raw] || "unknown";
53195
+ 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;
53196
+ return new PasteboardError(code, message, { accessBehavior });
53197
+ }
53198
+ if (code === "multiple_files") {
53199
+ const count = typeof reply.count === "number" ? reply.count : void 0;
53200
+ return new PasteboardError(
53201
+ code,
53202
+ count ? `The pasteboard holds ${count} copied files. Copy exactly one file, or attach each file with add-attachment.` : MESSAGES[code],
53203
+ count ? { count } : {}
53204
+ );
53205
+ }
53206
+ if (code === "unsupported_content" && Array.isArray(reply.types)) {
53207
+ const types = reply.types.filter((t) => typeof t === "string").slice(0, 20);
53208
+ if (types.length)
53209
+ return new PasteboardError(
53210
+ code,
53211
+ `${MESSAGES[code]} Pasteboard types found: ${types.join(", ")}.`,
53212
+ { types }
53213
+ );
53214
+ }
53215
+ if (code === "too_large" && typeof reply.bytes === "number")
53216
+ return new PasteboardError(code, MESSAGES[code], { bytes: reply.bytes });
53217
+ return new PasteboardError(code, MESSAGES[code]);
53218
+ }
53219
+ function pasteboardTimeoutMs() {
53220
+ return callTimeoutMs() ?? envPositiveNumber("APPLE_NOTES_MCP_TIMEOUT_MS") ?? 3e4;
53221
+ }
53222
+ function defaultRunJxa(args) {
53223
+ return execFileSync19("osascript", ["-l", "JavaScript", "-e", PASTEBOARD_FREEZE_JXA, ...args], {
53224
+ encoding: "utf8",
53225
+ timeout: pasteboardTimeoutMs(),
53226
+ maxBuffer: 1024 * 1024,
53227
+ stdio: ["pipe", "pipe", "pipe"]
53228
+ });
53229
+ }
53230
+ function pasteboardFilename(requested, frozenFilename) {
53231
+ if (requested === void 0) return void 0;
53232
+ const ext = extname6(frozenFilename);
53233
+ return ext && !extname6(requested) ? requested + ext : requested;
53234
+ }
53235
+ function readRegularFile(path10) {
53236
+ let descriptor;
53237
+ try {
53238
+ descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
53239
+ } catch {
53240
+ throw new PasteboardError("file_unreadable", MESSAGES.file_unreadable);
53241
+ }
53242
+ try {
53243
+ const stat = fstatSync6(descriptor);
53244
+ if (!stat.isFile() || stat.size === 0 || stat.size > MAX_PASTEBOARD_BYTES)
53245
+ throw new PasteboardError("file_unreadable", MESSAGES.file_unreadable);
53246
+ return readFileSync3(descriptor);
53247
+ } finally {
53248
+ closeSync6(descriptor);
53249
+ }
53250
+ }
53251
+ function freezePasteboard(options = {}) {
53252
+ const directory = mkdtempSync5(join23(tmpdir5(), "notes-pasteboard-"));
53253
+ const cleanup = () => rmSync5(directory, { recursive: true, force: true });
53254
+ try {
53255
+ const prefs = JSON.stringify(PASTEBOARD_DATA_TYPES.map((t) => [t.type, t.ext]));
53256
+ let raw;
53257
+ try {
53258
+ raw = (options.runJxa ?? defaultRunJxa)([
53259
+ directory,
53260
+ options.pasteboardName ?? "",
53261
+ String(MAX_PASTEBOARD_BYTES),
53262
+ prefs,
53263
+ options.allowPasteAlert ? "1" : "0"
53264
+ ]);
53265
+ } catch (error2) {
53266
+ throw error2?.code === "ETIMEDOUT" ? new PasteboardError("pasteboard_timeout", MESSAGES.pasteboard_timeout) : new PasteboardError("pasteboard_unavailable", MESSAGES.pasteboard_unavailable);
53267
+ }
53268
+ let reply;
53269
+ try {
53270
+ reply = JSON.parse(raw.trim());
53271
+ } catch {
53272
+ throw new PasteboardError("pasteboard_unavailable", MESSAGES.pasteboard_unavailable);
53273
+ }
53274
+ if (reply.status !== "ok" || !reply.path || !reply.type) {
53275
+ const code = reply.code && reply.code in MESSAGES ? reply.code : "pasteboard_unavailable";
53276
+ throw replyError(code, reply);
53277
+ }
53278
+ if (reply.kind === "file") {
53279
+ const bytes = readRegularFile(reply.path);
53280
+ const filename2 = basename5(reply.path);
53281
+ const path11 = join23(directory, filename2);
53282
+ writeFileSync4(path11, bytes, { mode: 384 });
53283
+ return { kind: "file", type: reply.type, path: path11, filename: filename2, bytes: bytes.length, cleanup };
53284
+ }
53285
+ if (join23(directory, basename5(reply.path)) !== reply.path)
53286
+ throw new PasteboardError("write_failed", MESSAGES.write_failed);
53287
+ const label = PASTEBOARD_DATA_TYPES.find((t) => t.type === reply.type)?.label ?? "item";
53288
+ const filename = `Pasted ${label}${extname6(reply.path)}`;
53289
+ const path10 = join23(directory, filename);
53290
+ writeFileSync4(path10, readFileSync3(reply.path), { mode: 384 });
53291
+ rmSync5(reply.path, { force: true });
53292
+ return { kind: "data", type: reply.type, path: path10, filename, bytes: statSync4(path10).size, cleanup };
53293
+ } catch (error2) {
53294
+ cleanup();
53295
+ throw error2;
53296
+ }
53297
+ }
53298
+
53299
+ // src/tools/directOperations.ts
53071
53300
  var noteId = exactIdInput(
53072
53301
  "ICNote",
53073
53302
  /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i,
@@ -53112,17 +53341,17 @@ function assertExistingContentPreserved(before, after) {
53112
53341
  }
53113
53342
  function localAttachment(path10) {
53114
53343
  if (!isAbsolute4(path10)) throw new Error("An absolute local file path is required");
53115
- const descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
53344
+ const descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
53116
53345
  try {
53117
- const stat = fstatSync6(descriptor);
53346
+ const stat = fstatSync7(descriptor);
53118
53347
  if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
53119
53348
  throw new Error("Attachment must be a nonempty regular file of at most 64 MiB");
53120
- const bytes = readFileSync3(descriptor);
53349
+ const bytes = readFileSync4(descriptor);
53121
53350
  if (bytes.length !== stat.size)
53122
53351
  throw new Error("Attachment changed while it was being read; try again");
53123
53352
  return bytes;
53124
53353
  } finally {
53125
- closeSync6(descriptor);
53354
+ closeSync7(descriptor);
53126
53355
  }
53127
53356
  }
53128
53357
  function registerDirectOperations(server2, manager) {
@@ -53235,19 +53464,67 @@ function registerDirectOperations(server2, manager) {
53235
53464
  }
53236
53465
  }
53237
53466
  );
53467
+ tool(
53468
+ "add-attachment-from-pasteboard",
53469
+ "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.",
53470
+ {
53471
+ id: noteId,
53472
+ expectedContentHash: revision,
53473
+ filename: attachmentInput.filename.describe(
53474
+ `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.`
53475
+ ),
53476
+ allowPasteAlert: external_exports.boolean().optional().describe(
53477
+ "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."
53478
+ )
53479
+ },
53480
+ ({ id: id2, expectedContentHash, filename, allowPasteAlert }) => {
53481
+ if (filename !== void 0) checkAttachmentFilename(filename);
53482
+ const before = readSnapshot(manager, id2);
53483
+ if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
53484
+ const frozen = freezePasteboard({
53485
+ pasteboardName: process.env[PASTEBOARD_NAME_ENV]?.trim() || void 0,
53486
+ allowPasteAlert: allowPasteAlert === true
53487
+ });
53488
+ try {
53489
+ const source = {
53490
+ kind: frozen.kind,
53491
+ type: frozen.type,
53492
+ filename: frozen.filename
53493
+ };
53494
+ return {
53495
+ ...attachFile(
53496
+ manager,
53497
+ {
53498
+ id: id2,
53499
+ expectedContentHash,
53500
+ path: frozen.path,
53501
+ filename: pasteboardFilename(filename, frozen.filename)
53502
+ },
53503
+ before
53504
+ ),
53505
+ source
53506
+ };
53507
+ } finally {
53508
+ frozen.cleanup();
53509
+ }
53510
+ }
53511
+ );
53238
53512
  }
53239
- function attachmentName(path10, filename) {
53240
- const source = basename5(path10);
53241
- if (filename === void 0) return source;
53513
+ function checkAttachmentFilename(filename) {
53242
53514
  if (Buffer.byteLength(filename, "utf8") > 255)
53243
53515
  throw new Error("filename must be at most 255 bytes");
53244
53516
  if (filename !== filename.trim() || filename === "" || filename.startsWith(".") || /[/:\\\p{Cc}]/u.test(filename))
53245
53517
  throw new Error(
53246
53518
  "filename must be one path component with no slash, colon, backslash, control character, leading dot, or surrounding spaces"
53247
53519
  );
53248
- if (extname6(filename).toLowerCase() !== extname6(source).toLowerCase())
53520
+ }
53521
+ function attachmentName(path10, filename) {
53522
+ const source = basename6(path10);
53523
+ if (filename === void 0) return source;
53524
+ checkAttachmentFilename(filename);
53525
+ if (extname7(filename).toLowerCase() !== extname7(source).toLowerCase())
53249
53526
  throw new Error(
53250
- `filename must keep the source file's extension (${extname6(source) || "none"})`
53527
+ `filename must keep the source file's extension (${extname7(source) || "none"})`
53251
53528
  );
53252
53529
  return filename;
53253
53530
  }
@@ -53286,13 +53563,13 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
53286
53563
  const matches = row.assetPaths.some((path10) => {
53287
53564
  let descriptor;
53288
53565
  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;
53566
+ descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
53567
+ const stat = fstatSync7(descriptor);
53568
+ return stat.isFile() && stat.size === bytes.length && sha256(readFileSync4(descriptor)) === expected;
53292
53569
  } catch {
53293
53570
  return false;
53294
53571
  } finally {
53295
- if (descriptor !== void 0) closeSync6(descriptor);
53572
+ if (descriptor !== void 0) closeSync7(descriptor);
53296
53573
  }
53297
53574
  });
53298
53575
  if (!matches)
@@ -53301,18 +53578,18 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
53301
53578
  );
53302
53579
  return { attachmentId, name: row.filename };
53303
53580
  }
53304
- function attachFile(manager, args) {
53581
+ function attachFile(manager, args, checked) {
53305
53582
  const { id: id2, expectedContentHash, path: path10 } = args;
53306
53583
  const name = attachmentName(path10, args.filename);
53307
- const before = readSnapshot(manager, id2);
53584
+ const before = checked ?? readSnapshot(manager, id2);
53308
53585
  if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
53309
53586
  const bytes = localAttachment(path10);
53310
53587
  const beforeAttachments = manager.listAttachmentsById(id2);
53311
53588
  const beforeStored = storedAttachmentIds(manager, id2);
53312
- const directory = mkdtempSync5(join23(tmpdir5(), "notes-attachment-add-"));
53313
- const temporaryFile = join23(directory, name);
53589
+ const directory = mkdtempSync6(join24(tmpdir6(), "notes-attachment-add-"));
53590
+ const temporaryFile = join24(directory, name);
53314
53591
  try {
53315
- writeFileSync4(temporaryFile, bytes, { mode: 384 });
53592
+ writeFileSync5(temporaryFile, bytes, { mode: 384 });
53316
53593
  if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
53317
53594
  let returnedId;
53318
53595
  let transportUncertain = false;
@@ -53372,7 +53649,7 @@ function attachFile(manager, args) {
53372
53649
  } : {}
53373
53650
  };
53374
53651
  } finally {
53375
- rmSync5(directory, { recursive: true, force: true });
53652
+ rmSync6(directory, { recursive: true, force: true });
53376
53653
  }
53377
53654
  }
53378
53655
 
@@ -53380,7 +53657,7 @@ function attachFile(manager, args) {
53380
53657
  import { createHash as createHash4 } from "node:crypto";
53381
53658
 
53382
53659
  // src/utils/folderStore.ts
53383
- import { execFileSync as execFileSync19 } from "child_process";
53660
+ import { execFileSync as execFileSync20 } from "child_process";
53384
53661
  import * as os9 from "os";
53385
53662
  import * as path9 from "path";
53386
53663
  var NOTE_STORE_PATH = path9.join(
@@ -53444,7 +53721,7 @@ function runSqlite7(dbPath2, sql, pk) {
53444
53721
  const args = ["-readonly"];
53445
53722
  if (pk !== void 0) args.push("-cmd", `.parameter set @pk ${pk}`);
53446
53723
  args.push(dbPath2, sql);
53447
- return execFileSync19("sqlite3", args, {
53724
+ return execFileSync20("sqlite3", args, {
53448
53725
  encoding: "utf8",
53449
53726
  timeout: 5e3,
53450
53727
  stdio: ["pipe", "pipe", "pipe"]
@@ -53702,13 +53979,13 @@ function registerFolderDelete(server2, manager, deps = defaultDeps) {
53702
53979
  // src/services/templateStore.ts
53703
53980
  import { randomBytes } from "node:crypto";
53704
53981
  import {
53705
- closeSync as closeSync7,
53706
- constants as constants7,
53707
- fstatSync as fstatSync7,
53982
+ closeSync as closeSync8,
53983
+ constants as constants8,
53984
+ fstatSync as fstatSync8,
53708
53985
  linkSync,
53709
53986
  lstatSync as lstatSync5,
53710
53987
  mkdirSync as mkdirSync7,
53711
- openSync as openSync7,
53988
+ openSync as openSync8,
53712
53989
  readdirSync as readdirSync4,
53713
53990
  readSync as readSync5,
53714
53991
  renameSync,
@@ -53716,7 +53993,7 @@ import {
53716
53993
  writeSync as writeSync5
53717
53994
  } from "node:fs";
53718
53995
  import { homedir as homedir19 } from "node:os";
53719
- import { isAbsolute as isAbsolute5, join as join25, resolve as resolve5 } from "node:path";
53996
+ import { isAbsolute as isAbsolute5, join as join26, resolve as resolve5 } from "node:path";
53720
53997
  var TEMPLATE_SLUG = /^[a-z0-9][a-z0-9_-]{0,63}$/;
53721
53998
  var TemplateStoreError = class extends Error {
53722
53999
  constructor(code, message) {
@@ -53736,7 +54013,7 @@ function templateDir(env = process.env) {
53736
54013
  );
53737
54014
  return resolve5(override);
53738
54015
  }
53739
- return join25(homedir19(), "Library/Application Support/apple-notes-mcp/templates");
54016
+ return join26(homedir19(), "Library/Application Support/apple-notes-mcp/templates");
53740
54017
  }
53741
54018
  function checkName(name, action) {
53742
54019
  if (isBuiltinTemplate(name) && action !== "read")
@@ -53773,19 +54050,19 @@ var TemplateStore = class {
53773
54050
  return this.dir;
53774
54051
  }
53775
54052
  file(name) {
53776
- return join25(this.dir, `${name}.json`);
54053
+ return join26(this.dir, `${name}.json`);
53777
54054
  }
53778
54055
  /** Read a saved template's text, refusing symlinks and non-regular files. */
53779
54056
  readText(path10) {
53780
54057
  let fd;
53781
54058
  try {
53782
- fd = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
54059
+ fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW);
53783
54060
  } catch (error2) {
53784
54061
  if (error2.code === "ENOENT") return void 0;
53785
54062
  throw new TemplateStoreError("unsafe-path", `Refusing to read ${path10}: not a regular file.`);
53786
54063
  }
53787
54064
  try {
53788
- const stat = fstatSync7(fd);
54065
+ const stat = fstatSync8(fd);
53789
54066
  if (!stat.isFile())
53790
54067
  throw new TemplateStoreError(
53791
54068
  "unsafe-path",
@@ -53807,7 +54084,7 @@ var TemplateStore = class {
53807
54084
  }
53808
54085
  return { text: data.subarray(0, read).toString("utf8"), bytes: read, mtime: stat.mtime };
53809
54086
  } finally {
53810
- closeSync7(fd);
54087
+ closeSync8(fd);
53811
54088
  }
53812
54089
  }
53813
54090
  /** Saved templates in name order; `skipped` counts entries that are not usable. */
@@ -53887,10 +54164,10 @@ var TemplateStore = class {
53887
54164
  "template-exists",
53888
54165
  `A template named "${name}" already exists. Pass force: true to replace it.`
53889
54166
  );
53890
- const temp = join25(this.dir, `.${name}.${randomBytes(6).toString("hex")}.tmp`);
53891
- const fd = openSync7(
54167
+ const temp = join26(this.dir, `.${name}.${randomBytes(6).toString("hex")}.tmp`);
54168
+ const fd = openSync8(
53892
54169
  temp,
53893
- constants7.O_WRONLY | constants7.O_CREAT | constants7.O_EXCL | constants7.O_NOFOLLOW,
54170
+ constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
53894
54171
  384
53895
54172
  );
53896
54173
  try {
@@ -53898,7 +54175,7 @@ var TemplateStore = class {
53898
54175
  let written = 0;
53899
54176
  while (written < data.length) written += writeSync5(fd, data, written);
53900
54177
  } finally {
53901
- closeSync7(fd);
54178
+ closeSync8(fd);
53902
54179
  }
53903
54180
  try {
53904
54181
  if (force) renameSync(temp, path10);
@@ -54118,7 +54395,7 @@ function registerMarkdownTemplates(server2, store = () => new TemplateStore()) {
54118
54395
 
54119
54396
  // src/utils/svgAnalyzer.ts
54120
54397
  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";
54398
+ import { closeSync as closeSync9, constants as constants9, fstatSync as fstatSync9, openSync as openSync9, readSync as readSync6 } from "node:fs";
54122
54399
 
54123
54400
  // src/utils/svgColor.ts
54124
54401
  var NAMED = {
@@ -56087,14 +56364,14 @@ function analyzeSvgBuffer(source) {
56087
56364
  function readSvgSource(path10) {
56088
56365
  let fd;
56089
56366
  try {
56090
- fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
56367
+ fd = openSync9(path10, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
56091
56368
  } catch (error2) {
56092
56369
  if (error2.code === "ELOOP")
56093
56370
  throw new SvgError("svg_file_invalid", "The SVG path is a symbolic link");
56094
56371
  throw new SvgError("svg_file_invalid", "The SVG file does not exist or cannot be read");
56095
56372
  }
56096
56373
  try {
56097
- const info = fstatSync8(fd);
56374
+ const info = fstatSync9(fd);
56098
56375
  if (!info.isFile())
56099
56376
  throw new SvgError("svg_file_invalid", "The SVG path is not a regular file");
56100
56377
  if (info.size > SVG_LIMITS.maxSourceBytes)
@@ -56116,7 +56393,7 @@ function readSvgSource(path10) {
56116
56393
  }
56117
56394
  return buffer.subarray(0, total);
56118
56395
  } finally {
56119
- closeSync8(fd);
56396
+ closeSync9(fd);
56120
56397
  }
56121
56398
  }
56122
56399
  function analyzeSvgFile(path10) {
@@ -56779,14 +57056,14 @@ import {
56779
57056
  chmodSync,
56780
57057
  existsSync as existsSync14,
56781
57058
  mkdirSync as mkdirSync8,
56782
- mkdtempSync as mkdtempSync6,
56783
- readFileSync as readFileSync4,
57059
+ mkdtempSync as mkdtempSync7,
57060
+ readFileSync as readFileSync5,
56784
57061
  renameSync as renameSync2,
56785
- rmSync as rmSync6,
56786
- writeFileSync as writeFileSync5
57062
+ rmSync as rmSync7,
57063
+ writeFileSync as writeFileSync6
56787
57064
  } from "node:fs";
56788
57065
  import { homedir as homedir20, release as release3 } from "node:os";
56789
- import { dirname as dirname8, join as join26, resolve as resolve7 } from "node:path";
57066
+ import { dirname as dirname8, join as join27, resolve as resolve7 } from "node:path";
56790
57067
  import { fileURLToPath as fileURLToPath2 } from "node:url";
56791
57068
  var PUBLIC_HELPER_PROTOCOL = 1;
56792
57069
  var PUBLIC_HELPER_DIR_ENV = "APPLE_NOTES_MCP_PUBLIC_HELPER_DIR";
@@ -56814,10 +57091,10 @@ var publicManifestSchema = external_exports.object({
56814
57091
  function packageRoot(fromDir = dirname8(fileURLToPath2(import.meta.url))) {
56815
57092
  let dir = fromDir;
56816
57093
  for (; ; ) {
56817
- const candidate = join26(dir, "package.json");
57094
+ const candidate = join27(dir, "package.json");
56818
57095
  if (existsSync14(candidate)) {
56819
57096
  try {
56820
- if (JSON.parse(readFileSync4(candidate, "utf8")).name === "apple-notes-mcp")
57097
+ if (JSON.parse(readFileSync5(candidate, "utf8")).name === "apple-notes-mcp")
56821
57098
  return dir;
56822
57099
  } catch {
56823
57100
  }
@@ -56831,9 +57108,9 @@ function defaultPublicHelperDeps(overrides = {}) {
56831
57108
  return {
56832
57109
  env: process.env,
56833
57110
  platform: process.platform,
56834
- sourcePath: join26(packageRoot(), PUBLIC_HELPER_SOURCE),
57111
+ sourcePath: join27(packageRoot(), PUBLIC_HELPER_SOURCE),
56835
57112
  exists: existsSync14,
56836
- readFile: (path10) => readFileSync4(path10),
57113
+ readFile: (path10) => readFileSync5(path10),
56837
57114
  spawn: spawnSync3,
56838
57115
  spawnAsync: spawn,
56839
57116
  ...overrides
@@ -56842,14 +57119,14 @@ function defaultPublicHelperDeps(overrides = {}) {
56842
57119
  function publicHelperInstallDir(env = process.env) {
56843
57120
  const override = env[PUBLIC_HELPER_DIR_ENV]?.trim();
56844
57121
  if (override) return override;
56845
- return join26(homedir20(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
57122
+ return join27(homedir20(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
56846
57123
  }
56847
57124
  function sha256Hex(data) {
56848
57125
  return createHash6("sha256").update(data).digest("hex");
56849
57126
  }
56850
57127
  function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
56851
57128
  const installDir = publicHelperInstallDir(deps.env);
56852
- const binaryPath = join26(installDir, PUBLIC_HELPER_BINARY);
57129
+ const binaryPath = join27(installDir, PUBLIC_HELPER_BINARY);
56853
57130
  const base = { installDir, binaryPath, sourcePath: deps.sourcePath, manifest: null };
56854
57131
  const fail = (reason, detail) => ({
56855
57132
  ...base,
@@ -56860,7 +57137,7 @@ function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
56860
57137
  if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
56861
57138
  if (!deps.exists(deps.sourcePath))
56862
57139
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
56863
- const manifestPath = join26(installDir, PUBLIC_HELPER_MANIFEST);
57140
+ const manifestPath = join27(installDir, PUBLIC_HELPER_MANIFEST);
56864
57141
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
56865
57142
  return fail(
56866
57143
  "helper_not_installed",
@@ -56900,14 +57177,14 @@ var UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
56900
57177
  "helper_modified",
56901
57178
  "helper_manifest_invalid"
56902
57179
  ]);
56903
- var ENVELOPE_CODES = {
57180
+ var ENVELOPE_CODES2 = {
56904
57181
  invalid_request: "validation_error",
56905
57182
  attachment_not_found: "not_found"
56906
57183
  };
56907
57184
  var PublicHelperError = class extends CodedError {
56908
57185
  constructor(code, message) {
56909
57186
  super(message, {
56910
- code: UNAVAILABLE_CODES.has(code) ? "unsupported" : ENVELOPE_CODES[code] ?? "operation_failed",
57187
+ code: UNAVAILABLE_CODES.has(code) ? "unsupported" : ENVELOPE_CODES2[code] ?? "operation_failed",
56911
57188
  helperCode: code
56912
57189
  });
56913
57190
  this.code = code;
@@ -57137,13 +57414,13 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
57137
57414
  steps.push({ step: "find compiler", ok: true, detail: compiler });
57138
57415
  const installDir = publicHelperInstallDir(deps.env);
57139
57416
  mkdirSync8(installDir, { recursive: true, mode: 448 });
57140
- const staging = mkdtempSync6(join26(installDir, ".staging-"));
57417
+ const staging = mkdtempSync7(join27(installDir, ".staging-"));
57141
57418
  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 });
57419
+ const stagedBinary = join27(staging, PUBLIC_HELPER_BINARY);
57420
+ const digestPath = join27(staging, "source-digest.swift");
57421
+ const plistPath = join27(staging, "Info.plist");
57422
+ writeFileSync6(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
57423
+ writeFileSync6(plistPath, publicHelperInfoPlist(), { mode: 384 });
57147
57424
  const compile = deps.spawn(
57148
57425
  "/usr/bin/xcrun",
57149
57426
  publicHelperCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
@@ -57204,15 +57481,15 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
57204
57481
  compiler
57205
57482
  };
57206
57483
  chmodSync(stagedBinary, 448);
57207
- renameSync2(stagedBinary, join26(installDir, PUBLIC_HELPER_BINARY));
57208
- writeFileSync5(
57209
- join26(installDir, PUBLIC_HELPER_MANIFEST),
57484
+ renameSync2(stagedBinary, join27(installDir, PUBLIC_HELPER_BINARY));
57485
+ writeFileSync6(
57486
+ join27(installDir, PUBLIC_HELPER_MANIFEST),
57210
57487
  JSON.stringify(manifest, null, 2) + "\n",
57211
57488
  { mode: 384 }
57212
57489
  );
57213
57490
  steps.push({ step: "install", ok: true, detail: installDir });
57214
57491
  } finally {
57215
- rmSync6(staging, { recursive: true, force: true });
57492
+ rmSync7(staging, { recursive: true, force: true });
57216
57493
  }
57217
57494
  const installation = inspectPublicHelper(deps);
57218
57495
  steps.push({
@@ -57450,8 +57727,8 @@ function formatNoteDrawings(result) {
57450
57727
  }
57451
57728
 
57452
57729
  // src/utils/noteAudio.ts
57453
- import { readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
57454
- import { join as join27 } from "node:path";
57730
+ import { readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
57731
+ import { join as join28 } from "node:path";
57455
57732
  var EXTRA_AUDIO_UTIS = [
57456
57733
  "public.mp3",
57457
57734
  "public.aiff-audio",
@@ -57476,7 +57753,7 @@ function accountDirsFor(containerDir, accountIdentifier) {
57476
57753
  if (own) return [own];
57477
57754
  let names;
57478
57755
  try {
57479
- names = readdirSync5(join27(containerDir, "Accounts")).sort();
57756
+ names = readdirSync5(join28(containerDir, "Accounts")).sort();
57480
57757
  } catch {
57481
57758
  return [];
57482
57759
  }
@@ -57488,8 +57765,8 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
57488
57765
  if (!id2 || !filename) return null;
57489
57766
  const generation = safeComponent(media?.generation);
57490
57767
  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)];
57768
+ const base = join28(dir, "Media", id2);
57769
+ const candidates = generation ? [join28(base, generation, filename), join28(base, filename)] : [join28(base, filename)];
57493
57770
  for (const candidate of candidates) {
57494
57771
  const real = realInside(candidate, dir);
57495
57772
  if (real && isFile2(real)) return real;
@@ -57499,7 +57776,7 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
57499
57776
  }
57500
57777
  function isFile2(path10) {
57501
57778
  try {
57502
- return statSync4(path10).isFile();
57779
+ return statSync5(path10).isFile();
57503
57780
  } catch {
57504
57781
  return false;
57505
57782
  }
@@ -57762,20 +58039,20 @@ import {
57762
58039
  chmodSync as chmodSync2,
57763
58040
  existsSync as existsSync16,
57764
58041
  mkdirSync as mkdirSync9,
57765
- mkdtempSync as mkdtempSync7,
58042
+ mkdtempSync as mkdtempSync8,
57766
58043
  renameSync as renameSync3,
57767
- rmSync as rmSync7,
57768
- writeFileSync as writeFileSync6
58044
+ rmSync as rmSync8,
58045
+ writeFileSync as writeFileSync7
57769
58046
  } from "node:fs";
57770
58047
  import { release as release4 } from "node:os";
57771
- import { join as join29 } from "node:path";
58048
+ import { join as join30 } from "node:path";
57772
58049
 
57773
58050
  // src/services/privateHelper.ts
57774
58051
  import { spawnSync as spawnSync4 } from "node:child_process";
57775
58052
  import { createHash as createHash7 } from "node:crypto";
57776
- import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
58053
+ import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
57777
58054
  import { homedir as homedir21 } from "node:os";
57778
- import { dirname as dirname9, join as join28, resolve as resolve8 } from "node:path";
58055
+ import { dirname as dirname9, join as join29, resolve as resolve8 } from "node:path";
57779
58056
  import { fileURLToPath as fileURLToPath3 } from "node:url";
57780
58057
  var PRIVATE_HELPER_PROTOCOL = 1;
57781
58058
  var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
@@ -57803,10 +58080,10 @@ var manifestSchema = external_exports.object({
57803
58080
  function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
57804
58081
  let dir = fromDir;
57805
58082
  for (; ; ) {
57806
- const candidate = join28(dir, "package.json");
58083
+ const candidate = join29(dir, "package.json");
57807
58084
  if (existsSync15(candidate)) {
57808
58085
  try {
57809
- const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
58086
+ const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
57810
58087
  if (pkg.name === "apple-notes-mcp") return dir;
57811
58088
  } catch {
57812
58089
  }
@@ -57820,9 +58097,9 @@ function defaultDeps2(overrides = {}) {
57820
58097
  return {
57821
58098
  env: process.env,
57822
58099
  platform: process.platform,
57823
- sourcePath: join28(packageRoot2(), HELPER_SOURCE_RELATIVE),
58100
+ sourcePath: join29(packageRoot2(), HELPER_SOURCE_RELATIVE),
57824
58101
  exists: existsSync15,
57825
- readFile: (path10) => readFileSync5(path10),
58102
+ readFile: (path10) => readFileSync6(path10),
57826
58103
  spawn: spawnSync4,
57827
58104
  ...overrides
57828
58105
  };
@@ -57833,14 +58110,14 @@ function privateHelperEnabled(env = process.env) {
57833
58110
  function helperInstallDir(env = process.env) {
57834
58111
  const override = env[HELPER_DIR_ENV]?.trim();
57835
58112
  if (override) return override;
57836
- return join28(homedir21(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
58113
+ return join29(homedir21(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
57837
58114
  }
57838
58115
  function sha256Hex2(data) {
57839
58116
  return createHash7("sha256").update(data).digest("hex");
57840
58117
  }
57841
58118
  function inspectInstallation(deps = defaultDeps2()) {
57842
58119
  const installDir = helperInstallDir(deps.env);
57843
- const binaryPath = join28(installDir, HELPER_BINARY_NAME);
58120
+ const binaryPath = join29(installDir, HELPER_BINARY_NAME);
57844
58121
  const base = {
57845
58122
  installDir,
57846
58123
  binaryPath,
@@ -57858,7 +58135,7 @@ function inspectInstallation(deps = defaultDeps2()) {
57858
58135
  if (!deps.exists(deps.sourcePath))
57859
58136
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
57860
58137
  base.expectedSourceSha256 = sha256Hex2(deps.readFile(deps.sourcePath));
57861
- const manifestPath = join28(installDir, MANIFEST_NAME);
58138
+ const manifestPath = join29(installDir, MANIFEST_NAME);
57862
58139
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
57863
58140
  return fail(
57864
58141
  "helper_not_installed",
@@ -58173,9 +58450,9 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
58173
58450
  steps.push({ step: "find compiler", ok: true, detail: compiler });
58174
58451
  const installDir = helperInstallDir(deps.env);
58175
58452
  mkdirSync9(installDir, { recursive: true, mode: 448 });
58176
- const staging = mkdtempSync7(join29(installDir, ".staging-"));
58453
+ const staging = mkdtempSync8(join30(installDir, ".staging-"));
58177
58454
  try {
58178
- const stagedBinary = join29(staging, HELPER_BINARY_NAME);
58455
+ const stagedBinary = join30(staging, HELPER_BINARY_NAME);
58179
58456
  const compile = deps.spawn(
58180
58457
  "/usr/bin/xcrun",
58181
58458
  compileArguments(deps.sourcePath, stagedBinary, sourceSha),
@@ -58252,13 +58529,13 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
58252
58529
  compiler
58253
58530
  };
58254
58531
  chmodSync2(stagedBinary, 448);
58255
- renameSync3(stagedBinary, join29(installDir, HELPER_BINARY_NAME));
58256
- writeFileSync6(join29(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
58532
+ renameSync3(stagedBinary, join30(installDir, HELPER_BINARY_NAME));
58533
+ writeFileSync7(join30(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
58257
58534
  mode: 384
58258
58535
  });
58259
58536
  steps.push({ step: "install", ok: true, detail: installDir });
58260
58537
  } finally {
58261
- if (existsSync16(staging)) rmSync7(staging, { recursive: true, force: true });
58538
+ if (existsSync16(staging)) rmSync8(staging, { recursive: true, force: true });
58262
58539
  }
58263
58540
  const installation = inspectInstallation(deps);
58264
58541
  steps.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.9.16",
3
+ "version": "2.9.17",
4
4
  "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",