apple-notes-mcp 2.8.21 → 2.8.23

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 +100 -0
  2. package/build/index.js +1030 -92
  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) {
@@ -42816,7 +43599,7 @@ function addNativeTags(request, deps) {
42816
43599
  };
42817
43600
  }
42818
43601
  function nativeTagsStatus(shortcut = process.env.APPLE_NOTES_MCP_TAGS_SHORTCUT || NATIVE_TAGS_SHORTCUT) {
42819
- const lines = execFileSync6("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
43602
+ const lines = execFileSync7("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
42820
43603
  encoding: "utf8",
42821
43604
  timeout: 15e3,
42822
43605
  maxBuffer: 1024 * 1024,
@@ -42836,11 +43619,11 @@ function runNativeTagsShortcut(input) {
42836
43619
  const status = nativeTagsStatus();
42837
43620
  if (!status.installed)
42838
43621
  throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
42839
- const directory = mkdtempSync2(join7(tmpdir2(), "apple-notes-native-tags-"));
43622
+ const directory = mkdtempSync2(join8(tmpdir2(), "apple-notes-native-tags-"));
42840
43623
  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], {
43624
+ const path5 = join8(directory, "request.json");
43625
+ writeFileSync(path5, JSON.stringify(input), { mode: 384 });
43626
+ execFileSync7("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path5], {
42844
43627
  encoding: "utf8",
42845
43628
  timeout: 6e4,
42846
43629
  maxBuffer: 1024 * 1024,
@@ -42859,10 +43642,10 @@ function runNativeTagsShortcut(input) {
42859
43642
  }
42860
43643
 
42861
43644
  // src/services/backgroundNotes.ts
42862
- import { execFileSync as execFileSync7 } from "node:child_process";
43645
+ import { execFileSync as execFileSync8 } from "node:child_process";
42863
43646
  import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
42864
43647
  import { tmpdir as tmpdir3 } from "node:os";
42865
- import { join as join8 } from "node:path";
43648
+ import { join as join9 } from "node:path";
42866
43649
 
42867
43650
  // src/utils/appendMarkdown.ts
42868
43651
  var escape2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -43049,9 +43832,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
43049
43832
  throw new Error(
43050
43833
  `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
43051
43834
  );
43052
- const directory = mkdtempSync3(join8(tmpdir3(), "apple-notes-background-"));
43835
+ const directory = mkdtempSync3(join9(tmpdir3(), "apple-notes-background-"));
43053
43836
  try {
43054
- const file = join8(directory, "request.json");
43837
+ const file = join9(directory, "request.json");
43055
43838
  writeFileSync2(
43056
43839
  file,
43057
43840
  JSON.stringify({
@@ -43071,7 +43854,7 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
43071
43854
  { mode: 384 }
43072
43855
  );
43073
43856
  try {
43074
- execFileSync7("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
43857
+ execFileSync8("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
43075
43858
  encoding: "utf8",
43076
43859
  timeout: 6e4,
43077
43860
  maxBuffer: 1024 * 1024,
@@ -43274,7 +44057,7 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
43274
44057
  throw new Error(
43275
44058
  `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
43276
44059
  );
43277
- const segments = (path4) => JSON.stringify(splitFolderPath(path4).map((part) => part.toLocaleLowerCase()));
44060
+ const segments = (path5) => JSON.stringify(splitFolderPath(path5).map((part) => part.toLocaleLowerCase()));
43278
44061
  if (request.folder) {
43279
44062
  const wanted = segments(request.folder);
43280
44063
  if (!manager.listAccounts().some(
@@ -43462,19 +44245,19 @@ function formatDoctorReport(r) {
43462
44245
  }
43463
44246
 
43464
44247
  // 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";
44248
+ import { existsSync as existsSync7, readFileSync as readFileSync2 } from "fs";
44249
+ import { join as join10 } from "path";
44250
+ import { homedir as homedir8 } from "os";
43468
44251
  function fileConfigPath(env = process.env) {
43469
44252
  const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
43470
44253
  if (override && override.trim()) return override.trim();
43471
- return join9(homedir7(), "Library", "Application Support", "apple-notes-mcp", "config.json");
44254
+ return join10(homedir8(), "Library", "Application Support", "apple-notes-mcp", "config.json");
43472
44255
  }
43473
- function loadFileConfig(env = process.env, path4 = fileConfigPath(env)) {
44256
+ function loadFileConfig(env = process.env, path5 = fileConfigPath(env)) {
43474
44257
  const applied = [];
43475
44258
  try {
43476
- if (!existsSync6(path4)) return applied;
43477
- const parsed = JSON.parse(readFileSync2(path4, "utf8"));
44259
+ if (!existsSync7(path5)) return applied;
44260
+ const parsed = JSON.parse(readFileSync2(path5, "utf8"));
43478
44261
  if (!parsed || typeof parsed !== "object") return applied;
43479
44262
  for (const [k, v] of Object.entries(parsed)) {
43480
44263
  if (typeof v !== "string") continue;
@@ -43484,7 +44267,7 @@ function loadFileConfig(env = process.env, path4 = fileConfigPath(env)) {
43484
44267
  }
43485
44268
  }
43486
44269
  } catch (e) {
43487
- console.error(`Failed to load apple-notes-mcp config file ${path4}: ${String(e)}`);
44270
+ console.error(`Failed to load apple-notes-mcp config file ${path5}: ${String(e)}`);
43488
44271
  }
43489
44272
  return applied;
43490
44273
  }
@@ -43700,7 +44483,7 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
43700
44483
  }
43701
44484
 
43702
44485
  // src/utils/noteTables.ts
43703
- import { gunzipSync as gunzipSync3 } from "node:zlib";
44486
+ import { gunzipSync as gunzipSync4 } from "node:zlib";
43704
44487
  var sub = (f, n) => {
43705
44488
  const value = embeddedMessage(getField(f, n));
43706
44489
  if (!value) throw new Error(`Missing table field ${n}`);
@@ -43721,7 +44504,7 @@ var hex = (f) => {
43721
44504
  return Buffer.from(f.value).toString("hex");
43722
44505
  };
43723
44506
  function parseNoteTable(compressed) {
43724
- const root = decodeMessage(gunzipSync3(compressed, { maxOutputLength: 16 * 1024 * 1024 }));
44507
+ const root = decodeMessage(gunzipSync4(compressed, { maxOutputLength: 16 * 1024 * 1024 }));
43725
44508
  const data = sub(sub(root, 2), 3), entries = many(data, 3);
43726
44509
  if (entries.length > 1e5) throw new Error("Table too large");
43727
44510
  const keys = getFields(data, 4).map(stringValue), types = getFields(data, 5).map(stringValue), uuids = getFields(data, 6).map(hex);
@@ -43924,7 +44707,7 @@ import {
43924
44707
  writeFileSync as writeFileSync3
43925
44708
  } from "node:fs";
43926
44709
  import { tmpdir as tmpdir4 } from "node:os";
43927
- import { basename, isAbsolute as isAbsolute2, join as join10 } from "node:path";
44710
+ import { basename, isAbsolute as isAbsolute2, join as join11 } from "node:path";
43928
44711
  var noteId = external_exports.string().regex(/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i);
43929
44712
  var revision = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
43930
44713
  function readSnapshot(manager, id2) {
@@ -43962,9 +44745,9 @@ function assertExistingContentPreserved(before, after) {
43962
44745
  throw new Error("Existing native object content or presentation changed");
43963
44746
  }
43964
44747
  }
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);
44748
+ function localAttachment(path5) {
44749
+ if (!isAbsolute2(path5)) throw new Error("An absolute local file path is required");
44750
+ const descriptor = openSync(path5, constants.O_RDONLY | constants.O_NOFOLLOW);
43968
44751
  try {
43969
44752
  const stat = fstatSync(descriptor);
43970
44753
  if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
@@ -44033,13 +44816,13 @@ function registerDirectOperations(server2, manager) {
44033
44816
  "add-attachment",
44034
44817
  "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
44818
  { id: noteId, expectedContentHash: revision, path: external_exports.string().min(1).max(4096) },
44036
- ({ id: id2, expectedContentHash, path: path4 }) => {
44819
+ ({ id: id2, expectedContentHash, path: path5 }) => {
44037
44820
  const before = readSnapshot(manager, id2);
44038
44821
  if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
44039
- const bytes = localAttachment(path4);
44822
+ const bytes = localAttachment(path5);
44040
44823
  const beforeAttachments = manager.listAttachmentsById(id2);
44041
- const directory = mkdtempSync4(join10(tmpdir4(), "notes-attachment-add-"));
44042
- const temporaryFile = join10(directory, basename(path4));
44824
+ const directory = mkdtempSync4(join11(tmpdir4(), "notes-attachment-add-"));
44825
+ const temporaryFile = join11(directory, basename(path5));
44043
44826
  try {
44044
44827
  writeFileSync3(temporaryFile, bytes, { mode: 384 });
44045
44828
  if (readSnapshot(manager, id2).hash !== before.hash)
@@ -44221,6 +45004,72 @@ var common = {
44221
45004
  )
44222
45005
  };
44223
45006
  var htmlEscape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
45007
+ var MAX_CHECKLIST_BATCH = 20;
45008
+ var byPosition = (items = []) => [...items].sort((a, b) => a.start - b.start);
45009
+ function appendChecklistItems(args, deps, readRich) {
45010
+ const existing = new Set(byPosition(readRich(args.id).checklistItems).map((item) => item.id));
45011
+ const landed = [];
45012
+ let contentHash = args.expectedContentHash;
45013
+ for (const [index, text] of args.items.entries()) {
45014
+ let wrote = false;
45015
+ try {
45016
+ const result = mutateBackground(
45017
+ { ...args, expectedContentHash: contentHash },
45018
+ "create-checklist-item",
45019
+ { text },
45020
+ (before, after) => {
45021
+ wrote = true;
45022
+ assertPreserved(before, after, { append: true });
45023
+ const added = after.checklist.slice(before.checklist.length);
45024
+ if (added.length !== 1 || added[0].text !== text || added[0].done)
45025
+ throw new Error("Native checklist item not verified");
45026
+ },
45027
+ deps
45028
+ );
45029
+ const items = byPosition(readRich(args.id).checklistItems);
45030
+ const fresh = items.filter(
45031
+ (item) => !existing.has(item.id) && !landed.some((done) => done.id === item.id)
45032
+ );
45033
+ if (fresh.length !== 1 || fresh[0].text !== text || fresh[0].done)
45034
+ throw new Error("Native checklist identity not verified");
45035
+ for (const done of landed) {
45036
+ const current = items.find((item) => item.id === done.id);
45037
+ if (!current || current.text !== done.text || current.done)
45038
+ throw new Error("An earlier appended item changed identity or text");
45039
+ }
45040
+ landed.push({ index, id: fresh[0].id, text });
45041
+ contentHash = result.contentHash;
45042
+ } catch (error2) {
45043
+ return {
45044
+ ok: false,
45045
+ id: args.id,
45046
+ landed,
45047
+ stoppedAt: {
45048
+ index,
45049
+ text,
45050
+ outcome: wrote ? "uncertain" : "not-written",
45051
+ error: error2 instanceof Error ? error2.message : String(error2)
45052
+ },
45053
+ notAttempted: args.items.slice(index + 1),
45054
+ contentHash,
45055
+ 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.`
45056
+ };
45057
+ }
45058
+ }
45059
+ const finalItems = byPosition(readRich(args.id).checklistItems);
45060
+ const tail = finalItems.slice(finalItems.length - landed.length);
45061
+ const orderVerified = tail.length === landed.length && tail.every((item, i) => item.id === landed[i].id && item.text === landed[i].text);
45062
+ return {
45063
+ ok: orderVerified,
45064
+ id: args.id,
45065
+ contentHash,
45066
+ items: landed,
45067
+ orderVerified,
45068
+ ...orderVerified ? {} : {
45069
+ 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."
45070
+ }
45071
+ };
45072
+ }
44224
45073
  function registerNativeOperations(server2, manager) {
44225
45074
  function tool(name, description, input, handler, readOnly = false) {
44226
45075
  server2.registerTool(
@@ -44346,6 +45195,20 @@ function registerNativeOperations(server2, manager) {
44346
45195
  return { ...result, items: readRichNote(args.id).checklistItems };
44347
45196
  }
44348
45197
  );
45198
+ tool(
45199
+ "create-checklist-items",
45200
+ "Use when: appending several real unchecked Notes checklist items to one note, in order.\nReturns: each landed item's native identity and text, the final order check, and the new revision; on a stop, which items landed, the item whose outcome is uncertain, and the items not attempted.\nDo not use when: one item is enough (create-checklist-item) or plain text is sufficient.\nSafety: runs the verified single-item bridge once per item, chaining each verified revision into the next; checks after every item that exactly one new unchecked item with that text and a new identity appeared and that earlier items kept theirs, and stops at the first uncertain result without retrying. Each item takes a few seconds, so a full batch can run about a minute; if the call times out on the client side, read the note before retrying and send only items that are not present.",
45201
+ {
45202
+ ...common,
45203
+ items: external_exports.array(
45204
+ external_exports.string().min(1).max(1e4).refine((s) => !/[\r\n\0]/u.test(s), "One line per checklist item")
45205
+ ).min(1).max(MAX_CHECKLIST_BATCH).describe(`Item texts in the order they should appear (1-${MAX_CHECKLIST_BATCH})`)
45206
+ },
45207
+ (args) => {
45208
+ requireValidated("create-checklist-item");
45209
+ return appendChecklistItems(args, backgroundDependencies(manager), readRichNote);
45210
+ }
45211
+ );
44349
45212
  tool(
44350
45213
  "create-table",
44351
45214
  "Use when: appending one native Notes table from a rectangular array of strings.\nReturns: the native table identity and decoded cells after readback.\nDo not use when: a text table is acceptable or the rows are not rectangular.\nSafety: never substitutes text; preserves existing rich content and reports failure unless the native table is verified.",
@@ -44516,7 +45379,7 @@ function registerNativeOperations(server2, manager) {
44516
45379
 
44517
45380
  // src/setupShortcuts.ts
44518
45381
  import { spawnSync as spawnSync2 } from "node:child_process";
44519
- import { existsSync as existsSync7 } from "node:fs";
45382
+ import { existsSync as existsSync8 } from "node:fs";
44520
45383
  import { release } from "node:os";
44521
45384
  import { dirname as dirname2, resolve as resolve2 } from "node:path";
44522
45385
  import { fileURLToPath } from "node:url";
@@ -44536,16 +45399,16 @@ var shortcutFiles = [
44536
45399
  ];
44537
45400
  function setupShortcuts(checkOnly, dependencies = {}) {
44538
45401
  const status = dependencies.status || nativeTagsStatus;
44539
- const exists = dependencies.exists || existsSync7;
44540
- const open = dependencies.open || ((path4) => {
44541
- const result = spawnSync2("/usr/bin/open", [path4], { encoding: "utf8" });
45402
+ const exists = dependencies.exists || existsSync8;
45403
+ const open = dependencies.open || ((path5) => {
45404
+ const result = spawnSync2("/usr/bin/open", [path5], { encoding: "utf8" });
44542
45405
  return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
44543
45406
  });
44544
45407
  const baseDirectory = dependencies.baseDirectory || resolve2(dirname2(fileURLToPath(import.meta.url)), "../shortcuts");
44545
45408
  const osRelease = (dependencies.osRelease || release)();
44546
45409
  const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
44547
45410
  const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
44548
- const path4 = resolve2(baseDirectory, file);
45411
+ const path5 = resolve2(baseDirectory, file);
44549
45412
  let installed = false;
44550
45413
  let identifier;
44551
45414
  let error2;
@@ -44561,9 +45424,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
44561
45424
  if (!installed && !checkOnly) {
44562
45425
  if (optional2 && !(darwinMajor >= MARKDOWN_MIN_DARWIN_MAJOR))
44563
45426
  skipped = `requires macOS 26 or later (this Mac reports Darwin ${osRelease})`;
44564
- else if (!exists(path4)) error2 = `Packaged Shortcut is missing: ${path4}`;
45427
+ else if (!exists(path5)) error2 = `Packaged Shortcut is missing: ${path5}`;
44565
45428
  else {
44566
- const result = open(path4);
45429
+ const result = open(path5);
44567
45430
  opened = result.ok;
44568
45431
  if (!result.ok) error2 = result.error || `Could not open ${file}`;
44569
45432
  }
@@ -44572,7 +45435,7 @@ function setupShortcuts(checkOnly, dependencies = {}) {
44572
45435
  name,
44573
45436
  installed,
44574
45437
  identifier,
44575
- file: path4,
45438
+ file: path5,
44576
45439
  opened,
44577
45440
  ...optional2 ? { optional: optional2 } : {},
44578
45441
  ...skipped ? { skipped } : {},
@@ -44844,6 +45707,81 @@ ${noteList}${truncationNote}${syncNote}`,
44844
45707
  );
44845
45708
  }, "Error searching notes")
44846
45709
  );
45710
+ registerTool(
45711
+ "query-notes",
45712
+ {
45713
+ 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.',
45714
+ inputSchema: {
45715
+ query: external_exports.string().min(1, "A query expression is required").max(MAX.QUERY).describe(
45716
+ 'Boolean query expression, e.g. `folder:"Work Projects" has:checklist -checklist:done`'
45717
+ ),
45718
+ limit: external_exports.number().int().positive().max(QUERY_RESULTS.MAX).optional().describe(
45719
+ `Maximum notes to return (default ${QUERY_RESULTS.DEFAULT}, max ${QUERY_RESULTS.MAX}). The response reports how many matched in total.`
45720
+ ),
45721
+ scanLimit: external_exports.number().int().positive().max(QUERY_SCAN.MAX).optional().describe(
45722
+ `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.`
45723
+ ),
45724
+ includeDeleted: external_exports.boolean().optional().describe(
45725
+ "Also scan notes in Recently Deleted, notes pending deletion, and folderless notes (default false)"
45726
+ )
45727
+ },
45728
+ outputSchema: {
45729
+ notes: external_exports.array(external_exports.object({}).passthrough()).optional(),
45730
+ count: external_exports.number().optional(),
45731
+ matched: external_exports.number().optional(),
45732
+ scanned: external_exports.number().optional(),
45733
+ eligible: external_exports.number().optional(),
45734
+ scanLimit: external_exports.number().optional(),
45735
+ scanTruncated: external_exports.boolean().optional(),
45736
+ limit: external_exports.number().optional(),
45737
+ truncated: external_exports.boolean().optional(),
45738
+ unreadable: external_exports.number().optional()
45739
+ },
45740
+ annotations: { readOnlyHint: true }
45741
+ },
45742
+ withErrorHandling(({ query, limit, scanLimit, includeDeleted }) => {
45743
+ let result;
45744
+ try {
45745
+ result = queryNotes(query, { limit, scanLimit, includeDeleted });
45746
+ } catch (error2) {
45747
+ if (error2 instanceof NoteQueryError || error2 instanceof NoteQueryStoreError) {
45748
+ return errorResponse(
45749
+ error2 instanceof NoteQueryError ? `Invalid query: ${error2.message}` : error2.message
45750
+ );
45751
+ }
45752
+ throw error2;
45753
+ }
45754
+ const scope = `scanned ${result.scanned} of ${result.eligible} notes` + (result.scanTruncated ? `, the most recent ${result.scanLimit}; pass a higher scanLimit to include older notes` : "");
45755
+ const notes = [];
45756
+ if (result.truncated) {
45757
+ notes.push(
45758
+ `\u2139\uFE0F ${result.matched} notes matched; showing the first ${result.count}. Pass a higher limit or narrow the query.`
45759
+ );
45760
+ }
45761
+ if (result.unreadable > 0) {
45762
+ notes.push(
45763
+ `\u26A0\uFE0F ${result.unreadable} note bodies could not be decoded, so body predicates did not match them.`
45764
+ );
45765
+ }
45766
+ const footer = notes.length ? `
45767
+
45768
+ ${notes.join("\n")}` : "";
45769
+ if (result.count === 0) {
45770
+ return successResponse(`No notes matched (${scope}).${footer}`, { ...result });
45771
+ }
45772
+ const lines = result.notes.map((n) => {
45773
+ const where = [n.account, n.folder].filter(Boolean).join(" / ");
45774
+ const snippet = n.snippet ? `
45775
+ ${n.snippet}` : "";
45776
+ return ` - ${n.title}${where ? ` (${where})` : ""}${n.locked ? " [locked]" : ""} [id: ${n.id}]${snippet}`;
45777
+ }).join("\n");
45778
+ return successResponse(
45779
+ `Found ${result.matched} matching notes (${scope}):
45780
+ ${lines}${footer}`,
45781
+ { ...result }
45782
+ );
45783
+ }, "Error querying notes")
45784
+ );
44847
45785
  registerTool(
44848
45786
  "get-note-content",
44849
45787
  {