apple-notes-mcp 2.8.49 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -2991,7 +2991,7 @@ var require_compile = __commonJS({
2991
2991
  const schOrFunc = root.refs[ref];
2992
2992
  if (schOrFunc)
2993
2993
  return schOrFunc;
2994
- let _sch = resolve5.call(this, root, ref);
2994
+ let _sch = resolve6.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 resolve5(root, ref) {
3021
+ function resolve6(root, ref) {
3022
3022
  let sch;
3023
3023
  while (typeof (sch = this.refs[ref]) == "string")
3024
3024
  ref = sch;
@@ -3843,7 +3843,7 @@ var require_fast_uri = __commonJS({
3843
3843
  }
3844
3844
  return uri;
3845
3845
  }
3846
- function resolve5(baseURI, relativeURI, options) {
3846
+ function resolve6(baseURI, relativeURI, options) {
3847
3847
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3848
3848
  const {
3849
3849
  parsed: baseParsed,
@@ -4205,7 +4205,7 @@ var require_fast_uri = __commonJS({
4205
4205
  var fastUri = {
4206
4206
  SCHEMES,
4207
4207
  normalize,
4208
- resolve: resolve5,
4208
+ resolve: resolve6,
4209
4209
  resolveComponent,
4210
4210
  equal,
4211
4211
  serialize,
@@ -24465,14 +24465,14 @@ var require_turndown_cjs = __commonJS({
24465
24465
  } else if (node.nodeType === 1) {
24466
24466
  replacement = replacementForNode.call(self, node);
24467
24467
  }
24468
- return join24(output, replacement);
24468
+ return join26(output, replacement);
24469
24469
  }, "");
24470
24470
  }
24471
24471
  function postProcess(output) {
24472
24472
  var self = this;
24473
24473
  this.rules.forEach(function(rule) {
24474
24474
  if (typeof rule.append === "function") {
24475
- output = join24(output, rule.append(self.options));
24475
+ output = join26(output, rule.append(self.options));
24476
24476
  }
24477
24477
  });
24478
24478
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -24484,7 +24484,7 @@ var require_turndown_cjs = __commonJS({
24484
24484
  if (whitespace.leading || whitespace.trailing) content = content.trim();
24485
24485
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
24486
24486
  }
24487
- function join24(output, replacement) {
24487
+ function join26(output, replacement) {
24488
24488
  var s1 = trimTrailingNewlines(output);
24489
24489
  var s2 = trimLeadingNewlines(replacement);
24490
24490
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -36608,7 +36608,7 @@ var Protocol = class {
36608
36608
  return;
36609
36609
  }
36610
36610
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
36611
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
36611
+ await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
36612
36612
  options?.signal?.throwIfAborted();
36613
36613
  }
36614
36614
  } catch (error2) {
@@ -36625,7 +36625,7 @@ var Protocol = class {
36625
36625
  */
36626
36626
  request(request, resultSchema, options) {
36627
36627
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
36628
- return new Promise((resolve5, reject) => {
36628
+ return new Promise((resolve6, reject) => {
36629
36629
  const earlyReject = (error2) => {
36630
36630
  reject(error2);
36631
36631
  };
@@ -36703,7 +36703,7 @@ var Protocol = class {
36703
36703
  if (!parseResult.success) {
36704
36704
  reject(parseResult.error);
36705
36705
  } else {
36706
- resolve5(parseResult.data);
36706
+ resolve6(parseResult.data);
36707
36707
  }
36708
36708
  } catch (error2) {
36709
36709
  reject(error2);
@@ -36964,12 +36964,12 @@ var Protocol = class {
36964
36964
  }
36965
36965
  } catch {
36966
36966
  }
36967
- return new Promise((resolve5, reject) => {
36967
+ return new Promise((resolve6, reject) => {
36968
36968
  if (signal.aborted) {
36969
36969
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
36970
36970
  return;
36971
36971
  }
36972
- const timeoutId = setTimeout(resolve5, interval);
36972
+ const timeoutId = setTimeout(resolve6, interval);
36973
36973
  signal.addEventListener("abort", () => {
36974
36974
  clearTimeout(timeoutId);
36975
36975
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -38282,7 +38282,7 @@ var McpServer = class {
38282
38282
  let task = createTaskResult.task;
38283
38283
  const pollInterval = task.pollInterval ?? 5e3;
38284
38284
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
38285
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
38285
+ await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
38286
38286
  const updatedTask = await extra.taskStore.getTask(taskId);
38287
38287
  if (!updatedTask) {
38288
38288
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -38970,12 +38970,12 @@ var StdioServerTransport = class {
38970
38970
  this.onclose?.();
38971
38971
  }
38972
38972
  send(message) {
38973
- return new Promise((resolve5) => {
38973
+ return new Promise((resolve6) => {
38974
38974
  const json2 = serializeMessage(message);
38975
38975
  if (this._stdout.write(json2)) {
38976
- resolve5();
38976
+ resolve6();
38977
38977
  } else {
38978
- this._stdout.once("drain", resolve5);
38978
+ this._stdout.once("drain", resolve6);
38979
38979
  }
38980
38980
  });
38981
38981
  }
@@ -40820,13 +40820,13 @@ function buildSmartFolders(output) {
40820
40820
  if (lines.length < 4) throw new Error("Unexpected smart folder query output");
40821
40821
  const [storeUuid, rowsJson, foldersJson, tagsJson] = lines;
40822
40822
  if (!/^[0-9A-F-]+$/i.test(storeUuid)) throw new Error("Unexpected Notes store identifier");
40823
- const coreDataId2 = (entity3, pk) => `x-coredata://${storeUuid}/${entity3}/p${pk}`;
40823
+ const coreDataId3 = (entity3, pk) => `x-coredata://${storeUuid}/${entity3}/p${pk}`;
40824
40824
  const rows = JSON.parse(rowsJson);
40825
40825
  const folderRows = JSON.parse(foldersJson || "{}");
40826
40826
  const tagRows = JSON.parse(tagsJson || "[]");
40827
40827
  const folders = {};
40828
40828
  for (const [identifier, row] of Object.entries(folderRows)) {
40829
- folders[identifier] = { id: coreDataId2("ICFolder", row.pk), title: row.title };
40829
+ folders[identifier] = { id: coreDataId3("ICFolder", row.pk), title: row.title };
40830
40830
  }
40831
40831
  const tags = {};
40832
40832
  for (const tag of tagRows) {
@@ -40837,14 +40837,14 @@ function buildSmartFolders(output) {
40837
40837
  return rows.map((row) => {
40838
40838
  const decoded = decodeSmartFolderQuery(row.query, { folders, tags });
40839
40839
  return {
40840
- id: coreDataId2("ICFolder", row.pk),
40840
+ id: coreDataId3("ICFolder", row.pk),
40841
40841
  identifier: row.identifier,
40842
40842
  name: row.title,
40843
40843
  account: row.accountName,
40844
- accountId: row.accountPk === null ? null : coreDataId2("ICAccount", row.accountPk),
40844
+ accountId: row.accountPk === null ? null : coreDataId3("ICAccount", row.accountPk),
40845
40845
  accountIdentifier: row.accountIdentifier,
40846
40846
  parent: row.parentTitle,
40847
- parentId: row.parentPk === null ? null : coreDataId2("ICFolder", row.parentPk),
40847
+ parentId: row.parentPk === null ? null : coreDataId3("ICFolder", row.parentPk),
40848
40848
  parentIdentifier: row.parentIdentifier,
40849
40849
  match: decoded.match,
40850
40850
  filters: decoded.filters,
@@ -42140,6 +42140,19 @@ function trashFolderIdList() {
42140
42140
  const ids = readTrashFolderIds().filter((id2) => FOLDER_ID_PATTERN.test(id2));
42141
42141
  return `{${ids.map((id2) => `"${id2}"`).join(", ")}}`;
42142
42142
  }
42143
+ function inRecentlyDeletedScript(containerVar, flagVar, trashIds) {
42144
+ return `
42145
+ set ${flagVar} to true
42146
+ try
42147
+ if (class of ${containerVar}) is folder then set ${flagVar} to false
42148
+ end try
42149
+ try
42150
+ if ${trashIds} contains (id of ${containerVar}) then set ${flagVar} to true
42151
+ end try
42152
+ try
42153
+ if (name of ${containerVar}) is "${RECENTLY_DELETED_FOLDER_NAME}" then set ${flagVar} to true
42154
+ end try`;
42155
+ }
42143
42156
  function buildTrashNoteIdsCollector() {
42144
42157
  return `
42145
42158
  set __trashNoteIds to {}
@@ -42453,8 +42466,8 @@ function folderDeleteFactsScript(id2) {
42453
42466
  end repeat
42454
42467
  set noteCount to count of notes of f`;
42455
42468
  }
42456
- function getNoteLinkFromDB(coreDataId2) {
42457
- const match = coreDataId2.match(/\/p(\d+)$/);
42469
+ function getNoteLinkFromDB(coreDataId3) {
42470
+ const match = coreDataId3.match(/\/p(\d+)$/);
42458
42471
  if (!match) return null;
42459
42472
  const pk = parseInt(match[1], 10);
42460
42473
  const dbPath2 = join9(homedir8(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
@@ -43024,29 +43037,53 @@ var AppleNotesManager = class {
43024
43037
  * Deletes one exact note only when its complete body still matches the body
43025
43038
  * the caller reviewed. The comparison and delete are one AppleScript action,
43026
43039
  * so a concurrent edit cannot slip between the guard and deletion.
43027
- */
43028
- deleteNoteByIdIfUnchanged(id2, expectedBody, scope2) {
43040
+ *
43041
+ * `guards` are other notes that must still be active when the delete runs
43042
+ * (the copy-then-retire guard of delete-note): each must exist, be unlocked,
43043
+ * and sit in a folder other than Recently Deleted, and one with
43044
+ * `expectedBody` must still have that body. They are checked in the same
43045
+ * script, just before the delete; `index` in a guard outcome points into
43046
+ * `guards`.
43047
+ */
43048
+ deleteNoteByIdIfUnchanged(id2, expectedBody, scope2, guards = []) {
43029
43049
  const safeId = sanitizeNoteId(id2);
43030
43050
  validateLength(expectedBody, MAX_CONTENT_LENGTH, "Expected note content");
43031
43051
  const safeExpectedBody = escapeHtmlForAppleScript(expectedBody);
43052
+ const trashIds = trashFolderIdList();
43053
+ const guardChecks = guards.map((guard, index) => {
43054
+ const safeGuardId = sanitizeNoteId(guard.id);
43055
+ const ref = `__guardRef${index}`;
43056
+ const folderVar = `__guardFolder${index}`;
43057
+ const inactive = (reason) => `return "SAFETY_GUARD_INACTIVE:${index}:${reason}"`;
43058
+ let bodyCheck = "";
43059
+ if (guard.expectedBody !== void 0) {
43060
+ validateLength(guard.expectedBody, MAX_CONTENT_LENGTH, "Expected guard note content");
43061
+ const safeGuardBody = escapeHtmlForAppleScript(guard.expectedBody);
43062
+ bodyCheck = `
43063
+ set __guardBody to body of ${ref}
43064
+ considering case
43065
+ if __guardBody is not "${safeGuardBody}" and __guardBody is not "${safeGuardBody}" & linefeed then return "SAFETY_GUARD_CONFLICT:${index}"
43066
+ end considering`;
43067
+ }
43068
+ return `
43069
+ if not (exists note id "${safeGuardId}") then ${inactive("missing")}
43070
+ set ${ref} to note id "${safeGuardId}"
43071
+ if password protected of ${ref} then ${inactive("locked")}
43072
+ set ${folderVar} to missing value
43073
+ try
43074
+ set ${folderVar} to container of ${ref}
43075
+ end try
43076
+ if ${folderVar} is missing value then ${inactive("folder unknown")}${inRecentlyDeletedScript(folderVar, "__guardInTrash", trashIds)}
43077
+ if __guardInTrash then ${inactive("in Recently Deleted")}${bodyCheck}`;
43078
+ }).join("");
43032
43079
  const script = buildAppLevelScript(`
43033
43080
  set noteRef to note id "${safeId}"${buildScopeGuardScript("noteRef", scope2)}
43034
43081
  set originalFolder to missing value
43035
43082
  try
43036
43083
  set originalFolder to container of noteRef
43037
43084
  end try
43038
- if originalFolder is missing value then return "SAFETY_CONTAINER_UNKNOWN"
43039
- set __inTrash to true
43040
- try
43041
- if (class of originalFolder) is folder then set __inTrash to false
43042
- end try
43043
- try
43044
- if ${trashFolderIdList()} contains (id of originalFolder) then set __inTrash to true
43045
- end try
43046
- try
43047
- if (name of originalFolder) is "${RECENTLY_DELETED_FOLDER_NAME}" then set __inTrash to true
43048
- end try
43049
- if __inTrash then return "SAFETY_IN_RECENTLY_DELETED"
43085
+ if originalFolder is missing value then return "SAFETY_CONTAINER_UNKNOWN"${inRecentlyDeletedScript("originalFolder", "__inTrash", trashIds)}
43086
+ if __inTrash then return "SAFETY_IN_RECENTLY_DELETED"${guardChecks}
43050
43087
  set currentBody to body of noteRef
43051
43088
  considering case
43052
43089
  if currentBody is not "${safeExpectedBody}" and currentBody is not "${safeExpectedBody}" & linefeed then return "SAFETY_CONFLICT"
@@ -43065,6 +43102,11 @@ var AppleNotesManager = class {
43065
43102
  const deleteScopeFailure = parseScopeFailure(result.output);
43066
43103
  if (deleteScopeFailure) return { status: "scope_conflict", reason: deleteScopeFailure };
43067
43104
  const status = result.output.trim();
43105
+ const guardOutcome = /^SAFETY_GUARD_(CONFLICT|INACTIVE):(\d+)(?::(.+))?$/.exec(status);
43106
+ if (guardOutcome && Number(guardOutcome[2]) < guards.length) {
43107
+ const index = Number(guardOutcome[2]);
43108
+ return guardOutcome[1] === "CONFLICT" ? { status: "guard-conflict", index } : { status: "guard-inactive", index, reason: guardOutcome[3] ?? "inactive" };
43109
+ }
43068
43110
  if (status === "SAFETY_CONFLICT") return { status: "conflict" };
43069
43111
  if (status === "SAFETY_IN_RECENTLY_DELETED") return { status: "in-recently-deleted" };
43070
43112
  if (status === "SAFETY_CONTAINER_UNKNOWN") return { status: "container-unknown" };
@@ -45568,6 +45610,23 @@ function toSpecialRow(row, uuid2, paths, accountNames, kind) {
45568
45610
  if (kind === "locked" && row.hint) note.passwordHint = row.hint;
45569
45611
  return note;
45570
45612
  }
45613
+ var EXACT_NOTE_ID = /^x-coredata:\/\/([0-9A-F-]+)\/ICNote\/p(\d+)$/i;
45614
+ function quickNoteFlag(noteId3, dbPath2 = NOTES_DB_PATH7) {
45615
+ const match = EXACT_NOTE_ID.exec(noteId3);
45616
+ if (!match) throw new NoteStoreError(`Not an exact note id: ${noteId3}`, "invalid_input");
45617
+ const columns = readColumns(dbPath2);
45618
+ const sql = `SELECT json_object('uuid', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'quick', (SELECT ${flag(columns, "n", "ZISSYSTEMPAPER")} FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk AND n.Z_ENT = ${entity("ICNote")}));`;
45619
+ const [row] = parseJsonLines(
45620
+ runReadOnlySql(dbPath2, sql, { pk: { int: Number(match[2]) } })
45621
+ );
45622
+ if (row?.uuid?.toUpperCase() !== match[1].toUpperCase()) {
45623
+ throw new NoteStoreError(
45624
+ `Note id ${noteId3} belongs to a different Notes database.`,
45625
+ "invalid_input"
45626
+ );
45627
+ }
45628
+ return row.quick === null ? null : row.quick === 1;
45629
+ }
45571
45630
  var TAG_REQUIRED = ["Z_PK", "Z_ENT", "ZFOLDER", "ZNOTE1", "ZTYPEUTI1", "ZALTTEXT", "ZIDENTIFIER"];
45572
45631
  function buildTagInventorySql(columns, scoped) {
45573
45632
  requireColumns(columns, TAG_REQUIRED, "list-native-tags");
@@ -46600,13 +46659,13 @@ var escapeSegment = escapeFolderName;
46600
46659
  function resolveFolders(rows) {
46601
46660
  const byPk = new Map(rows.map((row) => [row.pk, row]));
46602
46661
  const resolved = /* @__PURE__ */ new Map();
46603
- const resolve5 = (pk, seen) => {
46662
+ const resolve6 = (pk, seen) => {
46604
46663
  const cached2 = resolved.get(pk);
46605
46664
  if (cached2) return cached2;
46606
46665
  const row = byPk.get(pk);
46607
46666
  if (!row || seen.has(pk)) return void 0;
46608
46667
  seen.add(pk);
46609
- const parent = row.parent !== null ? resolve5(row.parent, seen) : void 0;
46668
+ const parent = row.parent !== null ? resolve6(row.parent, seen) : void 0;
46610
46669
  const name = row.name ?? "";
46611
46670
  const path10 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
46612
46671
  const plainPath = parent ? `${parent.plainPath}/${name}` : name;
@@ -46620,7 +46679,7 @@ function resolveFolders(rows) {
46620
46679
  resolved.set(pk, info);
46621
46680
  return info;
46622
46681
  };
46623
- for (const row of rows) resolve5(row.pk, /* @__PURE__ */ new Set());
46682
+ for (const row of rows) resolve6(row.pk, /* @__PURE__ */ new Set());
46624
46683
  return resolved;
46625
46684
  }
46626
46685
  var coreDataMs = (seconds) => seconds === null || !Number.isFinite(seconds) ? void 0 : CORE_DATA_EPOCH_MS2 + seconds * 1e3;
@@ -47742,35 +47801,37 @@ var FEATURES = [
47742
47801
  { kind: "shortcut", name: markdownShortcutName }
47743
47802
  ]
47744
47803
  },
47745
- // Placeholders for features that need a native helper this server does not
47746
- // ship. They always report `not_implemented` so clients can detect them.
47804
+ // Placeholders for features that need a native WRITE helper, which this
47805
+ // server does not ship. The opt-in private helper is read-only (write
47806
+ // support was deliberately deferred), so enabling or installing it changes
47807
+ // nothing here: these always report `not_implemented`.
47747
47808
  {
47748
47809
  name: "checklistToggle",
47749
47810
  description: "Check or uncheck an existing checklist item in place",
47750
47811
  tools: [],
47751
47812
  minimumMacOSVersion: null,
47752
- requirements: [{ kind: "native_helper" }]
47813
+ requirements: [{ kind: "native_write_helper" }]
47753
47814
  },
47754
47815
  {
47755
47816
  name: "smartFolders",
47756
47817
  description: "Create or edit Smart Folders and their tag rules",
47757
47818
  tools: [],
47758
47819
  minimumMacOSVersion: null,
47759
- requirements: [{ kind: "native_helper" }]
47820
+ requirements: [{ kind: "native_write_helper" }]
47760
47821
  },
47761
47822
  {
47762
47823
  name: "paragraphLinks",
47763
47824
  description: "Link to a specific paragraph or heading inside a note",
47764
47825
  tools: [],
47765
47826
  minimumMacOSVersion: null,
47766
- requirements: [{ kind: "native_helper" }]
47827
+ requirements: [{ kind: "native_write_helper" }]
47767
47828
  },
47768
47829
  {
47769
47830
  name: "audioTranscription",
47770
47831
  description: "Transcribe audio recordings attached to a note",
47771
47832
  tools: [],
47772
47833
  minimumMacOSVersion: null,
47773
- requirements: [{ kind: "native_helper" }]
47834
+ requirements: [{ kind: "native_write_helper" }]
47774
47835
  }
47775
47836
  ];
47776
47837
  function requirementLabel(requirement) {
@@ -47818,7 +47879,7 @@ function evaluateFeature(feature, env) {
47818
47879
  failures.add("shortcut_not_installed");
47819
47880
  }
47820
47881
  break;
47821
- case "native_helper":
47882
+ case "native_write_helper":
47822
47883
  missing.push(label);
47823
47884
  failures.add("not_implemented");
47824
47885
  break;
@@ -51425,10 +51486,653 @@ function formatShortcutSetup(report) {
51425
51486
  return lines.join("\n");
51426
51487
  }
51427
51488
 
51489
+ // src/services/privateHelperBuild.ts
51490
+ import { spawnSync as spawnSync4 } from "node:child_process";
51491
+ import {
51492
+ chmodSync,
51493
+ existsSync as existsSync15,
51494
+ mkdirSync as mkdirSync6,
51495
+ mkdtempSync as mkdtempSync5,
51496
+ renameSync,
51497
+ rmSync as rmSync5,
51498
+ writeFileSync as writeFileSync4
51499
+ } from "node:fs";
51500
+ import { release as release3 } from "node:os";
51501
+ import { join as join25 } from "node:path";
51502
+
51503
+ // src/services/privateHelper.ts
51504
+ import { spawnSync as spawnSync3 } from "node:child_process";
51505
+ import { createHash as createHash4 } from "node:crypto";
51506
+ import { existsSync as existsSync14, readFileSync as readFileSync4 } from "node:fs";
51507
+ import { homedir as homedir19 } from "node:os";
51508
+ import { dirname as dirname6, join as join24, resolve as resolve5 } from "node:path";
51509
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
51510
+ var PRIVATE_HELPER_PROTOCOL = 1;
51511
+ var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
51512
+ var HELPER_DIR_ENV = "APPLE_NOTES_MCP_PRIVATE_HELPER_DIR";
51513
+ var TIMEOUT_ENV = "APPLE_NOTES_MCP_PRIVATE_HELPER_TIMEOUT_MS";
51514
+ var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set([
51515
+ "hello",
51516
+ "probe",
51517
+ "read_note_state"
51518
+ ]);
51519
+ var HELPER_BINARY_NAME = "apple-notes-private-helper";
51520
+ var HELPER_SOURCE_RELATIVE = "native/private-helper/apple-notes-private-helper.m";
51521
+ var MANIFEST_NAME = "manifest.json";
51522
+ var DEFAULT_TIMEOUT_MS2 = 2e4;
51523
+ var MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
51524
+ var manifestSchema = external_exports.object({
51525
+ schemaVersion: external_exports.literal(1),
51526
+ protocolVersion: external_exports.number().int(),
51527
+ sourceSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
51528
+ binarySha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
51529
+ builtAt: external_exports.string(),
51530
+ osVersion: external_exports.string(),
51531
+ compiler: external_exports.string()
51532
+ });
51533
+ function packageRoot(fromDir = dirname6(fileURLToPath2(import.meta.url))) {
51534
+ let dir = fromDir;
51535
+ for (; ; ) {
51536
+ const candidate = join24(dir, "package.json");
51537
+ if (existsSync14(candidate)) {
51538
+ try {
51539
+ const pkg = JSON.parse(readFileSync4(candidate, "utf8"));
51540
+ if (pkg.name === "apple-notes-mcp") return dir;
51541
+ } catch {
51542
+ }
51543
+ }
51544
+ const parent = dirname6(dir);
51545
+ if (parent === dir) return resolve5(fromDir, "..");
51546
+ dir = parent;
51547
+ }
51548
+ }
51549
+ function defaultDeps2(overrides = {}) {
51550
+ return {
51551
+ env: process.env,
51552
+ platform: process.platform,
51553
+ sourcePath: join24(packageRoot(), HELPER_SOURCE_RELATIVE),
51554
+ exists: existsSync14,
51555
+ readFile: (path10) => readFileSync4(path10),
51556
+ spawn: spawnSync3,
51557
+ ...overrides
51558
+ };
51559
+ }
51560
+ function privateHelperEnabled(env = process.env) {
51561
+ return env[ENABLE_ENV] === "1";
51562
+ }
51563
+ function helperInstallDir(env = process.env) {
51564
+ const override = env[HELPER_DIR_ENV]?.trim();
51565
+ if (override) return override;
51566
+ return join24(homedir19(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
51567
+ }
51568
+ function sha256Hex(data) {
51569
+ return createHash4("sha256").update(data).digest("hex");
51570
+ }
51571
+ function inspectInstallation(deps = defaultDeps2()) {
51572
+ const installDir = helperInstallDir(deps.env);
51573
+ const binaryPath = join24(installDir, HELPER_BINARY_NAME);
51574
+ const base = {
51575
+ installDir,
51576
+ binaryPath,
51577
+ sourcePath: deps.sourcePath,
51578
+ expectedSourceSha256: null,
51579
+ manifest: null
51580
+ };
51581
+ const fail = (reason, detail) => ({
51582
+ ...base,
51583
+ ready: false,
51584
+ reason,
51585
+ detail
51586
+ });
51587
+ if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
51588
+ if (!deps.exists(deps.sourcePath))
51589
+ return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
51590
+ base.expectedSourceSha256 = sha256Hex(deps.readFile(deps.sourcePath));
51591
+ const manifestPath = join24(installDir, MANIFEST_NAME);
51592
+ if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
51593
+ return fail(
51594
+ "helper_not_installed",
51595
+ "The private helper is not built. Run `apple-notes-mcp setup --native-helper`."
51596
+ );
51597
+ let manifest;
51598
+ try {
51599
+ manifest = manifestSchema.parse(JSON.parse(deps.readFile(manifestPath).toString("utf8")));
51600
+ } catch (error2) {
51601
+ return fail(
51602
+ "helper_manifest_invalid",
51603
+ `Unreadable helper manifest (${error2 instanceof Error ? error2.message : String(error2)}). Run \`apple-notes-mcp setup --native-helper\`.`
51604
+ );
51605
+ }
51606
+ base.manifest = manifest;
51607
+ if (manifest.sourceSha256 !== base.expectedSourceSha256 || manifest.protocolVersion !== PRIVATE_HELPER_PROTOCOL)
51608
+ return fail(
51609
+ "helper_stale",
51610
+ "The installed helper was built from a different helper source or protocol than this apple-notes-mcp version ships. Run `apple-notes-mcp setup --native-helper` again."
51611
+ );
51612
+ if (sha256Hex(deps.readFile(binaryPath)) !== manifest.binarySha256)
51613
+ return fail(
51614
+ "helper_modified",
51615
+ "The helper binary does not match the checksum recorded when it was built. Run `apple-notes-mcp setup --native-helper` to rebuild it."
51616
+ );
51617
+ return { ...base, ready: true, reason: null, detail: null };
51618
+ }
51619
+ var PrivateHelperError = class extends Error {
51620
+ constructor(code, message, details = {}) {
51621
+ super(message);
51622
+ this.code = code;
51623
+ this.details = details;
51624
+ this.name = "PrivateHelperError";
51625
+ }
51626
+ code;
51627
+ details;
51628
+ };
51629
+ var errorSchema = external_exports.object({
51630
+ status: external_exports.literal("error"),
51631
+ code: external_exports.string(),
51632
+ message: external_exports.string()
51633
+ }).passthrough();
51634
+ var featureSchema = external_exports.object({
51635
+ available: external_exports.boolean(),
51636
+ reason: external_exports.string().nullable(),
51637
+ missing: external_exports.array(external_exports.string())
51638
+ });
51639
+ var helloSchema = external_exports.object({
51640
+ status: external_exports.literal("ok"),
51641
+ protocolVersion: external_exports.number().int(),
51642
+ sourceSha256: external_exports.string(),
51643
+ readOnly: external_exports.literal(true),
51644
+ actions: external_exports.array(external_exports.string())
51645
+ }).passthrough();
51646
+ var probeSchema = external_exports.object({
51647
+ status: external_exports.literal("ok"),
51648
+ protocolVersion: external_exports.number().int(),
51649
+ readOnly: external_exports.literal(true),
51650
+ os: external_exports.object({ version: external_exports.string(), notesAppVersion: external_exports.string().nullable() }).passthrough(),
51651
+ framework: external_exports.object({ loaded: external_exports.boolean(), error: external_exports.string().nullable() }).passthrough(),
51652
+ store: external_exports.object({
51653
+ kind: external_exports.enum(["live", "copy"]).nullable(),
51654
+ opened: external_exports.boolean(),
51655
+ reason: external_exports.string().nullable(),
51656
+ noteRows: external_exports.number().int().nullable()
51657
+ }).passthrough(),
51658
+ syncHostRunning: external_exports.boolean(),
51659
+ features: external_exports.object({ readNoteState: featureSchema }).passthrough()
51660
+ }).passthrough();
51661
+ var cloudSyncSchema = external_exports.object({
51662
+ available: external_exports.boolean(),
51663
+ inICloudAccount: external_exports.boolean(),
51664
+ currentLocalVersion: external_exports.number().int().optional(),
51665
+ latestVersionSyncedToCloud: external_exports.number().int().optional(),
51666
+ uploadPending: external_exports.boolean().optional()
51667
+ }).passthrough();
51668
+ var noteStateSchema = external_exports.object({
51669
+ status: external_exports.literal("ok"),
51670
+ identifier: external_exports.string(),
51671
+ objectURI: external_exports.string(),
51672
+ title: external_exports.string().nullable(),
51673
+ modificationDate: external_exports.string().nullable(),
51674
+ folderIdentifier: external_exports.string().nullable(),
51675
+ passwordProtected: external_exports.boolean(),
51676
+ deletedOrInTrash: external_exports.boolean(),
51677
+ sharedViaICloud: external_exports.boolean(),
51678
+ editable: external_exports.boolean(),
51679
+ revision: external_exports.string().regex(/^r1:[a-f0-9]{64}$/),
51680
+ cloudSync: cloudSyncSchema,
51681
+ syncHostRunning: external_exports.boolean()
51682
+ }).passthrough();
51683
+ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options = {}) {
51684
+ if (!READ_ONLY_ACTIONS.has(action))
51685
+ throw new PrivateHelperError(
51686
+ "unknown_action",
51687
+ `The private helper is read-only; "${action}" is not a supported action.`
51688
+ );
51689
+ if (!options.allowDisabled && !privateHelperEnabled(deps.env))
51690
+ throw new PrivateHelperError(
51691
+ "disabled",
51692
+ `The private helper is off. Set ${ENABLE_ENV}=1 to opt in.`
51693
+ );
51694
+ let binaryPath = options.binaryPath;
51695
+ if (!binaryPath) {
51696
+ const install = inspectInstallation(deps);
51697
+ if (!install.ready)
51698
+ throw new PrivateHelperError(install.reason || "helper_not_installed", install.detail || "");
51699
+ binaryPath = install.binaryPath;
51700
+ }
51701
+ const timeout = Number.parseInt(deps.env[TIMEOUT_ENV] || "", 10) || DEFAULT_TIMEOUT_MS2;
51702
+ const result = deps.spawn(binaryPath, [], {
51703
+ input: JSON.stringify({ protocol: PRIVATE_HELPER_PROTOCOL, action, ...fields }),
51704
+ encoding: "utf8",
51705
+ timeout,
51706
+ killSignal: "SIGKILL",
51707
+ maxBuffer: MAX_OUTPUT_BYTES,
51708
+ env: deps.env
51709
+ });
51710
+ const errno = result.error?.code;
51711
+ if (errno === "ETIMEDOUT" || result.signal && result.status === null)
51712
+ throw new PrivateHelperError("timeout", `The helper did not answer within ${timeout} ms.`);
51713
+ if (result.error)
51714
+ throw new PrivateHelperError(
51715
+ "helper_unreachable",
51716
+ `Could not run the helper: ${result.error.message}`
51717
+ );
51718
+ const stdout = String(result.stdout ?? "").trim();
51719
+ let parsed;
51720
+ try {
51721
+ parsed = JSON.parse(stdout);
51722
+ } catch {
51723
+ throw new PrivateHelperError(
51724
+ "invalid_response",
51725
+ `The helper exited with status ${result.status} and no JSON response.`
51726
+ );
51727
+ }
51728
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
51729
+ throw new PrivateHelperError("invalid_response", "The helper response is not a JSON object");
51730
+ const object3 = parsed;
51731
+ if (result.status !== 0 || object3.status === "error") {
51732
+ const error2 = errorSchema.safeParse(object3);
51733
+ if (!error2.success)
51734
+ throw new PrivateHelperError(
51735
+ "invalid_response",
51736
+ `The helper failed with an unrecognized error shape (exit ${result.status})`
51737
+ );
51738
+ const { status: _status, code, message, ...details } = error2.data;
51739
+ void _status;
51740
+ throw new PrivateHelperError(code, message, details);
51741
+ }
51742
+ return object3;
51743
+ }
51744
+ function parseOrThrow(schema, value) {
51745
+ const parsed = schema.safeParse(value);
51746
+ if (!parsed.success)
51747
+ throw new PrivateHelperError(
51748
+ "invalid_response",
51749
+ `Unexpected helper response: ${parsed.error.issues.map((i) => i.path.join(".") + " " + i.message).join("; ")}`
51750
+ );
51751
+ return parsed.data;
51752
+ }
51753
+ var UUID = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
51754
+ function assertNoteIdentifier(identifier) {
51755
+ if (!UUID.test(identifier))
51756
+ throw new PrivateHelperError("invalid_request", "identifier must be a Notes UUID");
51757
+ }
51758
+ function probePrivateHelper(deps = defaultDeps2()) {
51759
+ return parseOrThrow(probeSchema, callPrivateHelper("probe", {}, deps));
51760
+ }
51761
+ function readNoteState(identifier, deps = defaultDeps2()) {
51762
+ assertNoteIdentifier(identifier);
51763
+ return parseOrThrow(noteStateSchema, callPrivateHelper("read_note_state", { identifier }, deps));
51764
+ }
51765
+ function featureFromProbe(feature) {
51766
+ if (!feature) return { available: false, reason: "private_api_unavailable", detail: null };
51767
+ if (feature.available) return { available: true, reason: null, detail: null };
51768
+ const reason = feature.reason === "store_unavailable" || feature.reason === "disabled" ? feature.reason : "private_api_unavailable";
51769
+ return {
51770
+ available: false,
51771
+ reason,
51772
+ detail: feature.missing.length ? `missing: ${feature.missing.join(", ")}` : feature.reason
51773
+ };
51774
+ }
51775
+ function privateHelperCapabilities(deps = defaultDeps2()) {
51776
+ const enabled = privateHelperEnabled(deps.env);
51777
+ const installation = inspectInstallation(deps);
51778
+ const off = (reason, detail) => ({
51779
+ available: false,
51780
+ reason,
51781
+ detail
51782
+ });
51783
+ const both = (status) => ({ readNoteState: status });
51784
+ if (installation.reason === "unsupported_platform")
51785
+ return {
51786
+ enabled,
51787
+ readOnly: true,
51788
+ installation,
51789
+ probe: null,
51790
+ features: both(off("unsupported_platform", null))
51791
+ };
51792
+ if (!enabled)
51793
+ return {
51794
+ enabled,
51795
+ readOnly: true,
51796
+ installation,
51797
+ probe: null,
51798
+ features: both(off("disabled", `Set ${ENABLE_ENV}=1 to opt in to the private helper.`))
51799
+ };
51800
+ if (!installation.ready)
51801
+ return {
51802
+ enabled,
51803
+ readOnly: true,
51804
+ installation,
51805
+ probe: null,
51806
+ features: both(off(installation.reason || "helper_not_installed", installation.detail))
51807
+ };
51808
+ let probe;
51809
+ try {
51810
+ probe = probePrivateHelper(deps);
51811
+ } catch (error2) {
51812
+ const detail = error2 instanceof Error ? error2.message : String(error2);
51813
+ return {
51814
+ enabled,
51815
+ readOnly: true,
51816
+ installation,
51817
+ probe: null,
51818
+ features: both(off("helper_unreachable", detail))
51819
+ };
51820
+ }
51821
+ return {
51822
+ enabled,
51823
+ readOnly: true,
51824
+ installation,
51825
+ probe,
51826
+ features: { readNoteState: featureFromProbe(probe.features.readNoteState) }
51827
+ };
51828
+ }
51829
+
51830
+ // src/services/privateHelperBuild.ts
51831
+ function defaultBuildDeps() {
51832
+ return {
51833
+ ...defaultDeps2(),
51834
+ osVersion: () => {
51835
+ const r = spawnSync4("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
51836
+ return r.status === 0 ? r.stdout.trim() : `Darwin ${release3()}`;
51837
+ },
51838
+ now: () => /* @__PURE__ */ new Date()
51839
+ };
51840
+ }
51841
+ function compileArguments(sourcePath, outputPath, sourceSha) {
51842
+ return [
51843
+ "clang",
51844
+ "-fobjc-arc",
51845
+ "-O2",
51846
+ "-Wall",
51847
+ "-framework",
51848
+ "Foundation",
51849
+ "-framework",
51850
+ "CoreData",
51851
+ "-framework",
51852
+ "AppKit",
51853
+ // Embedded so `hello` can prove which source the binary came from.
51854
+ `-DHELPER_SOURCE_SHA256="${sourceSha}"`,
51855
+ "-o",
51856
+ outputPath,
51857
+ sourcePath
51858
+ ];
51859
+ }
51860
+ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
51861
+ const steps = [];
51862
+ const done = (ok) => ({
51863
+ ok,
51864
+ checkOnly,
51865
+ steps,
51866
+ installation: inspectInstallation(deps)
51867
+ });
51868
+ if (checkOnly) {
51869
+ const installation2 = inspectInstallation(deps);
51870
+ steps.push({
51871
+ step: "inspect installed helper",
51872
+ ok: installation2.ready,
51873
+ detail: installation2.ready ? installation2.binaryPath : installation2.detail || void 0
51874
+ });
51875
+ return { ok: installation2.ready, checkOnly, steps, installation: installation2 };
51876
+ }
51877
+ if (deps.platform !== "darwin") {
51878
+ steps.push({ step: "platform", ok: false, detail: "macOS only" });
51879
+ return done(false);
51880
+ }
51881
+ if (!deps.exists(deps.sourcePath)) {
51882
+ steps.push({ step: "locate source", ok: false, detail: deps.sourcePath });
51883
+ return done(false);
51884
+ }
51885
+ const source = deps.readFile(deps.sourcePath);
51886
+ const sourceSha = sha256Hex(source);
51887
+ steps.push({
51888
+ step: "locate source",
51889
+ ok: true,
51890
+ detail: `${deps.sourcePath} (sha256 ${sourceSha})`
51891
+ });
51892
+ const clang = deps.spawn("/usr/bin/xcrun", ["--find", "clang"], { encoding: "utf8" });
51893
+ if (clang.status !== 0) {
51894
+ steps.push({
51895
+ step: "find compiler",
51896
+ ok: false,
51897
+ detail: "No clang found. Install the Command Line Tools with `xcode-select --install`."
51898
+ });
51899
+ return done(false);
51900
+ }
51901
+ const clangVersion = deps.spawn("/usr/bin/xcrun", ["clang", "--version"], { encoding: "utf8" });
51902
+ const compiler = String(clangVersion.stdout || "").split("\n")[0] || "clang";
51903
+ steps.push({ step: "find compiler", ok: true, detail: compiler });
51904
+ const installDir = helperInstallDir(deps.env);
51905
+ mkdirSync6(installDir, { recursive: true, mode: 448 });
51906
+ const staging = mkdtempSync5(join25(installDir, ".staging-"));
51907
+ try {
51908
+ const stagedBinary = join25(staging, HELPER_BINARY_NAME);
51909
+ const compile = deps.spawn(
51910
+ "/usr/bin/xcrun",
51911
+ compileArguments(deps.sourcePath, stagedBinary, sourceSha),
51912
+ {
51913
+ encoding: "utf8",
51914
+ timeout: 18e4
51915
+ }
51916
+ );
51917
+ if (compile.status !== 0) {
51918
+ steps.push({
51919
+ step: "compile",
51920
+ ok: false,
51921
+ detail: String(compile.stderr || compile.error?.message || "clang failed").slice(0, 4e3)
51922
+ });
51923
+ return done(false);
51924
+ }
51925
+ steps.push({ step: "compile", ok: true });
51926
+ const sign = deps.spawn(
51927
+ "/usr/bin/codesign",
51928
+ ["--force", "--sign", "-", "--identifier", "apple-notes-mcp.private-helper", stagedBinary],
51929
+ { encoding: "utf8" }
51930
+ );
51931
+ if (sign.status !== 0) {
51932
+ steps.push({
51933
+ step: "ad-hoc sign",
51934
+ ok: false,
51935
+ detail: String(sign.stderr || "codesign failed")
51936
+ });
51937
+ return done(false);
51938
+ }
51939
+ steps.push({ step: "ad-hoc sign", ok: true });
51940
+ let hello;
51941
+ try {
51942
+ hello = helloSchema.parse(
51943
+ callPrivateHelper("hello", {}, deps, { allowDisabled: true, binaryPath: stagedBinary })
51944
+ );
51945
+ } catch (error2) {
51946
+ steps.push({
51947
+ step: "handshake",
51948
+ ok: false,
51949
+ detail: error2 instanceof Error ? error2.message : String(error2)
51950
+ });
51951
+ return done(false);
51952
+ }
51953
+ if (hello.protocolVersion !== PRIVATE_HELPER_PROTOCOL || hello.sourceSha256 !== sourceSha) {
51954
+ steps.push({
51955
+ step: "handshake",
51956
+ ok: false,
51957
+ detail: `helper reported protocol ${hello.protocolVersion}, source ${hello.sourceSha256}`
51958
+ });
51959
+ return done(false);
51960
+ }
51961
+ const writeActions = hello.actions.filter((action) => !READ_ONLY_ACTIONS.has(action));
51962
+ if (writeActions.length) {
51963
+ steps.push({
51964
+ step: "handshake",
51965
+ ok: false,
51966
+ detail: `helper offers non-read-only actions (${writeActions.join(", ")}); refusing to install`
51967
+ });
51968
+ return done(false);
51969
+ }
51970
+ steps.push({
51971
+ step: "handshake",
51972
+ ok: true,
51973
+ detail: `protocol ${hello.protocolVersion}, read-only`
51974
+ });
51975
+ const manifest = {
51976
+ schemaVersion: 1,
51977
+ protocolVersion: hello.protocolVersion,
51978
+ sourceSha256: sourceSha,
51979
+ binarySha256: sha256Hex(deps.readFile(stagedBinary)),
51980
+ builtAt: deps.now().toISOString(),
51981
+ osVersion: deps.osVersion(),
51982
+ compiler
51983
+ };
51984
+ chmodSync(stagedBinary, 448);
51985
+ renameSync(stagedBinary, join25(installDir, HELPER_BINARY_NAME));
51986
+ writeFileSync4(join25(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
51987
+ mode: 384
51988
+ });
51989
+ steps.push({ step: "install", ok: true, detail: installDir });
51990
+ } finally {
51991
+ if (existsSync15(staging)) rmSync5(staging, { recursive: true, force: true });
51992
+ }
51993
+ const installation = inspectInstallation(deps);
51994
+ steps.push({
51995
+ step: "verify installation",
51996
+ ok: installation.ready,
51997
+ detail: installation.ready ? void 0 : installation.detail || void 0
51998
+ });
51999
+ return { ok: installation.ready, checkOnly, steps, installation };
52000
+ }
52001
+ function formatHelperBuild(report) {
52002
+ const lines = ["Apple Notes MCP private helper", ""];
52003
+ for (const step of report.steps)
52004
+ lines.push(`${step.ok ? "\u2713" : "\u2717"} ${step.step}${step.detail ? `: ${step.detail}` : ""}`);
52005
+ lines.push("");
52006
+ if (report.ok) {
52007
+ lines.push(`Installed at ${report.installation.binaryPath}.`);
52008
+ lines.push(
52009
+ "The helper stays off until you set APPLE_NOTES_MCP_ENABLE_PRIVATE=1 for the MCP server, and it needs the same Full Disk Access grant as the server's database reads."
52010
+ );
52011
+ } else if (report.checkOnly) {
52012
+ lines.push("Run `apple-notes-mcp setup --native-helper` to build it.");
52013
+ } else {
52014
+ lines.push("The helper was not installed. Fix the failed step above and run setup again.");
52015
+ }
52016
+ return lines.join("\n");
52017
+ }
52018
+
52019
+ // src/tools/privateHelperTools.ts
52020
+ var coreDataId2 = external_exports.string().regex(/^x-coredata:\/\/[0-9A-F-]+\/ICNote\/p\d+$/i);
52021
+ var notesUuid = external_exports.string().regex(UUID_PATTERN);
52022
+ function resolveIdentifier(manager, args) {
52023
+ if (args.identifier && args.id)
52024
+ throw new PrivateHelperError("invalid_request", "Pass identifier or id, not both");
52025
+ if (args.identifier) return args.identifier;
52026
+ if (!args.id) throw new PrivateHelperError("invalid_request", "identifier or id is required");
52027
+ const link = manager.getNoteLinkById(args.id);
52028
+ const match = link?.match(/identifier=([0-9A-F-]{36})$/i);
52029
+ if (!match)
52030
+ throw new PrivateHelperError(
52031
+ "not_found",
52032
+ "Could not resolve that id to a Notes UUID (needs Full Disk Access); pass identifier instead"
52033
+ );
52034
+ return match[1];
52035
+ }
52036
+ function envelopeCode(helperCode, message) {
52037
+ switch (helperCode) {
52038
+ case "not_found":
52039
+ return "not_found";
52040
+ case "timeout":
52041
+ return "timeout_indeterminate";
52042
+ case "invalid_request":
52043
+ case "invalid_json":
52044
+ case "input_too_large":
52045
+ case "unknown_action":
52046
+ return "validation_error";
52047
+ case "store_unavailable":
52048
+ return /Full Disk Access/i.test(message) ? "full_disk_access_missing" : "operation_failed";
52049
+ case "disabled":
52050
+ case "unsupported_platform":
52051
+ case "unsupported_note":
52052
+ case "private_api_unavailable":
52053
+ case "protocol_mismatch":
52054
+ case "helper_not_installed":
52055
+ case "helper_stale":
52056
+ case "helper_modified":
52057
+ case "helper_manifest_invalid":
52058
+ return "unsupported";
52059
+ default:
52060
+ return "operation_failed";
52061
+ }
52062
+ }
52063
+ function helperErrorResult(error2) {
52064
+ if (!(error2 instanceof PrivateHelperError)) {
52065
+ const message2 = error2 instanceof Error ? error2.message : String(error2);
52066
+ return errorResult(`native helper: ${message2}`, error2);
52067
+ }
52068
+ const message = `native helper (${error2.code}): ${error2.message}`;
52069
+ return errorResult(
52070
+ message,
52071
+ new CodedError(message, {
52072
+ ...error2.details,
52073
+ code: envelopeCode(error2.code, error2.message),
52074
+ helperCode: error2.code,
52075
+ committed: false
52076
+ })
52077
+ );
52078
+ }
52079
+ function registerPrivateHelperTools(server2, manager, depsFactory = () => defaultDeps2()) {
52080
+ function tool(name, description, inputSchema, annotations, handler) {
52081
+ server2.registerTool(
52082
+ name,
52083
+ {
52084
+ description,
52085
+ inputSchema,
52086
+ annotations,
52087
+ outputSchema: external_exports.object({ ok: external_exports.boolean().optional() }).passthrough()
52088
+ },
52089
+ (async (args) => {
52090
+ try {
52091
+ const result = { ok: true, ...handler(args, depsFactory()) };
52092
+ return {
52093
+ content: [{ type: "text", text: JSON.stringify(result) }],
52094
+ structuredContent: result
52095
+ };
52096
+ } catch (error2) {
52097
+ return helperErrorResult(error2);
52098
+ }
52099
+ })
52100
+ );
52101
+ }
52102
+ tool(
52103
+ "native-helper-status",
52104
+ "Use when: checking whether the opt-in, read-only native private helper is enabled, built, current, and working on this macOS before calling native-note-state.\nReturns: enabled flag, installation state (path, manifest, stale/modified checks), the live probe (macOS and Notes versions, framework, store access), and per-feature availability with a machine reason.\nDo not use when: checking the Shortcuts bridges (native-tags-status, get-capabilities).\nSafety: read-only. The helper is read-only by design (write support was deliberately deferred by the maintainer); the probe opens the Notes store read-only and only when the helper is enabled and installed.",
52105
+ {},
52106
+ { readOnlyHint: true, openWorldHint: false },
52107
+ (_args, deps) => {
52108
+ const capabilities = privateHelperCapabilities(deps);
52109
+ return {
52110
+ ...capabilities,
52111
+ ...capabilities.installation.ready ? {} : { setupCommand: "apple-notes-mcp setup --native-helper" }
52112
+ };
52113
+ }
52114
+ );
52115
+ tool(
52116
+ "native-note-state",
52117
+ "Use when: you need a note's native title, modification date, folder identifier, lock/trash/shared/editable flags, or iCloud upload state as Notes' own data model reports them.\nReturns: identifier, title, modificationDate, folderIdentifier, lock/trash/shared/editable flags, `revision` (an opaque change token; compare two reads to detect a change), and cloudSync versions.\nDo not use when: reading note content (get-note-content, get-note-markdown).\nSafety: read-only; the helper opens the store with Core Data's read-only option and has no write action. Requires APPLE_NOTES_MCP_ENABLE_PRIVATE=1 and a built helper.",
52118
+ {
52119
+ identifier: notesUuid.optional().describe("Notes UUID (the notes://showNote identifier)"),
52120
+ id: coreDataId2.optional().describe("x-coredata note id; resolved to a UUID via the database")
52121
+ },
52122
+ { readOnlyHint: true, openWorldHint: false },
52123
+ (args, deps) => ({ ...readNoteState(resolveIdentifier(manager, args), deps) })
52124
+ );
52125
+ }
52126
+
51428
52127
  // src/index.ts
51429
52128
  loadFileConfig();
51430
52129
  var require2 = createRequire(import.meta.url);
51431
52130
  var { version: version2 } = require2("../package.json");
52131
+ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--native-helper")) {
52132
+ const report = buildPrivateHelper(process.argv.slice(3).includes("--check"));
52133
+ process.stdout.write(formatHelperBuild(report) + "\n");
52134
+ process.exit(report.ok ? 0 : 1);
52135
+ }
51432
52136
  if (process.argv[2] === "setup") {
51433
52137
  const report = setupShortcuts(process.argv.slice(3).includes("--check"));
51434
52138
  process.stdout.write(formatShortcutSetup(report) + "\n");
@@ -51444,6 +52148,7 @@ registerDirectOperations(server, notesManager);
51444
52148
  registerFolderDelete(server, notesManager);
51445
52149
  registerNativeTagsBridge(server, notesManager);
51446
52150
  registerNativeOperations(server, notesManager);
52151
+ registerPrivateHelperTools(server, notesManager);
51447
52152
  function successResponse(message, structured) {
51448
52153
  const res = { content: [{ type: "text", text: message }] };
51449
52154
  if (structured) res.structuredContent = structured;
@@ -51564,6 +52269,59 @@ function inRecentlyDeletedMessage(title) {
51564
52269
  function revisionConflictMessage(title) {
51565
52270
  return `Note "${title}" changed after it was read. Read it again and review the newer version before retrying.`;
51566
52271
  }
52272
+ function quickNoteGuardRefusal(label, id2) {
52273
+ let quick;
52274
+ try {
52275
+ quick = quickNoteFlag(id2);
52276
+ } catch (error2) {
52277
+ const detail = error2 instanceof Error ? error2.message : String(error2);
52278
+ if (error2 instanceof NoteStoreError && error2.kind === "no_fda") {
52279
+ return `${label} note cannot be checked: the delete-note guard needs Full Disk Access to rule out a Quick Note. Nothing was deleted. ${detail}`;
52280
+ }
52281
+ return `${label} note could not be checked (${detail}). Nothing was deleted.`;
52282
+ }
52283
+ return quick ? `${label} note is a Quick Note; a guard must be an ordinary note. Nothing was deleted.` : null;
52284
+ }
52285
+ function prepareDeleteGuards(args) {
52286
+ const { id: id2, guardNoteId, expectedGuardContentHash, requireActiveNoteId } = args;
52287
+ if (guardNoteId === void 0 !== (expectedGuardContentHash === void 0)) {
52288
+ return { error: "Pass guardNoteId and expectedGuardContentHash together." };
52289
+ }
52290
+ if (guardNoteId === id2 || requireActiveNoteId === id2) {
52291
+ return { error: "A guard note must be a different note from the one being deleted." };
52292
+ }
52293
+ if (guardNoteId !== void 0 && guardNoteId === requireActiveNoteId) {
52294
+ return { error: "requireActiveNoteId repeats guardNoteId; pass only guardNoteId." };
52295
+ }
52296
+ const prepared = { guards: [], labels: [] };
52297
+ if (guardNoteId !== void 0) {
52298
+ const refusal = quickNoteGuardRefusal("Guard", guardNoteId);
52299
+ if (refusal) return { error: refusal };
52300
+ const guard = readExactNoteSnapshot(guardNoteId);
52301
+ if ("error" in guard) return { error: `Guard note: ${guard.error}` };
52302
+ if (guard.contentHash !== expectedGuardContentHash) {
52303
+ return {
52304
+ error: `Guard note "${guard.note.title}" changed after it was read. Verify the copy again before retiring the original. Nothing was deleted.`
52305
+ };
52306
+ }
52307
+ prepared.guards.push({ id: guardNoteId, expectedBody: guard.body });
52308
+ prepared.labels.push("Guard");
52309
+ prepared.guardContentHash = guard.contentHash;
52310
+ }
52311
+ if (requireActiveNoteId !== void 0) {
52312
+ const refusal = quickNoteGuardRefusal("Required active", requireActiveNoteId);
52313
+ if (refusal) return { error: refusal };
52314
+ const active = notesManager.getNoteById(requireActiveNoteId);
52315
+ if (!active)
52316
+ return { error: `Required active note with ID "${requireActiveNoteId}" not found.` };
52317
+ if (active.passwordProtected) {
52318
+ return { error: `Required active note "${active.title}" is password-protected.` };
52319
+ }
52320
+ prepared.guards.push({ id: requireActiveNoteId });
52321
+ prepared.labels.push("Required active");
52322
+ }
52323
+ return prepared;
52324
+ }
51567
52325
  var folderNameSchema = {
51568
52326
  name: external_exports.string().min(1, "Folder name is required").max(MAX.FOLDER),
51569
52327
  account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
@@ -52799,10 +53557,17 @@ registerTool(
52799
53557
  registerTool(
52800
53558
  "delete-note",
52801
53559
  {
52802
- description: "Use when: moving one exact note to Recently Deleted after reading and reviewing it.\nReturns: confirmation with the exact id.\nDo not use when: you only have a title or the note changed since review.\nSafety: requires id and expectedContentHash from get-note-content. The body comparison and delete happen in one AppleScript, so a newer edit is preserved. Refuses a note already in Recently Deleted, where a delete would be permanent. Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds are re-checked inside that AppleScript too.",
53560
+ description: "Use when: moving one exact note to Recently Deleted after reading and reviewing it.\nReturns: confirmation with the exact id.\nDo not use when: you only have a title or the note changed since review.\nSafety: requires id and expectedContentHash from get-note-content. The body comparison and delete happen in one AppleScript, so a newer edit is preserved. Refuses a note already in Recently Deleted, where a delete would be permanent. Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds are re-checked inside that AppleScript too. Copy-then-retire: pass guardNoteId and expectedGuardContentHash (the verified copy's contentHash) to delete only while the copy still has that revision, is unlocked, is outside Recently Deleted, and is not a Quick Note; requireActiveNoteId requires the same of a second note without fingerprinting it. The guard needs Full Disk Access to rule out a Quick Note. The copy's body, lock state, and folder are checked again inside the delete AppleScript, but the pair is not one transaction.",
52803
53561
  inputSchema: {
52804
53562
  id: noteIdInput,
52805
53563
  expectedContentHash: expectedContentHashInput,
53564
+ guardNoteId: noteIdInput.optional().describe(
53565
+ "A second note (usually the verified copy) that must still match expectedGuardContentHash, be unlocked, stay outside Recently Deleted, and not be a Quick Note. Needs Full Disk Access"
53566
+ ),
53567
+ expectedGuardContentHash: expectedContentHashInput.optional().describe("get-note-content contentHash of guardNoteId; required with guardNoteId"),
53568
+ requireActiveNoteId: noteIdInput.optional().describe(
53569
+ "A second note that must still exist, be unlocked, stay outside Recently Deleted, and not be a Quick Note. Its content is not fingerprinted. Needs Full Disk Access"
53570
+ ),
52806
53571
  timeoutSeconds: timeoutSecondsInput,
52807
53572
  ...scopeGuardInputs
52808
53573
  },
@@ -52811,48 +53576,85 @@ registerTool(
52811
53576
  id: external_exports.string().optional(),
52812
53577
  title: external_exports.string().optional(),
52813
53578
  wasShared: external_exports.boolean().optional(),
52814
- previousContentHash: external_exports.string().optional()
53579
+ previousContentHash: external_exports.string().optional(),
53580
+ guardNoteId: external_exports.string().optional(),
53581
+ guardContentHash: external_exports.string().optional(),
53582
+ requireActiveNoteId: external_exports.string().optional()
52815
53583
  }
52816
53584
  },
52817
- withErrorHandling(({ id: id2, expectedContentHash, ...scopeArgs }) => {
52818
- const snapshot = readExactNoteSnapshot(id2);
52819
- if ("error" in snapshot) return errorResponse(snapshot.error);
52820
- if (snapshot.contentHash !== expectedContentHash) {
52821
- return errorResponse(revisionConflictMessage(snapshot.note.title));
52822
- }
52823
- const result = notesManager.deleteNoteByIdIfUnchanged(id2, snapshot.body, scopeFrom(scopeArgs));
52824
- if (result.status === "conflict") {
52825
- return errorResponse(revisionConflictMessage(snapshot.note.title));
52826
- }
52827
- if (result.status === "scope_conflict") {
52828
- return errorResponse(scopeConflictMessage(result.reason));
52829
- }
52830
- if (result.status === "in-recently-deleted") {
52831
- return errorResponse(inRecentlyDeletedMessage(snapshot.note.title));
52832
- }
52833
- if (result.status === "container-unknown") {
52834
- return errorResponse(containerUnknownMessage(snapshot.note.title));
52835
- }
52836
- if (result.status === "not-deleted") {
52837
- return errorResponse(NOT_DELETED_MESSAGE);
52838
- }
52839
- if (result.status !== "deleted") {
52840
- return errorResponse(
52841
- `The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
52842
- );
52843
- }
52844
- const sharedWarning = snapshot.note.shared ? "\n\n\u26A0\uFE0F This note was shared with collaborators. They will no longer have access." : "";
52845
- return successResponse(
52846
- `Note moved to Recently Deleted: "${snapshot.note.title}"${sharedWarning}`,
52847
- {
52848
- ok: true,
53585
+ withErrorHandling(
53586
+ ({
53587
+ id: id2,
53588
+ expectedContentHash,
53589
+ guardNoteId,
53590
+ expectedGuardContentHash,
53591
+ requireActiveNoteId,
53592
+ ...scopeArgs
53593
+ }) => {
53594
+ const prepared = prepareDeleteGuards({
52849
53595
  id: id2,
52850
- title: snapshot.note.title,
52851
- wasShared: snapshot.note.shared ?? false,
52852
- previousContentHash: expectedContentHash
53596
+ guardNoteId,
53597
+ expectedGuardContentHash,
53598
+ requireActiveNoteId
53599
+ });
53600
+ if ("error" in prepared) return errorResponse(prepared.error);
53601
+ const snapshot = readExactNoteSnapshot(id2);
53602
+ if ("error" in snapshot) return errorResponse(snapshot.error);
53603
+ if (snapshot.contentHash !== expectedContentHash) {
53604
+ return errorResponse(revisionConflictMessage(snapshot.note.title));
52853
53605
  }
52854
- );
52855
- }, "Error deleting note")
53606
+ const result = notesManager.deleteNoteByIdIfUnchanged(
53607
+ id2,
53608
+ snapshot.body,
53609
+ scopeFrom(scopeArgs),
53610
+ prepared.guards
53611
+ );
53612
+ if (result.status === "guard-conflict") {
53613
+ return errorResponse(
53614
+ `${prepared.labels[result.index]} note changed just before the delete. Nothing was deleted; verify it again before retrying.`
53615
+ );
53616
+ }
53617
+ if (result.status === "guard-inactive") {
53618
+ return errorResponse(
53619
+ `${prepared.labels[result.index]} note is no longer active (${result.reason}). Nothing was deleted.`
53620
+ );
53621
+ }
53622
+ if (result.status === "conflict") {
53623
+ return errorResponse(revisionConflictMessage(snapshot.note.title));
53624
+ }
53625
+ if (result.status === "scope_conflict") {
53626
+ return errorResponse(scopeConflictMessage(result.reason));
53627
+ }
53628
+ if (result.status === "in-recently-deleted") {
53629
+ return errorResponse(inRecentlyDeletedMessage(snapshot.note.title));
53630
+ }
53631
+ if (result.status === "container-unknown") {
53632
+ return errorResponse(containerUnknownMessage(snapshot.note.title));
53633
+ }
53634
+ if (result.status === "not-deleted") {
53635
+ return errorResponse(NOT_DELETED_MESSAGE);
53636
+ }
53637
+ if (result.status !== "deleted") {
53638
+ return errorResponse(
53639
+ `The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
53640
+ );
53641
+ }
53642
+ const sharedWarning = snapshot.note.shared ? "\n\n\u26A0\uFE0F This note was shared with collaborators. They will no longer have access." : "";
53643
+ return successResponse(
53644
+ `Note moved to Recently Deleted: "${snapshot.note.title}"${sharedWarning}`,
53645
+ {
53646
+ ok: true,
53647
+ id: id2,
53648
+ title: snapshot.note.title,
53649
+ wasShared: snapshot.note.shared ?? false,
53650
+ previousContentHash: expectedContentHash,
53651
+ ...guardNoteId ? { guardNoteId, guardContentHash: prepared.guardContentHash } : {},
53652
+ ...requireActiveNoteId ? { requireActiveNoteId } : {}
53653
+ }
53654
+ );
53655
+ },
53656
+ "Error deleting note"
53657
+ )
52856
53658
  );
52857
53659
  registerTool(
52858
53660
  "move-note",