apple-notes-mcp 2.14.2 → 2.15.0

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;");
@@ -48843,18 +48857,12 @@ var NATIVE_APPEND_ELEMENTS = [
48843
48857
  "s",
48844
48858
  "span",
48845
48859
  "strong",
48846
- "table",
48847
- "tbody",
48848
- "td",
48849
- "th",
48850
- "thead",
48851
- "tr",
48852
48860
  "tt",
48853
48861
  "u",
48854
48862
  "ul"
48855
48863
  ];
48856
48864
  var NATIVE_APPEND_SPAN_STYLE = /^font-size\s*:\s*\d{1,3}(?:\.\d+)?(?:px|pt)\s*;?$/i;
48857
- var NATIVE_APPEND_HTML_SUBSET = `Native append accepts ${NATIVE_APPEND_ELEMENTS.map((e) => `<${e}>`).join(" ")}, with href on <a> and a font-size style on <span> as the only attributes; everything else needs update-note.`;
48865
+ var NATIVE_APPEND_HTML_SUBSET = `Native append accepts ${NATIVE_APPEND_ELEMENTS.map((e) => `<${e}>`).join(" ")}, with href on <a> and a font-size style on <span> as the only attributes; a native table is refused here \u2014 use create-table; everything else needs update-note.`;
48858
48866
  var UNDERSCORES_OUTSIDE_A_WORD = "underscores outside a word";
48859
48867
  var UNMODELED_MARKDOWN = [
48860
48868
  [/(?<![\p{L}\p{N}])_|_(?![\p{L}\p{N}])/mu, UNDERSCORES_OUTSIDE_A_WORD],
@@ -48916,9 +48924,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
48916
48924
  ),
48917
48925
  { shortcut: status.shortcut, notInstalled: true }
48918
48926
  );
