apple-notes-mcp 2.9.24 → 2.9.26

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 +29 -19
  2. package/build/index.js +797 -592
  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(
@@ -41189,18 +41464,21 @@ function privateContentReason(p, roots) {
41189
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];
41190
41465
  const below = containing === void 0 ? p : relative(containing, p);
41191
41466
  if (below.split(sep).some((part) => part.startsWith("."))) return "a hidden file or directory";
41192
- const home = resolve(homedir6());
41467
+ const home = resolve(homedir7());
41193
41468
  const libraries = [
41194
- join6(home, "Library"),
41195
- ...canonicalRoots([home]).map((h) => join6(h, "Library"))
41469
+ join7(home, "Library"),
41470
+ ...canonicalRoots([home]).map((h) => join7(h, "Library"))
41196
41471
  ];
41197
41472
  if (!isWithinRoots(p, libraries)) return null;
41198
- const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) => join6(l, d)));
41473
+ const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) => join7(l, d)));
41199
41474
  return isWithinRoots(p, cloudDocuments) ? null : "~/Library";
41200
41475
  }
41201
41476
  var CLOUD_DOCUMENT_DIRS = ["Mobile Documents", "CloudStorage"];
41202
- function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPrivate = process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1") {
41203
- const abs = assertReadableInRoots(p, roots);
41477
+ function readAllowedFile(p, maxBytes, options = {}) {
41478
+ const roots = options.roots ?? allowedSaveRoots();
41479
+ const allowPrivate = options.allowPrivate ?? process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1";
41480
+ const label = options.label ?? "Content file";
41481
+ const abs = assertReadableInRoots(p, roots, label);
41204
41482
  const assertNotPrivate = (candidate, candidateRoots) => {
41205
41483
  if (allowPrivate) return;
41206
41484
  const reason = privateContentReason(candidate, candidateRoots);
@@ -41221,12 +41499,12 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPriva
41221
41499
  }
41222
41500
  try {
41223
41501
  const stat = fstatSync(descriptor);
41224
- if (!stat.isFile()) throw new Error(`Content file is not a regular file: "${abs}"`);
41502
+ if (!stat.isFile()) throw new Error(`${label} is not a regular file: "${abs}"`);
41225
41503
  let after;
41226
41504
  try {
41227
41505
  after = canonicalize(abs);
41228
41506
  } catch {
41229
- throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
41507
+ throw new Error(`${label} changed while it was being opened; try again: "${abs}"`);
41230
41508
  }
41231
41509
  if (!isWithinRoots(after, canonicalRoots(roots)))
41232
41510
  throw new Error(
@@ -41235,31 +41513,35 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPriva
41235
41513
  assertNotPrivate(after, canonicalRoots(roots));
41236
41514
  const named = statSync(after);
41237
41515
  if (named.dev !== stat.dev || named.ino !== stat.ino)
41238
- throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
41239
- if (stat.size === 0) throw new Error(`Content file is empty: "${abs}"`);
41516
+ throw new Error(`${label} changed while it was being opened; try again: "${abs}"`);
41517
+ if (stat.size === 0) throw new Error(`${label} is empty: "${abs}"`);
41240
41518
  if (stat.size > maxBytes)
41241
- throw new Error(`Content file is ${stat.size} bytes, over the ${maxBytes}-byte limit.`);
41519
+ throw new Error(`${label} is ${stat.size} bytes, over the ${maxBytes}-byte limit.`);
41242
41520
  const bytes = readFileSync(descriptor);
41243
41521
  if (bytes.length !== stat.size)
41244
- throw new Error("Content file changed while it was being read; try again");
41245
- try {
41246
- return new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
41247
- } catch {
41248
- throw new Error(`Content file is not valid UTF-8 text: "${abs}"`);
41249
- }
41522
+ throw new Error(`${label} changed while it was being read; try again`);
41523
+ return bytes;
41250
41524
  } finally {
41251
41525
  closeSync(descriptor);
41252
41526
  }
41253
41527
  }
41528
+ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPrivate = process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1") {
41529
+ const bytes = readAllowedFile(p, maxBytes, { roots, allowPrivate });
41530
+ try {
41531
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
41532
+ } catch {
41533
+ throw new Error(`Content file is not valid UTF-8 text: "${resolve(p)}"`);
41534
+ }
41535
+ }
41254
41536
  function cleanupTempDir(dir) {
41255
41537
  try {
41256
- if (existsSync4(dir)) rmSync(dir, { recursive: true, force: true });
41538
+ if (existsSync5(dir)) rmSync(dir, { recursive: true, force: true });
41257
41539
  } catch {
41258
41540
  }
41259
41541
  }
41260
41542
 
41261
41543
  // src/utils/paperAttachments.ts
