apple-notes-mcp 2.9.23 → 2.9.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +28 -17
  2. package/build/index.js +958 -601
  3. package/package.json +1 -1
package/build/index.js CHANGED
@@ -39088,6 +39088,10 @@ function isPermissionDenied(error2) {
39088
39088
  if (!error2) return false;
39089
39089
  return PERMISSION_DENIED_PATTERN.test(error2) || error2.includes(PERMISSION_DENIED_MESSAGE);
39090
39090
  }
39091
+ var QUOTED = /"[^"\n]*"|“[^”\n]*”/g;
39092
+ function stripQuoted(text2) {
39093
+ return text2.replace(QUOTED, '""');
39094
+ }
39091
39095
  var ERROR_MAPPINGS = [
39092
39096
  // Permission errors
39093
39097
  {
@@ -39158,12 +39162,13 @@ function parseErrorMessage(errorOutput) {
39158
39162
  if (executionError) {
39159
39163
  coreError = executionError[1].trim();
39160
39164
  }
39161
- if (isPermissionDenied(errorOutput)) {
39165
+ if (isPermissionDenied(stripQuoted(errorOutput))) {
39162
39166
  return PERMISSION_DENIED_MESSAGE;
39163
39167
  }
39168
+ const unquoted = stripQuoted(coreError);
39164
39169
  for (const { pattern: pattern2, message } of ERROR_MAPPINGS) {
39165
39170
  const match = coreError.match(pattern2);
39166
- if (match) {
39171
+ if (match && (match.length > 1 || pattern2.test(unquoted))) {
39167
39172
  let result = message;
39168
39173
  for (let i = 1; i < match.length; i++) {
39169
39174
  result = result.replace(`$${i}`, match[i] || "");
@@ -39179,7 +39184,10 @@ function parseErrorMessage(errorOutput) {
39179
39184
  }
39180
39185
  function executeAppleScript(script, options = {}) {
39181
39186
  const timeoutMs = options.timeoutMs ?? callTimeoutMs() ?? envPositiveNumber("APPLE_NOTES_MCP_TIMEOUT_MS") ?? DEFAULT_TIMEOUT_MS;
39182
- const maxRetries = options.maxRetries ?? envPositiveNumber("APPLE_NOTES_MCP_MAX_RETRIES") ?? DEFAULT_MAX_RETRIES;
39187
+ const maxRetries = Math.max(
39188
+ 1,
39189
+ options.maxRetries ?? envPositiveNumber("APPLE_NOTES_MCP_MAX_RETRIES") ?? DEFAULT_MAX_RETRIES
39190
+ );
39183
39191
  const retryDelayMs = options.retryDelayMs ?? envPositiveNumber("APPLE_NOTES_MCP_RETRY_DELAY_MS") ?? DEFAULT_RETRY_DELAY_MS;
39184
39192
  const maxBufferBytes = Math.min(options.maxBufferBytes ?? getMaxBuffer(), MAX_OUTPUT_BYTES);
39185
39193
  if (!script || !script.trim()) {
@@ -39588,6 +39596,252 @@ function getChecklistItems(noteId3) {
39588
39596
  return { items };
39589
39597
  }
39590
39598
 
39599
+ // src/utils/noteIdentifiers.ts
39600
+ import { execFileSync as execFileSync3 } from "child_process";
39601
+ import * as fs2 from "fs";
39602
+ import * as os2 from "os";
39603
+ import * as path2 from "path";
39604
+ var NOTES_DB_PATH2 = path2.join(
39605
+ os2.homedir(),
39606
+ "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
39607
+ );
39608
+ var UUID_PATTERN = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;
39609
+ var NUMERIC_KEY_PATTERN = /^\d{1,18}$/;
39610
+ var NOTE_ID_MESSAGE = "A canonical Apple Note ID is required (x-coredata://.../ICNote/p...), or the note's Notes UUID or numeric key";
39611
+ function identifierForm(value) {
39612
+ if (UUID_PATTERN.test(value)) return "uuid";
39613
+ if (NUMERIC_KEY_PATTERN.test(value)) return "key";
39614
+ return "other";
39615
+ }
39616
+ function isAlternateIdentifier(value) {
39617
+ return identifierForm(value) !== "other";
39618
+ }
39619
+ var IdentifierResolutionError = class extends Error {
39620
+ constructor(code, message) {
39621
+ super(message);
39622
+ this.code = code;
39623
+ this.name = "IdentifierResolutionError";
39624
+ }
39625
+ code;
39626
+ };
39627
+ var ENTITY_LABEL = {
39628
+ ICNote: "note",
39629
+ ICFolder: "folder",
39630
+ ICAccount: "account"
39631
+ };
39632
+ 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}`;
39633
+ function identifierFailureIn(message) {
39634
+ if (message.includes(NO_FDA_MESSAGE)) return "no_fda";
39635
+ if (/\bNo (?:note|folder|account) found for (?:numeric key|identifier) /.test(message))
39636
+ return "not_found";
39637
+ if (message.includes("Failed to read the Notes database.") || message.includes("The Notes database has no store UUID"))
39638
+ return "query_error";
39639
+ return null;
39640
+ }
39641
+ function canonicalKey(value) {
39642
+ return BigInt(value).toString();
39643
+ }
39644
+ function assertAll(values, pattern2, label) {
39645
+ for (const value of values) {
39646
+ if (!pattern2.test(value)) throw new Error(`Refusing to query with an invalid ${label}`);
39647
+ }
39648
+ }
39649
+ function entityClause(entity3) {
39650
+ return `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = '${entity3}')`;
39651
+ }
39652
+ function buildResolveSql(entity3, keys, uuids) {
39653
+ assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
39654
+ assertAll(uuids, UUID_PATTERN, "UUID");
39655
+ const matches = [];
39656
+ if (keys.length > 0) {
39657
+ matches.push(`o.Z_PK IN (${[...new Set(keys.map(canonicalKey))].join(", ")})`);
39658
+ }
39659
+ if (uuids.length > 0) {
39660
+ const variants = /* @__PURE__ */ new Set();
39661
+ for (const uuid2 of uuids) {
39662
+ variants.add(uuid2);
39663
+ variants.add(uuid2.toUpperCase());
39664
+ variants.add(uuid2.toLowerCase());
39665
+ }
39666
+ matches.push(`o.ZIDENTIFIER IN (${[...variants].map((v) => `'${v}'`).join(", ")})`);
39667
+ }
39668
+ const where = matches.length > 0 ? matches.join(" OR ") : "0";
39669
+ return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object('pk', o.Z_PK, 'identifier', o.ZIDENTIFIER)) FROM ZICCLOUDSYNCINGOBJECT o WHERE o.Z_ENT = ${entityClause(entity3)} AND (${where})));`;
39670
+ }
39671
+ function buildLookupSql(entity3, keys) {
39672
+ assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
39673
+ const pks = [...new Set(keys.map(canonicalKey))];
39674
+ const inList = pks.length > 0 ? pks.join(", ") : "NULL";
39675
+ let fields = "'pk', o.Z_PK, 'identifier', o.ZIDENTIFIER";
39676
+ let joins = "";
39677
+ if (entity3 === "ICNote") {
39678
+ fields += ", 'folderIdentifier', f.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
39679
+ joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT f ON f.Z_PK = o.ZFOLDER LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = f.ZOWNER ";
39680
+ } else if (entity3 === "ICFolder") {
39681
+ fields += ", 'parentIdentifier', p.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
39682
+ joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = o.ZPARENT LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = o.ZOWNER ";
39683
+ }
39684
+ 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})));`;
39685
+ }
39686
+ function runJsonQuery(sql, dbPath2) {
39687
+ if (!fs2.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
39688
+ let out;
39689
+ try {
39690
+ out = execFileSync3("sqlite3", ["-readonly", dbPath2, sql], {
39691
+ encoding: "utf8",
39692
+ timeout: 5e3,
39693
+ stdio: ["pipe", "pipe", "pipe"]
39694
+ }).trim();
39695
+ } catch (error2) {
39696
+ const detail = error2 instanceof Error ? `${error2.message} ${String(error2.stderr ?? "")}` : String(error2);
39697
+ if (detail.includes("authorization denied") || detail.includes("unable to open database")) {
39698
+ throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
39699
+ }
39700
+ console.error(`Identifier query failed: ${detail}`);
39701
+ throw new IdentifierResolutionError("query_error", "Failed to read the Notes database.");
39702
+ }
39703
+ const parsed = JSON.parse(out || "{}");
39704
+ const rows = typeof parsed.rows === "string" ? JSON.parse(parsed.rows) : parsed.rows;
39705
+ return { store: parsed.store ?? null, rows: rows ?? [] };
39706
+ }
39707
+ function coreDataId(store, entity3, pk) {
39708
+ return `x-coredata://${store}/${entity3}/p${pk}`;
39709
+ }
39710
+ function resolveIdentifiers(values, entity3, dbPath2 = NOTES_DB_PATH2) {
39711
+ const resolved = /* @__PURE__ */ new Map();
39712
+ const keys = [];
39713
+ const uuids = [];
39714
+ for (const value of values) {
39715
+ const form = identifierForm(value);
39716
+ if (form === "key") keys.push(value);
39717
+ else if (form === "uuid") uuids.push(value);
39718
+ else resolved.set(value, value);
39719
+ }
39720
+ if (keys.length === 0 && uuids.length === 0) return resolved;
39721
+ const { store, rows } = runJsonQuery(
39722
+ buildResolveSql(entity3, keys, uuids),
39723
+ dbPath2
39724
+ );
39725
+ if (!store) {
39726
+ throw new IdentifierResolutionError(
39727
+ "query_error",
39728
+ "The Notes database has no store UUID, so an x-coredata id cannot be built."
39729
+ );
39730
+ }
39731
+ const byKey = new Map(rows.map((row) => [String(row.pk), row]));
39732
+ const byUuid = new Map(
39733
+ rows.filter((row) => row.identifier).map((row) => [String(row.identifier).toUpperCase(), row])
39734
+ );
39735
+ const label = ENTITY_LABEL[entity3];
39736
+ const missing = [];
39737
+ for (const key of keys) {
39738
+ const row = byKey.get(canonicalKey(key));
39739
+ if (row) resolved.set(key, coreDataId(store, entity3, row.pk));
39740
+ else missing.push(`numeric key ${key}`);
39741
+ }
39742
+ for (const uuid2 of uuids) {
39743
+ const row = byUuid.get(uuid2.toUpperCase());
39744
+ if (row) resolved.set(uuid2, coreDataId(store, entity3, row.pk));
39745
+ else missing.push(`identifier ${uuid2}`);
39746
+ }
39747
+ if (missing.length > 0) {
39748
+ throw new IdentifierResolutionError(
39749
+ "not_found",
39750
+ `No ${label} found for ${missing.join(", ")}. A numeric key must belong to a ${label}, not another object type.`
39751
+ );
39752
+ }
39753
+ return resolved;
39754
+ }
39755
+ var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
39756
+ function lookupStableIdentifiers(ids, entity3, dbPath2 = NOTES_DB_PATH2) {
39757
+ const result = /* @__PURE__ */ new Map();
39758
+ const wanted = /* @__PURE__ */ new Map();
39759
+ for (const id2 of ids) {
39760
+ const match = COREDATA_PARTS.exec(id2);
39761
+ if (!match || match[2] !== entity3) continue;
39762
+ const pk = canonicalKey(match[3]);
39763
+ const list = wanted.get(pk) ?? [];
39764
+ list.push({ id: id2, store: match[1] });
39765
+ wanted.set(pk, list);
39766
+ }
39767
+ if (wanted.size === 0) return result;
39768
+ let query2;
39769
+ try {
39770
+ query2 = runJsonQuery(buildLookupSql(entity3, [...wanted.keys()]), dbPath2);
39771
+ } catch {
39772
+ return result;
39773
+ }
39774
+ const store = query2.store?.toUpperCase();
39775
+ for (const row of query2.rows) {
39776
+ const fields = {};
39777
+ for (const key of [
39778
+ "identifier",
39779
+ "folderIdentifier",
39780
+ "parentIdentifier",
39781
+ "accountIdentifier"
39782
+ ]) {
39783
+ const value = row[key];
39784
+ if (typeof value === "string" && value) fields[key] = value;
39785
+ }
39786
+ for (const target of wanted.get(String(row.pk)) ?? []) {
39787
+ if (target.store.toUpperCase() === store) result.set(target.id, fields);
39788
+ }
39789
+ }
39790
+ return result;
39791
+ }
39792
+ function withStableIdentifiers(items, entity3, dbPath2 = NOTES_DB_PATH2) {
39793
+ const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
39794
+ if (ids.length === 0) return items;
39795
+ const found = lookupStableIdentifiers(ids, entity3, dbPath2);
39796
+ if (found.size === 0) return items;
39797
+ return items.map(
39798
+ (item) => item.id && found.has(item.id) ? { ...item, ...found.get(item.id) } : item
39799
+ );
39800
+ }
39801
+ var defaultResolver = (values, entity3) => resolveIdentifiers(values, entity3);
39802
+ var DEFAULT_MAX_ID_LENGTH = 2e3;
39803
+ function acceptAlternateForms(pattern2) {
39804
+ return new RegExp(
39805
+ `${pattern2.source}|${UUID_PATTERN.source}|${NUMERIC_KEY_PATTERN.source}`,
39806
+ pattern2.flags
39807
+ );
39808
+ }
39809
+ function resolveInSchema(values, entity3, ctx, resolver) {
39810
+ if (!values.some(isAlternateIdentifier)) return new Map(values.map((v) => [v, v]));
39811
+ try {
39812
+ return resolver(values, entity3);
39813
+ } catch (error2) {
39814
+ ctx.addIssue({
39815
+ code: external_exports.ZodIssueCode.custom,
39816
+ message: error2 instanceof Error ? error2.message : String(error2)
39817
+ });
39818
+ return null;
39819
+ }
39820
+ }
39821
+ function exactIdInput(entity3, pattern2, message, options = {}) {
39822
+ const resolver = options.resolver ?? defaultResolver;
39823
+ return external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern2), message).transform((value, ctx) => {
39824
+ const map = resolveInSchema([value], entity3, ctx, resolver);
39825
+ return map ? map.get(value) ?? value : external_exports.NEVER;
39826
+ });
39827
+ }
39828
+ function exactIdArrayInput(entity3, pattern2, message, options = {}) {
39829
+ const resolver = options.resolver ?? defaultResolver;
39830
+ const item = external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern2), message);
39831
+ const array2 = options.maxItems === void 0 ? external_exports.array(item) : external_exports.array(item).max(options.maxItems);
39832
+ return array2.transform((values, ctx) => {
39833
+ const map = resolveInSchema(values, entity3, ctx, resolver);
39834
+ return map ? values.map((value) => map.get(value) ?? value) : external_exports.NEVER;
39835
+ });
39836
+ }
39837
+ function looseIdTransform(entity3, resolver = defaultResolver) {
39838
+ return (value, ctx) => {
39839
+ if (!isAlternateIdentifier(value)) return value;
39840
+ const map = resolveInSchema([value], entity3, ctx, resolver);
39841
+ return map ? map.get(value) ?? value : external_exports.NEVER;
39842
+ };
39843
+ }
39844
+
39591
39845
  // src/utils/errorCodes.ts