48919
- const directory = mkdtempSync4(join17(tmpdir4(), "apple-notes-background-"));
48927
+ const directory = mkdtempSync4(join18(tmpdir4(), "apple-notes-background-"));
48920
48928
  try {
48921
- const file = join17(directory, "request.json");
48929
+ const file = join18(directory, "request.json");
48922
48930
  writeFileSync3(
48923
48931
  file,
48924
48932
  JSON.stringify({
@@ -49125,9 +49133,9 @@ function assertAppendedVisibleText(beforeHtml, afterHtml, expected) {
49125
49133
  if (!suffix || !suffix.includes(wanted)) throw new Error("Appended text not verified");
49126
49134
  }
49127
49135
  function appendNative(manager, request) {
49128
- validateAppendContent(request.content, request.format);
49129
- if (request.format === "html" && /<table\b/i.test(request.content))
49136
+ if (request.format === "html" && /<\/?\s*(?:table|thead|tbody|tr|td|th)\b/i.test(request.content))
49130
49137
  throw new Error("Use create-table for verified native table insertion");
49138
+ validateAppendContent(request.content, request.format);
49131
49139
  const markdownHtml = request.format === "markdown" ? appendMarkdownHtml(request.content) : null;
49132
49140
  const op = request.format === "plaintext" ? "append-text" : request.format === "markdown" ? "append-markdown" : "append-html";
49133
49141
  const text2 = request.format === "html" ? "<div><br></div>" + request.content : "\n\n" + request.content;
@@ -49674,10 +49682,6 @@ function runDoctor(manager, capabilityMatrix = getCapabilityMatrix) {
49674
49682
  }
49675
49683
  return matrix ? { healthy, checks, runtimeOS: matrix.runtimeOS, features: matrix.features } : { healthy, checks };
49676
49684
  }
49677
- function fdaRemediation(execPath = process.execPath) {
49678
- const versioned = /\/(\.nvm|\.fnm|\.volta|\.asdf|\.local\/share\/mise|\.nodenv|n\/versions)\//.test(execPath);
49679
- 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}`;
49680
- }
49681
49685
  function markdownBridgeDetail() {
49682
49686
  const purpose = 'needed only for create-note format: "markdown" on macOS 26+';
49683
49687
  try {
@@ -49727,14 +49731,15 @@ function formatDoctorReport(r) {
49727
49731
 
49728
49732
  // src/services/fileConfig.ts
49729
49733
  import { existsSync as existsSync12, readFileSync as readFileSync2 } from "fs";
49730
- import { join as join18 } from "path";
49731
- import { homedir as homedir15 } from "os";
49734
+ import { join as join19 } from "path";
49735
+ import { homedir as homedir16 } from "os";
49732
49736
  function fileConfigPath(env = process.env) {
49733
49737
  const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
49734
49738
  if (override && override.trim()) return override.trim();
49735
- return join18(homedir15(), "Library", "Application Support", "apple-notes-mcp", "config.json");
49739
+ return join19(homedir16(), "Library", "Application Support", "apple-notes-mcp", "config.json");
49736
49740
  }
49737
49741
  function loadFileConfig(env = process.env, path10 = fileConfigPath(env)) {
49742
+ if (env.APPLE_NOTES_MCP_BROKERED === "1") return [];
49738
49743
  const applied = [];
49739
49744
  try {
49740
49745
  if (!existsSync12(path10)) return applied;
@@ -50917,7 +50922,7 @@ function insertLink(request, deps) {
50917
50922
 
50918
50923
  // src/services/notesExport.ts
50919
50924
  import { mkdirSync as mkdirSync7 } from "node:fs";
50920
- 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";
50921
50926
 
50922
50927
  // src/utils/exportAssets.ts
50923
50928
  import {
@@ -50932,9 +50937,9 @@ import {
50932
50937
  statSync as statSync3,
50933
50938
  writeSync as writeSync3
50934
50939
  } from "node:fs";
50935
- import { homedir as homedir16 } from "node:os";
50936
- import { basename as basename3, extname as extname3, join as join19, relative as relative3, resolve as resolve3, sep as sep3 } from "node:path";
50937
- 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");
50938
50943
  var MAX_EMBED_BYTES = 10 * 1024 * 1024;
50939
50944
  var MAX_EMBED_TOTAL_BYTES = 256 * 1024 * 1024;
50940
50945
  var MAX_DIRECTORY_ENTRIES = 2e4;
@@ -50990,11 +50995,11 @@ var AssetLocator = class {
50990
50995
  constructor(container = NOTES_CONTAINER) {
50991
50996
  let accountsRoot = "";
50992
50997
  try {
50993
- accountsRoot = realpathSync3.native(join19(container, "Accounts"));
50998
+ accountsRoot = realpathSync3.native(join20(container, "Accounts"));
50994
50999
  } catch {
50995
51000
  }
50996
51001
  this.accounts = accountsRoot ? listDirectory(accountsRoot, 1e3).flatMap((name) => {
50997
- const dir = safeComponent2(name) && confine(join19(accountsRoot, name), accountsRoot);
51002
+ const dir = safeComponent2(name) && confine(join20(accountsRoot, name), accountsRoot);
50998
51003
  return dir && isDirectory2(dir) ? [dir] : [];
50999
51004
  }) : [];
51000
51005
  }
@@ -51068,8 +51073,8 @@ var AssetLocator = class {
51068
51073
  if (!mediaId || !filename) return void 0;
51069
51074
  const generation = safeComponent2(source.mediaGeneration);
51070
51075
  const candidates = [
51071
- ...generation ? [join19(account, "Media", mediaId, generation, filename)] : [],
51072
- join19(account, "Media", mediaId, filename)
51076
+ ...generation ? [join20(account, "Media", mediaId, generation, filename)] : [],
51077
+ join20(account, "Media", mediaId, filename)
51073
51078
  ];
51074
51079
  for (const candidate of candidates) {
51075
51080
  const path10 = confine(candidate, account);
@@ -51083,7 +51088,7 @@ var AssetLocator = class {
51083
51088
  * somewhere else.
51084
51089
  */
51085
51090
  fallback(account, dir, id2, generation, names) {
51086
- const base = join19(account, dir, id2);
51091
+ const base = join20(account, dir, id2);
51087
51092
  const gen = safeComponent2(generation);
51088
51093
  const generations = [
51089
51094
  ...gen ? [gen] : [],
@@ -51092,7 +51097,7 @@ var AssetLocator = class {
51092
51097
  ];
51093
51098
  for (const g of generations)
51094
51099
  for (const name of names) {
51095
- 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);
51096
51101
  if (path10 && isFile(path10)) return { path: path10, stale: Boolean(gen) && g !== gen };
51097
51102
  }
51098
51103
  return void 0;
@@ -51102,7 +51107,7 @@ var AssetLocator = class {
51102
51107
  let index = this.previewIndex.get(account);
51103
51108
  if (!index) {
51104
51109
  index = /* @__PURE__ */ new Map();
51105
- for (const name of listDirectory(join19(account, "Previews"), MAX_DIRECTORY_ENTRIES)) {
51110
+ for (const name of listDirectory(join20(account, "Previews"), MAX_DIRECTORY_ENTRIES)) {
51106
51111
  const key = name.slice(0, 36).toUpperCase();
51107
51112
  const entries3 = index.get(key) ?? [];
51108
51113
  entries3.push(name);
@@ -51115,7 +51120,7 @@ var AssetLocator = class {
51115
51120
  return !ext || IMAGE_EXTENSIONS.has(ext) || /^\.\d+$/.test(ext);
51116
51121
  }).sort((a, b) => previewArea(b) - previewArea(a) || a.localeCompare(b));
51117
51122
  for (const name of entries2) {
51118
- const entry = confine(join19(account, "Previews", name), account);
51123
+ const entry = confine(join20(account, "Previews", name), account);
51119
51124
  if (!entry) continue;
51120
51125
  const file = isFile(entry) ? entry : this.bundleImage(entry, account);
51121
51126
  if (file) return { path: file, name: "preview", role: "preview" };
@@ -51126,12 +51131,12 @@ var AssetLocator = class {
51126
51131
  bundleImage(bundle, account) {
51127
51132
  const found = [];
51128
51133
  for (const child2 of listDirectory(bundle, MAX_BUNDLE_ENTRIES2)) {
51129
- const path10 = confine(join19(bundle, child2), account);
51134
+ const path10 = confine(join20(bundle, child2), account);
51130
51135
  if (!path10) continue;
51131
51136
  if (isFile(path10)) found.push(path10);
51132
51137
  else
51133
51138
  for (const grandchild of listDirectory(path10, MAX_BUNDLE_ENTRIES2)) {
51134
- const inner = confine(join19(path10, grandchild), account);
51139
+ const inner = confine(join20(path10, grandchild), account);
51135
51140
  if (inner && isFile(inner)) found.push(inner);
51136
51141
  }
51137
51142
  }
@@ -51192,11 +51197,11 @@ function canonicalForm(abs) {
51192
51197
  for (; ; ) {
51193
51198
  try {
51194
51199
  const real = realpathSync3.native(current);
51195
- return rest ? join19(real, rest) : real;
51200
+ return rest ? join20(real, rest) : real;
51196
51201
  } catch {
51197
51202
  const parent = resolve3(current, "..");
51198
51203
  if (parent === current) return abs;
51199
- rest = rest ? join19(basename3(current), rest) : basename3(current);
51204
+ rest = rest ? join20(basename3(current), rest) : basename3(current);
51200
51205
  current = parent;
51201
51206
  }
51202
51207
  }
@@ -51323,7 +51328,7 @@ var SidecarWriter = class {
51323
51328
  let target;
51324
51329
  let out;
51325
51330
  for (let n = 1; n <= 1e3 && out === void 0; n++) {
51326
- target = join19(this.dir, n === 1 ? safe2 : `${stem}-${n}${ext}`);
51331
+ target = join20(this.dir, n === 1 ? safe2 : `${stem}-${n}${ext}`);
51327
51332
  try {
51328
51333
  out = openSync4(target, CREATE_FLAGS, 420);
51329
51334
  } catch (error2) {
@@ -51995,8 +52000,8 @@ import {
51995
52000
  rmSync as rmSync5,
51996
52001
  writeFileSync as writeFileSync4
51997
52002
  } from "node:fs";
51998
- import { homedir as homedir17, release as release2 } from "node:os";
51999
- 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";
52000
52005
  import { fileURLToPath } from "node:url";
52001
52006
  var PUBLIC_HELPER_PROTOCOL = 1;
52002
52007
  var PUBLIC_HELPER_DIR_ENV = "APPLE_NOTES_MCP_PUBLIC_HELPER_DIR";
@@ -52025,7 +52030,7 @@ var publicManifestSchema = external_exports.object({
52025
52030
  function packageRoot(fromDir = dirname6(fileURLToPath(import.meta.url))) {
52026
52031
  let dir = fromDir;
52027
52032
  for (; ; ) {
52028
- const candidate = join20(dir, "package.json");
52033
+ const candidate = join21(dir, "package.json");
52029
52034
  if (existsSync13(candidate)) {
52030
52035
  try {
52031
52036
  if (JSON.parse(readFileSync3(candidate, "utf8")).name === "apple-notes-mcp")
@@ -52042,7 +52047,7 @@ function defaultPublicHelperDeps(overrides = {}) {
52042
52047
  return {
52043
52048
  env: process.env,
52044
52049
  platform: process.platform,
52045
- sourcePath: join20(packageRoot(), PUBLIC_HELPER_SOURCE),
52050
+ sourcePath: join21(packageRoot(), PUBLIC_HELPER_SOURCE),
52046
52051
  exists: existsSync13,
52047
52052
  readFile: (path10) => readFileSync3(path10),
52048
52053
  spawn: spawnSync2,
@@ -52053,14 +52058,14 @@ function defaultPublicHelperDeps(overrides = {}) {
52053
52058
  function publicHelperInstallDir(env = process.env) {
52054
52059
  const override = env[PUBLIC_HELPER_DIR_ENV]?.trim();
52055
52060
  if (override) return override;
52056
- return join20(homedir17(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
52061
+ return join21(homedir18(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
52057
52062
  }
52058
52063
  function sha256Hex(data) {
52059
52064
  return createHash2("sha256").update(data).digest("hex");
52060
52065
  }
52061
52066
  function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
52062
52067
  const installDir = publicHelperInstallDir(deps.env);
52063
- const binaryPath = join20(installDir, PUBLIC_HELPER_BINARY);
52068
+ const binaryPath = join21(installDir, PUBLIC_HELPER_BINARY);
52064
52069
  const base = { installDir, binaryPath, sourcePath: deps.sourcePath, manifest: null };
52065
52070
  const fail = (reason, detail) => ({
52066
52071
  ...base,
@@ -52071,7 +52076,7 @@ function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
52071
52076
  if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
52072
52077
  if (!deps.exists(deps.sourcePath))
52073
52078
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
52074
- const manifestPath = join20(installDir, PUBLIC_HELPER_MANIFEST);
52079
+ const manifestPath = join21(installDir, PUBLIC_HELPER_MANIFEST);
52075
52080
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
52076
52081
  return fail(
52077
52082
  "helper_not_installed",
@@ -52348,11 +52353,11 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
52348
52353
  steps.push({ step: "find compiler", ok: true, detail: compiler });
52349
52354
  const installDir = publicHelperInstallDir(deps.env);
52350
52355
  mkdirSync5(installDir, { recursive: true, mode: 448 });
52351
- const staging = mkdtempSync5(join20(installDir, ".staging-"));
52356
+ const staging = mkdtempSync5(join21(installDir, ".staging-"));
52352
52357
  try {
52353
- const stagedBinary = join20(staging, PUBLIC_HELPER_BINARY);
52354
- const digestPath = join20(staging, "source-digest.swift");
52355
- 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");
52356
52361
  writeFileSync4(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
52357
52362
  writeFileSync4(plistPath, publicHelperInfoPlist(), { mode: 384 });
52358
52363
  const compile = deps.spawn(
@@ -52415,9 +52420,9 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
52415
52420
  compiler
52416
52421
  };
52417
52422
  chmodSync(stagedBinary, 448);
52418
- renameSync(stagedBinary, join20(installDir, PUBLIC_HELPER_BINARY));
52423
+ renameSync(stagedBinary, join21(installDir, PUBLIC_HELPER_BINARY));
52419
52424
  writeFileSync4(
52420
- join20(installDir, PUBLIC_HELPER_MANIFEST),
52425
+ join21(installDir, PUBLIC_HELPER_MANIFEST),
52421
52426
  JSON.stringify(manifest, null, 2) + "\n",
52422
52427
  { mode: 384 }
52423
52428
  );
@@ -52869,10 +52874,10 @@ function renderNotesMarkdown(notes, ctx, { wrap: wrap3 = 0 } = {}) {
52869
52874
 
52870
52875
  // src/utils/noteExportData.ts
52871
52876
  import { execFileSync as execFileSync18 } from "node:child_process";
52872
- import { homedir as homedir18 } from "node:os";
52873
- import { join as join21 } from "node:path";
52874
- var NOTES_DB_PATH11 = join21(
52875
- 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(),
52876
52881
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
52877
52882
  );
52878
52883
  var IMAGE_UTIS2 = /* @__PURE__ */ new Set([
@@ -53183,16 +53188,16 @@ var NOTE_PLACEHOLDERS = [
53183
53188
  "exportStem"
53184
53189
  ];
53185
53190
  var PLACEHOLDER_MODIFIERS = ["raw", "yaml"];
53186
- var wrap2 = (before, after = "", join33) => ({
53191
+ var wrap2 = (before, after = "", join35) => ({
53187
53192
  mode: "wrap",
53188
53193
  before,
53189
53194
  after,
53190
- ...join33 ? { join: join33 } : {}
53195
+ ...join35 ? { join: join35 } : {}
53191
53196
  });
53192
- var pattern = (value, join33) => ({
53197
+ var pattern = (value, join35) => ({
53193
53198
  mode: "pattern",
53194
53199
  value,
53195
- ...join33 ? { join: join33 } : {}
53200
+ ...join35 ? { join: join35 } : {}
53196
53201
  });
53197
53202
  var STANDARD = {
53198
53203
  schemaVersion: 1,
@@ -54080,7 +54085,7 @@ import {
54080
54085
  unlinkSync as unlinkSync3,
54081
54086
  writeSync as writeSync4
54082
54087
  } from "node:fs";
54083
- 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";
54084
54089
  var CHUNK = 1024 * 1024;
54085
54090
  function openRegular(path10) {
54086
54091
  const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
@@ -54151,7 +54156,7 @@ var HashedSidecarWriter = class {
54151
54156
  if (directoryError) return { error: directoryError };
54152
54157
  const name = safeAssetName(asset.name, mime);
54153
54158
  const ext = extname4(name);
54154
- const target = join22(
54159
+ const target = join23(
54155
54160
  this.dir,
54156
54161
  `${name.slice(0, name.length - ext.length)}-${hash.slice(0, 8)}${ext}`
54157
54162
  );
@@ -54188,7 +54193,7 @@ var HashedSidecarWriter = class {
54188
54193
  * remove it if the copy fails.
54189
54194
  */
54190
54195
  copy(source, target) {
54191
- const temp = join22(this.dir, `.asset-${randomBytes(6).toString("hex")}.tmp`);
54196
+ const temp = join23(this.dir, `.asset-${randomBytes(6).toString("hex")}.tmp`);
54192
54197
  writeNew(source, temp);
54193
54198
  try {
54194
54199
  linkSync(temp, target);
@@ -54455,7 +54460,7 @@ function exportNotesMarkdown(request, deps) {
54455
54460
  return { ...receipt, markdown };
54456
54461
  }
54457
54462
  function defaultSidecarDir(output) {
54458
- return join23(dirname7(output), `${basename4(output, extname5(output))}.assets`);
54463
+ return join24(dirname7(output), `${basename4(output, extname5(output))}.assets`);
54459
54464
  }
54460
54465
  function exportNotesHtml(request, deps) {
54461
54466
  if (!request.outputPath)
@@ -54702,7 +54707,7 @@ import {
54702
54707
  writeFileSync as writeFileSync6
54703
54708
  } from "node:fs";
54704
54709
  import { tmpdir as tmpdir6 } from "node:os";
54705
- 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";
54706
54711
 
54707
54712
  // src/utils/pasteboardFreeze.ts
54708
54713
  import { execFileSync as execFileSync19 } from "node:child_process";
@@ -54718,7 +54723,7 @@ import {
54718
54723
  writeFileSync as writeFileSync5
54719
54724
  } from "node:fs";
54720
54725
  import { tmpdir as tmpdir5 } from "node:os";
54721
- 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";
54722
54727
  var PASTEBOARD_NAME_ENV = "APPLE_NOTES_MCP_PASTEBOARD_NAME";
54723
54728
  var MAX_PASTEBOARD_BYTES = 64 * 1024 * 1024;
54724
54729
  var PASTEBOARD_DATA_TYPES = [
@@ -54894,7 +54899,7 @@ function readRegularFile(path10) {
54894
54899
  }
54895
54900
  }
54896
54901
  function freezePasteboard(options = {}) {
54897
- const directory = mkdtempSync6(join24(tmpdir5(), "notes-pasteboard-"));
54902
+ const directory = mkdtempSync6(join25(tmpdir5(), "notes-pasteboard-"));
54898
54903
  const cleanup = () => rmSync6(directory, { recursive: true, force: true });
54899
54904
  try {
54900
54905
  const prefs = JSON.stringify(PASTEBOARD_DATA_TYPES.map((t) => [t.type, t.ext]));
@@ -54923,15 +54928,15 @@ function freezePasteboard(options = {}) {
54923
54928
  if (reply.kind === "file") {
54924
54929
  const bytes = readRegularFile(reply.path);
54925
54930
  const filename2 = basename5(reply.path);
54926
- const path11 = join24(directory, filename2);
54931
+ const path11 = join25(directory, filename2);
54927
54932
  writeFileSync5(path11, bytes, { mode: 384 });
54928
54933
  return { kind: "file", type: reply.type, path: path11, filename: filename2, bytes: bytes.length, cleanup };
54929
54934
  }
54930
- if (join24(directory, basename5(reply.path)) !== reply.path)
54935
+ if (join25(directory, basename5(reply.path)) !== reply.path)
54931
54936
  throw new PasteboardError("write_failed", MESSAGES.write_failed);
54932
54937
  const label = PASTEBOARD_DATA_TYPES.find((t) => t.type === reply.type)?.label ?? "item";
54933
54938
  const filename = `Pasted ${label}${extname6(reply.path)}`;
54934
- const path10 = join24(directory, filename);
54939
+ const path10 = join25(directory, filename);
54935
54940
  writeFileSync5(path10, readFileSync4(reply.path), { mode: 384 });
54936
54941
  rmSync6(reply.path, { force: true });
54937
54942
  return { kind: "data", type: reply.type, path: path10, filename, bytes: statSync4(path10).size, cleanup };
@@ -55290,8 +55295,8 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
55290
55295
  const bytes = localAttachment(path10);
55291
55296
  const beforeAttachments = manager.listAttachmentsById(id2);
55292
55297
  const beforeStored = storedAttachmentIds(manager, id2);
55293
- const directory = mkdtempSync7(join25(tmpdir6(), "notes-attachment-add-"));
55294
- const temporaryFile = join25(directory, name);
55298
+ const directory = mkdtempSync7(join26(tmpdir6(), "notes-attachment-add-"));
55299
+ const temporaryFile = join26(directory, name);
55295
55300
  try {
55296
55301
  writeFileSync6(temporaryFile, bytes, { mode: 384 });
55297
55302
  if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
@@ -55320,7 +55325,7 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
55320
55325
  if (inserted.length === 1 && !(persistentReturnedId && returnedId !== inserted[0].id)) {
55321
55326
  attachmentId = inserted[0].id;
55322
55327
  reportedName = inserted[0].name;
55323
- const verifyPath = join25(directory, "verify", "attachment.bin");
55328
+ const verifyPath = join26(directory, "verify", "attachment.bin");
55324
55329
  const saved = manager.saveAttachmentById(id2, attachmentId, verifyPath);
55325
55330
  if (!saved.success || !fileMatches(saved.savedPath ?? verifyPath, bytes.length, sha256(bytes)))
55326
55331
  throw new Error("Attachment bytes were not verified; read the exact note before retrying");
@@ -55328,7 +55333,7 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
55328
55333
  const stored = inserted.length === 0 && beforeStored ? storedInsertion(manager, id2, beforeStored, bytes, name, returnedId) : null;
55329
55334
  if (!stored)
55330
55335
  throw new Error(
55331
- 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
55332
55337
  );
55333
55338
  attachmentId = stored.attachmentId;
55334
55339
  reportedName = stored.name;
@@ -55442,7 +55447,7 @@ function classify(error2) {
55442
55447
  const message = error2 instanceof Error ? error2.message : String(error2);
55443
55448
  if (/authorization denied|unable to open database|not authorized/i.test(message)) {
55444
55449
  return new FolderStoreError(
55445
- "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()}`,
55446
55451
  "no_fda"
55447
55452
  );
55448
55453
  }
@@ -55717,8 +55722,8 @@ import {
55717
55722
  unlinkSync as unlinkSync4,
55718
55723
  writeSync as writeSync5
55719
55724
  } from "node:fs";
55720
- import { homedir as homedir20 } from "node:os";
55721
- 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";
55722
55727
  var TEMPLATE_SLUG = /^[a-z0-9][a-z0-9_-]{0,63}$/;
55723
55728
  var TemplateStoreError = class extends Error {
55724
55729
  constructor(code, message) {
@@ -55738,7 +55743,7 @@ function templateDir(env = process.env) {
55738
55743
  );
55739
55744
  return resolve6(override);
55740
55745
  }
55741
- return join27(homedir20(), "Library/Application Support/apple-notes-mcp/templates");
55746
+ return join28(homedir21(), "Library/Application Support/apple-notes-mcp/templates");
55742
55747
  }
55743
55748
  function checkName(name, action) {
55744
55749
  if (isBuiltinTemplate(name) && action !== "read")
@@ -55775,7 +55780,7 @@ var TemplateStore = class {
55775
55780
  return this.dir;
55776
55781
  }
55777
55782
  file(name) {
55778
- return join27(this.dir, `${name}.json`);
55783
+ return join28(this.dir, `${name}.json`);
55779
55784
  }
55780
55785
  /**
55781
55786
  * Read a saved template's text, refusing symlinks and non-regular files.
@@ -55901,7 +55906,7 @@ var TemplateStore = class {
55901
55906
  "template-exists",
55902
55907
  `A template named "${name}" already exists. Pass force: true to replace it.`
55903
55908
  );
55904
- const temp = join27(this.dir, `.${name}.${randomBytes2(6).toString("hex")}.tmp`);
55909
+ const temp = join28(this.dir, `.${name}.${randomBytes2(6).toString("hex")}.tmp`);
55905
55910
  const fd = openSync8(
55906
55911
  temp,
55907
55912
  constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
@@ -57120,7 +57125,7 @@ function parseXml(source, limits) {
57120
57125
  (own ??= /* @__PURE__ */ new Map()).set(prefix, a.value);
57121
57126
  }
57122
57127
  const scope2 = own ? { own, parent: parentScope } : parentScope ?? { own: null, parent: null };
57123
- const resolve10 = (qualified, isAttribute) => {
57128
+ const resolve11 = (qualified, isAttribute) => {
57124
57129
  const colon = qualified.indexOf(":");
57125
57130
  if (colon < 0)
57126
57131
  return {
@@ -57134,12 +57139,12 @@ function parseXml(source, limits) {
57134
57139
  if (ns === void 0) fail(`Undeclared namespace prefix "${prefix.slice(0, 40)}"`);
57135
57140
  return { local, ns };
57136
57141
  };
57137
- const resolved = resolve10(name, false);
57142
+ const resolved = resolve11(name, false);
57138
57143
  const element = {
57139
57144
  name,
57140
57145
  local: resolved.local,
57141
57146
  ns: resolved.ns,
57142
- 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) })),
57143
57148
  children: [],
57144
57149
  text: "",
57145
57150
  line: tagLine
@@ -58312,6 +58317,897 @@ function registerNativeTagsBridge(server2, manager) {
58312
58317
  );
58313
58318
  }