41262
- import { execFileSync as execFileSync8 } from "node:child_process";
41544
+ import { execFileSync as execFileSync9 } from "node:child_process";
41263
41545
  import {
41264
41546
  closeSync as closeSync3,
41265
41547
  constants as constants3,
@@ -41272,10 +41554,10 @@ import {
41272
41554
  unlinkSync as unlinkSync2,
41273
41555
  writeSync as writeSync2
41274
41556
  } from "node:fs";
41275
- import { basename as basename2, dirname as dirname3, extname as extname2, join as join8 } from "node:path";
41557
+ import { basename as basename2, dirname as dirname3, extname as extname2, join as join9 } from "node:path";
41276
41558
 
41277
41559
  // src/utils/attachmentAssets.ts
41278
- import { execFileSync as execFileSync7 } from "node:child_process";
41560
+ import { execFileSync as execFileSync8 } from "node:child_process";
41279
41561
  import {
41280
41562
  closeSync as closeSync2,
41281
41563
  constants as constants2,
@@ -41289,11 +41571,11 @@ import {
41289
41571
  unlinkSync,
41290
41572
  writeSync
41291
41573
  } from "node:fs";
41292
- import { homedir as homedir7 } from "node:os";
41293
- import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join7, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
41574
+ import { homedir as homedir8 } from "node:os";
41575
+ import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join8, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
41294
41576
  import { gunzipSync as gunzipSync5 } from "node:zlib";
41295
- var NOTES_CONTAINER_DIR = join7(
41296
- homedir7(),
41577
+ var NOTES_CONTAINER_DIR = join8(
41578
+ homedir8(),
41297
41579
  "Library/Group Containers/group.com.apple.notes"
41298
41580
  );
41299
41581
  var AttachmentStoreError = class extends Error {
@@ -41398,7 +41680,7 @@ function buildAttachmentRowsSql(notePk, columns) {
41398
41680
  }
41399
41681
  function runSqlite3(dbPath2, sql) {
41400
41682
  try {
41401
- return execFileSync7("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
41683
+ return execFileSync8("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
41402
41684
  encoding: "utf8",
41403
41685
  timeout: 1e4,
41404
41686
  maxBuffer: 128 * 1024 * 1024,
@@ -41466,7 +41748,7 @@ function attachmentOrderFromNoteData(hex3) {
41466
41748
  return null;
41467
41749
  }
41468
41750
  }
41469
- function readNoteAttachmentRows(noteId3, dbPath2 = join7(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
41751
+ function readNoteAttachmentRows(noteId3, dbPath2 = join8(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
41470
41752
  const { pk } = parseNoteId2(noteId3);
41471
41753
  const columnList = runSqlite3(
41472
41754
  dbPath2,
@@ -41527,7 +41809,7 @@ function boundedEntries(dir, limit) {
41527
41809
  }
41528
41810
  }
41529
41811
  function resolveAccountDir(containerDir, accountIdentifier) {
41530
- const accountsDir = join7(containerDir, "Accounts");
41812
+ const accountsDir = join8(containerDir, "Accounts");
41531
41813
  let accountsReal;
41532
41814
  try {
41533
41815
  accountsReal = realpathSync2.native(accountsDir);
@@ -41536,14 +41818,14 @@ function resolveAccountDir(containerDir, accountIdentifier) {
41536
41818
  }
41537
41819
  const account = safeComponent(accountIdentifier);
41538
41820
  if (account) {
41539
- const dir = realInside(join7(accountsReal, account), accountsReal);
41821
+ const dir = realInside(join8(accountsReal, account), accountsReal);
41540
41822
  if (dir && isDirectory(dir)) return dir;
41541
41823
  }
41542
41824
  const entries2 = (boundedEntries(accountsReal, 64) ?? []).filter(
41543
- (e) => isDirectory(join7(accountsReal, e))
41825
+ (e) => isDirectory(join8(accountsReal, e))
41544
41826
  );
41545
41827
  if (entries2.length !== 1) return null;
41546
- return realInside(join7(accountsReal, entries2[0]), accountsReal);
41828
+ return realInside(join8(accountsReal, entries2[0]), accountsReal);
41547
41829
  }
41548
41830
  function generationRank(name) {
41549
41831
  const m = /^(\d+)_/.exec(name);
@@ -41551,23 +41833,23 @@ function generationRank(name) {
41551
41833
  }
41552
41834
  function generationDirs(base, accountDir) {
41553
41835
  const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
41554
- return entries2.map((e) => realInside(join7(base, e), accountDir)).filter((p) => p !== null && isDirectory(p)).sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
41836
+ return entries2.map((e) => realInside(join8(base, e), accountDir)).filter((p) => p !== null && isDirectory(p)).sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
41555
41837
  }
41556
41838
  function fallbackFiles(accountDir, rootName, identifier, generation, names) {
41557
- const base = join7(accountDir, rootName, identifier);
41839
+ const base = join8(accountDir, rootName, identifier);
41558
41840
  const found = [];
41559
41841
  const add = (candidate) => {
41560
41842
  const real = realInside(candidate, accountDir);
41561
41843
  if (real && isRegularFile(real) && !found.includes(real)) found.push(real);
41562
41844
  };
41563
41845
  const gen = safeComponent(generation);
41564
- if (gen) for (const name of names) add(join7(base, gen, name));
41846
+ if (gen) for (const name of names) add(join8(base, gen, name));
41565
41847
  if (isDirectory(base)) {
41566
41848
  for (const dir of generationDirs(base, accountDir))
41567
- for (const name of names) add(join7(dir, name));
41568
- for (const name of names) add(join7(base, name));
41849
+ for (const name of names) add(join8(dir, name));
41850
+ for (const name of names) add(join8(base, name));
41569
41851
  }
41570
- for (const name of names) add(join7(accountDir, rootName, `${identifier}${extname(name)}`));
41852
+ for (const name of names) add(join8(accountDir, rootName, `${identifier}${extname(name)}`));
41571
41853
  return found;
41572
41854
  }
41573
41855
  function previewPixelArea(name) {
@@ -41579,7 +41861,7 @@ function previewFileInBundle(bundle, accountDir) {
41579
41861
  const direct = [];
41580
41862
  const nested = [];
41581
41863
  for (const entry of boundedEntries(bundle, MAX_BUNDLE_ENTRIES) ?? []) {
41582
- const child2 = realInside(join7(bundle, entry), accountDir);
41864
+ const child2 = realInside(join8(bundle, entry), accountDir);
41583
41865
  if (!child2) continue;
41584
41866
  if (isRegularFile(child2)) direct.push(child2);
41585
41867
  else if (isDirectory(child2)) nested.push(child2);
@@ -41587,14 +41869,14 @@ function previewFileInBundle(bundle, accountDir) {
41587
41869
  nested.sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
41588
41870
  const pick2 = (files) => files.find((f) => basename(f) === "Preview.png") ?? files.find((f) => PREVIEW_IMAGE_SUFFIXES.has(extname(f).toLowerCase())) ?? null;
41589
41871
  for (const dir of nested) {
41590
- const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(join7(dir, e), accountDir)).filter((p) => p !== null && isRegularFile(p));
41872
+ const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(join8(dir, e), accountDir)).filter((p) => p !== null && isRegularFile(p));
41591
41873
  const chosen = pick2(files);
41592
41874
  if (chosen) return chosen;
41593
41875
  }
41594
41876
  return pick2(direct);
41595
41877
  }
41596
41878
  function listPreviewEntries(accountDir) {
41597
- return boundedEntries(join7(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
41879
+ return boundedEntries(join8(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
41598
41880
  }
41599
41881
  function previewPaths(accountDir, identifier, entries2) {
41600
41882
  const id2 = safeComponent(identifier);
@@ -41606,7 +41888,7 @@ function previewPaths(accountDir, identifier, entries2) {
41606
41888
  }).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
41607
41889
  const files = [];
41608
41890
  for (const name of candidates) {
41609
- const real = realInside(join7(accountDir, "Previews", name), accountDir);
41891
+ const real = realInside(join8(accountDir, "Previews", name), accountDir);
41610
41892
  if (!real) continue;
41611
41893
  const file = isRegularFile(real) ? real : isDirectory(real) ? previewFileInBundle(real, accountDir) : null;
41612
41894
  if (file && !files.includes(file)) files.push(file);
@@ -41623,13 +41905,13 @@ function assetPathsFor(accountDir, row) {
41623
41905
  const mediaName = safeComponent(row.mediaFilename);
41624
41906
  const mediaGen = safeComponent(row.mediaGeneration);
41625
41907
  if (mediaId && mediaName) {
41626
- if (mediaGen) add(join7(accountDir, "Media", mediaId, mediaGen, mediaName));
41627
- add(join7(accountDir, "Media", mediaId, mediaName));
41908
+ if (mediaGen) add(join8(accountDir, "Media", mediaId, mediaGen, mediaName));
41909
+ add(join8(accountDir, "Media", mediaId, mediaName));
41628
41910
  }
41629
41911
  const id2 = safeComponent(row.identifier);
41630
41912
  if (!id2) return found;
41631
41913
  const ownName = safeComponent(row.filename);
41632
- if (ownName) add(join7(accountDir, "Media", id2, ownName));
41914
+ if (ownName) add(join8(accountDir, "Media", id2, ownName));
41633
41915
  for (const file of fallbackFiles(accountDir, "FallbackImages", id2, row.fallbackImageGeneration, [
41634
41916
  "FallbackImage.png",
41635
41917
  "FallbackImage.jpg"
@@ -41720,10 +42002,10 @@ function canonicalTail(p) {
41720
42002
  const tail = [];
41721
42003
  for (; ; ) {
41722
42004
  try {
41723
- return join7(realpathSync2.native(current), ...tail);
42005
+ return join8(realpathSync2.native(current), ...tail);
41724
42006
  } catch {
41725
42007
  const parent = dirname2(current);
41726
- if (parent === current) return join7(current, ...tail);
42008
+ if (parent === current) return join8(current, ...tail);
41727
42009
  tail.unshift(basename(current));
41728
42010
  current = parent;
41729
42011
  }
@@ -41806,7 +42088,7 @@ function exportOneAttachment(record2, dir, source) {
41806
42088
  if (!source) return base;
41807
42089
  const name = exportFileName(record2, source.path, source.kind);
41808
42090
  for (let attempt = 1; attempt <= MAX_COLLISION_SUFFIX; attempt++) {
41809
- const dest = join7(dir, collisionName(name, attempt));
42091
+ const dest = join8(dir, collisionName(name, attempt));
41810
42092
  try {
41811
42093
  if (dirname2(dest) !== resolve2(dir))
41812
42094
  throw new Error(`Refusing to write outside the export directory: "${dest}"`);
@@ -41935,7 +42217,7 @@ function buildDrawingRowsSql(notePk, columns) {
41935
42217
  }
41936
42218
  function runSqlite4(dbPath2, sql) {
41937
42219
  try {
41938
- return execFileSync8("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
42220
+ return execFileSync9("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
41939
42221
  encoding: "utf8",
41940
42222
  timeout: 1e4,
41941
42223
  maxBuffer: 16 * 1024 * 1024,
@@ -41975,7 +42257,7 @@ function parseDrawingRows(json2) {
41975
42257
  }
41976
42258
  return rows.sort((a, b) => a.pk - b.pk);
41977
42259
  }
41978
- function readDrawingRows(noteId3, dbPath2 = join8(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
42260
+ function readDrawingRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
41979
42261
  const { pk } = parseNoteId2(noteId3);
41980
42262
  const columns = new Set(
41981
42263
  runSqlite4(
@@ -42019,16 +42301,16 @@ var byGenerationDesc = (a, b) => generationRank(basename2(b)) - generationRank(b
42019
42301
  function findFallbackImage(accountDir, identifier, generation) {
42020
42302
  const id2 = safeComponent(identifier);
42021
42303
  if (!id2) return null;
42022
- const base = join8(accountDir, "FallbackImages", id2);
42304
+ const base = join9(accountDir, "FallbackImages", id2);
42023
42305
  const names = ["FallbackImage.png", "FallbackImage.jpg"];
42024
42306
  const candidates = [];
42025
42307
  const gen = safeComponent(generation);
42026
- if (gen) for (const n of names) candidates.push(join8(base, gen, n));
42027
- const gens = entries(base, MAX_DIR_ENTRIES).map((e) => join8(base, e)).sort(byGenerationDesc);
42028
- for (const g of gens) for (const n of names) candidates.push(join8(g, n));
42029
- for (const n of names) candidates.push(join8(base, n));
42030
- candidates.push(join8(accountDir, "FallbackImages", `${id2}.png`));
42031
- candidates.push(join8(accountDir, "FallbackImages", `${id2}.jpg`));
42308
+ if (gen) for (const n of names) candidates.push(join9(base, gen, n));
42309
+ const gens = entries(base, MAX_DIR_ENTRIES).map((e) => join9(base, e)).sort(byGenerationDesc);
42310
+ for (const g of gens) for (const n of names) candidates.push(join9(g, n));
42311
+ for (const n of names) candidates.push(join9(base, n));
42312
+ candidates.push(join9(accountDir, "FallbackImages", `${id2}.png`));
42313
+ candidates.push(join9(accountDir, "FallbackImages", `${id2}.jpg`));
42032
42314
  for (const c of candidates) {
42033
42315
  const real = realInside(c, accountDir);
42034
42316
  if (real && kindOf(real) === "file") return real;
@@ -42038,21 +42320,21 @@ function findFallbackImage(accountDir, identifier, generation) {
42038
42320
  function findLargestPreview(accountDir, identifier) {
42039
42321
  const id2 = safeComponent(identifier);
42040
42322
  if (!id2) return null;
42041
- const dir = join8(accountDir, "Previews");
42323
+ const dir = join9(accountDir, "Previews");
42042
42324
  const prefix = `${id2}-`.toLowerCase();
42043
42325
  const names = entries(dir, MAX_PREVIEW_DIR_ENTRIES2).filter((n) => n.toLowerCase().startsWith(prefix)).filter((n) => {
42044
42326
  const ext = extname2(n).toLowerCase();
42045
42327
  return !ext || IMAGE_SUFFIXES.has(ext) || /^\.\d+$/.test(ext);
42046
42328
  }).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
42047
42329
  for (const name of names) {
42048
- const real = realInside(join8(dir, name), accountDir);
42330
+ const real = realInside(join9(dir, name), accountDir);
42049
42331
  if (!real) continue;
42050
42332
  const kind = kindOf(real);
42051
42333
  if (kind === "file") return real;
42052
42334
  if (kind !== "dir") continue;
42053
- const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(join8(real, e), accountDir)).filter((p) => p !== null && kindOf(p) === "dir").sort(byGenerationDesc);
42335
+ const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(join9(real, e), accountDir)).filter((p) => p !== null && kindOf(p) === "dir").sort(byGenerationDesc);
42054
42336
  for (const g of [...gens, real]) {
42055
- const file = realInside(join8(g, "Preview.png"), accountDir);
42337
+ const file = realInside(join9(g, "Preview.png"), accountDir);
42056
42338
  if (file && kindOf(file) === "file") return file;
42057
42339
  }
42058
42340
  }
@@ -42065,7 +42347,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
42065
42347
  const id2 = safeComponent(row.identifier);
42066
42348
  const fallbackImagePath = accountDir ? findFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : null;
42067
42349
  const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
42068
- const bundle = accountDir && id2 && kind === "paper" ? realInside(join8(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
42350
+ const bundle = accountDir && id2 && kind === "paper" ? realInside(join9(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
42069
42351
  let raster = null;
42070
42352
  for (const [path10, source] of [
42071
42353
  [fallbackImagePath, "fallback"],
@@ -42262,9 +42544,9 @@ function scopeConflictMessage(reason) {
42262
42544
 
42263
42545
  // src/services/appleNotesManager.ts
42264
42546
  var import_turndown = __toESM(require_turndown_cjs(), 1);
42265
- import { existsSync as existsSync5, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
42266
- import { homedir as homedir8, tmpdir as tmpdir2 } from "os";
42267
- import { join as join9 } from "path";
42547
+ import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
42548
+ import { homedir as homedir9, tmpdir as tmpdir2 } from "os";
42549
+ import { join as join10 } from "path";
42268
42550
  var FIELD_SEP = "";
42269
42551
  var RECORD_SEP = "";
42270
42552
  var AS_FIELD_SEP = "(character id 31)";
@@ -42486,6 +42768,11 @@ function smartFolderDestinationError(folderPath) {
42486
42768
  }
42487
42769
  );
42488
42770
  }
42771
+ function smartFoldersReadError(kind, message) {
42772
+ return new CodedError(message, {
42773
+ code: kind === "unsupported_schema" ? "unsupported" : kind === "no_fda" ? "full_disk_access_missing" : "operation_failed"
42774
+ });
42775
+ }
42489
42776
  function throwIfSmartFolderDestination(error2, folderPath) {
42490
42777
  if (error2?.includes(SMART_FOLDER_DESTINATION)) throw smartFolderDestinationError(folderPath);
42491
42778
  }
@@ -42621,8 +42908,8 @@ var ExpectedBodies = class {
42621
42908
  if (body.length <= INLINE_EXPECTED_BODY_LIMIT) {
42622
42909
  return { setup: "", operand: `"${escapeHtmlForAppleScript(body)}"` };
42623
42910
  }
42624
- this.directory ??= mkdtempSync2(join9(tmpdir2(), "apple-notes-expected-body-"));
42625
- const file = join9(this.directory, `body-${this.files++}.html`);
42911
+ this.directory ??= mkdtempSync2(join10(tmpdir2(), "apple-notes-expected-body-"));
42912
+ const file = join10(this.directory, `body-${this.files++}.html`);
42626
42913
  writeFileSync(file, body, { encoding: "utf8", mode: 384 });
42627
42914
  return {
42628
42915
  setup: `
@@ -42668,8 +42955,8 @@ function getNoteLinkFromDB(coreDataId3) {
42668
42955
  const match = coreDataId3.match(/\/p(\d+)$/);
42669
42956
  if (!match) return null;
42670
42957
  const pk = parseInt(match[1], 10);
42671
- const dbPath2 = join9(homedir8(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
42672
- if (!existsSync5(dbPath2)) return null;
42958
+ const dbPath2 = join10(homedir9(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
42959
+ if (!existsSync6(dbPath2)) return null;
42673
42960
  try {
42674
42961
  const { DatabaseSync } = __require("node:sqlite");
42675
42962
  const db = new DatabaseSync(dbPath2, { readOnly: true });
@@ -43303,9 +43590,12 @@ var AppleNotesManager = class {
43303
43590
  if currentBody is not ${expected.operand} and currentBody is not ${expected.operand} & linefeed then return "SAFETY_CONFLICT"
43304
43591
  delete noteRef
43305
43592
  end considering
43593
+ set __stillThere to missing value
43306
43594
  try
43307
- if (id of notes of originalFolder) contains "${safeId}" then return "SAFETY_NOT_DELETED"
43595
+ set __stillThere to (id of notes of originalFolder) contains "${safeId}"
43308
43596
  end try
43597
+ if __stillThere is missing value then return "SAFETY_DELETE_UNVERIFIED"
43598
+ if __stillThere then return "SAFETY_NOT_DELETED"
43309
43599
  return "SAFETY_DELETED"
43310
43600
  `);
43311
43601
  result = executeMutationAppleScript(script);
@@ -43328,6 +43618,7 @@ var AppleNotesManager = class {
43328
43618
  if (status === "SAFETY_IN_RECENTLY_DELETED") return { status: "in-recently-deleted" };
43329
43619
  if (status === "SAFETY_CONTAINER_UNKNOWN") return { status: "container-unknown" };
43330
43620
  if (status === "SAFETY_NOT_DELETED") return { status: "not-deleted" };
43621
+ if (status === "SAFETY_DELETE_UNVERIFIED") return { status: "unverified" };
43331
43622
  return status === "SAFETY_DELETED" ? { status: "deleted" } : { status: "failed" };
43332
43623
  }
43333
43624
  /**
@@ -43475,7 +43766,8 @@ var AppleNotesManager = class {
43475
43766
  */
43476
43767
  listSmartFolders(options = {}) {
43477
43768
  const result = readSmartFolders();
43478
- if (!result.folders) throw new Error(result.message || "Failed to read smart folders.");
43769
+ if (!result.folders)
43770
+ throw smartFoldersReadError(result.error, result.message || "Failed to read smart folders.");
43479
43771
  if (!options.includeMatchingNotes) return result.folders;
43480
43772
  const limit = options.limit ?? 50;
43481
43773
  return result.folders.map((folder) => {
@@ -44863,7 +45155,7 @@ var AppleNotesManager = class {
44863
45155
  if (parts[0] !== "OK") {
44864
45156
  return { success: false, error: parts[1]?.trim() || "attachment not found" };
44865
45157
  }
44866
- if (!existsSync5(abs) || fileSize(abs) === 0) {
45158
+ if (!existsSync6(abs) || fileSize(abs) === 0) {
44867
45159
  return { success: false, error: `Notes reported success but no file was written to ${abs}` };
44868
45160
  }
44869
45161
  return {
@@ -45371,15 +45663,15 @@ var AppleNotesManager = class {
45371
45663
  };
45372
45664
 
45373
45665
  // src/utils/syncDetection.ts
45374
- import { execFileSync as execFileSync9 } from "child_process";
45375
- import * as fs4 from "fs";
45376
- import * as path4 from "path";
45377
- import * as os4 from "os";
45378
- var NOTES_DB_PATH5 = path4.join(
45379
- os4.homedir(),
45666
+ import { execFileSync as execFileSync10 } from "child_process";
45667
+ import * as fs5 from "fs";
45668
+ import * as path5 from "path";
45669
+ import * as os5 from "os";
45670
+ var NOTES_DB_PATH6 = path5.join(
45671
+ os5.homedir(),
45380
45672
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
45381
45673
  );
45382
- var WAL_PATH = `${NOTES_DB_PATH5}-wal`;
45674
+ var WAL_PATH = `${NOTES_DB_PATH6}-wal`;
45383
45675
  var RECENT_ACTIVITY_THRESHOLD_SECONDS = 5;
45384
45676
  var SYNC_STATUS_CACHE_TTL_MS = 2e3;
45385
45677
  var cachedSyncStatus = null;
@@ -45395,14 +45687,14 @@ function getSyncStatus(useCache = true) {
45395
45687
  recentActivity: false
45396
45688
  };
45397
45689
  try {
45398
- if (!fs4.existsSync(NOTES_DB_PATH5)) {
45690
+ if (!fs5.existsSync(NOTES_DB_PATH6)) {
45399
45691
  status.error = "Notes database not found";
45400
45692
  cachedSyncStatus = status;
45401
45693
  cacheTimestamp = Date.now();
45402
45694
  return status;
45403
45695
  }
45404
- if (fs4.existsSync(WAL_PATH)) {
45405
- const walStats = fs4.statSync(WAL_PATH);
45696
+ if (fs5.existsSync(WAL_PATH)) {
45697
+ const walStats = fs5.statSync(WAL_PATH);
45406
45698
  const secondsAgo = (Date.now() - walStats.mtimeMs) / 1e3;
45407
45699
  status.secondsSinceLastChange = Math.round(secondsAgo);
45408
45700
  status.recentActivity = secondsAgo < RECENT_ACTIVITY_THRESHOLD_SECONDS;
@@ -45416,9 +45708,9 @@ function getSyncStatus(useCache = true) {
45416
45708
  WHERE object.ZCLOUDSTATE = state.Z_PK
45417
45709
  );
45418
45710
  `;
45419
- const result = execFileSync9(
45711
+ const result = execFileSync10(
45420
45712
  "sqlite3",
45421
- ["-readonly", NOTES_DB_PATH5, query2.replace(/\n/g, " ")],
45713
+ ["-readonly", NOTES_DB_PATH6, query2.replace(/\n/g, " ")],
45422
45714
  {
45423
45715
  encoding: "utf8",
45424
45716
  timeout: 5e3,
@@ -45475,12 +45767,12 @@ function withSyncAwarenessSync(operation, fn) {
45475
45767
  }
45476
45768
 
45477
45769
  // src/utils/noteMetadata.ts
45478
- import { execFileSync as execFileSync10 } from "child_process";
45479
- import * as fs5 from "fs";
45480
- import * as path5 from "path";
45481
- import * as os5 from "os";
45482
- var NOTES_DB_PATH6 = path5.join(
45483
- os5.homedir(),
45770
+ import { execFileSync as execFileSync11 } from "child_process";
45771
+ import * as fs6 from "fs";
45772
+ import * as path6 from "path";
45773
+ import * as os6 from "os";
45774
+ var NOTES_DB_PATH7 = path6.join(
45775
+ os6.homedir(),
45484
45776
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
45485
45777
  );
45486
45778
  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.`;
@@ -45496,7 +45788,7 @@ var COLUMN_MAP = [
45496
45788
  { key: "smartFolderQuery", column: "ZSMARTFOLDERQUERYJSON", type: "text" }
45497
45789
  ];
45498
45790
  function runSqlite5(query2) {
45499
- return execFileSync10("sqlite3", ["-readonly", NOTES_DB_PATH6, query2], {
45791
+ return execFileSync11("sqlite3", ["-readonly", NOTES_DB_PATH7, query2], {
45500
45792
  encoding: "utf8",
45501
45793
  timeout: 5e3,
45502
45794
  stdio: ["pipe", "pipe", "pipe"]
@@ -45521,7 +45813,7 @@ function getNoteMetadata(noteId3) {
45521
45813
  };
45522
45814
  }
45523
45815
  const pk = pkMatch[1];
45524
- if (!fs5.existsSync(NOTES_DB_PATH6)) {
45816
+ if (!fs6.existsSync(NOTES_DB_PATH7)) {
45525
45817
  return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
45526
45818
  }
45527
45819
  try {
@@ -45567,12 +45859,12 @@ function getNoteMetadata(noteId3) {
45567
45859
  import { gunzipSync as gunzipSync6 } from "zlib";
45568
45860
 
45569
45861
  // src/utils/noteStoreSql.ts
45570
- import { execFileSync as execFileSync11 } from "child_process";
45571
- import * as fs6 from "fs";
45572
- import * as os6 from "os";
45573
- import * as path6 from "path";
45574
- var NOTES_DB_PATH7 = path6.join(
45575
- os6.homedir(),
45862
+ import { execFileSync as execFileSync12 } from "child_process";
45863
+ import * as fs7 from "fs";
45864
+ import * as os7 from "os";
45865
+ import * as path7 from "path";
45866
+ var NOTES_DB_PATH8 = path7.join(
45867
+ os7.homedir(),
45576
45868
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
45577
45869
  );
45578
45870
  var CORE_DATA_EPOCH_MS = Date.UTC(2001, 0, 1);
@@ -45610,7 +45902,7 @@ function renderValue(value) {
45610
45902
  return `ieee754_from_blob(x'${doubleToHex(double)}')`;
45611
45903
  }
45612
45904
  function runReadOnlySql(dbPath2, sql, params = {}) {
45613
- if (!fs6.existsSync(dbPath2)) throw new NoteStoreError(STORE_FDA_MESSAGE, "no_fda");
45905
+ if (!fs7.existsSync(dbPath2)) throw new NoteStoreError(STORE_FDA_MESSAGE, "no_fda");
45614
45906
  const args = ["-readonly"];
45615
45907
  const names = Object.keys(params);
45616
45908
  if (names.length) args.push("-cmd", ".parameter init");
@@ -45622,7 +45914,7 @@ function runReadOnlySql(dbPath2, sql, params = {}) {
45622
45914
  }
45623
45915
  args.push(dbPath2, sql);
45624
45916
  try {
45625
- return execFileSync11("sqlite3", args, {
45917
+ return execFileSync12("sqlite3", args, {
45626
45918
  encoding: "utf8",
45627
45919
  timeout: 3e4,
45628
45920
  maxBuffer: 512 * 1024 * 1024,
@@ -45815,7 +46107,7 @@ function buildSpecialNotesSql(columns, kind, scoped) {
45815
46107
  ].join(" ");
45816
46108
  }
45817
46109
  function listSpecialNotes(options) {
45818
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
46110
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
45819
46111
  const limit = Math.min(
45820
46112
  Math.max(1, Math.trunc(options.limit ?? SPECIAL_LIMIT.DEFAULT)),
45821
46113
  SPECIAL_LIMIT.MAX
@@ -45871,7 +46163,7 @@ function toSpecialRow(row, uuid2, paths, accountNames, kind) {
45871
46163
  return note;
45872
46164
  }
45873
46165
  var EXACT_NOTE_ID = /^x-coredata:\/\/([0-9A-F-]+)\/ICNote\/p(\d+)$/i;
45874
- function quickNoteFlag(noteId3, dbPath2 = NOTES_DB_PATH7) {
46166
+ function quickNoteFlag(noteId3, dbPath2 = NOTES_DB_PATH8) {
45875
46167
  const match = EXACT_NOTE_ID.exec(noteId3);
45876
46168
  if (!match) throw new NoteStoreError(`Not an exact note id: ${noteId3}`, "invalid_input");
45877
46169
  const columns = readColumns(dbPath2);
@@ -45923,7 +46215,7 @@ function referencedObjects(hex3) {
45923
46215
  }
45924
46216
  }
45925
46217
  function nativeTagInventory(options = {}) {
45926
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
46218
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
45927
46219
  const columns = readColumns(dbPath2);
45928
46220
  const context = readStoreContext(dbPath2, columns);
45929
46221
  const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
@@ -45985,15 +46277,27 @@ function assembleInventory(rows, accounts, scope2) {
45985
46277
  }
45986
46278
 
45987
46279
  // src/utils/noteBlocks.ts
45988
- import { execFileSync as execFileSync12 } from "node:child_process";
45989
- import { existsSync as existsSync9 } from "node:fs";
45990
- import { homedir as homedir12 } from "node:os";
45991
- import { join as join13 } from "node:path";
46280
+ import { execFileSync as execFileSync13 } from "node:child_process";
46281
+ import { existsSync as existsSync10 } from "node:fs";
46282
+ import { homedir as homedir13 } from "node:os";
46283
+ import { join as join14 } from "node:path";
45992
46284
  import { gunzipSync as gunzipSync7 } from "node:zlib";
45993
- var NoteBlocksError = class extends Error {
46285
+ var NOTE_BLOCKS_ERROR_CODES = {
46286
+ "invalid-id": "validation_error",
46287
+ "no-full-disk-access": "full_disk_access_missing",
46288
+ "not-found": "not_found",
46289
+ "no-body": "operation_failed",
46290
+ encrypted: "unsupported",
46291
+ "decompress-failed": "unsupported",
46292
+ "malformed-protobuf": "unsupported",
46293
+ "unsupported-structure": "unsupported",
46294
+ "invalid-runs": "unsupported",
46295
+ "query-failed": "operation_failed"
46296
+ };
46297
+ var NoteBlocksError = class extends CodedError {
45994
46298
  code;
45995
46299
  constructor(code, message) {
45996
- super(message);
46300
+ super(message, { code: NOTE_BLOCKS_ERROR_CODES[code] });
45997
46301
  this.name = "NoteBlocksError";
45998
46302
  this.code = code;
45999
46303
  }
@@ -46331,23 +46635,23 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
46331
46635
  undecodedFields: doc.undecodedFields
46332
46636
  };
46333
46637
  }
46334
- var NOTES_DB_PATH8 = join13(
46335
- homedir12(),
46638
+ var NOTES_DB_PATH9 = join14(
46639
+ homedir13(),
46336
46640
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
46337
46641
  );
46338
- function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
46642
+ function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
46339
46643
  const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]{1,18})$/i.exec(id2)?.[1];
46340
46644
  if (!pk)
46341
46645
  throw new NoteBlocksError(
46342
46646
  "invalid-id",
46343
46647
  `Invalid note ID: expected x-coredata://<store>/ICNote/p<number>`
46344
46648
  );
46345
- if (!existsSync9(dbPath2))
46649
+ if (!existsSync10(dbPath2))
46346
46650
  throw new NoteBlocksError("no-full-disk-access", "The Notes database is not readable");
46347
46651
  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));";
46348
46652
  let output;
46349
46653
  try {
46350
- output = execFileSync12(
46654
+ output = execFileSync13(
46351
46655
  "/usr/bin/sqlite3",
46352
46656
  ["-readonly", "-cmd", ".parameter init", "-cmd", `.parameter set @pk ${pk}`, dbPath2, sql],
46353
46657
  {
@@ -46494,7 +46798,7 @@ function resolveFolderPath(folders, paths, input, accountPk) {
46494
46798
  );
46495
46799
  }
46496
46800
  function listRecentNotes(options = {}) {
46497
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
46801
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
46498
46802
  const limit = Math.min(
46499
46803
  Math.max(1, Math.trunc(options.limit ?? RECENT_LIMIT.DEFAULT)),
46500
46804
  RECENT_LIMIT.MAX
@@ -46539,356 +46843,118 @@ function listRecentNotes(options = {}) {
46539
46843
  ...scope2 ? { account: scope2.name } : {},
46540
46844
  ...folder ? { folder: paths.get(folder.pk) } : {}
46541
46845
  };
46542
- }
46543
- function toRecentRow(row, context, paths, accountNames, options) {
46544
- const note = {
46545
- id: noteIdFor(context.uuid, row.pk),
46546
- identifier: row.identifier ?? null,
46547
- title: row.title ?? null,
46548
- folder: row.folder !== null ? paths.get(row.folder) ?? null : null,
46549
- account: row.account !== null ? accountNames.get(row.account) ?? null : null,
46550
- created: coreDataToIso(row.created),
46551
- modified: coreDataToIso(row.modified),
46552
- modifiedCheckpoint: checkpointFromBits(row.bits, row.pk),
46553
- pinned: Boolean(row.pinned),
46554
- locked: Boolean(row.locked),
46555
- inRecentlyDeleted: Boolean(row.trash),
46556
- markedForDeletion: Boolean(row.tombstoned)
46557
- };
46558
- const text2 = row.locked || row.cloud ? null : decodeBodyText(row.data);
46559
- if (options.wordCounts) {
46560
- const stats = text2 === null ? null : textStats(text2);
46561
- note.wordCount = stats?.wordCount ?? null;
46562
- note.charCount = stats?.charCount ?? null;
46563
- }
46564
- if (options.bodyPreview) {
46565
- note.textDecoded = text2 !== null;
46566
- note.bodyPreview = text2 !== null ? previewText(text2) : row.snippet ? previewText(row.snippet) : null;
46567
- }
46568
- return note;
46569
- }
46570
- function buildFolderCountsSql(columns) {
46571
- requireColumns(columns, ["Z_PK", "Z_ENT", "ZFOLDER"], "list-folder-tree");
46572
- 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;`;
46573
- }
46574
- var KIND_ORDER = { folder: 0, smart: 1, trash: 2 };
46575
- function compareNodes(a, b) {
46576
- return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
46577
- }
46578
- function folderTree(options = {}) {
46579
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
46580
- const columns = readColumns(dbPath2);
46581
- const context = readStoreContext(dbPath2, columns);
46582
- const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
46583
- const counts = new Map(
46584
- parseJsonLines(
46585
- runReadOnlySql(dbPath2, buildFolderCountsSql(columns))
46586
- ).map((row) => [row.folder, row.n])
46587
- );
46588
- return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
46589
- }
46590
- function assembleFolderTree(context, counts, includeDeleted, scope2) {
46591
- const paths = folderPaths(context.folders);
46592
- const visible2 = context.folders.filter(
46593
- (folder) => (includeDeleted || !folder.tombstoned) && (!scope2 || folder.account === scope2.pk)
46594
- );
46595
- const byPk = new Map(visible2.map((folder) => [folder.pk, folder]));
46596
- const nodes = /* @__PURE__ */ new Map();
46597
- for (const folder of visible2) {
46598
- const node = {
46599
- id: `x-coredata://${context.uuid}/ICFolder/p${folder.pk}`,
46600
- identifier: folder.identifier,
46601
- name: folder.name ?? "",
46602
- path: paths.get(folder.pk) ?? "",
46603
- kind: folder.trash ? "trash" : folder.folderType === 2 ? "smart" : "folder",
46604
- noteCount: counts.get(folder.pk) ?? 0,
46605
- totalNoteCount: 0,
46606
- children: []
46607
- };
46608
- if (includeDeleted) node.markedForDeletion = Boolean(folder.tombstoned);
46609
- nodes.set(folder.pk, node);
46610
- }
46611
- const parentOf = (folder) => {
46612
- if (folder.parent === null || !byPk.has(folder.parent)) return void 0;
46613
- const seen = /* @__PURE__ */ new Set([folder.pk]);
46614
- for (let at = byPk.get(folder.parent); at; at = at.parent !== null ? byPk.get(at.parent) : void 0) {
46615
- if (seen.has(at.pk)) return void 0;
46616
- seen.add(at.pk);
46617
- }
46618
- return folder.parent;
46619
- };
46620
- const roots = /* @__PURE__ */ new Map();
46621
- for (const folder of visible2) {
46622
- const node = nodes.get(folder.pk);
46623
- const parent = parentOf(folder);
46624
- if (parent !== void 0) nodes.get(parent).children.push(node);
46625
- else {
46626
- const key = folder.account !== null && context.accounts.some((a) => a.pk === folder.account) ? folder.account : null;
46627
- roots.set(key, [...roots.get(key) ?? [], node]);
46628
- }
46629
- }
46630
- const finish = (node) => {
46631
- node.children.sort(compareNodes);
46632
- node.totalNoteCount = node.noteCount + node.children.reduce((sum, c) => sum + finish(c), 0);
46633
- return node.totalNoteCount;
46634
- };
46635
- const accounts = [];
46636
- const addAccount = (name, identifier, folders) => {
46637
- folders.sort(compareNodes);
46638
- let noteCount = 0;
46639
- for (const node of folders) {
46640
- const total = finish(node);
46641
- if (node.kind === "folder") noteCount += total;
46642
- }
46643
- accounts.push({ account: name, identifier, noteCount, folders });
46644
- };
46645
- for (const account of scope2 ? [scope2] : context.accounts) {
46646
- addAccount(account.name, account.identifier, roots.get(account.pk) ?? []);
46647
- }
46648
- const orphans = roots.get(null);
46649
- if (orphans && includeDeleted) addAccount("unknown", null, orphans);
46650
- return { accounts, folderCount: accounts.reduce((sum, a) => sum + countNodes(a.folders), 0) };
46651
- }
46652
- function countNodes(nodes) {
46653
- return nodes.reduce((sum, node) => sum + 1 + countNodes(node.children), 0);
46654
- }
46655
-
46656
- // src/utils/noteIdentifiers.ts
46657
- import { execFileSync as execFileSync13 } from "child_process";
46658
- import * as fs7 from "fs";
46659
- import * as os7 from "os";
46660
- import * as path7 from "path";
46661
- var NOTES_DB_PATH9 = path7.join(
46662
- os7.homedir(),
46663
- "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
46664
- );
46665
- var UUID_PATTERN = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;
46666
- var NUMERIC_KEY_PATTERN = /^\d{1,18}$/;
46667
- var NOTE_ID_MESSAGE = "A canonical Apple Note ID is required (x-coredata://.../ICNote/p...), or the note's Notes UUID or numeric key";
46668
- function identifierForm(value) {
46669
- if (UUID_PATTERN.test(value)) return "uuid";
46670
- if (NUMERIC_KEY_PATTERN.test(value)) return "key";
46671
- return "other";
46672
- }
46673
- function isAlternateIdentifier(value) {
46674
- return identifierForm(value) !== "other";
46675
- }
46676
- var IdentifierResolutionError = class extends Error {
46677
- constructor(code, message) {
46678
- super(message);
46679
- this.code = code;
46680
- this.name = "IdentifierResolutionError";
46681
- }
46682
- code;
46683
- };
46684
- var ENTITY_LABEL = {
46685
- ICNote: "note",
46686
- ICFolder: "folder",
46687
- ICAccount: "account"
46688
- };
46689
- 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}`;
46690
- function canonicalKey(value) {
46691
- return BigInt(value).toString();
46692
- }
46693
- function assertAll(values, pattern2, label) {
46694
- for (const value of values) {
46695
- if (!pattern2.test(value)) throw new Error(`Refusing to query with an invalid ${label}`);
46696
- }
46697
- }
46698
- function entityClause(entity3) {
46699
- return `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = '${entity3}')`;
46700
- }
46701
- function buildResolveSql(entity3, keys, uuids) {
46702
- assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
46703
- assertAll(uuids, UUID_PATTERN, "UUID");
46704
- const matches = [];
46705
- if (keys.length > 0) {
46706
- matches.push(`o.Z_PK IN (${[...new Set(keys.map(canonicalKey))].join(", ")})`);
46707
- }
46708
- if (uuids.length > 0) {
46709
- const variants = /* @__PURE__ */ new Set();
46710
- for (const uuid2 of uuids) {
46711
- variants.add(uuid2);
46712
- variants.add(uuid2.toUpperCase());
46713
- variants.add(uuid2.toLowerCase());
46714
- }
46715
- matches.push(`o.ZIDENTIFIER IN (${[...variants].map((v) => `'${v}'`).join(", ")})`);
46716
- }
46717
- const where = matches.length > 0 ? matches.join(" OR ") : "0";
46718
- 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})));`;
46719
- }
46720
- function buildLookupSql(entity3, keys) {
46721
- assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
46722
- const pks = [...new Set(keys.map(canonicalKey))];
46723
- const inList = pks.length > 0 ? pks.join(", ") : "NULL";
46724
- let fields = "'pk', o.Z_PK, 'identifier', o.ZIDENTIFIER";
46725
- let joins = "";
46726
- if (entity3 === "ICNote") {
46727
- fields += ", 'folderIdentifier', f.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
46728
- joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT f ON f.Z_PK = o.ZFOLDER LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = f.ZOWNER ";
46729
- } else if (entity3 === "ICFolder") {
46730
- fields += ", 'parentIdentifier', p.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
46731
- joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = o.ZPARENT LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = o.ZOWNER ";
46732
- }
46733
- 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})));`;
46734
- }
46735
- function runJsonQuery(sql, dbPath2) {
46736
- if (!fs7.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
46737
- let out;
46738
- try {
46739
- out = execFileSync13("sqlite3", ["-readonly", dbPath2, sql], {
46740
- encoding: "utf8",
46741
- timeout: 5e3,
46742
- stdio: ["pipe", "pipe", "pipe"]
46743
- }).trim();
46744
- } catch (error2) {
46745
- const detail = error2 instanceof Error ? `${error2.message} ${String(error2.stderr ?? "")}` : String(error2);
46746
- if (detail.includes("authorization denied") || detail.includes("unable to open database")) {
46747
- throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
46748
- }
46749
- console.error(`Identifier query failed: ${detail}`);
46750
- throw new IdentifierResolutionError("query_error", "Failed to read the Notes database.");
46751
- }
46752
- const parsed = JSON.parse(out || "{}");
46753
- const rows = typeof parsed.rows === "string" ? JSON.parse(parsed.rows) : parsed.rows;
46754
- return { store: parsed.store ?? null, rows: rows ?? [] };
46755
- }
46756
- function coreDataId(store, entity3, pk) {
46757
- return `x-coredata://${store}/${entity3}/p${pk}`;
46758
- }
46759
- function resolveIdentifiers(values, entity3, dbPath2 = NOTES_DB_PATH9) {
46760
- const resolved = /* @__PURE__ */ new Map();
46761
- const keys = [];
46762
- const uuids = [];
46763
- for (const value of values) {
46764
- const form = identifierForm(value);
46765
- if (form === "key") keys.push(value);
46766
- else if (form === "uuid") uuids.push(value);
46767
- else resolved.set(value, value);
46768
- }
46769
- if (keys.length === 0 && uuids.length === 0) return resolved;
46770
- const { store, rows } = runJsonQuery(
46771
- buildResolveSql(entity3, keys, uuids),
46772
- dbPath2
46773
- );
46774
- if (!store) {
46775
- throw new IdentifierResolutionError(
46776
- "query_error",
46777
- "The Notes database has no store UUID, so an x-coredata id cannot be built."
46778
- );
46779
- }
46780
- const byKey = new Map(rows.map((row) => [String(row.pk), row]));
46781
- const byUuid = new Map(
46782
- rows.filter((row) => row.identifier).map((row) => [String(row.identifier).toUpperCase(), row])
46783
- );
46784
- const label = ENTITY_LABEL[entity3];
46785
- const missing = [];
46786
- for (const key of keys) {
46787
- const row = byKey.get(canonicalKey(key));
46788
- if (row) resolved.set(key, coreDataId(store, entity3, row.pk));
46789
- else missing.push(`numeric key ${key}`);
46790
- }
46791
- for (const uuid2 of uuids) {
46792
- const row = byUuid.get(uuid2.toUpperCase());
46793
- if (row) resolved.set(uuid2, coreDataId(store, entity3, row.pk));
46794
- else missing.push(`identifier ${uuid2}`);
46795
- }
46796
- if (missing.length > 0) {
46797
- throw new IdentifierResolutionError(
46798
- "not_found",
46799
- `No ${label} found for ${missing.join(", ")}. A numeric key must belong to a ${label}, not another object type.`
46800
- );
46801
- }
46802
- return resolved;
46803
- }
46804
- var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
46805
- function lookupStableIdentifiers(ids, entity3, dbPath2 = NOTES_DB_PATH9) {
46806
- const result = /* @__PURE__ */ new Map();
46807
- const wanted = /* @__PURE__ */ new Map();
46808
- for (const id2 of ids) {
46809
- const match = COREDATA_PARTS.exec(id2);
46810
- if (!match || match[2] !== entity3) continue;
46811
- const pk = canonicalKey(match[3]);
46812
- const list = wanted.get(pk) ?? [];
46813
- list.push({ id: id2, store: match[1] });
46814
- wanted.set(pk, list);
46815
- }
46816
- if (wanted.size === 0) return result;
46817
- let query2;
46818
- try {
46819
- query2 = runJsonQuery(buildLookupSql(entity3, [...wanted.keys()]), dbPath2);
46820
- } catch {
46821
- return result;
46846
+ }
46847
+ function toRecentRow(row, context, paths, accountNames, options) {
46848
+ const note = {
46849
+ id: noteIdFor(context.uuid, row.pk),
46850
+ identifier: row.identifier ?? null,
46851
+ title: row.title ?? null,
46852
+ folder: row.folder !== null ? paths.get(row.folder) ?? null : null,
46853
+ account: row.account !== null ? accountNames.get(row.account) ?? null : null,
46854
+ created: coreDataToIso(row.created),
46855
+ modified: coreDataToIso(row.modified),
46856
+ modifiedCheckpoint: checkpointFromBits(row.bits, row.pk),
46857
+ pinned: Boolean(row.pinned),
46858
+ locked: Boolean(row.locked),
46859
+ inRecentlyDeleted: Boolean(row.trash),
46860
+ markedForDeletion: Boolean(row.tombstoned)
46861
+ };
46862
+ const text2 = row.locked || row.cloud ? null : decodeBodyText(row.data);
46863
+ if (options.wordCounts) {
46864
+ const stats = text2 === null ? null : textStats(text2);
46865
+ note.wordCount = stats?.wordCount ?? null;
46866
+ note.charCount = stats?.charCount ?? null;
46822
46867
  }
46823
- const store = query2.store?.toUpperCase();
46824
- for (const row of query2.rows) {
46825
- const fields = {};
46826
- for (const key of [
46827
- "identifier",
46828
- "folderIdentifier",
46829
- "parentIdentifier",
46830
- "accountIdentifier"
46831
- ]) {
46832
- const value = row[key];
46833
- if (typeof value === "string" && value) fields[key] = value;
46834
- }
46835
- for (const target of wanted.get(String(row.pk)) ?? []) {
46836
- if (target.store.toUpperCase() === store) result.set(target.id, fields);
46837
- }
46868
+ if (options.bodyPreview) {
46869
+ note.textDecoded = text2 !== null;
46870
+ note.bodyPreview = text2 !== null ? previewText(text2) : row.snippet ? previewText(row.snippet) : null;
46838
46871
  }
46839
- return result;
46872
+ return note;
46840
46873
  }
46841
- function withStableIdentifiers(items, entity3, dbPath2 = NOTES_DB_PATH9) {
46842
- const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
46843
- if (ids.length === 0) return items;
46844
- const found = lookupStableIdentifiers(ids, entity3, dbPath2);
46845
- if (found.size === 0) return items;
46846
- return items.map(
46847
- (item) => item.id && found.has(item.id) ? { ...item, ...found.get(item.id) } : item
46848
- );
46874
+ function buildFolderCountsSql(columns) {
46875
+ requireColumns(columns, ["Z_PK", "Z_ENT", "ZFOLDER"], "list-folder-tree");
46876
+ 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;`;
46849
46877
  }
46850
- var defaultResolver = (values, entity3) => resolveIdentifiers(values, entity3);
46851
- var DEFAULT_MAX_ID_LENGTH = 2e3;
46852
- function acceptAlternateForms(pattern2) {
46853
- return new RegExp(
46854
- `${pattern2.source}|${UUID_PATTERN.source}|${NUMERIC_KEY_PATTERN.source}`,
46855
- pattern2.flags
46878
+ var KIND_ORDER = { folder: 0, smart: 1, trash: 2 };
46879
+ function compareNodes(a, b) {
46880
+ return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
46881
+ }
46882
+ function folderTree(options = {}) {
46883
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
46884
+ const columns = readColumns(dbPath2);
46885
+ const context = readStoreContext(dbPath2, columns);
46886
+ const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
46887
+ const counts = new Map(
46888
+ parseJsonLines(
46889
+ runReadOnlySql(dbPath2, buildFolderCountsSql(columns))
46890
+ ).map((row) => [row.folder, row.n])
46856
46891
  );
46892
+ return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
46857
46893
  }
46858
- function resolveInSchema(values, entity3, ctx, resolver) {
46859
- if (!values.some(isAlternateIdentifier)) return new Map(values.map((v) => [v, v]));
46860
- try {
46861
- return resolver(values, entity3);
46862
- } catch (error2) {
46863
- ctx.addIssue({
46864
- code: external_exports.ZodIssueCode.custom,
46865
- message: error2 instanceof Error ? error2.message : String(error2)
46866
- });
46867
- return null;
46894
+ function assembleFolderTree(context, counts, includeDeleted, scope2) {
46895
+ const paths = folderPaths(context.folders);
46896
+ const visible2 = context.folders.filter(
46897
+ (folder) => (includeDeleted || !folder.tombstoned) && (!scope2 || folder.account === scope2.pk)
46898
+ );
46899
+ const byPk = new Map(visible2.map((folder) => [folder.pk, folder]));
46900
+ const nodes = /* @__PURE__ */ new Map();
46901
+ for (const folder of visible2) {
46902
+ const node = {
46903
+ id: `x-coredata://${context.uuid}/ICFolder/p${folder.pk}`,
46904
+ identifier: folder.identifier,
46905
+ name: folder.name ?? "",
46906
+ path: paths.get(folder.pk) ?? "",
46907
+ kind: folder.trash ? "trash" : folder.folderType === 2 ? "smart" : "folder",
46908
+ noteCount: counts.get(folder.pk) ?? 0,
46909
+ totalNoteCount: 0,
46910
+ children: []
46911
+ };
46912
+ if (includeDeleted) node.markedForDeletion = Boolean(folder.tombstoned);
46913
+ nodes.set(folder.pk, node);
46868
46914
  }
46869
- }
46870
- function exactIdInput(entity3, pattern2, message, options = {}) {
46871
- const resolver = options.resolver ?? defaultResolver;
46872
- return external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern2), message).transform((value, ctx) => {
46873
- const map = resolveInSchema([value], entity3, ctx, resolver);
46874
- return map ? map.get(value) ?? value : external_exports.NEVER;
46875
- });
46876
- }
46877
- function exactIdArrayInput(entity3, pattern2, message, options = {}) {
46878
- const resolver = options.resolver ?? defaultResolver;
46879
- const item = external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern2), message);
46880
- const array2 = options.maxItems === void 0 ? external_exports.array(item) : external_exports.array(item).max(options.maxItems);
46881
- return array2.transform((values, ctx) => {
46882
- const map = resolveInSchema(values, entity3, ctx, resolver);
46883
- return map ? values.map((value) => map.get(value) ?? value) : external_exports.NEVER;
46884
- });
46885
- }
46886
- function looseIdTransform(entity3, resolver = defaultResolver) {
46887
- return (value, ctx) => {
46888
- if (!isAlternateIdentifier(value)) return value;
46889
- const map = resolveInSchema([value], entity3, ctx, resolver);
46890
- return map ? map.get(value) ?? value : external_exports.NEVER;
46915
+ const parentOf = (folder) => {
46916
+ if (folder.parent === null || !byPk.has(folder.parent)) return void 0;
46917
+ const seen = /* @__PURE__ */ new Set([folder.pk]);
46918
+ for (let at = byPk.get(folder.parent); at; at = at.parent !== null ? byPk.get(at.parent) : void 0) {
46919
+ if (seen.has(at.pk)) return void 0;
46920
+ seen.add(at.pk);
46921
+ }
46922
+ return folder.parent;
46923
+ };
46924
+ const roots = /* @__PURE__ */ new Map();
46925
+ for (const folder of visible2) {
46926
+ const node = nodes.get(folder.pk);
46927
+ const parent = parentOf(folder);
46928
+ if (parent !== void 0) nodes.get(parent).children.push(node);
46929
+ else {
46930
+ const key = folder.account !== null && context.accounts.some((a) => a.pk === folder.account) ? folder.account : null;
46931
+ roots.set(key, [...roots.get(key) ?? [], node]);
46932
+ }
46933
+ }
46934
+ const finish = (node) => {
46935
+ node.children.sort(compareNodes);
46936
+ node.totalNoteCount = node.noteCount + node.children.reduce((sum, c) => sum + finish(c), 0);
46937
+ return node.totalNoteCount;
46938
+ };
46939
+ const accounts = [];
46940
+ const addAccount = (name, identifier, folders) => {
46941
+ folders.sort(compareNodes);
46942
+ let noteCount = 0;
46943
+ for (const node of folders) {
46944
+ const total = finish(node);
46945
+ if (node.kind === "folder") noteCount += total;
46946
+ }
46947
+ accounts.push({ account: name, identifier, noteCount, folders });
46891
46948
  };
46949
+ for (const account of scope2 ? [scope2] : context.accounts) {
46950
+ addAccount(account.name, account.identifier, roots.get(account.pk) ?? []);
46951
+ }
46952
+ const orphans = roots.get(null);
46953
+ if (orphans && includeDeleted) addAccount("unknown", null, orphans);
46954
+ return { accounts, folderCount: accounts.reduce((sum, a) => sum + countNodes(a.folders), 0) };
46955
+ }
46956
+ function countNodes(nodes) {
46957
+ return nodes.reduce((sum, node) => sum + 1 + countNodes(node.children), 0);
46892
46958
  }
46893
46959
 
46894
46960
  // src/utils/contentWarnings.ts
@@ -48573,7 +48639,15 @@ function mutateBackground(request, operation, data, verify, deps) {
48573
48639
  transportError = error2 ?? {};
48574
48640
  transportMessage = describeTransportFailure(error2);
48575
48641
  }
48576
- const after = deps.read(request.id);
48642
+ let after;
48643
+ try {
48644
+ after = deps.read(request.id);
48645
+ } catch (error2) {
48646
+ throw new CodedError(
48647
+ `Operation outcome uncertain; read exact note before any retry: readback failed: ${error2 instanceof Error ? error2.message : String(error2)}${transportMessage ? "; " + transportMessage : ""}`,
48648
+ { code: "verification_failed", indeterminate: true }
48649
+ );
48650
+ }
48577
48651
  try {
48578
48652
  verify(before, after);
48579
48653
  } catch (error2) {
@@ -48839,14 +48913,27 @@ var FEATURES = [
48839
48913
  },
48840
48914
  {
48841
48915
  name: "fullDiskAccessReads",
48842
- description: "Read-only reads of the Notes database: query-notes, checklist state, note metadata, note links, native objects, native tags, and sync detail",
48916
+ 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",
48843
48917
  tools: [
48844
48918
  "query-notes",
48845
48919
  "get-checklist-state",
48846
48920
  "get-note-metadata",
48847
48921
  "get-note-link",
48848
48922
  "get-native-objects",
48923
+ "get-note-blocks",
48924
+ "get-note-structure",
48925
+ "get-note-tables",
48926
+ "list-note-links",
48849
48927
  "list-native-tags",
48928
+ "list-smart-folders",
48929
+ "list-special-notes",
48930
+ "list-recent-notes",
48931
+ "list-folder-tree",
48932
+ "list-paper-attachments",
48933
+ "export-paper-image",
48934
+ "export-attachments",
48935
+ "export-notes-markdown",
48936
+ "export-notes-html",
48850
48937
  "get-note-markdown (checklist annotations)",
48851
48938
  "get-sync-status (pending uploads)"
48852
48939
  ],
@@ -48907,7 +48994,8 @@ var FEATURES = [
48907
48994
  // Placeholders for features that need a native WRITE helper, which this
48908
48995
  // server does not ship. The opt-in private helper is read-only (write
48909
48996
  // support was deliberately deferred), so enabling or installing it changes
48910
- // nothing here: these always report `not_implemented`.
48997
+ // nothing here: these always report `not_implemented`. Reading smart folders
48998
+ // is available through list-smart-folders (fullDiskAccessReads).
48911
48999
  {
48912
49000
  name: "checklistToggle",
48913
49001
  description: "Check or uncheck an existing checklist item in place",
@@ -48931,17 +49019,17 @@ var FEATURES = [
48931
49019
  },
48932
49020
  {
48933
49021
  name: "paragraphLinks",
48934
- description: "Link to a specific paragraph or heading inside a note",
48935
- tools: [],
49022
+ description: "List a note's paragraphs and return a link that opens Notes at one paragraph or heading, read from the Notes database",
49023
+ tools: ["list-note-paragraphs", "get-paragraph-link"],
48936
49024
  minimumMacOSVersion: null,
48937
- requirements: [{ kind: "native_write_helper" }]
49025
+ requirements: [{ kind: "full_disk_access" }]
48938
49026
  },
48939
49027
  {
48940
49028
  name: "audioTranscription",
48941
- description: "Transcribe audio recordings attached to a note",
48942
- tools: [],
49029
+ 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)",
49030
+ tools: ["get-audio-transcripts", "transcribe-note-audio"],
48943
49031
  minimumMacOSVersion: null,
48944
- requirements: [{ kind: "native_write_helper" }]
49032
+ requirements: [{ kind: "full_disk_access" }]
48945
49033
  },
48946
49034
  {
48947
49035
  name: "markdownTemplateLibrary",
@@ -49612,7 +49700,7 @@ function resolveNote(dbPath2, columns, selector) {
49612
49700
  function noteBodySql(columns) {
49613
49701
  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;`;
49614
49702
  }
49615
- function readNoteParagraphs(selector, { dbPath: dbPath2 = NOTES_DB_PATH7 } = {}) {
49703
+ function readNoteParagraphs(selector, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
49616
49704
  checkNoteSelector(selector);
49617
49705
  const columns = readColumns(dbPath2);
49618
49706
  const { pk, id: id2 } = resolveNote(dbPath2, columns, selector);
@@ -49841,7 +49929,7 @@ function describeNoteStructure(s) {
49841
49929
  if (s.isShared) parts.push("shared");
49842
49930
  return `Note structure: ${parts.join("; ")}.`;
49843
49931
  }
49844
- function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH7, includeText = true, maxTextBytes = 4 * 1024 * 1024 } = {}) {
49932
+ function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH8, includeText = true, maxTextBytes = 4 * 1024 * 1024 } = {}) {
49845
49933
  const { pk } = parseNoteId2(id2);
49846
49934
  const columns = readColumns(dbPath2);
49847
49935
  const rows = parseJsonLines(
@@ -50087,7 +50175,7 @@ function decodeBodies(dbPath2, sql, params) {
50087
50175
  }
50088
50176
  var KIND_ORDER2 = { inline: 0, card: 1, note: 2, section: 3 };
50089
50177
  function listNoteLinks(options = {}) {
50090
- const { dbPath: dbPath2 = NOTES_DB_PATH7, offset = 0, limit = 200, maxBytes = 4 * 1024 * 1024 } = options;
50178
+ const { dbPath: dbPath2 = NOTES_DB_PATH8, offset = 0, limit = 200, maxBytes = 4 * 1024 * 1024 } = options;
50091
50179
  if (options.id && (options.account || options.folder))
50092
50180
  throw new NoteStoreError("Pass either id or account/folder, not both.", "invalid_input");
50093
50181
  const notePk = options.id ? parseNoteId2(options.id).pk : 0;
@@ -50309,8 +50397,9 @@ function insertLink(request, deps) {
50309
50397
  try {
50310
50398
  readback = verifyLinkReadback(before, deps.readLinks(request.id), insertion, request.url);
50311
50399
  } catch (error2) {
50312
- throw new Error(
50313
- `The text was written, but link verification failed: ${error2 instanceof Error ? error2.message : String(error2)}. Do not retry automatically.`
50400
+ throw new CodedError(
50401
+ `The text was written, but link verification failed: ${error2 instanceof Error ? error2.message : String(error2)}. Do not retry automatically.`,
50402
+ { code: "verification_failed", committed: true, indeterminate: true }
50314
50403
  );
50315
50404
  }
50316
50405
  return {
@@ -53176,7 +53265,6 @@ import {
53176
53265
  fstatSync as fstatSync7,
53177
53266
  mkdtempSync as mkdtempSync6,
53178
53267
  openSync as openSync7,
53179
- readFileSync as readFileSync4,
53180
53268
  readSync as readSync5,
53181
53269
  rmSync as rmSync6,
53182
53270
  writeFileSync as writeFileSync5
@@ -53456,17 +53544,14 @@ function assertExistingContentPreserved(before, after) {
53456
53544
  }
53457
53545
  function localAttachment(path10) {
53458
53546
  if (!isAbsolute4(path10)) throw new Error("An absolute local file path is required");
53459
- const descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
53460
53547
  try {
53461
- const stat = fstatSync7(descriptor);
53462
- if (!stat.isFile() || stat.size === 0 || stat.size > MAX_ADD_ATTACHMENT_BYTES)
53463
- throw new Error("Attachment must be a nonempty regular file of at most 64 MiB");
53464
- const bytes = readFileSync4(descriptor);
53465
- if (bytes.length !== stat.size)
53466
- throw new Error("Attachment changed while it was being read; try again");
53467
- return bytes;
53468
- } finally {
53469
- closeSync7(descriptor);
53548
+ return readAllowedFile(path10, MAX_ADD_ATTACHMENT_BYTES, { label: "Attachment" });
53549
+ } catch (error2) {
53550
+ throw new CodedError(error2 instanceof Error ? error2.message : String(error2), {
53551
+ code: "validation_error",
53552
+ committed: false,
53553
+ indeterminate: false
53554
+ });
53470
53555
  }
53471
53556
  }
53472
53557
  var MAX_ADD_ATTACHMENT_BYTES = 64 * 1024 * 1024;
@@ -53541,20 +53626,22 @@ function registerDirectOperations(server2, manager) {
53541
53626
  })
53542
53627
  );
53543
53628
  const attachmentInput = {
53544
- path: external_exports.string().min(1).max(4096).describe("Absolute path of the local file to attach"),
53629
+ path: external_exports.string().min(1).max(4096).describe(
53630
+ "Absolute path of the local regular file to attach, in home, temp or /Volumes; hidden paths and ~/Library (except iCloud Drive and CloudStorage) are refused"
53631
+ ),
53545
53632
  filename: external_exports.string().min(1).max(255).optional().describe(
53546
53633
  "Name the attachment gets in Notes instead of the source file's name. One path component with the source file's extension; no slash, colon, or control characters, and no leading dot."
53547
53634
  )
53548
53635
  };
53549
53636
  tool(
53550
53637
  "add-attachment",
53551
- "Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, attachment name, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes. On macOS 27 Notes' AppleScript does not list PDF attachments; with Full Disk Access the new attachment is verified through the read-only NoteStore database instead (verifiedBy: database), otherwise the outcome is reported as uncertain.",
53638
+ "Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, attachment name, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: reads only a regular file in home, temp or /Volumes, refusing hidden paths (~/.ssh, .env) and ~/Library outside iCloud Drive and CloudStorage unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes. On macOS 27 Notes' AppleScript does not list PDF attachments; with Full Disk Access the new attachment is verified through the read-only NoteStore database instead (verifiedBy: database), otherwise the outcome is reported as uncertain.",
53552
53639
  { id: noteId, expectedContentHash: revision, ...attachmentInput },
53553
53640
  (args) => attachFile(manager, args)
53554
53641
  );
53555
53642
  tool(
53556
53643
  "create-note-with-attachment",
53557
- "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.",
53644
+ "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, with the same read scope as add-attachment (home, temp or /Volumes; no hidden paths or ~/Library outside iCloud Drive and CloudStorage), 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.",
53558
53645
  {
53559
53646
  title: external_exports.string().min(1).max(2e3).refine((s) => !/[\r\n\0]/u.test(s), "One-line title"),
53560
53647
  content: external_exports.string().min(1).max(1024 * 1024).optional().describe("Optional plain-text body placed above the attachment"),
@@ -53580,26 +53667,45 @@ function registerDirectOperations(server2, manager) {
53580
53667
  `Failed to create note "${args.title}". Check that the folder and account exist (list-folders, list-accounts); nothing was attached`
53581
53668
  );
53582
53669
  const handOff = `Note ${note.id} was created; attach to it with add-attachment instead of creating another note`;
53670
+ const createdError = (message, cause) => new CodedError(message, {
53671
+ code: classifyError(message, cause).code,
53672
+ committed: true,
53673
+ indeterminate: false
53674
+ });
53583
53675
  let snapshot;
53584
53676
  try {
53585
53677
  snapshot = readSnapshot(manager, note.id);
53586
53678
  } catch (error2) {
53587
- throw new Error(`${handOff}. The new note could not be read back: ${String(error2)}`);
53679
+ const message = `${handOff}. The new note could not be read back: ${String(error2)}`;
53680
+ throw createdError(message, error2);
53588
53681
  }
53682
+ const progress = { insertionStarted: false };
53589
53683
  try {
53590
53684
  return {
53591
- ...attachFile(manager, {
53592
- id: note.id,
53593
- expectedContentHash: snapshot.hash,
53594
- path: args.path,
53595
- filename: name
53596
- }),
53685
+ ...attachFile(
53686
+ manager,
53687
+ {
53688
+ id: note.id,
53689
+ expectedContentHash: snapshot.hash,
53690
+ path: args.path,
53691
+ filename: name
53692
+ },
53693
+ void 0,
53694
+ progress
53695
+ ),
53597
53696
  title: args.title,
53598
53697
  folder: args.folder,
53599
53698
  noteCreated: true
53600
53699
  };
53601
53700
  } catch (error2) {
53602
- throw new Error(`${handOff}. ${error2 instanceof Error ? error2.message : String(error2)}`);
53701
+ const detail = error2 instanceof Error ? error2.message : String(error2);
53702
+ if (!progress.insertionStarted) throw createdError(`${handOff}. ${detail}`, error2);
53703
+ 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`;
53704
+ throw new CodedError(message, {
53705
+ code: "verification_failed",
53706
+ committed: true,
53707
+ indeterminate: true
53708
+ });
53603
53709
  }
53604
53710
  }
53605
53711
  );
@@ -53706,7 +53812,7 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
53706
53812
  );
53707
53813
  return { attachmentId, name: row.filename };
53708
53814
  }
53709
- function attachFile(manager, args, checked) {
53815
+ function attachFile(manager, args, checked, progress = { insertionStarted: false }) {
53710
53816
  const { id: id2, expectedContentHash, path: path10 } = args;
53711
53817
  const name = attachmentName(path10, args.filename);
53712
53818
  const before = checked ?? readSnapshot(manager, id2);
@@ -53721,6 +53827,7 @@ function attachFile(manager, args, checked) {
53721
53827
  if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
53722
53828
  let returnedId;
53723
53829
  let transportUncertain = false;
53830
+ progress.insertionStarted = true;
53724
53831
  try {
53725
53832
  returnedId = manager.addAttachmentById(id2, before.body, temporaryFile);
53726
53833
  } catch {
@@ -54048,15 +54155,27 @@ function runFolderDelete(manager, args, deps = defaultDeps) {
54048
54155
  throw uncertain(
54049
54156
  `The delete outcome is uncertain (${outcome.reason}); read folder ${args.id} before retrying`
54050
54157
  );
54051
- if (manager.folderExistsById(args.id))
54158
+ let stillExists;
54159
+ try {
54160
+ stillExists = manager.folderExistsById(args.id);
54161
+ } catch (error2) {
54162
+ throw uncertain(
54163
+ `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`
54164
+ );
54165
+ }
54166
+ if (stillExists)
54052
54167
  throw uncertain(
54053
54168
  `The delete outcome is uncertain: Notes.app still resolves folder ${args.id}; read it before retrying`
54054
54169
  );
54055
54170
  let storeTombstoned = false;
54056
54171
  for (let attempt = 0; attempt < 5 && !storeTombstoned; attempt++) {
54057
54172
  if (attempt > 0) deps.sleep(200);
54058
- const after = deps.readStore(coreDataPk(args.id));
54059
- storeTombstoned = !after || after.markedForDeletion;
54173
+ try {
54174
+ const after = deps.readStore(coreDataPk(args.id));
54175
+ storeTombstoned = !after || after.markedForDeletion;
54176
+ } catch {
54177
+ break;
54178
+ }
54060
54179
  }
54061
54180
  return {
54062
54181
  ...base,
@@ -56808,20 +56927,26 @@ function appendChecklistItems(args, deps, readRich) {
56808
56927
  const landed = [];
56809
56928
  let contentHash = args.expectedContentHash;
56810
56929
  for (const [index, text2] of args.items.entries()) {
56811
- let wrote = false;
56930
+ let attempted = false;
56931
+ const tracked = {
56932
+ ...deps,
56933
+ run: (input) => {
56934
+ attempted = true;
56935
+ deps.run(input);
56936
+ }
56937
+ };
56812
56938
  try {
56813
56939
  const result = mutateBackground(
56814
56940
  { ...args, expectedContentHash: contentHash },
56815
56941
  "create-checklist-item",
56816
56942
  { text: text2 },
56817
56943
  (before, after) => {
56818
- wrote = true;
56819
56944
  assertPreserved(before, after, { append: true });
56820
56945
  const added = after.checklist.slice(before.checklist.length);
56821
56946
  if (added.length !== 1 || added[0].text !== text2 || added[0].done)
56822
56947
  throw new Error("Native checklist item not verified");
56823
56948
  },
56824
- deps
56949
+ tracked
56825
56950
  );
56826
56951
  const items = byPosition(readRich(args.id).checklistItems);
56827
56952
  const fresh = items.filter(
@@ -56837,20 +56962,33 @@ function appendChecklistItems(args, deps, readRich) {
56837
56962
  landed.push({ index, id: fresh[0].id, text: text2 });
56838
56963
  contentHash = result.contentHash;
56839
56964
  } catch (error2) {
56965
+ const message = error2 instanceof Error ? error2.message : String(error2);
56966
+ const uncertain2 = attempted && !(error2 instanceof CodedError && error2.envelope.committed === false);
56967
+ const classified = classifyError(message, error2).code;
56968
+ const envelope = uncertain2 ? {
56969
+ code: classified === "timeout_indeterminate" ? classified : "verification_failed",
56970
+ indeterminate: true
56971
+ } : {
56972
+ code: classified,
56973
+ indeterminate: false,
56974
+ // Earlier items did land, so only an empty batch wrote nothing.
56975
+ ...landed.length === 0 ? { committed: false } : {}
56976
+ };
56840
56977
  return {
56841
56978
  ok: false,
56979
+ ...envelope,
56842
56980
  id: args.id,
56843
56981
  landed,
56844
56982
  stoppedAt: {
56845
56983
  index,
56846
56984
  text: text2,
56847
- // A bridge refusal or failure with an unchanged readback is
56848
- // reported as not written even though verification ran (#248).
56849
- outcome: wrote && !(error2 instanceof CodedError && error2.envelope.committed === false) ? "uncertain" : "not-written",
56850
- error: error2 instanceof Error ? error2.message : String(error2)
56985
+ outcome: uncertain2 ? "uncertain" : "not-written",
56986
+ error: message
56851
56987
  },
56852
56988
  notAttempted: args.items.slice(index + 1),
56853
- contentHash,
56989
+ // After an uncertain run the note may have changed, so the last
56990
+ // verified revision would be stale; read the note for a fresh one.
56991
+ ...uncertain2 ? {} : { contentHash },
56854
56992
  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.`
56855
56993
  };
56856
56994
  }
@@ -56860,15 +56998,24 @@ function appendChecklistItems(args, deps, readRich) {
56860
56998
  const orderVerified = tail.length === landed.length && tail.every((item, i) => item.id === landed[i].id && item.text === landed[i].text);
56861
56999
  return {
56862
57000
  ok: orderVerified,
57001
+ ...orderVerified ? {} : { code: "verification_failed", committed: true },
56863
57002
  id: args.id,
56864
57003
  contentHash,
56865
57004
  items: landed,
57005
+ landed,
56866
57006
  orderVerified,
56867
57007
  ...orderVerified ? {} : {
56868
57008
  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."
56869
57009
  }
56870
57010
  };
56871
57011
  }
57012
+ function nativeToolResult(result) {
57013
+ return {
57014
+ content: [{ type: "text", text: JSON.stringify(result) }],
57015
+ structuredContent: result,
57016
+ ...result.ok === false && typeof result.code === "string" ? { isError: true } : {}
57017
+ };
57018
+ }
56872
57019
  function registerNativeOperations(server2, manager) {
56873
57020
  function tool(name, description, input, handler, readOnly = false) {
56874
57021
  server2.registerTool(
@@ -56881,11 +57028,7 @@ function registerNativeOperations(server2, manager) {
56881
57028
  },
56882
57029
  (async (args) => {
56883
57030
  try {
56884
- const result = handler(args);
56885
- return {
56886
- content: [{ type: "text", text: JSON.stringify(result) }],
56887
- structuredContent: result
56888
- };
57031
+ return nativeToolResult(handler(args));
56889
57032
  } catch (error2) {
56890
57033
  return errorResult(error2 instanceof Error ? error2.message : "Operation failed", error2);
56891
57034
  }
@@ -57270,7 +57413,7 @@ import {
57270
57413
  existsSync as existsSync14,
57271
57414
  mkdirSync as mkdirSync8,
57272
57415
  mkdtempSync as mkdtempSync7,
57273
- readFileSync as readFileSync5,
57416
+ readFileSync as readFileSync4,
57274
57417
  renameSync as renameSync2,
57275
57418
  rmSync as rmSync7,
57276
57419
  writeFileSync as writeFileSync6
@@ -57307,7 +57450,7 @@ function packageRoot(fromDir = dirname8(fileURLToPath2(import.meta.url))) {
57307
57450
  const candidate = join27(dir, "package.json");
57308
57451
  if (existsSync14(candidate)) {
57309
57452
  try {
57310
- if (JSON.parse(readFileSync5(candidate, "utf8")).name === "apple-notes-mcp")
57453
+ if (JSON.parse(readFileSync4(candidate, "utf8")).name === "apple-notes-mcp")
57311
57454
  return dir;
57312
57455
  } catch {
57313
57456
  }
@@ -57323,7 +57466,7 @@ function defaultPublicHelperDeps(overrides = {}) {
57323
57466
  platform: process.platform,
57324
57467
  sourcePath: join27(packageRoot(), PUBLIC_HELPER_SOURCE),
57325
57468
  exists: existsSync14,
57326
- readFile: (path10) => readFileSync5(path10),
57469
+ readFile: (path10) => readFileSync4(path10),
57327
57470
  spawn: spawnSync3,
57328
57471
  spawnAsync: spawn,
57329
57472
  ...overrides
@@ -57733,7 +57876,7 @@ function parseNoteObjectId(noteId3) {
57733
57876
  );
57734
57877
  return { store: match[1], pk: Number(match[2]) };
57735
57878
  }
57736
- function queryNoteScoped(sql, pk, dbPath2 = NOTES_DB_PATH7) {
57879
+ function queryNoteScoped(sql, pk, dbPath2 = NOTES_DB_PATH8) {
57737
57880
  return runReadOnlySql(dbPath2, sql, { pk: { int: pk } }).split("\n");
57738
57881
  }
57739
57882
  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));`;
@@ -57762,7 +57905,7 @@ function drawingRowsSql(columns, dataColumn) {
57762
57905
  `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;`
57763
57906
  ].join("\n");
57764
57907
  }
57765
- function readDrawingRows2(noteId3, dbPath2 = NOTES_DB_PATH7) {
57908
+ function readDrawingRows2(noteId3, dbPath2 = NOTES_DB_PATH8) {
57766
57909
  const { store, pk } = parseNoteObjectId(noteId3);
57767
57910
  const columns = readColumns(dbPath2);
57768
57911
  const dataColumn = columns.has("ZMERGEABLEDATA1") ? "ZMERGEABLEDATA1" : columns.has("ZMERGEABLEDATA") ? "ZMERGEABLEDATA" : null;
@@ -57997,7 +58140,7 @@ function isFile2(path10) {
57997
58140
  var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
57998
58141
  function readAudioAssets(noteId3, options = {}) {
57999
58142
  const { store, pk } = parseNoteId2(noteId3);
58000
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
58143
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
58001
58144
  const columns = readColumns(dbPath2);
58002
58145
  const missing = ["ZMEDIA", "ZPARENTATTACHMENT", "ZFILENAME"].filter((c) => !columns.has(c));
58003
58146
  if (missing.length)
@@ -58263,7 +58406,7 @@ import { join as join30 } from "node:path";
58263
58406
  // src/services/privateHelper.ts
58264
58407
  import { spawnSync as spawnSync4 } from "node:child_process";
58265
58408
  import { createHash as createHash7 } from "node:crypto";
58266
- import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
58409
+ import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
58267
58410
  import { homedir as homedir21 } from "node:os";
58268
58411
  import { dirname as dirname9, join as join29, resolve as resolve8 } from "node:path";
58269
58412
  import { fileURLToPath as fileURLToPath3 } from "node:url";
@@ -58296,7 +58439,7 @@ function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
58296
58439
  const candidate = join29(dir, "package.json");
58297
58440
  if (existsSync15(candidate)) {
58298
58441
  try {
58299
- const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
58442
+ const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
58300
58443
  if (pkg.name === "apple-notes-mcp") return dir;
58301
58444
  } catch {
58302
58445
  }
@@ -58312,7 +58455,7 @@ function defaultDeps2(overrides = {}) {
58312
58455
  platform: process.platform,
58313
58456
  sourcePath: join29(packageRoot2(), HELPER_SOURCE_RELATIVE),
58314
58457
  exists: existsSync15,
58315
- readFile: (path10) => readFileSync6(path10),
58458
+ readFile: (path10) => readFileSync5(path10),
58316
58459
  spawn: spawnSync4,
58317
58460
  ...overrides
58318
58461
  };
@@ -58376,6 +58519,10 @@ function inspectInstallation(deps = defaultDeps2()) {
58376
58519
  );
58377
58520
  return { ...base, ready: true, reason: null, detail: null };
58378
58521
  }
58522
+ function helperTimeoutMs(env) {
58523
+ const value = Number.parseInt(env[TIMEOUT_ENV] || "", 10);
58524
+ return Number.isFinite(value) && value > 0 ? value : DEFAULT_TIMEOUT_MS3;
58525
+ }
58379
58526
  var PrivateHelperError = class extends Error {
58380
58527
  constructor(code, message, details = {}) {
58381
58528
  super(message);
@@ -58458,7 +58605,7 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
58458
58605
  throw new PrivateHelperError(install.reason || "helper_not_installed", install.detail || "");
58459
58606
  binaryPath = install.binaryPath;
58460
58607
  }
58461
- const timeout = Number.parseInt(deps.env[TIMEOUT_ENV] || "", 10) || DEFAULT_TIMEOUT_MS3;
58608
+ const timeout = helperTimeoutMs(deps.env);
58462
58609
  const result = deps.spawn(binaryPath, [], {
58463
58610
  input: JSON.stringify({ protocol: PRIVATE_HELPER_PROTOCOL, action, ...fields }),
58464
58611
  encoding: "utf8",
@@ -58468,13 +58615,23 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
58468
58615
  env: deps.env
58469
58616
  });
58470
58617
  const errno = result.error?.code;
58471
- if (errno === "ETIMEDOUT" || result.signal && result.status === null)
58618
+ if (errno === "ETIMEDOUT")
58472
58619
  throw new PrivateHelperError("timeout", `The helper did not answer within ${timeout} ms.`);
58620
+ if (errno === "ENOBUFS")
58621
+ throw new PrivateHelperError(
58622
+ "invalid_response",
58623
+ `The helper's output exceeded ${MAX_OUTPUT_BYTES3} bytes and was cut off.`
58624
+ );
58473
58625
  if (result.error)
58474
58626
  throw new PrivateHelperError(
58475
58627
  "helper_unreachable",
58476
58628
  `Could not run the helper: ${result.error.message}`
58477
58629
  );
58630
+ if (result.signal)
58631
+ throw new PrivateHelperError(
58632
+ "helper_crashed",
58633
+ `The helper was terminated by ${result.signal} before it answered.`
58634
+ );
58478
58635
  const stdout = String(result.stdout ?? "").trim();
58479
58636
  let parsed;
58480
58637
  try {
@@ -58798,7 +58955,8 @@ function envelopeCode(helperCode, message) {
58798
58955
  case "not_found":
58799
58956
  return "not_found";
58800
58957
  case "timeout":
58801
- return "timeout_indeterminate";
58958
+ case "helper_crashed":
58959
+ return "operation_failed";
58802
58960
  case "invalid_request":
58803
58961
  case "invalid_json":
58804
58962
  case "input_too_large":
@@ -58908,6 +59066,7 @@ var server = new McpServer({
58908
59066
  version: version2,
58909
59067
  description: "MCP server for managing Apple Notes - create, search, update, and organize notes"
58910
59068
  });
59069
+ installSdkErrorCodes(server);
58911
59070
  var notesManager = new AppleNotesManager();
58912
59071
  registerDirectOperations(server, notesManager);
58913
59072
  registerFolderDelete(server, notesManager);
@@ -58924,6 +59083,9 @@ function successResponse(message, structured) {
58924
59083
  function errorResponse(message, cause) {
58925
59084
  return errorResult(message, cause);
58926
59085
  }
59086
+ function notFoundResponse(message) {
59087
+ return errorResult(message, new CodedError(message, { code: "not_found" }));
59088
+ }
58927
59089
  function withErrorHandling(handler, errorPrefix) {
58928
59090
  return async (params) => {
58929
59091
  try {
@@ -59051,6 +59213,9 @@ function bodyReadFailureMessage(id2, title, error2) {
59051
59213
  return describeBodyReadFailure(title, error2, attachments);
59052
59214
  }
59053
59215
  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.";
59216
+ function deleteUnverifiedMessage(title, id2) {
59217
+ 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.`;
59218
+ }
59054
59219
  function containerUnknownMessage(title) {
59055
59220
  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.`;
59056
59221
  }
@@ -59121,12 +59286,32 @@ var folderNameSchema = {
59121
59286
  };
59122
59287
  function registerTool(name, config2, cb) {
59123
59288
  const { outputSchema, ...rest } = config2;
59289
+ const callback = NOTES_WRITE_TOOLS.has(name) ? (async (...args) => writeToolResult(
59290
+ await cb(...args)
59291
+ )) : cb;
59124
59292
  return server.registerTool(
59125
59293
  name,
59126
59294
  outputSchema ? { ...rest, outputSchema: external_exports.object(outputSchema).passthrough() } : rest,
59127
- cb
59295
+ callback
59128
59296
  );
59129
59297
  }
59298
+ var NOTES_WRITE_TOOLS = /* @__PURE__ */ new Set([
59299
+ "create-note",
59300
+ "update-note",
59301
+ "append-to-note",
59302
+ "insert-link",
59303
+ "delete-note",
59304
+ "move-note",
59305
+ "create-folder",
59306
+ "delete-folder",
59307
+ "batch-delete-notes",
59308
+ "batch-move-notes"
59309
+ ]);
59310
+ function writeToolResult(result) {
59311
+ const envelope = result.structuredContent;
59312
+ if (!result.isError || !envelope) return result;
59313
+ return { ...result, structuredContent: unavailableWriteEnvelope(envelope) };
59314
+ }
59130
59315
  registerTool(
59131
59316
  "create-note",
59132
59317
  {
@@ -59519,7 +59704,7 @@ registerTool(
59519
59704
  if (id2) {
59520
59705
  const note2 = notesManager.getNoteById(id2);
59521
59706
  if (!note2) {
59522
- return errorResponse(`Note with ID "${id2}" not found`);
59707
+ return notFoundResponse(`Note with ID "${id2}" not found`);
59523
59708
  }
59524
59709
  if (note2.passwordProtected) {
59525
59710
  return errorResponse(
@@ -59561,7 +59746,7 @@ registerTool(
59561
59746
  }
59562
59747
  const note = notesManager.getNoteDetails(title, account);
59563
59748
  if (!note) {
59564
- return errorResponse(`Note "${title}" not found`);
59749
+ return notFoundResponse(`Note "${title}" not found`);
59565
59750
  }
59566
59751
  if (note.passwordProtected) {
59567
59752
  return errorResponse(
@@ -59619,7 +59804,7 @@ registerTool(
59619
59804
  if (id2) {
59620
59805
  const note2 = notesManager.getNoteById(id2);
59621
59806
  if (!note2) {
59622
- return errorResponse(`Note with ID "${id2}" not found`);
59807
+ return notFoundResponse(`Note with ID "${id2}" not found`);
59623
59808
  }
59624
59809
  if (note2.passwordProtected) {
59625
59810
  return errorResponse(
@@ -59637,7 +59822,7 @@ registerTool(
59637
59822
  }
59638
59823
  const note = notesManager.getNoteDetails(title, account);
59639
59824
  if (!note) {
59640
- return errorResponse(`Note "${title}" not found`);
59825
+ return notFoundResponse(`Note "${title}" not found`);
59641
59826
  }
59642
59827
  if (note.passwordProtected) {
59643
59828
  return errorResponse(
@@ -59673,7 +59858,7 @@ registerTool(
59673
59858
  withErrorHandling(({ id: id2 }) => {
59674
59859
  const note = notesManager.getNoteById(id2);
59675
59860
  if (!note) {
59676
- return errorResponse(`Note with ID "${id2}" not found`);
59861
+ return notFoundResponse(`Note with ID "${id2}" not found`);
59677
59862
  }
59678
59863
  const metadata = {
59679
59864
  id: note.id,
@@ -59706,7 +59891,7 @@ registerTool(
59706
59891
  withErrorHandling(({ title, account }) => {
59707
59892
  const note = notesManager.getNoteDetails(title, account);
59708
59893
  if (!note) {
59709
- return errorResponse(`Note "${title}" not found`);
59894
+ return notFoundResponse(`Note "${title}" not found`);
59710
59895
  }
59711
59896
  const metadata = {
59712
59897
  id: note.id,
@@ -59763,7 +59948,7 @@ registerTool(
59763
59948
  if (id2) {
59764
59949
  const note2 = notesManager.getNoteById(id2);
59765
59950
  if (!note2) {
59766
- return errorResponse(`Note with ID "${id2}" not found`);
59951
+ return notFoundResponse(`Note with ID "${id2}" not found`);
59767
59952
  }
59768
59953
  if (note2.passwordProtected) {
59769
59954
  return errorResponse(
@@ -59783,7 +59968,7 @@ registerTool(
59783
59968
  }
59784
59969
  const note = notesManager.getNoteDetails(title, account);
59785
59970
  if (!note) {
59786
- return errorResponse(
59971
+ return notFoundResponse(
59787
59972
  `Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
59788
59973
  );
59789
59974
  }
@@ -59861,7 +60046,7 @@ registerTool(
59861
60046
  },
59862
60047
  withErrorHandling(({ id: id2 }) => {
59863
60048
  const note = notesManager.getNoteById(id2);
59864
- if (!note) return errorResponse(`Note with ID "${id2}" not found`);
60049
+ if (!note) return notFoundResponse(`Note with ID "${id2}" not found`);
59865
60050
  const body = notesManager.getNoteContentById(id2);
59866
60051
  if (!body) return errorResponse(`Failed to read content of note "${note.title}"`);
59867
60052
  const rich = readRichNote(id2);
@@ -59967,7 +60152,10 @@ registerTool(
59967
60152
  } catch (error2) {
59968
60153
  if (!(error2 instanceof NoteBlocksError)) throw error2;
59969
60154
  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}` : "";
59970
- return errorResponse(`Error reading note blocks [${error2.code}]: ${error2.message}${hint}`);
60155
+ return errorResponse(
60156
+ `Error reading note blocks [${error2.code}]: ${error2.message}${hint}`,
60157
+ error2
60158
+ );
59971
60159
  }
59972
60160
  const { summary } = page;
59973
60161
  const styles = Object.entries(summary.styles).map(([style, count]) => `${style} ${count}`).join(", ");
@@ -60528,7 +60716,10 @@ registerTool(
60528
60716
  },
60529
60717
  (r) => route = r
60530
60718
  );
60531
- if (response.isError) throw new Error(response.content[0].text);
60719
+ if (response.isError) {
60720
+ const envelope = response.structuredContent;
60721
+ throw envelope?.code ? new CodedError(response.content[0].text, envelope) : new Error(response.content[0].text);
60722
+ }
60532
60723
  return { route, contentHash: String(response.structuredContent?.contentHash ?? "") };
60533
60724
  }
60534
60725
  }
@@ -60620,6 +60811,13 @@ registerTool(
60620
60811
  if (result.status === "not-deleted") {
60621
60812
  return errorResponse(NOT_DELETED_MESSAGE);
60622
60813
  }
60814
+ if (result.status === "unverified") {
60815
+ const message = deleteUnverifiedMessage(snapshot.note.title, id2);
60816
+ return errorResponse(
60817
+ message,
60818
+ new CodedError(message, { code: "verification_failed", indeterminate: true })
60819
+ );
60820
+ }
60623
60821
  if (result.status !== "deleted") {
60624
60822
  return errorResponse(
60625
60823
  `The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
@@ -60664,7 +60862,7 @@ registerTool(
60664
60862
  withErrorHandling(({ id: id2, folder, account, ...scopeArgs }) => {
60665
60863
  const note = notesManager.getNoteById(id2);
60666
60864
  if (!note) {
60667
- return errorResponse(`Note with ID "${id2}" not found`);
60865
+ return notFoundResponse(`Note with ID "${id2}" not found`);
60668
60866
  }
60669
60867
  const success = notesManager.moveNoteById(id2, folder, account, scopeFrom(scopeArgs));
60670
60868
  if (!success) {
@@ -61195,7 +61393,7 @@ registerTool(
61195
61393
  if (id2) {
61196
61394
  const note2 = notesManager.getNoteById(id2);
61197
61395
  if (!note2) {
61198
- return errorResponse(`Note with ID "${id2}" not found`);
61396
+ return notFoundResponse(`Note with ID "${id2}" not found`);
61199
61397
  }
61200
61398
  const attachments2 = notesManager.listAttachmentsById(id2);
61201
61399
  if (attachments2.length === 0) {
@@ -61248,7 +61446,7 @@ ${attachmentList2}`,
61248
61446
  }
61249
61447
  const note = notesManager.getNoteDetails(title, account);
61250
61448
  if (!note) {
61251
- return errorResponse(
61449
+ return notFoundResponse(
61252
61450
  `Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
61253
61451
  );
61254
61452
  }
@@ -61306,6 +61504,8 @@ registerTool(
61306
61504
  }
61307
61505
  if (result.status === "not-deleted")
61308
61506
  return { id: id2, success: false, error: NOT_DELETED_MESSAGE };
61507
+ if (result.status === "unverified")
61508
+ return { id: id2, success: false, error: deleteUnverifiedMessage(snapshot.note.title, id2) };
61309
61509
  return { id: id2, success: false, error: "Delete result uncertain; inspect this exact ID" };
61310
61510
  });
61311
61511
  const succeeded = results.filter((r) => r.success).length;
@@ -61533,10 +61733,12 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
61533
61733
  );
61534
61734
  }
61535
61735
  }
61536
- return successResponse(
61537
- `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${skipped} with nothing on disk, ${failed} failed).`,
61538
- structured
61539
- );
61736
+ const summary = `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${skipped} with nothing on disk, ${failed} failed).`;
61737
+ if (exported === 0 && failed > 0) {
61738
+ const error2 = errorResponse(summary, new CodedError(summary, { code: "operation_failed" }));
61739
+ return { ...error2, structuredContent: { ...structured, ...error2.structuredContent } };
61740
+ }
61741
+ return successResponse(summary, structured);
61540
61742
  }, "Error exporting attachments")
61541
61743
  );
61542
61744
  registerTool(
@@ -61686,7 +61888,7 @@ registerTool(
61686
61888
  if (id2) {
61687
61889
  const markdown2 = notesManager.getNoteMarkdownById(id2);
61688
61890
  if (!markdown2) {
61689
- return errorResponse(`Note with ID "${id2}" not found or has no content`);
61891
+ return notFoundResponse(`Note with ID "${id2}" not found or has no content`);
61690
61892
  }
61691
61893
  return successResponse(markdown2, { markdown: markdown2 });
61692
61894
  }
@@ -61695,7 +61897,7 @@ registerTool(
61695
61897
  }
61696
61898
  const markdown = notesManager.getNoteMarkdown(title, account);
61697
61899
  if (!markdown) {
61698
- return errorResponse(
61900
+ return notFoundResponse(
61699
61901
  `Note "${title}" not found or has no content. Use search-notes to find notes, then use the note's ID for reliable operations.`
61700
61902
  );
61701
61903
  }
@@ -61770,7 +61972,10 @@ registerTool(
61770
61972
  `Error exporting Markdown [${error2.code}]: the template is invalid:
61771
61973
  ` + error2.details.map((detail) => `${detail.path}: ${detail.message}`).join("\n")
61772
61974
  );
61773
- return errorResponse(`Error exporting Markdown [${error2.code}]: ${error2.message}${hint}`);
61975
+ return errorResponse(
61976
+ `Error exporting Markdown [${error2.code}]: ${error2.message}${hint}`,
61977
+ error2
61978
+ );
61774
61979
  }
61775
61980
  const warned = receipt.warnings?.length ? `; ${receipt.warnings.length + (receipt.warningsOmitted ?? 0)} warning(s)` : "";
61776
61981
  const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + warned;
@@ -61823,7 +62028,7 @@ registerTool(
61823
62028
  } catch (error2) {
61824
62029
  if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
61825
62030
  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}` : "";
61826
- return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`);
62031
+ return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
61827
62032
  }
61828
62033
  const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;
61829
62034
  const skipped = receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "";
@@ -61851,7 +62056,7 @@ registerTool(
61851
62056
  withErrorHandling(({ id: id2 }) => {
61852
62057
  const note = notesManager.getNoteById(id2);
61853
62058
  if (!note) {
61854
- return errorResponse(`Note with ID "${id2}" not found`);
62059
+ return notFoundResponse(`Note with ID "${id2}" not found`);
61855
62060
  }
61856
62061
  if (note.passwordProtected) {
61857
62062
  return errorResponse(