apple-notes-mcp 2.8.22 → 2.8.24

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 +96 -0
  2. package/build/index.js +1233 -103
  3. package/package.json +1 -1
package/build/index.js CHANGED
@@ -3268,8 +3268,8 @@ var require_utils = __commonJS({
3268
3268
  }
3269
3269
  return ind;
3270
3270
  }
3271
- function removeDotSegments(path4) {
3272
- let input = path4;
3271
+ function removeDotSegments(path5) {
3272
+ let input = path5;
3273
3273
  const output = [];
3274
3274
  let nextSlash = -1;
3275
3275
  let len = 0;
@@ -3674,8 +3674,8 @@ var require_schemes = __commonJS({
3674
3674
  }
3675
3675
  if (wsComponent.resourceName) {
3676
3676
  const queryIndex = wsComponent.resourceName.indexOf("?");
3677
- const path4 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3678
- wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
3677
+ const path5 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3678
+ wsComponent.path = path5 && path5 !== "/" ? path5 : void 0;
3679
3679
  wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
3680
3680
  wsComponent.resourceName = void 0;
3681
3681
  }
@@ -6883,12 +6883,12 @@ var require_formats = __commonJS({
6883
6883
  "use strict";
6884
6884
  Object.defineProperty(exports, "__esModule", { value: true });
6885
6885
  exports.formatNames = exports.fastFormats = exports.fullFormats = void 0;
6886
- function fmtDef(validate, compare) {
6887
- return { validate, compare };
6886
+ function fmtDef(validate, compare2) {
6887
+ return { validate, compare: compare2 };
6888
6888
  }
6889
6889
  exports.fullFormats = {
6890
6890
  // date: http://tools.ietf.org/html/rfc3339#section-5.6
6891
- date: fmtDef(date3, compareDate),
6891
+ date: fmtDef(date3, compareDate2),
6892
6892
  // date-time: http://tools.ietf.org/html/rfc3339#section-5.6
6893
6893
  time: fmtDef(getTime(true), compareTime),
6894
6894
  "date-time": fmtDef(getDateTime(true), compareDateTime),
@@ -6935,7 +6935,7 @@ var require_formats = __commonJS({
6935
6935
  };
6936
6936
  exports.fastFormats = {
6937
6937
  ...exports.fullFormats,
6938
- date: fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\d$/, compareDate),
6938
+ date: fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\d$/, compareDate2),
6939
6939
  time: fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i, compareTime),
6940
6940
  "date-time": fmtDef(/^\d\d\d\d-[0-1]\d-[0-3]\dt(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i, compareDateTime),
6941
6941
  "iso-time": fmtDef(/^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i, compareIsoTime),
@@ -6963,7 +6963,7 @@ var require_formats = __commonJS({
6963
6963
  const day = +matches[3];
6964
6964
  return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]);
6965
6965
  }
6966
- function compareDate(d1, d2) {
6966
+ function compareDate2(d1, d2) {
6967
6967
  if (!(d1 && d2))
6968
6968
  return void 0;
6969
6969
  if (d1 > d2)
@@ -7040,7 +7040,7 @@ var require_formats = __commonJS({
7040
7040
  return void 0;
7041
7041
  const [d1, t1] = dt1.split(DATE_TIME_SEPARATOR);
7042
7042
  const [d2, t2] = dt2.split(DATE_TIME_SEPARATOR);
7043
- const res = compareDate(d1, d2);
7043
+ const res = compareDate2(d1, d2);
7044
7044
  if (res === void 0)
7045
7045
  return void 0;
7046
7046
  return res || compareTime(t1, t2);
@@ -7181,12 +7181,12 @@ var require_dist = __commonJS({
7181
7181
  throw new Error(`Unknown format "${name}"`);
7182
7182
  return f;
7183
7183
  };
7184
- function addFormats(ajv, list, fs4, exportName) {
7184
+ function addFormats(ajv, list, fs5, exportName) {
7185
7185
  var _a;
7186
7186
  var _b;
7187
7187
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
7188
7188
  for (const f of list)
7189
- ajv.addFormat(f, fs4[f]);
7189
+ ajv.addFormat(f, fs5[f]);
7190
7190
  }
7191
7191
  module.exports = exports = formatsPlugin;
7192
7192
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -12238,32 +12238,32 @@ var require_URL = __commonJS({
12238
12238
  else
12239
12239
  return basepath.substring(0, lastslash + 1) + refpath;
12240
12240
  }
12241
- function remove_dot_segments(path4) {
12242
- if (!path4) return path4;
12241
+ function remove_dot_segments(path5) {
12242
+ if (!path5) return path5;
12243
12243
  var output = "";
12244
- while (path4.length > 0) {
12245
- if (path4 === "." || path4 === "..") {
12246
- path4 = "";
12244
+ while (path5.length > 0) {
12245
+ if (path5 === "." || path5 === "..") {
12246
+ path5 = "";
12247
12247
  break;
12248
12248
  }
12249
- var twochars = path4.substring(0, 2);
12250
- var threechars = path4.substring(0, 3);
12251
- var fourchars = path4.substring(0, 4);
12249
+ var twochars = path5.substring(0, 2);
12250
+ var threechars = path5.substring(0, 3);
12251
+ var fourchars = path5.substring(0, 4);
12252
12252
  if (threechars === "../") {
12253
- path4 = path4.substring(3);
12253
+ path5 = path5.substring(3);
12254
12254
  } else if (twochars === "./") {
12255
- path4 = path4.substring(2);
12255
+ path5 = path5.substring(2);
12256
12256
  } else if (threechars === "/./") {
12257
- path4 = "/" + path4.substring(3);
12258
- } else if (twochars === "/." && path4.length === 2) {
12259
- path4 = "/";
12260
- } else if (fourchars === "/../" || threechars === "/.." && path4.length === 3) {
12261
- path4 = "/" + path4.substring(4);
12257
+ path5 = "/" + path5.substring(3);
12258
+ } else if (twochars === "/." && path5.length === 2) {
12259
+ path5 = "/";
12260
+ } else if (fourchars === "/../" || threechars === "/.." && path5.length === 3) {
12261
+ path5 = "/" + path5.substring(4);
12262
12262
  output = output.replace(/\/?[^\/]*$/, "");
12263
12263
  } else {
12264
- var segment = path4.match(/(\/?([^\/]*))/)[0];
12264
+ var segment = path5.match(/(\/?([^\/]*))/)[0];
12265
12265
  output += segment;
12266
- path4 = path4.substring(segment.length);
12266
+ path5 = path5.substring(segment.length);
12267
12267
  }
12268
12268
  }
12269
12269
  return output;
@@ -24232,10 +24232,10 @@ var require_turndown_cjs = __commonJS({
24232
24232
  }
24233
24233
  var root = typeof window !== "undefined" ? window : {};
24234
24234
  function canParseHTMLNatively() {
24235
- var Parser = root.DOMParser;
24235
+ var Parser2 = root.DOMParser;
24236
24236
  var canParse = false;
24237
24237
  try {
24238
- if (new Parser().parseFromString("", "text/html")) {
24238
+ if (new Parser2().parseFromString("", "text/html")) {
24239
24239
  canParse = true;
24240
24240
  }
24241
24241
  } catch (e) {
@@ -24243,15 +24243,15 @@ var require_turndown_cjs = __commonJS({
24243
24243
  return canParse;
24244
24244
  }
24245
24245
  function createHTMLParser() {
24246
- var Parser = function() {
24246
+ var Parser2 = function() {
24247
24247
  };
24248
24248
  {
24249
24249
  var domino = require_lib();
24250
- Parser.prototype.parseFromString = function(string3) {
24250
+ Parser2.prototype.parseFromString = function(string3) {
24251
24251
  return domino.createDocument(string3);
24252
24252
  };
24253
24253
  }
24254
- return Parser;
24254
+ return Parser2;
24255
24255
  }
24256
24256
  var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();
24257
24257
  function RootNode(input, options) {
@@ -24465,14 +24465,14 @@ var require_turndown_cjs = __commonJS({
24465
24465
  } else if (node.nodeType === 1) {
24466
24466
  replacement = replacementForNode.call(self, node);
24467
24467
  }
24468
- return join11(output, replacement);
24468
+ return join12(output, replacement);
24469
24469
  }, "");
24470
24470
  }
24471
24471
  function postProcess(output) {
24472
24472
  var self = this;
24473
24473
  this.rules.forEach(function(rule) {
24474
24474
  if (typeof rule.append === "function") {
24475
- output = join11(output, rule.append(self.options));
24475
+ output = join12(output, rule.append(self.options));
24476
24476
  }
24477
24477
  });
24478
24478
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -24484,7 +24484,7 @@ var require_turndown_cjs = __commonJS({
24484
24484
  if (whitespace.leading || whitespace.trailing) content = content.trim();
24485
24485
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
24486
24486
  }
24487
- function join11(output, replacement) {
24487
+ function join12(output, replacement) {
24488
24488
  var s1 = trimTrailingNewlines(output);
24489
24489
  var s2 = trimLeadingNewlines(replacement);
24490
24490
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -24979,8 +24979,8 @@ function getErrorMap() {
24979
24979
 
24980
24980
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
24981
24981
  var makeIssue = (params) => {
24982
- const { data, path: path4, errorMaps, issueData } = params;
24983
- const fullPath = [...path4, ...issueData.path || []];
24982
+ const { data, path: path5, errorMaps, issueData } = params;
24983
+ const fullPath = [...path5, ...issueData.path || []];
24984
24984
  const fullIssue = {
24985
24985
  ...issueData,
24986
24986
  path: fullPath
@@ -25096,11 +25096,11 @@ var errorUtil;
25096
25096
 
25097
25097
  // node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
25098
25098
  var ParseInputLazyPath = class {
25099
- constructor(parent, value, path4, key) {
25099
+ constructor(parent, value, path5, key) {
25100
25100
  this._cachedPath = [];
25101
25101
  this.parent = parent;
25102
25102
  this.data = value;
25103
- this._path = path4;
25103
+ this._path = path5;
25104
25104
  this._key = key;
25105
25105
  }
25106
25106
  get path() {
@@ -28737,10 +28737,10 @@ function assignProp(target, prop, value) {
28737
28737
  configurable: true
28738
28738
  });
28739
28739
  }
28740
- function getElementAtPath(obj, path4) {
28741
- if (!path4)
28740
+ function getElementAtPath(obj, path5) {
28741
+ if (!path5)
28742
28742
  return obj;
28743
- return path4.reduce((acc, key) => acc?.[key], obj);
28743
+ return path5.reduce((acc, key) => acc?.[key], obj);
28744
28744
  }
28745
28745
  function promiseAllObject(promisesObj) {
28746
28746
  const keys = Object.keys(promisesObj);
@@ -29060,11 +29060,11 @@ function aborted(x, startIndex = 0) {
29060
29060
  }
29061
29061
  return false;
29062
29062
  }
29063
- function prefixIssues(path4, issues) {
29063
+ function prefixIssues(path5, issues) {
29064
29064
  return issues.map((iss) => {
29065
29065
  var _a;
29066
29066
  (_a = iss).path ?? (_a.path = []);
29067
- iss.path.unshift(path4);
29067
+ iss.path.unshift(path5);
29068
29068
  return iss;
29069
29069
  });
29070
29070
  }
@@ -32475,11 +32475,11 @@ function normalizeObjectSchema(schema) {
32475
32475
  }
32476
32476
  return void 0;
32477
32477
  }
32478
- function getDotPath(path4) {
32479
- if (path4.length === 0) {
32478
+ function getDotPath(path5) {
32479
+ if (path5.length === 0) {
32480
32480
  return "object root";
32481
32481
  }
32482
- return path4.reduce((acc, seg, index) => {
32482
+ return path5.reduce((acc, seg, index) => {
32483
32483
  if (index === 0) {
32484
32484
  return String(seg);
32485
32485
  }
@@ -39862,8 +39862,8 @@ function allowedSaveRoots() {
39862
39862
  "/private/tmp"
39863
39863
  ];
39864
39864
  }
39865
- function canonicalize(path4) {
39866
- return realpathSync.native(path4);
39865
+ function canonicalize(path5) {
39866
+ return realpathSync.native(path5);
39867
39867
  }
39868
39868
  function isWithinRoots(candidate, roots) {
39869
39869
  return roots.some((root) => {
@@ -39885,9 +39885,9 @@ function canonicalRoots(roots) {
39885
39885
  }
39886
39886
  return canonical;
39887
39887
  }
39888
- function entryExists(path4) {
39888
+ function entryExists(path5) {
39889
39889
  try {
39890
- lstatSync(path4);
39890
+ lstatSync(path5);
39891
39891
  return true;
39892
39892
  } catch (e) {
39893
39893
  const code = e.code;
@@ -42733,14 +42733,797 @@ function describeSearchScope(searchContent, resultCount) {
42733
42733
  return "\n\n\u2139\uFE0F Only note titles were searched, so a term that appears in note bodies would not match. Retry with `searchContent: true` to search bodies instead.";
42734
42734
  }
42735
42735
 
42736
+ // src/utils/noteQuery.ts
42737
+ var QUERY_LIMITS = {
42738
+ /** Maximum number of tokens (terms, operators, parentheses). */
42739
+ MAX_TOKENS: 256,
42740
+ /** Maximum nesting depth of parentheses and NOT operators combined. */
42741
+ MAX_DEPTH: 64
42742
+ };
42743
+ var FACETS = [
42744
+ "link",
42745
+ "attachment",
42746
+ "checklist",
42747
+ "drawing",
42748
+ "image",
42749
+ "video",
42750
+ "audio",
42751
+ "pdf",
42752
+ "table",
42753
+ "scan",
42754
+ "tag"
42755
+ ];
42756
+ var FLAGS = ["pinned", "locked", "shared"];
42757
+ var NoteQueryError = class extends Error {
42758
+ constructor(message, position) {
42759
+ super(position === void 0 ? message : `${message} (at position ${position + 1})`);
42760
+ this.position = position;
42761
+ this.name = "NoteQueryError";
42762
+ }
42763
+ position;
42764
+ };
42765
+ var FIELDS = /* @__PURE__ */ new Set([
42766
+ "title",
42767
+ "body",
42768
+ "text",
42769
+ "folder",
42770
+ "account",
42771
+ "tag",
42772
+ "has",
42773
+ "is",
42774
+ "checklist",
42775
+ "words",
42776
+ "created",
42777
+ "modified"
42778
+ ]);
42779
+ function readQuoted(input, start) {
42780
+ let value = "";
42781
+ let i = start + 1;
42782
+ while (i < input.length) {
42783
+ const ch = input[i];
42784
+ if (ch === "\\" && (input[i + 1] === '"' || input[i + 1] === "\\")) {
42785
+ value += input[i + 1];
42786
+ i += 2;
42787
+ continue;
42788
+ }
42789
+ if (ch === '"') return [value, i + 1];
42790
+ value += ch;
42791
+ i++;
42792
+ }
42793
+ throw new NoteQueryError("Unterminated quoted phrase", start);
42794
+ }
42795
+ var isSpace = (ch) => /\s/u.test(ch);
42796
+ function tokenize(input) {
42797
+ const tokens = [];
42798
+ const push = (token) => {
42799
+ if (tokens.length >= QUERY_LIMITS.MAX_TOKENS) {
42800
+ throw new NoteQueryError(
42801
+ `Query has more than ${QUERY_LIMITS.MAX_TOKENS} tokens; simplify it`,
42802
+ token.pos
42803
+ );
42804
+ }
42805
+ tokens.push(token);
42806
+ };
42807
+ let i = 0;
42808
+ while (i < input.length) {
42809
+ const ch = input[i];
42810
+ if (isSpace(ch)) {
42811
+ i++;
42812
+ continue;
42813
+ }
42814
+ if (ch === "(" || ch === ")") {
42815
+ push({ kind: ch === "(" ? "lparen" : "rparen", pos: i });
42816
+ i++;
42817
+ continue;
42818
+ }
42819
+ if (ch === '"') {
42820
+ const [value, next] = readQuoted(input, i);
42821
+ push({ kind: "term", pos: i, value, quoted: true });
42822
+ i = next;
42823
+ continue;
42824
+ }
42825
+ if (ch === "-" && i + 1 < input.length && !isSpace(input[i + 1]) && input[i + 1] !== ")") {
42826
+ push({ kind: "not", pos: i });
42827
+ i++;
42828
+ continue;
42829
+ }
42830
+ let j = i;
42831
+ while (j < input.length && !isSpace(input[j]) && !'()"'.includes(input[j])) j++;
42832
+ const word = input.slice(i, j);
42833
+ const colon = word.indexOf(":");
42834
+ if (colon > 0) {
42835
+ const prefix = word.slice(0, colon);
42836
+ const field = prefix.toLowerCase();
42837
+ let value = word.slice(colon + 1);
42838
+ if (FIELDS.has(field)) {
42839
+ let quoted = false;
42840
+ if (value === "" && input[j] === '"') {
42841
+ [value, j] = readQuoted(input, j);
42842
+ quoted = true;
42843
+ }
42844
+ push({ kind: "term", pos: i, field, value, quoted });
42845
+ i = j;
42846
+ continue;
42847
+ }
42848
+ if (/^[a-z]+$/i.test(prefix) && !value.startsWith("//")) {
42849
+ throw new NoteQueryError(
42850
+ `Unknown field "${prefix}:". Known fields: ${[...FIELDS].join(", ")}. Quote the term to search it literally`,
42851
+ i
42852
+ );
42853
+ }
42854
+ }
42855
+ const upper = word.toUpperCase();
42856
+ if (upper === "AND" || upper === "OR" || upper === "NOT") {
42857
+ push({ kind: upper.toLowerCase(), pos: i });
42858
+ } else {
42859
+ push({ kind: "term", pos: i, value: word, quoted: false });
42860
+ }
42861
+ i = j;
42862
+ }
42863
+ return tokens;
42864
+ }
42865
+ var COMPARE_RE = /^(>=|<=|>|<|=)?(.*)$/s;
42866
+ function splitComparison(value) {
42867
+ const match = COMPARE_RE.exec(value);
42868
+ return [match[1] ?? "=", match[2]];
42869
+ }
42870
+ function parseLocalDate(text, pos) {
42871
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
42872
+ if (!match) {
42873
+ throw new NoteQueryError(`Expected a date as YYYY-MM-DD, got "${text}"`, pos);
42874
+ }
42875
+ const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
42876
+ const start = new Date(year, month - 1, day);
42877
+ if (start.getFullYear() !== year || start.getMonth() !== month - 1 || start.getDate() !== day) {
42878
+ throw new NoteQueryError(`"${text}" is not a valid calendar date`, pos);
42879
+ }
42880
+ return { start: start.getTime(), end: new Date(year, month - 1, day + 1).getTime() };
42881
+ }
42882
+ function termNode(token) {
42883
+ const { field, value, quoted, pos } = token;
42884
+ if (field === void 0) {
42885
+ if (value === "") throw new NoteQueryError("Empty quoted phrase", pos);
42886
+ const lower2 = value.toLowerCase();
42887
+ if (!quoted && FLAGS.includes(lower2)) {
42888
+ return { type: "flag", flag: lower2 };
42889
+ }
42890
+ return { type: "text", field: "any", value };
42891
+ }
42892
+ if (value === "") throw new NoteQueryError(`"${field}:" needs a value`, pos);
42893
+ const lower = value.toLowerCase();
42894
+ switch (field) {
42895
+ case "title":
42896
+ case "body":
42897
+ return { type: "text", field, value };
42898
+ case "text":
42899
+ return { type: "text", field: "any", value };
42900
+ case "folder":
42901
+ case "account":
42902
+ return { type: field, value };
42903
+ case "tag": {
42904
+ const tag = value.replace(/^#/, "");
42905
+ if (!tag) throw new NoteQueryError(`"tag:" needs a tag name`, pos);
42906
+ return { type: "tag", value: tag };
42907
+ }
42908
+ case "has":
42909
+ if (!FACETS.includes(lower)) {
42910
+ throw new NoteQueryError(
42911
+ `Unknown facet "has:${value}". Supported: ${FACETS.map((f) => `has:${f}`).join(", ")}`,
42912
+ pos
42913
+ );
42914
+ }
42915
+ return { type: "has", facet: lower };
42916
+ case "is":
42917
+ if (!FLAGS.includes(lower)) {
42918
+ throw new NoteQueryError(`Unknown flag "is:${value}". Supported: ${FLAGS.join(", ")}`, pos);
42919
+ }
42920
+ return { type: "flag", flag: lower };
42921
+ case "checklist":
42922
+ if (lower !== "open" && lower !== "done") {
42923
+ throw new NoteQueryError(`"checklist:" accepts open or done, got "${value}"`, pos);
42924
+ }
42925
+ return { type: "checklist", state: lower };
42926
+ case "words": {
42927
+ const [op, number3] = splitComparison(value);
42928
+ if (!/^\d{1,9}$/.test(number3)) {
42929
+ throw new NoteQueryError(`"words:" needs a whole number, e.g. words:>250`, pos);
42930
+ }
42931
+ return { type: "words", op, value: Number(number3) };
42932
+ }
42933
+ case "created":
42934
+ case "modified": {
42935
+ const [op, date3] = splitComparison(value);
42936
+ return { type: "date", field, op, date: date3, ...parseLocalDate(date3, pos) };
42937
+ }
42938
+ }
42939
+ throw new NoteQueryError(`Unknown field "${field}:"`, pos);
42940
+ }
42941
+ var Parser = class {
42942
+ constructor(tokens, length) {
42943
+ this.tokens = tokens;
42944
+ this.length = length;
42945
+ }
42946
+ tokens;
42947
+ length;
42948
+ index = 0;
42949
+ depth = 0;
42950
+ parse() {
42951
+ if (this.tokens.length === 0) throw new NoteQueryError("Query is empty");
42952
+ const node = this.parseOr();
42953
+ const extra = this.peek();
42954
+ if (extra) {
42955
+ throw new NoteQueryError(
42956
+ extra.kind === "rparen" ? "Unmatched closing parenthesis" : "Unexpected token",
42957
+ extra.pos
42958
+ );
42959
+ }
42960
+ return node;
42961
+ }
42962
+ peek() {
42963
+ return this.tokens[this.index];
42964
+ }
42965
+ enter(pos) {
42966
+ this.depth++;
42967
+ if (this.depth > QUERY_LIMITS.MAX_DEPTH) {
42968
+ throw new NoteQueryError(
42969
+ `Query nests deeper than ${QUERY_LIMITS.MAX_DEPTH} levels; simplify it`,
42970
+ pos
42971
+ );
42972
+ }
42973
+ }
42974
+ missingOperand(after) {
42975
+ const next = this.peek();
42976
+ throw new NoteQueryError(`${after} needs a search term after it`, next?.pos ?? this.length);
42977
+ }
42978
+ parseOr() {
42979
+ const children = [this.parseAnd()];
42980
+ while (this.peek()?.kind === "or") {
42981
+ this.index++;
42982
+ children.push(this.parseAnd());
42983
+ }
42984
+ return children.length === 1 ? children[0] : { type: "or", children };
42985
+ }
42986
+ parseAnd() {
42987
+ const children = [this.parseUnary()];
42988
+ for (; ; ) {
42989
+ const next = this.peek();
42990
+ if (next?.kind === "and") {
42991
+ this.index++;
42992
+ children.push(this.parseUnary());
42993
+ } else if (next && (next.kind === "term" || next.kind === "not" || next.kind === "lparen")) {
42994
+ children.push(this.parseUnary());
42995
+ } else {
42996
+ break;
42997
+ }
42998
+ }
42999
+ return children.length === 1 ? children[0] : { type: "and", children };
43000
+ }
43001
+ parseUnary() {
43002
+ const token = this.peek();
43003
+ if (token?.kind === "not") {
43004
+ this.index++;
43005
+ this.enter(token.pos);
43006
+ const child = this.parseUnary();
43007
+ this.depth--;
43008
+ return { type: "not", child };
43009
+ }
43010
+ return this.parsePrimary();
43011
+ }
43012
+ parsePrimary() {
43013
+ const token = this.peek();
43014
+ if (!token) {
43015
+ const previous2 = this.tokens[this.index - 1];
43016
+ this.missingOperand(previous2 ? previous2.kind.toUpperCase().replace("LPAREN", "(") : "Query");
43017
+ }
43018
+ if (token.kind === "lparen") {
43019
+ this.index++;
43020
+ this.enter(token.pos);
43021
+ if (this.peek()?.kind === "rparen") {
43022
+ throw new NoteQueryError("Empty parentheses", token.pos);
43023
+ }
43024
+ const node = this.parseOr();
43025
+ if (this.peek()?.kind !== "rparen") {
43026
+ throw new NoteQueryError("Missing closing parenthesis", token.pos);
43027
+ }
43028
+ this.index++;
43029
+ this.depth--;
43030
+ return node;
43031
+ }
43032
+ if (token.kind === "term") {
43033
+ this.index++;
43034
+ return termNode(token);
43035
+ }
43036
+ if (token.kind === "rparen") {
43037
+ throw new NoteQueryError("Unexpected closing parenthesis", token.pos);
43038
+ }
43039
+ const previous = this.tokens[this.index - 1];
43040
+ if (!previous || previous.kind === "lparen") {
43041
+ throw new NoteQueryError(
43042
+ `${token.kind.toUpperCase()} needs a search term before it`,
43043
+ token.pos
43044
+ );
43045
+ }
43046
+ this.missingOperand(previous.kind === "term" ? "Operator" : previous.kind.toUpperCase());
43047
+ }
43048
+ };
43049
+ function parseNoteQuery(input) {
43050
+ return new Parser(tokenize(input), input.length).parse();
43051
+ }
43052
+ function normalizeForMatch(text) {
43053
+ return text.normalize("NFC").replace(/[\ufffc\u00a0]/gu, " ").toLowerCase();
43054
+ }
43055
+ function needsContent(node) {
43056
+ switch (node.type) {
43057
+ case "and":
43058
+ case "or":
43059
+ return node.children.some(needsContent);
43060
+ case "not":
43061
+ return needsContent(node.child);
43062
+ case "text":
43063
+ return node.field !== "title";
43064
+ case "tag":
43065
+ case "has":
43066
+ case "checklist":
43067
+ case "words":
43068
+ return true;
43069
+ default:
43070
+ return false;
43071
+ }
43072
+ }
43073
+ function needsTags(node) {
43074
+ switch (node.type) {
43075
+ case "and":
43076
+ case "or":
43077
+ return node.children.some(needsTags);
43078
+ case "not":
43079
+ return needsTags(node.child);
43080
+ case "tag":
43081
+ return true;
43082
+ case "has":
43083
+ return node.facet === "tag";
43084
+ default:
43085
+ return false;
43086
+ }
43087
+ }
43088
+ function compare(actual, op, expected) {
43089
+ switch (op) {
43090
+ case "=":
43091
+ return actual === expected;
43092
+ case ">":
43093
+ return actual > expected;
43094
+ case ">=":
43095
+ return actual >= expected;
43096
+ case "<":
43097
+ return actual < expected;
43098
+ case "<=":
43099
+ return actual <= expected;
43100
+ }
43101
+ }
43102
+ function compareDate(actual, node) {
43103
+ if (actual === void 0) return false;
43104
+ switch (node.op) {
43105
+ case "=":
43106
+ return actual >= node.start && actual < node.end;
43107
+ case ">":
43108
+ return actual >= node.end;
43109
+ case ">=":
43110
+ return actual >= node.start;
43111
+ case "<":
43112
+ return actual < node.start;
43113
+ case "<=":
43114
+ return actual < node.end;
43115
+ }
43116
+ }
43117
+ var orderCache = /* @__PURE__ */ new WeakMap();
43118
+ function cheapFirst(children) {
43119
+ let ordered = orderCache.get(children);
43120
+ if (!ordered) {
43121
+ ordered = [...children].sort((a, b) => Number(needsContent(a)) - Number(needsContent(b)));
43122
+ orderCache.set(children, ordered);
43123
+ }
43124
+ return ordered;
43125
+ }
43126
+ function evaluateNoteQuery(node, note) {
43127
+ switch (node.type) {
43128
+ case "and":
43129
+ return cheapFirst(node.children).every((child) => evaluateNoteQuery(child, note));
43130
+ case "or":
43131
+ return cheapFirst(node.children).some((child) => evaluateNoteQuery(child, note));
43132
+ case "not":
43133
+ return !evaluateNoteQuery(node.child, note);
43134
+ case "text": {
43135
+ const needle = normalizeForMatch(node.value);
43136
+ if (node.field === "title") return note.titleLower.includes(needle);
43137
+ if (node.field === "any" && note.titleLower.includes(needle)) return true;
43138
+ const content = note.content();
43139
+ if (!content) return false;
43140
+ return (node.field === "body" ? content.bodyLower : content.textLower).includes(needle);
43141
+ }
43142
+ case "folder":
43143
+ return note.folderKeys.includes(normalizeForMatch(node.value));
43144
+ case "account":
43145
+ return note.accountLower === normalizeForMatch(node.value);
43146
+ case "flag":
43147
+ return note[node.flag];
43148
+ case "date":
43149
+ return compareDate(node.field === "created" ? note.created : note.modified, node);
43150
+ case "tag": {
43151
+ const content = note.content();
43152
+ return Boolean(content?.tags.includes(normalizeForMatch(node.value)));
43153
+ }
43154
+ case "has":
43155
+ return Boolean(note.content()?.facets.has(node.facet));
43156
+ case "checklist": {
43157
+ const content = note.content();
43158
+ if (!content || content.checklist.total === 0) return false;
43159
+ return node.state === "open" ? content.checklist.open > 0 : content.checklist.open === 0;
43160
+ }
43161
+ case "words": {
43162
+ const content = note.content();
43163
+ return content ? compare(content.words, node.op, node.value) : false;
43164
+ }
43165
+ }
43166
+ }
43167
+ function positiveTextTerms(node, negated = false) {
43168
+ switch (node.type) {
43169
+ case "and":
43170
+ case "or":
43171
+ return node.children.flatMap((child) => positiveTextTerms(child, negated));
43172
+ case "not":
43173
+ return positiveTextTerms(node.child, !negated);
43174
+ case "text":
43175
+ return negated ? [] : [node.value];
43176
+ default:
43177
+ return [];
43178
+ }
43179
+ }
43180
+
43181
+ // src/utils/noteQueryStore.ts
43182
+ import { execFileSync as execFileSync6 } from "child_process";
43183
+ import * as fs4 from "fs";
43184
+ import * as os4 from "os";
43185
+ import * as path4 from "path";
43186
+ import { gunzipSync as gunzipSync3 } from "zlib";
43187
+ var NOTES_DB_PATH4 = path4.join(
43188
+ os4.homedir(),
43189
+ "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
43190
+ );
43191
+ var QUERY_SCAN = { DEFAULT: 500, MAX: 5e3 };
43192
+ var QUERY_RESULTS = { DEFAULT: 50, MAX: 500 };
43193
+ var CORE_DATA_EPOCH_MS = Date.UTC(2001, 0, 1);
43194
+ var SNIPPET_BEFORE = 60;
43195
+ var SNIPPET_LENGTH = 180;
43196
+ var QUERY_FDA_MESSAGE = `Full Disk Access is required to query notes. In System Settings > Privacy & Security > Full Disk Access, grant access to the app that launches this server (Claude Desktop / Terminal / iTerm2), then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
43197
+ var NoteQueryStoreError = class extends Error {
43198
+ constructor(message, kind) {
43199
+ super(message);
43200
+ this.kind = kind;
43201
+ this.name = "NoteQueryStoreError";
43202
+ }
43203
+ kind;
43204
+ };
43205
+ function facetsForAttachmentType(uti) {
43206
+ const type = uti.toLowerCase();
43207
+ if (type === "com.apple.notes.inlinetextattachment.hashtag") return [];
43208
+ if (type === "com.apple.notes.inlinetextattachment.link") return ["link"];
43209
+ if (type.startsWith("com.apple.notes.inlinetextattachment.")) return [];
43210
+ if (type === "com.apple.notes.table") return ["table"];
43211
+ const facets = ["attachment"];
43212
+ if (type === "public.url") facets.push("link");
43213
+ else if (type === "com.apple.paper.doc.scan" || type === "com.apple.notes.gallery")
43214
+ facets.push("scan");
43215
+ else if (type === "com.adobe.pdf" || type === "com.apple.paper.doc.pdf") facets.push("pdf");
43216
+ else if (type === "com.apple.paper" || type === "com.apple.drawing" || type === "com.apple.drawing.2" || type === "com.apple.notes.sketch")
43217
+ facets.push("drawing");
43218
+ else if (/^public\.(jpeg|png|heic|heif|tiff|gif|image|camera-raw-image|webp|bmp|svg-image)$/.test(
43219
+ type
43220
+ ) || type === "com.adobe.raw-image" || type === "com.compuserve.gif" || type === "org.webmproject.webp" || type === "com.microsoft.bmp")
43221
+ facets.push("image");
43222
+ else if (/^public\.(movie|video|mpeg-4|mpeg|avi|3gpp|3gpp2)$/.test(type) || type === "com.apple.quicktime-movie" || type === "com.apple.m4v-video")
43223
+ facets.push("video");
43224
+ else if (/^public\.(audio|mp3|mpeg-4-audio|aiff-audio|aifc-audio)$/.test(type) || type === "com.apple.m4a-audio" || type === "com.apple.coreaudio-format" || type === "com.microsoft.waveform-audio")
43225
+ facets.push("audio");
43226
+ return facets;
43227
+ }
43228
+ var CHECKLIST_STYLE = 103;
43229
+ function decodeNoteBody(data) {
43230
+ const wrapper = embeddedMessage(getField(decodeMessage(data), 2));
43231
+ const body = wrapper && embeddedMessage(getField(wrapper, 3));
43232
+ const text = body && stringValue(getField(body, 2));
43233
+ if (!body || text === void 0) return null;
43234
+ const facets = /* @__PURE__ */ new Set();
43235
+ const objectIds = /* @__PURE__ */ new Set();
43236
+ const checklist = /* @__PURE__ */ new Map();
43237
+ let position = 0;
43238
+ for (const run of getFields(body, 5)) {
43239
+ const fields = embeddedMessage(run);
43240
+ if (!fields) continue;
43241
+ const length = varintValue(getField(fields, 1)) ?? 0;
43242
+ if (stringValue(getField(fields, 9))) facets.add("link");
43243
+ const attachment = embeddedMessage(getField(fields, 12));
43244
+ if (attachment) {
43245
+ const id2 = stringValue(getField(attachment, 1));
43246
+ if (id2) objectIds.add(id2);
43247
+ for (const facet of facetsForAttachmentType(stringValue(getField(attachment, 2)) ?? ""))
43248
+ facets.add(facet);
43249
+ }
43250
+ const paragraph = embeddedMessage(getField(fields, 2));
43251
+ if (paragraph && varintValue(getField(paragraph, 1)) === CHECKLIST_STYLE) {
43252
+ const item = embeddedMessage(getField(paragraph, 5));
43253
+ const rawId = item && getField(item, 1)?.value;
43254
+ const key = rawId instanceof Uint8Array ? Buffer.from(rawId).toString("hex") : `line:${text.lastIndexOf("\n", position - 1) + 1}`;
43255
+ checklist.set(key, (item && varintValue(getField(item, 2))) === 1);
43256
+ }
43257
+ position += length;
43258
+ }
43259
+ if (checklist.size > 0) facets.add("checklist");
43260
+ const done = [...checklist.values()].filter(Boolean).length;
43261
+ return {
43262
+ text,
43263
+ facets,
43264
+ checklist: { total: checklist.size, open: checklist.size - done },
43265
+ objectIds
43266
+ };
43267
+ }
43268
+ function countWords(text) {
43269
+ let count = 0;
43270
+ for (const word of text.replace(/\ufffc/gu, " ").split(/\s+/u)) {
43271
+ if (/[\p{L}\p{N}]/u.test(word)) count++;
43272
+ }
43273
+ return count;
43274
+ }
43275
+ var REQUIRED_COLUMNS = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
43276
+ function col(available, alias, name) {
43277
+ return available.has(name) ? `${alias}.${name}` : "NULL";
43278
+ }
43279
+ var entity = (name) => `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME='${name}')`;
43280
+ function buildScanSql(available, options) {
43281
+ const missing = REQUIRED_COLUMNS.filter((c) => !available.has(c));
43282
+ if (missing.length) {
43283
+ throw new NoteQueryStoreError(
43284
+ `This macOS version's Notes database lacks columns query-notes needs (${missing.join(", ")}).`,
43285
+ "schema"
43286
+ );
43287
+ }
43288
+ const scan = Math.trunc(options.scanLimit);
43289
+ if (!Number.isInteger(scan) || scan < 1 || scan > QUERY_SCAN.MAX) {
43290
+ throw new NoteQueryStoreError(`scanLimit must be 1\u2013${QUERY_SCAN.MAX}`, "query_error");
43291
+ }
43292
+ const created = ["ZCREATIONDATE3", "ZCREATIONDATE1", "ZCREATIONDATE"].filter((c) => available.has(c)).map((c) => `n.${c}`).join(", ") || "NULL";
43293
+ const createdExpr = created.includes(",") ? `COALESCE(${created})` : created;
43294
+ const bool = (name) => available.has(name) ? `COALESCE(n.${name}, 0)` : "0";
43295
+ const notNull = (alias, name) => available.has(name) ? `${alias}.${name} IS NOT NULL` : "0";
43296
+ const tagsExpr = options.withTags && ["ZNOTE1", "ZTYPEUTI1", "ZALTTEXT", "ZIDENTIFIER"].every((c) => available.has(c)) ? `(SELECT json_group_array(json_array(t.ZIDENTIFIER, t.ZALTTEXT)) FROM ZICCLOUDSYNCINGOBJECT t WHERE t.ZNOTE1 = n.Z_PK AND t.ZTYPEUTI1 = 'com.apple.notes.inlinetextattachment.hashtag')` : "NULL";
43297
+ const where = [`n.Z_ENT = ${entity("ICNote")}`];
43298
+ if (!options.includeDeleted) {
43299
+ where.push("n.ZFOLDER IS NOT NULL");
43300
+ if (available.has("ZMARKEDFORDELETION")) where.push("COALESCE(n.ZMARKEDFORDELETION, 0) = 0");
43301
+ if (available.has("ZFOLDERTYPE")) {
43302
+ where.push(
43303
+ `n.ZFOLDER NOT IN (SELECT Z_PK FROM ZICCLOUDSYNCINGOBJECT WHERE Z_ENT = ${entity("ICFolder")} AND ZFOLDERTYPE = 1)`
43304
+ );
43305
+ }
43306
+ }
43307
+ const whereSql = where.join(" AND ");
43308
+ const folderRow = `SELECT json_object('k', 'folder', 'pk', f.Z_PK, 'name', ${col(available, "f", "ZTITLE2")}, 'parent', ${col(available, "f", "ZPARENT")}, 'type', ${col(available, "f", "ZFOLDERTYPE")}, 'owner', ${col(available, "f", "ZOWNER")}, 'shared', ${notNull("f", "ZSERVERSHAREDATA")}) FROM ZICCLOUDSYNCINGOBJECT f WHERE f.Z_ENT = ${entity("ICFolder")};`;
43309
+ const accountRow = `SELECT json_object('k', 'account', 'pk', a.Z_PK, 'name', ${col(available, "a", "ZNAME")}) FROM ZICCLOUDSYNCINGOBJECT a WHERE a.Z_ENT = ${entity("ICAccount")};`;
43310
+ const dataExpr = options.withBodies ? "(SELECT hex(d.ZDATA) FROM ZICNOTEDATA d WHERE d.ZNOTE = n.Z_PK ORDER BY d.Z_PK DESC LIMIT 1)" : "NULL";
43311
+ const noteRow = `SELECT json_object('k', 'note', 'pk', n.Z_PK, 'title', n.ZTITLE1, 'folder', n.ZFOLDER, 'created', ${createdExpr}, 'modified', n.ZMODIFICATIONDATE1, 'pinned', ${bool("ZISPINNED")}, 'locked', ${bool("ZISPASSWORDPROTECTED")}, 'shared', ${notNull("n", "ZSERVERSHAREDATA")}, 'snippet', ${col(available, "n", "ZSNIPPET")}, 'data', ${dataExpr}, 'tags', ${tagsExpr}) FROM ZICCLOUDSYNCINGOBJECT n WHERE ${whereSql} ORDER BY n.ZMODIFICATIONDATE1 DESC, n.Z_PK DESC LIMIT ${scan};`;
43312
+ return [
43313
+ "BEGIN;",
43314
+ "SELECT json_object('k', 'meta', 'uuid', (SELECT Z_UUID FROM Z_METADATA LIMIT 1));",
43315
+ `SELECT json_object('k', 'total', 'n', COUNT(*)) FROM ZICCLOUDSYNCINGOBJECT n WHERE ${whereSql};`,
43316
+ folderRow,
43317
+ accountRow,
43318
+ noteRow,
43319
+ "COMMIT;"
43320
+ ].join(" ");
43321
+ }
43322
+ function runSqlite2(dbPath2, query) {
43323
+ return execFileSync6("sqlite3", ["-readonly", dbPath2, query], {
43324
+ encoding: "utf8",
43325
+ timeout: 3e4,
43326
+ maxBuffer: 512 * 1024 * 1024,
43327
+ stdio: ["pipe", "pipe", "pipe"]
43328
+ });
43329
+ }
43330
+ function presentColumns2(dbPath2) {
43331
+ const cols = /* @__PURE__ */ new Set();
43332
+ for (const line of runSqlite2(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n")) {
43333
+ const name = line.split("|")[1];
43334
+ if (name) cols.add(name);
43335
+ }
43336
+ return cols;
43337
+ }
43338
+ var escapeSegment = escapeFolderName;
43339
+ function resolveFolders(rows) {
43340
+ const byPk = new Map(rows.map((row) => [row.pk, row]));
43341
+ const resolved = /* @__PURE__ */ new Map();
43342
+ const resolve3 = (pk, seen) => {
43343
+ const cached2 = resolved.get(pk);
43344
+ if (cached2) return cached2;
43345
+ const row = byPk.get(pk);
43346
+ if (!row || seen.has(pk)) return void 0;
43347
+ seen.add(pk);
43348
+ const parent = row.parent !== null ? resolve3(row.parent, seen) : void 0;
43349
+ const name = row.name ?? "";
43350
+ const path5 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
43351
+ const plainPath = parent ? `${parent.plainPath}/${name}` : name;
43352
+ const info = {
43353
+ path: path5,
43354
+ plainPath,
43355
+ keys: [...new Set([name, plainPath, path5].map(normalizeForMatch))],
43356
+ accountPk: row.owner ?? parent?.accountPk ?? null,
43357
+ shared: Boolean(row.shared) || Boolean(parent?.shared)
43358
+ };
43359
+ resolved.set(pk, info);
43360
+ return info;
43361
+ };
43362
+ for (const row of rows) resolve3(row.pk, /* @__PURE__ */ new Set());
43363
+ return resolved;
43364
+ }
43365
+ var coreDataMs = (seconds) => seconds === null || !Number.isFinite(seconds) ? void 0 : CORE_DATA_EPOCH_MS + seconds * 1e3;
43366
+ function buildSnippet(text, terms) {
43367
+ const clean = text.replace(/\ufffc/gu, " ");
43368
+ const firstBreak = clean.indexOf("\n");
43369
+ const body = firstBreak === -1 ? clean : clean.slice(firstBreak + 1);
43370
+ const lower = normalizeForMatch(body);
43371
+ let start = 0;
43372
+ if (lower.length === body.length) {
43373
+ const hits = terms.map((term) => lower.indexOf(normalizeForMatch(term))).filter((index) => index >= 0);
43374
+ if (hits.length) start = Math.max(0, Math.min(...hits) - SNIPPET_BEFORE);
43375
+ }
43376
+ const slice = body.slice(start, start + SNIPPET_LENGTH).replace(/\s+/gu, " ").trim();
43377
+ if (!slice) return "";
43378
+ return `${start > 0 ? "\u2026" : ""}${slice}${start + SNIPPET_LENGTH < body.length ? "\u2026" : ""}`;
43379
+ }
43380
+ function queryNotes(expression, options = {}) {
43381
+ const ast = parseNoteQuery(expression);
43382
+ const limit = Math.min(options.limit ?? QUERY_RESULTS.DEFAULT, QUERY_RESULTS.MAX);
43383
+ const scanLimit = Math.min(options.scanLimit ?? QUERY_SCAN.DEFAULT, QUERY_SCAN.MAX);
43384
+ const includeDeleted = options.includeDeleted ?? false;
43385
+ const withBodies = needsContent(ast);
43386
+ const withTags = needsTags(ast);
43387
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH4;
43388
+ if (!fs4.existsSync(dbPath2)) throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
43389
+ let output;
43390
+ try {
43391
+ const available = presentColumns2(dbPath2);
43392
+ output = runSqlite2(
43393
+ dbPath2,
43394
+ buildScanSql(available, { scanLimit, includeDeleted, withBodies, withTags })
43395
+ );
43396
+ } catch (error2) {
43397
+ if (error2 instanceof NoteQueryStoreError) throw error2;
43398
+ const message = error2 instanceof Error ? error2.message : String(error2);
43399
+ if (message.includes("authorization denied") || message.includes("unable to open database")) {
43400
+ throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
43401
+ }
43402
+ console.error(`query-notes: database read failed: ${message}`);
43403
+ throw new NoteQueryStoreError("Failed to read the Notes database.", "query_error");
43404
+ }
43405
+ let uuid2;
43406
+ let eligible = 0;
43407
+ const folderRows = [];
43408
+ const accounts = /* @__PURE__ */ new Map();
43409
+ const notes = [];
43410
+ for (const line of output.split("\n")) {
43411
+ if (!line.trim()) continue;
43412
+ const row = JSON.parse(line);
43413
+ if (row.k === "meta") uuid2 = typeof row.uuid === "string" ? row.uuid : void 0;
43414
+ else if (row.k === "total") eligible = Number(row.n) || 0;
43415
+ else if (row.k === "folder") folderRows.push(row);
43416
+ else if (row.k === "account" && typeof row.name === "string")
43417
+ accounts.set(Number(row.pk), row.name);
43418
+ else if (row.k === "note") notes.push(row);
43419
+ }
43420
+ if (!uuid2 || !/^[0-9A-Fa-f-]+$/.test(uuid2)) {
43421
+ throw new NoteQueryStoreError(
43422
+ "The Notes database has no store identifier, so note IDs cannot be formed.",
43423
+ "schema"
43424
+ );
43425
+ }
43426
+ const folders = resolveFolders(folderRows);
43427
+ const textTerms = positiveTextTerms(ast);
43428
+ const hits = [];
43429
+ let matched = 0;
43430
+ let unreadable = 0;
43431
+ for (const row of notes) {
43432
+ const folder = row.folder !== null ? folders.get(row.folder) : void 0;
43433
+ const account = folder?.accountPk !== null && folder?.accountPk !== void 0 ? accounts.get(folder.accountPk) : void 0;
43434
+ const locked = Boolean(row.locked);
43435
+ let decoded;
43436
+ let content;
43437
+ const decode = () => {
43438
+ if (decoded !== void 0) return decoded;
43439
+ decoded = null;
43440
+ if (locked) return decoded;
43441
+ if (!row.data) {
43442
+ if (withBodies) unreadable++;
43443
+ return decoded;
43444
+ }
43445
+ try {
43446
+ decoded = decodeNoteBody(
43447
+ new Uint8Array(
43448
+ gunzipSync3(Buffer.from(row.data, "hex"), { maxOutputLength: 32 * 1024 * 1024 })
43449
+ )
43450
+ );
43451
+ } catch {
43452
+ decoded = null;
43453
+ }
43454
+ if (!decoded) unreadable++;
43455
+ return decoded;
43456
+ };
43457
+ const note = {
43458
+ titleLower: normalizeForMatch(row.title ?? ""),
43459
+ folderKeys: folder?.keys ?? [],
43460
+ accountLower: account !== void 0 ? normalizeForMatch(account) : void 0,
43461
+ pinned: Boolean(row.pinned),
43462
+ locked,
43463
+ shared: Boolean(row.shared) || Boolean(folder?.shared),
43464
+ created: coreDataMs(row.created),
43465
+ modified: coreDataMs(row.modified),
43466
+ content: () => {
43467
+ if (content !== void 0) return content;
43468
+ const body2 = decode();
43469
+ if (!body2) return content = null;
43470
+ const text = body2.text;
43471
+ const firstBreak = text.indexOf("\n");
43472
+ const tags = /* @__PURE__ */ new Set();
43473
+ for (const [id2, alt] of row.tags ?? []) {
43474
+ if (id2 && alt && body2.objectIds.has(id2))
43475
+ tags.add(normalizeForMatch(alt.replace(/^#/, "")));
43476
+ }
43477
+ const facets = new Set(body2.facets);
43478
+ if (tags.size) facets.add("tag");
43479
+ content = {
43480
+ textLower: normalizeForMatch(text),
43481
+ bodyLower: normalizeForMatch(firstBreak === -1 ? "" : text.slice(firstBreak + 1)),
43482
+ words: countWords(text),
43483
+ facets,
43484
+ checklist: body2.checklist,
43485
+ tags: [...tags]
43486
+ };
43487
+ return content;
43488
+ }
43489
+ };
43490
+ if (!evaluateNoteQuery(ast, note)) continue;
43491
+ matched++;
43492
+ if (hits.length >= limit) continue;
43493
+ const body = locked ? null : decode();
43494
+ hits.push({
43495
+ id: `x-coredata://${uuid2}/ICNote/p${row.pk}`,
43496
+ title: row.title ?? "",
43497
+ ...folder ? { folder: folder.path } : {},
43498
+ ...account !== void 0 ? { account } : {},
43499
+ ...note.modified !== void 0 ? { modified: new Date(note.modified).toISOString() } : {},
43500
+ ...note.created !== void 0 ? { created: new Date(note.created).toISOString() } : {},
43501
+ snippet: locked ? "" : body ? buildSnippet(body.text, textTerms) : (row.snippet ?? "").replace(/\s+/gu, " ").trim(),
43502
+ ...locked ? { locked: true } : {}
43503
+ });
43504
+ }
43505
+ return {
43506
+ notes: hits,
43507
+ count: hits.length,
43508
+ matched,
43509
+ scanned: notes.length,
43510
+ eligible,
43511
+ scanLimit,
43512
+ scanTruncated: eligible > notes.length,
43513
+ limit,
43514
+ truncated: matched > hits.length,
43515
+ unreadable
43516
+ };
43517
+ }
43518
+
42736
43519
  // src/tools/doctor.ts
42737
43520
  import { spawnSync } from "child_process";
42738
43521
 
42739
43522
  // src/services/nativeTags.ts
42740
- import { execFileSync as execFileSync6 } from "node:child_process";
43523
+ import { execFileSync as execFileSync7 } from "node:child_process";
42741
43524
  import { mkdtempSync as mkdtempSync2, writeFileSync, rmSync as rmSync2 } from "node:fs";
42742
43525
  import { tmpdir as tmpdir2 } from "node:os";
42743
- import { join as join7 } from "node:path";
43526
+ import { join as join8 } from "node:path";
42744
43527
 
42745
43528
  // src/services/shortcutConsent.ts
42746
43529
  function shortcutConsentHint(shortcut) {
@@ -42815,13 +43598,19 @@ function addNativeTags(request, deps) {
42815
43598
  } : {}
42816
43599
  };
42817
43600
  }
42818
- function nativeTagsStatus(shortcut = process.env.APPLE_NOTES_MCP_TAGS_SHORTCUT || NATIVE_TAGS_SHORTCUT) {
42819
- const lines = execFileSync6("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
43601
+ function listInstalledShortcuts() {
43602
+ return execFileSync7("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
42820
43603
  encoding: "utf8",
42821
43604
  timeout: 15e3,
42822
43605
  maxBuffer: 1024 * 1024,
42823
43606
  stdio: ["ignore", "pipe", "pipe"]
42824
43607
  }).split(/\r?\n/u);
43608
+ }
43609
+ var nativeTagsShortcutName = () => process.env.APPLE_NOTES_MCP_TAGS_SHORTCUT || NATIVE_TAGS_SHORTCUT;
43610
+ function nativeTagsStatus(shortcut = nativeTagsShortcutName()) {
43611
+ return resolveShortcut(listInstalledShortcuts(), shortcut);
43612
+ }
43613
+ function resolveShortcut(lines, shortcut) {
42825
43614
  const matches = lines.flatMap((line) => {
42826
43615
  const match = /^(.*) \(([0-9A-Fa-f-]{36})\)$/.exec(line);
42827
43616
  return match && (match[1] === shortcut || match[2].toLowerCase() === shortcut.toLowerCase()) ? [{ name: match[1], identifier: match[2] }] : [];
@@ -42836,11 +43625,11 @@ function runNativeTagsShortcut(input) {
42836
43625
  const status = nativeTagsStatus();
42837
43626
  if (!status.installed)
42838
43627
  throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
42839
- const directory = mkdtempSync2(join7(tmpdir2(), "apple-notes-native-tags-"));
43628
+ const directory = mkdtempSync2(join8(tmpdir2(), "apple-notes-native-tags-"));
42840
43629
  try {
42841
- const path4 = join7(directory, "request.json");
42842
- writeFileSync(path4, JSON.stringify(input), { mode: 384 });
42843
- execFileSync6("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path4], {
43630
+ const path5 = join8(directory, "request.json");
43631
+ writeFileSync(path5, JSON.stringify(input), { mode: 384 });
43632
+ execFileSync7("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path5], {
42844
43633
  encoding: "utf8",
42845
43634
  timeout: 6e4,
42846
43635
  maxBuffer: 1024 * 1024,
@@ -42859,10 +43648,10 @@ function runNativeTagsShortcut(input) {
42859
43648
  }
42860
43649
 
42861
43650
  // src/services/backgroundNotes.ts
42862
- import { execFileSync as execFileSync7 } from "node:child_process";
43651
+ import { execFileSync as execFileSync8 } from "node:child_process";
42863
43652
  import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
42864
43653
  import { tmpdir as tmpdir3 } from "node:os";
42865
- import { join as join8 } from "node:path";
43654
+ import { join as join9 } from "node:path";
42866
43655
 
42867
43656
  // src/utils/appendMarkdown.ts
42868
43657
  var escape2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -42938,10 +43727,12 @@ function comparableVisibleText(html) {
42938
43727
 
42939
43728
  // src/services/backgroundNotes.ts
42940
43729
  var BACKGROUND_SHORTCUT = "Apple Notes MCP - Background Operations v5";
42941
- var backgroundStatus = () => nativeTagsStatus(process.env.APPLE_NOTES_MCP_BACKGROUND_SHORTCUT || BACKGROUND_SHORTCUT);
43730
+ var backgroundShortcutName = () => process.env.APPLE_NOTES_MCP_BACKGROUND_SHORTCUT || BACKGROUND_SHORTCUT;
43731
+ var backgroundStatus = () => nativeTagsStatus(backgroundShortcutName());
42942
43732
  var nativeTagBridgeStatus = () => nativeTagsStatus();
42943
43733
  var MARKDOWN_NOTE_SHORTCUT = "Apple Notes MCP - Create Markdown Note";
42944
- var markdownNoteStatus = () => nativeTagsStatus(process.env.APPLE_NOTES_MCP_MARKDOWN_SHORTCUT || MARKDOWN_NOTE_SHORTCUT);
43734
+ var markdownShortcutName = () => process.env.APPLE_NOTES_MCP_MARKDOWN_SHORTCUT || MARKDOWN_NOTE_SHORTCUT;
43735
+ var markdownNoteStatus = () => nativeTagsStatus(markdownShortcutName());
42945
43736
  function readBackgroundSnapshot(manager, id2) {
42946
43737
  if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(id2))
42947
43738
  throw new Error("Exact note ID required");
@@ -43049,9 +43840,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
43049
43840
  throw new Error(
43050
43841
  `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
43051
43842
  );
43052
- const directory = mkdtempSync3(join8(tmpdir3(), "apple-notes-background-"));
43843
+ const directory = mkdtempSync3(join9(tmpdir3(), "apple-notes-background-"));
43053
43844
  try {
43054
- const file = join8(directory, "request.json");
43845
+ const file = join9(directory, "request.json");
43055
43846
  writeFileSync2(
43056
43847
  file,
43057
43848
  JSON.stringify({
@@ -43071,7 +43862,7 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
43071
43862
  { mode: 384 }
43072
43863
  );
43073
43864
  try {
43074
- execFileSync7("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
43865
+ execFileSync8("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
43075
43866
  encoding: "utf8",
43076
43867
  timeout: 6e4,
43077
43868
  maxBuffer: 1024 * 1024,
@@ -43274,7 +44065,7 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
43274
44065
  throw new Error(
43275
44066
  `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
43276
44067
  );
43277
- const segments = (path4) => JSON.stringify(splitFolderPath(path4).map((part) => part.toLocaleLowerCase()));
44068
+ const segments = (path5) => JSON.stringify(splitFolderPath(path5).map((part) => part.toLocaleLowerCase()));
43278
44069
  if (request.folder) {
43279
44070
  const wanted = segments(request.folder);
43280
44071
  if (!manager.listAccounts().some(
@@ -43362,8 +44153,261 @@ ${request.content}`
43362
44153
  }
43363
44154
  }
43364
44155
 
44156
+ // src/services/capabilityMatrix.ts
44157
+ import { execFileSync as execFileSync9 } from "node:child_process";
44158
+ import { release } from "node:os";
44159
+ var MACOS_SHORTCUTS_CLI = "12.0";
44160
+ var MACOS_MARKDOWN_IMPORT = "26.0";
44161
+ var FEATURES = [
44162
+ {
44163
+ name: "applescriptCore",
44164
+ description: "Create, read, search, update, move, and delete notes and folders through Notes.app's AppleScript interface",
44165
+ tools: [
44166
+ "create-note",
44167
+ "search-notes",
44168
+ "get-note-content",
44169
+ "update-note",
44170
+ "append-to-note",
44171
+ "insert-link",
44172
+ "delete-note",
44173
+ "move-note",
44174
+ "list-notes",
44175
+ "list-folders",
44176
+ "create-folder",
44177
+ "list-accounts"
44178
+ ],
44179
+ minimumMacOSVersion: null,
44180
+ requirements: [{ kind: "notes_automation" }]
44181
+ },
44182
+ {
44183
+ name: "fullDiskAccessReads",
44184
+ description: "Read-only reads of the Notes database: query-notes, checklist state, note metadata, note links, native objects, native tags, and sync detail",
44185
+ tools: [
44186
+ "query-notes",
44187
+ "get-checklist-state",
44188
+ "get-note-metadata",
44189
+ "get-note-link",
44190
+ "get-native-objects",
44191
+ "list-native-tags",
44192
+ "get-note-markdown (checklist annotations)",
44193
+ "get-sync-status (pending uploads)"
44194
+ ],
44195
+ minimumMacOSVersion: null,
44196
+ requirements: [{ kind: "full_disk_access" }]
44197
+ },
44198
+ {
44199
+ name: "shortcutsBridges",
44200
+ description: "Run the optional native-write Shortcut bridges in the background",
44201
+ tools: [],
44202
+ minimumMacOSVersion: MACOS_SHORTCUTS_CLI,
44203
+ requirements: [{ kind: "shortcuts_cli" }]
44204
+ },
44205
+ {
44206
+ name: "backgroundOperationsBridge",
44207
+ description: "Native background edits through the Background Operations bridge, verified by exact-ID database readback",
44208
+ tools: [
44209
+ "append-native",
44210
+ "create-checklist-item",
44211
+ "create-checklist-items",
44212
+ "create-table",
44213
+ "insert-note-link",
44214
+ "set-note-pinned",
44215
+ "remove-native-tags"
44216
+ ],
44217
+ minimumMacOSVersion: MACOS_SHORTCUTS_CLI,
44218
+ requirements: [
44219
+ { kind: "notes_automation" },
44220
+ { kind: "full_disk_access" },
44221
+ { kind: "shortcuts_cli" },
44222
+ { kind: "shortcut", name: backgroundShortcutName }
44223
+ ]
44224
+ },
44225
+ {
44226
+ name: "nativeTagsBridge",
44227
+ description: "Add real Notes tags through the Native Tags bridge",
44228
+ tools: ["add-native-tags", "replace-native-tag"],
44229
+ minimumMacOSVersion: MACOS_SHORTCUTS_CLI,
44230
+ requirements: [
44231
+ { kind: "notes_automation" },
44232
+ { kind: "full_disk_access" },
44233
+ { kind: "shortcuts_cli" },
44234
+ { kind: "shortcut", name: nativeTagsShortcutName }
44235
+ ]
44236
+ },
44237
+ {
44238
+ name: "markdownNoteBridge",
44239
+ description: "Create a note from Markdown with real Title, Heading, and Subheading styles through the Create Markdown Note bridge",
44240
+ tools: ['create-note (format: "markdown")'],
44241
+ minimumMacOSVersion: MACOS_MARKDOWN_IMPORT,
44242
+ requirements: [
44243
+ { kind: "notes_automation" },
44244
+ { kind: "full_disk_access" },
44245
+ { kind: "shortcuts_cli" },
44246
+ { kind: "shortcut", name: markdownShortcutName }
44247
+ ]
44248
+ },
44249
+ // Placeholders for features that need a native helper this server does not
44250
+ // ship. They always report `not_implemented` so clients can detect them.
44251
+ {
44252
+ name: "checklistToggle",
44253
+ description: "Check or uncheck an existing checklist item in place",
44254
+ tools: [],
44255
+ minimumMacOSVersion: null,
44256
+ requirements: [{ kind: "native_helper" }]
44257
+ },
44258
+ {
44259
+ name: "smartFolders",
44260
+ description: "Create or edit Smart Folders and their tag rules",
44261
+ tools: [],
44262
+ minimumMacOSVersion: null,
44263
+ requirements: [{ kind: "native_helper" }]
44264
+ },
44265
+ {
44266
+ name: "paragraphLinks",
44267
+ description: "Link to a specific paragraph or heading inside a note",
44268
+ tools: [],
44269
+ minimumMacOSVersion: null,
44270
+ requirements: [{ kind: "native_helper" }]
44271
+ },
44272
+ {
44273
+ name: "audioTranscription",
44274
+ description: "Transcribe audio recordings attached to a note",
44275
+ tools: [],
44276
+ minimumMacOSVersion: null,
44277
+ requirements: [{ kind: "native_helper" }]
44278
+ }
44279
+ ];
44280
+ function requirementLabel(requirement) {
44281
+ return requirement.kind === "shortcut" ? `shortcut:${requirement.name()}` : requirement.kind;
44282
+ }
44283
+ function compareVersions(a, b) {
44284
+ const pa = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
44285
+ const pb = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
44286
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
44287
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
44288
+ if (diff !== 0) return diff;
44289
+ }
44290
+ return 0;
44291
+ }
44292
+ function evaluateFeature(feature, env) {
44293
+ const isMac = env.platform === "darwin";
44294
+ const floor = feature.minimumMacOSVersion;
44295
+ const versionKnown = env.macOSVersion !== null;
44296
+ const osSupported = isMac && (floor === null || versionKnown && compareVersions(env.macOSVersion, floor) >= 0);
44297
+ const missing = [];
44298
+ const unverified = [];
44299
+ const failures = /* @__PURE__ */ new Set();
44300
+ for (const requirement of feature.requirements) {
44301
+ const label = requirementLabel(requirement);
44302
+ switch (requirement.kind) {
44303
+ case "notes_automation":
44304
+ unverified.push(label);
44305
+ break;
44306
+ case "full_disk_access":
44307
+ if (!env.fullDiskAccess) {
44308
+ missing.push(label);
44309
+ failures.add("full_disk_access_missing");
44310
+ }
44311
+ break;
44312
+ case "shortcuts_cli":
44313
+ if (env.shortcutLines === null) {
44314
+ missing.push(label);
44315
+ failures.add("shortcuts_unavailable");
44316
+ }
44317
+ break;
44318
+ case "shortcut":
44319
+ if (env.shortcutLines === null) unverified.push(label);
44320
+ else if (!resolveShortcut(env.shortcutLines, requirement.name()).installed) {
44321
+ missing.push(label);
44322
+ failures.add("shortcut_not_installed");
44323
+ }
44324
+ break;
44325
+ case "native_helper":
44326
+ missing.push(label);
44327
+ failures.add("not_implemented");
44328
+ break;
44329
+ }
44330
+ }
44331
+ let reason = null;
44332
+ if (!isMac) reason = "unsupported_platform";
44333
+ else if (failures.has("not_implemented")) reason = "not_implemented";
44334
+ else if (floor !== null && !versionKnown) reason = "unknown_os_version";
44335
+ else if (!osSupported) reason = `requires_macos_${Number.parseInt(floor, 10)}`;
44336
+ else if (failures.has("full_disk_access_missing")) reason = "full_disk_access_missing";
44337
+ else if (failures.has("shortcuts_unavailable")) reason = "shortcuts_unavailable";
44338
+ else if (failures.has("shortcut_not_installed")) reason = "shortcut_not_installed";
44339
+ return {
44340
+ description: feature.description,
44341
+ tools: feature.tools,
44342
+ available: reason === null,
44343
+ osSupported,
44344
+ minimumMacOSVersion: floor,
44345
+ requirements: feature.requirements.map(requirementLabel),
44346
+ missing,
44347
+ unverified,
44348
+ reason
44349
+ };
44350
+ }
44351
+ function evaluateFeatures(env, features = FEATURES) {
44352
+ return {
44353
+ runtimeOS: {
44354
+ platform: env.platform,
44355
+ macOSVersion: env.macOSVersion,
44356
+ darwinRelease: env.darwinRelease
44357
+ },
44358
+ features: Object.fromEntries(features.map((f) => [f.name, evaluateFeature(f, env)]))
44359
+ };
44360
+ }
44361
+ function readMacOSVersion() {
44362
+ if (process.platform !== "darwin") return null;
44363
+ try {
44364
+ const out = execFileSync9("/usr/bin/sw_vers", ["-productVersion"], {
44365
+ encoding: "utf8",
44366
+ timeout: 3e3,
44367
+ stdio: ["ignore", "pipe", "ignore"]
44368
+ }).trim();
44369
+ return /^\d+(\.\d+)*$/.test(out) ? out : null;
44370
+ } catch {
44371
+ return null;
44372
+ }
44373
+ }
44374
+ function probeCapabilityEnvironment() {
44375
+ const platform = process.platform;
44376
+ const isMac = platform === "darwin";
44377
+ let shortcutLines = null;
44378
+ if (isMac) {
44379
+ try {
44380
+ shortcutLines = listInstalledShortcuts();
44381
+ } catch {
44382
+ shortcutLines = null;
44383
+ }
44384
+ }
44385
+ return {
44386
+ platform,
44387
+ macOSVersion: readMacOSVersion(),
44388
+ darwinRelease: release(),
44389
+ fullDiskAccess: isMac && hasFullDiskAccess(),
44390
+ shortcutLines
44391
+ };
44392
+ }
44393
+ function getCapabilityMatrix(probe = probeCapabilityEnvironment) {
44394
+ return evaluateFeatures(probe());
44395
+ }
44396
+ function formatCapabilityMatrix(matrix) {
44397
+ const os5 = matrix.runtimeOS;
44398
+ const lines = [
44399
+ `Feature matrix (macOS ${os5.macOSVersion ?? "unknown"}, Darwin ${os5.darwinRelease}):`
44400
+ ];
44401
+ for (const [name, status] of Object.entries(matrix.features)) {
44402
+ lines.push(
44403
+ status.available ? ` \u2713 ${name}: available${status.unverified.length ? ` (unverified: ${status.unverified.join(", ")})` : ""}` : ` \u2717 ${name}: ${status.reason}${status.missing.length ? ` (missing: ${status.missing.join(", ")})` : ""}`
44404
+ );
44405
+ }
44406
+ return lines.join("\n");
44407
+ }
44408
+
43365
44409
  // src/tools/doctor.ts
43366
- function runDoctor(manager) {
44410
+ function runDoctor(manager, capabilityMatrix = getCapabilityMatrix) {
43367
44411
  const checks = [];
43368
44412
  const hc = manager.healthCheck();
43369
44413
  for (const c of hc.checks) {
@@ -43414,7 +44458,13 @@ function runDoctor(manager) {
43414
44458
  }
43415
44459
  checks.push(checkNodeRuntimeSignature());
43416
44460
  const healthy = !checks.some((c) => c.status === "fail");
43417
- return { healthy, checks };
44461
+ let matrix;
44462
+ try {
44463
+ matrix = capabilityMatrix();
44464
+ } catch {
44465
+ matrix = void 0;
44466
+ }
44467
+ return matrix ? { healthy, checks, runtimeOS: matrix.runtimeOS, features: matrix.features } : { healthy, checks };
43418
44468
  }
43419
44469
  function markdownBridgeDetail() {
43420
44470
  const purpose = 'needed only for create-note format: "markdown" on macOS 26+';
@@ -43458,23 +44508,25 @@ function formatDoctorReport(r) {
43458
44508
  const icon = (s) => s === "ok" ? "\u2705" : s === "warn" ? "\u26A0\uFE0F " : "\u274C";
43459
44509
  const lines = [`\u{1FA7A} apple-notes-mcp doctor \u2014 ${r.healthy ? "healthy" : "ISSUES FOUND"}`, ""];
43460
44510
  for (const c of r.checks) lines.push(`${icon(c.status)} ${c.name}: ${c.detail}`);
44511
+ if (r.runtimeOS && r.features)
44512
+ lines.push("", formatCapabilityMatrix({ runtimeOS: r.runtimeOS, features: r.features }));
43461
44513
  return lines.join("\n");
43462
44514
  }
43463
44515
 
43464
44516
  // src/services/fileConfig.ts
43465
- import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
43466
- import { join as join9 } from "path";
43467
- import { homedir as homedir7 } from "os";
44517
+ import { existsSync as existsSync7, readFileSync as readFileSync2 } from "fs";
44518
+ import { join as join10 } from "path";
44519
+ import { homedir as homedir8 } from "os";
43468
44520
  function fileConfigPath(env = process.env) {
43469
44521
  const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
43470
44522
  if (override && override.trim()) return override.trim();
43471
- return join9(homedir7(), "Library", "Application Support", "apple-notes-mcp", "config.json");
44523
+ return join10(homedir8(), "Library", "Application Support", "apple-notes-mcp", "config.json");
43472
44524
  }
43473
- function loadFileConfig(env = process.env, path4 = fileConfigPath(env)) {
44525
+ function loadFileConfig(env = process.env, path5 = fileConfigPath(env)) {
43474
44526
  const applied = [];
43475
44527
  try {
43476
- if (!existsSync6(path4)) return applied;
43477
- const parsed = JSON.parse(readFileSync2(path4, "utf8"));
44528
+ if (!existsSync7(path5)) return applied;
44529
+ const parsed = JSON.parse(readFileSync2(path5, "utf8"));
43478
44530
  if (!parsed || typeof parsed !== "object") return applied;
43479
44531
  for (const [k, v] of Object.entries(parsed)) {
43480
44532
  if (typeof v !== "string") continue;
@@ -43484,7 +44536,7 @@ function loadFileConfig(env = process.env, path4 = fileConfigPath(env)) {
43484
44536
  }
43485
44537
  }
43486
44538
  } catch (e) {
43487
- console.error(`Failed to load apple-notes-mcp config file ${path4}: ${String(e)}`);
44539
+ console.error(`Failed to load apple-notes-mcp config file ${path5}: ${String(e)}`);
43488
44540
  }
43489
44541
  return applied;
43490
44542
  }
@@ -43700,7 +44752,7 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
43700
44752
  }
43701
44753
 
43702
44754
  // src/utils/noteTables.ts
43703
- import { gunzipSync as gunzipSync3 } from "node:zlib";
44755
+ import { gunzipSync as gunzipSync4 } from "node:zlib";
43704
44756
  var sub = (f, n) => {
43705
44757
  const value = embeddedMessage(getField(f, n));
43706
44758
  if (!value) throw new Error(`Missing table field ${n}`);
@@ -43721,7 +44773,7 @@ var hex = (f) => {
43721
44773
  return Buffer.from(f.value).toString("hex");
43722
44774
  };
43723
44775
  function parseNoteTable(compressed) {
43724
- const root = decodeMessage(gunzipSync3(compressed, { maxOutputLength: 16 * 1024 * 1024 }));
44776
+ const root = decodeMessage(gunzipSync4(compressed, { maxOutputLength: 16 * 1024 * 1024 }));
43725
44777
  const data = sub(sub(root, 2), 3), entries = many(data, 3);
43726
44778
  if (entries.length > 1e5) throw new Error("Table too large");
43727
44779
  const keys = getFields(data, 4).map(stringValue), types = getFields(data, 5).map(stringValue), uuids = getFields(data, 6).map(hex);
@@ -43924,7 +44976,7 @@ import {
43924
44976
  writeFileSync as writeFileSync3
43925
44977
  } from "node:fs";
43926
44978
  import { tmpdir as tmpdir4 } from "node:os";
43927
- import { basename, isAbsolute as isAbsolute2, join as join10 } from "node:path";
44979
+ import { basename, isAbsolute as isAbsolute2, join as join11 } from "node:path";
43928
44980
  var noteId = external_exports.string().regex(/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i);
43929
44981
  var revision = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
43930
44982
  function readSnapshot(manager, id2) {
@@ -43962,9 +45014,9 @@ function assertExistingContentPreserved(before, after) {
43962
45014
  throw new Error("Existing native object content or presentation changed");
43963
45015
  }
43964
45016
  }
43965
- function localAttachment(path4) {
43966
- if (!isAbsolute2(path4)) throw new Error("An absolute local file path is required");
43967
- const descriptor = openSync(path4, constants.O_RDONLY | constants.O_NOFOLLOW);
45017
+ function localAttachment(path5) {
45018
+ if (!isAbsolute2(path5)) throw new Error("An absolute local file path is required");
45019
+ const descriptor = openSync(path5, constants.O_RDONLY | constants.O_NOFOLLOW);
43968
45020
  try {
43969
45021
  const stat = fstatSync(descriptor);
43970
45022
  if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
@@ -44033,13 +45085,13 @@ function registerDirectOperations(server2, manager) {
44033
45085
  "add-attachment",
44034
45086
  "Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, 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.",
44035
45087
  { id: noteId, expectedContentHash: revision, path: external_exports.string().min(1).max(4096) },
44036
- ({ id: id2, expectedContentHash, path: path4 }) => {
45088
+ ({ id: id2, expectedContentHash, path: path5 }) => {
44037
45089
  const before = readSnapshot(manager, id2);
44038
45090
  if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
44039
- const bytes = localAttachment(path4);
45091
+ const bytes = localAttachment(path5);
44040
45092
  const beforeAttachments = manager.listAttachmentsById(id2);
44041
- const directory = mkdtempSync4(join10(tmpdir4(), "notes-attachment-add-"));
44042
- const temporaryFile = join10(directory, basename(path4));
45093
+ const directory = mkdtempSync4(join11(tmpdir4(), "notes-attachment-add-"));
45094
+ const temporaryFile = join11(directory, basename(path5));
44043
45095
  try {
44044
45096
  writeFileSync3(temporaryFile, bytes, { mode: 384 });
44045
45097
  if (readSnapshot(manager, id2).hash !== before.hash)
@@ -44320,7 +45372,7 @@ function registerNativeOperations(server2, manager) {
44320
45372
  }
44321
45373
  tool(
44322
45374
  "get-capabilities",
44323
- "Use when: checking native background-edit support before calling a write tool.\nReturns: bridge installation, implementation, verification, availability, and specific limitations per operation.\nDo not use when: checking only the Native Tags bridge (native-tags-status).\nSafety: read-only; does not open Notes or run a mutation.",
45375
+ "Use when: checking native background-edit support or which feature groups this Mac supports before calling a tool.\nReturns: bridge installation, implementation, verification, availability, and specific limitations per operation; plus runtimeOS and an OS-version-aware features matrix (available, osSupported, minimumMacOSVersion, requirements, missing, unverified, machine reason code) per feature group.\nDo not use when: checking only the Native Tags bridge (native-tags-status).\nSafety: read-only; does not open Notes or run a mutation.",
44324
45376
  {},
44325
45377
  () => {
44326
45378
  let bridge;
@@ -44370,7 +45422,8 @@ function registerNativeOperations(server2, manager) {
44370
45422
  }
44371
45423
  ])
44372
45424
  ),
44373
- unavailable: UNAVAILABLE
45425
+ unavailable: UNAVAILABLE,
45426
+ ...getCapabilityMatrix()
44374
45427
  };
44375
45428
  },
44376
45429
  true
@@ -44596,8 +45649,8 @@ function registerNativeOperations(server2, manager) {
44596
45649
 
44597
45650
  // src/setupShortcuts.ts
44598
45651
  import { spawnSync as spawnSync2 } from "node:child_process";
44599
- import { existsSync as existsSync7 } from "node:fs";
44600
- import { release } from "node:os";
45652
+ import { existsSync as existsSync8 } from "node:fs";
45653
+ import { release as release2 } from "node:os";
44601
45654
  import { dirname as dirname2, resolve as resolve2 } from "node:path";
44602
45655
  import { fileURLToPath } from "node:url";
44603
45656
  var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
@@ -44616,16 +45669,16 @@ var shortcutFiles = [
44616
45669
  ];
44617
45670
  function setupShortcuts(checkOnly, dependencies = {}) {
44618
45671
  const status = dependencies.status || nativeTagsStatus;
44619
- const exists = dependencies.exists || existsSync7;
44620
- const open = dependencies.open || ((path4) => {
44621
- const result = spawnSync2("/usr/bin/open", [path4], { encoding: "utf8" });
45672
+ const exists = dependencies.exists || existsSync8;
45673
+ const open = dependencies.open || ((path5) => {
45674
+ const result = spawnSync2("/usr/bin/open", [path5], { encoding: "utf8" });
44622
45675
  return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
44623
45676
  });
44624
45677
  const baseDirectory = dependencies.baseDirectory || resolve2(dirname2(fileURLToPath(import.meta.url)), "../shortcuts");
44625
- const osRelease = (dependencies.osRelease || release)();
45678
+ const osRelease = (dependencies.osRelease || release2)();
44626
45679
  const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
44627
45680
  const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
44628
- const path4 = resolve2(baseDirectory, file);
45681
+ const path5 = resolve2(baseDirectory, file);
44629
45682
  let installed = false;
44630
45683
  let identifier;
44631
45684
  let error2;
@@ -44641,9 +45694,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
44641
45694
  if (!installed && !checkOnly) {
44642
45695
  if (optional2 && !(darwinMajor >= MARKDOWN_MIN_DARWIN_MAJOR))
44643
45696
  skipped = `requires macOS 26 or later (this Mac reports Darwin ${osRelease})`;
44644
- else if (!exists(path4)) error2 = `Packaged Shortcut is missing: ${path4}`;
45697
+ else if (!exists(path5)) error2 = `Packaged Shortcut is missing: ${path5}`;
44645
45698
  else {
44646
- const result = open(path4);
45699
+ const result = open(path5);
44647
45700
  opened = result.ok;
44648
45701
  if (!result.ok) error2 = result.error || `Could not open ${file}`;
44649
45702
  }
@@ -44652,7 +45705,7 @@ function setupShortcuts(checkOnly, dependencies = {}) {
44652
45705
  name,
44653
45706
  installed,
44654
45707
  identifier,
44655
- file: path4,
45708
+ file: path5,
44656
45709
  opened,
44657
45710
  ...optional2 ? { optional: optional2 } : {},
44658
45711
  ...skipped ? { skipped } : {},
@@ -44924,6 +45977,81 @@ ${noteList}${truncationNote}${syncNote}`,
44924
45977
  );
44925
45978
  }, "Error searching notes")
44926
45979
  );
45980
+ registerTool(
45981
+ "query-notes",
45982
+ {
45983
+ description: 'Use when: finding notes with a boolean expression over text and metadata \u2014 e.g. `folder:Work has:checklist -checklist:done`, `(title:invoice OR tag:finance) modified:>=2026-07-01`, `pinned words:>250`. Reads the Notes database directly, so it is fast and can match title OR body in one call.\nSyntax: bare words and "quoted phrases" match title or body (case-insensitive substring); fields title:, body:, text:, folder:, account:, tag: (values may be quoted, e.g. folder:"Work Projects"); facets has:link|attachment|checklist|drawing|image|video|audio|pdf|table|scan|tag; checklist:open|done; flags pinned, locked, shared (or is:pinned); words:>250 and created:/modified: with =, >, >=, <, <= and YYYY-MM-DD local dates. AND is implicit; OR, NOT, leading -, and parentheses are supported; operators are case-insensitive and a quoted "and" searches the literal word.\nReturns: matching notes (most recently modified first) with id, title, folder, account, modified date, and snippet, plus scan/match counts. Ids work with get-note-content and every other id-based tool.\nDo not use when: Full Disk Access is unavailable (use search-notes). Scans the most recent scanLimit notes (default 500); raise it for older notes.\nSafety: read-only; never writes the database. Excludes Recently Deleted and folderless notes unless includeDeleted is true. Locked notes match on title and metadata only; body predicates never match them.',
45984
+ inputSchema: {
45985
+ query: external_exports.string().min(1, "A query expression is required").max(MAX.QUERY).describe(
45986
+ 'Boolean query expression, e.g. `folder:"Work Projects" has:checklist -checklist:done`'
45987
+ ),
45988
+ limit: external_exports.number().int().positive().max(QUERY_RESULTS.MAX).optional().describe(
45989
+ `Maximum notes to return (default ${QUERY_RESULTS.DEFAULT}, max ${QUERY_RESULTS.MAX}). The response reports how many matched in total.`
45990
+ ),
45991
+ scanLimit: external_exports.number().int().positive().max(QUERY_SCAN.MAX).optional().describe(
45992
+ `How many of the most recently modified notes to examine (default ${QUERY_SCAN.DEFAULT}, max ${QUERY_SCAN.MAX}). The response says when older notes were left unscanned.`
45993
+ ),
45994
+ includeDeleted: external_exports.boolean().optional().describe(
45995
+ "Also scan notes in Recently Deleted, notes pending deletion, and folderless notes (default false)"
45996
+ )
45997
+ },
45998
+ outputSchema: {
45999
+ notes: external_exports.array(external_exports.object({}).passthrough()).optional(),
46000
+ count: external_exports.number().optional(),
46001
+ matched: external_exports.number().optional(),
46002
+ scanned: external_exports.number().optional(),
46003
+ eligible: external_exports.number().optional(),
46004
+ scanLimit: external_exports.number().optional(),
46005
+ scanTruncated: external_exports.boolean().optional(),
46006
+ limit: external_exports.number().optional(),
46007
+ truncated: external_exports.boolean().optional(),
46008
+ unreadable: external_exports.number().optional()
46009
+ },
46010
+ annotations: { readOnlyHint: true }
46011
+ },
46012
+ withErrorHandling(({ query, limit, scanLimit, includeDeleted }) => {
46013
+ let result;
46014
+ try {
46015
+ result = queryNotes(query, { limit, scanLimit, includeDeleted });
46016
+ } catch (error2) {
46017
+ if (error2 instanceof NoteQueryError || error2 instanceof NoteQueryStoreError) {
46018
+ return errorResponse(
46019
+ error2 instanceof NoteQueryError ? `Invalid query: ${error2.message}` : error2.message
46020
+ );
46021
+ }
46022
+ throw error2;
46023
+ }
46024
+ const scope = `scanned ${result.scanned} of ${result.eligible} notes` + (result.scanTruncated ? `, the most recent ${result.scanLimit}; pass a higher scanLimit to include older notes` : "");
46025
+ const notes = [];
46026
+ if (result.truncated) {
46027
+ notes.push(
46028
+ `\u2139\uFE0F ${result.matched} notes matched; showing the first ${result.count}. Pass a higher limit or narrow the query.`
46029
+ );
46030
+ }
46031
+ if (result.unreadable > 0) {
46032
+ notes.push(
46033
+ `\u26A0\uFE0F ${result.unreadable} note bodies could not be decoded, so body predicates did not match them.`
46034
+ );
46035
+ }
46036
+ const footer = notes.length ? `
46037
+
46038
+ ${notes.join("\n")}` : "";
46039
+ if (result.count === 0) {
46040
+ return successResponse(`No notes matched (${scope}).${footer}`, { ...result });
46041
+ }
46042
+ const lines = result.notes.map((n) => {
46043
+ const where = [n.account, n.folder].filter(Boolean).join(" / ");
46044
+ const snippet = n.snippet ? `
46045
+ ${n.snippet}` : "";
46046
+ return ` - ${n.title}${where ? ` (${where})` : ""}${n.locked ? " [locked]" : ""} [id: ${n.id}]${snippet}`;
46047
+ }).join("\n");
46048
+ return successResponse(
46049
+ `Found ${result.matched} matching notes (${scope}):
46050
+ ${lines}${footer}`,
46051
+ { ...result }
46052
+ );
46053
+ }, "Error querying notes")
46054
+ );
44927
46055
  registerTool(
44928
46056
  "get-note-content",
44929
46057
  {
@@ -46081,11 +47209,13 @@ ${fdaLine}`, {
46081
47209
  registerTool(
46082
47210
  "doctor",
46083
47211
  {
46084
- description: "Use when: diagnosing setup problems (Notes.app automation permission, account state, Full Disk Access) with actionable guidance.\nReturns: a detailed report plus structured fields.\nDo not use when: you just need a quick pass/fail (health-check).\nRead-only.",
47212
+ description: "Use when: diagnosing setup problems (Notes.app automation permission, account state, Full Disk Access) with actionable guidance.\nReturns: a detailed report plus structured fields, including runtimeOS and the same OS-version-aware features matrix as get-capabilities.\nDo not use when: you just need a quick pass/fail (health-check).\nRead-only.",
46085
47213
  inputSchema: {},
46086
47214
  outputSchema: {
46087
47215
  healthy: external_exports.boolean().optional(),
46088
- checks: external_exports.array(external_exports.object({}).passthrough()).optional()
47216
+ checks: external_exports.array(external_exports.object({}).passthrough()).optional(),
47217
+ runtimeOS: external_exports.object({}).passthrough().optional(),
47218
+ features: external_exports.record(external_exports.string(), external_exports.object({}).passthrough()).optional()
46089
47219
  }
46090
47220
  },
46091
47221
  withErrorHandling(() => {