58314
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
+
58315
59211
  // src/tools/nativeOperations.ts
58316
59212
  var VERIFIED_BACKGROUND = /* @__PURE__ */ new Set([
58317
59213
  "append-native",
@@ -58530,7 +59426,9 @@ function registerNativeOperations(server2, manager) {
58530
59426
  ])
58531
59427
  ),
58532
59428
  unavailable: UNAVAILABLE,
58533
- ...getCapabilityMatrix()
59429
+ ...getCapabilityMatrix(),
59430
+ // Whether this process runs under the permission broker (#220).
59431
+ broker: brokerStatus()
58534
59432
  };
58535
59433
  },
58536
59434
  true
@@ -58758,10 +59656,10 @@ function registerNativeOperations(server2, manager) {
58758
59656
  }
58759
59657
 
58760
59658
  // src/setupShortcuts.ts
58761
- import { spawnSync as spawnSync3 } from "node:child_process";
58762
- import { existsSync as existsSync14 } from "node:fs";
59659
+ import { spawnSync as spawnSync4 } from "node:child_process";
59660
+ import { existsSync as existsSync15 } from "node:fs";
58763
59661
  import { release as release3 } from "node:os";
58764
- import { dirname as dirname8, resolve as resolve7 } from "node:path";
59662
+ import { dirname as dirname8, resolve as resolve8 } from "node:path";
58765
59663
  import { fileURLToPath as fileURLToPath2 } from "node:url";