39592
39846
  var CodedError = class extends Error {
39593
39847
  envelope;
@@ -39634,8 +39888,10 @@ var NOTHING_WRITTEN = /nothing (?:was )?(?:changed|created|written)|no replaceme
39634
39888
  var WRITE_ACCEPTED = /\baccepted (?:the|an) \w+, but/i;
39635
39889
  function classifyError(message, cause) {
39636
39890
  if (cause instanceof CodedError) return { ...cause.envelope };
39637
- const text2 = cause instanceof Error && cause.message && !message.includes(cause.message) ? `${message}
39638
- ${cause.message}` : message;
39891
+ const text2 = stripQuoted(
39892
+ cause instanceof Error && cause.message && !message.includes(cause.message) ? `${message}
39893
+ ${cause.message}` : message
39894
+ );
39639
39895
  const timeoutCause = typeof cause === "object" && cause !== null && cause.code === "ETIMEDOUT";
39640
39896
  let code = "operation_failed";
39641
39897
  if (timeoutCause) code = "timeout_indeterminate";
@@ -39658,6 +39914,24 @@ function errorResult(message, cause) {
39658
39914
  isError: true
39659
39915
  };
39660
39916
  }
39917
+ function unavailableWriteEnvelope(envelope) {
39918
+ if (envelope.code !== "notes_unavailable" || envelope.indeterminate !== void 0 || envelope.committed !== void 0)
39919
+ return envelope;
39920
+ return { ...envelope, indeterminate: true };
39921
+ }
39922
+ var INPUT_VALIDATION = /^(?:MCP error -?\d+: )?Input validation error:/;
39923
+ function inputValidationEnvelope(message) {
39924
+ const failure2 = identifierFailureIn(message);
39925
+ const code = failure2 === "no_fda" ? "full_disk_access_missing" : failure2 === "not_found" ? "not_found" : failure2 === "query_error" ? "operation_failed" : "validation_error";
39926
+ return { code, committed: false, indeterminate: false };
39927
+ }
39928
+ function sdkToolError(message) {
39929
+ return INPUT_VALIDATION.test(message) ? errorResult(message, new CodedError(message, inputValidationEnvelope(message))) : errorResult(message);
39930
+ }
39931
+ function installSdkErrorCodes(server2) {
39932
+ const target = server2;
39933
+ if (typeof target.createToolError === "function") target.createToolError = sdkToolError;
39934
+ }
39661
39935
 
39662
39936
  // src/utils/inlineImages.ts
39663
39937
  var DEFAULT_MAX_INLINE_IMAGE_BYTES = 256 * 1024;
@@ -39700,10 +39974,10 @@ function strippedImagesWarning(stripped) {
39700
39974
  }
39701
39975
 
39702
39976
  // src/utils/noteRichText.ts
39703
- import { execFileSync as execFileSync3 } from "node:child_process";
39977
+ import { execFileSync as execFileSync4 } from "node:child_process";
39704
39978
  import { createHash } from "node:crypto";
39705
- import { homedir as homedir2 } from "node:os";
39706
- import { join as join2 } from "node:path";
39979
+ import { homedir as homedir3 } from "node:os";
39980
+ import { join as join3 } from "node:path";
39707
39981
  import { gunzipSync as gunzipSync2 } from "node:zlib";
39708
39982
 
39709
39983
  // src/utils/uniqueById.ts
@@ -39727,7 +40001,7 @@ var HTML_LOSSY_ORDER = [
39727
40001
  "alignment",
39728
40002
  "highlight"
39729
40003
  ];
39730
- var dbPath = join2(homedir2(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
40004
+ var dbPath = join3(homedir3(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
39731
40005
  var safeUrl = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
39732
40006
  var escapeAttribute = (text2) => text2.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
39733
40007
  var normalized = (text2) => text2.replace(/[\s\ufffc]/gu, "");
@@ -39859,7 +40133,7 @@ function readRichNote(id2) {
39859
40133
  const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]+)$/i.exec(id2)?.[1];
39860
40134
  if (!pk) throw new Error("Invalid exact note ID");
39861
40135
  const sql = `BEGIN; SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE=${pk}; SELECT json_group_object(ZIDENTIFIER,ZALTTEXT) FROM ZICCLOUDSYNCINGOBJECT WHERE ZNOTE1=${pk} AND ZTYPEUTI1='com.apple.notes.inlinetextattachment.hashtag'; SELECT json_group_array(json_object('id',ZIDENTIFIER,'pk',Z_PK,'type',COALESCE(ZTYPEUTI1,ZTYPEUTI),'mergeable',hex(COALESCE(ZMERGEABLEDATA1,ZMERGEABLEDATA)),'view',ZATTACHMENTVIEWTYPE)) FROM ZICCLOUDSYNCINGOBJECT WHERE ZNOTE1=${pk} OR ZNOTE=${pk}; COMMIT;`;
39862
- const rows = execFileSync3("/usr/bin/sqlite3", ["-readonly", dbPath, sql], {
40136
+ const rows = execFileSync4("/usr/bin/sqlite3", ["-readonly", dbPath, sql], {
39863
40137
  encoding: "utf8",
39864
40138
  timeout: 5e3,
39865
40139
  maxBuffer: 64 * 1024 * 1024,
@@ -40055,12 +40329,12 @@ function assertLinkedWrite(rich, content, format, allowLinkChanges = false) {
40055
40329
  }
40056
40330
 
40057
40331
  // src/utils/audioTranscripts.ts
40058
- import { execFileSync as execFileSync4 } from "node:child_process";
40059
- import { homedir as homedir3 } from "node:os";
40060
- import { join as join3 } from "node:path";
40332
+ import { execFileSync as execFileSync5 } from "node:child_process";
40333
+ import { homedir as homedir4 } from "node:os";
40334
+ import { join as join4 } from "node:path";
40061
40335
  import { gunzipSync as gunzipSync3 } from "node:zlib";
40062
- var NOTES_DB_PATH2 = join3(
40063
- homedir3(),
40336
+ var NOTES_DB_PATH3 = join4(
40337
+ homedir4(),
40064
40338
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
40065
40339
  );
40066
40340
  var DEFAULT_MAX_SEGMENTS = 2e3;
@@ -40228,10 +40502,11 @@ function buildTranscriptSql(pk, available) {
40228
40502
  if (!/^\d+$/.test(pk)) throw new Error("Invalid primary key");
40229
40503
  const optional2 = (column, key, alias = "a") => available.has(column) ? `, '${key}', ${alias}.${column}` : "";
40230
40504
  const locked = available.has("ZISPASSWORDPROTECTED") ? "COALESCE(n.ZISPASSWORDPROTECTED, 0)" : "0";
40505
+ const isNote = `n.Z_PK = ${pk} AND n.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')`;
40231
40506
  return [
40232
40507
  "BEGIN;",
40233
40508
  // Every statement yields exactly one row, so output lines stay positional.
40234
- `SELECT json_object('found', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = ${pk}), 'locked', (SELECT ${locked} FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = ${pk}));`,
40509
+ `SELECT json_object('found', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n WHERE ${isNote}), 'locked', (SELECT ${locked} FROM ZICCLOUDSYNCINGOBJECT n WHERE ${isNote}));`,
40235
40510
  `SELECT COALESCE((SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE = ${pk} LIMIT 1), '');`,
40236
40511
  "SELECT json_group_array(json_object('pk', a.Z_PK, 'identifier', a.ZIDENTIFIER, 'uti', a.ZTYPEUTI, 'data', hex(a.ZMERGEABLEDATA1)" + optional2("ZDURATION", "duration") + optional2("ZNEEDSTRANSCRIPTION", "needsTranscription") + ", 'fragments', json((SELECT json_group_array(json_object('identifier', c.ZIDENTIFIER" + optional2("ZDURATION", "duration", "c") + `)) FROM ZICCLOUDSYNCINGOBJECT c WHERE c.ZPARENTATTACHMENT = a.Z_PK)))) FROM ZICCLOUDSYNCINGOBJECT a WHERE a.ZNOTE = ${pk} AND a.ZPARENTATTACHMENT IS NULL AND ${AUDIO_UTI_SQL};`,
40237
40512
  "COMMIT;"
@@ -40245,7 +40520,7 @@ var REQUIRED_COLUMNS = [
40245
40520
  "ZIDENTIFIER"
40246
40521
  ];
40247
40522
  function runSqlite(dbPath2, sql) {
40248
- return execFileSync4("sqlite3", ["-readonly", dbPath2, sql], {
40523
+ return execFileSync5("sqlite3", ["-readonly", dbPath2, sql], {
40249
40524
  encoding: "utf8",
40250
40525
  timeout: 1e4,
40251
40526
  maxBuffer: 256 * 1024 * 1024,
@@ -40266,7 +40541,7 @@ function bodyAttachmentOrder(gzipped) {
40266
40541
  }
40267
40542
  function readAudioTranscripts(noteId3, options = {}) {
40268
40543
  const { store, pk } = parseNoteId(noteId3);
40269
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH2;
40544
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH3;
40270
40545
  const maxSegments = Math.min(
40271
40546
  Math.max(1, Math.floor(options.maxSegments ?? DEFAULT_MAX_SEGMENTS)),
40272
40547
  MAX_SEGMENTS_LIMIT
@@ -40598,17 +40873,17 @@ function collectNoteTables(rich, noteId3) {
40598
40873
  }
40599
40874
 
40600
40875
  // src/utils/smartFolders.ts
40601
- import { execFileSync as execFileSync5 } from "child_process";
40602
- import * as fs2 from "fs";
40603
- import * as os2 from "os";
40604
- import * as path2 from "path";
40605
- var NOTES_DB_PATH3 = path2.join(
40606
- os2.homedir(),
40607
- "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
40608
- );
40609
- 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.`;
40610
- var COCOA_EPOCH_OFFSET = 978307200;
40611
- var MAX_DEPTH = 32;
40876
+ import { execFileSync as execFileSync6 } from "child_process";
40877
+ import * as fs3 from "fs";
40878
+ import * as os3 from "os";
40879
+ import * as path3 from "path";
40880
+ var NOTES_DB_PATH4 = path3.join(
40881
+ os3.homedir(),
40882
+ "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
40883
+ );
40884
+ 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.`;
40885
+ var COCOA_EPOCH_OFFSET = 978307200;
40886
+ var MAX_DEPTH = 32;
40612
40887
  var RELATIVE_RANGES = {
40613
40888
  0: "today",
40614
40889
  1: "yesterday",
@@ -40948,15 +41223,15 @@ function buildSmartFolders(output) {
40948
41223
  );
40949
41224
  }
40950
41225
  function runSqlite2(dbPath2, sql) {
40951
- return execFileSync5("sqlite3", ["-readonly", dbPath2, sql], {
41226
+ return execFileSync6("sqlite3", ["-readonly", dbPath2, sql], {
40952
41227
  encoding: "utf8",
40953
41228
  timeout: 5e3,
40954
41229
  maxBuffer: 16 * 1024 * 1024,
40955
41230
  stdio: ["pipe", "pipe", "pipe"]
40956
41231
  });
40957
41232
  }
40958
- function readSmartFolders(dbPath2 = NOTES_DB_PATH3) {
40959
- if (!fs2.existsSync(dbPath2)) return { folders: null, error: "no_fda", message: FDA_MESSAGE };
41233
+ function readSmartFolders(dbPath2 = NOTES_DB_PATH4) {
41234
+ if (!fs3.existsSync(dbPath2)) return { folders: null, error: "no_fda", message: FDA_MESSAGE };
40960
41235
  try {
40961
41236
  const columns = new Set(
40962
41237
  runSqlite2(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").trim().split("\n")
@@ -40981,30 +41256,30 @@ function readSmartFolders(dbPath2 = NOTES_DB_PATH3) {
40981
41256
  }
40982
41257
 
40983
41258
  // src/utils/trashFolders.ts
40984
- import { execFileSync as execFileSync6 } from "child_process";
40985
- import * as fs3 from "fs";
40986
- import * as os3 from "os";
40987
- import * as path3 from "path";
40988
- var NOTES_DB_PATH4 = path3.join(
40989
- os3.homedir(),
41259
+ import { execFileSync as execFileSync7 } from "child_process";
41260
+ import * as fs4 from "fs";
41261
+ import * as os4 from "os";
41262
+ import * as path4 from "path";
41263
+ var NOTES_DB_PATH5 = path4.join(
41264
+ os4.homedir(),
40990
41265
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
40991
41266
  );
40992
41267
  var RECENTLY_DELETED_FOLDER_NAME = "Recently Deleted";
40993
41268
  var CACHE_TTL_MS = 5 * 60 * 1e3;
40994
41269
  var cache = null;
40995
41270
  function query(dbPath2, sql) {
40996
- return execFileSync6("sqlite3", ["-readonly", dbPath2, sql], {
41271
+ return execFileSync7("sqlite3", ["-readonly", dbPath2, sql], {
40997
41272
  encoding: "utf8",
40998
41273
  timeout: 5e3,
40999
41274
  stdio: ["pipe", "pipe", "pipe"]
41000
41275
  });
41001
41276
  }
41002
- function readTrashFolderIds(dbPath2 = NOTES_DB_PATH4) {
41277
+ function readTrashFolderIds(dbPath2 = NOTES_DB_PATH5) {
41003
41278
  if (cache && cache.dbPath === dbPath2 && Date.now() - cache.at < CACHE_TTL_MS) {
41004
41279
  return cache.ids;
41005
41280
  }
41006
41281
  try {
41007
- if (!fs3.existsSync(dbPath2)) return [];
41282
+ if (!fs4.existsSync(dbPath2)) return [];
41008
41283
  const columns = new Set(
41009
41284
  query(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").split("\n").map((line) => line.trim()).filter(Boolean)
41010
41285
  );
@@ -41028,7 +41303,7 @@ function readTrashFolderIds(dbPath2 = NOTES_DB_PATH4) {
41028
41303
  import {
41029
41304
  closeSync,
41030
41305
  constants,
41031
- existsSync as existsSync4,
41306
+ existsSync as existsSync5,
41032
41307
  fstatSync,
41033
41308
  lstatSync,
41034
41309
  mkdirSync,
@@ -41039,11 +41314,11 @@ import {
41039
41314
  rmSync,
41040
41315
  statSync
41041
41316
  } from "fs";
41042
- import { dirname, isAbsolute, join as join6, relative, resolve, sep } from "path";
41043
- import { homedir as homedir6, tmpdir } from "os";
41317
+ import { dirname, isAbsolute, join as join7, relative, resolve, sep } from "path";
41318
+ import { homedir as homedir7, tmpdir } from "os";
41044
41319
  function allowedSaveRoots() {
41045
41320
  return [
41046
- resolve(homedir6()),
41321
+ resolve(homedir7()),
41047
41322
  resolve(tmpdir()),
41048
41323
  "/Volumes",
41049
41324
  "/private/var/folders",
@@ -41052,7 +41327,7 @@ function allowedSaveRoots() {
41052
41327
  ];
41053
41328
  }
41054
41329
  function deniedSaveRoots() {
41055
- return [join6(homedir6(), "Library/Group Containers/group.com.apple.notes")];
41330
+ return [join7(homedir7(), "Library/Group Containers/group.com.apple.notes")];
41056
41331
  }
41057
41332
  function canonicalize(path10) {
41058
41333
  return realpathSync.native(path10);
@@ -41120,7 +41395,7 @@ function assertSafeSavePath(p, roots = allowedSaveRoots(), denied = deniedSaveRo
41120
41395
  if (suffix.split(sep).includes("..")) {
41121
41396
  throw new Error(`Refusing to write outside allowed locations (home, temp, /Volumes): "${abs}"`);
41122
41397
  }
41123
- const canonicalDest = suffix ? join6(canonicalAncestor, suffix) : canonicalAncestor;
41398
+ const canonicalDest = suffix ? join7(canonicalAncestor, suffix) : canonicalAncestor;
41124
41399
  const allowed = canonicalRoots(roots);
41125
41400
  if (!isWithinRoots(canonicalAncestor, allowed) || !isWithinRoots(canonicalDest, allowed)) {
41126
41401
  throw new Error(
@@ -41184,11 +41459,36 @@ function assertReadableInRoots(p, roots = allowedSaveRoots(), label = "Content f
41184
41459
  );
41185
41460
  return abs;
41186
41461
  }
41187
- function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots()) {
41462
+ var ALLOW_PRIVATE_CONTENT_ENV = "APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS";
41463
+ function privateContentReason(p, roots) {
41464
+ 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];
41465
+ const below = containing === void 0 ? p : relative(containing, p);
41466
+ if (below.split(sep).some((part) => part.startsWith("."))) return "a hidden file or directory";
41467
+ const home = resolve(homedir7());
41468
+ const libraries = [
41469
+ join7(home, "Library"),
41470
+ ...canonicalRoots([home]).map((h) => join7(h, "Library"))
41471
+ ];
41472
+ if (!isWithinRoots(p, libraries)) return null;
41473
+ const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) => join7(l, d)));
41474
+ return isWithinRoots(p, cloudDocuments) ? null : "~/Library";
41475
+ }
41476
+ var CLOUD_DOCUMENT_DIRS = ["Mobile Documents", "CloudStorage"];
41477
+ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPrivate = process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1") {
41188
41478
  const abs = assertReadableInRoots(p, roots);
41479
+ const assertNotPrivate = (candidate, candidateRoots) => {
41480
+ if (allowPrivate) return;
41481
+ const reason = privateContentReason(candidate, candidateRoots);
41482
+ if (reason)
41483
+ throw new Error(
41484
+ `Refusing to read "${abs}": it is in ${reason}, which can hold credentials or app data. Move the file to a regular folder, or set ${ALLOW_PRIVATE_CONTENT_ENV}=1 for the server to allow it.`
41485
+ );
41486
+ };
41487
+ assertNotPrivate(abs, roots);
41488
+ assertNotPrivate(canonicalize(abs), canonicalRoots(roots));
41189
41489
  let descriptor;
41190
41490
  try {
41191
- descriptor = openSync(abs, constants.O_RDONLY | constants.O_NOFOLLOW);
41491
+ descriptor = openSync(abs, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
41192
41492
  } catch (error2) {
41193
41493
  if (error2.code === "ELOOP")
41194
41494
  throw new Error(`Refusing to read the symbolic link "${abs}".`);
@@ -41197,6 +41497,20 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots()) {
41197
41497
  try {
41198
41498
  const stat = fstatSync(descriptor);
41199
41499
  if (!stat.isFile()) throw new Error(`Content file is not a regular file: "${abs}"`);
41500
+ let after;
41501
+ try {
41502
+ after = canonicalize(abs);
41503
+ } catch {
41504
+ throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
41505
+ }
41506
+ if (!isWithinRoots(after, canonicalRoots(roots)))
41507
+ throw new Error(
41508
+ `Refusing to read outside allowed locations (home, temp, /Volumes): "${abs}" resolves to "${after}".`
41509
+ );
41510
+ assertNotPrivate(after, canonicalRoots(roots));
41511
+ const named = statSync(after);
41512
+ if (named.dev !== stat.dev || named.ino !== stat.ino)
41513
+ throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
41200
41514
  if (stat.size === 0) throw new Error(`Content file is empty: "${abs}"`);
41201
41515
  if (stat.size > maxBytes)
41202
41516
  throw new Error(`Content file is ${stat.size} bytes, over the ${maxBytes}-byte limit.`);
@@ -41214,13 +41528,13 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots()) {
41214
41528
  }
41215
41529
  function cleanupTempDir(dir) {
41216
41530
  try {
41217
- if (existsSync4(dir)) rmSync(dir, { recursive: true, force: true });
41531
+ if (existsSync5(dir)) rmSync(dir, { recursive: true, force: true });
41218
41532
  } catch {
41219
41533
  }
41220
41534
  }
41221
41535
 
41222
41536
  // src/utils/paperAttachments.ts
41223
- import { execFileSync as execFileSync8 } from "node:child_process";
41537
+ import { execFileSync as execFileSync9 } from "node:child_process";
41224
41538
  import {
41225
41539
  closeSync as closeSync3,
41226
41540
  constants as constants3,
@@ -41233,10 +41547,10 @@ import {
41233
41547
  unlinkSync as unlinkSync2,
41234
41548
  writeSync as writeSync2
41235
41549
  } from "node:fs";
41236
- import { basename as basename2, dirname as dirname3, extname as extname2, join as join8 } from "node:path";
41550
+ import { basename as basename2, dirname as dirname3, extname as extname2, join as join9 } from "node:path";
41237
41551
 
41238
41552
  // src/utils/attachmentAssets.ts
41239
- import { execFileSync as execFileSync7 } from "node:child_process";
41553
+ import { execFileSync as execFileSync8 } from "node:child_process";
41240
41554
  import {
41241
41555
  closeSync as closeSync2,
41242
41556
  constants as constants2,
@@ -41250,11 +41564,11 @@ import {
41250
41564
  unlinkSync,
41251
41565
  writeSync
41252
41566
  } from "node:fs";
41253
- import { homedir as homedir7 } from "node:os";
41254
- import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join7, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
41567
+ import { homedir as homedir8 } from "node:os";
41568
+ import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join8, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
41255
41569
  import { gunzipSync as gunzipSync5 } from "node:zlib";
41256
- var NOTES_CONTAINER_DIR = join7(
41257
- homedir7(),
41570
+ var NOTES_CONTAINER_DIR = join8(
41571
+ homedir8(),
41258
41572
  "Library/Group Containers/group.com.apple.notes"
41259
41573
  );
41260
41574
  var AttachmentStoreError = class extends Error {
@@ -41359,7 +41673,7 @@ function buildAttachmentRowsSql(notePk, columns) {
41359
41673
  }
41360
41674
  function runSqlite3(dbPath2, sql) {
41361
41675
  try {
41362
- return execFileSync7("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
41676
+ return execFileSync8("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
41363
41677
  encoding: "utf8",
41364
41678
  timeout: 1e4,
41365
41679
  maxBuffer: 128 * 1024 * 1024,
@@ -41427,7 +41741,7 @@ function attachmentOrderFromNoteData(hex3) {
41427
41741
  return null;
41428
41742
  }
41429
41743
  }
41430
- function readNoteAttachmentRows(noteId3, dbPath2 = join7(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
41744
+ function readNoteAttachmentRows(noteId3, dbPath2 = join8(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
41431
41745
  const { pk } = parseNoteId2(noteId3);
41432
41746
  const columnList = runSqlite3(
41433
41747
  dbPath2,
@@ -41488,7 +41802,7 @@ function boundedEntries(dir, limit) {
41488
41802
  }
41489
41803
  }
41490
41804
  function resolveAccountDir(containerDir, accountIdentifier) {
41491
- const accountsDir = join7(containerDir, "Accounts");
41805
+ const accountsDir = join8(containerDir, "Accounts");
41492
41806
  let accountsReal;
41493
41807
  try {
41494
41808
  accountsReal = realpathSync2.native(accountsDir);
@@ -41497,14 +41811,14 @@ function resolveAccountDir(containerDir, accountIdentifier) {
41497
41811
  }
41498
41812
  const account = safeComponent(accountIdentifier);
41499
41813
  if (account) {
41500
- const dir = realInside(join7(accountsReal, account), accountsReal);
41814
+ const dir = realInside(join8(accountsReal, account), accountsReal);
41501
41815
  if (dir && isDirectory(dir)) return dir;
41502
41816
  }
41503
41817
  const entries2 = (boundedEntries(accountsReal, 64) ?? []).filter(
41504
- (e) => isDirectory(join7(accountsReal, e))
41818
+ (e) => isDirectory(join8(accountsReal, e))
41505
41819
  );
41506
41820
  if (entries2.length !== 1) return null;
41507
- return realInside(join7(accountsReal, entries2[0]), accountsReal);
41821
+ return realInside(join8(accountsReal, entries2[0]), accountsReal);
41508
41822
  }
41509
41823
  function generationRank(name) {
41510
41824
  const m = /^(\d+)_/.exec(name);
@@ -41512,23 +41826,23 @@ function generationRank(name) {
41512
41826
  }
41513
41827
  function generationDirs(base, accountDir) {
41514
41828
  const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
41515
- return entries2.map((e) => realInside(join7(base, e), accountDir)).filter((p) => p !== null && isDirectory(p)).sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
41829
+ return entries2.map((e) => realInside(join8(base, e), accountDir)).filter((p) => p !== null && isDirectory(p)).sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
41516
41830
  }
41517
41831
  function fallbackFiles(accountDir, rootName, identifier, generation, names) {
41518
- const base = join7(accountDir, rootName, identifier);
41832
+ const base = join8(accountDir, rootName, identifier);
41519
41833
  const found = [];
41520
41834
  const add = (candidate) => {
41521
41835
  const real = realInside(candidate, accountDir);
41522
41836
  if (real && isRegularFile(real) && !found.includes(real)) found.push(real);
41523
41837
  };
41524
41838
  const gen = safeComponent(generation);
41525
- if (gen) for (const name of names) add(join7(base, gen, name));
41839
+ if (gen) for (const name of names) add(join8(base, gen, name));
41526
41840
  if (isDirectory(base)) {
41527
41841
  for (const dir of generationDirs(base, accountDir))
41528
- for (const name of names) add(join7(dir, name));
41529
- for (const name of names) add(join7(base, name));
41842
+ for (const name of names) add(join8(dir, name));
41843
+ for (const name of names) add(join8(base, name));
41530
41844
  }
41531
- for (const name of names) add(join7(accountDir, rootName, `${identifier}${extname(name)}`));
41845
+ for (const name of names) add(join8(accountDir, rootName, `${identifier}${extname(name)}`));
41532
41846
  return found;
41533
41847
  }
41534
41848
  function previewPixelArea(name) {
@@ -41540,7 +41854,7 @@ function previewFileInBundle(bundle, accountDir) {
41540
41854
  const direct = [];
41541
41855
  const nested = [];
41542
41856
  for (const entry of boundedEntries(bundle, MAX_BUNDLE_ENTRIES) ?? []) {
41543
- const child2 = realInside(join7(bundle, entry), accountDir);
41857
+ const child2 = realInside(join8(bundle, entry), accountDir);
41544
41858
  if (!child2) continue;
41545
41859
  if (isRegularFile(child2)) direct.push(child2);
41546
41860
  else if (isDirectory(child2)) nested.push(child2);
@@ -41548,14 +41862,14 @@ function previewFileInBundle(bundle, accountDir) {
41548
41862
  nested.sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
41549
41863
  const pick2 = (files) => files.find((f) => basename(f) === "Preview.png") ?? files.find((f) => PREVIEW_IMAGE_SUFFIXES.has(extname(f).toLowerCase())) ?? null;
41550
41864
  for (const dir of nested) {
41551
- const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(join7(dir, e), accountDir)).filter((p) => p !== null && isRegularFile(p));
41865
+ const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(join8(dir, e), accountDir)).filter((p) => p !== null && isRegularFile(p));
41552
41866
  const chosen = pick2(files);
41553
41867
  if (chosen) return chosen;
41554
41868
  }
41555
41869
  return pick2(direct);
41556
41870
  }
41557
41871
  function listPreviewEntries(accountDir) {
41558
- return boundedEntries(join7(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
41872
+ return boundedEntries(join8(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
41559
41873
  }
41560
41874
  function previewPaths(accountDir, identifier, entries2) {
41561
41875
  const id2 = safeComponent(identifier);
@@ -41567,7 +41881,7 @@ function previewPaths(accountDir, identifier, entries2) {
41567
41881
  }).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
41568
41882
  const files = [];
41569
41883
  for (const name of candidates) {
41570
- const real = realInside(join7(accountDir, "Previews", name), accountDir);
41884
+ const real = realInside(join8(accountDir, "Previews", name), accountDir);
41571
41885
  if (!real) continue;
41572
41886
  const file = isRegularFile(real) ? real : isDirectory(real) ? previewFileInBundle(real, accountDir) : null;
41573
41887
  if (file && !files.includes(file)) files.push(file);
@@ -41584,13 +41898,13 @@ function assetPathsFor(accountDir, row) {
41584
41898
  const mediaName = safeComponent(row.mediaFilename);
41585
41899
  const mediaGen = safeComponent(row.mediaGeneration);
41586
41900
  if (mediaId && mediaName) {
41587
- if (mediaGen) add(join7(accountDir, "Media", mediaId, mediaGen, mediaName));
41588
- add(join7(accountDir, "Media", mediaId, mediaName));
41901
+ if (mediaGen) add(join8(accountDir, "Media", mediaId, mediaGen, mediaName));
41902
+ add(join8(accountDir, "Media", mediaId, mediaName));
41589
41903
  }
41590
41904
  const id2 = safeComponent(row.identifier);
41591
41905
  if (!id2) return found;
41592
41906
  const ownName = safeComponent(row.filename);
41593
- if (ownName) add(join7(accountDir, "Media", id2, ownName));
41907
+ if (ownName) add(join8(accountDir, "Media", id2, ownName));
41594
41908
  for (const file of fallbackFiles(accountDir, "FallbackImages", id2, row.fallbackImageGeneration, [
41595
41909
  "FallbackImage.png",
41596
41910
  "FallbackImage.jpg"
@@ -41681,10 +41995,10 @@ function canonicalTail(p) {
41681
41995
  const tail = [];
41682
41996
  for (; ; ) {
41683
41997
  try {
41684
- return join7(realpathSync2.native(current), ...tail);
41998
+ return join8(realpathSync2.native(current), ...tail);
41685
41999
  } catch {
41686
42000
  const parent = dirname2(current);
41687
- if (parent === current) return join7(current, ...tail);
42001
+ if (parent === current) return join8(current, ...tail);
41688
42002
  tail.unshift(basename(current));
41689
42003
  current = parent;
41690
42004
  }
@@ -41725,16 +42039,17 @@ function copyFileExclusive(src, dest) {
41725
42039
  }
41726
42040
  function exportFileName(record2, source, kind) {
41727
42041
  const ext = extname(source);
42042
+ const id2 = safeComponent(record2.identifier) ?? "attachment";
41728
42043
  if (kind === "preview") {
41729
42044
  const stored2 = safeComponent(record2.filename ? basename(record2.filename) : null);
41730
- const stem = stored2 ? basename(stored2, extname(stored2)) : record2.identifier;
41731
- return `${safeComponent(stem) ?? record2.identifier}-preview${ext}`;
42045
+ const stem = stored2 ? basename(stored2, extname(stored2)) : id2;
42046
+ return `${safeComponent(stem) ?? id2}-preview${ext}`;
41732
42047
  }
41733
42048
  const stored = safeComponent(record2.filename ? basename(record2.filename) : null);
41734
42049
  if (stored && !GENERIC_FILE_NAMES.has(stored.toLowerCase())) return stored;
41735
- const own = basename(source);
41736
- if (!GENERIC_FILE_NAMES.has(own.toLowerCase())) return own;
41737
- return `${record2.identifier}${ext}`;
42050
+ const own = safeComponent(basename(source));
42051
+ if (own && !GENERIC_FILE_NAMES.has(own.toLowerCase())) return own;
42052
+ return `${id2}${ext}`;
41738
42053
  }
41739
42054
  function collisionName(name, attempt) {
41740
42055
  if (attempt <= 1) return name;
@@ -41766,8 +42081,10 @@ function exportOneAttachment(record2, dir, source) {
41766
42081
  if (!source) return base;
41767
42082
  const name = exportFileName(record2, source.path, source.kind);
41768
42083
  for (let attempt = 1; attempt <= MAX_COLLISION_SUFFIX; attempt++) {
41769
- const dest = join7(dir, collisionName(name, attempt));
42084
+ const dest = join8(dir, collisionName(name, attempt));
41770
42085
  try {
42086
+ if (dirname2(dest) !== resolve2(dir))
42087
+ throw new Error(`Refusing to write outside the export directory: "${dest}"`);
41771
42088
  assertSafeSavePath(dest);
41772
42089
  copyFileExclusive(source.path, dest);
41773
42090
  return { ...base, exportedTo: dest, exportedKind: source.kind };
@@ -41893,7 +42210,7 @@ function buildDrawingRowsSql(notePk, columns) {
41893
42210
  }
41894
42211
  function runSqlite4(dbPath2, sql) {
41895
42212
  try {
41896
- return execFileSync8("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
42213
+ return execFileSync9("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
41897
42214
  encoding: "utf8",
41898
42215
  timeout: 1e4,
41899
42216
  maxBuffer: 16 * 1024 * 1024,
@@ -41933,7 +42250,7 @@ function parseDrawingRows(json2) {
41933
42250
  }
41934
42251
  return rows.sort((a, b) => a.pk - b.pk);
41935
42252
  }
41936
- function readDrawingRows(noteId3, dbPath2 = join8(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
42253
+ function readDrawingRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
41937
42254
  const { pk } = parseNoteId2(noteId3);
41938
42255
  const columns = new Set(
41939
42256
  runSqlite4(
@@ -41977,16 +42294,16 @@ var byGenerationDesc = (a, b) => generationRank(basename2(b)) - generationRank(b
41977
42294
  function findFallbackImage(accountDir, identifier, generation) {
41978
42295
  const id2 = safeComponent(identifier);
41979
42296
  if (!id2) return null;
41980
- const base = join8(accountDir, "FallbackImages", id2);
42297
+ const base = join9(accountDir, "FallbackImages", id2);
41981
42298
  const names = ["FallbackImage.png", "FallbackImage.jpg"];
41982
42299
  const candidates = [];
41983
42300
  const gen = safeComponent(generation);
41984
- if (gen) for (const n of names) candidates.push(join8(base, gen, n));
41985
- const gens = entries(base, MAX_DIR_ENTRIES).map((e) => join8(base, e)).sort(byGenerationDesc);
41986
- for (const g of gens) for (const n of names) candidates.push(join8(g, n));
41987
- for (const n of names) candidates.push(join8(base, n));
41988
- candidates.push(join8(accountDir, "FallbackImages", `${id2}.png`));
41989
- candidates.push(join8(accountDir, "FallbackImages", `${id2}.jpg`));
42301
+ if (gen) for (const n of names) candidates.push(join9(base, gen, n));
42302
+ const gens = entries(base, MAX_DIR_ENTRIES).map((e) => join9(base, e)).sort(byGenerationDesc);
42303
+ for (const g of gens) for (const n of names) candidates.push(join9(g, n));
42304
+ for (const n of names) candidates.push(join9(base, n));
42305
+ candidates.push(join9(accountDir, "FallbackImages", `${id2}.png`));
42306
+ candidates.push(join9(accountDir, "FallbackImages", `${id2}.jpg`));
41990
42307
  for (const c of candidates) {
41991
42308
  const real = realInside(c, accountDir);
41992
42309
  if (real && kindOf(real) === "file") return real;
@@ -41996,21 +42313,21 @@ function findFallbackImage(accountDir, identifier, generation) {
41996
42313
  function findLargestPreview(accountDir, identifier) {
41997
42314
  const id2 = safeComponent(identifier);
41998
42315
  if (!id2) return null;
41999
- const dir = join8(accountDir, "Previews");
42316
+ const dir = join9(accountDir, "Previews");
42000
42317
  const prefix = `${id2}-`.toLowerCase();
42001
42318
  const names = entries(dir, MAX_PREVIEW_DIR_ENTRIES2).filter((n) => n.toLowerCase().startsWith(prefix)).filter((n) => {
42002
42319
  const ext = extname2(n).toLowerCase();
42003
42320
  return !ext || IMAGE_SUFFIXES.has(ext) || /^\.\d+$/.test(ext);
42004
42321
  }).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
42005
42322
  for (const name of names) {
42006
- const real = realInside(join8(dir, name), accountDir);
42323
+ const real = realInside(join9(dir, name), accountDir);
42007
42324
  if (!real) continue;
42008
42325
  const kind = kindOf(real);
42009
42326
  if (kind === "file") return real;
42010
42327
  if (kind !== "dir") continue;
42011
- const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(join8(real, e), accountDir)).filter((p) => p !== null && kindOf(p) === "dir").sort(byGenerationDesc);
42328
+ const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(join9(real, e), accountDir)).filter((p) => p !== null && kindOf(p) === "dir").sort(byGenerationDesc);
42012
42329
  for (const g of [...gens, real]) {
42013
- const file = realInside(join8(g, "Preview.png"), accountDir);
42330
+ const file = realInside(join9(g, "Preview.png"), accountDir);
42014
42331
  if (file && kindOf(file) === "file") return file;
42015
42332
  }
42016
42333
  }
@@ -42023,7 +42340,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
42023
42340
  const id2 = safeComponent(row.identifier);
42024
42341
  const fallbackImagePath = accountDir ? findFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : null;
42025
42342
  const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
42026
- const bundle = accountDir && id2 && kind === "paper" ? realInside(join8(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
42343
+ const bundle = accountDir && id2 && kind === "paper" ? realInside(join9(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
42027
42344
  let raster = null;
42028
42345
  for (const [path10, source] of [
42029
42346
  [fallbackImagePath, "fallback"],
@@ -42220,9 +42537,9 @@ function scopeConflictMessage(reason) {
42220
42537
 
42221
42538
  // src/services/appleNotesManager.ts
42222
42539
  var import_turndown = __toESM(require_turndown_cjs(), 1);
42223
- import { existsSync as existsSync5, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
42224
- import { homedir as homedir8, tmpdir as tmpdir2 } from "os";
42225
- import { join as join9 } from "path";
42540
+ import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
42541
+ import { homedir as homedir9, tmpdir as tmpdir2 } from "os";
42542
+ import { join as join10 } from "path";
42226
42543
  var FIELD_SEP = "";
42227
42544
  var RECORD_SEP = "";
42228
42545
  var AS_FIELD_SEP = "(character id 31)";
@@ -42444,6 +42761,11 @@ function smartFolderDestinationError(folderPath) {
42444
42761
  }
42445
42762
  );
42446
42763
  }
42764
+ function smartFoldersReadError(kind, message) {
42765
+ return new CodedError(message, {
42766
+ code: kind === "unsupported_schema" ? "unsupported" : kind === "no_fda" ? "full_disk_access_missing" : "operation_failed"
42767
+ });
42768
+ }
42447
42769
  function throwIfSmartFolderDestination(error2, folderPath) {
42448
42770
  if (error2?.includes(SMART_FOLDER_DESTINATION)) throw smartFolderDestinationError(folderPath);
42449
42771
  }
@@ -42579,8 +42901,8 @@ var ExpectedBodies = class {
42579
42901
  if (body.length <= INLINE_EXPECTED_BODY_LIMIT) {
42580
42902
  return { setup: "", operand: `"${escapeHtmlForAppleScript(body)}"` };
42581
42903
  }
42582
- this.directory ??= mkdtempSync2(join9(tmpdir2(), "apple-notes-expected-body-"));
42583
- const file = join9(this.directory, `body-${this.files++}.html`);
42904
+ this.directory ??= mkdtempSync2(join10(tmpdir2(), "apple-notes-expected-body-"));
42905
+ const file = join10(this.directory, `body-${this.files++}.html`);
42584
42906
  writeFileSync(file, body, { encoding: "utf8", mode: 384 });
42585
42907
  return {
42586
42908
  setup: `
@@ -42626,8 +42948,8 @@ function getNoteLinkFromDB(coreDataId3) {
42626
42948
  const match = coreDataId3.match(/\/p(\d+)$/);
42627
42949
  if (!match) return null;
42628
42950
  const pk = parseInt(match[1], 10);
42629
- const dbPath2 = join9(homedir8(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
42630
- if (!existsSync5(dbPath2)) return null;
42951
+ const dbPath2 = join10(homedir9(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
42952
+ if (!existsSync6(dbPath2)) return null;
42631
42953
  try {
42632
42954
  const { DatabaseSync } = __require("node:sqlite");
42633
42955
  const db = new DatabaseSync(dbPath2, { readOnly: true });
@@ -43261,9 +43583,12 @@ var AppleNotesManager = class {
43261
43583
  if currentBody is not ${expected.operand} and currentBody is not ${expected.operand} & linefeed then return "SAFETY_CONFLICT"
43262
43584
  delete noteRef
43263
43585
  end considering
43586
+ set __stillThere to missing value
43264
43587
  try
43265
- if (id of notes of originalFolder) contains "${safeId}" then return "SAFETY_NOT_DELETED"
43588
+ set __stillThere to (id of notes of originalFolder) contains "${safeId}"
43266
43589
  end try
43590
+ if __stillThere is missing value then return "SAFETY_DELETE_UNVERIFIED"
43591
+ if __stillThere then return "SAFETY_NOT_DELETED"
43267
43592
  return "SAFETY_DELETED"
43268
43593
  `);
43269
43594
  result = executeMutationAppleScript(script);
@@ -43286,6 +43611,7 @@ var AppleNotesManager = class {
43286
43611
  if (status === "SAFETY_IN_RECENTLY_DELETED") return { status: "in-recently-deleted" };
43287
43612
  if (status === "SAFETY_CONTAINER_UNKNOWN") return { status: "container-unknown" };
43288
43613
  if (status === "SAFETY_NOT_DELETED") return { status: "not-deleted" };
43614
+ if (status === "SAFETY_DELETE_UNVERIFIED") return { status: "unverified" };
43289
43615
  return status === "SAFETY_DELETED" ? { status: "deleted" } : { status: "failed" };
43290
43616
  }
43291
43617
  /**
@@ -43433,7 +43759,8 @@ var AppleNotesManager = class {
43433
43759
  */
43434
43760
  listSmartFolders(options = {}) {
43435
43761
  const result = readSmartFolders();
43436
- if (!result.folders) throw new Error(result.message || "Failed to read smart folders.");
43762
+ if (!result.folders)
43763
+ throw smartFoldersReadError(result.error, result.message || "Failed to read smart folders.");
43437
43764
  if (!options.includeMatchingNotes) return result.folders;
43438
43765
  const limit = options.limit ?? 50;
43439
43766
  return result.folders.map((folder) => {
@@ -44821,7 +45148,7 @@ var AppleNotesManager = class {
44821
45148
  if (parts[0] !== "OK") {
44822
45149
  return { success: false, error: parts[1]?.trim() || "attachment not found" };
44823
45150
  }
44824
- if (!existsSync5(abs) || fileSize(abs) === 0) {
45151
+ if (!existsSync6(abs) || fileSize(abs) === 0) {
44825
45152
  return { success: false, error: `Notes reported success but no file was written to ${abs}` };
44826
45153
  }
44827
45154
  return {
@@ -45329,15 +45656,15 @@ var AppleNotesManager = class {
45329
45656
  };
45330
45657
 
45331
45658
  // src/utils/syncDetection.ts
45332
- import { execFileSync as execFileSync9 } from "child_process";
45333
- import * as fs4 from "fs";
45334
- import * as path4 from "path";
45335
- import * as os4 from "os";
45336
- var NOTES_DB_PATH5 = path4.join(
45337
- os4.homedir(),
45659
+ import { execFileSync as execFileSync10 } from "child_process";
45660
+ import * as fs5 from "fs";
45661
+ import * as path5 from "path";
45662
+ import * as os5 from "os";
45663
+ var NOTES_DB_PATH6 = path5.join(
45664
+ os5.homedir(),
45338
45665
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
45339
45666
  );
45340
- var WAL_PATH = `${NOTES_DB_PATH5}-wal`;
45667
+ var WAL_PATH = `${NOTES_DB_PATH6}-wal`;
45341
45668
  var RECENT_ACTIVITY_THRESHOLD_SECONDS = 5;
45342
45669
  var SYNC_STATUS_CACHE_TTL_MS = 2e3;
45343
45670
  var cachedSyncStatus = null;
@@ -45353,14 +45680,14 @@ function getSyncStatus(useCache = true) {
45353
45680
  recentActivity: false
45354
45681
  };
45355
45682
  try {
45356
- if (!fs4.existsSync(NOTES_DB_PATH5)) {
45683
+ if (!fs5.existsSync(NOTES_DB_PATH6)) {
45357
45684
  status.error = "Notes database not found";
45358
45685
  cachedSyncStatus = status;
45359
45686
  cacheTimestamp = Date.now();
45360
45687
  return status;
45361
45688
  }
45362
- if (fs4.existsSync(WAL_PATH)) {
45363
- const walStats = fs4.statSync(WAL_PATH);
45689
+ if (fs5.existsSync(WAL_PATH)) {
45690
+ const walStats = fs5.statSync(WAL_PATH);
45364
45691
  const secondsAgo = (Date.now() - walStats.mtimeMs) / 1e3;
45365
45692
  status.secondsSinceLastChange = Math.round(secondsAgo);
45366
45693
  status.recentActivity = secondsAgo < RECENT_ACTIVITY_THRESHOLD_SECONDS;
@@ -45374,9 +45701,9 @@ function getSyncStatus(useCache = true) {
45374
45701
  WHERE object.ZCLOUDSTATE = state.Z_PK
45375
45702
  );
45376
45703
  `;
45377
- const result = execFileSync9(
45704
+ const result = execFileSync10(
45378
45705
  "sqlite3",
45379
- ["-readonly", NOTES_DB_PATH5, query2.replace(/\n/g, " ")],
45706
+ ["-readonly", NOTES_DB_PATH6, query2.replace(/\n/g, " ")],
45380
45707
  {
45381
45708
  encoding: "utf8",
45382
45709
  timeout: 5e3,
@@ -45433,12 +45760,12 @@ function withSyncAwarenessSync(operation, fn) {
45433
45760
  }
45434
45761
 
45435
45762
  // src/utils/noteMetadata.ts
45436
- import { execFileSync as execFileSync10 } from "child_process";
45437
- import * as fs5 from "fs";
45438
- import * as path5 from "path";
45439
- import * as os5 from "os";
45440
- var NOTES_DB_PATH6 = path5.join(
45441
- os5.homedir(),
45763
+ import { execFileSync as execFileSync11 } from "child_process";
45764
+ import * as fs6 from "fs";
45765
+ import * as path6 from "path";
45766
+ import * as os6 from "os";
45767
+ var NOTES_DB_PATH7 = path6.join(
45768
+ os6.homedir(),
45442
45769
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
45443
45770
  );
45444
45771
  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.`;
@@ -45454,7 +45781,7 @@ var COLUMN_MAP = [
45454
45781
  { key: "smartFolderQuery", column: "ZSMARTFOLDERQUERYJSON", type: "text" }
45455
45782
  ];
45456
45783
  function runSqlite5(query2) {
45457
- return execFileSync10("sqlite3", ["-readonly", NOTES_DB_PATH6, query2], {
45784
+ return execFileSync11("sqlite3", ["-readonly", NOTES_DB_PATH7, query2], {
45458
45785
  encoding: "utf8",
45459
45786
  timeout: 5e3,
45460
45787
  stdio: ["pipe", "pipe", "pipe"]
@@ -45479,7 +45806,7 @@ function getNoteMetadata(noteId3) {
45479
45806
  };
45480
45807
  }
45481
45808
  const pk = pkMatch[1];
45482
- if (!fs5.existsSync(NOTES_DB_PATH6)) {
45809
+ if (!fs6.existsSync(NOTES_DB_PATH7)) {
45483
45810
  return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
45484
45811
  }
45485
45812
  try {
@@ -45525,12 +45852,12 @@ function getNoteMetadata(noteId3) {
45525
45852
  import { gunzipSync as gunzipSync6 } from "zlib";
45526
45853
 
45527
45854
  // src/utils/noteStoreSql.ts
45528
- import { execFileSync as execFileSync11 } from "child_process";
45529
- import * as fs6 from "fs";
45530
- import * as os6 from "os";
45531
- import * as path6 from "path";
45532
- var NOTES_DB_PATH7 = path6.join(
45533
- os6.homedir(),
45855
+ import { execFileSync as execFileSync12 } from "child_process";
45856
+ import * as fs7 from "fs";
45857
+ import * as os7 from "os";
45858
+ import * as path7 from "path";
45859
+ var NOTES_DB_PATH8 = path7.join(
45860
+ os7.homedir(),
45534
45861
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
45535
45862
  );
45536
45863
  var CORE_DATA_EPOCH_MS = Date.UTC(2001, 0, 1);
@@ -45568,7 +45895,7 @@ function renderValue(value) {
45568
45895
  return `ieee754_from_blob(x'${doubleToHex(double)}')`;
45569
45896
  }
45570
45897
  function runReadOnlySql(dbPath2, sql, params = {}) {
45571
- if (!fs6.existsSync(dbPath2)) throw new NoteStoreError(STORE_FDA_MESSAGE, "no_fda");
45898
+ if (!fs7.existsSync(dbPath2)) throw new NoteStoreError(STORE_FDA_MESSAGE, "no_fda");
45572
45899
  const args = ["-readonly"];
45573
45900
  const names = Object.keys(params);
45574
45901
  if (names.length) args.push("-cmd", ".parameter init");
@@ -45580,7 +45907,7 @@ function runReadOnlySql(dbPath2, sql, params = {}) {
45580
45907
  }
45581
45908
  args.push(dbPath2, sql);
45582
45909
  try {
45583
- return execFileSync11("sqlite3", args, {
45910
+ return execFileSync12("sqlite3", args, {
45584
45911
  encoding: "utf8",
45585
45912
  timeout: 3e4,
45586
45913
  maxBuffer: 512 * 1024 * 1024,
@@ -45773,7 +46100,7 @@ function buildSpecialNotesSql(columns, kind, scoped) {
45773
46100
  ].join(" ");
45774
46101
  }
45775
46102
  function listSpecialNotes(options) {
45776
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
46103
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
45777
46104
  const limit = Math.min(
45778
46105
  Math.max(1, Math.trunc(options.limit ?? SPECIAL_LIMIT.DEFAULT)),
45779
46106
  SPECIAL_LIMIT.MAX
@@ -45829,7 +46156,7 @@ function toSpecialRow(row, uuid2, paths, accountNames, kind) {
45829
46156
  return note;
45830
46157
  }
45831
46158
  var EXACT_NOTE_ID = /^x-coredata:\/\/([0-9A-F-]+)\/ICNote\/p(\d+)$/i;
45832
- function quickNoteFlag(noteId3, dbPath2 = NOTES_DB_PATH7) {
46159
+ function quickNoteFlag(noteId3, dbPath2 = NOTES_DB_PATH8) {
45833
46160
  const match = EXACT_NOTE_ID.exec(noteId3);
45834
46161
  if (!match) throw new NoteStoreError(`Not an exact note id: ${noteId3}`, "invalid_input");
45835
46162
  const columns = readColumns(dbPath2);
@@ -45881,7 +46208,7 @@ function referencedObjects(hex3) {
45881
46208
  }
45882
46209
  }
45883
46210
  function nativeTagInventory(options = {}) {
45884
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
46211
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
45885
46212
  const columns = readColumns(dbPath2);
45886
46213
  const context = readStoreContext(dbPath2, columns);
45887
46214
  const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
@@ -45943,15 +46270,27 @@ function assembleInventory(rows, accounts, scope2) {
45943
46270
  }
45944
46271
 
45945
46272
  // src/utils/noteBlocks.ts
45946
- import { execFileSync as execFileSync12 } from "node:child_process";
45947
- import { existsSync as existsSync9 } from "node:fs";
45948
- import { homedir as homedir12 } from "node:os";
45949
- import { join as join13 } from "node:path";
46273
+ import { execFileSync as execFileSync13 } from "node:child_process";
46274
+ import { existsSync as existsSync10 } from "node:fs";
46275
+ import { homedir as homedir13 } from "node:os";
46276
+ import { join as join14 } from "node:path";
45950
46277
  import { gunzipSync as gunzipSync7 } from "node:zlib";
45951
- var NoteBlocksError = class extends Error {
46278
+ var NOTE_BLOCKS_ERROR_CODES = {
46279
+ "invalid-id": "validation_error",
46280
+ "no-full-disk-access": "full_disk_access_missing",
46281
+ "not-found": "not_found",
46282
+ "no-body": "operation_failed",
46283
+ encrypted: "unsupported",
46284
+ "decompress-failed": "unsupported",
46285
+ "malformed-protobuf": "unsupported",
46286
+ "unsupported-structure": "unsupported",
46287
+ "invalid-runs": "unsupported",
46288
+ "query-failed": "operation_failed"
46289
+ };
46290
+ var NoteBlocksError = class extends CodedError {
45952
46291
  code;
45953
46292
  constructor(code, message) {
45954
- super(message);
46293
+ super(message, { code: NOTE_BLOCKS_ERROR_CODES[code] });
45955
46294
  this.name = "NoteBlocksError";
45956
46295
  this.code = code;
45957
46296
  }
@@ -46289,23 +46628,23 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
46289
46628
  undecodedFields: doc.undecodedFields
46290
46629
  };
46291
46630
  }
46292
- var NOTES_DB_PATH8 = join13(
46293
- homedir12(),
46631
+ var NOTES_DB_PATH9 = join14(
46632
+ homedir13(),
46294
46633
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
46295
46634
  );
46296
- function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
46635
+ function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
46297
46636
  const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]{1,18})$/i.exec(id2)?.[1];
46298
46637
  if (!pk)
46299
46638
  throw new NoteBlocksError(
46300
46639
  "invalid-id",
46301
46640
  `Invalid note ID: expected x-coredata://<store>/ICNote/p<number>`
46302
46641
  );
46303
- if (!existsSync9(dbPath2))
46642
+ if (!existsSync10(dbPath2))
46304
46643
  throw new NoteBlocksError("no-full-disk-access", "The Notes database is not readable");
46305
46644
  const sql = "SELECT json_object('exists', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = @pk AND Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')), 'data', (SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE = @pk), 'encrypted', (SELECT ZCRYPTOINITIALIZATIONVECTOR IS NOT NULL FROM ZICNOTEDATA WHERE ZNOTE = @pk));";
46306
46645
  let output;
46307
46646
  try {
46308
- output = execFileSync12(
46647
+ output = execFileSync13(
46309
46648
  "/usr/bin/sqlite3",
46310
46649
  ["-readonly", "-cmd", ".parameter init", "-cmd", `.parameter set @pk ${pk}`, dbPath2, sql],
46311
46650
  {
@@ -46452,7 +46791,7 @@ function resolveFolderPath(folders, paths, input, accountPk) {
46452
46791
  );
46453
46792
  }
46454
46793
  function listRecentNotes(options = {}) {
46455
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
46794
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
46456
46795
  const limit = Math.min(
46457
46796
  Math.max(1, Math.trunc(options.limit ?? RECENT_LIMIT.DEFAULT)),
46458
46797
  RECENT_LIMIT.MAX
@@ -46530,323 +46869,85 @@ function buildFolderCountsSql(columns) {
46530
46869
  return `SELECT json_object('folder', n.ZFOLDER, 'n', COUNT(*)) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_ENT = ${entity("ICNote")} AND n.ZFOLDER IS NOT NULL AND ${notTombstonedSql(columns, "n")} GROUP BY n.ZFOLDER;`;
46531
46870
  }
46532
46871
  var KIND_ORDER = { folder: 0, smart: 1, trash: 2 };
46533
- function compareNodes(a, b) {
46534
- return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
46535
- }
46536
- function folderTree(options = {}) {
46537
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
46538
- const columns = readColumns(dbPath2);
46539
- const context = readStoreContext(dbPath2, columns);
46540
- const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
46541
- const counts = new Map(
46542
- parseJsonLines(
46543
- runReadOnlySql(dbPath2, buildFolderCountsSql(columns))
46544
- ).map((row) => [row.folder, row.n])
46545
- );
46546
- return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
46547
- }
46548
- function assembleFolderTree(context, counts, includeDeleted, scope2) {
46549
- const paths = folderPaths(context.folders);
46550
- const visible2 = context.folders.filter(
46551
- (folder) => (includeDeleted || !folder.tombstoned) && (!scope2 || folder.account === scope2.pk)
46552
- );
46553
- const byPk = new Map(visible2.map((folder) => [folder.pk, folder]));
46554
- const nodes = /* @__PURE__ */ new Map();
46555
- for (const folder of visible2) {
46556
- const node = {
46557
- id: `x-coredata://${context.uuid}/ICFolder/p${folder.pk}`,
46558
- identifier: folder.identifier,
46559
- name: folder.name ?? "",
46560
- path: paths.get(folder.pk) ?? "",
46561
- kind: folder.trash ? "trash" : folder.folderType === 2 ? "smart" : "folder",
46562
- noteCount: counts.get(folder.pk) ?? 0,
46563
- totalNoteCount: 0,
46564
- children: []
46565
- };
46566
- if (includeDeleted) node.markedForDeletion = Boolean(folder.tombstoned);
46567
- nodes.set(folder.pk, node);
46568
- }
46569
- const parentOf = (folder) => {
46570
- if (folder.parent === null || !byPk.has(folder.parent)) return void 0;
46571
- const seen = /* @__PURE__ */ new Set([folder.pk]);
46572
- for (let at = byPk.get(folder.parent); at; at = at.parent !== null ? byPk.get(at.parent) : void 0) {
46573
- if (seen.has(at.pk)) return void 0;
46574
- seen.add(at.pk);
46575
- }
46576
- return folder.parent;
46577
- };
46578
- const roots = /* @__PURE__ */ new Map();
46579
- for (const folder of visible2) {
46580
- const node = nodes.get(folder.pk);
46581
- const parent = parentOf(folder);
46582
- if (parent !== void 0) nodes.get(parent).children.push(node);
46583
- else {
46584
- const key = folder.account !== null && context.accounts.some((a) => a.pk === folder.account) ? folder.account : null;
46585
- roots.set(key, [...roots.get(key) ?? [], node]);
46586
- }
46587
- }
46588
- const finish = (node) => {
46589
- node.children.sort(compareNodes);
46590
- node.totalNoteCount = node.noteCount + node.children.reduce((sum, c) => sum + finish(c), 0);
46591
- return node.totalNoteCount;
46592
- };
46593
- const accounts = [];
46594
- const addAccount = (name, identifier, folders) => {
46595
- folders.sort(compareNodes);
46596
- let noteCount = 0;
46597
- for (const node of folders) {
46598
- const total = finish(node);
46599
- if (node.kind === "folder") noteCount += total;
46600
- }
46601
- accounts.push({ account: name, identifier, noteCount, folders });
46602
- };
46603
- for (const account of scope2 ? [scope2] : context.accounts) {
46604
- addAccount(account.name, account.identifier, roots.get(account.pk) ?? []);
46605
- }
46606
- const orphans = roots.get(null);
46607
- if (orphans && includeDeleted) addAccount("unknown", null, orphans);
46608
- return { accounts, folderCount: accounts.reduce((sum, a) => sum + countNodes(a.folders), 0) };
46609
- }
46610
- function countNodes(nodes) {
46611
- return nodes.reduce((sum, node) => sum + 1 + countNodes(node.children), 0);
46612
- }
46613
-
46614
- // src/utils/noteIdentifiers.ts
46615
- import { execFileSync as execFileSync13 } from "child_process";
46616
- import * as fs7 from "fs";
46617
- import * as os7 from "os";
46618
- import * as path7 from "path";
46619
- var NOTES_DB_PATH9 = path7.join(
46620
- os7.homedir(),
46621
- "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
46622
- );
46623
- var UUID_PATTERN = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;
46624
- var NUMERIC_KEY_PATTERN = /^\d{1,18}$/;
46625
- var NOTE_ID_MESSAGE = "A canonical Apple Note ID is required (x-coredata://.../ICNote/p...), or the note's Notes UUID or numeric key";
46626
- function identifierForm(value) {
46627
- if (UUID_PATTERN.test(value)) return "uuid";
46628
- if (NUMERIC_KEY_PATTERN.test(value)) return "key";
46629
- return "other";
46630
- }
46631
- function isAlternateIdentifier(value) {
46632
- return identifierForm(value) !== "other";
46633
- }
46634
- var IdentifierResolutionError = class extends Error {
46635
- constructor(code, message) {
46636
- super(message);
46637
- this.code = code;
46638
- this.name = "IdentifierResolutionError";
46639
- }
46640
- code;
46641
- };
46642
- var ENTITY_LABEL = {
46643
- ICNote: "note",
46644
- ICFolder: "folder",
46645
- ICAccount: "account"
46646
- };
46647
- 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}`;
46648
- function canonicalKey(value) {
46649
- return BigInt(value).toString();
46650
- }
46651
- function assertAll(values, pattern2, label) {
46652
- for (const value of values) {
46653
- if (!pattern2.test(value)) throw new Error(`Refusing to query with an invalid ${label}`);
46654
- }
46655
- }
46656
- function entityClause(entity3) {
46657
- return `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = '${entity3}')`;
46658
- }
46659
- function buildResolveSql(entity3, keys, uuids) {
46660
- assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
46661
- assertAll(uuids, UUID_PATTERN, "UUID");
46662
- const matches = [];
46663
- if (keys.length > 0) {
46664
- matches.push(`o.Z_PK IN (${[...new Set(keys.map(canonicalKey))].join(", ")})`);
46665
- }
46666
- if (uuids.length > 0) {
46667
- const variants = /* @__PURE__ */ new Set();
46668
- for (const uuid2 of uuids) {
46669
- variants.add(uuid2);
46670
- variants.add(uuid2.toUpperCase());
46671
- variants.add(uuid2.toLowerCase());
46672
- }
46673
- matches.push(`o.ZIDENTIFIER IN (${[...variants].map((v) => `'${v}'`).join(", ")})`);
46674
- }
46675
- const where = matches.length > 0 ? matches.join(" OR ") : "0";
46676
- return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object('pk', o.Z_PK, 'identifier', o.ZIDENTIFIER)) FROM ZICCLOUDSYNCINGOBJECT o WHERE o.Z_ENT = ${entityClause(entity3)} AND (${where})));`;
46677
- }
46678
- function buildLookupSql(entity3, keys) {
46679
- assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
46680
- const pks = [...new Set(keys.map(canonicalKey))];
46681
- const inList = pks.length > 0 ? pks.join(", ") : "NULL";
46682
- let fields = "'pk', o.Z_PK, 'identifier', o.ZIDENTIFIER";
46683
- let joins = "";
46684
- if (entity3 === "ICNote") {
46685
- fields += ", 'folderIdentifier', f.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
46686
- joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT f ON f.Z_PK = o.ZFOLDER LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = f.ZOWNER ";
46687
- } else if (entity3 === "ICFolder") {
46688
- fields += ", 'parentIdentifier', p.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
46689
- joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = o.ZPARENT LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = o.ZOWNER ";
46690
- }
46691
- 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})));`;
46692
- }
46693
- function runJsonQuery(sql, dbPath2) {
46694
- if (!fs7.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
46695
- let out;
46696
- try {
46697
- out = execFileSync13("sqlite3", ["-readonly", dbPath2, sql], {
46698
- encoding: "utf8",
46699
- timeout: 5e3,
46700
- stdio: ["pipe", "pipe", "pipe"]
46701
- }).trim();
46702
- } catch (error2) {
46703
- const detail = error2 instanceof Error ? `${error2.message} ${String(error2.stderr ?? "")}` : String(error2);
46704
- if (detail.includes("authorization denied") || detail.includes("unable to open database")) {
46705
- throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
46706
- }
46707
- console.error(`Identifier query failed: ${detail}`);
46708
- throw new IdentifierResolutionError("query_error", "Failed to read the Notes database.");
46709
- }
46710
- const parsed = JSON.parse(out || "{}");
46711
- const rows = typeof parsed.rows === "string" ? JSON.parse(parsed.rows) : parsed.rows;
46712
- return { store: parsed.store ?? null, rows: rows ?? [] };
46713
- }
46714
- function coreDataId(store, entity3, pk) {
46715
- return `x-coredata://${store}/${entity3}/p${pk}`;
46716
- }
46717
- function resolveIdentifiers(values, entity3, dbPath2 = NOTES_DB_PATH9) {
46718
- const resolved = /* @__PURE__ */ new Map();
46719
- const keys = [];
46720
- const uuids = [];
46721
- for (const value of values) {
46722
- const form = identifierForm(value);
46723
- if (form === "key") keys.push(value);
46724
- else if (form === "uuid") uuids.push(value);
46725
- else resolved.set(value, value);
46726
- }
46727
- if (keys.length === 0 && uuids.length === 0) return resolved;
46728
- const { store, rows } = runJsonQuery(
46729
- buildResolveSql(entity3, keys, uuids),
46730
- dbPath2
46731
- );
46732
- if (!store) {
46733
- throw new IdentifierResolutionError(
46734
- "query_error",
46735
- "The Notes database has no store UUID, so an x-coredata id cannot be built."
46736
- );
46737
- }
46738
- const byKey = new Map(rows.map((row) => [String(row.pk), row]));
46739
- const byUuid = new Map(
46740
- rows.filter((row) => row.identifier).map((row) => [String(row.identifier).toUpperCase(), row])
46741
- );
46742
- const label = ENTITY_LABEL[entity3];
46743
- const missing = [];
46744
- for (const key of keys) {
46745
- const row = byKey.get(canonicalKey(key));
46746
- if (row) resolved.set(key, coreDataId(store, entity3, row.pk));
46747
- else missing.push(`numeric key ${key}`);
46748
- }
46749
- for (const uuid2 of uuids) {
46750
- const row = byUuid.get(uuid2.toUpperCase());
46751
- if (row) resolved.set(uuid2, coreDataId(store, entity3, row.pk));
46752
- else missing.push(`identifier ${uuid2}`);
46753
- }
46754
- if (missing.length > 0) {
46755
- throw new IdentifierResolutionError(
46756
- "not_found",
46757
- `No ${label} found for ${missing.join(", ")}. A numeric key must belong to a ${label}, not another object type.`
46758
- );
46759
- }
46760
- return resolved;
46761
- }
46762
- var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
46763
- function lookupStableIdentifiers(ids, entity3, dbPath2 = NOTES_DB_PATH9) {
46764
- const result = /* @__PURE__ */ new Map();
46765
- const wanted = /* @__PURE__ */ new Map();
46766
- for (const id2 of ids) {
46767
- const match = COREDATA_PARTS.exec(id2);
46768
- if (!match || match[2] !== entity3) continue;
46769
- const pk = canonicalKey(match[3]);
46770
- const list = wanted.get(pk) ?? [];
46771
- list.push({ id: id2, store: match[1] });
46772
- wanted.set(pk, list);
46773
- }
46774
- if (wanted.size === 0) return result;
46775
- let query2;
46776
- try {
46777
- query2 = runJsonQuery(buildLookupSql(entity3, [...wanted.keys()]), dbPath2);
46778
- } catch {
46779
- return result;
46780
- }
46781
- const store = query2.store?.toUpperCase();
46782
- for (const row of query2.rows) {
46783
- const fields = {};
46784
- for (const key of [
46785
- "identifier",
46786
- "folderIdentifier",
46787
- "parentIdentifier",
46788
- "accountIdentifier"
46789
- ]) {
46790
- const value = row[key];
46791
- if (typeof value === "string" && value) fields[key] = value;
46792
- }
46793
- for (const target of wanted.get(String(row.pk)) ?? []) {
46794
- if (target.store.toUpperCase() === store) result.set(target.id, fields);
46795
- }
46796
- }
46797
- return result;
46872
+ function compareNodes(a, b) {
46873
+ return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
46798
46874
  }
46799
- function withStableIdentifiers(items, entity3, dbPath2 = NOTES_DB_PATH9) {
46800
- const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
46801
- if (ids.length === 0) return items;
46802
- const found = lookupStableIdentifiers(ids, entity3, dbPath2);
46803
- if (found.size === 0) return items;
46804
- return items.map(
46805
- (item) => item.id && found.has(item.id) ? { ...item, ...found.get(item.id) } : item
46875
+ function folderTree(options = {}) {
46876
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
46877
+ const columns = readColumns(dbPath2);
46878
+ const context = readStoreContext(dbPath2, columns);
46879
+ const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
46880
+ const counts = new Map(
46881
+ parseJsonLines(
46882
+ runReadOnlySql(dbPath2, buildFolderCountsSql(columns))
46883
+ ).map((row) => [row.folder, row.n])
46806
46884
  );
46885
+ return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
46807
46886
  }
46808
- var defaultResolver = (values, entity3) => resolveIdentifiers(values, entity3);
46809
- var DEFAULT_MAX_ID_LENGTH = 2e3;
46810
- function acceptAlternateForms(pattern2) {
46811
- return new RegExp(
46812
- `${pattern2.source}|${UUID_PATTERN.source}|${NUMERIC_KEY_PATTERN.source}`,
46813
- pattern2.flags
46887
+ function assembleFolderTree(context, counts, includeDeleted, scope2) {
46888
+ const paths = folderPaths(context.folders);
46889
+ const visible2 = context.folders.filter(
46890
+ (folder) => (includeDeleted || !folder.tombstoned) && (!scope2 || folder.account === scope2.pk)
46814
46891
  );
46815
- }
46816
- function resolveInSchema(values, entity3, ctx, resolver) {
46817
- if (!values.some(isAlternateIdentifier)) return new Map(values.map((v) => [v, v]));
46818
- try {
46819
- return resolver(values, entity3);
46820
- } catch (error2) {
46821
- ctx.addIssue({
46822
- code: external_exports.ZodIssueCode.custom,
46823
- message: error2 instanceof Error ? error2.message : String(error2)
46824
- });
46825
- return null;
46892
+ const byPk = new Map(visible2.map((folder) => [folder.pk, folder]));
46893
+ const nodes = /* @__PURE__ */ new Map();
46894
+ for (const folder of visible2) {
46895
+ const node = {
46896
+ id: `x-coredata://${context.uuid}/ICFolder/p${folder.pk}`,
46897
+ identifier: folder.identifier,
46898
+ name: folder.name ?? "",
46899
+ path: paths.get(folder.pk) ?? "",
46900
+ kind: folder.trash ? "trash" : folder.folderType === 2 ? "smart" : "folder",
46901
+ noteCount: counts.get(folder.pk) ?? 0,
46902
+ totalNoteCount: 0,
46903
+ children: []
46904
+ };
46905
+ if (includeDeleted) node.markedForDeletion = Boolean(folder.tombstoned);
46906
+ nodes.set(folder.pk, node);
46826
46907
  }
46827
- }
46828
- function exactIdInput(entity3, pattern2, message, options = {}) {
46829
- const resolver = options.resolver ?? defaultResolver;
46830
- return external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern2), message).transform((value, ctx) => {
46831
- const map = resolveInSchema([value], entity3, ctx, resolver);
46832
- return map ? map.get(value) ?? value : external_exports.NEVER;
46833
- });
46834
- }
46835
- function exactIdArrayInput(entity3, pattern2, message, options = {}) {
46836
- const resolver = options.resolver ?? defaultResolver;
46837
- const item = external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern2), message);
46838
- const array2 = options.maxItems === void 0 ? external_exports.array(item) : external_exports.array(item).max(options.maxItems);
46839
- return array2.transform((values, ctx) => {
46840
- const map = resolveInSchema(values, entity3, ctx, resolver);
46841
- return map ? values.map((value) => map.get(value) ?? value) : external_exports.NEVER;
46842
- });
46843
- }
46844
- function looseIdTransform(entity3, resolver = defaultResolver) {
46845
- return (value, ctx) => {
46846
- if (!isAlternateIdentifier(value)) return value;
46847
- const map = resolveInSchema([value], entity3, ctx, resolver);
46848
- return map ? map.get(value) ?? value : external_exports.NEVER;
46908
+ const parentOf = (folder) => {
46909
+ if (folder.parent === null || !byPk.has(folder.parent)) return void 0;
46910
+ const seen = /* @__PURE__ */ new Set([folder.pk]);
46911
+ for (let at = byPk.get(folder.parent); at; at = at.parent !== null ? byPk.get(at.parent) : void 0) {
46912
+ if (seen.has(at.pk)) return void 0;
46913
+ seen.add(at.pk);
46914
+ }
46915
+ return folder.parent;
46916
+ };
46917
+ const roots = /* @__PURE__ */ new Map();
46918
+ for (const folder of visible2) {
46919
+ const node = nodes.get(folder.pk);
46920
+ const parent = parentOf(folder);
46921
+ if (parent !== void 0) nodes.get(parent).children.push(node);
46922
+ else {
46923
+ const key = folder.account !== null && context.accounts.some((a) => a.pk === folder.account) ? folder.account : null;
46924
+ roots.set(key, [...roots.get(key) ?? [], node]);
46925
+ }
46926
+ }
46927
+ const finish = (node) => {
46928
+ node.children.sort(compareNodes);
46929
+ node.totalNoteCount = node.noteCount + node.children.reduce((sum, c) => sum + finish(c), 0);
46930
+ return node.totalNoteCount;
46931
+ };
46932
+ const accounts = [];
46933
+ const addAccount = (name, identifier, folders) => {
46934
+ folders.sort(compareNodes);
46935
+ let noteCount = 0;
46936
+ for (const node of folders) {
46937
+ const total = finish(node);
46938
+ if (node.kind === "folder") noteCount += total;
46939
+ }
46940
+ accounts.push({ account: name, identifier, noteCount, folders });
46849
46941
  };
46942
+ for (const account of scope2 ? [scope2] : context.accounts) {
46943
+ addAccount(account.name, account.identifier, roots.get(account.pk) ?? []);
46944
+ }
46945
+ const orphans = roots.get(null);
46946
+ if (orphans && includeDeleted) addAccount("unknown", null, orphans);
46947
+ return { accounts, folderCount: accounts.reduce((sum, a) => sum + countNodes(a.folders), 0) };
46948
+ }
46949
+ function countNodes(nodes) {
46950
+ return nodes.reduce((sum, node) => sum + 1 + countNodes(node.children), 0);
46850
46951
  }
46851
46952
 
46852
46953
  // src/utils/contentWarnings.ts
@@ -48531,7 +48632,15 @@ function mutateBackground(request, operation, data, verify, deps) {
48531
48632
  transportError = error2 ?? {};
48532
48633
  transportMessage = describeTransportFailure(error2);
48533
48634
  }
48534
- const after = deps.read(request.id);
48635
+ let after;
48636
+ try {
48637
+ after = deps.read(request.id);
48638
+ } catch (error2) {
48639
+ throw new CodedError(
48640
+ `Operation outcome uncertain; read exact note before any retry: readback failed: ${error2 instanceof Error ? error2.message : String(error2)}${transportMessage ? "; " + transportMessage : ""}`,
48641
+ { code: "verification_failed", indeterminate: true }
48642
+ );
48643
+ }
48535
48644
  try {
48536
48645
  verify(before, after);
48537
48646
  } catch (error2) {
@@ -48797,14 +48906,27 @@ var FEATURES = [
48797
48906
  },
48798
48907
  {
48799
48908
  name: "fullDiskAccessReads",
48800
- description: "Read-only reads of the Notes database: query-notes, checklist state, note metadata, note links, native objects, native tags, and sync detail",
48909
+ description: "Read-only reads of the Notes database: queries, checklist state, metadata, links, note structure, tables, native objects, tags, smart folders, special and recent notes, folder tree, Paper and attachment exports, Markdown and HTML exports, and sync detail",
48801
48910
  tools: [
48802
48911
  "query-notes",
48803
48912
  "get-checklist-state",
48804
48913
  "get-note-metadata",
48805
48914
  "get-note-link",
48806
48915
  "get-native-objects",
48916
+ "get-note-blocks",
48917
+ "get-note-structure",
48918
+ "get-note-tables",
48919
+ "list-note-links",
48807
48920
  "list-native-tags",
48921
+ "list-smart-folders",
48922
+ "list-special-notes",
48923
+ "list-recent-notes",
48924
+ "list-folder-tree",
48925
+ "list-paper-attachments",
48926
+ "export-paper-image",
48927
+ "export-attachments",
48928
+ "export-notes-markdown",
48929
+ "export-notes-html",
48808
48930
  "get-note-markdown (checklist annotations)",
48809
48931
  "get-sync-status (pending uploads)"
48810
48932
  ],
@@ -48865,7 +48987,8 @@ var FEATURES = [
48865
48987
  // Placeholders for features that need a native WRITE helper, which this
48866
48988
  // server does not ship. The opt-in private helper is read-only (write
48867
48989
  // support was deliberately deferred), so enabling or installing it changes
48868
- // nothing here: these always report `not_implemented`.
48990
+ // nothing here: these always report `not_implemented`. Reading smart folders
48991
+ // is available through list-smart-folders (fullDiskAccessReads).
48869
48992
  {
48870
48993
  name: "checklistToggle",
48871
48994
  description: "Check or uncheck an existing checklist item in place",
@@ -48889,17 +49012,17 @@ var FEATURES = [
48889
49012
  },
48890
49013
  {
48891
49014
  name: "paragraphLinks",
48892
- description: "Link to a specific paragraph or heading inside a note",
48893
- tools: [],
49015
+ description: "List a note's paragraphs and return a link that opens Notes at one paragraph or heading, read from the Notes database",
49016
+ tools: ["list-note-paragraphs", "get-paragraph-link"],
48894
49017
  minimumMacOSVersion: null,
48895
- requirements: [{ kind: "native_write_helper" }]
49018
+ requirements: [{ kind: "full_disk_access" }]
48896
49019
  },
48897
49020
  {
48898
49021
  name: "audioTranscription",
48899
- description: "Transcribe audio recordings attached to a note",
48900
- tools: [],
49022
+ description: "Read the transcripts Notes stored for a note's audio recordings, or transcribe them on this Mac (transcribe-note-audio also needs the public helper built with setup --public-helper)",
49023
+ tools: ["get-audio-transcripts", "transcribe-note-audio"],
48901
49024
  minimumMacOSVersion: null,
48902
- requirements: [{ kind: "native_write_helper" }]
49025
+ requirements: [{ kind: "full_disk_access" }]
48903
49026
  },
48904
49027
  {
48905
49028
  name: "markdownTemplateLibrary",
@@ -49570,7 +49693,7 @@ function resolveNote(dbPath2, columns, selector) {
49570
49693
  function noteBodySql(columns) {
49571
49694
  return `SELECT json_object('isNote', n.Z_ENT = ${entity("ICNote")}, 'identifier', ${col(columns, "n", "ZIDENTIFIER")}, 'data', (SELECT hex(d.ZDATA) FROM ZICNOTEDATA d WHERE d.ZNOTE = n.Z_PK), 'encrypted', (SELECT d.ZCRYPTOINITIALIZATIONVECTOR IS NOT NULL FROM ZICNOTEDATA d WHERE d.ZNOTE = n.Z_PK), 'locked', ${col(columns, "n", "ZISPASSWORDPROTECTED")}) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk;`;
49572
49695
  }
49573
- function readNoteParagraphs(selector, { dbPath: dbPath2 = NOTES_DB_PATH7 } = {}) {
49696
+ function readNoteParagraphs(selector, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
49574
49697
  checkNoteSelector(selector);
49575
49698
  const columns = readColumns(dbPath2);
49576
49699
  const { pk, id: id2 } = resolveNote(dbPath2, columns, selector);
@@ -49799,7 +49922,7 @@ function describeNoteStructure(s) {
49799
49922
  if (s.isShared) parts.push("shared");
49800
49923
  return `Note structure: ${parts.join("; ")}.`;
49801
49924
  }
49802
- function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH7, includeText = true, maxTextBytes = 4 * 1024 * 1024 } = {}) {
49925
+ function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH8, includeText = true, maxTextBytes = 4 * 1024 * 1024 } = {}) {
49803
49926
  const { pk } = parseNoteId2(id2);
49804
49927
  const columns = readColumns(dbPath2);
49805
49928
  const rows = parseJsonLines(
@@ -50045,7 +50168,7 @@ function decodeBodies(dbPath2, sql, params) {
50045
50168
  }
50046
50169
  var KIND_ORDER2 = { inline: 0, card: 1, note: 2, section: 3 };
50047
50170
  function listNoteLinks(options = {}) {
50048
- const { dbPath: dbPath2 = NOTES_DB_PATH7, offset = 0, limit = 200, maxBytes = 4 * 1024 * 1024 } = options;
50171
+ const { dbPath: dbPath2 = NOTES_DB_PATH8, offset = 0, limit = 200, maxBytes = 4 * 1024 * 1024 } = options;
50049
50172
  if (options.id && (options.account || options.folder))
50050
50173
  throw new NoteStoreError("Pass either id or account/folder, not both.", "invalid_input");
50051
50174
  const notePk = options.id ? parseNoteId2(options.id).pk : 0;
@@ -50267,8 +50390,9 @@ function insertLink(request, deps) {
50267
50390
  try {
50268
50391
  readback = verifyLinkReadback(before, deps.readLinks(request.id), insertion, request.url);
50269
50392
  } catch (error2) {
50270
- throw new Error(
50271
- `The text was written, but link verification failed: ${error2 instanceof Error ? error2.message : String(error2)}. Do not retry automatically.`
50393
+ throw new CodedError(
50394
+ `The text was written, but link verification failed: ${error2 instanceof Error ? error2.message : String(error2)}. Do not retry automatically.`,
50395
+ { code: "verification_failed", committed: true, indeterminate: true }
50272
50396
  );
50273
50397
  }
50274
50398
  return {
@@ -52590,21 +52714,23 @@ function renderNotesWithTemplate(notes, ctx, options) {
52590
52714
  }
52591
52715
 
52592
52716
  // src/utils/templateAssets.ts
52593
- import { createHash as createHash2 } from "node:crypto";
52717
+ import { createHash as createHash2, randomBytes } from "node:crypto";
52594
52718
  import {
52595
52719
  closeSync as closeSync5,
52596
52720
  constants as constants5,
52597
52721
  fstatSync as fstatSync5,
52722
+ linkSync,
52598
52723
  lstatSync as lstatSync4,
52599
52724
  mkdirSync as mkdirSync5,
52600
52725
  openSync as openSync5,
52601
52726
  readSync as readSync4,
52727
+ unlinkSync as unlinkSync3,
52602
52728
  writeSync as writeSync4
52603
52729
  } from "node:fs";
52604
52730
  import { extname as extname4, isAbsolute as isAbsolute3, join as join21, relative as relative4, resolve as resolve4, sep as sep4 } from "node:path";
52605
52731
  var CHUNK = 1024 * 1024;
52606
52732
  function openRegular(path10) {
52607
- const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW);
52733
+ const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
52608
52734
  if (!fstatSync5(fd).isFile()) {
52609
52735
  closeSync5(fd);
52610
52736
  throw new Error("not a regular file");
@@ -52690,26 +52816,56 @@ var HashedSidecarWriter = class {
52690
52816
  closeSync5(source);
52691
52817
  }
52692
52818
  }
52819
+ /**
52820
+ * Copy to a private temporary name, then hard-link it into place, so the
52821
+ * hashed name only ever holds complete content: an interrupted copy can't
52822
+ * leave a truncated file that later exports would report as name-taken.
52823
+ * Where the volume has no hard links, copy straight to the hashed name and
52824
+ * remove it if the copy fails.
52825
+ */
52693
52826
  copy(source, target) {
52694
- const out = openSync5(
52695
- target,
52696
- constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW,
52697
- 420
52698
- );
52827
+ const temp = join21(this.dir, `.asset-${randomBytes(6).toString("hex")}.tmp`);
52828
+ writeNew(source, temp);
52699
52829
  try {
52700
- const chunk = Buffer.alloc(CHUNK);
52701
- for (let position = 0; ; ) {
52702
- const n = readSync4(source, chunk, 0, chunk.length, position);
52703
- if (n <= 0) break;
52704
- let written = 0;
52705
- while (written < n) written += writeSync4(out, chunk, written, n - written);
52706
- position += n;
52707
- }
52830
+ linkSync(temp, target);
52831
+ return;
52832
+ } catch (error2) {
52833
+ const code = error2.code;
52834
+ if (code !== "ENOTSUP" && code !== "EPERM" && code !== "EXDEV") throw error2;
52708
52835
  } finally {
52709
- closeSync5(out);
52836
+ try {
52837
+ unlinkSync3(temp);
52838
+ } catch {
52839
+ }
52710
52840
  }
52841
+ writeNew(source, target);
52711
52842
  }
52712
52843
  };
52844
+ function writeNew(source, target) {
52845
+ const out = openSync5(
52846
+ target,
52847
+ constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW,
52848
+ 420
52849
+ );
52850
+ try {
52851
+ const chunk = Buffer.alloc(CHUNK);
52852
+ for (let position = 0; ; ) {
52853
+ const n = readSync4(source, chunk, 0, chunk.length, position);
52854
+ if (n <= 0) break;
52855
+ let written = 0;
52856
+ while (written < n) written += writeSync4(out, chunk, written, n - written);
52857
+ position += n;
52858
+ }
52859
+ } catch (error2) {
52860
+ closeSync5(out);
52861
+ try {
52862
+ unlinkSync3(target);
52863
+ } catch {
52864
+ }
52865
+ throw error2;
52866
+ }
52867
+ closeSync5(out);
52868
+ }
52713
52869
  var ReferenceWriter = class {
52714
52870
  constructor(linkBase) {
52715
52871
  this.linkBase = linkBase;
@@ -53276,7 +53432,7 @@ function pasteboardFilename(requested, frozenFilename) {
53276
53432
  function readRegularFile(path10) {
53277
53433
  let descriptor;
53278
53434
  try {
53279
- descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
53435
+ descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
53280
53436
  } catch {
53281
53437
  throw new PasteboardError("file_unreadable", MESSAGES.file_unreadable);
53282
53438
  }
@@ -53480,7 +53636,7 @@ function registerDirectOperations(server2, manager) {
53480
53636
  );
53481
53637
  tool(
53482
53638
  "create-note-with-attachment",
53483
- "Use when: creating a new note that holds one local file, in one call.\nReturns: the new note id plus the add-attachment result (attachment id, bytes, name, content hash).\nDo not use when: the note already exists (add-attachment).\nSafety: checks the file and filename before creating anything, creates the note through Notes.app like create-note, then attaches with the same byte verification as add-attachment. If the attachment step fails after the note exists, the error names the new note's id; attach to it with add-attachment instead of creating another note.",
53639
+ "Use when: creating a new note that holds one local file, in one call.\nReturns: the new note id plus the add-attachment result (attachment id, bytes, name, content hash).\nDo not use when: the note already exists (add-attachment).\nSafety: checks the file and filename before creating anything, creates the note through Notes.app like create-note, then attaches with the same byte verification as add-attachment. If the attachment step fails before the file is inserted, the error names the new note's id; attach to it with add-attachment instead of creating another note. If insertion started but could not be verified, the outcome is uncertain: read the note with list-attachments before attaching again.",
53484
53640
  {
53485
53641
  title: external_exports.string().min(1).max(2e3).refine((s) => !/[\r\n\0]/u.test(s), "One-line title"),
53486
53642
  content: external_exports.string().min(1).max(1024 * 1024).optional().describe("Optional plain-text body placed above the attachment"),
@@ -53506,26 +53662,45 @@ function registerDirectOperations(server2, manager) {
53506
53662
  `Failed to create note "${args.title}". Check that the folder and account exist (list-folders, list-accounts); nothing was attached`
53507
53663
  );
53508
53664
  const handOff = `Note ${note.id} was created; attach to it with add-attachment instead of creating another note`;
53665
+ const createdError = (message, cause) => new CodedError(message, {
53666
+ code: classifyError(message, cause).code,
53667
+ committed: true,
53668
+ indeterminate: false
53669
+ });
53509
53670
  let snapshot;
53510
53671
  try {
53511
53672
  snapshot = readSnapshot(manager, note.id);
53512
53673
  } catch (error2) {
53513
- throw new Error(`${handOff}. The new note could not be read back: ${String(error2)}`);
53674
+ const message = `${handOff}. The new note could not be read back: ${String(error2)}`;
53675
+ throw createdError(message, error2);
53514
53676
  }
53677
+ const progress = { insertionStarted: false };
53515
53678
  try {
53516
53679
  return {
53517
- ...attachFile(manager, {
53518
- id: note.id,
53519
- expectedContentHash: snapshot.hash,
53520
- path: args.path,
53521
- filename: name
53522
- }),
53680
+ ...attachFile(
53681
+ manager,
53682
+ {
53683
+ id: note.id,
53684
+ expectedContentHash: snapshot.hash,
53685
+ path: args.path,
53686
+ filename: name
53687
+ },
53688
+ void 0,
53689
+ progress
53690
+ ),
53523
53691
  title: args.title,
53524
53692
  folder: args.folder,
53525
53693
  noteCreated: true
53526
53694
  };
53527
53695
  } catch (error2) {
53528
- throw new Error(`${handOff}. ${error2 instanceof Error ? error2.message : String(error2)}`);
53696
+ const detail = error2 instanceof Error ? error2.message : String(error2);
53697
+ if (!progress.insertionStarted) throw createdError(`${handOff}. ${detail}`, error2);
53698
+ const message = `Note ${note.id} was created, but the attachment outcome is uncertain: ${detail}. Read the note (list-attachments) before attaching again, and do not create another note`;
53699
+ throw new CodedError(message, {
53700
+ code: "verification_failed",
53701
+ committed: true,
53702
+ indeterminate: true
53703
+ });
53529
53704
  }
53530
53705
  }
53531
53706
  );
@@ -53632,7 +53807,7 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
53632
53807
  );
53633
53808
  return { attachmentId, name: row.filename };
53634
53809
  }
53635
- function attachFile(manager, args, checked) {
53810
+ function attachFile(manager, args, checked, progress = { insertionStarted: false }) {
53636
53811
  const { id: id2, expectedContentHash, path: path10 } = args;
53637
53812
  const name = attachmentName(path10, args.filename);
53638
53813
  const before = checked ?? readSnapshot(manager, id2);
@@ -53647,6 +53822,7 @@ function attachFile(manager, args, checked) {
53647
53822
  if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
53648
53823
  let returnedId;
53649
53824
  let transportUncertain = false;
53825
+ progress.insertionStarted = true;
53650
53826
  try {
53651
53827
  returnedId = manager.addAttachmentById(id2, before.body, temporaryFile);
53652
53828
  } catch {
@@ -53974,15 +54150,27 @@ function runFolderDelete(manager, args, deps = defaultDeps) {
53974
54150
  throw uncertain(
53975
54151
  `The delete outcome is uncertain (${outcome.reason}); read folder ${args.id} before retrying`
53976
54152
  );
53977
- if (manager.folderExistsById(args.id))
54153
+ let stillExists;
54154
+ try {
54155
+ stillExists = manager.folderExistsById(args.id);
54156
+ } catch (error2) {
54157
+ throw uncertain(
54158
+ `The delete outcome is uncertain: Notes.app accepted the delete, but the readback failed (${error2 instanceof Error ? error2.message : String(error2)}); read folder ${args.id} before retrying`
54159
+ );
54160
+ }
54161
+ if (stillExists)
53978
54162
  throw uncertain(
53979
54163
  `The delete outcome is uncertain: Notes.app still resolves folder ${args.id}; read it before retrying`
53980
54164
  );
53981
54165
  let storeTombstoned = false;
53982
54166
  for (let attempt = 0; attempt < 5 && !storeTombstoned; attempt++) {
53983
54167
  if (attempt > 0) deps.sleep(200);
53984
- const after = deps.readStore(coreDataPk(args.id));
53985
- storeTombstoned = !after || after.markedForDeletion;
54168
+ try {
54169
+ const after = deps.readStore(coreDataPk(args.id));
54170
+ storeTombstoned = !after || after.markedForDeletion;
54171
+ } catch {
54172
+ break;
54173
+ }
53986
54174
  }
53987
54175
  return {
53988
54176
  ...base,
@@ -54031,19 +54219,19 @@ function registerFolderDelete(server2, manager, deps = defaultDeps) {
54031
54219
  }
54032
54220
 
54033
54221
  // src/services/templateStore.ts
54034
- import { randomBytes } from "node:crypto";
54222
+ import { randomBytes as randomBytes2 } from "node:crypto";
54035
54223
  import {
54036
54224
  closeSync as closeSync8,
54037
54225
  constants as constants8,
54038
54226
  fstatSync as fstatSync8,
54039
- linkSync,
54227
+ linkSync as linkSync2,
54040
54228
  lstatSync as lstatSync5,
54041
54229
  mkdirSync as mkdirSync7,
54042
54230
  openSync as openSync8,
54043
54231
  readdirSync as readdirSync4,
54044
54232
  readSync as readSync6,
54045
54233
  renameSync,
54046
- unlinkSync as unlinkSync3,
54234
+ unlinkSync as unlinkSync4,
54047
54235
  writeSync as writeSync5
54048
54236
  } from "node:fs";
54049
54237
  import { homedir as homedir19 } from "node:os";
@@ -54106,14 +54294,26 @@ var TemplateStore = class {
54106
54294
  file(name) {
54107
54295
  return join26(this.dir, `${name}.json`);
54108
54296
  }
54109
- /** Read a saved template's text, refusing symlinks and non-regular files. */
54297
+ /**
54298
+ * Read a saved template's text, refusing symlinks and non-regular files.
54299
+ * O_NONBLOCK keeps a FIFO from blocking the event loop (it is then refused as
54300
+ * not a regular file); it does not affect reading a regular file. Only a
54301
+ * symlink (ELOOP) is reported as unsafe-path; other open failures, such as
54302
+ * EACCES, are rethrown as they are.
54303
+ */
54110
54304
  readText(path10) {
54111
54305
  let fd;
54112
54306
  try {
54113
- fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW);
54307
+ fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
54114
54308
  } catch (error2) {
54115
- if (error2.code === "ENOENT") return void 0;
54116
- throw new TemplateStoreError("unsafe-path", `Refusing to read ${path10}: not a regular file.`);
54309
+ const errno = error2.code;
54310
+ if (errno === "ENOENT") return void 0;
54311
+ if (errno === "ELOOP")
54312
+ throw new TemplateStoreError(
54313
+ "unsafe-path",
54314
+ `Refusing to read ${path10}: it is a symbolic link.`
54315
+ );
54316
+ throw error2;
54117
54317
  }
54118
54318
  try {
54119
54319
  const stat = fstatSync8(fd);
@@ -54218,7 +54418,7 @@ var TemplateStore = class {
54218
54418
  "template-exists",
54219
54419
  `A template named "${name}" already exists. Pass force: true to replace it.`
54220
54420
  );
54221
- const temp = join26(this.dir, `.${name}.${randomBytes(6).toString("hex")}.tmp`);
54421
+ const temp = join26(this.dir, `.${name}.${randomBytes2(6).toString("hex")}.tmp`);
54222
54422
  const fd = openSync8(
54223
54423
  temp,
54224
54424
  constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
@@ -54228,14 +54428,20 @@ var TemplateStore = class {
54228
54428
  const data = Buffer.from(body, "utf8");
54229
54429
  let written = 0;
54230
54430
  while (written < data.length) written += writeSync5(fd, data, written);
54231
- } finally {
54431
+ } catch (error2) {
54232
54432
  closeSync8(fd);
54433
+ try {
54434
+ unlinkSync4(temp);
54435
+ } catch {
54436
+ }
54437
+ throw error2;
54233
54438
  }
54439
+ closeSync8(fd);
54234
54440
  try {
54235
54441
  if (force) renameSync(temp, path10);
54236
54442
  else {
54237
54443
  try {
54238
- linkSync(temp, path10);
54444
+ linkSync2(temp, path10);
54239
54445
  } catch (error2) {
54240
54446
  if (error2.code === "EEXIST")
54241
54447
  throw new TemplateStoreError(
@@ -54247,7 +54453,7 @@ var TemplateStore = class {
54247
54453
  }
54248
54454
  } finally {
54249
54455
  try {
54250
- unlinkSync3(temp);
54456
+ unlinkSync4(temp);
54251
54457
  } catch {
54252
54458
  }
54253
54459
  }
@@ -54270,7 +54476,7 @@ var TemplateStore = class {
54270
54476
  "unsafe-path",
54271
54477
  `Refusing to delete ${path10}: not a regular file.`
54272
54478
  );
54273
- unlinkSync3(path10);
54479
+ unlinkSync4(path10);
54274
54480
  return { path: path10 };
54275
54481
  }
54276
54482
  };
@@ -54656,7 +54862,7 @@ function parseColor(value) {
54656
54862
  }
54657
54863
  function parsePaint(value) {
54658
54864
  const v = value.trim();
54659
- if (v === "none") return { kind: "none" };
54865
+ if (/^none$/i.test(v)) return { kind: "none" };
54660
54866
  if (/^currentcolor$/i.test(v)) return { kind: "current" };
54661
54867
  const url = /^url\(\s*['"]?([^'")]*)['"]?\s*\)/i.exec(v);
54662
54868
  if (url) return { kind: "url", target: url[1].trim() };
@@ -55031,8 +55237,9 @@ function distanceToChord(p, a, b) {
55031
55237
  if (len === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
55032
55238
  return Math.abs((p[0] - a[0]) * dy - (p[1] - a[1]) * dx) / len;
55033
55239
  }
55034
- function flattenCubic(p0, c1, c2, p3, tolerance, out, depth = 0) {
55240
+ function flattenCubic(p0, c1, c2, p3, tolerance, out, depth = 0, charge) {
55035
55241
  if (depth >= 16 || Math.max(distanceToChord(c1, p0, p3), distanceToChord(c2, p0, p3)) <= tolerance) {
55242
+ charge?.();
55036
55243
  out.push(p3);
55037
55244
  return;
55038
55245
  }
@@ -55043,32 +55250,48 @@ function flattenCubic(p0, c1, c2, p3, tolerance, out, depth = 0) {
55043
55250
  const d = mid(a, b);
55044
55251
  const e = mid(b, c);
55045
55252
  const m = mid(d, e);
55046
- flattenCubic(p0, a, d, m, tolerance, out, depth + 1);
55047
- flattenCubic(m, e, c, p3, tolerance, out, depth + 1);
55253
+ flattenCubic(p0, a, d, m, tolerance, out, depth + 1, charge);
55254
+ flattenCubic(m, e, c, p3, tolerance, out, depth + 1, charge);
55048
55255
  }
55049
- function flattenSubpath(sub4, m, tolerance) {
55256
+ function flattenSubpath(sub4, m, tolerance, charge) {
55257
+ charge?.();
55050
55258
  const points = [apply(m, sub4.start)];
55051
55259
  let pen = sub4.start;
55052
55260
  for (const seg of sub4.segments) {
55053
- if (seg.kind === "L") points.push(apply(m, seg.to));
55054
- else
55261
+ if (seg.kind === "L") {
55262
+ charge?.();
55263
+ points.push(apply(m, seg.to));
55264
+ } else
55055
55265
  flattenCubic(
55056
55266
  apply(m, pen),
55057
55267
  apply(m, seg.c1),
55058
55268
  apply(m, seg.c2),
55059
55269
  apply(m, seg.to),
55060
55270
  tolerance,
55061
- points
55271
+ points,
55272
+ 0,
55273
+ charge
55062
55274
  );
55063
55275
  pen = seg.to;
55064
55276
  }
55065
55277
  if (sub4.closed) {
55066
55278
  const first2 = points[0];
55067
55279
  const last = points[points.length - 1];
55068
- if (last[0] !== first2[0] || last[1] !== first2[1]) points.push(first2);
55280
+ if (last[0] !== first2[0] || last[1] !== first2[1]) {
55281
+ charge?.();
55282
+ points.push(first2);
55283
+ }
55069
55284
  }
55070
55285
  return points;
55071
55286
  }
55287
+ function subpathControlPoints(sub4, m) {
55288
+ const out = [apply(m, sub4.start)];
55289
+ for (const seg of sub4.segments) {
55290
+ if (seg.kind === "C") out.push(apply(m, seg.c1), apply(m, seg.c2));
55291
+ out.push(apply(m, seg.to));
55292
+ }
55293
+ return out;
55294
+ }
55072
55295
  function clipSegment(a, b, r) {
55073
55296
  const dx = b[0] - a[0];
55074
55297
  const dy = b[1] - a[1];
@@ -55248,6 +55471,13 @@ var SvgError = class extends Error {
55248
55471
  code;
55249
55472
  location;
55250
55473
  };
55474
+ function lookupNamespace(scope2, prefix) {
55475
+ for (let s = scope2; s; s = s.parent) {
55476
+ const ns = s.own?.get(prefix);
55477
+ if (ns !== void 0) return ns;
55478
+ }
55479
+ return void 0;
55480
+ }
55251
55481
  var NAME_START = /[A-Za-z_À-￿]/;
55252
55482
  var NAME_CHAR = /[A-Za-z0-9_.\-:·À-￿]/;
55253
55483
  var PREDEFINED = { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'" };
@@ -55276,6 +55506,9 @@ function parseXml(source, limits) {
55276
55506
  let pos = 0;
55277
55507
  let line = 1;
55278
55508
  let elementCount = 0;
55509
+ let namespaceDeclarations = 0;
55510
+ const maxAttributes = limits.maxAttributes ?? 1024;
55511
+ const maxNamespaceDeclarations = limits.maxNamespaceDeclarations ?? 1024;
55279
55512
  const stack = [];
55280
55513
  let root = null;
55281
55514
  const fail = (message) => {
@@ -55337,6 +55570,7 @@ function parseXml(source, limits) {
55337
55570
  while (i < source.length && NAME_CHAR.test(source[i])) i++;
55338
55571
  const name = source.slice(pos + 1, i);
55339
55572
  const rawAttributes = [];
55573
+ const attributeNames = /* @__PURE__ */ new Set();
55340
55574
  let selfClosing = false;
55341
55575
  for (; ; ) {
55342
55576
  const ws = i;
@@ -55366,8 +55600,13 @@ function parseXml(source, limits) {
55366
55600
  if (close < 0) fail("Unterminated attribute value");
55367
55601
  const rawValue = source.slice(i + 1, close);
55368
55602
  if (rawValue.includes("<")) fail("'<' in an attribute value");
55369
- if (rawAttributes.some((a) => a.name === attrName))
55370
- fail(`Duplicate attribute ${attrName.slice(0, 40)}`);
55603
+ if (attributeNames.has(attrName)) fail(`Duplicate attribute ${attrName.slice(0, 40)}`);
55604
+ attributeNames.add(attrName);
55605
+ if (attributeNames.size > maxAttributes)
55606
+ throw new SvgError(
55607
+ "svg_complexity_limit",
55608
+ `An element has more than ${maxAttributes} attributes (line ${line})`
55609
+ );
55371
55610
  rawAttributes.push({
55372
55611
  name: attrName,
55373
55612
  value: decodeEntities(rawValue.replace(/[\t\n\r]/g, " "), line)
@@ -55384,19 +55623,30 @@ function parseXml(source, limits) {
55384
55623
  );
55385
55624
  if (stack.length + 1 > limits.maxDepth)
55386
55625
  throw new SvgError("svg_complexity_limit", `Elements nest deeper than ${limits.maxDepth}`);
55387
- const parentScope = stack.length ? stack[stack.length - 1].scope : /* @__PURE__ */ new Map();
55388
- const scope2 = new Map(parentScope);
55626
+ const parentScope = stack.length ? stack[stack.length - 1].scope : null;
55627
+ let own = null;
55389
55628
  for (const a of rawAttributes) {
55390
- if (a.name === "xmlns") scope2.set("", a.value);
55391
- else if (a.name.startsWith("xmlns:")) scope2.set(a.name.slice(6), a.value);
55629
+ const prefix = a.name === "xmlns" ? "" : a.name.startsWith("xmlns:") ? a.name.slice(6) : null;
55630
+ if (prefix === null) continue;
55631
+ if (++namespaceDeclarations > maxNamespaceDeclarations)
55632
+ throw new SvgError(
55633
+ "svg_complexity_limit",
55634
+ `The document declares more than ${maxNamespaceDeclarations} namespaces`
55635
+ );
55636
+ (own ??= /* @__PURE__ */ new Map()).set(prefix, a.value);
55392
55637
  }
55638
+ const scope2 = own ? { own, parent: parentScope } : parentScope ?? { own: null, parent: null };
55393
55639
  const resolve9 = (qualified, isAttribute) => {
55394
55640
  const colon = qualified.indexOf(":");
55395
- if (colon < 0) return { local: qualified, ns: isAttribute ? null : scope2.get("") ?? null };
55641
+ if (colon < 0)
55642
+ return {
55643
+ local: qualified,
55644
+ ns: isAttribute ? null : lookupNamespace(scope2, "") ?? null
55645
+ };
55396
55646
  const prefix = qualified.slice(0, colon);
55397
55647
  const local = qualified.slice(colon + 1);
55398
55648
  if (prefix === "xml") return { local, ns: XML_NS };
55399
- const ns = scope2.get(prefix);
55649
+ const ns = lookupNamespace(scope2, prefix);
55400
55650
  if (ns === void 0) fail(`Undeclared namespace prefix "${prefix.slice(0, 40)}"`);
55401
55651
  return { local, ns };
55402
55652
  };
@@ -55424,6 +55674,8 @@ var SVG_ANALYZER_VERSION = "apple-notes-mcp/svg-analyzer@1";
55424
55674
  var SVG_LIMITS = {
55425
55675
  maxSourceBytes: 1048576,
55426
55676
  maxSourceElements: 16384,
55677
+ maxAttributesPerElement: 1024,
55678
+ maxNamespaceDeclarations: 1024,
55427
55679
  maxExpandedElements: 16384,
55428
55680
  maxReferenceExpansions: 8192,
55429
55681
  maxDepth: 64,
@@ -55595,7 +55847,7 @@ function attr(el, local) {
55595
55847
  return el.attributes.find((a) => a.local === local && a.ns === null)?.value;
55596
55848
  }
55597
55849
  function href(el) {
55598
- return el.attributes.find((a) => a.local === "href" && (a.ns === null || a.ns === XLINK_NS))?.value ?? void 0;
55850
+ return el.attributes.find((a) => a.local === "href" && a.ns === null)?.value ?? el.attributes.find((a) => a.local === "href" && a.ns === XLINK_NS)?.value;
55599
55851
  }
55600
55852
  function parseStyleAttribute(value) {
55601
55853
  const out = [];
@@ -55611,7 +55863,24 @@ function parseStyleAttribute(value) {
55611
55863
  function hasText(el) {
55612
55864
  return el.text.trim().length > 0 || el.children.some(hasText);
55613
55865
  }
55614
- var ACTIVE_SCHEME = /^\s*(?:javascript|vbscript|data)\s*:/i;
55866
+ var ACTIVE_SCHEME = /^(?:javascript|vbscript|data)\s*:/i;
55867
+ function urlAsParsed(value) {
55868
+ return value.replace(/[\t\n\r]/g, "").replace(/^[\x00-\x20]+|[\x00-\x20]+$/g, "");
55869
+ }
55870
+ function unescapeCss(value) {
55871
+ if (!value.includes("\\")) return value;
55872
+ return value.replace(
55873
+ /\\(?:([0-9A-Fa-f]{1,6})(?:\r\n|[ \t\n\r\f])?|(\r\n|[\n\r\f])|([\s\S]))/g,
55874
+ (_m, hex3, newline, other) => {
55875
+ if (hex3 !== void 0) {
55876
+ const code = Number.parseInt(hex3, 16);
55877
+ return code === 0 || code > 1114111 || code >= 55296 && code <= 57343 ? "\uFFFD" : String.fromCodePoint(code);
55878
+ }
55879
+ if (newline !== void 0) return "";
55880
+ return other;
55881
+ }
55882
+ );
55883
+ }
55615
55884
  function assertInert(el, location) {
55616
55885
  if (UNSAFE_ELEMENTS.has(el.local))
55617
55886
  throw new SvgError(
@@ -55628,7 +55897,7 @@ function assertInert(el, location) {
55628
55897
  );
55629
55898
  const value = a.value;
55630
55899
  if (a.local === "href" && (a.ns === null || a.ns === XLINK_NS)) {
55631
- const target = value.trim();
55900
+ const target = urlAsParsed(value);
55632
55901
  if (el.local === "a") {
55633
55902
  if (ACTIVE_SCHEME.test(target))
55634
55903
  throw new SvgError("svg_unsafe", "Link uses an active URL scheme", location);
@@ -55641,10 +55910,11 @@ function assertInert(el, location) {
55641
55910
  );
55642
55911
  }
55643
55912
  }
55644
- for (const m of value.matchAll(/url\(\s*['"]?([^'")]*)/gi))
55645
- if (!m[1].trim().startsWith("#"))
55913
+ const css = unescapeCss(value);
55914
+ for (const m of css.matchAll(/url\(\s*['"]?([^'")]*)/gi))
55915
+ if (!urlAsParsed(m[1]).startsWith("#"))
55646
55916
  throw new SvgError("svg_unsafe", "url() references must be local (#id)", location);
55647
- if (a.local === "style" && /@import|expression\s*\(|javascript\s*:/i.test(value))
55917
+ if (a.local === "style" && /@import|expression\s*\(|javascript\s*:/i.test(urlAsParsed(css)))
55648
55918
  throw new SvgError("svg_unsafe", "Active content in a style attribute", location);
55649
55919
  }
55650
55920
  const counts = /* @__PURE__ */ new Map();
@@ -55726,7 +55996,8 @@ var Analyzer = class {
55726
55996
  if (styleAttr) declarations.push(...parseStyleAttribute(styleAttr));
55727
55997
  const diag = Math.hypot(ctx.viewport[0], ctx.viewport[1]) / Math.SQRT2;
55728
55998
  for (const [name, value] of declarations) {
55729
- if (value === "inherit") continue;
55999
+ const keyword = value.toLowerCase();
56000
+ if (keyword === "inherit") continue;
55730
56001
  const invalid2 = () => this.issue(
55731
56002
  "invalid_value",
55732
56003
  null,
@@ -55765,23 +56036,23 @@ var Analyzer = class {
55765
56036
  break;
55766
56037
  }
55767
56038
  case "fill-rule":
55768
- if (value === "evenodd" || value === "nonzero") style.evenOdd = value === "evenodd";
56039
+ if (keyword === "evenodd" || keyword === "nonzero") style.evenOdd = keyword === "evenodd";
55769
56040
  else invalid2();
55770
56041
  break;
55771
56042
  case "visibility":
55772
- style.visible = value === "visible";
56043
+ style.visible = keyword === "visible";
55773
56044
  break;
55774
56045
  case "display":
55775
- if (value === "none") display = false;
56046
+ display = keyword !== "none";
55776
56047
  break;
55777
56048
  case "stroke-linecap":
55778
- style.linecap = value;
56049
+ style.linecap = keyword;
55779
56050
  break;
55780
56051
  case "stroke-linejoin":
55781
- style.linejoin = value;
56052
+ style.linejoin = keyword;
55782
56053
  break;
55783
56054
  case "stroke-dasharray": {
55784
- if (value === "none") {
56055
+ if (keyword === "none") {
55785
56056
  style.dasharray = null;
55786
56057
  break;
55787
56058
  }
@@ -55802,7 +56073,7 @@ var Analyzer = class {
55802
56073
  case "overflow":
55803
56074
  break;
55804
56075
  case "mix-blend-mode":
55805
- if (value !== "normal")
56076
+ if (keyword !== "normal")
55806
56077
  this.issue(
55807
56078
  "blend_mode_ignored",
55808
56079
  "paint-approximation",
@@ -55811,7 +56082,7 @@ var Analyzer = class {
55811
56082
  );
55812
56083
  break;
55813
56084
  case "paint-order":
55814
- if (value !== "normal" && !/^fill(\s+stroke)?(\s+markers)?$/.test(value))
56085
+ if (keyword !== "normal" && !/^fill(\s+stroke)?(\s+markers)?$/.test(keyword))
55815
56086
  this.issue(
55816
56087
  "paint_order_ignored",
55817
56088
  "paint-approximation",
@@ -55821,7 +56092,7 @@ var Analyzer = class {
55821
56092
  break;
55822
56093
  default:
55823
56094
  if (DROPPED_MODIFIERS.has(name)) {
55824
- if (value !== "none")
56095
+ if (keyword !== "none")
55825
56096
  this.issue(
55826
56097
  "modifier_dropped",
55827
56098
  "drop-content",
@@ -55843,7 +56114,7 @@ var Analyzer = class {
55843
56114
  propertyValue(el, name) {
55844
56115
  const styleAttr = attr(el, "style");
55845
56116
  const fromStyle = styleAttr ? parseStyleAttribute(styleAttr).filter(([n]) => n === name).pop() : void 0;
55846
- return fromStyle ? fromStyle[1] : attr(el, name);
56117
+ return (fromStyle ? fromStyle[1] : attr(el, name))?.trim().toLowerCase();
55847
56118
  }
55848
56119
  run() {
55849
56120
  const root = this.root;
@@ -56243,9 +56514,7 @@ var Analyzer = class {
56243
56514
  if (!geometry || !ctx.style.visible) return;
56244
56515
  const { style } = ctx;
56245
56516
  const polylines = geometry.subpaths.filter((s) => s.segments.length > 0).map((s) => {
56246
- const pts = flattenSubpath(s, ctx.matrix, TOLERANCE);
56247
- this.charge("geometryWork", pts.length);
56248
- if (pts.some(
56517
+ if (subpathControlPoints(s, ctx.matrix).some(
56249
56518
  (p) => !(Math.abs(p[0]) <= SVG_LIMITS.maxCoordinate && Math.abs(p[1]) <= SVG_LIMITS.maxCoordinate)
56250
56519
  ))
56251
56520
  throw new SvgError(
@@ -56253,7 +56522,7 @@ var Analyzer = class {
56253
56522
  "Geometry is not finite or exceeds the coordinate limit",
56254
56523
  ctx.location
56255
56524
  );
56256
- return pts;
56525
+ return flattenSubpath(s, ctx.matrix, TOLERANCE, () => this.charge("geometryWork", 1));
56257
56526
  });
56258
56527
  if (!polylines.length) return;
56259
56528
  const fill = this.paintColor(style.fill, style);
@@ -56409,7 +56678,9 @@ function analyzeSvgBuffer(source) {
56409
56678
  if (text2.charCodeAt(0) === 65279) text2 = text2.slice(1);
56410
56679
  const root = parseXml(text2, {
56411
56680
  maxElements: SVG_LIMITS.maxSourceElements,
56412
- maxDepth: SVG_LIMITS.maxDepth
56681
+ maxDepth: SVG_LIMITS.maxDepth,
56682
+ maxAttributes: SVG_LIMITS.maxAttributesPerElement,
56683
+ maxNamespaceDeclarations: SVG_LIMITS.maxNamespaceDeclarations
56413
56684
  });
56414
56685
  const analyzer = new Analyzer(root);
56415
56686
  const viewport = analyzer.run();
@@ -56651,20 +56922,26 @@ function appendChecklistItems(args, deps, readRich) {
56651
56922
  const landed = [];
56652
56923
  let contentHash = args.expectedContentHash;
56653
56924
  for (const [index, text2] of args.items.entries()) {
56654
- let wrote = false;
56925
+ let attempted = false;
56926
+ const tracked = {
56927
+ ...deps,
56928
+ run: (input) => {
56929
+ attempted = true;
56930
+ deps.run(input);
56931
+ }
56932
+ };
56655
56933
  try {
56656
56934
  const result = mutateBackground(
56657
56935
  { ...args, expectedContentHash: contentHash },
56658
56936
  "create-checklist-item",
56659
56937
  { text: text2 },
56660
56938
  (before, after) => {
56661
- wrote = true;
56662
56939
  assertPreserved(before, after, { append: true });
56663
56940
  const added = after.checklist.slice(before.checklist.length);
56664
56941
  if (added.length !== 1 || added[0].text !== text2 || added[0].done)
56665
56942
  throw new Error("Native checklist item not verified");
56666
56943
  },
56667
- deps
56944
+ tracked
56668
56945
  );
56669
56946
  const items = byPosition(readRich(args.id).checklistItems);
56670
56947
  const fresh = items.filter(
@@ -56680,20 +56957,33 @@ function appendChecklistItems(args, deps, readRich) {
56680
56957
  landed.push({ index, id: fresh[0].id, text: text2 });
56681
56958
  contentHash = result.contentHash;
56682
56959
  } catch (error2) {
56960
+ const message = error2 instanceof Error ? error2.message : String(error2);
56961
+ const uncertain2 = attempted && !(error2 instanceof CodedError && error2.envelope.committed === false);
56962
+ const classified = classifyError(message, error2).code;
56963
+ const envelope = uncertain2 ? {
56964
+ code: classified === "timeout_indeterminate" ? classified : "verification_failed",
56965
+ indeterminate: true
56966
+ } : {
56967
+ code: classified,
56968
+ indeterminate: false,
56969
+ // Earlier items did land, so only an empty batch wrote nothing.
56970
+ ...landed.length === 0 ? { committed: false } : {}
56971
+ };
56683
56972
  return {
56684
56973
  ok: false,
56974
+ ...envelope,
56685
56975
  id: args.id,
56686
56976
  landed,
56687
56977
  stoppedAt: {
56688
56978
  index,
56689
56979
  text: text2,
56690
- // A bridge refusal or failure with an unchanged readback is
56691
- // reported as not written even though verification ran (#248).
56692
- outcome: wrote && !(error2 instanceof CodedError && error2.envelope.committed === false) ? "uncertain" : "not-written",
56693
- error: error2 instanceof Error ? error2.message : String(error2)
56980
+ outcome: uncertain2 ? "uncertain" : "not-written",
56981
+ error: message
56694
56982
  },
56695
56983
  notAttempted: args.items.slice(index + 1),
56696
- contentHash,
56984
+ // After an uncertain run the note may have changed, so the last
56985
+ // verified revision would be stale; read the note for a fresh one.
56986
+ ...uncertain2 ? {} : { contentHash },
56697
56987
  message: `Stopped at item ${index + 1} of ${args.items.length}; ${landed.length} item(s) landed and were verified. Read the exact note before retrying, and retry only items that are not present.`
56698
56988
  };
56699
56989
  }
@@ -56703,15 +56993,24 @@ function appendChecklistItems(args, deps, readRich) {
56703
56993
  const orderVerified = tail.length === landed.length && tail.every((item, i) => item.id === landed[i].id && item.text === landed[i].text);
56704
56994
  return {
56705
56995
  ok: orderVerified,
56996
+ ...orderVerified ? {} : { code: "verification_failed", committed: true },
56706
56997
  id: args.id,
56707
56998
  contentHash,
56708
56999
  items: landed,
57000
+ landed,
56709
57001
  orderVerified,
56710
57002
  ...orderVerified ? {} : {
56711
57003
  message: "Every item landed and was verified, but they are not the note's last checklist items in the requested order. Read the note before editing it further."
56712
57004
  }
56713
57005
  };
56714
57006
  }
57007
+ function nativeToolResult(result) {
57008
+ return {
57009
+ content: [{ type: "text", text: JSON.stringify(result) }],
57010
+ structuredContent: result,
57011
+ ...result.ok === false && typeof result.code === "string" ? { isError: true } : {}
57012
+ };
57013
+ }
56715
57014
  function registerNativeOperations(server2, manager) {
56716
57015
  function tool(name, description, input, handler, readOnly = false) {
56717
57016
  server2.registerTool(
@@ -56724,11 +57023,7 @@ function registerNativeOperations(server2, manager) {
56724
57023
  },
56725
57024
  (async (args) => {
56726
57025
  try {
56727
- const result = handler(args);
56728
- return {
56729
- content: [{ type: "text", text: JSON.stringify(result) }],
56730
- structuredContent: result
56731
- };
57026
+ return nativeToolResult(handler(args));
56732
57027
  } catch (error2) {
56733
57028
  return errorResult(error2 instanceof Error ? error2.message : "Operation failed", error2);
56734
57029
  }
@@ -57576,7 +57871,7 @@ function parseNoteObjectId(noteId3) {
57576
57871
  );
57577
57872
  return { store: match[1], pk: Number(match[2]) };
57578
57873
  }
57579
- function queryNoteScoped(sql, pk, dbPath2 = NOTES_DB_PATH7) {
57874
+ function queryNoteScoped(sql, pk, dbPath2 = NOTES_DB_PATH8) {
57580
57875
  return runReadOnlySql(dbPath2, sql, { pk: { int: pk } }).split("\n");
57581
57876
  }
57582
57877
  var NOTE_STATE_SQL = `SELECT json_object('found', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk AND n.Z_ENT = ${entity("ICNote")}), 'locked', (SELECT COALESCE(n.ZISPASSWORDPROTECTED, 0) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk));`;
@@ -57605,7 +57900,7 @@ function drawingRowsSql(columns, dataColumn) {
57605
57900
  `SELECT json_group_array(json_object('pk', a.Z_PK, 'identifier', a.ZIDENTIFIER, 'uti', a.ZTYPEUTI, 'data', hex(a.${dataColumn}))) FROM (SELECT * FROM ZICCLOUDSYNCINGOBJECT a WHERE a.ZNOTE = @pk AND a.ZTYPEUTI IN ('com.apple.drawing.2', 'com.apple.drawing') AND ${notTombstonedSql(columns, "a")} ORDER BY a.Z_PK) a;`
57606
57901
  ].join("\n");
57607
57902
  }
57608
- function readDrawingRows2(noteId3, dbPath2 = NOTES_DB_PATH7) {
57903
+ function readDrawingRows2(noteId3, dbPath2 = NOTES_DB_PATH8) {
57609
57904
  const { store, pk } = parseNoteObjectId(noteId3);
57610
57905
  const columns = readColumns(dbPath2);
57611
57906
  const dataColumn = columns.has("ZMERGEABLEDATA1") ? "ZMERGEABLEDATA1" : columns.has("ZMERGEABLEDATA") ? "ZMERGEABLEDATA" : null;
@@ -57840,7 +58135,7 @@ function isFile2(path10) {
57840
58135
  var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
57841
58136
  function readAudioAssets(noteId3, options = {}) {
57842
58137
  const { store, pk } = parseNoteId2(noteId3);
57843
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
58138
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
57844
58139
  const columns = readColumns(dbPath2);
57845
58140
  const missing = ["ZMEDIA", "ZPARENTATTACHMENT", "ZFILENAME"].filter((c) => !columns.has(c));
57846
58141
  if (missing.length)
@@ -58219,6 +58514,10 @@ function inspectInstallation(deps = defaultDeps2()) {
58219
58514
  );
58220
58515
  return { ...base, ready: true, reason: null, detail: null };
58221
58516
  }
58517
+ function helperTimeoutMs(env) {
58518
+ const value = Number.parseInt(env[TIMEOUT_ENV] || "", 10);
58519
+ return Number.isFinite(value) && value > 0 ? value : DEFAULT_TIMEOUT_MS3;
58520
+ }
58222
58521
  var PrivateHelperError = class extends Error {
58223
58522
  constructor(code, message, details = {}) {
58224
58523
  super(message);
@@ -58301,7 +58600,7 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
58301
58600
  throw new PrivateHelperError(install.reason || "helper_not_installed", install.detail || "");
58302
58601
  binaryPath = install.binaryPath;
58303
58602
  }
58304
- const timeout = Number.parseInt(deps.env[TIMEOUT_ENV] || "", 10) || DEFAULT_TIMEOUT_MS3;
58603
+ const timeout = helperTimeoutMs(deps.env);
58305
58604
  const result = deps.spawn(binaryPath, [], {
58306
58605
  input: JSON.stringify({ protocol: PRIVATE_HELPER_PROTOCOL, action, ...fields }),
58307
58606
  encoding: "utf8",
@@ -58311,13 +58610,23 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
58311
58610
  env: deps.env
58312
58611
  });
58313
58612
  const errno = result.error?.code;
58314
- if (errno === "ETIMEDOUT" || result.signal && result.status === null)
58613
+ if (errno === "ETIMEDOUT")
58315
58614
  throw new PrivateHelperError("timeout", `The helper did not answer within ${timeout} ms.`);
58615
+ if (errno === "ENOBUFS")
58616
+ throw new PrivateHelperError(
58617
+ "invalid_response",
58618
+ `The helper's output exceeded ${MAX_OUTPUT_BYTES3} bytes and was cut off.`
58619
+ );
58316
58620
  if (result.error)
58317
58621
  throw new PrivateHelperError(
58318
58622
  "helper_unreachable",
58319
58623
  `Could not run the helper: ${result.error.message}`
58320
58624
  );
58625
+ if (result.signal)
58626
+ throw new PrivateHelperError(
58627
+ "helper_crashed",
58628
+ `The helper was terminated by ${result.signal} before it answered.`
58629
+ );
58321
58630
  const stdout = String(result.stdout ?? "").trim();
58322
58631
  let parsed;
58323
58632
  try {
@@ -58641,7 +58950,8 @@ function envelopeCode(helperCode, message) {
58641
58950
  case "not_found":
58642
58951
  return "not_found";
58643
58952
  case "timeout":
58644
- return "timeout_indeterminate";
58953
+ case "helper_crashed":
58954
+ return "operation_failed";
58645
58955
  case "invalid_request":
58646
58956
  case "invalid_json":
58647
58957
  case "input_too_large":
@@ -58751,6 +59061,7 @@ var server = new McpServer({
58751
59061
  version: version2,
58752
59062
  description: "MCP server for managing Apple Notes - create, search, update, and organize notes"
58753
59063
  });
59064
+ installSdkErrorCodes(server);
58754
59065
  var notesManager = new AppleNotesManager();
58755
59066
  registerDirectOperations(server, notesManager);
58756
59067
  registerFolderDelete(server, notesManager);
@@ -58767,6 +59078,9 @@ function successResponse(message, structured) {
58767
59078
  function errorResponse(message, cause) {
58768
59079
  return errorResult(message, cause);
58769
59080
  }
59081
+ function notFoundResponse(message) {
59082
+ return errorResult(message, new CodedError(message, { code: "not_found" }));
59083
+ }
58770
59084
  function withErrorHandling(handler, errorPrefix) {
58771
59085
  return async (params) => {
58772
59086
  try {
@@ -58894,6 +59208,9 @@ function bodyReadFailureMessage(id2, title, error2) {
58894
59208
  return describeBodyReadFailure(title, error2, attachments);
58895
59209
  }
58896
59210
  var NOT_DELETED_MESSAGE = "Notes.app accepted the delete, but the note is still in its original folder, so it was not moved to Recently Deleted. Nothing was deleted; read the note again before retrying.";
59211
+ function deleteUnverifiedMessage(title, id2) {
59212
+ return `Notes.app accepted the delete for note "${title}", but its original folder could not be re-read to confirm the note left it. The outcome is uncertain; read exact ID ${id2} before retrying.`;
59213
+ }
58897
59214
  function containerUnknownMessage(title) {
58898
59215
  return `Could not read which folder note "${title}" is in, so it may already be in Recently Deleted, where deleting it would remove it permanently. Nothing was deleted. Retry in a moment; if it keeps failing, quit and reopen Notes.app, then retry.`;
58899
59216
  }
@@ -58964,12 +59281,32 @@ var folderNameSchema = {
58964
59281
  };
58965
59282
  function registerTool(name, config2, cb) {
58966
59283
  const { outputSchema, ...rest } = config2;
59284
+ const callback = NOTES_WRITE_TOOLS.has(name) ? (async (...args) => writeToolResult(
59285
+ await cb(...args)
59286
+ )) : cb;
58967
59287
  return server.registerTool(
58968
59288
  name,
58969
59289
  outputSchema ? { ...rest, outputSchema: external_exports.object(outputSchema).passthrough() } : rest,
58970
- cb
59290
+ callback
58971
59291
  );
58972
59292
  }
59293
+ var NOTES_WRITE_TOOLS = /* @__PURE__ */ new Set([
59294
+ "create-note",
59295
+ "update-note",
59296
+ "append-to-note",
59297
+ "insert-link",
59298
+ "delete-note",
59299
+ "move-note",
59300
+ "create-folder",
59301
+ "delete-folder",
59302
+ "batch-delete-notes",
59303
+ "batch-move-notes"
59304
+ ]);
59305
+ function writeToolResult(result) {
59306
+ const envelope = result.structuredContent;
59307
+ if (!result.isError || !envelope) return result;
59308
+ return { ...result, structuredContent: unavailableWriteEnvelope(envelope) };
59309
+ }
58973
59310
  registerTool(
58974
59311
  "create-note",
58975
59312
  {
@@ -58980,7 +59317,7 @@ registerTool(
58980
59317
  'Note body; required unless contentPath is given (pass exactly one). In plaintext and HTML, AppleScript cannot create true Apple Notes checklists \u2014 `<input type="checkbox">`, checklist CSS classes, and markdown `- [ ]` lines do not render as checkable items; create a plain `<ul>` or `- ` list and convert it in Notes.app with \u21E7\u2318L. With format "markdown" (markdownRoute "shortcut"), `- [ ]`/`- [x]` lines, `>` block quotes, ``` fenced code, `---` dividers and `inline code` (which Notes renders as a highlight, not monospace) become native styles (see create-note-markdown-blocks in get-capabilities).'
58981
59318
  ),
58982
59319
  contentPath: external_exports.string().min(1).max(MAX.SAVE_PATH).optional().describe(
58983
- `Absolute path of a local UTF-8 file to use as the body instead of content (pass exactly one). The same locations save-attachment may write to are allowed (home, temp, /Volumes); symbolic links and non-regular files are refused. Limit ${MAX_CONTENT_FILE_BYTES} bytes.`
59320
+ `Absolute path of a local UTF-8 file to use as the body instead of content (pass exactly one). The same locations save-attachment may write to are allowed (home, temp, /Volumes), except hidden paths (such as ~/.ssh or ~/.config) and ~/Library (iCloud Drive and ~/Library/CloudStorage are allowed); symbolic links and non-regular files are refused. Limit ${MAX_CONTENT_FILE_BYTES} bytes.`
58984
59321
  ),
58985
59322
  format: external_exports.enum(["plaintext", "html", "markdown"]).optional().default("plaintext").describe(
58986
59323
  "Content format: 'plaintext' (default), 'html' for rich formatting, or 'markdown'. Markdown whose first line is exactly `# <title>` (same case and spacing) has that line and one blank line after it removed, since the title is supplied separately."
@@ -59362,7 +59699,7 @@ registerTool(
59362
59699
  if (id2) {
59363
59700
  const note2 = notesManager.getNoteById(id2);
59364
59701
  if (!note2) {
59365
- return errorResponse(`Note with ID "${id2}" not found`);
59702
+ return notFoundResponse(`Note with ID "${id2}" not found`);
59366
59703
  }
59367
59704
  if (note2.passwordProtected) {
59368
59705
  return errorResponse(
@@ -59404,7 +59741,7 @@ registerTool(
59404
59741
  }
59405
59742
  const note = notesManager.getNoteDetails(title, account);
59406
59743
  if (!note) {
59407
- return errorResponse(`Note "${title}" not found`);
59744
+ return notFoundResponse(`Note "${title}" not found`);
59408
59745
  }
59409
59746
  if (note.passwordProtected) {
59410
59747
  return errorResponse(
@@ -59462,7 +59799,7 @@ registerTool(
59462
59799
  if (id2) {
59463
59800
  const note2 = notesManager.getNoteById(id2);
59464
59801
  if (!note2) {
59465
- return errorResponse(`Note with ID "${id2}" not found`);
59802
+ return notFoundResponse(`Note with ID "${id2}" not found`);
59466
59803
  }
59467
59804
  if (note2.passwordProtected) {
59468
59805
  return errorResponse(
@@ -59480,7 +59817,7 @@ registerTool(
59480
59817
  }
59481
59818
  const note = notesManager.getNoteDetails(title, account);
59482
59819
  if (!note) {
59483
- return errorResponse(`Note "${title}" not found`);
59820
+ return notFoundResponse(`Note "${title}" not found`);
59484
59821
  }
59485
59822
  if (note.passwordProtected) {
59486
59823
  return errorResponse(
@@ -59516,7 +59853,7 @@ registerTool(
59516
59853
  withErrorHandling(({ id: id2 }) => {
59517
59854
  const note = notesManager.getNoteById(id2);
59518
59855
  if (!note) {
59519
- return errorResponse(`Note with ID "${id2}" not found`);
59856
+ return notFoundResponse(`Note with ID "${id2}" not found`);
59520
59857
  }
59521
59858
  const metadata = {
59522
59859
  id: note.id,
@@ -59549,7 +59886,7 @@ registerTool(
59549
59886
  withErrorHandling(({ title, account }) => {
59550
59887
  const note = notesManager.getNoteDetails(title, account);
59551
59888
  if (!note) {
59552
- return errorResponse(`Note "${title}" not found`);
59889
+ return notFoundResponse(`Note "${title}" not found`);
59553
59890
  }
59554
59891
  const metadata = {
59555
59892
  id: note.id,
@@ -59606,7 +59943,7 @@ registerTool(
59606
59943
  if (id2) {
59607
59944
  const note2 = notesManager.getNoteById(id2);
59608
59945
  if (!note2) {
59609
- return errorResponse(`Note with ID "${id2}" not found`);
59946
+ return notFoundResponse(`Note with ID "${id2}" not found`);
59610
59947
  }
59611
59948
  if (note2.passwordProtected) {
59612
59949
  return errorResponse(
@@ -59626,7 +59963,7 @@ registerTool(
59626
59963
  }
59627
59964
  const note = notesManager.getNoteDetails(title, account);
59628
59965
  if (!note) {
59629
- return errorResponse(
59966
+ return notFoundResponse(
59630
59967
  `Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
59631
59968
  );
59632
59969
  }
@@ -59704,7 +60041,7 @@ registerTool(
59704
60041
  },
59705
60042
  withErrorHandling(({ id: id2 }) => {
59706
60043
  const note = notesManager.getNoteById(id2);
59707
- if (!note) return errorResponse(`Note with ID "${id2}" not found`);
60044
+ if (!note) return notFoundResponse(`Note with ID "${id2}" not found`);
59708
60045
  const body = notesManager.getNoteContentById(id2);
59709
60046
  if (!body) return errorResponse(`Failed to read content of note "${note.title}"`);
59710
60047
  const rich = readRichNote(id2);
@@ -59810,7 +60147,10 @@ registerTool(
59810
60147
  } catch (error2) {
59811
60148
  if (!(error2 instanceof NoteBlocksError)) throw error2;
59812
60149
  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}` : "";
59813
- return errorResponse(`Error reading note blocks [${error2.code}]: ${error2.message}${hint}`);
60150
+ return errorResponse(
60151
+ `Error reading note blocks [${error2.code}]: ${error2.message}${hint}`,
60152
+ error2
60153
+ );
59814
60154
  }
59815
60155
  const { summary } = page;
59816
60156
  const styles = Object.entries(summary.styles).map(([style, count]) => `${style} ${count}`).join(", ");
@@ -60371,7 +60711,10 @@ registerTool(
60371
60711
  },
60372
60712
  (r) => route = r
60373
60713
  );
60374
- if (response.isError) throw new Error(response.content[0].text);
60714
+ if (response.isError) {
60715
+ const envelope = response.structuredContent;
60716
+ throw envelope?.code ? new CodedError(response.content[0].text, envelope) : new Error(response.content[0].text);
60717
+ }
60375
60718
  return { route, contentHash: String(response.structuredContent?.contentHash ?? "") };
60376
60719
  }
60377
60720
  }
@@ -60463,6 +60806,13 @@ registerTool(
60463
60806
  if (result.status === "not-deleted") {
60464
60807
  return errorResponse(NOT_DELETED_MESSAGE);
60465
60808
  }
60809
+ if (result.status === "unverified") {
60810
+ const message = deleteUnverifiedMessage(snapshot.note.title, id2);
60811
+ return errorResponse(
60812
+ message,
60813
+ new CodedError(message, { code: "verification_failed", indeterminate: true })
60814
+ );
60815
+ }
60466
60816
  if (result.status !== "deleted") {
60467
60817
  return errorResponse(
60468
60818
  `The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
@@ -60507,7 +60857,7 @@ registerTool(
60507
60857
  withErrorHandling(({ id: id2, folder, account, ...scopeArgs }) => {
60508
60858
  const note = notesManager.getNoteById(id2);
60509
60859
  if (!note) {
60510
- return errorResponse(`Note with ID "${id2}" not found`);
60860
+ return notFoundResponse(`Note with ID "${id2}" not found`);
60511
60861
  }
60512
60862
  const success = notesManager.moveNoteById(id2, folder, account, scopeFrom(scopeArgs));
60513
60863
  if (!success) {
@@ -61038,7 +61388,7 @@ registerTool(
61038
61388
  if (id2) {
61039
61389
  const note2 = notesManager.getNoteById(id2);
61040
61390
  if (!note2) {
61041
- return errorResponse(`Note with ID "${id2}" not found`);
61391
+ return notFoundResponse(`Note with ID "${id2}" not found`);
61042
61392
  }
61043
61393
  const attachments2 = notesManager.listAttachmentsById(id2);
61044
61394
  if (attachments2.length === 0) {
@@ -61091,7 +61441,7 @@ ${attachmentList2}`,
61091
61441
  }
61092
61442
  const note = notesManager.getNoteDetails(title, account);
61093
61443
  if (!note) {
61094
- return errorResponse(
61444
+ return notFoundResponse(
61095
61445
  `Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
61096
61446
  );
61097
61447
  }
@@ -61149,6 +61499,8 @@ registerTool(
61149
61499
  }
61150
61500
  if (result.status === "not-deleted")
61151
61501
  return { id: id2, success: false, error: NOT_DELETED_MESSAGE };
61502
+ if (result.status === "unverified")
61503
+ return { id: id2, success: false, error: deleteUnverifiedMessage(snapshot.note.title, id2) };
61152
61504
  return { id: id2, success: false, error: "Delete result uncertain; inspect this exact ID" };
61153
61505
  });
61154
61506
  const succeeded = results.filter((r) => r.success).length;
@@ -61376,10 +61728,12 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61376
61728
  );
61377
61729
  }
61378
61730
  }
61379
- return successResponse(
61380
- `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${skipped} with nothing on disk, ${failed} failed).`,
61381
- structured
61382
- );
61731
+ const summary = `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${skipped} with nothing on disk, ${failed} failed).`;
61732
+ if (exported === 0 && failed > 0) {
61733
+ const error2 = errorResponse(summary, new CodedError(summary, { code: "operation_failed" }));
61734
+ return { ...error2, structuredContent: { ...structured, ...error2.structuredContent } };
61735
+ }
61736
+ return successResponse(summary, structured);
61383
61737
  }, "Error exporting attachments")
61384
61738
  );
61385
61739
  registerTool(
@@ -61529,7 +61883,7 @@ registerTool(
61529
61883
  if (id2) {
61530
61884
  const markdown2 = notesManager.getNoteMarkdownById(id2);
61531
61885
  if (!markdown2) {
61532
- return errorResponse(`Note with ID "${id2}" not found or has no content`);
61886
+ return notFoundResponse(`Note with ID "${id2}" not found or has no content`);
61533
61887
  }
61534
61888
  return successResponse(markdown2, { markdown: markdown2 });
61535
61889
  }
@@ -61538,7 +61892,7 @@ registerTool(
61538
61892
  }
61539
61893
  const markdown = notesManager.getNoteMarkdown(title, account);
61540
61894
  if (!markdown) {
61541
- return errorResponse(
61895
+ return notFoundResponse(
61542
61896
  `Note "${title}" not found or has no content. Use search-notes to find notes, then use the note's ID for reliable operations.`
61543
61897
  );
61544
61898
  }
@@ -61613,7 +61967,10 @@ registerTool(
61613
61967
  `Error exporting Markdown [${error2.code}]: the template is invalid:
61614
61968
  ` + error2.details.map((detail) => `${detail.path}: ${detail.message}`).join("\n")
61615
61969
  );
61616
- return errorResponse(`Error exporting Markdown [${error2.code}]: ${error2.message}${hint}`);
61970
+ return errorResponse(
61971
+ `Error exporting Markdown [${error2.code}]: ${error2.message}${hint}`,
61972
+ error2
61973
+ );
61617
61974
  }
61618
61975
  const warned = receipt.warnings?.length ? `; ${receipt.warnings.length + (receipt.warningsOmitted ?? 0)} warning(s)` : "";
61619
61976
  const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + warned;
@@ -61666,7 +62023,7 @@ registerTool(
61666
62023
  } catch (error2) {
61667
62024
  if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
61668
62025
  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}` : "";
61669
- return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`);
62026
+ return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
61670
62027
  }
61671
62028
  const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;
61672
62029
  const skipped = receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "";
@@ -61694,7 +62051,7 @@ registerTool(
61694
62051
  withErrorHandling(({ id: id2 }) => {
61695
62052
  const note = notesManager.getNoteById(id2);
61696
62053
  if (!note) {
61697
- return errorResponse(`Note with ID "${id2}" not found`);
62054
+ return notFoundResponse(`Note with ID "${id2}" not found`);
61698
62055
  }
61699
62056
  if (note.passwordProtected) {
61700
62057
  return errorResponse(