apple-notes-mcp 2.14.3 → 2.15.1

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
@@ -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 = resolve10.call(this, root, ref);
2994
+ let _sch = resolve11.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 resolve10(root, ref) {
3021
+ function resolve11(root, ref) {
3022
3022
  let sch;
3023
3023
  while (typeof (sch = this.refs[ref]) == "string")
3024
3024
  ref = sch;
@@ -3848,7 +3848,7 @@ var require_fast_uri = __commonJS({
3848
3848
  }
3849
3849
  return uri;
3850
3850
  }
3851
- function resolve10(baseURI, relativeURI, options) {
3851
+ function resolve11(baseURI, relativeURI, options) {
3852
3852
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3853
3853
  const {
3854
3854
  parsed: baseParsed,
@@ -4217,7 +4217,7 @@ var require_fast_uri = __commonJS({
4217
4217
  var fastUri = {
4218
4218
  SCHEMES,
4219
4219
  normalize,
4220
- resolve: resolve10,
4220
+ resolve: resolve11,
4221
4221
  resolveComponent,
4222
4222
  equal,
4223
4223
  serialize,
@@ -24477,14 +24477,14 @@ var require_turndown_cjs = __commonJS({
24477
24477
  } else if (node.nodeType === 1) {
24478
24478
  replacement = replacementForNode.call(self, node);
24479
24479
  }
24480
- return join33(output, replacement);
24480
+ return join35(output, replacement);
24481
24481
  }, "");
24482
24482
  }
24483
24483
  function postProcess(output) {
24484
24484
  var self = this;
24485
24485
  this.rules.forEach(function(rule) {
24486
24486
  if (typeof rule.append === "function") {
24487
- output = join33(output, rule.append(self.options));
24487
+ output = join35(output, rule.append(self.options));
24488
24488
  }
24489
24489
  });
24490
24490
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -24496,7 +24496,7 @@ var require_turndown_cjs = __commonJS({
24496
24496
  if (whitespace.leading || whitespace.trailing) content = content.trim();
24497
24497
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
24498
24498
  }
24499
- function join33(output, replacement) {
24499
+ function join35(output, replacement) {
24500
24500
  var s1 = trimTrailingNewlines(output);
24501
24501
  var s2 = trimLeadingNewlines(replacement);
24502
24502
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -36620,7 +36620,7 @@ var Protocol = class {
36620
36620
  return;
36621
36621
  }
36622
36622
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
36623
- await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
36623
+ await new Promise((resolve11) => setTimeout(resolve11, pollInterval));
36624
36624
  options?.signal?.throwIfAborted();
36625
36625
  }
36626
36626
  } catch (error2) {
@@ -36637,7 +36637,7 @@ var Protocol = class {
36637
36637
  */
36638
36638
  request(request, resultSchema, options) {
36639
36639
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
36640
- return new Promise((resolve10, reject) => {
36640
+ return new Promise((resolve11, reject) => {
36641
36641
  const earlyReject = (error2) => {
36642
36642
  reject(error2);
36643
36643
  };
@@ -36715,7 +36715,7 @@ var Protocol = class {
36715
36715
  if (!parseResult.success) {
36716
36716
  reject(parseResult.error);
36717
36717
  } else {
36718
- resolve10(parseResult.data);
36718
+ resolve11(parseResult.data);
36719
36719
  }
36720
36720
  } catch (error2) {
36721
36721
  reject(error2);
@@ -36976,12 +36976,12 @@ var Protocol = class {
36976
36976
  }
36977
36977
  } catch {
36978
36978
  }
36979
- return new Promise((resolve10, reject) => {
36979
+ return new Promise((resolve11, reject) => {
36980
36980
  if (signal.aborted) {
36981
36981
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
36982
36982
  return;
36983
36983
  }
36984
- const timeoutId = setTimeout(resolve10, interval);
36984
+ const timeoutId = setTimeout(resolve11, interval);
36985
36985
  signal.addEventListener("abort", () => {
36986
36986
  clearTimeout(timeoutId);
36987
36987
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -38294,7 +38294,7 @@ var McpServer = class {
38294
38294
  let task = createTaskResult.task;
38295
38295
  const pollInterval = task.pollInterval ?? 5e3;
38296
38296
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
38297
- await new Promise((resolve10) => setTimeout(resolve10, pollInterval));
38297
+ await new Promise((resolve11) => setTimeout(resolve11, pollInterval));
38298
38298
  const updatedTask = await extra.taskStore.getTask(taskId);
38299
38299
  if (!updatedTask) {
38300
38300
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -38982,12 +38982,12 @@ var StdioServerTransport = class {
38982
38982
  this.onclose?.();
38983
38983
  }
38984
38984
  send(message) {
38985
- return new Promise((resolve10) => {
38985
+ return new Promise((resolve11) => {
38986
38986
  const json2 = serializeMessage(message);
38987
38987
  if (this._stdout.write(json2)) {
38988
- resolve10();
38988
+ resolve11();
38989
38989
  } else {
38990
- this._stdout.once("drain", resolve10);
38990
+ this._stdout.once("drain", resolve11);
38991
38991
  }
38992
38992
  });
38993
38993
  }
@@ -39448,6 +39448,16 @@ function fixed32Float(field) {
39448
39448
  return new DataView(bytes.buffer, bytes.byteOffset, 4).getFloat32(0, true);
39449
39449
  }
39450
39450
 
39451
+ // src/utils/fullDiskAccess.ts
39452
+ import { homedir } from "node:os";
39453
+ import { join } from "node:path";
39454
+ function fdaRemediation(execPath = process.execPath, env = process.env) {
39455
+ if (env.APPLE_NOTES_MCP_BROKERED === "1")
39456
+ return `This server runs under the permission broker, so the grant belongs to the broker app, not to Node or the MCP host. In System Settings > Privacy & Security > Full Disk Access, click + and add ${env.APPLE_NOTES_MCP_BROKER_APP || join(homedir(), "Applications", "Apple Notes MCP Broker.app")} (press Cmd+Shift+G in the file picker to paste the path), turn it on, and re-run doctor. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
39457
+ const versioned = /\/(\.nvm|\.fnm|\.volta|\.asdf|\.local\/share\/mise|\.nodenv|n\/versions)\//.test(execPath);
39458
+ return `In System Settings > Privacy & Security > Full Disk Access, click + and add the Node binary running this server: ${execPath} (press Cmd+Shift+G in the file picker to paste the path). Under Claude Desktop that entry is required: Claude Desktop launches servers as their own responsible process, so a grant on Claude.app does not reach them. When the server runs from a terminal (Terminal, iTerm2) or an editor, granting that app is enough. Then fully quit (Cmd+Q) and relaunch the host app and re-run doctor; if it still reports not granted, restart the Mac. ` + (versioned ? "This Node lives under a version manager, so the path changes with each Node version and the grant has to be added again after switching; pointing the MCP config at one fixed Node path avoids that. " : "") + `Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
39459
+ }
39460
+
39451
39461
  // src/utils/checklistRuns.ts
39452
39462
  function checklistRunLineStart(text2, position, length) {
39453
39463
  const end = position + length;
@@ -39575,7 +39585,7 @@ function getChecklistItems(noteId3) {
39575
39585
  return {
39576
39586
  items: null,
39577
39587
  error: "no_fda",
39578
- message: `Full Disk Access is required to read checklist state. In System Settings > Privacy & Security > Full Disk Access, grant access to the Node binary running this server (required under Claude Desktop) or the terminal that launches it, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`
39588
+ message: `Full Disk Access is required to read checklist state. ${fdaRemediation()}`
39579
39589
  };
39580
39590
  }
39581
39591
  if (!hexData) {
@@ -39641,7 +39651,7 @@ var ENTITY_LABEL = {
39641
39651
  ICFolder: "folder",
39642
39652
  ICAccount: "account"
39643
39653
  };
39644
- var NO_FDA_MESSAGE = `Resolving a Notes UUID or numeric key reads the Notes database, which needs Full Disk Access for the Node binary running this server, or the terminal that launches it (System Settings > Privacy & Security > Full Disk Access, then fully quit and relaunch it). x-coredata ids from search-notes, list-notes, or list-folders work without it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
39654
+ var NO_FDA_MESSAGE = "Resolving a Notes UUID or numeric key reads the Notes database, which needs Full Disk Access. x-coredata ids from search-notes, list-notes, or list-folders work without it.";
39645
39655
  function identifierFailureIn(message) {
39646
39656
  if (message.includes(NO_FDA_MESSAGE)) return "no_fda";
39647
39657
  if (/\bNo (?:note|folder|account) found for (?:numeric key|identifier) /.test(message))
@@ -39700,7 +39710,8 @@ function buildLookupSql(entity3, keys) {
39700
39710
  return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object(${fields})) FROM ZICCLOUDSYNCINGOBJECT o ${joins}WHERE o.Z_ENT = ${entityClause(entity3)} AND o.Z_PK IN (${inList})));`;
39701
39711
  }
39702
39712
  function runJsonQuery(sql, dbPath2) {
39703
- if (!fs2.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
39713
+ if (!fs2.existsSync(dbPath2))
39714
+ throw new IdentifierResolutionError("no_fda", `${NO_FDA_MESSAGE} ${fdaRemediation()}`);
39704
39715
  let out;
39705
39716
  try {
39706
39717
  out = execFileSync3("sqlite3", ["-readonly", dbPath2, sql], {
@@ -39711,7 +39722,7 @@ function runJsonQuery(sql, dbPath2) {
39711
39722
  } catch (error2) {
39712
39723
  const detail = error2 instanceof Error ? `${error2.message} ${String(error2.stderr ?? "")}` : String(error2);
39713
39724
  if (detail.includes("authorization denied") || detail.includes("unable to open database")) {
39714
- throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
39725
+ throw new IdentifierResolutionError("no_fda", `${NO_FDA_MESSAGE} ${fdaRemediation()}`);
39715
39726
  }
39716
39727
  console.error(`Identifier query failed: ${detail}`);
39717
39728
  throw new IdentifierResolutionError("query_error", "Failed to read the Notes database.");
@@ -39996,8 +40007,8 @@ function strippedImagesWarning(stripped) {
39996
40007
  // src/utils/noteRichText.ts
39997
40008
  import { execFileSync as execFileSync4 } from "node:child_process";
39998
40009
  import { createHash } from "node:crypto";
39999
- import { homedir as homedir3 } from "node:os";
40000
- import { join as join3 } from "node:path";
40010
+ import { homedir as homedir4 } from "node:os";
40011
+ import { join as join4 } from "node:path";
40001
40012
  import { gunzipSync as gunzipSync2 } from "node:zlib";
40002
40013
 
40003
40014
  // src/utils/uniqueById.ts
@@ -40331,7 +40342,7 @@ var HTML_LOSSY_ORDER = [
40331
40342
  "alignment",
40332
40343
  "highlight"
40333
40344
  ];
40334
- var dbPath = join3(homedir3(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
40345
+ var dbPath = join4(homedir4(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
40335
40346
  var safeUrl = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
40336
40347
  var escapeAttribute = (text2) => text2.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
40337
40348
  var normalized = (text2) => text2.replace(/[\s\ufffc]/gu, "");
@@ -40662,11 +40673,11 @@ function assertLinkedWrite(rich, content, format, allowLinkChanges = false) {
40662
40673
 
40663
40674
  // src/utils/audioTranscripts.ts
40664
40675
  import { execFileSync as execFileSync5 } from "node:child_process";
40665
- import { homedir as homedir4 } from "node:os";
40666
- import { join as join4 } from "node:path";
40676
+ import { homedir as homedir5 } from "node:os";
40677
+ import { join as join5 } from "node:path";
40667
40678
  import { gunzipSync as gunzipSync3 } from "node:zlib";
40668
- var NOTES_DB_PATH3 = join4(
40669
- homedir4(),
40679
+ var NOTES_DB_PATH3 = join5(
40680
+ homedir5(),
40670
40681
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
40671
40682
  );
40672
40683
  var DEFAULT_MAX_SEGMENTS = 2e3;
@@ -40896,7 +40907,7 @@ function readAudioTranscripts(noteId3, options = {}) {
40896
40907
  if (/authorization denied|unable to open database/i.test(message))
40897
40908
  throw new AudioTranscriptError(
40898
40909
  "no_fda",
40899
- `Full Disk Access is required to read stored transcripts. Grant it to the Node binary running this server (or the terminal that launches it), then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`
40910
+ `Full Disk Access is required to read stored transcripts. ${fdaRemediation()}`
40900
40911
  );
40901
40912
  throw new AudioTranscriptError("query_error", "Failed to read the Notes database.");
40902
40913
  }
@@ -41213,7 +41224,7 @@ var NOTES_DB_PATH4 = path3.join(
41213
41224
  os3.homedir(),
41214
41225
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
41215
41226
  );
41216
- var FDA_MESSAGE = `Full Disk Access is required to read smart folders. In System Settings > Privacy & Security > Full Disk Access, grant access to the Node binary running this server (required under Claude Desktop) or the terminal that launches it, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
41227
+ var FDA_MESSAGE = "Full Disk Access is required to read smart folders.";
41217
41228
  var COCOA_EPOCH_OFFSET = 978307200;
41218
41229
  var MAX_DEPTH = 32;
41219
41230
  var RELATIVE_RANGES = {
@@ -41563,7 +41574,8 @@ function runSqlite2(dbPath2, sql) {
41563
41574
  });
41564
41575
  }
41565
41576
  function readSmartFolders(dbPath2 = NOTES_DB_PATH4) {
41566
- if (!fs3.existsSync(dbPath2)) return { folders: null, error: "no_fda", message: FDA_MESSAGE };
41577
+ if (!fs3.existsSync(dbPath2))
41578
+ return { folders: null, error: "no_fda", message: `${FDA_MESSAGE} ${fdaRemediation()}` };
41567
41579
  try {
41568
41580
  const columns = new Set(
41569
41581
  runSqlite2(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").trim().split("\n")
@@ -41580,7 +41592,7 @@ function readSmartFolders(dbPath2 = NOTES_DB_PATH4) {
41580
41592
  } catch (error2) {
41581
41593
  const message = error2 instanceof Error ? error2.message : String(error2);
41582
41594
  if (message.includes("authorization denied") || message.includes("unable to open database")) {
41583
- return { folders: null, error: "no_fda", message: FDA_MESSAGE };
41595
+ return { folders: null, error: "no_fda", message: `${FDA_MESSAGE} ${fdaRemediation()}` };
41584
41596
  }
41585
41597
  console.error(`Failed to read smart folders: ${message}`);
41586
41598
  return { folders: null, error: "query_error", message: "Failed to read smart folders." };
@@ -41646,11 +41658,11 @@ import {
41646
41658
  rmSync,
41647
41659
  statSync
41648
41660
  } from "fs";
41649
- import { dirname, isAbsolute, join as join7, relative, resolve, sep } from "path";
41650
- import { homedir as homedir7, tmpdir } from "os";
41661
+ import { dirname, isAbsolute, join as join8, relative, resolve, sep } from "path";
41662
+ import { homedir as homedir8, tmpdir } from "os";
41651
41663
  function allowedSaveRoots() {
41652
41664
  return [
41653
- resolve(homedir7()),
41665
+ resolve(homedir8()),
41654
41666
  resolve(tmpdir()),
41655
41667
  "/Volumes",
41656
41668
  "/private/var/folders",
@@ -41659,7 +41671,7 @@ function allowedSaveRoots() {
41659
41671
  ];
41660
41672
  }
41661
41673
  function deniedSaveRoots() {
41662
- return [join7(homedir7(), "Library/Group Containers/group.com.apple.notes")];
41674
+ return [join8(homedir8(), "Library/Group Containers/group.com.apple.notes")];
41663
41675
  }
41664
41676
  function canonicalize(path10) {
41665
41677
  return realpathSync.native(path10);
@@ -41727,7 +41739,7 @@ function assertSafeSavePath(p, roots = allowedSaveRoots(), denied = deniedSaveRo
41727
41739
  if (suffix.split(sep).includes("..")) {
41728
41740
  throw new Error(`Refusing to write outside allowed locations (home, temp, /Volumes): "${abs}"`);
41729
41741
  }
41730
- const canonicalDest = suffix ? join7(canonicalAncestor, suffix) : canonicalAncestor;
41742
+ const canonicalDest = suffix ? join8(canonicalAncestor, suffix) : canonicalAncestor;
41731
41743
  const allowed = canonicalRoots(roots);
41732
41744
  if (!isWithinRoots(canonicalAncestor, allowed) || !isWithinRoots(canonicalDest, allowed)) {
41733
41745
  throw new Error(
@@ -41796,13 +41808,13 @@ function privateContentReason(p, roots) {
41796
41808
  const containing = roots.map((r) => r.endsWith(sep) ? r.slice(0, -1) : r).filter((r) => isWithinRoots(p, [r])).sort((a, b) => b.length - a.length)[0];
41797
41809
  const below = containing === void 0 ? p : relative(containing, p);
41798
41810
  if (below.split(sep).some((part) => part.startsWith("."))) return "a hidden file or directory";
41799
- const home = resolve(homedir7());
41811
+ const home = resolve(homedir8());
41800
41812
  const libraries = [
41801
- join7(home, "Library"),
41802
- ...canonicalRoots([home]).map((h) => join7(h, "Library"))
41813
+ join8(home, "Library"),
41814
+ ...canonicalRoots([home]).map((h) => join8(h, "Library"))
41803
41815
  ];
41804
41816
  if (!isWithinRoots(p, libraries)) return null;
41805
- const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) => join7(l, d)));
41817
+ const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) => join8(l, d)));
41806
41818
  return isWithinRoots(p, cloudDocuments) ? null : "~/Library";
41807
41819
  }
41808
41820
  var CLOUD_DOCUMENT_DIRS = ["Mobile Documents", "CloudStorage"];
@@ -41886,7 +41898,7 @@ import {
41886
41898
  unlinkSync as unlinkSync2,
41887
41899
  writeSync as writeSync2
41888
41900
  } from "node:fs";
41889
- import { basename as basename2, dirname as dirname3, extname as extname2, join as join9 } from "node:path";
41901
+ import { basename as basename2, dirname as dirname3, extname as extname2, join as join10 } from "node:path";
41890
41902
 
41891
41903
  // src/utils/attachmentAssets.ts
41892
41904
  import { execFileSync as execFileSync8 } from "node:child_process";
@@ -41903,11 +41915,11 @@ import {
41903
41915
  unlinkSync,
41904
41916
  writeSync
41905
41917
  } from "node:fs";
41906
- import { homedir as homedir8 } from "node:os";
41907
- import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join8, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
41918
+ import { homedir as homedir9 } from "node:os";
41919
+ import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
41908
41920
  import { gunzipSync as gunzipSync5 } from "node:zlib";
41909
- var NOTES_CONTAINER_DIR = join8(
41910
- homedir8(),
41921
+ var NOTES_CONTAINER_DIR = join9(
41922
+ homedir9(),
41911
41923
  "Library/Group Containers/group.com.apple.notes"
41912
41924
  );
41913
41925
  var AttachmentStoreError = class extends Error {
@@ -42023,7 +42035,7 @@ function runSqlite3(dbPath2, sql) {
42023
42035
  const message = error2 instanceof Error ? error2.message : String(error2);
42024
42036
  if (/authorization denied|unable to open database/i.test(message)) {
42025
42037
  throw new AttachmentStoreError(
42026
- "Full Disk Access is required to read attachment paths. Grant it to the Node binary running this server (or the terminal that launches it), then relaunch it (run the doctor tool to verify).",
42038
+ `Full Disk Access is required to read attachment paths. ${fdaRemediation()}`,
42027
42039
  "no_fda"
42028
42040
  );
42029
42041
  }
@@ -42082,7 +42094,7 @@ function attachmentOrderFromNoteData(hex3) {
42082
42094
  return null;
42083
42095
  }
42084
42096
  }
42085
- function readNoteAttachmentRows(noteId3, dbPath2 = join8(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
42097
+ function readNoteAttachmentRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
42086
42098
  const { pk } = parseNoteId2(noteId3);
42087
42099
  const columnList = runSqlite3(
42088
42100
  dbPath2,
@@ -42143,7 +42155,7 @@ function boundedEntries(dir, limit) {
42143
42155
  }
42144
42156
  }
42145
42157
  function resolveAccountDir(containerDir, accountIdentifier) {
42146
- const accountsDir = join8(containerDir, "Accounts");
42158
+ const accountsDir = join9(containerDir, "Accounts");
42147
42159
  let accountsReal;
42148
42160
  try {
42149
42161
  accountsReal = realpathSync2.native(accountsDir);
@@ -42152,14 +42164,14 @@ function resolveAccountDir(containerDir, accountIdentifier) {
42152
42164
  }
42153
42165
  const account = safeComponent(accountIdentifier);
42154
42166
  if (account) {
42155
- const dir = realInside(join8(accountsReal, account), accountsReal);
42167
+ const dir = realInside(join9(accountsReal, account), accountsReal);
42156
42168
  if (dir && isDirectory(dir)) return dir;
42157
42169
  }
42158
42170
  const entries2 = (boundedEntries(accountsReal, 64) ?? []).filter(
42159
- (e) => isDirectory(join8(accountsReal, e))
42171
+ (e) => isDirectory(join9(accountsReal, e))
42160
42172
  );
42161
42173
  if (entries2.length !== 1) return null;
42162
- return realInside(join8(accountsReal, entries2[0]), accountsReal);
42174
+ return realInside(join9(accountsReal, entries2[0]), accountsReal);
42163
42175
  }
42164
42176
  function generationRank(name) {
42165
42177
  const m = /^(\d+)_/.exec(name);
@@ -42167,24 +42179,24 @@ function generationRank(name) {
42167
42179
  }
42168
42180
  function generationDirs(base, accountDir) {
42169
42181
  const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
42170
- return entries2.map((e) => realInside(join8(base, e), accountDir)).filter((p) => p !== null && isDirectory(p)).sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
42182
+ return entries2.map((e) => realInside(join9(base, e), accountDir)).filter((p) => p !== null && isDirectory(p)).sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
42171
42183
  }
42172
42184
  function fallbackFiles(accountDir, rootName, identifier, generation, names, onStale) {
42173
- const base = join8(accountDir, rootName, identifier);
42185
+ const base = join9(accountDir, rootName, identifier);
42174
42186
  const found = [];
42175
42187
  const add = (candidate) => {
42176
42188
  const real = realInside(candidate, accountDir);
42177
42189
  if (real && isRegularFile(real) && !found.includes(real)) found.push(real);
42178
42190
  };
42179
42191
  const gen = safeComponent(generation);
42180
- if (gen) for (const name of names) add(join8(base, gen, name));
42192
+ if (gen) for (const name of names) add(join9(base, gen, name));
42181
42193
  const recorded = found.length;
42182
42194
  if (isDirectory(base)) {
42183
42195
  for (const dir of generationDirs(base, accountDir))
42184
- for (const name of names) add(join8(dir, name));
42185
- for (const name of names) add(join8(base, name));
42196
+ for (const name of names) add(join9(dir, name));
42197
+ for (const name of names) add(join9(base, name));
42186
42198
  }
42187
- for (const name of names) add(join8(accountDir, rootName, `${identifier}${extname(name)}`));
42199
+ for (const name of names) add(join9(accountDir, rootName, `${identifier}${extname(name)}`));
42188
42200
  if (gen && recorded === 0 && found.length > 0) onStale?.();
42189
42201
  return found;
42190
42202
  }
@@ -42197,7 +42209,7 @@ function previewFileInBundle(bundle, accountDir) {
42197
42209
  const direct = [];
42198
42210
  const nested = [];
42199
42211
  for (const entry of boundedEntries(bundle, MAX_BUNDLE_ENTRIES) ?? []) {
42200
- const child2 = realInside(join8(bundle, entry), accountDir);
42212
+ const child2 = realInside(join9(bundle, entry), accountDir);
42201
42213
  if (!child2) continue;
42202
42214
  if (isRegularFile(child2)) direct.push(child2);
42203
42215
  else if (isDirectory(child2)) nested.push(child2);
@@ -42205,14 +42217,14 @@ function previewFileInBundle(bundle, accountDir) {
42205
42217
  nested.sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
42206
42218
  const pick2 = (files) => files.find((f) => basename(f) === "Preview.png") ?? files.find((f) => PREVIEW_IMAGE_SUFFIXES.has(extname(f).toLowerCase())) ?? null;
42207
42219
  for (const dir of nested) {
42208
- const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(join8(dir, e), accountDir)).filter((p) => p !== null && isRegularFile(p));
42220
+ const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(join9(dir, e), accountDir)).filter((p) => p !== null && isRegularFile(p));
42209
42221
  const chosen = pick2(files);
42210
42222
  if (chosen) return chosen;
42211
42223
  }
42212
42224
  return pick2(direct);
42213
42225
  }
42214
42226
  function listPreviewEntries(accountDir) {
42215
- return boundedEntries(join8(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
42227
+ return boundedEntries(join9(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
42216
42228
  }
42217
42229
  function previewPaths(accountDir, identifier, entries2) {
42218
42230
  const id2 = safeComponent(identifier);
@@ -42224,7 +42236,7 @@ function previewPaths(accountDir, identifier, entries2) {
42224
42236
  }).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
42225
42237
  const files = [];
42226
42238
  for (const name of candidates) {
42227
- const real = realInside(join8(accountDir, "Previews", name), accountDir);
42239
+ const real = realInside(join9(accountDir, "Previews", name), accountDir);
42228
42240
  if (!real) continue;
42229
42241
  const file = isRegularFile(real) ? real : isDirectory(real) ? previewFileInBundle(real, accountDir) : null;
42230
42242
  if (file && !files.includes(file)) files.push(file);
@@ -42241,13 +42253,13 @@ function assetPathsFor(accountDir, row, onStale) {
42241
42253
  const mediaName = safeComponent(row.mediaFilename);
42242
42254
  const mediaGen = safeComponent(row.mediaGeneration);
42243
42255
  if (mediaId && mediaName) {
42244
- if (mediaGen) add(join8(accountDir, "Media", mediaId, mediaGen, mediaName));
42245
- add(join8(accountDir, "Media", mediaId, mediaName));
42256
+ if (mediaGen) add(join9(accountDir, "Media", mediaId, mediaGen, mediaName));
42257
+ add(join9(accountDir, "Media", mediaId, mediaName));
42246
42258
  }
42247
42259
  const id2 = safeComponent(row.identifier);
42248
42260
  if (!id2) return found;
42249
42261
  const ownName = safeComponent(row.filename);
42250
- if (ownName) add(join8(accountDir, "Media", id2, ownName));
42262
+ if (ownName) add(join9(accountDir, "Media", id2, ownName));
42251
42263
  for (const file of fallbackFiles(
42252
42264
  accountDir,
42253
42265
  "FallbackImages",
@@ -42350,10 +42362,10 @@ function canonicalTail(p) {
42350
42362
  const tail = [];
42351
42363
  for (; ; ) {
42352
42364
  try {
42353
- return join8(realpathSync2.native(current), ...tail);
42365
+ return join9(realpathSync2.native(current), ...tail);
42354
42366
  } catch {
42355
42367
  const parent = dirname2(current);
42356
- if (parent === current) return join8(current, ...tail);
42368
+ if (parent === current) return join9(current, ...tail);
42357
42369
  tail.unshift(basename(current));
42358
42370
  current = parent;
42359
42371
  }
@@ -42442,7 +42454,7 @@ function exportOneAttachment(record2, dir, source, inBody) {
42442
42454
  if (!source) return base;
42443
42455
  const name = exportFileName(record2, source.path, source.kind);
42444
42456
  for (let attempt = 1; attempt <= MAX_COLLISION_SUFFIX; attempt++) {
42445
- const dest = join8(dir, collisionName(name, attempt));
42457
+ const dest = join9(dir, collisionName(name, attempt));
42446
42458
  try {
42447
42459
  if (dirname2(dest) !== resolve2(dir))
42448
42460
  throw new Error(`Refusing to write outside the export directory: "${dest}"`);
@@ -42597,7 +42609,7 @@ function runSqlite4(dbPath2, sql) {
42597
42609
  const message = error2 instanceof Error ? error2.message : String(error2);
42598
42610
  if (/authorization denied|unable to open database/i.test(message)) {
42599
42611
  throw new AttachmentStoreError(
42600
- "Full Disk Access is required to read drawing attachments. Grant it to the Node binary running this server (or the terminal that launches it), then relaunch it (run the doctor tool to verify).",
42612
+ `Full Disk Access is required to read drawing attachments. ${fdaRemediation()}`,
42601
42613
  "no_fda"
42602
42614
  );
42603
42615
  }
@@ -42627,7 +42639,7 @@ function parseDrawingRows(json2) {
42627
42639
  }
42628
42640
  return rows.sort((a, b) => a.pk - b.pk);
42629
42641
  }
42630
- function readDrawingRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
42642
+ function readDrawingRows(noteId3, dbPath2 = join10(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
42631
42643
  const { pk } = parseNoteId2(noteId3);
42632
42644
  const columns = new Set(
42633
42645
  runSqlite4(
@@ -42674,23 +42686,23 @@ function locateFallbackImage(accountDir, identifier, generation) {
42674
42686
  if (!path10 || !gen) return { path: path10, stale: false };
42675
42687
  const id2 = safeComponent(identifier);
42676
42688
  const recorded = ["FallbackImage.png", "FallbackImage.jpg"].map(
42677
- (n) => id2 ? realInside(join9(accountDir, "FallbackImages", id2, gen, n), accountDir) : null
42689
+ (n) => id2 ? realInside(join10(accountDir, "FallbackImages", id2, gen, n), accountDir) : null
42678
42690
  ).filter((p) => p !== null);
42679
42691
  return { path: path10, stale: !recorded.includes(path10) };
42680
42692
  }
42681
42693
  function searchFallbackImage(accountDir, identifier, generation) {
42682
42694
  const id2 = safeComponent(identifier);
42683
42695
  if (!id2) return null;
42684
- const base = join9(accountDir, "FallbackImages", id2);
42696
+ const base = join10(accountDir, "FallbackImages", id2);
42685
42697
  const names = ["FallbackImage.png", "FallbackImage.jpg"];
42686
42698
  const candidates = [];
42687
42699
  const gen = safeComponent(generation);
42688
- if (gen) for (const n of names) candidates.push(join9(base, gen, n));
42689
- const gens = entries(base, MAX_DIR_ENTRIES).map((e) => join9(base, e)).sort(byGenerationDesc);
42690
- for (const g of gens) for (const n of names) candidates.push(join9(g, n));
42691
- for (const n of names) candidates.push(join9(base, n));
42692
- candidates.push(join9(accountDir, "FallbackImages", `${id2}.png`));
42693
- candidates.push(join9(accountDir, "FallbackImages", `${id2}.jpg`));
42700
+ if (gen) for (const n of names) candidates.push(join10(base, gen, n));
42701
+ const gens = entries(base, MAX_DIR_ENTRIES).map((e) => join10(base, e)).sort(byGenerationDesc);
42702
+ for (const g of gens) for (const n of names) candidates.push(join10(g, n));
42703
+ for (const n of names) candidates.push(join10(base, n));
42704
+ candidates.push(join10(accountDir, "FallbackImages", `${id2}.png`));
42705
+ candidates.push(join10(accountDir, "FallbackImages", `${id2}.jpg`));
42694
42706
  for (const c of candidates) {
42695
42707
  const real = realInside(c, accountDir);
42696
42708
  if (real && kindOf(real) === "file") return real;
@@ -42700,21 +42712,21 @@ function searchFallbackImage(accountDir, identifier, generation) {
42700
42712
  function findLargestPreview(accountDir, identifier) {
42701
42713
  const id2 = safeComponent(identifier);
42702
42714
  if (!id2) return null;
42703
- const dir = join9(accountDir, "Previews");
42715
+ const dir = join10(accountDir, "Previews");
42704
42716
  const prefix = `${id2}-`.toLowerCase();
42705
42717
  const names = entries(dir, MAX_PREVIEW_DIR_ENTRIES2).filter((n) => n.toLowerCase().startsWith(prefix)).filter((n) => {
42706
42718
  const ext = extname2(n).toLowerCase();
42707
42719
  return !ext || IMAGE_SUFFIXES.has(ext) || /^\.\d+$/.test(ext);
42708
42720
  }).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
42709
42721
  for (const name of names) {
42710
- const real = realInside(join9(dir, name), accountDir);
42722
+ const real = realInside(join10(dir, name), accountDir);
42711
42723
  if (!real) continue;
42712
42724
  const kind = kindOf(real);
42713
42725
  if (kind === "file") return real;
42714
42726
  if (kind !== "dir") continue;
42715
- const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(join9(real, e), accountDir)).filter((p) => p !== null && kindOf(p) === "dir").sort(byGenerationDesc);
42727
+ const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(join10(real, e), accountDir)).filter((p) => p !== null && kindOf(p) === "dir").sort(byGenerationDesc);
42716
42728
  for (const g of [...gens, real]) {
42717
- const file = realInside(join9(g, "Preview.png"), accountDir);
42729
+ const file = realInside(join10(g, "Preview.png"), accountDir);
42718
42730
  if (file && kindOf(file) === "file") return file;
42719
42731
  }
42720
42732
  }
@@ -42728,7 +42740,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
42728
42740
  const fallback = accountDir ? locateFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : { path: null, stale: false };
42729
42741
  const fallbackImagePath = fallback.path;
42730
42742
  const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
42731
- const bundle = accountDir && id2 && kind === "paper" ? realInside(join9(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
42743
+ const bundle = accountDir && id2 && kind === "paper" ? realInside(join10(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
42732
42744
  let raster = null;
42733
42745
  for (const [path10, source] of [
42734
42746
  [fallbackImagePath, "fallback"],
@@ -42934,8 +42946,8 @@ function scopeConflictMessage(reason) {
42934
42946
  // src/services/appleNotesManager.ts
42935
42947
  var import_turndown = __toESM(require_turndown_cjs(), 1);
42936
42948
  import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
42937
- import { homedir as homedir9, tmpdir as tmpdir2 } from "os";
42938
- import { join as join10 } from "path";
42949
+ import { homedir as homedir10, tmpdir as tmpdir2 } from "os";
42950
+ import { join as join11 } from "path";
42939
42951
  var FIELD_SEP = "";
42940
42952
  var RECORD_SEP = "";
42941
42953
  var AS_FIELD_SEP = "(character id 31)";
@@ -43297,8 +43309,8 @@ var ExpectedBodies = class {
43297
43309
  if (body.length <= INLINE_EXPECTED_BODY_LIMIT) {
43298
43310
  return { setup: "", operand: `"${escapeHtmlForAppleScript(body)}"` };
43299
43311
  }
43300
- this.directory ??= mkdtempSync2(join10(tmpdir2(), "apple-notes-expected-body-"));
43301
- const file = join10(this.directory, `body-${this.files++}.html`);
43312
+ this.directory ??= mkdtempSync2(join11(tmpdir2(), "apple-notes-expected-body-"));
43313
+ const file = join11(this.directory, `body-${this.files++}.html`);
43302
43314
  writeFileSync(file, body, { encoding: "utf8", mode: 384 });
43303
43315
  return {
43304
43316
  setup: `
@@ -43344,7 +43356,7 @@ function getNoteLinkFromDB(coreDataId3) {
43344
43356
  const match = coreDataId3.match(/\/p(\d+)$/);
43345
43357
  if (!match) return null;
43346
43358
  const pk = parseInt(match[1], 10);
43347
- const dbPath2 = join10(homedir9(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
43359
+ const dbPath2 = join11(homedir10(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
43348
43360
  if (!existsSync6(dbPath2)) return null;
43349
43361
  try {
43350
43362
  const { DatabaseSync } = __require("node:sqlite");
@@ -46165,7 +46177,7 @@ var NOTES_DB_PATH7 = path6.join(
46165
46177
  os6.homedir(),
46166
46178
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
46167
46179
  );
46168
- var FDA_MESSAGE2 = `Full Disk Access is required to read note metadata. In System Settings > Privacy & Security > Full Disk Access, grant access to the Node binary running this server (required under Claude Desktop) or the terminal that launches it, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
46180
+ var FDA_MESSAGE2 = "Full Disk Access is required to read note metadata.";
46169
46181
  var COLUMN_MAP = [
46170
46182
  { key: "pinned", column: "ZISPINNED", type: "bool" },
46171
46183
  { key: "hasChecklist", column: "ZHASCHECKLIST", type: "bool" },
@@ -46204,7 +46216,7 @@ function getNoteMetadata(noteId3) {
46204
46216
  }
46205
46217
  const pk = pkMatch[1];
46206
46218
  if (!fs6.existsSync(NOTES_DB_PATH7)) {
46207
- return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
46219
+ return { metadata: null, error: "no_fda", message: `${FDA_MESSAGE2} ${fdaRemediation()}` };
46208
46220
  }
46209
46221
  try {
46210
46222
  const available = presentColumns();
@@ -46238,7 +46250,7 @@ function getNoteMetadata(noteId3) {
46238
46250
  } catch (error2) {
46239
46251
  const message = error2 instanceof Error ? error2.message : String(error2);
46240
46252
  if (message.includes("authorization denied") || message.includes("unable to open database")) {
46241
- return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
46253
+ return { metadata: null, error: "no_fda", message: `${FDA_MESSAGE2} ${fdaRemediation()}` };
46242
46254
  }
46243
46255
  console.error(`Failed to read note metadata: ${message}`);
46244
46256
  return { metadata: null, error: "query_error", message: "Failed to read note metadata." };
@@ -46258,7 +46270,7 @@ var NOTES_DB_PATH8 = path7.join(
46258
46270
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
46259
46271
  );
46260
46272
  var CORE_DATA_EPOCH_MS = Date.UTC(2001, 0, 1);
46261
- var STORE_FDA_MESSAGE = `Full Disk Access is required to read the Notes database. In System Settings > Privacy & Security > Full Disk Access, grant access to the Node binary running this server (required under Claude Desktop) or the terminal that launches it, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
46273
+ var STORE_FDA_MESSAGE = "Full Disk Access is required to read the Notes database.";
46262
46274
  var NoteStoreError = class extends Error {
46263
46275
  constructor(message, kind) {
46264
46276
  super(message);
@@ -46292,7 +46304,8 @@ function renderValue(value) {
46292
46304
  return `ieee754_from_blob(x'${doubleToHex(double)}')`;
46293
46305
  }
46294
46306
  function runReadOnlySql(dbPath2, sql, params = {}) {
46295
- if (!fs7.existsSync(dbPath2)) throw new NoteStoreError(STORE_FDA_MESSAGE, "no_fda");
46307
+ if (!fs7.existsSync(dbPath2))
46308
+ throw new NoteStoreError(`${STORE_FDA_MESSAGE} ${fdaRemediation()}`, "no_fda");
46296
46309
  const args = ["-readonly"];
46297
46310
  const names = Object.keys(params);
46298
46311
  if (names.length) args.push("-cmd", ".parameter init");
@@ -46313,7 +46326,7 @@ function runReadOnlySql(dbPath2, sql, params = {}) {
46313
46326
  } catch (error2) {
46314
46327
  const message = error2 instanceof Error ? error2.message : String(error2);
46315
46328
  if (/authorization denied|unable to open database/i.test(message)) {
46316
- throw new NoteStoreError(STORE_FDA_MESSAGE, "no_fda");
46329
+ throw new NoteStoreError(`${STORE_FDA_MESSAGE} ${fdaRemediation()}`, "no_fda");
46317
46330
  }
46318
46331
  console.error(`Notes database query failed: ${message}`);
46319
46332
  throw new NoteStoreError("Failed to query the Notes database.", "query_error");
@@ -46669,8 +46682,8 @@ function assembleInventory(rows, accounts, scope2) {
46669
46682
  // src/utils/noteBlocks.ts
46670
46683
  import { execFileSync as execFileSync13 } from "node:child_process";
46671
46684
  import { existsSync as existsSync10 } from "node:fs";
46672
- import { homedir as homedir13 } from "node:os";
46673
- import { join as join14 } from "node:path";
46685
+ import { homedir as homedir14 } from "node:os";
46686
+ import { join as join15 } from "node:path";
46674
46687
  import { gunzipSync as gunzipSync7 } from "node:zlib";
46675
46688
  var NOTE_BLOCKS_ERROR_CODES = {
46676
46689
  "invalid-id": "validation_error",
@@ -47026,8 +47039,8 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
47026
47039
  undecodedFields: doc.undecodedFields
47027
47040
  };
47028
47041
  }
47029
- var NOTES_DB_PATH9 = join14(
47030
- homedir13(),
47042
+ var NOTES_DB_PATH9 = join15(
47043
+ homedir14(),
47031
47044
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
47032
47045
  );
47033
47046
  function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
@@ -47966,7 +47979,7 @@ var QUERY_RESULTS = { DEFAULT: 50, MAX: 500 };
47966
47979
  var CORE_DATA_EPOCH_MS2 = Date.UTC(2001, 0, 1);
47967
47980
  var SNIPPET_BEFORE = 60;
47968
47981
  var SNIPPET_LENGTH = 180;
47969
- var QUERY_FDA_MESSAGE = `Full Disk Access is required to query notes. In System Settings > Privacy & Security > Full Disk Access, grant access to the Node binary running this server (required under Claude Desktop) or the terminal that launches it, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
47982
+ var QUERY_FDA_MESSAGE = "Full Disk Access is required to query notes.";
47970
47983
  var NoteQueryStoreError = class extends Error {
47971
47984
  constructor(message, kind) {
47972
47985
  super(message);
@@ -48112,14 +48125,15 @@ function presentColumns2(dbPath2) {
48112
48125
  return cols;
48113
48126
  }
48114
48127
  function readStore(dbPath2, build) {
48115
- if (!fs8.existsSync(dbPath2)) throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
48128
+ if (!fs8.existsSync(dbPath2))
48129
+ throw new NoteQueryStoreError(`${QUERY_FDA_MESSAGE} ${fdaRemediation()}`, "no_fda");
48116
48130
  try {
48117
48131
  return runSqlite6(dbPath2, build(presentColumns2(dbPath2)));
48118
48132
  } catch (error2) {
48119
48133
  if (error2 instanceof NoteQueryStoreError) throw error2;
48120
48134
  const message = error2 instanceof Error ? error2.message : String(error2);
48121
48135
  if (message.includes("authorization denied") || message.includes("unable to open database")) {
48122
- throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
48136
+ throw new NoteQueryStoreError(`${QUERY_FDA_MESSAGE} ${fdaRemediation()}`, "no_fda");
48123
48137
  }
48124
48138
  console.error(`query-notes: database read failed: ${message}`);
48125
48139
  throw new NoteQueryStoreError("Failed to read the Notes database.", "query_error");
@@ -48176,13 +48190,13 @@ var escapeSegment = escapeFolderName;
48176
48190
  function resolveFolders(rows) {
48177
48191
  const byPk = new Map(rows.map((row) => [row.pk, row]));
48178
48192
  const resolved = /* @__PURE__ */ new Map();
48179
- const resolve10 = (pk, seen) => {
48193
+ const resolve11 = (pk, seen) => {
48180
48194
  const cached2 = resolved.get(pk);
48181
48195
  if (cached2) return cached2;
48182
48196
  const row = byPk.get(pk);
48183
48197
  if (!row || seen.has(pk)) return void 0;
48184
48198
  seen.add(pk);
48185
- const parent = row.parent !== null ? resolve10(row.parent, seen) : void 0;
48199
+ const parent = row.parent !== null ? resolve11(row.parent, seen) : void 0;
48186
48200
  const name = row.name ?? "";
48187
48201
  const path10 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
48188
48202
  const plainPath = parent ? `${parent.plainPath}/${name}` : name;
@@ -48196,7 +48210,7 @@ function resolveFolders(rows) {
48196
48210
  resolved.set(pk, info);
48197
48211
  return info;
48198
48212
  };
48199
- for (const row of rows) resolve10(row.pk, /* @__PURE__ */ new Set());
48213
+ for (const row of rows) resolve11(row.pk, /* @__PURE__ */ new Set());
48200
48214
  return resolved;
48201
48215
  }
48202
48216
  var coreDataMs = (seconds) => seconds === null || !Number.isFinite(seconds) ? void 0 : CORE_DATA_EPOCH_MS2 + seconds * 1e3;
@@ -48411,7 +48425,7 @@ function describeContentScan(scan) {
48411
48425
  }
48412
48426
  function contentSearchFailureHint(message, dbUnavailable) {
48413
48427
  if (!/timed out/i.test(message)) return message;
48414
- const remedy = dbUnavailable === "no_fda" ? " Grant Full Disk Access to the Node binary running this server (run the doctor tool for its path) so search-notes can search note bodies through the Notes database instead, which takes well under a second." : "";
48428
+ const remedy = dbUnavailable === "no_fda" ? ` Full Disk Access lets search-notes search note bodies through the Notes database instead. ${fdaRemediation()}` : "";
48415
48429
  return `${message} Body search through AppleScript scans every note body before the result limit applies, so a broad term can exceed the time budget on a large library.${remedy} Otherwise narrow the search with \`folder\` or \`modifiedSince\`, or use a more specific term.`;
48416
48430
  }
48417
48431
  var NOTE_ID = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/ICNote\/p(\d{1,15})$/;
@@ -48471,7 +48485,7 @@ import { spawnSync } from "child_process";
48471
48485
  import { execFileSync as execFileSync15 } from "node:child_process";
48472
48486
  import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
48473
48487
  import { tmpdir as tmpdir3 } from "node:os";
48474
- import { join as join16 } from "node:path";
48488
+ import { join as join17 } from "node:path";
48475
48489
 
48476
48490
  // src/services/shortcutConsent.ts
48477
48491
  function shortcutConsentHint(shortcut) {
@@ -48573,9 +48587,9 @@ function runNativeTagsShortcut(input) {
48573
48587
  const status = nativeTagsStatus();
48574
48588
  if (!status.installed)
48575
48589
  throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
48576
- const directory = mkdtempSync3(join16(tmpdir3(), "apple-notes-native-tags-"));
48590
+ const directory = mkdtempSync3(join17(tmpdir3(), "apple-notes-native-tags-"));
48577
48591
  try {
48578
- const path10 = join16(directory, "request.json");
48592
+ const path10 = join17(directory, "request.json");
48579
48593
  writeFileSync2(path10, JSON.stringify(input), { mode: 384 });
48580
48594
  execFileSync15("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path10], {
48581
48595
  encoding: "utf8",
@@ -48599,7 +48613,7 @@ function runNativeTagsShortcut(input) {
48599
48613
  import { execFileSync as execFileSync16 } from "node:child_process";
48600
48614
  import { mkdtempSync as mkdtempSync4, writeFileSync as writeFileSync3, rmSync as rmSync4 } from "node:fs";
48601
48615
  import { tmpdir as tmpdir4 } from "node:os";
48602
- import { join as join17 } from "node:path";
48616
+ import { join as join18 } from "node:path";
48603
48617
 
48604
48618
  // src/utils/appendMarkdown.ts
48605
48619
  var escape2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -48788,7 +48802,7 @@ function appendMarkdownHtml(markdown, options = {}) {
48788
48802
  }
48789
48803
 
48790
48804
  // src/utils/noteRevision.ts
48791
- var INLINE_TAG = /^<\/?(?:b|i|u|s|strike|em|strong|span|a|font|sub|sup|code|tt|small|big|mark)\b/i;
48805
+ var INLINE_TAG = /^<\/?(?:b|i|u|s|strike|del|em|strong|span|a|font|sub|sup|code|tt|small|big|mark)\b/i;
48792
48806
  function comparableVisibleText(html) {
48793
48807
  return decodeHtmlEntities(
48794
48808
  html.replace(/<br\s*\/?\s*>/gi, " ").replace(/<[^>]*>/g, (tag) => INLINE_TAG.test(tag) ? "" : " ")
@@ -48910,9 +48924,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
48910
48924
  ),
48911
48925
  { shortcut: status.shortcut, notInstalled: true }
48912
48926
  );
48913
- const directory = mkdtempSync4(join17(tmpdir4(), "apple-notes-background-"));
48927
+ const directory = mkdtempSync4(join18(tmpdir4(), "apple-notes-background-"));
48914
48928
  try {
48915
- const file = join17(directory, "request.json");
48929
+ const file = join18(directory, "request.json");
48916
48930
  writeFileSync3(
48917
48931
  file,
48918
48932
  JSON.stringify({
@@ -49668,10 +49682,6 @@ function runDoctor(manager, capabilityMatrix = getCapabilityMatrix) {
49668
49682
  }
49669
49683
  return matrix ? { healthy, checks, runtimeOS: matrix.runtimeOS, features: matrix.features } : { healthy, checks };
49670
49684
  }
49671
- function fdaRemediation(execPath = process.execPath) {
49672
- const versioned = /\/(\.nvm|\.fnm|\.volta|\.asdf|\.local\/share\/mise|\.nodenv|n\/versions)\//.test(execPath);
49673
- return `In System Settings > Privacy & Security > Full Disk Access, click + and add the Node binary running this server: ${execPath} (press Cmd+Shift+G in the file picker to paste the path). Under Claude Desktop that entry is required: Claude Desktop launches servers as their own responsible process, so a grant on Claude.app does not reach them. When the server runs from a terminal (Terminal, iTerm2) or an editor, granting that app is enough. Then fully quit (Cmd+Q) and relaunch the host app and re-run doctor; if it still reports not granted, restart the Mac. ` + (versioned ? "This Node lives under a version manager, so the path changes with each Node version and the grant has to be added again after switching; pointing the MCP config at one fixed Node path avoids that. " : "") + `Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
49674
- }
49675
49685
  function markdownBridgeDetail() {
49676
49686
  const purpose = 'needed only for create-note format: "markdown" on macOS 26+';
49677
49687
  try {
@@ -49721,14 +49731,15 @@ function formatDoctorReport(r) {
49721
49731
 
49722
49732
  // src/services/fileConfig.ts
49723
49733
  import { existsSync as existsSync12, readFileSync as readFileSync2 } from "fs";
49724
- import { join as join18 } from "path";
49725
- import { homedir as homedir15 } from "os";
49734
+ import { join as join19 } from "path";
49735
+ import { homedir as homedir16 } from "os";
49726
49736
  function fileConfigPath(env = process.env) {
49727
49737
  const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
49728
49738
  if (override && override.trim()) return override.trim();
49729
- return join18(homedir15(), "Library", "Application Support", "apple-notes-mcp", "config.json");
49739
+ return join19(homedir16(), "Library", "Application Support", "apple-notes-mcp", "config.json");
49730
49740
  }
49731
49741
  function loadFileConfig(env = process.env, path10 = fileConfigPath(env)) {
49742
+ if (env.APPLE_NOTES_MCP_BROKERED === "1") return [];
49732
49743
  const applied = [];
49733
49744
  try {
49734
49745
  if (!existsSync12(path10)) return applied;
@@ -50911,7 +50922,7 @@ function insertLink(request, deps) {
50911
50922
 
50912
50923
  // src/services/notesExport.ts
50913
50924
  import { mkdirSync as mkdirSync7 } from "node:fs";
50914
- import { basename as basename4, dirname as dirname7, extname as extname5, join as join23 } from "node:path";
50925
+ import { basename as basename4, dirname as dirname7, extname as extname5, join as join24 } from "node:path";
50915
50926
 
50916
50927
  // src/utils/exportAssets.ts
50917
50928
  import {
@@ -50926,9 +50937,9 @@ import {
50926
50937
  statSync as statSync3,
50927
50938
  writeSync as writeSync3
50928
50939
  } from "node:fs";
50929
- import { homedir as homedir16 } from "node:os";
50930
- import { basename as basename3, extname as extname3, join as join19, relative as relative3, resolve as resolve3, sep as sep3 } from "node:path";
50931
- var NOTES_CONTAINER = join19(homedir16(), "Library/Group Containers/group.com.apple.notes");
50940
+ import { homedir as homedir17 } from "node:os";
50941
+ import { basename as basename3, extname as extname3, join as join20, relative as relative3, resolve as resolve3, sep as sep3 } from "node:path";
50942
+ var NOTES_CONTAINER = join20(homedir17(), "Library/Group Containers/group.com.apple.notes");
50932
50943
  var MAX_EMBED_BYTES = 10 * 1024 * 1024;
50933
50944
  var MAX_EMBED_TOTAL_BYTES = 256 * 1024 * 1024;
50934
50945
  var MAX_DIRECTORY_ENTRIES = 2e4;
@@ -50984,11 +50995,11 @@ var AssetLocator = class {
50984
50995
  constructor(container = NOTES_CONTAINER) {
50985
50996
  let accountsRoot = "";
50986
50997
  try {
50987
- accountsRoot = realpathSync3.native(join19(container, "Accounts"));
50998
+ accountsRoot = realpathSync3.native(join20(container, "Accounts"));
50988
50999
  } catch {
50989
51000
  }
50990
51001
  this.accounts = accountsRoot ? listDirectory(accountsRoot, 1e3).flatMap((name) => {
50991
- const dir = safeComponent2(name) && confine(join19(accountsRoot, name), accountsRoot);
51002
+ const dir = safeComponent2(name) && confine(join20(accountsRoot, name), accountsRoot);
50992
51003
  return dir && isDirectory2(dir) ? [dir] : [];
50993
51004
  }) : [];
50994
51005
  }
@@ -51062,8 +51073,8 @@ var AssetLocator = class {
51062
51073
  if (!mediaId || !filename) return void 0;
51063
51074
  const generation = safeComponent2(source.mediaGeneration);
51064
51075
  const candidates = [
51065
- ...generation ? [join19(account, "Media", mediaId, generation, filename)] : [],
51066
- join19(account, "Media", mediaId, filename)
51076
+ ...generation ? [join20(account, "Media", mediaId, generation, filename)] : [],
51077
+ join20(account, "Media", mediaId, filename)
51067
51078
  ];
51068
51079
  for (const candidate of candidates) {
51069
51080
  const path10 = confine(candidate, account);
@@ -51077,7 +51088,7 @@ var AssetLocator = class {
51077
51088
  * somewhere else.
51078
51089
  */
51079
51090
  fallback(account, dir, id2, generation, names) {
51080
- const base = join19(account, dir, id2);
51091
+ const base = join20(account, dir, id2);
51081
51092
  const gen = safeComponent2(generation);
51082
51093
  const generations = [
51083
51094
  ...gen ? [gen] : [],
@@ -51086,7 +51097,7 @@ var AssetLocator = class {
51086
51097
  ];
51087
51098
  for (const g of generations)
51088
51099
  for (const name of names) {
51089
- const path10 = confine(g ? join19(base, g, name) : join19(base, name), account);
51100
+ const path10 = confine(g ? join20(base, g, name) : join20(base, name), account);
51090
51101
  if (path10 && isFile(path10)) return { path: path10, stale: Boolean(gen) && g !== gen };
51091
51102
  }
51092
51103
  return void 0;
@@ -51096,7 +51107,7 @@ var AssetLocator = class {
51096
51107
  let index = this.previewIndex.get(account);
51097
51108
  if (!index) {
51098
51109
  index = /* @__PURE__ */ new Map();
51099
- for (const name of listDirectory(join19(account, "Previews"), MAX_DIRECTORY_ENTRIES)) {
51110
+ for (const name of listDirectory(join20(account, "Previews"), MAX_DIRECTORY_ENTRIES)) {
51100
51111
  const key = name.slice(0, 36).toUpperCase();
51101
51112
  const entries3 = index.get(key) ?? [];
51102
51113
  entries3.push(name);
@@ -51109,7 +51120,7 @@ var AssetLocator = class {
51109
51120
  return !ext || IMAGE_EXTENSIONS.has(ext) || /^\.\d+$/.test(ext);
51110
51121
  }).sort((a, b) => previewArea(b) - previewArea(a) || a.localeCompare(b));
51111
51122
  for (const name of entries2) {
51112
- const entry = confine(join19(account, "Previews", name), account);
51123
+ const entry = confine(join20(account, "Previews", name), account);
51113
51124
  if (!entry) continue;
51114
51125
  const file = isFile(entry) ? entry : this.bundleImage(entry, account);
51115
51126
  if (file) return { path: file, name: "preview", role: "preview" };
@@ -51120,12 +51131,12 @@ var AssetLocator = class {
51120
51131
  bundleImage(bundle, account) {
51121
51132
  const found = [];
51122
51133
  for (const child2 of listDirectory(bundle, MAX_BUNDLE_ENTRIES2)) {
51123
- const path10 = confine(join19(bundle, child2), account);
51134
+ const path10 = confine(join20(bundle, child2), account);
51124
51135
  if (!path10) continue;
51125
51136
  if (isFile(path10)) found.push(path10);
51126
51137
  else
51127
51138
  for (const grandchild of listDirectory(path10, MAX_BUNDLE_ENTRIES2)) {
51128
- const inner = confine(join19(path10, grandchild), account);
51139
+ const inner = confine(join20(path10, grandchild), account);
51129
51140
  if (inner && isFile(inner)) found.push(inner);
51130
51141
  }
51131
51142
  }
@@ -51186,11 +51197,11 @@ function canonicalForm(abs) {
51186
51197
  for (; ; ) {
51187
51198
  try {
51188
51199
  const real = realpathSync3.native(current);
51189
- return rest ? join19(real, rest) : real;
51200
+ return rest ? join20(real, rest) : real;
51190
51201
  } catch {
51191
51202
  const parent = resolve3(current, "..");
51192
51203
  if (parent === current) return abs;
51193
- rest = rest ? join19(basename3(current), rest) : basename3(current);
51204
+ rest = rest ? join20(basename3(current), rest) : basename3(current);
51194
51205
  current = parent;
51195
51206
  }
51196
51207
  }
@@ -51317,7 +51328,7 @@ var SidecarWriter = class {
51317
51328
  let target;
51318
51329
  let out;
51319
51330
  for (let n = 1; n <= 1e3 && out === void 0; n++) {
51320
- target = join19(this.dir, n === 1 ? safe2 : `${stem}-${n}${ext}`);
51331
+ target = join20(this.dir, n === 1 ? safe2 : `${stem}-${n}${ext}`);
51321
51332
  try {
51322
51333
  out = openSync4(target, CREATE_FLAGS, 420);
51323
51334
  } catch (error2) {
@@ -51989,8 +52000,8 @@ import {
51989
52000
  rmSync as rmSync5,
51990
52001
  writeFileSync as writeFileSync4
51991
52002
  } from "node:fs";
51992
- import { homedir as homedir17, release as release2 } from "node:os";
51993
- import { dirname as dirname6, join as join20, resolve as resolve4 } from "node:path";
52003
+ import { homedir as homedir18, release as release2 } from "node:os";
52004
+ import { dirname as dirname6, join as join21, resolve as resolve4 } from "node:path";
51994
52005
  import { fileURLToPath } from "node:url";
51995
52006
  var PUBLIC_HELPER_PROTOCOL = 1;
51996
52007
  var PUBLIC_HELPER_DIR_ENV = "APPLE_NOTES_MCP_PUBLIC_HELPER_DIR";
@@ -52019,7 +52030,7 @@ var publicManifestSchema = external_exports.object({
52019
52030
  function packageRoot(fromDir = dirname6(fileURLToPath(import.meta.url))) {
52020
52031
  let dir = fromDir;
52021
52032
  for (; ; ) {
52022
- const candidate = join20(dir, "package.json");
52033
+ const candidate = join21(dir, "package.json");
52023
52034
  if (existsSync13(candidate)) {
52024
52035
  try {
52025
52036
  if (JSON.parse(readFileSync3(candidate, "utf8")).name === "apple-notes-mcp")
@@ -52036,7 +52047,7 @@ function defaultPublicHelperDeps(overrides = {}) {
52036
52047
  return {
52037
52048
  env: process.env,
52038
52049
  platform: process.platform,
52039
- sourcePath: join20(packageRoot(), PUBLIC_HELPER_SOURCE),
52050
+ sourcePath: join21(packageRoot(), PUBLIC_HELPER_SOURCE),
52040
52051
  exists: existsSync13,
52041
52052
  readFile: (path10) => readFileSync3(path10),
52042
52053
  spawn: spawnSync2,
@@ -52047,14 +52058,14 @@ function defaultPublicHelperDeps(overrides = {}) {
52047
52058
  function publicHelperInstallDir(env = process.env) {
52048
52059
  const override = env[PUBLIC_HELPER_DIR_ENV]?.trim();
52049
52060
  if (override) return override;
52050
- return join20(homedir17(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
52061
+ return join21(homedir18(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
52051
52062
  }
52052
52063
  function sha256Hex(data) {
52053
52064
  return createHash2("sha256").update(data).digest("hex");
52054
52065
  }
52055
52066
  function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
52056
52067
  const installDir = publicHelperInstallDir(deps.env);
52057
- const binaryPath = join20(installDir, PUBLIC_HELPER_BINARY);
52068
+ const binaryPath = join21(installDir, PUBLIC_HELPER_BINARY);
52058
52069
  const base = { installDir, binaryPath, sourcePath: deps.sourcePath, manifest: null };
52059
52070
  const fail = (reason, detail) => ({
52060
52071
  ...base,
@@ -52065,7 +52076,7 @@ function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
52065
52076
  if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
52066
52077
  if (!deps.exists(deps.sourcePath))
52067
52078
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
52068
- const manifestPath = join20(installDir, PUBLIC_HELPER_MANIFEST);
52079
+ const manifestPath = join21(installDir, PUBLIC_HELPER_MANIFEST);
52069
52080
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
52070
52081
  return fail(
52071
52082
  "helper_not_installed",
@@ -52342,11 +52353,11 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
52342
52353
  steps.push({ step: "find compiler", ok: true, detail: compiler });
52343
52354
  const installDir = publicHelperInstallDir(deps.env);
52344
52355
  mkdirSync5(installDir, { recursive: true, mode: 448 });
52345
- const staging = mkdtempSync5(join20(installDir, ".staging-"));
52356
+ const staging = mkdtempSync5(join21(installDir, ".staging-"));
52346
52357
  try {
52347
- const stagedBinary = join20(staging, PUBLIC_HELPER_BINARY);
52348
- const digestPath = join20(staging, "source-digest.swift");
52349
- const plistPath = join20(staging, "Info.plist");
52358
+ const stagedBinary = join21(staging, PUBLIC_HELPER_BINARY);
52359
+ const digestPath = join21(staging, "source-digest.swift");
52360
+ const plistPath = join21(staging, "Info.plist");
52350
52361
  writeFileSync4(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
52351
52362
  writeFileSync4(plistPath, publicHelperInfoPlist(), { mode: 384 });
52352
52363
  const compile = deps.spawn(
@@ -52409,9 +52420,9 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
52409
52420
  compiler
52410
52421
  };
52411
52422
  chmodSync(stagedBinary, 448);
52412
- renameSync(stagedBinary, join20(installDir, PUBLIC_HELPER_BINARY));
52423
+ renameSync(stagedBinary, join21(installDir, PUBLIC_HELPER_BINARY));
52413
52424
  writeFileSync4(
52414
- join20(installDir, PUBLIC_HELPER_MANIFEST),
52425
+ join21(installDir, PUBLIC_HELPER_MANIFEST),
52415
52426
  JSON.stringify(manifest, null, 2) + "\n",
52416
52427
  { mode: 384 }
52417
52428
  );
@@ -52863,10 +52874,10 @@ function renderNotesMarkdown(notes, ctx, { wrap: wrap3 = 0 } = {}) {
52863
52874
 
52864
52875
  // src/utils/noteExportData.ts
52865
52876
  import { execFileSync as execFileSync18 } from "node:child_process";
52866
- import { homedir as homedir18 } from "node:os";
52867
- import { join as join21 } from "node:path";
52868
- var NOTES_DB_PATH11 = join21(
52869
- homedir18(),
52877
+ import { homedir as homedir19 } from "node:os";
52878
+ import { join as join22 } from "node:path";
52879
+ var NOTES_DB_PATH11 = join22(
52880
+ homedir19(),
52870
52881
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
52871
52882
  );
52872
52883
  var IMAGE_UTIS2 = /* @__PURE__ */ new Set([
@@ -53177,16 +53188,16 @@ var NOTE_PLACEHOLDERS = [
53177
53188
  "exportStem"
53178
53189
  ];
53179
53190
  var PLACEHOLDER_MODIFIERS = ["raw", "yaml"];
53180
- var wrap2 = (before, after = "", join33) => ({
53191
+ var wrap2 = (before, after = "", join35) => ({
53181
53192
  mode: "wrap",
53182
53193
  before,
53183
53194
  after,
53184
- ...join33 ? { join: join33 } : {}
53195
+ ...join35 ? { join: join35 } : {}
53185
53196
  });
53186
- var pattern = (value, join33) => ({
53197
+ var pattern = (value, join35) => ({
53187
53198
  mode: "pattern",
53188
53199
  value,
53189
- ...join33 ? { join: join33 } : {}
53200
+ ...join35 ? { join: join35 } : {}
53190
53201
  });
53191
53202
  var STANDARD = {
53192
53203
  schemaVersion: 1,
@@ -54074,7 +54085,7 @@ import {
54074
54085
  unlinkSync as unlinkSync3,
54075
54086
  writeSync as writeSync4
54076
54087
  } from "node:fs";
54077
- import { extname as extname4, isAbsolute as isAbsolute3, join as join22, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
54088
+ import { extname as extname4, isAbsolute as isAbsolute3, join as join23, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
54078
54089
  var CHUNK = 1024 * 1024;
54079
54090
  function openRegular(path10) {
54080
54091
  const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
@@ -54145,7 +54156,7 @@ var HashedSidecarWriter = class {
54145
54156
  if (directoryError) return { error: directoryError };
54146
54157
  const name = safeAssetName(asset.name, mime);
54147
54158
  const ext = extname4(name);
54148
- const target = join22(
54159
+ const target = join23(
54149
54160
  this.dir,
54150
54161
  `${name.slice(0, name.length - ext.length)}-${hash.slice(0, 8)}${ext}`
54151
54162
  );
@@ -54182,7 +54193,7 @@ var HashedSidecarWriter = class {
54182
54193
  * remove it if the copy fails.
54183
54194
  */
54184
54195
  copy(source, target) {
54185
- const temp = join22(this.dir, `.asset-${randomBytes(6).toString("hex")}.tmp`);
54196
+ const temp = join23(this.dir, `.asset-${randomBytes(6).toString("hex")}.tmp`);
54186
54197
  writeNew(source, temp);
54187
54198
  try {
54188
54199
  linkSync(temp, target);
@@ -54449,7 +54460,7 @@ function exportNotesMarkdown(request, deps) {
54449
54460
  return { ...receipt, markdown };
54450
54461
  }
54451
54462
  function defaultSidecarDir(output) {
54452
- return join23(dirname7(output), `${basename4(output, extname5(output))}.assets`);
54463
+ return join24(dirname7(output), `${basename4(output, extname5(output))}.assets`);
54453
54464
  }
54454
54465
  function exportNotesHtml(request, deps) {
54455
54466
  if (!request.outputPath)
@@ -54696,7 +54707,7 @@ import {
54696
54707
  writeFileSync as writeFileSync6
54697
54708
  } from "node:fs";
54698
54709
  import { tmpdir as tmpdir6 } from "node:os";
54699
- import { basename as basename6, extname as extname7, isAbsolute as isAbsolute4, join as join25 } from "node:path";
54710
+ import { basename as basename6, extname as extname7, isAbsolute as isAbsolute4, join as join26 } from "node:path";
54700
54711
 
54701
54712
  // src/utils/pasteboardFreeze.ts
54702
54713
  import { execFileSync as execFileSync19 } from "node:child_process";
@@ -54712,7 +54723,7 @@ import {
54712
54723
  writeFileSync as writeFileSync5
54713
54724
  } from "node:fs";
54714
54725
  import { tmpdir as tmpdir5 } from "node:os";
54715
- import { basename as basename5, extname as extname6, join as join24 } from "node:path";
54726
+ import { basename as basename5, extname as extname6, join as join25 } from "node:path";
54716
54727
  var PASTEBOARD_NAME_ENV = "APPLE_NOTES_MCP_PASTEBOARD_NAME";
54717
54728
  var MAX_PASTEBOARD_BYTES = 64 * 1024 * 1024;
54718
54729
  var PASTEBOARD_DATA_TYPES = [
@@ -54888,7 +54899,7 @@ function readRegularFile(path10) {
54888
54899
  }
54889
54900
  }
54890
54901
  function freezePasteboard(options = {}) {
54891
- const directory = mkdtempSync6(join24(tmpdir5(), "notes-pasteboard-"));
54902
+ const directory = mkdtempSync6(join25(tmpdir5(), "notes-pasteboard-"));
54892
54903
  const cleanup = () => rmSync6(directory, { recursive: true, force: true });
54893
54904
  try {
54894
54905
  const prefs = JSON.stringify(PASTEBOARD_DATA_TYPES.map((t) => [t.type, t.ext]));
@@ -54917,15 +54928,15 @@ function freezePasteboard(options = {}) {
54917
54928
  if (reply.kind === "file") {
54918
54929
  const bytes = readRegularFile(reply.path);
54919
54930
  const filename2 = basename5(reply.path);
54920
- const path11 = join24(directory, filename2);
54931
+ const path11 = join25(directory, filename2);
54921
54932
  writeFileSync5(path11, bytes, { mode: 384 });
54922
54933
  return { kind: "file", type: reply.type, path: path11, filename: filename2, bytes: bytes.length, cleanup };
54923
54934
  }
54924
- if (join24(directory, basename5(reply.path)) !== reply.path)
54935
+ if (join25(directory, basename5(reply.path)) !== reply.path)
54925
54936
  throw new PasteboardError("write_failed", MESSAGES.write_failed);
54926
54937
  const label = PASTEBOARD_DATA_TYPES.find((t) => t.type === reply.type)?.label ?? "item";
54927
54938
  const filename = `Pasted ${label}${extname6(reply.path)}`;
54928
- const path10 = join24(directory, filename);
54939
+ const path10 = join25(directory, filename);
54929
54940
  writeFileSync5(path10, readFileSync4(reply.path), { mode: 384 });
54930
54941
  rmSync6(reply.path, { force: true });
54931
54942
  return { kind: "data", type: reply.type, path: path10, filename, bytes: statSync4(path10).size, cleanup };
@@ -55284,8 +55295,8 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
55284
55295
  const bytes = localAttachment(path10);
55285
55296
  const beforeAttachments = manager.listAttachmentsById(id2);
55286
55297
  const beforeStored = storedAttachmentIds(manager, id2);
55287
- const directory = mkdtempSync7(join25(tmpdir6(), "notes-attachment-add-"));
55288
- const temporaryFile = join25(directory, name);
55298
+ const directory = mkdtempSync7(join26(tmpdir6(), "notes-attachment-add-"));
55299
+ const temporaryFile = join26(directory, name);
55289
55300
  try {
55290
55301
  writeFileSync6(temporaryFile, bytes, { mode: 384 });
55291
55302
  if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
@@ -55314,7 +55325,7 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
55314
55325
  if (inserted.length === 1 && !(persistentReturnedId && returnedId !== inserted[0].id)) {
55315
55326
  attachmentId = inserted[0].id;
55316
55327
  reportedName = inserted[0].name;
55317
- const verifyPath = join25(directory, "verify", "attachment.bin");
55328
+ const verifyPath = join26(directory, "verify", "attachment.bin");
55318
55329
  const saved = manager.saveAttachmentById(id2, attachmentId, verifyPath);
55319
55330
  if (!saved.success || !fileMatches(saved.savedPath ?? verifyPath, bytes.length, sha256(bytes)))
55320
55331
  throw new Error("Attachment bytes were not verified; read the exact note before retrying");
@@ -55322,7 +55333,7 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
55322
55333
  const stored = inserted.length === 0 && beforeStored ? storedInsertion(manager, id2, beforeStored, bytes, name, returnedId) : null;
55323
55334
  if (!stored)
55324
55335
  throw new Error(
55325
- inserted.length === 0 && !beforeStored ? `${UNCERTAIN}. Notes' AppleScript does not list some attachments (PDFs on macOS 27); grant Full Disk Access so the server can verify through the Notes database` : UNCERTAIN
55336
+ inserted.length === 0 && !beforeStored ? `${UNCERTAIN}. Notes' AppleScript does not list some attachments (PDFs on macOS 27); Full Disk Access is required to verify through the Notes database. ${fdaRemediation()}` : UNCERTAIN
55326
55337
  );
55327
55338
  attachmentId = stored.attachmentId;
55328
55339
  reportedName = stored.name;
@@ -55436,7 +55447,7 @@ function classify(error2) {
55436
55447
  const message = error2 instanceof Error ? error2.message : String(error2);
55437
55448
  if (/authorization denied|unable to open database|not authorized/i.test(message)) {
55438
55449
  return new FolderStoreError(
55439
- "Full Disk Access is required to verify folder type and contents before deleting. Grant it to the Node binary running this server (or the terminal that launches it), then relaunch it.",
55450
+ `Full Disk Access is required to verify folder type and contents before deleting. ${fdaRemediation()}`,
55440
55451
  "no_fda"
55441
55452
  );
55442
55453
  }
@@ -55711,8 +55722,8 @@ import {
55711
55722
  unlinkSync as unlinkSync4,
55712
55723
  writeSync as writeSync5
55713
55724
  } from "node:fs";
55714
- import { homedir as homedir20 } from "node:os";
55715
- import { isAbsolute as isAbsolute5, join as join27, resolve as resolve6 } from "node:path";
55725
+ import { homedir as homedir21 } from "node:os";
55726
+ import { isAbsolute as isAbsolute5, join as join28, resolve as resolve6 } from "node:path";
55716
55727
  var TEMPLATE_SLUG = /^[a-z0-9][a-z0-9_-]{0,63}$/;
55717
55728
  var TemplateStoreError = class extends Error {
55718
55729
  constructor(code, message) {
@@ -55732,7 +55743,7 @@ function templateDir(env = process.env) {
55732
55743
  );
55733
55744
  return resolve6(override);
55734
55745
  }
55735
- return join27(homedir20(), "Library/Application Support/apple-notes-mcp/templates");
55746
+ return join28(homedir21(), "Library/Application Support/apple-notes-mcp/templates");
55736
55747
  }
55737
55748
  function checkName(name, action) {
55738
55749
  if (isBuiltinTemplate(name) && action !== "read")
@@ -55769,7 +55780,7 @@ var TemplateStore = class {
55769
55780
  return this.dir;
55770
55781
  }
55771
55782
  file(name) {
55772
- return join27(this.dir, `${name}.json`);
55783
+ return join28(this.dir, `${name}.json`);
55773
55784
  }
55774
55785
  /**
55775
55786
  * Read a saved template's text, refusing symlinks and non-regular files.
@@ -55895,7 +55906,7 @@ var TemplateStore = class {
55895
55906
  "template-exists",
55896
55907
  `A template named "${name}" already exists. Pass force: true to replace it.`
55897
55908
  );
55898
- const temp = join27(this.dir, `.${name}.${randomBytes2(6).toString("hex")}.tmp`);
55909
+ const temp = join28(this.dir, `.${name}.${randomBytes2(6).toString("hex")}.tmp`);
55899
55910
  const fd = openSync8(
55900
55911
  temp,
55901
55912
  constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
@@ -57114,7 +57125,7 @@ function parseXml(source, limits) {
57114
57125
  (own ??= /* @__PURE__ */ new Map()).set(prefix, a.value);
57115
57126
  }
57116
57127
  const scope2 = own ? { own, parent: parentScope } : parentScope ?? { own: null, parent: null };
57117
- const resolve10 = (qualified, isAttribute) => {
57128
+ const resolve11 = (qualified, isAttribute) => {
57118
57129
  const colon = qualified.indexOf(":");
57119
57130
  if (colon < 0)
57120
57131
  return {
@@ -57128,12 +57139,12 @@ function parseXml(source, limits) {
57128
57139
  if (ns === void 0) fail(`Undeclared namespace prefix "${prefix.slice(0, 40)}"`);
57129
57140
  return { local, ns };
57130
57141
  };
57131
- const resolved = resolve10(name, false);
57142
+ const resolved = resolve11(name, false);
57132
57143
  const element = {
57133
57144
  name,
57134
57145
  local: resolved.local,
57135
57146
  ns: resolved.ns,
57136
- attributes: rawAttributes.filter((a) => a.name !== "xmlns" && !a.name.startsWith("xmlns:")).map((a) => ({ name: a.name, value: a.value, ...resolve10(a.name, true) })),
57147
+ attributes: rawAttributes.filter((a) => a.name !== "xmlns" && !a.name.startsWith("xmlns:")).map((a) => ({ name: a.name, value: a.value, ...resolve11(a.name, true) })),
57137
57148
  children: [],
57138
57149
  text: "",
57139
57150
  line: tagLine
@@ -58306,6 +58317,897 @@ function registerNativeTagsBridge(server2, manager) {
58306
58317
  );
58307
58318
  }
58308
58319
 
58320
+ // src/services/broker.ts
58321
+ import { spawnSync as spawnSync3 } from "node:child_process";
58322
+ import {
58323
+ chmodSync as chmodSync2,
58324
+ existsSync as existsSync14,
58325
+ lstatSync as lstatSync6,
58326
+ mkdirSync as mkdirSync9,
58327
+ mkdtempSync as mkdtempSync8,
58328
+ readFileSync as readFileSync5,
58329
+ realpathSync as realpathSync4,
58330
+ renameSync as renameSync3,
58331
+ rmdirSync,
58332
+ rmSync as rmSync8,
58333
+ writeFileSync as writeFileSync7
58334
+ } from "node:fs";
58335
+ import { homedir as homedir22 } from "node:os";
58336
+ import { isAbsolute as isAbsolute6, join as join29, resolve as resolve7 } from "node:path";
58337
+
58338
+ // src/services/brokerClient.ts
58339
+ import { connect as netConnect } from "node:net";
58340
+ var BrokerUnreachableError = class extends Error {
58341
+ constructor(message, code) {
58342
+ super(message);
58343
+ this.code = code;
58344
+ this.name = "BrokerUnreachableError";
58345
+ }
58346
+ code;
58347
+ };
58348
+ var MAX_ANSWER_BYTES = 65536;
58349
+ function requestBroker(socketPath, request, timeoutMs, connect = (path10) => netConnect({ path: path10, allowHalfOpen: true })) {
58350
+ return new Promise((resolve11, reject) => {
58351
+ const socket = connect(socketPath);
58352
+ const buffered = Buffer.allocUnsafe(MAX_ANSWER_BYTES);
58353
+ let bufferedBytes = 0;
58354
+ let settled = false;
58355
+ const fail = (message, code) => {
58356
+ if (settled) return;
58357
+ settled = true;
58358
+ clearTimeout(timer);
58359
+ socket.destroy();
58360
+ reject(new BrokerUnreachableError(message, code));
58361
+ };
58362
+ const timer = setTimeout(
58363
+ () => fail(`The broker did not answer within ${timeoutMs} ms.`, "timeout"),
58364
+ timeoutMs
58365
+ );
58366
+ socket.once("connect", () => {
58367
+ socket.write(JSON.stringify(request) + "\n");
58368
+ });
58369
+ const onError = (error2) => fail(`Could not reach the broker at ${socketPath}: ${error2.message}`, error2.code ?? "error");
58370
+ const onClose = () => fail("The broker closed the connection without answering.", "closed");
58371
+ socket.on("error", onError);
58372
+ socket.on("close", onClose);
58373
+ socket.on("end", onClose);
58374
+ const onData = (chunk) => {
58375
+ if (settled) return;
58376
+ const newline = chunk.indexOf(10);
58377
+ const answerBytes = newline < 0 ? chunk.length : newline;
58378
+ if (bufferedBytes + answerBytes > MAX_ANSWER_BYTES) {
58379
+ fail("The broker's answer was too long.", "bad_answer");
58380
+ return;
58381
+ }
58382
+ chunk.copy(buffered, bufferedBytes, 0, answerBytes);
58383
+ bufferedBytes += answerBytes;
58384
+ if (newline < 0) return;
58385
+ let answer;
58386
+ try {
58387
+ answer = JSON.parse(buffered.subarray(0, bufferedBytes).toString("utf8"));
58388
+ } catch {
58389
+ fail("The broker's answer was not JSON.", "bad_answer");
58390
+ return;
58391
+ }
58392
+ if (!answer || typeof answer !== "object" || Array.isArray(answer)) {
58393
+ fail("The broker's answer was not a JSON object.", "bad_answer");
58394
+ return;
58395
+ }
58396
+ settled = true;
58397
+ clearTimeout(timer);
58398
+ socket.off("data", onData);
58399
+ socket.off("close", onClose);
58400
+ socket.off("end", onClose);
58401
+ socket.off("error", onError);
58402
+ socket.pause();
58403
+ resolve11({
58404
+ answer,
58405
+ socket,
58406
+ leftover: chunk.subarray(newline + 1)
58407
+ });
58408
+ };
58409
+ socket.on("data", onData);
58410
+ });
58411
+ }
58412
+ async function pingBroker(socketPath, timeoutMs = 1500) {
58413
+ try {
58414
+ const { answer, socket } = await requestBroker(socketPath, { type: "ping" }, timeoutMs);
58415
+ socket.destroy();
58416
+ return answer.type === "pong";
58417
+ } catch {
58418
+ return false;
58419
+ }
58420
+ }
58421
+
58422
+ // src/services/broker.ts
58423
+ var BROKER_PROTOCOL = 3;
58424
+ var BROKER_BUNDLE_ID = "apple-notes-mcp.broker";
58425
+ var BROKER_LABEL = BROKER_BUNDLE_ID;
58426
+ var BROKER_APP_NAME = "Apple Notes MCP Broker.app";
58427
+ var BROKER_EXECUTABLE = "apple-notes-mcp-broker";
58428
+ var BROKER_SOURCE = "native/broker/apple-notes-mcp-broker.swift";
58429
+ var BROKER_MANIFEST = "manifest.json";
58430
+ var BROKER_SOCKET = "broker.sock";
58431
+ var BROKER_SETUP_COMMAND = "apple-notes-mcp setup --broker";
58432
+ var BROKER_LOG_COMMAND = `log show --last 10m --predicate 'subsystem == "${BROKER_BUNDLE_ID}"'`;
58433
+ var BROKERED_ENV = "APPLE_NOTES_MCP_BROKERED";
58434
+ var BROKER_APP_ENV = "APPLE_NOTES_MCP_BROKER_APP";
58435
+ var BROKER_MODE_ENV = "APPLE_NOTES_MCP_BROKER";
58436
+ var BROKER_DIR_ENV = "APPLE_NOTES_MCP_BROKER_DIR";
58437
+ var BROKER_APP_DIR_ENV = "APPLE_NOTES_MCP_BROKER_APP_DIR";
58438
+ var BROKER_AGENT_DIR_ENV = "APPLE_NOTES_MCP_BROKER_AGENT_DIR";
58439
+ var BROKER_SIGN_IDENTITY_ENV = "APPLE_NOTES_MCP_BROKER_SIGN_IDENTITY";
58440
+ var MAX_SOCKET_PATH_BYTES = 103;
58441
+ var brokerManifestSchema = external_exports.object({
58442
+ schemaVersion: external_exports.literal(2),
58443
+ protocolVersion: external_exports.number().int(),
58444
+ packageVersion: external_exports.string(),
58445
+ sourceSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
58446
+ binarySha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
58447
+ nodeSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
58448
+ entrySha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
58449
+ appPath: external_exports.string(),
58450
+ agentPath: external_exports.string(),
58451
+ socketPath: external_exports.string(),
58452
+ logPath: external_exports.string(),
58453
+ nodePath: external_exports.string(),
58454
+ entryPath: external_exports.string(),
58455
+ signing: external_exports.object({
58456
+ identity: external_exports.string(),
58457
+ teamId: external_exports.string().nullable(),
58458
+ stable: external_exports.boolean()
58459
+ }),
58460
+ builtAt: external_exports.string(),
58461
+ compiler: external_exports.string()
58462
+ });
58463
+ var brokerConfigSchema = external_exports.object({
58464
+ schemaVersion: external_exports.literal(1),
58465
+ nodePath: external_exports.string(),
58466
+ nodeSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
58467
+ packageVersion: external_exports.string(),
58468
+ entrySha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
58469
+ }).strict();
58470
+ function brokerResources(appPath) {
58471
+ const resources = join29(appPath, "Contents", "Resources");
58472
+ return {
58473
+ resources,
58474
+ configPath: join29(resources, "broker-config.json"),
58475
+ entryPath: join29(resources, "server", "build", "index.js"),
58476
+ packagePath: join29(resources, "server", "package.json"),
58477
+ sourcePath: join29(resources, "server", BROKER_SOURCE),
58478
+ serverConfigPath: join29(resources, "config.json"),
58479
+ disabledHelpers: join29(resources, "disabled-helpers")
58480
+ };
58481
+ }
58482
+ function brokerPaths(env = process.env) {
58483
+ const home = homedir22();
58484
+ const stateDir = resolve7(
58485
+ env[BROKER_DIR_ENV]?.trim() || join29(home, "Library", "Application Support", "apple-notes-mcp", "broker")
58486
+ );
58487
+ const appDir = env[BROKER_APP_DIR_ENV]?.trim() || join29(home, "Applications");
58488
+ const agentDir = env[BROKER_AGENT_DIR_ENV]?.trim() || join29(home, "Library", "LaunchAgents");
58489
+ const appPath = join29(appDir, BROKER_APP_NAME);
58490
+ return {
58491
+ stateDir,
58492
+ manifestPath: join29(stateDir, BROKER_MANIFEST),
58493
+ socketPath: join29(stateDir, BROKER_SOCKET),
58494
+ appDir,
58495
+ appPath,
58496
+ executablePath: join29(appPath, "Contents", "MacOS", BROKER_EXECUTABLE),
58497
+ agentPath: join29(agentDir, `${BROKER_LABEL}.plist`),
58498
+ logPath: join29(home, "Library", "Logs", "apple-notes-mcp-broker.log")
58499
+ };
58500
+ }
58501
+ function defaultBrokerDeps(overrides = {}) {
58502
+ const root = packageRoot();
58503
+ let packageVersion = "0.0.0";
58504
+ try {
58505
+ packageVersion = JSON.parse(readFileSync5(join29(root, "package.json"), "utf8")).version ?? packageVersion;
58506
+ } catch {
58507
+ }
58508
+ return {
58509
+ env: process.env,
58510
+ platform: process.platform,
58511
+ sourcePath: join29(root, BROKER_SOURCE),
58512
+ entryPath: join29(root, "build", "index.js"),
58513
+ execPath: process.execPath,
58514
+ packageVersion,
58515
+ uid: process.getuid?.() ?? -1,
58516
+ exists: existsSync14,
58517
+ readFile: (path10) => readFileSync5(path10),
58518
+ realpath: (path10) => realpathSync4(path10),
58519
+ removePath: rmSync8,
58520
+ removeEmptyDirectory: (path10) => rmdirSync(path10),
58521
+ spawn: spawnSync3,
58522
+ ping: (socketPath) => pingBroker(socketPath),
58523
+ sleep: (ms) => new Promise((resolve11) => setTimeout(resolve11, ms)),
58524
+ now: () => /* @__PURE__ */ new Date(),
58525
+ ...overrides
58526
+ };
58527
+ }
58528
+ function inspectBroker(deps = defaultBrokerDeps()) {
58529
+ const paths = brokerPaths(deps.env);
58530
+ const result = (reason, detail, manifest2) => ({
58531
+ installed: manifest2 !== null,
58532
+ ready: reason === null,
58533
+ reason,
58534
+ detail,
58535
+ paths,
58536
+ manifest: manifest2
58537
+ });
58538
+ if (deps.platform !== "darwin") return result("unsupported_platform", "macOS only", null);
58539
+ const rebuild = `Run \`${BROKER_SETUP_COMMAND}\` again.`;
58540
+ if (!deps.exists(paths.manifestPath))
58541
+ return result("broker_not_installed", "The permission broker is not installed.", null);
58542
+ let manifest;
58543
+ try {
58544
+ manifest = brokerManifestSchema.parse(
58545
+ JSON.parse(deps.readFile(paths.manifestPath).toString("utf8"))
58546
+ );
58547
+ } catch {
58548
+ return result("broker_manifest_invalid", `The broker manifest is unreadable. ${rebuild}`, null);
58549
+ }
58550
+ const resources = brokerResources(paths.appPath);
58551
+ if (manifest.appPath !== paths.appPath || manifest.agentPath !== paths.agentPath || manifest.socketPath !== paths.socketPath || manifest.logPath !== paths.logPath || manifest.entryPath !== resources.entryPath)
58552
+ return result(
58553
+ "broker_modified",
58554
+ `The broker manifest contains unexpected paths. ${rebuild}`,
58555
+ manifest
58556
+ );
58557
+ if (!deps.exists(paths.appPath) || !deps.exists(paths.executablePath))
58558
+ return result(
58559
+ "broker_not_installed",
58560
+ `The broker app is missing from ${paths.appPath}. ${rebuild}`,
58561
+ manifest
58562
+ );
58563
+ try {
58564
+ if (!deps.exists(deps.sourcePath) || !deps.exists(deps.entryPath) || manifest.sourceSha256 !== sha256Hex(deps.readFile(deps.sourcePath)) || manifest.entrySha256 !== sha256Hex(deps.readFile(deps.entryPath)) || manifest.packageVersion !== deps.packageVersion || manifest.protocolVersion !== BROKER_PROTOCOL)
58565
+ return result(
58566
+ "broker_stale",
58567
+ `The installed broker serves a different package or build. ${rebuild}`,
58568
+ manifest
58569
+ );
58570
+ if (!deps.exists(manifest.agentPath))
58571
+ return result(
58572
+ "broker_agent_missing",
58573
+ `The broker's LaunchAgent is missing (${manifest.agentPath}). ${rebuild}`,
58574
+ manifest
58575
+ );
58576
+ if (!deps.exists(manifest.nodePath))
58577
+ return result(
58578
+ "broker_node_missing",
58579
+ `The Node binary the broker launches is gone (${manifest.nodePath}). ${rebuild} Your grants stay with the broker app.`,
58580
+ manifest
58581
+ );
58582
+ if (!deps.exists(resources.entryPath))
58583
+ return result(
58584
+ "broker_entry_missing",
58585
+ `The bundled server entry point is gone (${resources.entryPath}). ${rebuild}`,
58586
+ manifest
58587
+ );
58588
+ const config2 = brokerConfigSchema.parse(
58589
+ JSON.parse(deps.readFile(resources.configPath).toString("utf8"))
58590
+ );
58591
+ if (sha256Hex(deps.readFile(paths.executablePath)) !== manifest.binarySha256 || sha256Hex(deps.readFile(resources.entryPath)) !== manifest.entrySha256 || sha256Hex(deps.readFile(resources.sourcePath)) !== manifest.sourceSha256 || deps.readFile(resources.serverConfigPath).toString("utf8") !== "{}\n" || !isAbsolute6(manifest.nodePath) || deps.realpath(manifest.nodePath) !== manifest.nodePath || sha256Hex(deps.readFile(manifest.nodePath)) !== manifest.nodeSha256 || config2.nodePath !== manifest.nodePath || config2.nodeSha256 !== manifest.nodeSha256 || config2.packageVersion !== manifest.packageVersion || config2.entrySha256 !== manifest.entrySha256)
58592
+ return result(
58593
+ "broker_modified",
58594
+ `The broker bundle, sealed configuration, or Node runtime no longer matches its installation. ${rebuild}`,
58595
+ manifest
58596
+ );
58597
+ const bundledPackage = JSON.parse(
58598
+ deps.readFile(resources.packagePath).toString("utf8")
58599
+ );
58600
+ if (bundledPackage.name !== "apple-notes-mcp" || bundledPackage.type !== "module" || bundledPackage.version !== manifest.packageVersion)
58601
+ return result(
58602
+ "broker_modified",
58603
+ `The bundled package metadata changed. ${rebuild}`,
58604
+ manifest
58605
+ );
58606
+ const verified = deps.spawn(
58607
+ "/usr/bin/codesign",
58608
+ ["--verify", "--strict", "--deep", paths.appPath],
58609
+ {
58610
+ encoding: "utf8",
58611
+ timeout: 3e4
58612
+ }
58613
+ );
58614
+ if (verified.status !== 0)
58615
+ return result("broker_modified", `The broker app signature is invalid. ${rebuild}`, manifest);
58616
+ } catch {
58617
+ return result(
58618
+ "broker_modified",
58619
+ `The broker installation could not be verified. ${rebuild}`,
58620
+ manifest
58621
+ );
58622
+ }
58623
+ return result(null, null, manifest);
58624
+ }
58625
+ function brokerInfoPlist(packageVersion) {
58626
+ const escape3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
58627
+ return [
58628
+ '<?xml version="1.0" encoding="UTF-8"?>',
58629
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
58630
+ '<plist version="1.0">',
58631
+ "<dict>",
58632
+ " <key>CFBundleIdentifier</key>",
58633
+ ` <string>${BROKER_BUNDLE_ID}</string>`,
58634
+ " <key>CFBundleName</key>",
58635
+ " <string>Apple Notes MCP Broker</string>",
58636
+ " <key>CFBundleDisplayName</key>",
58637
+ " <string>Apple Notes MCP Broker</string>",
58638
+ " <key>CFBundleExecutable</key>",
58639
+ ` <string>${BROKER_EXECUTABLE}</string>`,
58640
+ " <key>CFBundlePackageType</key>",
58641
+ " <string>APPL</string>",
58642
+ " <key>CFBundleInfoDictionaryVersion</key>",
58643
+ " <string>6.0</string>",
58644
+ " <key>CFBundleShortVersionString</key>",
58645
+ ` <string>${escape3(packageVersion)}</string>`,
58646
+ " <key>CFBundleVersion</key>",
58647
+ ` <string>${escape3(packageVersion)}</string>`,
58648
+ " <key>LSUIElement</key>",
58649
+ " <true/>",
58650
+ " <key>NSAppleEventsUsageDescription</key>",
58651
+ " <string>apple-notes-mcp reads and organizes your notes through Notes when an MCP client asks it to.</string>",
58652
+ "</dict>",
58653
+ "</plist>",
58654
+ ""
58655
+ ].join("\n");
58656
+ }
58657
+ function brokerCompileArguments(sourcePath, digestPath, outputPath) {
58658
+ return ["swiftc", "-O", "-parse-as-library", sourcePath, digestPath, "-o", outputPath];
58659
+ }
58660
+ function brokerLaunchAgentPlist(args) {
58661
+ const escape3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
58662
+ const programArguments = [args.executablePath, "serve", "--socket", args.socketPath];
58663
+ return [
58664
+ '<?xml version="1.0" encoding="UTF-8"?>',
58665
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
58666
+ '<plist version="1.0">',
58667
+ "<dict>",
58668
+ " <key>Label</key>",
58669
+ ` <string>${BROKER_LABEL}</string>`,
58670
+ " <key>AssociatedBundleIdentifiers</key>",
58671
+ " <array>",
58672
+ ` <string>${BROKER_BUNDLE_ID}</string>`,
58673
+ " </array>",
58674
+ " <key>ProgramArguments</key>",
58675
+ " <array>",
58676
+ ...programArguments.map((arg) => ` <string>${escape3(arg)}</string>`),
58677
+ " </array>",
58678
+ " <key>RunAtLoad</key>",
58679
+ " <true/>",
58680
+ " <key>KeepAlive</key>",
58681
+ " <true/>",
58682
+ " <key>StandardErrorPath</key>",
58683
+ " <string>/dev/null</string>",
58684
+ "</dict>",
58685
+ "</plist>",
58686
+ ""
58687
+ ].join("\n");
58688
+ }
58689
+ function chooseSigningIdentity(securityOutput, explicit) {
58690
+ const identities = [...securityOutput.matchAll(/^\s*\d+\)\s+([0-9A-F]{40})\s+"([^"]+)"/gm)].map(
58691
+ (m) => ({ identity: m[1], name: m[2] })
58692
+ );
58693
+ if (explicit && explicit.trim()) {
58694
+ const value = explicit.trim();
58695
+ const found = identities.find(
58696
+ (identity) => identity.identity === value || identity.name === value
58697
+ );
58698
+ return found ?? { identity: value, name: value === "-" ? "ad-hoc" : value };
58699
+ }
58700
+ const qualifying = identities.filter(
58701
+ (identity) => /^(Developer ID Application:|Apple Development:)/.test(identity.name)
58702
+ );
58703
+ if (qualifying.length > 1)
58704
+ throw new Error(
58705
+ "Multiple signing identities qualify. Choose one explicitly with --sign-identity <certificate SHA-1 or name>."
58706
+ );
58707
+ return qualifying[0] ?? { identity: "-", name: "ad-hoc" };
58708
+ }
58709
+ function parseTeamId(codesignOutput) {
58710
+ if (/Signature=adhoc/.test(codesignOutput)) return null;
58711
+ const match = codesignOutput.match(/^TeamIdentifier=(.+)$/m);
58712
+ const team = match?.[1]?.trim();
58713
+ return team && team !== "not set" ? team : null;
58714
+ }
58715
+ function chooseNodePath(deps) {
58716
+ const nodePath = deps.realpath(deps.execPath);
58717
+ if (!isAbsolute6(nodePath))
58718
+ throw new Error("The Node executable must resolve to an absolute path.");
58719
+ return nodePath;
58720
+ }
58721
+ function validateNodeLibraries(output) {
58722
+ const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
58723
+ let libraries = 0;
58724
+ for (const line of lines) {
58725
+ if (line.endsWith(":")) continue;
58726
+ const match = line.match(/^(.+?) \(compatibility version [^)]+\)$/);
58727
+ if (!match || !/^\/(?:usr\/lib|System\/Library)\//.test(match[1])) return false;
58728
+ if (match[1].split("/").includes("..")) return false;
58729
+ libraries++;
58730
+ }
58731
+ return libraries > 0;
58732
+ }
58733
+ function parseBrokerArgs(args) {
58734
+ const index = args.indexOf("--sign-identity");
58735
+ return {
58736
+ checkOnly: args.includes("--check"),
58737
+ uninstall: args.includes("--uninstall"),
58738
+ signIdentity: index >= 0 ? args[index + 1] : void 0
58739
+ };
58740
+ }
58741
+ function launchctl(deps, args) {
58742
+ return deps.spawn("/bin/launchctl", args, { encoding: "utf8", timeout: 3e4 });
58743
+ }
58744
+ async function stopBroker(deps, domain) {
58745
+ const target = `${domain}/${BROKER_LABEL}`;
58746
+ const step = "stop LaunchAgent";
58747
+ try {
58748
+ const stopped = deps.spawn("/bin/launchctl", ["bootout", target], {
58749
+ encoding: "utf8",
58750
+ timeout: 1e4,
58751
+ killSignal: "SIGKILL"
58752
+ });
58753
+ const stopDetail = stopped.status === 0 ? "" : `bootout: ${String(stopped.stderr || stopped.stdout || stopped.error?.message || `exit ${stopped.status}`).trim()}. `;
58754
+ for (let attempt = 0; attempt < 20; attempt++) {
58755
+ const probe = deps.spawn("/bin/launchctl", ["print", target], {
58756
+ encoding: "utf8",
58757
+ timeout: 1e3,
58758
+ killSignal: "SIGKILL"
58759
+ });
58760
+ const output = String(probe.stderr || probe.stdout || "");
58761
+ const absent = !probe.error && !probe.signal && probe.status === 113 && output.split("\n").some(
58762
+ (line) => line.trim().startsWith(`Could not find service "${BROKER_LABEL}" in domain`)
58763
+ );
58764
+ if (absent)
58765
+ return { step, ok: true, detail: `${stopDetail}Confirmed ${target} is not loaded.` };
58766
+ if (probe.status !== 0 || probe.error || probe.signal)
58767
+ return {
58768
+ step,
58769
+ ok: false,
58770
+ detail: `${stopDetail}Cannot confirm service removal: ${String(output || probe.error?.message || `exit ${probe.status}`).trim()}. Broker files were preserved.`
58771
+ };
58772
+ if (attempt < 19) await deps.sleep(250);
58773
+ }
58774
+ return {
58775
+ step,
58776
+ ok: false,
58777
+ detail: `${stopDetail}${target} is still loaded after 20 checks. Broker files were preserved.`
58778
+ };
58779
+ } catch (error2) {
58780
+ return {
58781
+ step,
58782
+ ok: false,
58783
+ detail: `Cannot confirm service removal: ${error2 instanceof Error ? error2.message : String(error2)}. Broker files were preserved.`
58784
+ };
58785
+ }
58786
+ }
58787
+ function removeBroker(deps, paths, warnings) {
58788
+ const failures = [];
58789
+ let stateIsSymlink = false;
58790
+ try {
58791
+ stateIsSymlink = lstatSync6(paths.stateDir).isSymbolicLink();
58792
+ } catch (error2) {
58793
+ if (error2.code !== "ENOENT")
58794
+ return {
58795
+ step: "remove broker",
58796
+ ok: false,
58797
+ detail: `${paths.stateDir}: ${error2 instanceof Error ? error2.message : String(error2)}`
58798
+ };
58799
+ }
58800
+ const remove = (path10) => {
58801
+ try {
58802
+ deps.removePath(path10, { force: true, recursive: path10 === paths.appPath });
58803
+ if (deps.exists(path10)) throw new Error("the path still exists after removal");
58804
+ } catch (error2) {
58805
+ failures.push(`${path10}: ${error2 instanceof Error ? error2.message : String(error2)}`);
58806
+ }
58807
+ };
58808
+ for (const path10 of [
58809
+ paths.agentPath,
58810
+ paths.appPath,
58811
+ ...stateIsSymlink ? [] : [paths.socketPath],
58812
+ paths.logPath
58813
+ ]) {
58814
+ remove(path10);
58815
+ }
58816
+ if (!failures.length) remove(stateIsSymlink ? paths.stateDir : paths.manifestPath);
58817
+ if (!failures.length && stateIsSymlink)
58818
+ warnings.push(
58819
+ `Removed the state-directory symlink ${paths.stateDir}; files in its target were left untouched.`
58820
+ );
58821
+ if (!failures.length && !stateIsSymlink) {
58822
+ try {
58823
+ deps.removeEmptyDirectory(paths.stateDir);
58824
+ if (deps.exists(paths.stateDir)) throw new Error("the directory still exists after removal");
58825
+ } catch (error2) {
58826
+ const code = error2.code;
58827
+ if (code === "ENOTEMPTY" || code === "EEXIST")
58828
+ warnings.push(
58829
+ `Kept ${paths.stateDir} because it contains other files. Only known broker artifacts were removed.`
58830
+ );
58831
+ else if (code !== "ENOENT")
58832
+ failures.push(
58833
+ `${paths.stateDir}: ${error2 instanceof Error ? error2.message : String(error2)}`
58834
+ );
58835
+ }
58836
+ }
58837
+ return {
58838
+ step: "remove broker",
58839
+ ok: failures.length === 0,
58840
+ detail: failures.length ? failures.join("\n") : paths.appPath
58841
+ };
58842
+ }
58843
+ async function waitForBroker(deps, socketPath) {
58844
+ for (let attempt = 0; attempt < 20; attempt++) {
58845
+ if (await deps.ping(socketPath)) return true;
58846
+ await deps.sleep(250);
58847
+ }
58848
+ return false;
58849
+ }
58850
+ async function setupBroker(options, deps = defaultBrokerDeps()) {
58851
+ const steps = [];
58852
+ const warnings = [];
58853
+ const paths = brokerPaths(deps.env);
58854
+ const mode = options.uninstall ? "uninstall" : options.checkOnly ? "check" : "install";
58855
+ const finish = async () => {
58856
+ const installation = inspectBroker(deps);
58857
+ const stopFailed = steps.some((step) => step.step === "stop LaunchAgent" && !step.ok);
58858
+ const running = installation.installed || stopFailed ? await deps.ping(paths.socketPath) : false;
58859
+ const ok2 = steps.every((s) => s.ok) && (mode === "uninstall" ? !installation.installed : installation.ready && running);
58860
+ return { ok: ok2, mode, steps, installation, running, warnings };
58861
+ };
58862
+ const domain = `gui/${deps.uid}`;
58863
+ if (deps.platform !== "darwin") {
58864
+ steps.push({ step: "platform", ok: false, detail: "macOS only" });
58865
+ return finish();
58866
+ }
58867
+ if (mode === "check") {
58868
+ const installation = inspectBroker(deps);
58869
+ steps.push({
58870
+ step: "inspect installed broker",
58871
+ ok: installation.ready,
58872
+ detail: installation.ready ? installation.paths.appPath : installation.detail ?? void 0
58873
+ });
58874
+ if (installation.manifest && !installation.manifest.signing.stable)
58875
+ warnings.push(signingWarning(installation.manifest.signing.teamId));
58876
+ return finish();
58877
+ }
58878
+ if (mode === "uninstall") {
58879
+ const stopped = await stopBroker(deps, domain);
58880
+ steps.push(stopped);
58881
+ if (!stopped.ok) return finish();
58882
+ steps.push(removeBroker(deps, paths, warnings));
58883
+ warnings.push(
58884
+ `The Full Disk Access and Automation entries for "Apple Notes MCP Broker" stay in System Settings until you remove them, or run \`tccutil reset All ${BROKER_BUNDLE_ID}\`.`
58885
+ );
58886
+ return finish();
58887
+ }
58888
+ if (Buffer.byteLength(paths.socketPath) > MAX_SOCKET_PATH_BYTES) {
58889
+ steps.push({
58890
+ step: "socket path",
58891
+ ok: false,
58892
+ detail: `${paths.socketPath} is longer than ${MAX_SOCKET_PATH_BYTES} bytes. Set ${BROKER_DIR_ENV} to a shorter directory.`
58893
+ });
58894
+ return finish();
58895
+ }
58896
+ if (!deps.exists(deps.sourcePath)) {
58897
+ steps.push({ step: "locate source", ok: false, detail: deps.sourcePath });
58898
+ return finish();
58899
+ }
58900
+ if (!deps.exists(deps.entryPath)) {
58901
+ steps.push({
58902
+ step: "locate server entry point",
58903
+ ok: false,
58904
+ detail: `${deps.entryPath} is missing. Build the package first.`
58905
+ });
58906
+ return finish();
58907
+ }
58908
+ const sourceSha = sha256Hex(deps.readFile(deps.sourcePath));
58909
+ steps.push({ step: "locate source", ok: true, detail: `sha256 ${sourceSha}` });
58910
+ let nodePath;
58911
+ let nodeSha256;
58912
+ const entry = deps.readFile(deps.entryPath);
58913
+ const entrySha256 = sha256Hex(entry);
58914
+ try {
58915
+ nodePath = chooseNodePath(deps);
58916
+ nodeSha256 = sha256Hex(deps.readFile(nodePath));
58917
+ const libraries = deps.spawn("/usr/bin/otool", ["-L", nodePath], {
58918
+ encoding: "utf8",
58919
+ timeout: 3e4
58920
+ });
58921
+ if (libraries.status !== 0 || !validateNodeLibraries(String(libraries.stdout ?? "")))
58922
+ throw new Error(
58923
+ "Node must link only to absolute /usr/lib or /System/Library libraries. Install a self-contained Node runtime (for example the official Node distribution), then run setup with it."
58924
+ );
58925
+ steps.push({ step: "verify Node runtime", ok: true, detail: nodePath });
58926
+ } catch (error2) {
58927
+ steps.push({
58928
+ step: "verify Node runtime",
58929
+ ok: false,
58930
+ detail: error2 instanceof Error ? error2.message : String(error2)
58931
+ });
58932
+ return finish();
58933
+ }
58934
+ const version3 = deps.spawn("/usr/bin/xcrun", ["swiftc", "--version"], { encoding: "utf8" });
58935
+ if (version3.status !== 0) {
58936
+ steps.push({
58937
+ step: "find compiler",
58938
+ ok: false,
58939
+ detail: "No Swift compiler found. Install the Command Line Tools with `xcode-select --install`."
58940
+ });
58941
+ return finish();
58942
+ }
58943
+ const compiler = String(version3.stdout || version3.stderr || "").split("\n").find((line) => line.includes("Swift version"))?.trim() || "swiftc";
58944
+ steps.push({ step: "find compiler", ok: true, detail: compiler });
58945
+ mkdirSync9(paths.stateDir, { recursive: true, mode: 448 });
58946
+ chmodSync2(paths.stateDir, 448);
58947
+ mkdirSync9(paths.appDir, { recursive: true });
58948
+ const staging = mkdtempSync8(join29(paths.appDir, ".apple-notes-mcp-broker-staging-"));
58949
+ try {
58950
+ const stagedApp = join29(staging, BROKER_APP_NAME);
58951
+ const macosDir = join29(stagedApp, "Contents", "MacOS");
58952
+ mkdirSync9(macosDir, { recursive: true });
58953
+ const stagedBinary = join29(macosDir, BROKER_EXECUTABLE);
58954
+ const digestPath = join29(staging, "source-digest.swift");
58955
+ writeFileSync7(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
58956
+ writeFileSync7(join29(stagedApp, "Contents", "Info.plist"), brokerInfoPlist(deps.packageVersion));
58957
+ const resources = brokerResources(stagedApp);
58958
+ mkdirSync9(join29(resources.entryPath, ".."), { recursive: true });
58959
+ mkdirSync9(join29(resources.sourcePath, ".."), { recursive: true });
58960
+ mkdirSync9(join29(resources.disabledHelpers, "public"), { recursive: true });
58961
+ mkdirSync9(join29(resources.disabledHelpers, "private"), { recursive: true });
58962
+ writeFileSync7(resources.entryPath, entry);
58963
+ writeFileSync7(resources.sourcePath, deps.readFile(deps.sourcePath));
58964
+ writeFileSync7(
58965
+ resources.packagePath,
58966
+ JSON.stringify({ name: "apple-notes-mcp", type: "module", version: deps.packageVersion }) + "\n"
58967
+ );
58968
+ writeFileSync7(resources.serverConfigPath, "{}\n");
58969
+ writeFileSync7(
58970
+ resources.configPath,
58971
+ JSON.stringify(
58972
+ {
58973
+ schemaVersion: 1,
58974
+ nodePath,
58975
+ nodeSha256,
58976
+ packageVersion: deps.packageVersion,
58977
+ entrySha256
58978
+ },
58979
+ null,
58980
+ 2
58981
+ ) + "\n"
58982
+ );
58983
+ const compile = deps.spawn(
58984
+ "/usr/bin/xcrun",
58985
+ brokerCompileArguments(deps.sourcePath, digestPath, stagedBinary),
58986
+ { encoding: "utf8", timeout: 3e5 }
58987
+ );
58988
+ if (compile.status !== 0) {
58989
+ steps.push({
58990
+ step: "compile",
58991
+ ok: false,
58992
+ detail: String(compile.stderr || compile.error?.message || "swiftc failed").slice(0, 4e3)
58993
+ });
58994
+ return finish();
58995
+ }
58996
+ rmSync8(digestPath, { force: true });
58997
+ steps.push({ step: "compile", ok: true });
58998
+ const identities = deps.spawn(
58999
+ "/usr/bin/security",
59000
+ ["find-identity", "-v", "-p", "codesigning"],
59001
+ { encoding: "utf8" }
59002
+ );
59003
+ let signing;
59004
+ try {
59005
+ signing = chooseSigningIdentity(
59006
+ String(identities.stdout ?? ""),
59007
+ options.signIdentity ?? deps.env[BROKER_SIGN_IDENTITY_ENV]
59008
+ );
59009
+ } catch (error2) {
59010
+ steps.push({
59011
+ step: "choose signing identity",
59012
+ ok: false,
59013
+ detail: error2 instanceof Error ? error2.message : String(error2)
59014
+ });
59015
+ return finish();
59016
+ }
59017
+ const sign = deps.spawn(
59018
+ "/usr/bin/codesign",
59019
+ [
59020
+ "--force",
59021
+ "--sign",
59022
+ signing.identity,
59023
+ "--identifier",
59024
+ BROKER_BUNDLE_ID,
59025
+ "--timestamp=none",
59026
+ "--options",
59027
+ "runtime",
59028
+ stagedApp
59029
+ ],
59030
+ { encoding: "utf8", timeout: 6e4 }
59031
+ );
59032
+ if (sign.status !== 0) {
59033
+ steps.push({
59034
+ step: "sign",
59035
+ ok: false,
59036
+ detail: `${signing.name}: ${String(sign.stderr || "codesign failed").trim()}`
59037
+ });
59038
+ return finish();
59039
+ }
59040
+ const described = deps.spawn("/usr/bin/codesign", ["-dv", "--verbose=2", stagedApp], {
59041
+ encoding: "utf8"
59042
+ });
59043
+ const description = String(described.stderr ?? "") + String(described.stdout ?? "");
59044
+ const teamId = parseTeamId(description);
59045
+ const stable = teamId !== null && /^Authority=Developer ID Application:/m.test(description);
59046
+ steps.push({
59047
+ step: "sign",
59048
+ ok: true,
59049
+ detail: teamId ? `${signing.name} (team ${teamId})` : "ad-hoc"
59050
+ });
59051
+ if (!stable) warnings.push(signingWarning(teamId));
59052
+ const verified = deps.spawn(
59053
+ "/usr/bin/codesign",
59054
+ ["--verify", "--strict", "--deep", stagedApp],
59055
+ {
59056
+ encoding: "utf8",
59057
+ timeout: 3e4
59058
+ }
59059
+ );
59060
+ if (verified.status !== 0) {
59061
+ steps.push({
59062
+ step: "verify signature",
59063
+ ok: false,
59064
+ detail: String(verified.stderr || "The signed bundle failed verification.").trim()
59065
+ });
59066
+ return finish();
59067
+ }
59068
+ steps.push({ step: "verify signature", ok: true });
59069
+ const hello = deps.spawn(stagedBinary, [], {
59070
+ input: JSON.stringify({ type: "hello" }) + "\n",
59071
+ encoding: "utf8",
59072
+ timeout: 1e4,
59073
+ killSignal: "SIGKILL"
59074
+ });
59075
+ let handshake = null;
59076
+ try {
59077
+ handshake = JSON.parse(String(hello.stdout ?? "").trim());
59078
+ } catch {
59079
+ handshake = null;
59080
+ }
59081
+ if (hello.status !== 0 || handshake?.protocolVersion !== BROKER_PROTOCOL || handshake?.sourceSha256 !== sourceSha || handshake?.packageVersion !== deps.packageVersion || handshake?.entrySha256 !== entrySha256) {
59082
+ steps.push({
59083
+ step: "handshake",
59084
+ ok: false,
59085
+ detail: handshake ? `broker reported protocol ${String(handshake.protocolVersion)}, source ${String(handshake.sourceSha256)}` : `no valid hello (exit ${hello.status})`
59086
+ });
59087
+ return finish();
59088
+ }
59089
+ steps.push({ step: "handshake", ok: true });
59090
+ const stopped = await stopBroker(deps, domain);
59091
+ steps.push(stopped);
59092
+ if (!stopped.ok) return finish();
59093
+ rmSync8(paths.appPath, { recursive: true, force: true });
59094
+ renameSync3(stagedApp, paths.appPath);
59095
+ const manifest = {
59096
+ schemaVersion: 2,
59097
+ protocolVersion: BROKER_PROTOCOL,
59098
+ packageVersion: deps.packageVersion,
59099
+ sourceSha256: sourceSha,
59100
+ binarySha256: sha256Hex(deps.readFile(paths.executablePath)),
59101
+ nodeSha256,
59102
+ entrySha256,
59103
+ appPath: paths.appPath,
59104
+ agentPath: paths.agentPath,
59105
+ socketPath: paths.socketPath,
59106
+ logPath: paths.logPath,
59107
+ nodePath,
59108
+ entryPath: brokerResources(paths.appPath).entryPath,
59109
+ signing: { identity: signing.name, teamId, stable },
59110
+ builtAt: deps.now().toISOString(),
59111
+ compiler
59112
+ };
59113
+ writeFileSync7(paths.manifestPath, JSON.stringify(manifest, null, 2) + "\n", { mode: 384 });
59114
+ steps.push({ step: "install app", ok: true, detail: paths.appPath });
59115
+ mkdirSync9(join29(paths.agentPath, ".."), { recursive: true });
59116
+ writeFileSync7(
59117
+ paths.agentPath,
59118
+ brokerLaunchAgentPlist({
59119
+ executablePath: paths.executablePath,
59120
+ socketPath: paths.socketPath
59121
+ }),
59122
+ { mode: 420 }
59123
+ );
59124
+ const bootstrap = launchctl(deps, ["bootstrap", domain, paths.agentPath]);
59125
+ if (bootstrap.status !== 0) {
59126
+ steps.push({
59127
+ step: "start LaunchAgent",
59128
+ ok: false,
59129
+ detail: String(bootstrap.stderr || bootstrap.stdout || "launchctl bootstrap failed").trim()
59130
+ });
59131
+ return finish();
59132
+ }
59133
+ steps.push({ step: "start LaunchAgent", ok: true, detail: paths.agentPath });
59134
+ } finally {
59135
+ rmSync8(staging, { recursive: true, force: true });
59136
+ }
59137
+ const answered = await waitForBroker(deps, paths.socketPath);
59138
+ steps.push({
59139
+ step: "broker answers",
59140
+ ok: answered,
59141
+ detail: answered ? paths.socketPath : `no answer on ${paths.socketPath}; inspect diagnostics with \`${BROKER_LOG_COMMAND}\``
59142
+ });
59143
+ return finish();
59144
+ }
59145
+ function signingWarning(teamId) {
59146
+ return teamId ? "This signing identity does not establish Developer ID continuity. Apple Development certificate renewal can change the designated requirement and require granting permissions again." : "The broker is ad-hoc signed, so macOS ties its grants to this exact build: after every rebuild, grant Full Disk Access and Automation again. A Developer ID Application identity can retain the bundle and team designated requirement across rebuilds.";
59147
+ }
59148
+ function formatBrokerSetup(report) {
59149
+ const lines = ["Apple Notes MCP permission broker", ""];
59150
+ for (const step of report.steps)
59151
+ lines.push(`${step.ok ? "\u2713" : "\u2717"} ${step.step}${step.detail ? `: ${step.detail}` : ""}`);
59152
+ for (const warning of report.warnings) lines.push(`! ${warning}`);
59153
+ lines.push("");
59154
+ const app = report.installation.paths.appPath;
59155
+ if (report.mode === "uninstall") {
59156
+ lines.push(
59157
+ report.ok ? "The broker is removed. MCP clients now run the server in-process again." : "The broker was not fully removed. Fix the failed step above and run it again."
59158
+ );
59159
+ } else if (report.ok) {
59160
+ lines.push(
59161
+ `The broker is ${report.mode === "check" ? "installed and" : "installed and"} running. MCP clients that launch apple-notes-mcp now reach it through the broker.`,
59162
+ "",
59163
+ "Grant it access once:",
59164
+ ` 1. System Settings > Privacy & Security > Full Disk Access: click +, add ${app}, and turn it on.`,
59165
+ " 2. The first Notes request through the broker asks to let Apple Notes MCP Broker control Notes. Click Allow.",
59166
+ "",
59167
+ "Restart your MCP client so it starts a fresh apple-notes-mcp process."
59168
+ );
59169
+ } else if (report.mode === "check") {
59170
+ lines.push(
59171
+ report.installation.installed && !report.running && report.installation.ready ? `The broker is installed but not answering. Inspect diagnostics with \`${BROKER_LOG_COMMAND}\`, or run \`${BROKER_SETUP_COMMAND}\` again.` : `Run \`${BROKER_SETUP_COMMAND}\` to install it.`
59172
+ );
59173
+ } else {
59174
+ lines.push("The broker was not installed. Fix the failed step above and run setup again.");
59175
+ }
59176
+ return lines.join("\n");
59177
+ }
59178
+ var proxyFallbackReason = null;
59179
+ function recordBrokerFallback(reason) {
59180
+ proxyFallbackReason = reason;
59181
+ }
59182
+ function brokerStatus(deps = defaultBrokerDeps()) {
59183
+ const inUse = deps.env[BROKERED_ENV] === "1";
59184
+ let installation = null;
59185
+ try {
59186
+ installation = inspectBroker(deps);
59187
+ } catch {
59188
+ installation = null;
59189
+ }
59190
+ const manifest = installation?.manifest ?? null;
59191
+ const base = {
59192
+ inUse,
59193
+ installed: installation?.installed ?? false,
59194
+ ready: installation?.ready ?? false,
59195
+ appPath: inUse ? deps.env[BROKER_APP_ENV] ?? manifest?.appPath ?? null : manifest?.appPath ?? null,
59196
+ stableSigning: manifest ? manifest.signing.stable : null,
59197
+ fallbackReason: inUse ? null : proxyFallbackReason
59198
+ };
59199
+ let detail;
59200
+ if (inUse)
59201
+ detail = `In use: this server runs under ${base.appPath ?? "the broker app"}, which holds the Full Disk Access and Automation grants.`;
59202
+ else if (!base.installed)
59203
+ detail = `Not installed. Grants apply to the app or Node binary that launches this server. \`${BROKER_SETUP_COMMAND}\` moves them to one signed app.`;
59204
+ else if (deps.env[BROKER_MODE_ENV] === "off")
59205
+ detail = `Installed but turned off with ${BROKER_MODE_ENV}=off; running in-process.`;
59206
+ else
59207
+ detail = `Installed but not in use; running in-process.${base.fallbackReason ? ` ${base.fallbackReason}` : ""}${installation?.detail ? ` ${installation.detail}` : ""}`;
59208
+ return { ...base, detail };
59209
+ }
59210
+
58309
59211
  // src/tools/nativeOperations.ts
58310
59212
  var VERIFIED_BACKGROUND = /* @__PURE__ */ new Set([
58311
59213
  "append-native",
@@ -58524,7 +59426,9 @@ function registerNativeOperations(server2, manager) {
58524
59426
  ])
58525
59427
  ),
58526
59428
  unavailable: UNAVAILABLE,
58527
- ...getCapabilityMatrix()
59429
+ ...getCapabilityMatrix(),
59430
+ // Whether this process runs under the permission broker (#220).
59431
+ broker: brokerStatus()
58528
59432
  };
58529
59433
  },
58530
59434
  true
@@ -58752,10 +59656,10 @@ function registerNativeOperations(server2, manager) {
58752
59656
  }
58753
59657
 
58754
59658
  // src/setupShortcuts.ts
58755
- import { spawnSync as spawnSync3 } from "node:child_process";
58756
- import { existsSync as existsSync14 } from "node:fs";
59659
+ import { spawnSync as spawnSync4 } from "node:child_process";
59660
+ import { existsSync as existsSync15 } from "node:fs";
58757
59661
  import { release as release3 } from "node:os";
58758
- import { dirname as dirname8, resolve as resolve7 } from "node:path";
59662
+ import { dirname as dirname8, resolve as resolve8 } from "node:path";
58759
59663
  import { fileURLToPath as fileURLToPath2 } from "node:url";
58760
59664
  var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
58761
59665
  var MARKDOWN_MIN_DARWIN_MAJOR = 25;
@@ -58773,16 +59677,16 @@ var shortcutFiles = [
58773
59677
  ];
58774
59678
  function setupShortcuts(checkOnly, dependencies = {}) {
58775
59679
  const status = dependencies.status || nativeTagsStatus;
58776
- const exists = dependencies.exists || existsSync14;
59680
+ const exists = dependencies.exists || existsSync15;
58777
59681
  const open = dependencies.open || ((path10) => {
58778
- const result = spawnSync3("/usr/bin/open", [path10], { encoding: "utf8" });
59682
+ const result = spawnSync4("/usr/bin/open", [path10], { encoding: "utf8" });
58779
59683
  return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
58780
59684
  });
58781
- const baseDirectory = dependencies.baseDirectory || resolve7(dirname8(fileURLToPath2(import.meta.url)), "../shortcuts");
59685
+ const baseDirectory = dependencies.baseDirectory || resolve8(dirname8(fileURLToPath2(import.meta.url)), "../shortcuts");
58782
59686
  const osRelease = (dependencies.osRelease || release3)();
58783
59687
  const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
58784
59688
  const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
58785
- const path10 = resolve7(baseDirectory, file);
59689
+ const path10 = resolve8(baseDirectory, file);
58786
59690
  let installed = false;
58787
59691
  let identifier;
58788
59692
  let error2;
@@ -58842,7 +59746,7 @@ function formatShortcutSetup(report) {
58842
59746
 
58843
59747
  // src/utils/noteAudio.ts
58844
59748
  import { readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
58845
- import { join as join28 } from "node:path";
59749
+ import { join as join30 } from "node:path";
58846
59750
  var EXTRA_AUDIO_UTIS = [
58847
59751
  "public.mp3",
58848
59752
  "public.aiff-audio",
@@ -58867,7 +59771,7 @@ function accountDirsFor(containerDir, accountIdentifier) {
58867
59771
  if (own) return [own];
58868
59772
  let names;
58869
59773
  try {
58870
- names = readdirSync5(join28(containerDir, "Accounts")).sort();
59774
+ names = readdirSync5(join30(containerDir, "Accounts")).sort();
58871
59775
  } catch {
58872
59776
  return [];
58873
59777
  }
@@ -58879,8 +59783,8 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
58879
59783
  if (!id2 || !filename) return null;
58880
59784
  const generation = safeComponent(media?.generation);
58881
59785
  for (const dir of accountDirsFor(containerDir, accountIdentifier)) {
58882
- const base = join28(dir, "Media", id2);
58883
- const candidates = generation ? [join28(base, generation, filename), join28(base, filename)] : [join28(base, filename)];
59786
+ const base = join30(dir, "Media", id2);
59787
+ const candidates = generation ? [join30(base, generation, filename), join30(base, filename)] : [join30(base, filename)];
58884
59788
  for (const candidate of candidates) {
58885
59789
  const real = realInside(candidate, dir);
58886
59790
  if (real && isFile2(real)) return real;
@@ -59151,25 +60055,25 @@ function formatTranscription(result) {
59151
60055
  }
59152
60056
 
59153
60057
  // src/services/privateHelperBuild.ts
59154
- import { spawnSync as spawnSync5 } from "node:child_process";
60058
+ import { spawnSync as spawnSync6 } from "node:child_process";
59155
60059
  import {
59156
- chmodSync as chmodSync2,
59157
- existsSync as existsSync16,
59158
- mkdirSync as mkdirSync9,
59159
- mkdtempSync as mkdtempSync8,
59160
- renameSync as renameSync3,
59161
- rmSync as rmSync8,
59162
- writeFileSync as writeFileSync7
60060
+ chmodSync as chmodSync3,
60061
+ existsSync as existsSync17,
60062
+ mkdirSync as mkdirSync10,
60063
+ mkdtempSync as mkdtempSync9,
60064
+ renameSync as renameSync4,
60065
+ rmSync as rmSync9,
60066
+ writeFileSync as writeFileSync8
59163
60067
  } from "node:fs";
59164
60068
  import { release as release4 } from "node:os";
59165
- import { join as join30 } from "node:path";
60069
+ import { join as join32 } from "node:path";
59166
60070
 
59167
60071
  // src/services/privateHelper.ts
59168
- import { spawnSync as spawnSync4 } from "node:child_process";
60072
+ import { spawnSync as spawnSync5 } from "node:child_process";
59169
60073
  import { createHash as createHash7 } from "node:crypto";
59170
- import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
59171
- import { homedir as homedir21 } from "node:os";
59172
- import { dirname as dirname9, join as join29, resolve as resolve8 } from "node:path";
60074
+ import { existsSync as existsSync16, readFileSync as readFileSync6 } from "node:fs";
60075
+ import { homedir as homedir23 } from "node:os";
60076
+ import { dirname as dirname9, join as join31, resolve as resolve9 } from "node:path";
59173
60077
  import { fileURLToPath as fileURLToPath3 } from "node:url";
59174
60078
  var PRIVATE_HELPER_PROTOCOL = 1;
59175
60079
  var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
@@ -59197,16 +60101,16 @@ var manifestSchema = external_exports.object({
59197
60101
  function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
59198
60102
  let dir = fromDir;
59199
60103
  for (; ; ) {
59200
- const candidate = join29(dir, "package.json");
59201
- if (existsSync15(candidate)) {
60104
+ const candidate = join31(dir, "package.json");
60105
+ if (existsSync16(candidate)) {
59202
60106
  try {
59203
- const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
60107
+ const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
59204
60108
  if (pkg.name === "apple-notes-mcp") return dir;
59205
60109
  } catch {
59206
60110
  }
59207
60111
  }
59208
60112
  const parent = dirname9(dir);
59209
- if (parent === dir) return resolve8(fromDir, "..");
60113
+ if (parent === dir) return resolve9(fromDir, "..");
59210
60114
  dir = parent;
59211
60115
  }
59212
60116
  }
@@ -59214,10 +60118,10 @@ function defaultDeps2(overrides = {}) {
59214
60118
  return {
59215
60119
  env: process.env,
59216
60120
  platform: process.platform,
59217
- sourcePath: join29(packageRoot2(), HELPER_SOURCE_RELATIVE),
59218
- exists: existsSync15,
59219
- readFile: (path10) => readFileSync5(path10),
59220
- spawn: spawnSync4,
60121
+ sourcePath: join31(packageRoot2(), HELPER_SOURCE_RELATIVE),
60122
+ exists: existsSync16,
60123
+ readFile: (path10) => readFileSync6(path10),
60124
+ spawn: spawnSync5,
59221
60125
  ...overrides
59222
60126
  };
59223
60127
  }
@@ -59227,14 +60131,14 @@ function privateHelperEnabled(env = process.env) {
59227
60131
  function helperInstallDir(env = process.env) {
59228
60132
  const override = env[HELPER_DIR_ENV]?.trim();
59229
60133
  if (override) return override;
59230
- return join29(homedir21(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
60134
+ return join31(homedir23(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
59231
60135
  }
59232
60136
  function sha256Hex2(data) {
59233
60137
  return createHash7("sha256").update(data).digest("hex");
59234
60138
  }
59235
60139
  function inspectInstallation(deps = defaultDeps2()) {
59236
60140
  const installDir = helperInstallDir(deps.env);
59237
- const binaryPath = join29(installDir, HELPER_BINARY_NAME);
60141
+ const binaryPath = join31(installDir, HELPER_BINARY_NAME);
59238
60142
  const base = {
59239
60143
  installDir,
59240
60144
  binaryPath,
@@ -59252,7 +60156,7 @@ function inspectInstallation(deps = defaultDeps2()) {
59252
60156
  if (!deps.exists(deps.sourcePath))
59253
60157
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
59254
60158
  base.expectedSourceSha256 = sha256Hex2(deps.readFile(deps.sourcePath));
59255
- const manifestPath = join29(installDir, MANIFEST_NAME);
60159
+ const manifestPath = join31(installDir, MANIFEST_NAME);
59256
60160
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
59257
60161
  return fail(
59258
60162
  "helper_not_installed",
@@ -59510,7 +60414,7 @@ function defaultBuildDeps() {
59510
60414
  return {
59511
60415
  ...defaultDeps2(),
59512
60416
  osVersion: () => {
59513
- const r = spawnSync5("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
60417
+ const r = spawnSync6("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
59514
60418
  return r.status === 0 ? r.stdout.trim() : `Darwin ${release4()}`;
59515
60419
  },
59516
60420
  now: () => /* @__PURE__ */ new Date()
@@ -59580,10 +60484,10 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
59580
60484
  const compiler = String(clangVersion.stdout || "").split("\n")[0] || "clang";
59581
60485
  steps.push({ step: "find compiler", ok: true, detail: compiler });
59582
60486
  const installDir = helperInstallDir(deps.env);
59583
- mkdirSync9(installDir, { recursive: true, mode: 448 });
59584
- const staging = mkdtempSync8(join30(installDir, ".staging-"));
60487
+ mkdirSync10(installDir, { recursive: true, mode: 448 });
60488
+ const staging = mkdtempSync9(join32(installDir, ".staging-"));
59585
60489
  try {
59586
- const stagedBinary = join30(staging, HELPER_BINARY_NAME);
60490
+ const stagedBinary = join32(staging, HELPER_BINARY_NAME);
59587
60491
  const compile = deps.spawn(
59588
60492
  "/usr/bin/xcrun",
59589
60493
  compileArguments(deps.sourcePath, stagedBinary, sourceSha),
@@ -59659,14 +60563,14 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
59659
60563
  osVersion: deps.osVersion(),
59660
60564
  compiler
59661
60565
  };
59662
- chmodSync2(stagedBinary, 448);
59663
- renameSync3(stagedBinary, join30(installDir, HELPER_BINARY_NAME));
59664
- writeFileSync7(join30(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
60566
+ chmodSync3(stagedBinary, 448);
60567
+ renameSync4(stagedBinary, join32(installDir, HELPER_BINARY_NAME));
60568
+ writeFileSync8(join32(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
59665
60569
  mode: 384
59666
60570
  });
59667
60571
  steps.push({ step: "install", ok: true, detail: installDir });
59668
60572
  } finally {
59669
- if (existsSync16(staging)) rmSync8(staging, { recursive: true, force: true });
60573
+ if (existsSync17(staging)) rmSync9(staging, { recursive: true, force: true });
59670
60574
  }
59671
60575
  const installation = inspectInstallation(deps);
59672
60576
  steps.push({
@@ -59695,7 +60599,7 @@ function formatHelperBuild(report) {
59695
60599
  }
59696
60600
 
59697
60601
  // src/services/permissions.ts
59698
- import { spawnSync as spawnSync6 } from "node:child_process";
60602
+ import { spawnSync as spawnSync7 } from "node:child_process";
59699
60603
  import { createInterface } from "node:readline";
59700
60604
  var PRIVACY = "System Settings > Privacy & Security";
59701
60605
  var PANE_ANCHORS = {
@@ -59731,7 +60635,7 @@ function checkPermissions(probes = defaultPermissionProbes(), options = defaultC
59731
60635
  const launchingApp = safe(probes.launchingApp, null);
59732
60636
  const who = launchingApp ?? "the app that launches the server";
59733
60637
  const items = [
59734
- fullDiskAccessItem(probes, macOSVersion),
60638
+ fullDiskAccessItem(probes, macOSVersion, options.env),
59735
60639
  automationItem(probes, macOSVersion, who, automationSkipReason(options)),
59736
60640
  shortcutsItem(probes),
59737
60641
  speechItem(probes, macOSVersion, who)
@@ -59754,7 +60658,7 @@ function safe(fn, fallback) {
59754
60658
  function errorText(error2) {
59755
60659
  return error2 instanceof Error ? error2.message : String(error2);
59756
60660
  }
59757
- function fullDiskAccessItem(probes, macOSVersion) {
60661
+ function fullDiskAccessItem(probes, macOSVersion, env) {
59758
60662
  const base = {
59759
60663
  id: "fullDiskAccess",
59760
60664
  title: "Full Disk Access",
@@ -59769,7 +60673,7 @@ function fullDiskAccessItem(probes, macOSVersion) {
59769
60673
  ...base,
59770
60674
  status: "unknown",
59771
60675
  detail: `could not probe the Notes database: ${errorText(error2)}`,
59772
- fix: fdaRemediation(probes.execPath)
60676
+ fix: fdaRemediation(probes.execPath, env)
59773
60677
  };
59774
60678
  }
59775
60679
  return granted ? {
@@ -59781,7 +60685,7 @@ function fullDiskAccessItem(probes, macOSVersion) {
59781
60685
  ...base,
59782
60686
  status: "missing",
59783
60687
  detail: "the Notes database is not readable, so query-notes, checklist state, note metadata, note links, native objects, exports and the bridges' readback do not work",
59784
- fix: fdaRemediation(probes.execPath)
60688
+ fix: fdaRemediation(probes.execPath, env)
59785
60689
  };
59786
60690
  }
59787
60691
  var PROBE_AUTOMATION_COMMAND = "apple-notes-mcp setup --permissions --probe-automation";
@@ -59939,7 +60843,7 @@ function findLaunchingApp(startPid = process.ppid, readProcess = readProcessEntr
59939
60843
  return null;
59940
60844
  }
59941
60845
  function readProcessEntry(pid) {
59942
- const result = spawnSync6("/bin/ps", ["-o", "ppid=", "-o", "comm=", "-p", String(pid)], {
60846
+ const result = spawnSync7("/bin/ps", ["-o", "ppid=", "-o", "comm=", "-p", String(pid)], {
59943
60847
  encoding: "utf8",
59944
60848
  timeout: 3e3
59945
60849
  });
@@ -59980,7 +60884,7 @@ function openSettingsPane(item, open = openUrl) {
59980
60884
  return open(item.settingsUrl);
59981
60885
  }
59982
60886
  function openUrl(url) {
59983
- const result = spawnSync6("/usr/bin/open", [url], { encoding: "utf8" });
60887
+ const result = spawnSync7("/usr/bin/open", [url], { encoding: "utf8" });
59984
60888
  return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
59985
60889
  }
59986
60890
  function pendingItems(report) {
@@ -60092,19 +60996,19 @@ function defaultPermissionsCliDeps(options) {
60092
60996
  }
60093
60997
 
60094
60998
  // src/services/permissionsWindow.ts
60095
- import { spawn as spawn2, spawnSync as spawnSync7 } from "node:child_process";
60999
+ import { spawn as spawn2, spawnSync as spawnSync8 } from "node:child_process";
60096
61000
  import {
60097
- chmodSync as chmodSync3,
60098
- existsSync as existsSync17,
60099
- mkdirSync as mkdirSync10,
60100
- mkdtempSync as mkdtempSync9,
60101
- readFileSync as readFileSync6,
60102
- renameSync as renameSync4,
60103
- rmSync as rmSync9,
60104
- writeFileSync as writeFileSync8
61001
+ chmodSync as chmodSync4,
61002
+ existsSync as existsSync18,
61003
+ mkdirSync as mkdirSync11,
61004
+ mkdtempSync as mkdtempSync10,
61005
+ readFileSync as readFileSync7,
61006
+ renameSync as renameSync5,
61007
+ rmSync as rmSync10,
61008
+ writeFileSync as writeFileSync9
60105
61009
  } from "node:fs";
60106
- import { homedir as homedir22 } from "node:os";
60107
- import { join as join31 } from "node:path";
61010
+ import { homedir as homedir24 } from "node:os";
61011
+ import { join as join33 } from "node:path";
60108
61012
  import { createInterface as createInterface2 } from "node:readline";
60109
61013
  var PERMISSIONS_WINDOW_PROTOCOL = 1;
60110
61014
  var PERMISSIONS_WINDOW_DIR_ENV = "APPLE_NOTES_MCP_PERMISSIONS_WINDOW_DIR";
@@ -60125,10 +61029,10 @@ function defaultPermissionsWindowDeps(overrides = {}) {
60125
61029
  return {
60126
61030
  env: process.env,
60127
61031
  platform: process.platform,
60128
- sourcePath: join31(packageRoot(), PERMISSIONS_WINDOW_SOURCE),
60129
- exists: existsSync17,
60130
- readFile: (path10) => readFileSync6(path10),
60131
- spawn: spawnSync7,
61032
+ sourcePath: join33(packageRoot(), PERMISSIONS_WINDOW_SOURCE),
61033
+ exists: existsSync18,
61034
+ readFile: (path10) => readFileSync7(path10),
61035
+ spawn: spawnSync8,
60132
61036
  now: () => /* @__PURE__ */ new Date(),
60133
61037
  ...overrides
60134
61038
  };
@@ -60136,17 +61040,17 @@ function defaultPermissionsWindowDeps(overrides = {}) {
60136
61040
  function permissionsWindowInstallDir(env = process.env) {
60137
61041
  const override = env[PERMISSIONS_WINDOW_DIR_ENV]?.trim();
60138
61042
  if (override) return override;
60139
- return join31(homedir22(), "Library", "Application Support", "apple-notes-mcp", "permissions-window");
61043
+ return join33(homedir24(), "Library", "Application Support", "apple-notes-mcp", "permissions-window");
60140
61044
  }
60141
61045
  function inspectPermissionsWindow(deps = defaultPermissionsWindowDeps()) {
60142
61046
  const installDir = permissionsWindowInstallDir(deps.env);
60143
- const binaryPath = join31(installDir, PERMISSIONS_WINDOW_BINARY);
61047
+ const binaryPath = join33(installDir, PERMISSIONS_WINDOW_BINARY);
60144
61048
  const fail = (reason, detail) => ({ ready: false, reason, detail, installDir, binaryPath });
60145
61049
  if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
60146
61050
  const rebuild = `Run \`${PERMISSIONS_WINDOW_SETUP_COMMAND}\`.`;
60147
61051
  if (!deps.exists(deps.sourcePath))
60148
61052
  return fail("window_not_installed", `Packaged window source is missing: ${deps.sourcePath}`);
60149
- const manifestPath = join31(installDir, PERMISSIONS_WINDOW_MANIFEST);
61053
+ const manifestPath = join33(installDir, PERMISSIONS_WINDOW_MANIFEST);
60150
61054
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
60151
61055
  return fail("window_not_installed", `The permissions window is not built. ${rebuild}`);
60152
61056
  let manifest;
@@ -60254,14 +61158,14 @@ function buildPermissionsWindow(checkOnly, deps = defaultPermissionsWindowDeps()
60254
61158
  const compiler = String(version3.stdout || version3.stderr || "").split("\n").find((line) => line.includes("Swift version"))?.trim() || "swiftc";
60255
61159
  steps.push({ step: "find compiler", ok: true, detail: compiler });
60256
61160
  const installDir = permissionsWindowInstallDir(deps.env);
60257
- mkdirSync10(installDir, { recursive: true, mode: 448 });
60258
- const staging = mkdtempSync9(join31(installDir, ".staging-"));
61161
+ mkdirSync11(installDir, { recursive: true, mode: 448 });
61162
+ const staging = mkdtempSync10(join33(installDir, ".staging-"));
60259
61163
  try {
60260
- const stagedBinary = join31(staging, PERMISSIONS_WINDOW_BINARY);
60261
- const digestPath = join31(staging, "source-digest.swift");
60262
- const plistPath = join31(staging, "Info.plist");
60263
- writeFileSync8(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
60264
- writeFileSync8(plistPath, permissionsWindowInfoPlist(), { mode: 384 });
61164
+ const stagedBinary = join33(staging, PERMISSIONS_WINDOW_BINARY);
61165
+ const digestPath = join33(staging, "source-digest.swift");
61166
+ const plistPath = join33(staging, "Info.plist");
61167
+ writeFileSync9(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
61168
+ writeFileSync9(plistPath, permissionsWindowInfoPlist(), { mode: 384 });
60265
61169
  const compile = deps.spawn(
60266
61170
  "/usr/bin/xcrun",
60267
61171
  permissionsWindowCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
@@ -60315,16 +61219,16 @@ function buildPermissionsWindow(checkOnly, deps = defaultPermissionsWindowDeps()
60315
61219
  builtAt: deps.now().toISOString(),
60316
61220
  compiler
60317
61221
  };
60318
- chmodSync3(stagedBinary, 448);
60319
- renameSync4(stagedBinary, join31(installDir, PERMISSIONS_WINDOW_BINARY));
60320
- writeFileSync8(
60321
- join31(installDir, PERMISSIONS_WINDOW_MANIFEST),
61222
+ chmodSync4(stagedBinary, 448);
61223
+ renameSync5(stagedBinary, join33(installDir, PERMISSIONS_WINDOW_BINARY));
61224
+ writeFileSync9(
61225
+ join33(installDir, PERMISSIONS_WINDOW_MANIFEST),
60322
61226
  JSON.stringify(manifest, null, 2) + "\n",
60323
61227
  { mode: 384 }
60324
61228
  );
60325
61229
  steps.push({ step: "install", ok: true, detail: installDir });
60326
61230
  } finally {
60327
- rmSync9(staging, { recursive: true, force: true });
61231
+ rmSync10(staging, { recursive: true, force: true });
60328
61232
  }
60329
61233
  const installation = inspectPermissionsWindow(deps);
60330
61234
  steps.push({
@@ -60398,6 +61302,288 @@ function runPermissionsWindow(binaryPath, session) {
60398
61302
  });
60399
61303
  }
60400
61304
 
61305
+ // src/services/brokerProxy.ts
61306
+ var BROKER_CONNECT_TIMEOUT_MS = 5e3;
61307
+ var MAX_FRAME_BYTES = 65536;
61308
+ var OUTPUT_FLUSH_TIMEOUT_MS = 2e3;
61309
+ var OUTPUT_STALL_TIMEOUT_MS = 3e4;
61310
+ var BROKER_PASSED_ENV_KEYS = [
61311
+ "APPLE_NOTES_MCP_BLOCKS_MAX_BYTES",
61312
+ "APPLE_NOTES_MCP_EXPORT_MAX_BYTES",
61313
+ "APPLE_NOTES_MCP_MAX_ATTACHMENT_BYTES",
61314
+ "APPLE_NOTES_MCP_MAX_BUFFER",
61315
+ "APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES",
61316
+ "APPLE_NOTES_MCP_MAX_RETRIES",
61317
+ "APPLE_NOTES_MCP_PRIVATE_HELPER_TIMEOUT_MS",
61318
+ "APPLE_NOTES_MCP_PUBLIC_HELPER_TIMEOUT_MS",
61319
+ "APPLE_NOTES_MCP_RETRY_DELAY_MS",
61320
+ "APPLE_NOTES_MCP_TIMEOUT_MS"
61321
+ ];
61322
+ var passedEnvironmentKeys = new Set(BROKER_PASSED_ENV_KEYS);
61323
+ var MAX_PASSED_VALUE = 2147483647;
61324
+ var MAX_VARIABLE_BYTES = 8192;
61325
+ function defaultBrokerProxyDeps(overrides = {}) {
61326
+ return {
61327
+ env: process.env,
61328
+ inspect: () => inspectBroker(),
61329
+ request: requestBroker,
61330
+ stdin: process.stdin,
61331
+ stdout: process.stdout,
61332
+ stderr: process.stderr,
61333
+ exit: (code) => process.exit(code),
61334
+ log: (message) => process.stderr.write(`[apple-notes-mcp] ${message}
61335
+ `),
61336
+ ...overrides
61337
+ };
61338
+ }
61339
+ function passedEnvironment(env) {
61340
+ const passed = {};
61341
+ for (const [key, value] of Object.entries(env)) {
61342
+ if (!passedEnvironmentKeys.has(key) || typeof value !== "string" || value.length > MAX_VARIABLE_BYTES || !/^[0-9]+$/.test(value))
61343
+ continue;
61344
+ const numeric = Number(value);
61345
+ if (!Number.isInteger(numeric) || numeric < 1 || numeric > MAX_PASSED_VALUE) continue;
61346
+ passed[key] = value;
61347
+ }
61348
+ return passed;
61349
+ }
61350
+ async function startBrokerProxy(deps = defaultBrokerProxyDeps()) {
61351
+ if (deps.env[BROKERED_ENV] === "1" || deps.env[BROKER_MODE_ENV] === "off") return false;
61352
+ let installation;
61353
+ try {
61354
+ installation = deps.inspect();
61355
+ } catch {
61356
+ return false;
61357
+ }
61358
+ if (!installation.installed) return false;
61359
+ const fallBack = (reason) => {
61360
+ recordBrokerFallback(reason);
61361
+ deps.log(`Permission broker not used, running in-process: ${reason}`);
61362
+ return false;
61363
+ };
61364
+ if (!installation.ready) return fallBack(installation.detail ?? "the broker is not ready.");
61365
+ const manifest = installation.manifest;
61366
+ if (!manifest) return fallBack("the broker has no verified runtime manifest.");
61367
+ let socket;
61368
+ let leftover;
61369
+ try {
61370
+ const result = await deps.request(
61371
+ installation.paths.socketPath,
61372
+ {
61373
+ type: "connect",
61374
+ protocolVersion: BROKER_PROTOCOL,
61375
+ packageVersion: manifest.packageVersion,
61376
+ entrySha256: manifest.entrySha256,
61377
+ env: passedEnvironment(deps.env)
61378
+ },
61379
+ BROKER_CONNECT_TIMEOUT_MS
61380
+ );
61381
+ if (result.answer.type !== "ready") {
61382
+ result.socket.destroy();
61383
+ const message = typeof result.answer.message === "string" ? result.answer.message : "no reason given";
61384
+ return fallBack(`the broker refused the connection (${message})`);
61385
+ }
61386
+ if (result.answer.protocolVersion !== BROKER_PROTOCOL || result.answer.packageVersion !== manifest.packageVersion || result.answer.entrySha256 !== manifest.entrySha256) {
61387
+ result.socket.destroy();
61388
+ return fallBack(
61389
+ "the running broker serves a different server version. Run `apple-notes-mcp setup --broker` again."
61390
+ );
61391
+ }
61392
+ socket = result.socket;
61393
+ leftover = result.leftover;
61394
+ } catch (error2) {
61395
+ return fallBack(
61396
+ error2 instanceof BrokerUnreachableError ? error2.message : `unexpected error: ${String(error2)}`
61397
+ );
61398
+ }
61399
+ relayBroker(socket, leftover, deps);
61400
+ return true;
61401
+ }
61402
+ function relayBroker(socket, leftover, deps) {
61403
+ const header = Buffer.allocUnsafe(5);
61404
+ let headerBytes = 0;
61405
+ let payload = null;
61406
+ let payloadBytes = 0;
61407
+ let channel2 = 0;
61408
+ let initialBytes = leftover;
61409
+ let initialOffset = 0;
61410
+ let pendingWrites = 0;
61411
+ let exitCode = null;
61412
+ let finishingCode = null;
61413
+ let exited = false;
61414
+ let failing = false;
61415
+ let pumping = false;
61416
+ let remoteEnded = false;
61417
+ let deadline;
61418
+ let stallDeadline;
61419
+ const blocked = /* @__PURE__ */ new Set();
61420
+ const log = (message) => {
61421
+ try {
61422
+ deps.log(message);
61423
+ } catch {
61424
+ }
61425
+ };
61426
+ const stopInput = () => {
61427
+ deps.stdin.unpipe(socket);
61428
+ deps.stdin.pause();
61429
+ };
61430
+ const exit = () => {
61431
+ if (exited || finishingCode === null || pendingWrites > 0) return;
61432
+ exited = true;
61433
+ clearTimeout(deadline);
61434
+ clearTimeout(stallDeadline);
61435
+ socket.off("readable", pump);
61436
+ deps.stdout.off("drain", stdoutDrained);
61437
+ deps.stderr.off("drain", stderrDrained);
61438
+ deps.exit(finishingCode);
61439
+ };
61440
+ const startDeadline = () => {
61441
+ if (deadline) return;
61442
+ deadline = setTimeout(() => {
61443
+ if (exited) return;
61444
+ log("Broker output did not finish within 2000 ms.");
61445
+ finishingCode = 1;
61446
+ pendingWrites = 0;
61447
+ stopInput();
61448
+ socket.destroy();
61449
+ exit();
61450
+ }, OUTPUT_FLUSH_TIMEOUT_MS);
61451
+ };
61452
+ const finish = (code) => {
61453
+ if (exited) return;
61454
+ if (finishingCode === null || code !== 0) finishingCode = code;
61455
+ stopInput();
61456
+ socket.destroy();
61457
+ startDeadline();
61458
+ exit();
61459
+ };
61460
+ const fail = (message) => {
61461
+ if (exited || failing) return;
61462
+ failing = true;
61463
+ log(message);
61464
+ finish(1);
61465
+ };
61466
+ const updateStallDeadline = (progress = false) => {
61467
+ if (progress || pendingWrites === 0 && blocked.size === 0) {
61468
+ clearTimeout(stallDeadline);
61469
+ stallDeadline = void 0;
61470
+ }
61471
+ if (exited || stallDeadline || pendingWrites === 0 && blocked.size === 0) return;
61472
+ stallDeadline = setTimeout(() => {
61473
+ if (exited) return;
61474
+ log("Broker output stalled for 30000 ms.");
61475
+ pendingWrites = 0;
61476
+ finish(1);
61477
+ }, OUTPUT_STALL_TIMEOUT_MS);
61478
+ };
61479
+ const writeOutput = (stream, bytes) => {
61480
+ pendingWrites++;
61481
+ try {
61482
+ const accepted = stream.write(bytes, (error2) => {
61483
+ if (exited) return;
61484
+ pendingWrites--;
61485
+ updateStallDeadline(true);
61486
+ if (error2) fail(`Broker output error: ${error2.message}`);
61487
+ else exit();
61488
+ });
61489
+ if (!accepted) blocked.add(stream);
61490
+ updateStallDeadline();
61491
+ } catch (error2) {
61492
+ pendingWrites--;
61493
+ fail(`Broker output error: ${String(error2)}`);
61494
+ }
61495
+ };
61496
+ const read = (length) => {
61497
+ if (initialOffset < initialBytes.length) {
61498
+ const end = Math.min(initialBytes.length, initialOffset + length);
61499
+ const bytes = initialBytes.subarray(initialOffset, end);
61500
+ initialOffset = end;
61501
+ if (initialOffset === initialBytes.length) initialBytes = Buffer.alloc(0);
61502
+ return bytes;
61503
+ }
61504
+ const available = socket.readableLength;
61505
+ return socket.read(Math.min(length, available));
61506
+ };
61507
+ function pump() {
61508
+ if (pumping || finishingCode !== null || exited) return;
61509
+ pumping = true;
61510
+ try {
61511
+ while (blocked.size === 0 && finishingCode === null) {
61512
+ if (exitCode !== null) {
61513
+ if (read(1)) fail("The broker sent bytes after its exit frame.");
61514
+ break;
61515
+ }
61516
+ if (payload === null) {
61517
+ const bytes2 = read(header.length - headerBytes);
61518
+ if (!bytes2) break;
61519
+ headerBytes += bytes2.copy(header, headerBytes);
61520
+ if (headerBytes !== header.length) continue;
61521
+ channel2 = header[0];
61522
+ const length = header.readUInt32BE(1);
61523
+ if (channel2 !== 1 && channel2 !== 2 && channel2 !== 3 || length === 0 || length > MAX_FRAME_BYTES || channel2 === 3 && length !== 4) {
61524
+ fail("The broker sent an invalid output frame.");
61525
+ break;
61526
+ }
61527
+ payload = Buffer.allocUnsafe(length);
61528
+ payloadBytes = 0;
61529
+ }
61530
+ const bytes = read(payload.length - payloadBytes);
61531
+ if (!bytes) break;
61532
+ payloadBytes += bytes.copy(payload, payloadBytes);
61533
+ if (payloadBytes !== payload.length) continue;
61534
+ const complete = payload;
61535
+ payload = null;
61536
+ headerBytes = 0;
61537
+ if (channel2 === 3) {
61538
+ const code = complete.readUInt32BE(0);
61539
+ if (code > 255) {
61540
+ fail("The broker sent an invalid exit status.");
61541
+ break;
61542
+ }
61543
+ exitCode = code;
61544
+ stopInput();
61545
+ startDeadline();
61546
+ } else {
61547
+ writeOutput(channel2 === 1 ? deps.stdout : deps.stderr, complete);
61548
+ }
61549
+ }
61550
+ if (remoteEnded && blocked.size === 0 && finishingCode === null) {
61551
+ if (exitCode === null || headerBytes !== 0 || payload !== null)
61552
+ fail("The broker closed without a complete exit frame.");
61553
+ else finish(exitCode);
61554
+ }
61555
+ } finally {
61556
+ pumping = false;
61557
+ }
61558
+ }
61559
+ const drained = (stream) => {
61560
+ blocked.delete(stream);
61561
+ updateStallDeadline(true);
61562
+ pump();
61563
+ };
61564
+ const stdoutDrained = () => drained(deps.stdout);
61565
+ const stderrDrained = () => drained(deps.stderr);
61566
+ deps.stdout.on("drain", stdoutDrained);
61567
+ deps.stderr.on("drain", stderrDrained);
61568
+ deps.stdout.on("error", (error2) => fail(`Broker stdout error: ${error2.message}`));
61569
+ deps.stderr.on("error", (error2) => fail(`Broker stderr error: ${error2.message}`));
61570
+ deps.stdout.on("close", () => fail("Broker stdout closed before the session finished."));
61571
+ deps.stderr.on("close", () => fail("Broker stderr closed before the session finished."));
61572
+ deps.stdin.on("error", (error2) => fail(`Broker stdin error: ${error2.message}`));
61573
+ socket.on("error", (error2) => fail(`Broker connection error: ${error2.message}`));
61574
+ socket.on("readable", pump);
61575
+ socket.on("end", () => {
61576
+ remoteEnded = true;
61577
+ pump();
61578
+ });
61579
+ socket.on("close", () => {
61580
+ if (finishingCode === null && !remoteEnded)
61581
+ fail("The broker connection closed before the session finished.");
61582
+ });
61583
+ deps.stdin.pipe(socket);
61584
+ pump();
61585
+ }
61586
+
60401
61587
  // src/tools/privateHelperTools.ts
60402
61588
  var coreDataId2 = external_exports.string().regex(/^x-coredata:\/\/[0-9A-F-]+\/ICNote\/p\d+$/i);
60403
61589
  var notesUuid = external_exports.string().regex(UUID_PATTERN);
@@ -61085,24 +62271,24 @@ async function startTemplateEditor(options = {}) {
61085
62271
  socket.on("close", () => sockets.delete(socket));
61086
62272
  });
61087
62273
  let resolveClosed;
61088
- const closed = new Promise((resolve10) => resolveClosed = resolve10);
62274
+ const closed = new Promise((resolve11) => resolveClosed = resolve11);
61089
62275
  let closing;
61090
62276
  const close = () => {
61091
- closing ??= new Promise((resolve10) => {
62277
+ closing ??= new Promise((resolve11) => {
61092
62278
  if (idleTimer) clearTimeout(idleTimer);
61093
62279
  server2.close(() => {
61094
62280
  resolveClosed(closeReason);
61095
- resolve10();
62281
+ resolve11();
61096
62282
  });
61097
62283
  for (const socket of sockets) socket.destroy();
61098
62284
  });
61099
62285
  return closing;
61100
62286
  };
61101
- await new Promise((resolve10, reject) => {
62287
+ await new Promise((resolve11, reject) => {
61102
62288
  server2.once("error", reject);
61103
62289
  server2.listen({ host, port: options.port ?? 0, exclusive: true }, () => {
61104
62290
  server2.off("error", reject);
61105
- resolve10();
62291
+ resolve11();
61106
62292
  });
61107
62293
  });
61108
62294
  const port = server2.address().port;
@@ -61546,13 +62732,13 @@ function createAnchorServer(options) {
61546
62732
  }
61547
62733
  function startAnchorServer(options) {
61548
62734
  const server2 = createAnchorServer(options);
61549
- return new Promise((resolve10, reject) => {
62735
+ return new Promise((resolve11, reject) => {
61550
62736
  server2.once("error", reject);
61551
62737
  server2.listen(options.port, options.host, () => {
61552
62738
  server2.off("error", reject);
61553
62739
  const address = server2.address();
61554
62740
  const port = address && typeof address === "object" ? address.port : options.port;
61555
- resolve10({
62741
+ resolve11({
61556
62742
  server: server2,
61557
62743
  baseUrl: `http://${hostAuthority(options.host, port)}`,
61558
62744
  close: () => new Promise((done) => {
@@ -61590,7 +62776,7 @@ function parseAnchorsArgs(argv) {
61590
62776
  }
61591
62777
  async function runAnchorsCli(argv, {
61592
62778
  env = process.env,
61593
- resolve: resolve10,
62779
+ resolve: resolve11,
61594
62780
  out = (text2) => process.stdout.write(text2),
61595
62781
  interfaces,
61596
62782
  signals = process
@@ -61623,7 +62809,7 @@ ${ANCHORS_USAGE}
61623
62809
  const token = fromEnv || newServerToken();
61624
62810
  let started;
61625
62811
  try {
61626
- started = await startAnchorServer({ host, port: args.port, token, resolve: resolve10 });
62812
+ started = await startAnchorServer({ host, port: args.port, token, resolve: resolve11 });
61627
62813
  } catch (error2) {
61628
62814
  out(`Could not listen on ${host}:${args.port}: ${error2.message}
61629
62815
  `);
@@ -61650,17 +62836,17 @@ import {
61650
62836
  constants as constants9,
61651
62837
  fstatSync as fstatSync9,
61652
62838
  fsyncSync,
61653
- lstatSync as lstatSync6,
61654
- mkdirSync as mkdirSync11,
62839
+ lstatSync as lstatSync7,
62840
+ mkdirSync as mkdirSync12,
61655
62841
  openSync as openSync9,
61656
62842
  readSync as readSync7,
61657
- renameSync as renameSync5,
62843
+ renameSync as renameSync6,
61658
62844
  statSync as statSync6,
61659
62845
  unlinkSync as unlinkSync5,
61660
62846
  writeSync as writeSync6
61661
62847
  } from "node:fs";
61662
- import { homedir as homedir23 } from "node:os";
61663
- import { dirname as dirname10, isAbsolute as isAbsolute6, join as join32, resolve as resolve9 } from "node:path";
62848
+ import { homedir as homedir25 } from "node:os";
62849
+ import { dirname as dirname10, isAbsolute as isAbsolute7, join as join34, resolve as resolve10 } from "node:path";
61664
62850
  var MAX_ANCHORS = 2e4;
61665
62851
  var MAX_REGISTRY_BYTES = 32 * 1024 * 1024;
61666
62852
  var LOCK_WAIT_MS = 3e3;
@@ -61684,14 +62870,14 @@ var AnchorRegistryError = class extends CodedError {
61684
62870
  function anchorRegistryPath(env = process.env) {
61685
62871
  const override = env.APPLE_NOTES_MCP_ANCHOR_FILE?.trim();
61686
62872
  if (override) {
61687
- if (!isAbsolute6(override))
62873
+ if (!isAbsolute7(override))
61688
62874
  throw new AnchorRegistryError(
61689
62875
  "unsafe-path",
61690
62876
  "APPLE_NOTES_MCP_ANCHOR_FILE must be an absolute path."
61691
62877
  );
61692
- return resolve9(override);
62878
+ return resolve10(override);
61693
62879
  }
61694
- return join32(homedir23(), "Library/Application Support/apple-notes-mcp/paragraph-anchors.json");
62880
+ return join34(homedir25(), "Library/Application Support/apple-notes-mcp/paragraph-anchors.json");
61695
62881
  }
61696
62882
  var STRING_OR_NULL = (v) => v === null || typeof v === "string";
61697
62883
  function isAnchor(v) {
@@ -61714,14 +62900,14 @@ var AnchorRegistry = class _AnchorRegistry {
61714
62900
  ensureDir() {
61715
62901
  const dir = dirname10(this.path);
61716
62902
  try {
61717
- if (!lstatSync6(dir).isDirectory())
62903
+ if (!lstatSync7(dir).isDirectory())
61718
62904
  throw new AnchorRegistryError(
61719
62905
  "unsafe-path",
61720
62906
  `${dir} is not a directory (symlinks are refused).`
61721
62907
  );
61722
62908
  } catch (error2) {
61723
62909
  if (error2 instanceof AnchorRegistryError) throw error2;
61724
- mkdirSync11(dir, { recursive: true, mode: 448 });
62910
+ mkdirSync12(dir, { recursive: true, mode: 448 });
61725
62911
  }
61726
62912
  }
61727
62913
  /** Every anchor in the file, in the order recorded. A missing file is an empty registry. */
@@ -61811,7 +62997,7 @@ var AnchorRegistry = class _AnchorRegistry {
61811
62997
  }
61812
62998
  write(anchors) {
61813
62999
  try {
61814
- if (!lstatSync6(this.path).isFile())
63000
+ if (!lstatSync7(this.path).isFile())
61815
63001
  throw new AnchorRegistryError(
61816
63002
  "unsafe-path",
61817
63003
  `Refusing to replace ${this.path}: it is not a regular file.`
@@ -61819,7 +63005,7 @@ var AnchorRegistry = class _AnchorRegistry {
61819
63005
  } catch (error2) {
61820
63006
  if (error2 instanceof AnchorRegistryError) throw error2;
61821
63007
  }
61822
- const temp = join32(
63008
+ const temp = join34(
61823
63009
  dirname10(this.path),
61824
63010
  `.paragraph-anchors.${randomBytes5(6).toString("hex")}.tmp`
61825
63011
  );
@@ -61840,7 +63026,7 @@ var AnchorRegistry = class _AnchorRegistry {
61840
63026
  }
61841
63027
  closeSync9(fd);
61842
63028
  try {
61843
- renameSync5(temp, this.path);
63029
+ renameSync6(temp, this.path);
61844
63030
  } catch (error2) {
61845
63031
  unlinkSync5(temp);
61846
63032
  throw error2;
@@ -62035,6 +63221,11 @@ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--permissions
62035
63221
  cli.close();
62036
63222
  process.exit(code);
62037
63223
  }
63224
+ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--broker")) {
63225
+ const report = await setupBroker(parseBrokerArgs(process.argv.slice(3)));
63226
+ process.stdout.write(formatBrokerSetup(report) + "\n");
63227
+ process.exit(report.ok ? 0 : 1);
63228
+ }
62038
63229
  if (process.argv[2] === "setup") {
62039
63230
  const report = setupShortcuts(process.argv.slice(3).includes("--check"));
62040
63231
  process.stdout.write(formatShortcutSetup(report) + "\n");
@@ -62048,6 +63239,10 @@ if (process.argv[2] === "anchors") {
62048
63239
  await runAnchorsCli(process.argv.slice(3), { resolve: registryLookup(new AnchorRegistry()) })
62049
63240
  );
62050
63241
  }
63242
+ if (await startBrokerProxy()) {
63243
+ await new Promise(() => {
63244
+ });
63245
+ }
62051
63246
  var server = new McpServer({
62052
63247
  name: "apple-notes",
62053
63248
  version: version2,
@@ -62921,7 +64116,7 @@ registerTool(
62921
64116
  registerTool(
62922
64117
  "get-note-link",
62923
64118
  {
62924
- description: "Use when: you need the notes:// deep-link URL for a note so it can be stored in a Reminders task, shared, or opened directly.\nReturns: a notes://showNote?identifier=<uuid> URL that opens the note in Notes.app on iOS and macOS.\nDo not use when: you only need the note's CoreData id (get-note-by-id) or want to reveal the note on screen (show-note).\nNote: the primary path reads the note's identifier from the Notes database, so it needs Full Disk Access for the Node binary running this server; macOS 12-15 can fall back to the AppleScript 'note link' property, which macOS 26+ no longer exposes. Password-protected notes cannot be linked.",
64119
+ description: "Use when: you need the notes:// deep-link URL for a note so it can be stored in a Reminders task, shared, or opened directly.\nReturns: a notes://showNote?identifier=<uuid> URL that opens the note in Notes.app on iOS and macOS.\nDo not use when: you only need the note's CoreData id (get-note-by-id) or want to reveal the note on screen (show-note).\nNote: the primary path reads the note's identifier from the Notes database, so it needs Full Disk Access for the responsible process (run doctor to identify it); macOS 12-15 can fall back to the AppleScript 'note link' property, which macOS 26+ no longer exposes. Password-protected notes cannot be linked.",
62925
64120
  inputSchema: {
62926
64121
  id: looseNoteId(external_exports.string()).optional().describe(`Note ID (preferred - more reliable than title): ${NOTE_ID_FORMS}`),
62927
64122
  title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
@@ -62947,7 +64142,7 @@ registerTool(
62947
64142
  const url2 = notesManager.getNoteLinkById(id2);
62948
64143
  if (!url2) {
62949
64144
  return errorResponse(
62950
- `Failed to get note link for "${note2.title}". The Notes database may not be accessible \u2014 grant Full Disk Access to the Node binary running the server (or the terminal that launches it), fully quit and relaunch, then run the doctor tool. See: ${FULL_DISK_ACCESS_GUIDE_URL}. (On macOS 12\u201315 this also falls back to the AppleScript note link property.)`
64145
+ `Failed to get note link for "${note2.title}". The Notes database may not be accessible. ${fdaRemediation()} (On macOS 12\u201315 this also falls back to the AppleScript note link property.)`
62951
64146
  );
62952
64147
  }
62953
64148
  return successResponse(`Note link: ${url2}`, { id: id2, title: note2.title, url: url2 });
@@ -62967,7 +64162,7 @@ registerTool(
62967
64162
  const url = notesManager.getNoteLink(title, account);
62968
64163
  if (!url) {
62969
64164
  return errorResponse(
62970
- `Failed to get note link for "${title}". The Notes database may not be accessible \u2014 grant Full Disk Access to the Node binary running the server (or the terminal that launches it), fully quit and relaunch, then run the doctor tool. See: ${FULL_DISK_ACCESS_GUIDE_URL}. (On macOS 12\u201315 this also falls back to the AppleScript note link property.)`
64165
+ `Failed to get note link for "${title}". The Notes database may not be accessible. ${fdaRemediation()} (On macOS 12\u201315 this also falls back to the AppleScript note link property.)`
62971
64166
  );
62972
64167
  }
62973
64168
  return successResponse(`Note link: ${url}`, { title, url });
@@ -63140,7 +64335,7 @@ registerTool(
63140
64335
  page = pageNoteBlocks(readNoteBlocks(id2), { offset, limit });
63141
64336
  } catch (error2) {
63142
64337
  if (!(error2 instanceof NoteBlocksError)) throw error2;
63143
- const hint = error2.code === "no-full-disk-access" ? ` Grant Full Disk Access to the Node binary running this server (run the doctor tool for its path): ${FULL_DISK_ACCESS_GUIDE_URL}` : "";
64338
+ const hint = error2.code === "no-full-disk-access" ? ` ${fdaRemediation()}` : "";
63144
64339
  return errorResponse(
63145
64340
  `Error reading note blocks [${error2.code}]: ${error2.message}${hint}`,
63146
64341
  error2
@@ -64469,12 +65664,19 @@ registerTool(
64469
65664
  healthy: external_exports.boolean().optional(),
64470
65665
  checks: external_exports.array(external_exports.object({}).passthrough()).optional(),
64471
65666
  runtimeOS: external_exports.object({}).passthrough().optional(),
64472
- features: external_exports.record(external_exports.string(), external_exports.object({}).passthrough()).optional()
65667
+ features: external_exports.record(external_exports.string(), external_exports.object({}).passthrough()).optional(),
65668
+ broker: external_exports.object({}).passthrough().optional()
64473
65669
  }
64474
65670
  },
64475
65671
  withErrorHandling(() => {
64476
65672
  const report = runDoctor(notesManager);
64477
- return successResponse(formatDoctorReport(report), { ...report });
65673
+ const broker = brokerStatus();
65674
+ return successResponse(`${formatDoctorReport(report)}
65675
+
65676
+ Permission broker: ${broker.detail}`, {
65677
+ ...report,
65678
+ broker
65679
+ });
64478
65680
  }, "Error running doctor")
64479
65681
  );
64480
65682
  registerTool(
@@ -65157,7 +66359,7 @@ registerTool(
65157
66359
  });
65158
66360
  } catch (error2) {
65159
66361
  if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
65160
- const hint = error2.code === "no-full-disk-access" ? ` Grant Full Disk Access to the Node binary running this server (run the doctor tool for its path): ${FULL_DISK_ACCESS_GUIDE_URL}` : "";
66362
+ const hint = error2.code === "no-full-disk-access" ? ` ${fdaRemediation()}` : "";
65161
66363
  if (error2 instanceof NotesExportError && error2.details)
65162
66364
  return errorResponse(
65163
66365
  `Error exporting Markdown [${error2.code}]: the template is invalid:
@@ -65227,7 +66429,7 @@ registerTool(
65227
66429
  });
65228
66430
  } catch (error2) {
65229
66431
  if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
65230
- const hint = error2.code === "no-full-disk-access" ? ` Grant Full Disk Access to the Node binary running this server (run the doctor tool for its path): ${FULL_DISK_ACCESS_GUIDE_URL}` : "";
66432
+ const hint = error2.code === "no-full-disk-access" ? ` ${fdaRemediation()}` : "";
65231
66433
  return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
65232
66434
  }
65233
66435
  const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;