58766
59664
  var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
58767
59665
  var MARKDOWN_MIN_DARWIN_MAJOR = 25;
@@ -58779,16 +59677,16 @@ var shortcutFiles = [
58779
59677
  ];
58780
59678
  function setupShortcuts(checkOnly, dependencies = {}) {
58781
59679
  const status = dependencies.status || nativeTagsStatus;
58782
- const exists = dependencies.exists || existsSync14;
59680
+ const exists = dependencies.exists || existsSync15;
58783
59681
  const open = dependencies.open || ((path10) => {
58784
- const result = spawnSync3("/usr/bin/open", [path10], { encoding: "utf8" });
59682
+ const result = spawnSync4("/usr/bin/open", [path10], { encoding: "utf8" });
58785
59683
  return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
58786
59684
  });
58787
- const baseDirectory = dependencies.baseDirectory || resolve7(dirname8(fileURLToPath2(import.meta.url)), "../shortcuts");
59685
+ const baseDirectory = dependencies.baseDirectory || resolve8(dirname8(fileURLToPath2(import.meta.url)), "../shortcuts");
58788
59686
  const osRelease = (dependencies.osRelease || release3)();
58789
59687
  const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
58790
59688
  const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
58791
- const path10 = resolve7(baseDirectory, file);
59689
+ const path10 = resolve8(baseDirectory, file);
58792
59690
  let installed = false;
58793
59691
  let identifier;
58794
59692
  let error2;
@@ -58848,7 +59746,7 @@ function formatShortcutSetup(report) {
58848
59746
 
58849
59747
  // src/utils/noteAudio.ts
58850
59748
  import { readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
58851
- import { join as join28 } from "node:path";
59749
+ import { join as join30 } from "node:path";
58852
59750
  var EXTRA_AUDIO_UTIS = [
58853
59751
  "public.mp3",
58854
59752
  "public.aiff-audio",
@@ -58873,7 +59771,7 @@ function accountDirsFor(containerDir, accountIdentifier) {
58873
59771
  if (own) return [own];
58874
59772
  let names;
58875
59773
  try {
58876
- names = readdirSync5(join28(containerDir, "Accounts")).sort();
59774
+ names = readdirSync5(join30(containerDir, "Accounts")).sort();
58877
59775
  } catch {
58878
59776
  return [];
58879
59777
  }
@@ -58885,8 +59783,8 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
58885
59783
  if (!id2 || !filename) return null;
58886
59784
  const generation = safeComponent(media?.generation);
58887
59785
  for (const dir of accountDirsFor(containerDir, accountIdentifier)) {
58888
- const base = join28(dir, "Media", id2);
58889
- 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)];
58890
59788
  for (const candidate of candidates) {
58891
59789
  const real = realInside(candidate, dir);
58892
59790
  if (real && isFile2(real)) return real;
@@ -59157,25 +60055,25 @@ function formatTranscription(result) {
59157
60055
  }
59158
60056
 
59159
60057
  // src/services/privateHelperBuild.ts
59160
- import { spawnSync as spawnSync5 } from "node:child_process";
60058
+ import { spawnSync as spawnSync6 } from "node:child_process";
59161
60059
  import {
59162
- chmodSync as chmodSync2,
59163
- existsSync as existsSync16,
59164
- mkdirSync as mkdirSync9,
59165
- mkdtempSync as mkdtempSync8,
59166
- renameSync as renameSync3,
59167
- rmSync as rmSync8,
59168
- 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
59169
60067
  } from "node:fs";
59170
60068
  import { release as release4 } from "node:os";
59171
- import { join as join30 } from "node:path";
60069
+ import { join as join32 } from "node:path";
59172
60070
 
59173
60071
  // src/services/privateHelper.ts
59174
- import { spawnSync as spawnSync4 } from "node:child_process";
60072
+ import { spawnSync as spawnSync5 } from "node:child_process";
59175
60073
  import { createHash as createHash7 } from "node:crypto";
59176
- import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
59177
- import { homedir as homedir21 } from "node:os";
59178
- 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";
59179
60077
  import { fileURLToPath as fileURLToPath3 } from "node:url";
59180
60078
  var PRIVATE_HELPER_PROTOCOL = 1;
59181
60079
  var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
@@ -59203,16 +60101,16 @@ var manifestSchema = external_exports.object({
59203
60101
  function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
59204
60102
  let dir = fromDir;
59205
60103
  for (; ; ) {
59206
- const candidate = join29(dir, "package.json");
59207
- if (existsSync15(candidate)) {
60104
+ const candidate = join31(dir, "package.json");
60105
+ if (existsSync16(candidate)) {
59208
60106
  try {
59209
- const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
60107
+ const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
59210
60108
  if (pkg.name === "apple-notes-mcp") return dir;
59211
60109
  } catch {
59212
60110
  }
59213
60111
  }
59214
60112
  const parent = dirname9(dir);
59215
- if (parent === dir) return resolve8(fromDir, "..");
60113
+ if (parent === dir) return resolve9(fromDir, "..");
59216
60114
  dir = parent;
59217
60115
  }
59218
60116
  }
@@ -59220,10 +60118,10 @@ function defaultDeps2(overrides = {}) {
59220
60118
  return {
59221
60119
  env: process.env,
59222
60120
  platform: process.platform,
59223
- sourcePath: join29(packageRoot2(), HELPER_SOURCE_RELATIVE),
59224
- exists: existsSync15,
59225
- readFile: (path10) => readFileSync5(path10),
59226
- spawn: spawnSync4,
60121
+ sourcePath: join31(packageRoot2(), HELPER_SOURCE_RELATIVE),
60122
+ exists: existsSync16,
60123
+ readFile: (path10) => readFileSync6(path10),
60124
+ spawn: spawnSync5,
59227
60125
  ...overrides
59228
60126
  };
59229
60127
  }
@@ -59233,14 +60131,14 @@ function privateHelperEnabled(env = process.env) {
59233
60131
  function helperInstallDir(env = process.env) {
59234
60132
  const override = env[HELPER_DIR_ENV]?.trim();
59235
60133
  if (override) return override;
59236
- return join29(homedir21(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
60134
+ return join31(homedir23(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
59237
60135
  }
59238
60136
  function sha256Hex2(data) {
59239
60137
  return createHash7("sha256").update(data).digest("hex");
59240
60138
  }
59241
60139
  function inspectInstallation(deps = defaultDeps2()) {
59242
60140
  const installDir = helperInstallDir(deps.env);
59243
- const binaryPath = join29(installDir, HELPER_BINARY_NAME);
60141
+ const binaryPath = join31(installDir, HELPER_BINARY_NAME);
59244
60142
  const base = {
59245
60143
  installDir,
59246
60144
  binaryPath,
@@ -59258,7 +60156,7 @@ function inspectInstallation(deps = defaultDeps2()) {
59258
60156
  if (!deps.exists(deps.sourcePath))
59259
60157
  return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
59260
60158
  base.expectedSourceSha256 = sha256Hex2(deps.readFile(deps.sourcePath));
59261
- const manifestPath = join29(installDir, MANIFEST_NAME);
60159
+ const manifestPath = join31(installDir, MANIFEST_NAME);
59262
60160
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
59263
60161
  return fail(
59264
60162
  "helper_not_installed",
@@ -59516,7 +60414,7 @@ function defaultBuildDeps() {
59516
60414
  return {
59517
60415
  ...defaultDeps2(),
59518
60416
  osVersion: () => {
59519
- const r = spawnSync5("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
60417
+ const r = spawnSync6("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
59520
60418
  return r.status === 0 ? r.stdout.trim() : `Darwin ${release4()}`;
59521
60419
  },
59522
60420
  now: () => /* @__PURE__ */ new Date()
@@ -59586,10 +60484,10 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
59586
60484
  const compiler = String(clangVersion.stdout || "").split("\n")[0] || "clang";
59587
60485
  steps.push({ step: "find compiler", ok: true, detail: compiler });
59588
60486
  const installDir = helperInstallDir(deps.env);
59589
- mkdirSync9(installDir, { recursive: true, mode: 448 });
59590
- const staging = mkdtempSync8(join30(installDir, ".staging-"));
60487
+ mkdirSync10(installDir, { recursive: true, mode: 448 });
60488
+ const staging = mkdtempSync9(join32(installDir, ".staging-"));
59591
60489
  try {
59592
- const stagedBinary = join30(staging, HELPER_BINARY_NAME);
60490
+ const stagedBinary = join32(staging, HELPER_BINARY_NAME);
59593
60491
  const compile = deps.spawn(
59594
60492
  "/usr/bin/xcrun",
59595
60493
  compileArguments(deps.sourcePath, stagedBinary, sourceSha),
@@ -59665,14 +60563,14 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
59665
60563
  osVersion: deps.osVersion(),
59666
60564
  compiler
59667
60565
  };
59668
- chmodSync2(stagedBinary, 448);
59669
- renameSync3(stagedBinary, join30(installDir, HELPER_BINARY_NAME));
59670
- 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", {
59671
60569
  mode: 384
59672
60570
  });
59673
60571
  steps.push({ step: "install", ok: true, detail: installDir });
59674
60572
  } finally {
59675
- if (existsSync16(staging)) rmSync8(staging, { recursive: true, force: true });
60573
+ if (existsSync17(staging)) rmSync9(staging, { recursive: true, force: true });
59676
60574
  }
59677
60575
  const installation = inspectInstallation(deps);
59678
60576
  steps.push({
@@ -59701,7 +60599,7 @@ function formatHelperBuild(report) {
59701
60599
  }
59702
60600
 
59703
60601
  // src/services/permissions.ts
59704
- import { spawnSync as spawnSync6 } from "node:child_process";
60602
+ import { spawnSync as spawnSync7 } from "node:child_process";
59705
60603
  import { createInterface } from "node:readline";
59706
60604
  var PRIVACY = "System Settings > Privacy & Security";
59707
60605
  var PANE_ANCHORS = {
@@ -59737,7 +60635,7 @@ function checkPermissions(probes = defaultPermissionProbes(), options = defaultC
59737
60635
  const launchingApp = safe(probes.launchingApp, null);
59738
60636
  const who = launchingApp ?? "the app that launches the server";
59739
60637
  const items = [
59740
- fullDiskAccessItem(probes, macOSVersion),
60638
+ fullDiskAccessItem(probes, macOSVersion, options.env),
59741
60639
  automationItem(probes, macOSVersion, who, automationSkipReason(options)),
59742
60640
  shortcutsItem(probes),
59743
60641
  speechItem(probes, macOSVersion, who)
@@ -59760,7 +60658,7 @@ function safe(fn, fallback) {
59760
60658
  function errorText(error2) {
59761
60659
  return error2 instanceof Error ? error2.message : String(error2);
59762
60660
  }
59763
- function fullDiskAccessItem(probes, macOSVersion) {
60661
+ function fullDiskAccessItem(probes, macOSVersion, env) {
59764
60662
  const base = {
59765
60663
  id: "fullDiskAccess",
59766
60664
  title: "Full Disk Access",
@@ -59775,7 +60673,7 @@ function fullDiskAccessItem(probes, macOSVersion) {
59775
60673
  ...base,
59776
60674
  status: "unknown",
59777
60675
  detail: `could not probe the Notes database: ${errorText(error2)}`,
59778
- fix: fdaRemediation(probes.execPath)
60676
+ fix: fdaRemediation(probes.execPath, env)
59779
60677
  };
59780
60678
  }
59781
60679
  return granted ? {
@@ -59787,7 +60685,7 @@ function fullDiskAccessItem(probes, macOSVersion) {
59787
60685
  ...base,
59788
60686
  status: "missing",
59789
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",
59790
- fix: fdaRemediation(probes.execPath)
60688
+ fix: fdaRemediation(probes.execPath, env)
59791
60689
  };
59792
60690
  }
59793
60691
  var PROBE_AUTOMATION_COMMAND = "apple-notes-mcp setup --permissions --probe-automation";
@@ -59945,7 +60843,7 @@ function findLaunchingApp(startPid = process.ppid, readProcess = readProcessEntr
59945
60843
  return null;
59946
60844
  }
59947
60845
  function readProcessEntry(pid) {
59948
- 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)], {
59949
60847
  encoding: "utf8",
59950
60848
  timeout: 3e3
59951
60849
  });
@@ -59986,7 +60884,7 @@ function openSettingsPane(item, open = openUrl) {
59986
60884
  return open(item.settingsUrl);
59987
60885
  }
59988
60886
  function openUrl(url) {
59989
- const result = spawnSync6("/usr/bin/open", [url], { encoding: "utf8" });
60887
+ const result = spawnSync7("/usr/bin/open", [url], { encoding: "utf8" });
59990
60888
  return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
59991
60889
  }
59992
60890
  function pendingItems(report) {
@@ -60098,19 +60996,19 @@ function defaultPermissionsCliDeps(options) {
60098
60996
  }
60099
60997
 
60100
60998
  // src/services/permissionsWindow.ts
60101
- import { spawn as spawn2, spawnSync as spawnSync7 } from "node:child_process";
60999
+ import { spawn as spawn2, spawnSync as spawnSync8 } from "node:child_process";
60102
61000
  import {
60103
- chmodSync as chmodSync3,
60104
- existsSync as existsSync17,
60105
- mkdirSync as mkdirSync10,
60106
- mkdtempSync as mkdtempSync9,
60107
- readFileSync as readFileSync6,
60108
- renameSync as renameSync4,
60109
- rmSync as rmSync9,
60110
- 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
60111
61009
  } from "node:fs";
60112
- import { homedir as homedir22 } from "node:os";
60113
- import { join as join31 } from "node:path";
61010
+ import { homedir as homedir24 } from "node:os";
61011
+ import { join as join33 } from "node:path";
60114
61012
  import { createInterface as createInterface2 } from "node:readline";
60115
61013
  var PERMISSIONS_WINDOW_PROTOCOL = 1;
60116
61014
  var PERMISSIONS_WINDOW_DIR_ENV = "APPLE_NOTES_MCP_PERMISSIONS_WINDOW_DIR";
@@ -60131,10 +61029,10 @@ function defaultPermissionsWindowDeps(overrides = {}) {
60131
61029
  return {
60132
61030
  env: process.env,
60133
61031
  platform: process.platform,
60134
- sourcePath: join31(packageRoot(), PERMISSIONS_WINDOW_SOURCE),
60135
- exists: existsSync17,
60136
- readFile: (path10) => readFileSync6(path10),
60137
- spawn: spawnSync7,
61032
+ sourcePath: join33(packageRoot(), PERMISSIONS_WINDOW_SOURCE),
61033
+ exists: existsSync18,
61034
+ readFile: (path10) => readFileSync7(path10),
61035
+ spawn: spawnSync8,
60138
61036
  now: () => /* @__PURE__ */ new Date(),
60139
61037
  ...overrides
60140
61038
  };
@@ -60142,17 +61040,17 @@ function defaultPermissionsWindowDeps(overrides = {}) {
60142
61040
  function permissionsWindowInstallDir(env = process.env) {
60143
61041
  const override = env[PERMISSIONS_WINDOW_DIR_ENV]?.trim();
60144
61042
  if (override) return override;
60145
- return join31(homedir22(), "Library", "Application Support", "apple-notes-mcp", "permissions-window");
61043
+ return join33(homedir24(), "Library", "Application Support", "apple-notes-mcp", "permissions-window");
60146
61044
  }
60147
61045
  function inspectPermissionsWindow(deps = defaultPermissionsWindowDeps()) {
60148
61046
  const installDir = permissionsWindowInstallDir(deps.env);
60149
- const binaryPath = join31(installDir, PERMISSIONS_WINDOW_BINARY);
61047
+ const binaryPath = join33(installDir, PERMISSIONS_WINDOW_BINARY);
60150
61048
  const fail = (reason, detail) => ({ ready: false, reason, detail, installDir, binaryPath });
60151
61049
  if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
60152
61050
  const rebuild = `Run \`${PERMISSIONS_WINDOW_SETUP_COMMAND}\`.`;
60153
61051
  if (!deps.exists(deps.sourcePath))
60154
61052
  return fail("window_not_installed", `Packaged window source is missing: ${deps.sourcePath}`);
60155
- const manifestPath = join31(installDir, PERMISSIONS_WINDOW_MANIFEST);
61053
+ const manifestPath = join33(installDir, PERMISSIONS_WINDOW_MANIFEST);
60156
61054
  if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
60157
61055
  return fail("window_not_installed", `The permissions window is not built. ${rebuild}`);
60158
61056
  let manifest;
@@ -60260,14 +61158,14 @@ function buildPermissionsWindow(checkOnly, deps = defaultPermissionsWindowDeps()
60260
61158
  const compiler = String(version3.stdout || version3.stderr || "").split("\n").find((line) => line.includes("Swift version"))?.trim() || "swiftc";
60261
61159
  steps.push({ step: "find compiler", ok: true, detail: compiler });
60262
61160
  const installDir = permissionsWindowInstallDir(deps.env);
60263
- mkdirSync10(installDir, { recursive: true, mode: 448 });
60264
- const staging = mkdtempSync9(join31(installDir, ".staging-"));
61161
+ mkdirSync11(installDir, { recursive: true, mode: 448 });
61162
+ const staging = mkdtempSync10(join33(installDir, ".staging-"));
60265
61163
  try {
60266
- const stagedBinary = join31(staging, PERMISSIONS_WINDOW_BINARY);
60267
- const digestPath = join31(staging, "source-digest.swift");
60268
- const plistPath = join31(staging, "Info.plist");
60269
- writeFileSync8(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
60270
- 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 });
60271
61169
  const compile = deps.spawn(
60272
61170
  "/usr/bin/xcrun",
60273
61171
  permissionsWindowCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
@@ -60321,16 +61219,16 @@ function buildPermissionsWindow(checkOnly, deps = defaultPermissionsWindowDeps()
60321
61219
  builtAt: deps.now().toISOString(),
60322
61220
  compiler
60323
61221
  };
60324
- chmodSync3(stagedBinary, 448);
60325
- renameSync4(stagedBinary, join31(installDir, PERMISSIONS_WINDOW_BINARY));
60326
- writeFileSync8(
60327
- 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),
60328
61226
  JSON.stringify(manifest, null, 2) + "\n",
60329
61227
  { mode: 384 }
60330
61228
  );
60331
61229
  steps.push({ step: "install", ok: true, detail: installDir });
60332
61230
  } finally {
60333
- rmSync9(staging, { recursive: true, force: true });
61231
+ rmSync10(staging, { recursive: true, force: true });
60334
61232
  }
60335
61233
  const installation = inspectPermissionsWindow(deps);
60336
61234
  steps.push({
@@ -60404,6 +61302,288 @@ function runPermissionsWindow(binaryPath, session) {
60404
61302
  });
60405
61303
  }
60406
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
+
60407
61587
  // src/tools/privateHelperTools.ts
60408
61588
  var coreDataId2 = external_exports.string().regex(/^x-coredata:\/\/[0-9A-F-]+\/ICNote\/p\d+$/i);
60409
61589
  var notesUuid = external_exports.string().regex(UUID_PATTERN);
@@ -61091,24 +62271,24 @@ async function startTemplateEditor(options = {}) {
61091
62271
  socket.on("close", () => sockets.delete(socket));
61092
62272
  });
61093
62273
  let resolveClosed;
61094
- const closed = new Promise((resolve10) => resolveClosed = resolve10);
62274
+ const closed = new Promise((resolve11) => resolveClosed = resolve11);
61095
62275
  let closing;
61096
62276
  const close = () => {
61097
- closing ??= new Promise((resolve10) => {
62277
+ closing ??= new Promise((resolve11) => {
61098
62278
  if (idleTimer) clearTimeout(idleTimer);
61099
62279
  server2.close(() => {
61100
62280
  resolveClosed(closeReason);
61101
- resolve10();
62281
+ resolve11();
61102
62282
  });
61103
62283
  for (const socket of sockets) socket.destroy();
61104
62284
  });
61105
62285
  return closing;
61106
62286
  };
61107
- await new Promise((resolve10, reject) => {
62287
+ await new Promise((resolve11, reject) => {
61108
62288
  server2.once("error", reject);
61109
62289
  server2.listen({ host, port: options.port ?? 0, exclusive: true }, () => {
61110
62290
  server2.off("error", reject);
61111
- resolve10();
62291
+ resolve11();
61112
62292
  });
61113
62293
  });
61114
62294
  const port = server2.address().port;
@@ -61552,13 +62732,13 @@ function createAnchorServer(options) {
61552
62732
  }
61553
62733
  function startAnchorServer(options) {
61554
62734
  const server2 = createAnchorServer(options);
61555
- return new Promise((resolve10, reject) => {
62735
+ return new Promise((resolve11, reject) => {
61556
62736
  server2.once("error", reject);
61557
62737
  server2.listen(options.port, options.host, () => {
61558
62738
  server2.off("error", reject);
61559
62739
  const address = server2.address();
61560
62740
  const port = address && typeof address === "object" ? address.port : options.port;
61561
- resolve10({
62741
+ resolve11({
61562
62742
  server: server2,
61563
62743
  baseUrl: `http://${hostAuthority(options.host, port)}`,
61564
62744
  close: () => new Promise((done) => {
@@ -61596,7 +62776,7 @@ function parseAnchorsArgs(argv) {
61596
62776
  }
61597
62777
  async function runAnchorsCli(argv, {
61598
62778
  env = process.env,
61599
- resolve: resolve10,
62779
+ resolve: resolve11,
61600
62780
  out = (text2) => process.stdout.write(text2),
61601
62781
  interfaces,
61602
62782
  signals = process
@@ -61629,7 +62809,7 @@ ${ANCHORS_USAGE}
61629
62809
  const token = fromEnv || newServerToken();
61630
62810
  let started;
61631
62811
  try {
61632
- started = await startAnchorServer({ host, port: args.port, token, resolve: resolve10 });
62812
+ started = await startAnchorServer({ host, port: args.port, token, resolve: resolve11 });
61633
62813
  } catch (error2) {
61634
62814
  out(`Could not listen on ${host}:${args.port}: ${error2.message}
61635
62815
  `);
@@ -61656,17 +62836,17 @@ import {
61656
62836
  constants as constants9,
61657
62837
  fstatSync as fstatSync9,
61658
62838
  fsyncSync,
61659
- lstatSync as lstatSync6,
61660
- mkdirSync as mkdirSync11,
62839
+ lstatSync as lstatSync7,
62840
+ mkdirSync as mkdirSync12,
61661
62841
  openSync as openSync9,
61662
62842
  readSync as readSync7,
61663
- renameSync as renameSync5,
62843
+ renameSync as renameSync6,
61664
62844
  statSync as statSync6,
61665
62845
  unlinkSync as unlinkSync5,
61666
62846
  writeSync as writeSync6
61667
62847
  } from "node:fs";
61668
- import { homedir as homedir23 } from "node:os";
61669
- 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";
61670
62850
  var MAX_ANCHORS = 2e4;
61671
62851
  var MAX_REGISTRY_BYTES = 32 * 1024 * 1024;
61672
62852
  var LOCK_WAIT_MS = 3e3;
@@ -61690,14 +62870,14 @@ var AnchorRegistryError = class extends CodedError {
61690
62870
  function anchorRegistryPath(env = process.env) {
61691
62871
  const override = env.APPLE_NOTES_MCP_ANCHOR_FILE?.trim();
61692
62872
  if (override) {
61693
- if (!isAbsolute6(override))
62873
+ if (!isAbsolute7(override))
61694
62874
  throw new AnchorRegistryError(
61695
62875
  "unsafe-path",
61696
62876
  "APPLE_NOTES_MCP_ANCHOR_FILE must be an absolute path."
61697
62877
  );
61698
- return resolve9(override);
62878
+ return resolve10(override);
61699
62879
  }
61700
- 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");
61701
62881
  }
61702
62882
  var STRING_OR_NULL = (v) => v === null || typeof v === "string";
61703
62883
  function isAnchor(v) {
@@ -61720,14 +62900,14 @@ var AnchorRegistry = class _AnchorRegistry {
61720
62900
  ensureDir() {
61721
62901
  const dir = dirname10(this.path);
61722
62902
  try {
61723
- if (!lstatSync6(dir).isDirectory())
62903
+ if (!lstatSync7(dir).isDirectory())
61724
62904
  throw new AnchorRegistryError(
61725
62905
  "unsafe-path",
61726
62906
  `${dir} is not a directory (symlinks are refused).`
61727
62907
  );
61728
62908
  } catch (error2) {
61729
62909
  if (error2 instanceof AnchorRegistryError) throw error2;
61730
- mkdirSync11(dir, { recursive: true, mode: 448 });
62910
+ mkdirSync12(dir, { recursive: true, mode: 448 });
61731
62911
  }
61732
62912
  }
61733
62913
  /** Every anchor in the file, in the order recorded. A missing file is an empty registry. */
@@ -61817,7 +62997,7 @@ var AnchorRegistry = class _AnchorRegistry {
61817
62997
  }
61818
62998
  write(anchors) {
61819
62999
  try {
61820
- if (!lstatSync6(this.path).isFile())
63000
+ if (!lstatSync7(this.path).isFile())
61821
63001
  throw new AnchorRegistryError(
61822
63002
  "unsafe-path",
61823
63003
  `Refusing to replace ${this.path}: it is not a regular file.`
@@ -61825,7 +63005,7 @@ var AnchorRegistry = class _AnchorRegistry {
61825
63005
  } catch (error2) {
61826
63006
  if (error2 instanceof AnchorRegistryError) throw error2;
61827
63007
  }
61828
- const temp = join32(
63008
+ const temp = join34(
61829
63009
  dirname10(this.path),
61830
63010
  `.paragraph-anchors.${randomBytes5(6).toString("hex")}.tmp`
61831
63011
  );
@@ -61846,7 +63026,7 @@ var AnchorRegistry = class _AnchorRegistry {
61846
63026
  }
61847
63027
  closeSync9(fd);
61848
63028
  try {
61849
- renameSync5(temp, this.path);
63029
+ renameSync6(temp, this.path);
61850
63030
  } catch (error2) {
61851
63031
  unlinkSync5(temp);
61852
63032
  throw error2;
@@ -62041,6 +63221,11 @@ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--permissions
62041
63221
  cli.close();
62042
63222
  process.exit(code);
62043
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
+ }
62044
63229
  if (process.argv[2] === "setup") {
62045
63230
  const report = setupShortcuts(process.argv.slice(3).includes("--check"));
62046
63231
  process.stdout.write(formatShortcutSetup(report) + "\n");
@@ -62054,6 +63239,10 @@ if (process.argv[2] === "anchors") {
62054
63239
  await runAnchorsCli(process.argv.slice(3), { resolve: registryLookup(new AnchorRegistry()) })
62055
63240
  );
62056
63241
  }
63242
+ if (await startBrokerProxy()) {
63243
+ await new Promise(() => {
63244
+ });
63245
+ }
62057
63246
  var server = new McpServer({
62058
63247
  name: "apple-notes",
62059
63248
  version: version2,
@@ -62927,7 +64116,7 @@ registerTool(
62927
64116
  registerTool(
62928
64117
  "get-note-link",
62929
64118
  {
62930
- 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.",
62931
64120
  inputSchema: {
62932
64121
  id: looseNoteId(external_exports.string()).optional().describe(`Note ID (preferred - more reliable than title): ${NOTE_ID_FORMS}`),
62933
64122
  title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
@@ -62953,7 +64142,7 @@ registerTool(
62953
64142
  const url2 = notesManager.getNoteLinkById(id2);
62954
64143
  if (!url2) {
62955
64144
  return errorResponse(
62956
- `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.)`
62957
64146
  );
62958
64147
  }
62959
64148
  return successResponse(`Note link: ${url2}`, { id: id2, title: note2.title, url: url2 });
@@ -62973,7 +64162,7 @@ registerTool(
62973
64162
  const url = notesManager.getNoteLink(title, account);
62974
64163
  if (!url) {
62975
64164
  return errorResponse(
62976
- `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.)`
62977
64166
  );
62978
64167
  }
62979
64168
  return successResponse(`Note link: ${url}`, { title, url });
@@ -63146,7 +64335,7 @@ registerTool(
63146
64335
  page = pageNoteBlocks(readNoteBlocks(id2), { offset, limit });
63147
64336
  } catch (error2) {
63148
64337
  if (!(error2 instanceof NoteBlocksError)) throw error2;
63149
- 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()}` : "";
63150
64339
  return errorResponse(
63151
64340
  `Error reading note blocks [${error2.code}]: ${error2.message}${hint}`,
63152
64341
  error2
@@ -64475,12 +65664,19 @@ registerTool(
64475
65664
  healthy: external_exports.boolean().optional(),
64476
65665
  checks: external_exports.array(external_exports.object({}).passthrough()).optional(),
64477
65666
  runtimeOS: external_exports.object({}).passthrough().optional(),
64478
- 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()
64479
65669
  }
64480
65670
  },
64481
65671
  withErrorHandling(() => {
64482
65672
  const report = runDoctor(notesManager);
64483
- 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
+ });
64484
65680
  }, "Error running doctor")
64485
65681
  );
64486
65682
  registerTool(
@@ -65163,7 +66359,7 @@ registerTool(
65163
66359
  });
65164
66360
  } catch (error2) {
65165
66361
  if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
65166
- 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()}` : "";
65167
66363
  if (error2 instanceof NotesExportError && error2.details)
65168
66364
  return errorResponse(
65169
66365
  `Error exporting Markdown [${error2.code}]: the template is invalid:
@@ -65233,7 +66429,7 @@ registerTool(
65233
66429
  });
65234
66430
  } catch (error2) {
65235
66431
  if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
65236
- 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()}` : "";
65237
66433
  return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
65238
66434
  }
65239
66435
  const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;