apple-notes-mcp 2.9.15 → 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.
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, constants8) {
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, constants8);
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, constants8) {
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, constants8);
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, constants8) {
506
- this.code = optimizeExpr(this.code, names, constants8);
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, constants8) {
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, constants8))
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, constants8) {
593
+ optimizeNames(names, constants10) {
594
594
  var _a;
595
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants8);
596
- if (!(super.optimizeNames(names, constants8) || 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, constants8);
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, constants8) {
622
- if (!super.optimizeNames(names, constants8))
621
+ optimizeNames(names, constants10) {
622
+ if (!super.optimizeNames(names, constants10))
623
623
  return;
624
- this.iteration = optimizeExpr(this.iteration, names, constants8);
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, constants8) {
661
- if (!super.optimizeNames(names, constants8))
660
+ optimizeNames(names, constants10) {
661
+ if (!super.optimizeNames(names, constants10))
662
662
  return;
663
- this.iterable = optimizeExpr(this.iterable, names, constants8);
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, constants8) {
705
+ optimizeNames(names, constants10) {
706
706
  var _a, _b;
707
- super.optimizeNames(names, constants8);
708
- (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants8);
709
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants8);
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, constants8) {
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 = constants8[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 && constants8[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) {
@@ -2991,7 +2991,7 @@ var require_compile = __commonJS({
2991
2991
  const schOrFunc = root.refs[ref];
2992
2992
  if (schOrFunc)
2993
2993
  return schOrFunc;
2994
- let _sch = resolve8.call(this, root, ref);
2994
+ let _sch = resolve9.call(this, root, ref);
2995
2995
  if (_sch === void 0) {
2996
2996
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
2997
2997
  const { schemaId } = this.opts;
@@ -3018,7 +3018,7 @@ var require_compile = __commonJS({
3018
3018
  function sameSchemaEnv(s1, s2) {
3019
3019
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3020
3020
  }
3021
- function resolve8(root, ref) {
3021
+ function resolve9(root, ref) {
3022
3022
  let sch;
3023
3023
  while (typeof (sch = this.refs[ref]) == "string")
3024
3024
  ref = sch;
@@ -3843,7 +3843,7 @@ var require_fast_uri = __commonJS({
3843
3843
  }
3844
3844
  return uri;
3845
3845
  }
3846
- function resolve8(baseURI, relativeURI, options) {
3846
+ function resolve9(baseURI, relativeURI, options) {
3847
3847
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3848
3848
  const {
3849
3849
  parsed: baseParsed,
@@ -4205,7 +4205,7 @@ var require_fast_uri = __commonJS({
4205
4205
  var fastUri = {
4206
4206
  SCHEMES,
4207
4207
  normalize,
4208
- resolve: resolve8,
4208
+ resolve: resolve9,
4209
4209
  resolveComponent,
4210
4210
  equal,
4211
4211
  serialize,
@@ -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 constants8 = {
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 constants8) {
7465
- v = { value: constants8[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 join29(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 = join29(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 join29(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);
@@ -36608,7 +36608,7 @@ var Protocol = class {
36608
36608
  return;
36609
36609
  }
36610
36610
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
36611
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
36611
+ await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
36612
36612
  options?.signal?.throwIfAborted();
36613
36613
  }
36614
36614
  } catch (error2) {
@@ -36625,7 +36625,7 @@ var Protocol = class {
36625
36625
  */
36626
36626
  request(request, resultSchema, options) {
36627
36627
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
36628
- return new Promise((resolve8, reject) => {
36628
+ return new Promise((resolve9, reject) => {
36629
36629
  const earlyReject = (error2) => {
36630
36630
  reject(error2);
36631
36631
  };
@@ -36703,7 +36703,7 @@ var Protocol = class {
36703
36703
  if (!parseResult.success) {
36704
36704
  reject(parseResult.error);
36705
36705
  } else {
36706
- resolve8(parseResult.data);
36706
+ resolve9(parseResult.data);
36707
36707
  }
36708
36708
  } catch (error2) {
36709
36709
  reject(error2);
@@ -36964,12 +36964,12 @@ var Protocol = class {
36964
36964
  }
36965
36965
  } catch {
36966
36966
  }
36967
- return new Promise((resolve8, reject) => {
36967
+ return new Promise((resolve9, reject) => {
36968
36968
  if (signal.aborted) {
36969
36969
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
36970
36970
  return;
36971
36971
  }
36972
- const timeoutId = setTimeout(resolve8, interval);
36972
+ const timeoutId = setTimeout(resolve9, interval);
36973
36973
  signal.addEventListener("abort", () => {
36974
36974
  clearTimeout(timeoutId);
36975
36975
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -38282,7 +38282,7 @@ var McpServer = class {
38282
38282
  let task = createTaskResult.task;
38283
38283
  const pollInterval = task.pollInterval ?? 5e3;
38284
38284
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
38285
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
38285
+ await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
38286
38286
  const updatedTask = await extra.taskStore.getTask(taskId);
38287
38287
  if (!updatedTask) {
38288
38288
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -38970,12 +38970,12 @@ var StdioServerTransport = class {
38970
38970
  this.onclose?.();
38971
38971
  }
38972
38972
  send(message) {
38973
- return new Promise((resolve8) => {
38973
+ return new Promise((resolve9) => {
38974
38974
  const json2 = serializeMessage(message);
38975
38975
  if (this._stdout.write(json2)) {
38976
- resolve8();
38976
+ resolve9();
38977
38977
  } else {
38978
- this._stdout.once("drain", resolve8);
38978
+ this._stdout.once("drain", resolve9);
38979
38979
  }
38980
38980
  });
38981
38981
  }
@@ -47633,13 +47633,13 @@ var escapeSegment = escapeFolderName;
47633
47633
  function resolveFolders(rows) {
47634
47634
  const byPk = new Map(rows.map((row) => [row.pk, row]));
47635
47635
  const resolved = /* @__PURE__ */ new Map();
47636
- const resolve8 = (pk, seen) => {
47636
+ const resolve9 = (pk, seen) => {
47637
47637
  const cached2 = resolved.get(pk);
47638
47638
  if (cached2) return cached2;
47639
47639
  const row = byPk.get(pk);
47640
47640
  if (!row || seen.has(pk)) return void 0;
47641
47641
  seen.add(pk);
47642
- const parent = row.parent !== null ? resolve8(row.parent, seen) : void 0;
47642
+ const parent = row.parent !== null ? resolve9(row.parent, seen) : void 0;
47643
47643
  const name = row.name ?? "";
47644
47644
  const path10 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
47645
47645
  const plainPath = parent ? `${parent.plainPath}/${name}` : name;
@@ -47653,7 +47653,7 @@ function resolveFolders(rows) {
47653
47653
  resolved.set(pk, info);
47654
47654
  return info;
47655
47655
  };
47656
- for (const row of rows) resolve8(row.pk, /* @__PURE__ */ new Set());
47656
+ for (const row of rows) resolve9(row.pk, /* @__PURE__ */ new Set());
47657
47657
  return resolved;
47658
47658
  }
47659
47659
  var coreDataMs = (seconds) => seconds === null || !Number.isFinite(seconds) ? void 0 : CORE_DATA_EPOCH_MS2 + seconds * 1e3;
@@ -48860,6 +48860,19 @@ var FEATURES = [
48860
48860
  tools: [],
48861
48861
  minimumMacOSVersion: null,
48862
48862
  requirements: [{ kind: "native_write_helper" }]
48863
+ },
48864
+ {
48865
+ name: "markdownTemplateLibrary",
48866
+ description: "Validate, save, list, show and delete Markdown export templates (local JSON files; exporting with one still needs Full Disk Access)",
48867
+ tools: [
48868
+ "list-markdown-templates",
48869
+ "show-markdown-template",
48870
+ "validate-markdown-template",
48871
+ "save-markdown-template",
48872
+ "delete-markdown-template"
48873
+ ],
48874
+ minimumMacOSVersion: null,
48875
+ requirements: []
48863
48876
  }
48864
48877
  ];
48865
48878
  function requirementLabel(requirement) {
@@ -51653,16 +51666,16 @@ var NOTE_PLACEHOLDERS = [
51653
51666
  "exportStem"
51654
51667
  ];
51655
51668
  var PLACEHOLDER_MODIFIERS = ["raw", "yaml"];
51656
- var wrap2 = (before, after = "", join29) => ({
51669
+ var wrap2 = (before, after = "", join31) => ({
51657
51670
  mode: "wrap",
51658
51671
  before,
51659
51672
  after,
51660
- ...join29 ? { join: join29 } : {}
51673
+ ...join31 ? { join: join31 } : {}
51661
51674
  });
51662
- var pattern = (value, join29) => ({
51675
+ var pattern = (value, join31) => ({
51663
51676
  mode: "pattern",
51664
51677
  value,
51665
- ...join29 ? { join: join29 } : {}
51678
+ ...join31 ? { join: join31 } : {}
51666
51679
  });
51667
51680
  var STANDARD = {
51668
51681
  schemaVersion: 1,
@@ -53043,6 +53056,21 @@ function exportWithTemplate(request, deps, chosen, notes, skipped, { output, ass
53043
53056
 
53044
53057
  // src/tools/directOperations.ts
53045
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";
53046
53074
  import {
53047
53075
  closeSync as closeSync6,
53048
53076
  constants as constants6,
@@ -53051,10 +53079,224 @@ import {
53051
53079
  openSync as openSync6,
53052
53080
  readFileSync as readFileSync3,
53053
53081
  rmSync as rmSync5,
53082
+ statSync as statSync4,
53054
53083
  writeFileSync as writeFileSync4
53055
53084
  } from "node:fs";
53056
53085
  import { tmpdir as tmpdir5 } from "node:os";
53057
- 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
53058
53300
  var noteId = exactIdInput(
53059
53301
  "ICNote",
53060
53302
  /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i,
@@ -53099,17 +53341,17 @@ function assertExistingContentPreserved(before, after) {
53099
53341
  }
53100
53342
  function localAttachment(path10) {
53101
53343
  if (!isAbsolute4(path10)) throw new Error("An absolute local file path is required");
53102
- const descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
53344
+ const descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
53103
53345
  try {
53104
- const stat = fstatSync6(descriptor);
53346
+ const stat = fstatSync7(descriptor);
53105
53347
  if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
53106
53348
  throw new Error("Attachment must be a nonempty regular file of at most 64 MiB");
53107
- const bytes = readFileSync3(descriptor);
53349
+ const bytes = readFileSync4(descriptor);
53108
53350
  if (bytes.length !== stat.size)
53109
53351
  throw new Error("Attachment changed while it was being read; try again");
53110
53352
  return bytes;
53111
53353
  } finally {
53112
- closeSync6(descriptor);
53354
+ closeSync7(descriptor);
53113
53355
  }
53114
53356
  }
53115
53357
  function registerDirectOperations(server2, manager) {
@@ -53222,19 +53464,67 @@ function registerDirectOperations(server2, manager) {
53222
53464
  }
53223
53465
  }
53224
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
+ );
53225
53512
  }
53226
- function attachmentName(path10, filename) {
53227
- const source = basename5(path10);
53228
- if (filename === void 0) return source;
53513
+ function checkAttachmentFilename(filename) {
53229
53514
  if (Buffer.byteLength(filename, "utf8") > 255)
53230
53515
  throw new Error("filename must be at most 255 bytes");
53231
53516
  if (filename !== filename.trim() || filename === "" || filename.startsWith(".") || /[/:\\\p{Cc}]/u.test(filename))
53232
53517
  throw new Error(
53233
53518
  "filename must be one path component with no slash, colon, backslash, control character, leading dot, or surrounding spaces"
53234
53519
  );
53235
- 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())
53236
53526
  throw new Error(
53237
- `filename must keep the source file's extension (${extname6(source) || "none"})`
53527
+ `filename must keep the source file's extension (${extname7(source) || "none"})`
53238
53528
  );
53239
53529
  return filename;
53240
53530
  }
@@ -53273,13 +53563,13 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
53273
53563
  const matches = row.assetPaths.some((path10) => {
53274
53564
  let descriptor;
53275
53565
  try {
53276
- descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
53277
- const stat = fstatSync6(descriptor);
53278
- 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;
53279
53569
  } catch {
53280
53570
  return false;
53281
53571
  } finally {
53282
- if (descriptor !== void 0) closeSync6(descriptor);
53572
+ if (descriptor !== void 0) closeSync7(descriptor);
53283
53573
  }
53284
53574
  });
53285
53575
  if (!matches)
@@ -53288,18 +53578,18 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
53288
53578
  );
53289
53579
  return { attachmentId, name: row.filename };
53290
53580
  }
53291
- function attachFile(manager, args) {
53581
+ function attachFile(manager, args, checked) {
53292
53582
  const { id: id2, expectedContentHash, path: path10 } = args;
53293
53583
  const name = attachmentName(path10, args.filename);
53294
- const before = readSnapshot(manager, id2);
53584
+ const before = checked ?? readSnapshot(manager, id2);
53295
53585
  if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
53296
53586
  const bytes = localAttachment(path10);
53297
53587
  const beforeAttachments = manager.listAttachmentsById(id2);
53298
53588
  const beforeStored = storedAttachmentIds(manager, id2);
53299
- const directory = mkdtempSync5(join23(tmpdir5(), "notes-attachment-add-"));
53300
- const temporaryFile = join23(directory, name);
53589
+ const directory = mkdtempSync6(join24(tmpdir6(), "notes-attachment-add-"));
53590
+ const temporaryFile = join24(directory, name);
53301
53591
  try {
53302
- writeFileSync4(temporaryFile, bytes, { mode: 384 });
53592
+ writeFileSync5(temporaryFile, bytes, { mode: 384 });
53303
53593
  if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
53304
53594
  let returnedId;
53305
53595
  let transportUncertain = false;
@@ -53359,7 +53649,7 @@ function attachFile(manager, args) {
53359
53649
  } : {}
53360
53650
  };
53361
53651
  } finally {
53362
- rmSync5(directory, { recursive: true, force: true });
53652
+ rmSync6(directory, { recursive: true, force: true });
53363
53653
  }
53364
53654
  }
53365
53655
 
@@ -53367,7 +53657,7 @@ function attachFile(manager, args) {
53367
53657
  import { createHash as createHash4 } from "node:crypto";
53368
53658
 
53369
53659
  // src/utils/folderStore.ts
53370
- import { execFileSync as execFileSync19 } from "child_process";
53660
+ import { execFileSync as execFileSync20 } from "child_process";
53371
53661
  import * as os9 from "os";
53372
53662
  import * as path9 from "path";
53373
53663
  var NOTE_STORE_PATH = path9.join(
@@ -53431,7 +53721,7 @@ function runSqlite7(dbPath2, sql, pk) {
53431
53721
  const args = ["-readonly"];
53432
53722
  if (pk !== void 0) args.push("-cmd", `.parameter set @pk ${pk}`);
53433
53723
  args.push(dbPath2, sql);
53434
- return execFileSync19("sqlite3", args, {
53724
+ return execFileSync20("sqlite3", args, {
53435
53725
  encoding: "utf8",
53436
53726
  timeout: 5e3,
53437
53727
  stdio: ["pipe", "pipe", "pipe"]
@@ -53686,9 +53976,426 @@ function registerFolderDelete(server2, manager, deps = defaultDeps) {
53686
53976
  );
53687
53977
  }
53688
53978
 
53979
+ // src/services/templateStore.ts
53980
+ import { randomBytes } from "node:crypto";
53981
+ import {
53982
+ closeSync as closeSync8,
53983
+ constants as constants8,
53984
+ fstatSync as fstatSync8,
53985
+ linkSync,
53986
+ lstatSync as lstatSync5,
53987
+ mkdirSync as mkdirSync7,
53988
+ openSync as openSync8,
53989
+ readdirSync as readdirSync4,
53990
+ readSync as readSync5,
53991
+ renameSync,
53992
+ unlinkSync as unlinkSync3,
53993
+ writeSync as writeSync5
53994
+ } from "node:fs";
53995
+ import { homedir as homedir19 } from "node:os";
53996
+ import { isAbsolute as isAbsolute5, join as join26, resolve as resolve5 } from "node:path";
53997
+ var TEMPLATE_SLUG = /^[a-z0-9][a-z0-9_-]{0,63}$/;
53998
+ var TemplateStoreError = class extends Error {
53999
+ constructor(code, message) {
54000
+ super(message);
54001
+ this.code = code;
54002
+ this.name = "TemplateStoreError";
54003
+ }
54004
+ code;
54005
+ };
54006
+ function templateDir(env = process.env) {
54007
+ const override = env.APPLE_NOTES_MCP_TEMPLATE_DIR?.trim();
54008
+ if (override) {
54009
+ if (!isAbsolute5(override))
54010
+ throw new TemplateStoreError(
54011
+ "unsafe-path",
54012
+ "APPLE_NOTES_MCP_TEMPLATE_DIR must be an absolute path."
54013
+ );
54014
+ return resolve5(override);
54015
+ }
54016
+ return join26(homedir19(), "Library/Application Support/apple-notes-mcp/templates");
54017
+ }
54018
+ function checkName(name, action) {
54019
+ if (isBuiltinTemplate(name) && action !== "read")
54020
+ throw new TemplateStoreError(
54021
+ "reserved-name",
54022
+ `"${name}" is a built-in template and cannot be ${action === "save" ? "saved over" : "deleted"}.`
54023
+ );
54024
+ if (!TEMPLATE_SLUG.test(name))
54025
+ throw new TemplateStoreError(
54026
+ "invalid-name",
54027
+ `Template names are 1-64 characters of a-z, 0-9, "-" and "_", starting with a letter or digit; got "${name}".`
54028
+ );
54029
+ }
54030
+ var TemplateStore = class {
54031
+ constructor(dir = templateDir()) {
54032
+ this.dir = dir;
54033
+ }
54034
+ dir;
54035
+ /** The library root, which must be a real directory (not a symlink) when it exists. */
54036
+ root(create) {
54037
+ let stat;
54038
+ try {
54039
+ stat = lstatSync5(this.dir);
54040
+ } catch {
54041
+ if (!create) return void 0;
54042
+ mkdirSync7(this.dir, { recursive: true, mode: 448 });
54043
+ stat = lstatSync5(this.dir);
54044
+ }
54045
+ if (!stat.isDirectory())
54046
+ throw new TemplateStoreError(
54047
+ "unsafe-path",
54048
+ `The template library ${this.dir} is not a directory (symlinks are refused).`
54049
+ );
54050
+ return this.dir;
54051
+ }
54052
+ file(name) {
54053
+ return join26(this.dir, `${name}.json`);
54054
+ }
54055
+ /** Read a saved template's text, refusing symlinks and non-regular files. */
54056
+ readText(path10) {
54057
+ let fd;
54058
+ try {
54059
+ fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW);
54060
+ } catch (error2) {
54061
+ if (error2.code === "ENOENT") return void 0;
54062
+ throw new TemplateStoreError("unsafe-path", `Refusing to read ${path10}: not a regular file.`);
54063
+ }
54064
+ try {
54065
+ const stat = fstatSync8(fd);
54066
+ if (!stat.isFile())
54067
+ throw new TemplateStoreError(
54068
+ "unsafe-path",
54069
+ `Refusing to read ${path10}: not a regular file.`
54070
+ );
54071
+ if (stat.size > MAX_TEMPLATE_BYTES)
54072
+ throw new TemplateValidationError([
54073
+ {
54074
+ path: "$",
54075
+ message: `template is ${stat.size} bytes; the limit is ${MAX_TEMPLATE_BYTES}`
54076
+ }
54077
+ ]);
54078
+ const data = Buffer.alloc(stat.size);
54079
+ let read = 0;
54080
+ while (read < stat.size) {
54081
+ const n = readSync5(fd, data, read, stat.size - read, read);
54082
+ if (n <= 0) break;
54083
+ read += n;
54084
+ }
54085
+ return { text: data.subarray(0, read).toString("utf8"), bytes: read, mtime: stat.mtime };
54086
+ } finally {
54087
+ closeSync8(fd);
54088
+ }
54089
+ }
54090
+ /** Saved templates in name order; `skipped` counts entries that are not usable. */
54091
+ list() {
54092
+ const templates = [];
54093
+ let skipped = 0;
54094
+ if (!this.root(false)) return { templates, skipped, dir: this.dir };
54095
+ for (const entry of readdirSync4(this.dir).sort()) {
54096
+ if (!entry.endsWith(".json") || entry.startsWith(".")) continue;
54097
+ const name = entry.slice(0, -5);
54098
+ if (!TEMPLATE_SLUG.test(name) || isBuiltinTemplate(name)) {
54099
+ skipped++;
54100
+ continue;
54101
+ }
54102
+ try {
54103
+ const read = this.readText(this.file(name));
54104
+ if (!read) continue;
54105
+ const template = parseTemplate(read.text);
54106
+ templates.push({
54107
+ name,
54108
+ ...template.name && template.name !== name ? { displayName: template.name } : {},
54109
+ ...template.description ? { description: template.description } : {},
54110
+ ...template.extends ? { extends: template.extends } : {},
54111
+ bytes: read.bytes,
54112
+ modified: read.mtime.toISOString()
54113
+ });
54114
+ } catch {
54115
+ skipped++;
54116
+ }
54117
+ }
54118
+ return { templates, skipped, dir: this.dir };
54119
+ }
54120
+ /**
54121
+ * A saved template in its portable (as stored) form, or undefined when
54122
+ * there is none. Throws {@link TemplateValidationError} for a corrupt file.
54123
+ */
54124
+ find(name) {
54125
+ checkName(name, "read");
54126
+ if (!this.root(false)) return void 0;
54127
+ const read = this.readText(this.file(name));
54128
+ return read ? parseTemplate(read.text) : void 0;
54129
+ }
54130
+ /** Like {@link find}, but a missing template is an error. */
54131
+ get(name) {
54132
+ const template = this.find(name);
54133
+ if (!template)
54134
+ throw new TemplateStoreError(
54135
+ "template-not-found",
54136
+ `No saved template named "${name}". Built-in templates: ${BUILTIN_TEMPLATE_NAMES.join(", ")}.`
54137
+ );
54138
+ return template;
54139
+ }
54140
+ /**
54141
+ * Validate and save a template under `name`. Create-only unless `force`.
54142
+ * Returns the file path and whether an existing template was replaced.
54143
+ */
54144
+ save(name, text2, { force = false } = {}) {
54145
+ checkName(name, "save");
54146
+ const template = parseTemplate(text2);
54147
+ const body = JSON.stringify(template, null, 2) + "\n";
54148
+ this.root(true);
54149
+ const path10 = this.file(name);
54150
+ let exists = false;
54151
+ try {
54152
+ const stat = lstatSync5(path10);
54153
+ if (!stat.isFile())
54154
+ throw new TemplateStoreError(
54155
+ "unsafe-path",
54156
+ `Refusing to replace ${path10}: it is not a regular file.`
54157
+ );
54158
+ exists = true;
54159
+ } catch (error2) {
54160
+ if (error2 instanceof TemplateStoreError) throw error2;
54161
+ }
54162
+ if (exists && !force)
54163
+ throw new TemplateStoreError(
54164
+ "template-exists",
54165
+ `A template named "${name}" already exists. Pass force: true to replace it.`
54166
+ );
54167
+ const temp = join26(this.dir, `.${name}.${randomBytes(6).toString("hex")}.tmp`);
54168
+ const fd = openSync8(
54169
+ temp,
54170
+ constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
54171
+ 384
54172
+ );
54173
+ try {
54174
+ const data = Buffer.from(body, "utf8");
54175
+ let written = 0;
54176
+ while (written < data.length) written += writeSync5(fd, data, written);
54177
+ } finally {
54178
+ closeSync8(fd);
54179
+ }
54180
+ try {
54181
+ if (force) renameSync(temp, path10);
54182
+ else {
54183
+ try {
54184
+ linkSync(temp, path10);
54185
+ } catch (error2) {
54186
+ if (error2.code === "EEXIST")
54187
+ throw new TemplateStoreError(
54188
+ "template-exists",
54189
+ `A template named "${name}" already exists. Pass force: true to replace it.`
54190
+ );
54191
+ throw error2;
54192
+ }
54193
+ }
54194
+ } finally {
54195
+ try {
54196
+ unlinkSync3(temp);
54197
+ } catch {
54198
+ }
54199
+ }
54200
+ return { path: path10, replaced: exists, bytes: Buffer.byteLength(body), template };
54201
+ }
54202
+ /** Delete a saved template. Built-ins cannot be deleted. */
54203
+ delete(name) {
54204
+ checkName(name, "delete");
54205
+ const path10 = this.file(name);
54206
+ let stat;
54207
+ try {
54208
+ if (!this.root(false)) throw new Error("absent");
54209
+ stat = lstatSync5(path10);
54210
+ } catch (error2) {
54211
+ if (error2 instanceof TemplateStoreError) throw error2;
54212
+ throw new TemplateStoreError("template-not-found", `No saved template named "${name}".`);
54213
+ }
54214
+ if (!stat.isFile())
54215
+ throw new TemplateStoreError(
54216
+ "unsafe-path",
54217
+ `Refusing to delete ${path10}: not a regular file.`
54218
+ );
54219
+ unlinkSync3(path10);
54220
+ return { path: path10 };
54221
+ }
54222
+ };
54223
+
54224
+ // src/tools/markdownTemplates.ts
54225
+ var ok = (text2, structured) => ({
54226
+ content: [{ type: "text", text: text2 }],
54227
+ structuredContent: structured
54228
+ });
54229
+ function failure(action, error2) {
54230
+ if (error2 instanceof TemplateValidationError)
54231
+ return errorResult(
54232
+ `Error ${action} [invalid-template]: the template is invalid:
54233
+ ` + error2.errors.map((e) => `${e.path}: ${e.message}`).join("\n"),
54234
+ error2
54235
+ );
54236
+ if (error2 instanceof TemplateStoreError)
54237
+ return errorResult(`Error ${action} [${error2.code}]: ${error2.message}`, error2);
54238
+ const message = error2 instanceof Error ? error2.message : String(error2);
54239
+ return errorResult(`Error ${action}: ${message}`, error2);
54240
+ }
54241
+ var nameInput = external_exports.string().min(1).max(64).describe("Template name: a built-in (standard-markdown, obsidian) or a saved template's name");
54242
+ var templateInput = external_exports.union([external_exports.record(external_exports.unknown()), external_exports.string().max(MAX_TEMPLATE_BYTES)]).optional().describe("The template itself, as a JSON object or JSON text (schemaVersion 1)");
54243
+ var templateFileInput = external_exports.string().min(1).max(4096).optional().describe(
54244
+ "Absolute path of a JSON template file ending in .json (home, a temp dir, or /Volumes; symlinks refused; at most 256 KiB)"
54245
+ );
54246
+ function sourceText(args) {
54247
+ if (args.template === void 0 === (args.templateFile === void 0))
54248
+ throw new Error("Provide exactly one of 'template' or 'templateFile'.");
54249
+ if (args.templateFile !== void 0) return readTemplateFile(args.templateFile);
54250
+ return typeof args.template === "string" ? args.template : JSON.stringify(args.template);
54251
+ }
54252
+ function registerMarkdownTemplates(server2, store = () => new TemplateStore()) {
54253
+ const loose = external_exports.object({}).passthrough();
54254
+ server2.registerTool(
54255
+ "list-markdown-templates",
54256
+ {
54257
+ description: "Use when: choosing a Markdown export template, or checking which saved templates exist.\nReturns: the built-in templates, the saved templates (name, display name, description, size, modified date), the library directory, and how many unusable files were skipped.\nDo not use when: you need a template's rules (show-markdown-template).\nSafety: read-only; never opens Notes.",
54258
+ inputSchema: {},
54259
+ outputSchema: loose,
54260
+ annotations: { readOnlyHint: true }
54261
+ },
54262
+ (async () => {
54263
+ try {
54264
+ const listing = store().list();
54265
+ const builtins = BUILTIN_TEMPLATE_NAMES.map((name) => ({
54266
+ name,
54267
+ description: builtinTemplate(name).description ?? ""
54268
+ }));
54269
+ return ok(
54270
+ `${builtins.length} built-in and ${listing.templates.length} saved template(s)` + (listing.skipped ? `; skipped ${listing.skipped} unusable file(s)` : "") + ".",
54271
+ { builtins, ...listing }
54272
+ );
54273
+ } catch (error2) {
54274
+ return failure("listing templates", error2);
54275
+ }
54276
+ })
54277
+ );
54278
+ const showInput = {
54279
+ name: nameInput,
54280
+ expanded: external_exports.boolean().optional().describe("Also return the fully expanded template (every rule filled from its base)")
54281
+ };
54282
+ server2.registerTool(
54283
+ "show-markdown-template",
54284
+ {
54285
+ description: "Use when: reading a built-in or saved template, to copy it as the start of a new one or to see what an export will do.\nReturns: the template in its portable form (as saved, overrides only), its source, and with expanded: true every rule filled in.\nDo not use when: listing names (list-markdown-templates).\nSafety: read-only.",
54286
+ inputSchema: showInput,
54287
+ outputSchema: loose,
54288
+ annotations: { readOnlyHint: true }
54289
+ },
54290
+ (async ({ name, expanded }) => {
54291
+ try {
54292
+ const builtin = isBuiltinTemplate(name);
54293
+ const template = builtin ? builtinTemplate(name) : store().get(name);
54294
+ const structured = {
54295
+ name,
54296
+ source: builtin ? "builtin" : "saved",
54297
+ template,
54298
+ ...expanded ? { expanded: resolveTemplate(template, name) } : {}
54299
+ };
54300
+ return ok(JSON.stringify(template, null, 2), structured);
54301
+ } catch (error2) {
54302
+ return failure("showing template", error2);
54303
+ }
54304
+ })
54305
+ );
54306
+ const validateInput = {
54307
+ name: nameInput.optional(),
54308
+ template: templateInput,
54309
+ templateFile: templateFileInput
54310
+ };
54311
+ server2.registerTool(
54312
+ "validate-markdown-template",
54313
+ {
54314
+ description: 'Use when: checking a template before saving it or exporting with it.\nReturns: valid true, or valid false with every problem as {path, message}, where path is a JSON path such as $.rules["inline.bold"].after.\nDo not use when: you want to store it (save-markdown-template validates too).\nSafety: read-only. Pass exactly one of name (a saved or built-in template), template (JSON object or text) or templateFile.',
54315
+ inputSchema: validateInput,
54316
+ outputSchema: loose,
54317
+ annotations: { readOnlyHint: true }
54318
+ },
54319
+ (async (args) => {
54320
+ try {
54321
+ const given = [args.name, args.template, args.templateFile].filter(
54322
+ (value) => value !== void 0
54323
+ ).length;
54324
+ if (given !== 1)
54325
+ throw new Error("Provide exactly one of 'name', 'template' or 'templateFile'.");
54326
+ try {
54327
+ if (args.name !== void 0) {
54328
+ if (!isBuiltinTemplate(args.name)) store().get(args.name);
54329
+ } else parseTemplate(sourceText(args));
54330
+ } catch (error2) {
54331
+ if (!(error2 instanceof TemplateValidationError)) throw error2;
54332
+ return ok(
54333
+ `Invalid template (${error2.errors.length} problem(s)):
54334
+ ` + error2.errors.map((e) => `${e.path}: ${e.message}`).join("\n"),
54335
+ { valid: false, errors: error2.errors }
54336
+ );
54337
+ }
54338
+ return ok("The template is valid.", { valid: true, errors: [] });
54339
+ } catch (error2) {
54340
+ return failure("validating template", error2);
54341
+ }
54342
+ })
54343
+ );
54344
+ const saveInput = {
54345
+ name: external_exports.string().min(1).max(64).describe(
54346
+ 'Name to save under: lowercase a-z, 0-9, "-" and "_", starting with a letter or digit'
54347
+ ),
54348
+ template: templateInput,
54349
+ templateFile: templateFileInput,
54350
+ force: external_exports.boolean().optional().describe("Replace an existing saved template of this name (default false: create-only)")
54351
+ };
54352
+ server2.registerTool(
54353
+ "save-markdown-template",
54354
+ {
54355
+ description: "Use when: storing a template so export-notes-markdown can use it by name.\nReturns: name, path, bytes and whether an existing template was replaced.\nDo not use when: exporting once (pass templateFile to export-notes-markdown instead).\nSafety: writes one file in the template library only (APPLE_NOTES_MCP_TEMPLATE_DIR, default ~/Library/Application Support/apple-notes-mcp/templates, mode 0600). Validates first; an invalid template is refused with every JSON path. Create-only: an existing name is refused with [template-exists] unless force is true. Built-in names are reserved.",
54356
+ inputSchema: saveInput,
54357
+ outputSchema: loose,
54358
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
54359
+ },
54360
+ (async (args) => {
54361
+ try {
54362
+ const saved = store().save(args.name, sourceText(args), { force: args.force });
54363
+ return ok(
54364
+ `${saved.replaced ? "Replaced" : "Saved"} template "${args.name}" (${saved.bytes} bytes) at ${saved.path}.`,
54365
+ { name: args.name, path: saved.path, bytes: saved.bytes, replaced: saved.replaced }
54366
+ );
54367
+ } catch (error2) {
54368
+ return failure("saving template", error2);
54369
+ }
54370
+ })
54371
+ );
54372
+ const deleteInput = { name: external_exports.string().min(1).max(64).describe("Saved template to delete") };
54373
+ server2.registerTool(
54374
+ "delete-markdown-template",
54375
+ {
54376
+ description: "Use when: removing a saved Markdown template the user no longer wants.\nReturns: the name and the path of the removed file.\nDo not use when: the template is built in (standard-markdown, obsidian cannot be deleted).\nSafety: permanently removes one file from the template library; never touches Notes.",
54377
+ inputSchema: deleteInput,
54378
+ outputSchema: loose,
54379
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false }
54380
+ },
54381
+ (async ({ name }) => {
54382
+ try {
54383
+ const removed = store().delete(name);
54384
+ return ok(`Deleted template "${name}" (${removed.path}).`, {
54385
+ name,
54386
+ path: removed.path,
54387
+ deleted: true
54388
+ });
54389
+ } catch (error2) {
54390
+ return failure("deleting template", error2);
54391
+ }
54392
+ })
54393
+ );
54394
+ }
54395
+
53689
54396
  // src/utils/svgAnalyzer.ts
53690
54397
  import { createHash as createHash5 } from "node:crypto";
53691
- import { closeSync as closeSync7, constants as constants7, fstatSync as fstatSync7, openSync as openSync7, readSync as readSync5 } from "node:fs";
54398
+ import { closeSync as closeSync9, constants as constants9, fstatSync as fstatSync9, openSync as openSync9, readSync as readSync6 } from "node:fs";
53692
54399
 
53693
54400
  // src/utils/svgColor.ts
53694
54401
  var NAMED = {
@@ -54629,7 +55336,7 @@ function parseXml(source, limits) {
54629
55336
  if (a.name === "xmlns") scope2.set("", a.value);
54630
55337
  else if (a.name.startsWith("xmlns:")) scope2.set(a.name.slice(6), a.value);
54631
55338
  }
54632
- const resolve8 = (qualified, isAttribute) => {
55339
+ const resolve9 = (qualified, isAttribute) => {
54633
55340
  const colon = qualified.indexOf(":");
54634
55341
  if (colon < 0) return { local: qualified, ns: isAttribute ? null : scope2.get("") ?? null };
54635
55342
  const prefix = qualified.slice(0, colon);
@@ -54639,12 +55346,12 @@ function parseXml(source, limits) {
54639
55346
  if (ns === void 0) fail(`Undeclared namespace prefix "${prefix.slice(0, 40)}"`);
54640
55347
  return { local, ns };
54641
55348
  };
54642
- const resolved = resolve8(name, false);
55349
+ const resolved = resolve9(name, false);
54643
55350
  const element = {
54644
55351
  name,
54645
55352
  local: resolved.local,
54646
55353
  ns: resolved.ns,
54647
- attributes: rawAttributes.filter((a) => a.name !== "xmlns" && !a.name.startsWith("xmlns:")).map((a) => ({ name: a.name, value: a.value, ...resolve8(a.name, true) })),
55354
+ attributes: rawAttributes.filter((a) => a.name !== "xmlns" && !a.name.startsWith("xmlns:")).map((a) => ({ name: a.name, value: a.value, ...resolve9(a.name, true) })),
54648
55355
  children: [],
54649
55356
  text: "",
54650
55357
  line: tagLine
@@ -55657,14 +56364,14 @@ function analyzeSvgBuffer(source) {
55657
56364
  function readSvgSource(path10) {
55658
56365
  let fd;
55659
56366
  try {
55660
- fd = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW | constants7.O_NONBLOCK);
56367
+ fd = openSync9(path10, constants9.O_RDONLY | constants9.O_NOFOLLOW | constants9.O_NONBLOCK);
55661
56368
  } catch (error2) {
55662
56369
  if (error2.code === "ELOOP")
55663
56370
  throw new SvgError("svg_file_invalid", "The SVG path is a symbolic link");
55664
56371
  throw new SvgError("svg_file_invalid", "The SVG file does not exist or cannot be read");
55665
56372
  }
55666
56373
  try {
55667
- const info = fstatSync7(fd);
56374
+ const info = fstatSync9(fd);
55668
56375
  if (!info.isFile())
55669
56376
  throw new SvgError("svg_file_invalid", "The SVG path is not a regular file");
55670
56377
  if (info.size > SVG_LIMITS.maxSourceBytes)
@@ -55675,7 +56382,7 @@ function readSvgSource(path10) {
55675
56382
  const buffer = Buffer.alloc(SVG_LIMITS.maxSourceBytes + 1);
55676
56383
  let total = 0;
55677
56384
  for (; ; ) {
55678
- const n = readSync5(fd, buffer, total, buffer.length - total, null);
56385
+ const n = readSync6(fd, buffer, total, buffer.length - total, null);
55679
56386
  if (n === 0) break;
55680
56387
  total += n;
55681
56388
  if (total > SVG_LIMITS.maxSourceBytes)
@@ -55686,7 +56393,7 @@ function readSvgSource(path10) {
55686
56393
  }
55687
56394
  return buffer.subarray(0, total);
55688
56395
  } finally {
55689
- closeSync7(fd);
56396
+ closeSync9(fd);
55690
56397
  }
55691
56398
  }
55692
56399
  function analyzeSvgFile(path10) {
@@ -56257,7 +56964,7 @@ function registerNativeOperations(server2, manager) {
56257
56964
  import { spawnSync as spawnSync2 } from "node:child_process";
56258
56965
  import { existsSync as existsSync13 } from "node:fs";
56259
56966
  import { release as release2 } from "node:os";
56260
- import { dirname as dirname7, resolve as resolve5 } from "node:path";
56967
+ import { dirname as dirname7, resolve as resolve6 } from "node:path";
56261
56968
  import { fileURLToPath } from "node:url";
56262
56969
  var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
56263
56970
  var MARKDOWN_MIN_DARWIN_MAJOR = 25;
@@ -56280,11 +56987,11 @@ function setupShortcuts(checkOnly, dependencies = {}) {
56280
56987
  const result = spawnSync2("/usr/bin/open", [path10], { encoding: "utf8" });
56281
56988
  return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
56282
56989
  });
56283
- const baseDirectory = dependencies.baseDirectory || resolve5(dirname7(fileURLToPath(import.meta.url)), "../shortcuts");
56990
+ const baseDirectory = dependencies.baseDirectory || resolve6(dirname7(fileURLToPath(import.meta.url)), "../shortcuts");
56284
56991
  const osRelease = (dependencies.osRelease || release2)();
56285
56992
  const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
56286
56993
  const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
56287
- const path10 = resolve5(baseDirectory, file);
56994
+ const path10 = resolve6(baseDirectory, file);
56288
56995
  let installed = false;
56289
56996
  let identifier;
56290
56997
  let error2;
@@ -56348,15 +57055,15 @@ import { createHash as createHash6 } from "node:crypto";
56348
57055
  import {
56349
57056
  chmodSync,
56350
57057
  existsSync as existsSync14,
56351
- mkdirSync as mkdirSync7,
56352
- mkdtempSync as mkdtempSync6,
56353
- readFileSync as readFileSync4,
56354
- renameSync,
56355
- rmSync as rmSync6,
56356
- writeFileSync as writeFileSync5
57058
+ mkdirSync as mkdirSync8,
57059
+ mkdtempSync as mkdtempSync7,
57060
+ readFileSync as readFileSync5,
57061
+ renameSync as renameSync2,
57062
+ rmSync as rmSync7,
57063
+ writeFileSync as writeFileSync6
56357
57064
  } from "node:fs";
56358
- import { homedir as homedir19, release as release3 } from "node:os";
56359
- import { dirname as dirname8, join as join25, resolve as resolve6 } from "node:path";
57065
+ import { homedir as homedir20, release as release3 } from "node:os";
57066
+ import { dirname as dirname8, join as join27, resolve as resolve7 } from "node:path";
56360
57067
  import { fileURLToPath as fileURLToPath2 } from "node:url";
56361
57068
  var PUBLIC_HELPER_PROTOCOL = 1;
56362
57069
  var PUBLIC_HELPER_DIR_ENV = "APPLE_NOTES_MCP_PUBLIC_HELPER_DIR";
@@ -56384,16 +57091,16 @@ var publicManifestSchema = external_exports.object({
56384
57091
  function packageRoot(fromDir = dirname8(fileURLToPath2(import.meta.url))) {
56385
57092
  let dir = fromDir;
56386
57093
  for (; ; ) {
56387
- const candidate = join25(dir, "package.json");
57094
+ const candidate = join27(dir, "package.json");
56388
57095
  if (existsSync14(candidate)) {
56389
57096
  try {
56390
- if (JSON.parse(readFileSync4(candidate, "utf8")).name === "apple-notes-mcp")
57097
+ if (JSON.parse(readFileSync5(candidate, "utf8")).name === "apple-notes-mcp")
56391
57098
  return dir;
56392
57099
  } catch {
56393
57100
  }
56394
57101
  }
56395
57102
  const parent = dirname8(dir);
56396
- if (parent === dir) return resolve6(fromDir, "..");
57103
+ if (parent === dir) return resolve7(fromDir, "..");
56397
57104
  dir = parent;
56398
57105
  }
56399
57106
  }
@@ -56401,9 +57108,9 @@ function defaultPublicHelperDeps(overrides = {}) {
56401
57108
  return {
56402
57109
  env: process.env,
56403
57110
  platform: process.platform,
56404
- sourcePath: join25(packageRoot(), PUBLIC_HELPER_SOURCE),
57111
+ sourcePath: join27(packageRoot(), PUBLIC_HELPER_SOURCE),
56405
57112
  exists: existsSync14,
56406
- readFile: (path10) => readFileSync4(path10),
57113
+ readFile: (path10) => readFileSync5(path10),
56407
57114
  spawn: spawnSync3,
56408
57115
  spawnAsync: spawn,
56409
57116
  ...overrides
@@ -56412,14 +57119,14 @@ function defaultPublicHelperDeps(overrides = {}) {
56412
57119
  function publicHelperInstallDir(env = process.env) {
56413
57120
  const override = env[PUBLIC_HELPER_DIR_ENV]?.trim();
56414
57121
  if (override) return override;
56415
- return join25(homedir19(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
57122
+ return join27(homedir20(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
56416
57123
  }
56417
57124
  function sha256Hex(data) {
56418
57125
  return createHash6("sha256").update(data).digest("hex");
56419
57126
  }
56420
57127
  function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
56421
57128
  const installDir = publicHelperInstallDir(deps.env);
56422
- const binaryPath = join25(installDir, PUBLIC_HELPER_BINARY);
57129
+ const binaryPath = join27(installDir, PUBLIC_HELPER_BINARY);
56423
57130
  const base = { installDir, binaryPath, sourcePath: deps.sourcePath, manifest: null };
56424
57131
  const fail = (reason, detail) => ({
56425
57132
  ...base,
@@ -56430,7 +57137,7 @@ function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
56430
57137
  if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
56431
57138
  if (!deps.exists(deps.sourcePath))
56432
57139
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
56433
- const manifestPath = join25(installDir, PUBLIC_HELPER_MANIFEST);
57140
+ const manifestPath = join27(installDir, PUBLIC_HELPER_MANIFEST);
56434
57141
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
56435
57142
  return fail(
56436
57143
  "helper_not_installed",
@@ -56470,14 +57177,14 @@ var UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
56470
57177
  "helper_modified",
56471
57178
  "helper_manifest_invalid"
56472
57179
  ]);
56473
- var ENVELOPE_CODES = {
57180
+ var ENVELOPE_CODES2 = {
56474
57181
  invalid_request: "validation_error",
56475
57182
  attachment_not_found: "not_found"
56476
57183
  };
56477
57184
  var PublicHelperError = class extends CodedError {
56478
57185
  constructor(code, message) {
56479
57186
  super(message, {
56480
- code: UNAVAILABLE_CODES.has(code) ? "unsupported" : ENVELOPE_CODES[code] ?? "operation_failed",
57187
+ code: UNAVAILABLE_CODES.has(code) ? "unsupported" : ENVELOPE_CODES2[code] ?? "operation_failed",
56481
57188
  helperCode: code
56482
57189
  });
56483
57190
  this.code = code;
@@ -56522,9 +57229,9 @@ async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHe
56522
57229
  });
56523
57230
  const chunks = [];
56524
57231
  let size = 0;
56525
- let failure = null;
57232
+ let failure2 = null;
56526
57233
  const stop = (error2) => {
56527
- failure ??= error2;
57234
+ failure2 ??= error2;
56528
57235
  child2.kill("SIGKILL");
56529
57236
  };
56530
57237
  const timer = setTimeout(
@@ -56546,12 +57253,12 @@ async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHe
56546
57253
  child2.on("error", (error2) => {
56547
57254
  settle();
56548
57255
  reject(
56549
- failure ?? new PublicHelperError("helper_unreachable", `Could not run the helper: ${error2.message}`)
57256
+ failure2 ?? new PublicHelperError("helper_unreachable", `Could not run the helper: ${error2.message}`)
56550
57257
  );
56551
57258
  });
56552
57259
  child2.on("close", (status, exitSignal) => {
56553
57260
  settle();
56554
- if (failure) return reject(failure);
57261
+ if (failure2) return reject(failure2);
56555
57262
  if (status === null && exitSignal)
56556
57263
  return reject(
56557
57264
  new PublicHelperError("helper_crashed", `The helper stopped on signal ${exitSignal}.`)
@@ -56669,8 +57376,8 @@ function publicHelperCompileArguments(sourcePath, digestPath, plistPath, outputP
56669
57376
  }
56670
57377
  function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
56671
57378
  const steps = [];
56672
- const done = (ok) => ({
56673
- ok,
57379
+ const done = (ok2) => ({
57380
+ ok: ok2,
56674
57381
  checkOnly,
56675
57382
  steps,
56676
57383
  installation: inspectPublicHelper(deps)
@@ -56706,14 +57413,14 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
56706
57413
  const compiler = String(version3.stdout || version3.stderr || "").split("\n").find((line) => line.includes("Swift version"))?.trim() || "swiftc";
56707
57414
  steps.push({ step: "find compiler", ok: true, detail: compiler });
56708
57415
  const installDir = publicHelperInstallDir(deps.env);
56709
- mkdirSync7(installDir, { recursive: true, mode: 448 });
56710
- const staging = mkdtempSync6(join25(installDir, ".staging-"));
57416
+ mkdirSync8(installDir, { recursive: true, mode: 448 });
57417
+ const staging = mkdtempSync7(join27(installDir, ".staging-"));
56711
57418
  try {
56712
- const stagedBinary = join25(staging, PUBLIC_HELPER_BINARY);
56713
- const digestPath = join25(staging, "source-digest.swift");
56714
- const plistPath = join25(staging, "Info.plist");
56715
- writeFileSync5(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
56716
- 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 });
56717
57424
  const compile = deps.spawn(
56718
57425
  "/usr/bin/xcrun",
56719
57426
  publicHelperCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
@@ -56774,15 +57481,15 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
56774
57481
  compiler
56775
57482
  };
56776
57483
  chmodSync(stagedBinary, 448);
56777
- renameSync(stagedBinary, join25(installDir, PUBLIC_HELPER_BINARY));
56778
- writeFileSync5(
56779
- join25(installDir, PUBLIC_HELPER_MANIFEST),
57484
+ renameSync2(stagedBinary, join27(installDir, PUBLIC_HELPER_BINARY));
57485
+ writeFileSync6(
57486
+ join27(installDir, PUBLIC_HELPER_MANIFEST),
56780
57487
  JSON.stringify(manifest, null, 2) + "\n",
56781
57488
  { mode: 384 }
56782
57489
  );
56783
57490
  steps.push({ step: "install", ok: true, detail: installDir });
56784
57491
  } finally {
56785
- rmSync6(staging, { recursive: true, force: true });
57492
+ rmSync7(staging, { recursive: true, force: true });
56786
57493
  }
56787
57494
  const installation = inspectPublicHelper(deps);
56788
57495
  steps.push({
@@ -56999,11 +57706,11 @@ function getNoteDrawings(noteId3, options = {}) {
56999
57706
  if (!install.ready)
57000
57707
  throw new PublicHelperError(install.reason ?? "helper_not_installed", install.detail ?? "");
57001
57708
  const drawings = rows.map((row) => decodeRow(row, format, includePoints, deps));
57002
- const ok = drawings.filter((d) => d.status === "ok").length;
57709
+ const ok2 = drawings.filter((d) => d.status === "ok").length;
57003
57710
  return {
57004
57711
  id: noteId3,
57005
57712
  drawingCount: drawings.length,
57006
- status: ok === drawings.length ? "ok" : ok ? "partial" : "error",
57713
+ status: ok2 === drawings.length ? "ok" : ok2 ? "partial" : "error",
57007
57714
  drawings
57008
57715
  };
57009
57716
  }
@@ -57020,8 +57727,8 @@ function formatNoteDrawings(result) {
57020
57727
  }
57021
57728
 
57022
57729
  // src/utils/noteAudio.ts
57023
- import { readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
57024
- import { join as join26 } from "node:path";
57730
+ import { readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
57731
+ import { join as join28 } from "node:path";
57025
57732
  var EXTRA_AUDIO_UTIS = [
57026
57733
  "public.mp3",
57027
57734
  "public.aiff-audio",
@@ -57046,7 +57753,7 @@ function accountDirsFor(containerDir, accountIdentifier) {
57046
57753
  if (own) return [own];
57047
57754
  let names;
57048
57755
  try {
57049
- names = readdirSync4(join26(containerDir, "Accounts")).sort();
57756
+ names = readdirSync5(join28(containerDir, "Accounts")).sort();
57050
57757
  } catch {
57051
57758
  return [];
57052
57759
  }
@@ -57058,8 +57765,8 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
57058
57765
  if (!id2 || !filename) return null;
57059
57766
  const generation = safeComponent(media?.generation);
57060
57767
  for (const dir of accountDirsFor(containerDir, accountIdentifier)) {
57061
- const base = join26(dir, "Media", id2);
57062
- const candidates = generation ? [join26(base, generation, filename), join26(base, filename)] : [join26(base, filename)];
57768
+ const base = join28(dir, "Media", id2);
57769
+ const candidates = generation ? [join28(base, generation, filename), join28(base, filename)] : [join28(base, filename)];
57063
57770
  for (const candidate of candidates) {
57064
57771
  const real = realInside(candidate, dir);
57065
57772
  if (real && isFile2(real)) return real;
@@ -57069,7 +57776,7 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
57069
57776
  }
57070
57777
  function isFile2(path10) {
57071
57778
  try {
57072
- return statSync4(path10).isFile();
57779
+ return statSync5(path10).isFile();
57073
57780
  } catch {
57074
57781
  return false;
57075
57782
  }
@@ -57331,21 +58038,21 @@ import { spawnSync as spawnSync5 } from "node:child_process";
57331
58038
  import {
57332
58039
  chmodSync as chmodSync2,
57333
58040
  existsSync as existsSync16,
57334
- mkdirSync as mkdirSync8,
57335
- mkdtempSync as mkdtempSync7,
57336
- renameSync as renameSync2,
57337
- rmSync as rmSync7,
57338
- writeFileSync as writeFileSync6
58041
+ mkdirSync as mkdirSync9,
58042
+ mkdtempSync as mkdtempSync8,
58043
+ renameSync as renameSync3,
58044
+ rmSync as rmSync8,
58045
+ writeFileSync as writeFileSync7
57339
58046
  } from "node:fs";
57340
58047
  import { release as release4 } from "node:os";
57341
- import { join as join28 } from "node:path";
58048
+ import { join as join30 } from "node:path";
57342
58049
 
57343
58050
  // src/services/privateHelper.ts
57344
58051
  import { spawnSync as spawnSync4 } from "node:child_process";
57345
58052
  import { createHash as createHash7 } from "node:crypto";
57346
- import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
57347
- import { homedir as homedir20 } from "node:os";
57348
- import { dirname as dirname9, join as join27, resolve as resolve7 } from "node:path";
58053
+ import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
58054
+ import { homedir as homedir21 } from "node:os";
58055
+ import { dirname as dirname9, join as join29, resolve as resolve8 } from "node:path";
57349
58056
  import { fileURLToPath as fileURLToPath3 } from "node:url";
57350
58057
  var PRIVATE_HELPER_PROTOCOL = 1;
57351
58058
  var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
@@ -57373,16 +58080,16 @@ var manifestSchema = external_exports.object({
57373
58080
  function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
57374
58081
  let dir = fromDir;
57375
58082
  for (; ; ) {
57376
- const candidate = join27(dir, "package.json");
58083
+ const candidate = join29(dir, "package.json");
57377
58084
  if (existsSync15(candidate)) {
57378
58085
  try {
57379
- const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
58086
+ const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
57380
58087
  if (pkg.name === "apple-notes-mcp") return dir;
57381
58088
  } catch {
57382
58089
  }
57383
58090
  }
57384
58091
  const parent = dirname9(dir);
57385
- if (parent === dir) return resolve7(fromDir, "..");
58092
+ if (parent === dir) return resolve8(fromDir, "..");
57386
58093
  dir = parent;
57387
58094
  }
57388
58095
  }
@@ -57390,9 +58097,9 @@ function defaultDeps2(overrides = {}) {
57390
58097
  return {
57391
58098
  env: process.env,
57392
58099
  platform: process.platform,
57393
- sourcePath: join27(packageRoot2(), HELPER_SOURCE_RELATIVE),
58100
+ sourcePath: join29(packageRoot2(), HELPER_SOURCE_RELATIVE),
57394
58101
  exists: existsSync15,
57395
- readFile: (path10) => readFileSync5(path10),
58102
+ readFile: (path10) => readFileSync6(path10),
57396
58103
  spawn: spawnSync4,
57397
58104
  ...overrides
57398
58105
  };
@@ -57403,14 +58110,14 @@ function privateHelperEnabled(env = process.env) {
57403
58110
  function helperInstallDir(env = process.env) {
57404
58111
  const override = env[HELPER_DIR_ENV]?.trim();
57405
58112
  if (override) return override;
57406
- return join27(homedir20(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
58113
+ return join29(homedir21(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
57407
58114
  }
57408
58115
  function sha256Hex2(data) {
57409
58116
  return createHash7("sha256").update(data).digest("hex");
57410
58117
  }
57411
58118
  function inspectInstallation(deps = defaultDeps2()) {
57412
58119
  const installDir = helperInstallDir(deps.env);
57413
- const binaryPath = join27(installDir, HELPER_BINARY_NAME);
58120
+ const binaryPath = join29(installDir, HELPER_BINARY_NAME);
57414
58121
  const base = {
57415
58122
  installDir,
57416
58123
  binaryPath,
@@ -57428,7 +58135,7 @@ function inspectInstallation(deps = defaultDeps2()) {
57428
58135
  if (!deps.exists(deps.sourcePath))
57429
58136
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
57430
58137
  base.expectedSourceSha256 = sha256Hex2(deps.readFile(deps.sourcePath));
57431
- const manifestPath = join27(installDir, MANIFEST_NAME);
58138
+ const manifestPath = join29(installDir, MANIFEST_NAME);
57432
58139
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
57433
58140
  return fail(
57434
58141
  "helper_not_installed",
@@ -57699,8 +58406,8 @@ function compileArguments(sourcePath, outputPath, sourceSha) {
57699
58406
  }
57700
58407
  function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
57701
58408
  const steps = [];
57702
- const done = (ok) => ({
57703
- ok,
58409
+ const done = (ok2) => ({
58410
+ ok: ok2,
57704
58411
  checkOnly,
57705
58412
  steps,
57706
58413
  installation: inspectInstallation(deps)
@@ -57742,10 +58449,10 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
57742
58449
  const compiler = String(clangVersion.stdout || "").split("\n")[0] || "clang";
57743
58450
  steps.push({ step: "find compiler", ok: true, detail: compiler });
57744
58451
  const installDir = helperInstallDir(deps.env);
57745
- mkdirSync8(installDir, { recursive: true, mode: 448 });
57746
- const staging = mkdtempSync7(join28(installDir, ".staging-"));
58452
+ mkdirSync9(installDir, { recursive: true, mode: 448 });
58453
+ const staging = mkdtempSync8(join30(installDir, ".staging-"));
57747
58454
  try {
57748
- const stagedBinary = join28(staging, HELPER_BINARY_NAME);
58455
+ const stagedBinary = join30(staging, HELPER_BINARY_NAME);
57749
58456
  const compile = deps.spawn(
57750
58457
  "/usr/bin/xcrun",
57751
58458
  compileArguments(deps.sourcePath, stagedBinary, sourceSha),
@@ -57822,13 +58529,13 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
57822
58529
  compiler
57823
58530
  };
57824
58531
  chmodSync2(stagedBinary, 448);
57825
- renameSync2(stagedBinary, join28(installDir, HELPER_BINARY_NAME));
57826
- writeFileSync6(join28(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", {
57827
58534
  mode: 384
57828
58535
  });
57829
58536
  steps.push({ step: "install", ok: true, detail: installDir });
57830
58537
  } finally {
57831
- if (existsSync16(staging)) rmSync7(staging, { recursive: true, force: true });
58538
+ if (existsSync16(staging)) rmSync8(staging, { recursive: true, force: true });
57832
58539
  }
57833
58540
  const installation = inspectInstallation(deps);
57834
58541
  steps.push({
@@ -57991,6 +58698,7 @@ var server = new McpServer({
57991
58698
  var notesManager = new AppleNotesManager();
57992
58699
  registerDirectOperations(server, notesManager);
57993
58700
  registerFolderDelete(server, notesManager);
58701
+ registerMarkdownTemplates(server);
57994
58702
  registerSvgAnalysis(server);
57995
58703
  registerNativeTagsBridge(server, notesManager);
57996
58704
  registerNativeOperations(server, notesManager);
@@ -60808,7 +61516,7 @@ registerTool(
60808
61516
  assetsDir: exportPathInput("Directory that receives copies of attachment files"),
60809
61517
  wrap: external_exports.number().int().min(0).max(1e3).optional().describe("Hard-wrap prose at this many columns (0 or omitted: no wrapping)"),
60810
61518
  template: external_exports.string().min(1).max(64).optional().describe(
60811
- `Render through this template: built-in ${BUILTIN_TEMPLATE_NAMES.map((n) => `'${n}'`).join(" or ")}. Exclusive with templateFile`
61519
+ `Render through this template: built-in ${BUILTIN_TEMPLATE_NAMES.map((n) => `'${n}'`).join(" or ")}, or a saved template's name (list-markdown-templates). Exclusive with templateFile`
60812
61520
  ),
60813
61521
  templateFile: exportPathInput(
60814
61522
  "JSON template file (.json) to render through (exclusive with template; at most 256 KiB)"
@@ -60837,6 +61545,7 @@ registerTool(
60837
61545
  try {
60838
61546
  receipt = exportNotesMarkdown(request, {
60839
61547
  listNoteRefs: (account, folder, since, limit) => notesManager.listNoteRefs(account, folder, since, limit),
61548
+ findTemplate: (name) => TEMPLATE_SLUG.test(name) ? new TemplateStore().find(name) : void 0,
60840
61549
  // The document travels twice (text and structuredContent).
60841
61550
  maxInlineBytes: Math.floor(exportMaxResponseBytes() / 2) - 64 * 1024
60842
61551
  });