apple-notes-mcp 2.8.26 → 2.8.27

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 +41 -0
  2. package/build/index.js +633 -149
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -930,6 +930,47 @@ Lists all folders in an account with full hierarchical paths.
930
930
 
931
931
  ---
932
932
 
933
+ #### `list-smart-folders`
934
+
935
+ Lists every Smart Folder with the rules that define it, read from the NoteStore
936
+ database. Requires Full Disk Access.
937
+
938
+ | Parameter | Type | Required | Description |
939
+ |-----------|------|----------|-------------|
940
+ | `includeMatchingNotes` | boolean | No | Also list the notes each smart folder currently shows (default `false`) |
941
+ | `limit` | number | No | Maximum matching notes per folder, 1–500 (default 50). `matchingNoteCount` is always the total |
942
+
943
+ **Example:**
944
+ ```json
945
+ {
946
+ "includeMatchingNotes": true,
947
+ "limit": 20
948
+ }
949
+ ```
950
+
951
+ **Returns:** For each smart folder: `name`, `id`, `identifier`, `account`,
952
+ `accountId`, `accountIdentifier`, `parent`, `parentId`, and `parentIdentifier`
953
+ (the parent fields are `null` at the account root). Its rules are decoded as
954
+ `match` (`"all"`, `"any"`, or `"none"`) and `filters`. Each filter has a `type`
955
+ (the stored rule key, such as `folder`, `tag`, `checklist`, or
956
+ `creationDateRelativeRange`), its stored `value`, `excluded` for an Exclude
957
+ rule, and a readable `description`. Folder filters add the folder's `name` and
958
+ `folderId`, and a nested rule group is a filter of type `group` with its own
959
+ `match` and `filters`. Notes stores each query inside an outer
960
+ `{"deleted": false}` wrapper that keeps Recently Deleted out. `query` is the
961
+ stored query with that wrapper removed, `includesRecentlyDeleted` reports the
962
+ wrapper's value, and `rawQuery` is the stored JSON verbatim. A rule this server
963
+ does not recognize is kept as a filter of type `unknown`, and `fullyDecoded` is
964
+ then `false`.
965
+
966
+ With `includeMatchingNotes`, each folder also carries `matchingNoteCount` and
967
+ `matchingNotes` (`title` and `id`). These come from Notes.app itself, which
968
+ evaluates the folder's rules, so they need Automation permission. The server
969
+ does not re-evaluate the rules on its own. A folder whose notes cannot be read
970
+ carries `matchingNotesError` instead.
971
+
972
+ ---
973
+
933
974
  #### `create-folder`
934
975
 
935
976
  Creates a new folder, including a whole nested hierarchy in one call.
package/build/index.js CHANGED
@@ -3268,8 +3268,8 @@ var require_utils = __commonJS({
3268
3268
  }
3269
3269
  return ind;
3270
3270
  }
3271
- function removeDotSegments(path6) {
3272
- let input = path6;
3271
+ function removeDotSegments(path7) {
3272
+ let input = path7;
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 path6 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3678
- wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
3677
+ const path7 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3678
+ wsComponent.path = path7 && path7 !== "/" ? path7 : void 0;
3679
3679
  wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
3680
3680
  wsComponent.resourceName = void 0;
3681
3681
  }
@@ -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, fs6, exportName) {
7184
+ function addFormats(ajv, list, fs7, 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, fs6[f]);
7189
+ ajv.addFormat(f, fs7[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(path6) {
12242
- if (!path6) return path6;
12241
+ function remove_dot_segments(path7) {
12242
+ if (!path7) return path7;
12243
12243
  var output = "";
12244
- while (path6.length > 0) {
12245
- if (path6 === "." || path6 === "..") {
12246
- path6 = "";
12244
+ while (path7.length > 0) {
12245
+ if (path7 === "." || path7 === "..") {
12246
+ path7 = "";
12247
12247
  break;
12248
12248
  }
12249
- var twochars = path6.substring(0, 2);
12250
- var threechars = path6.substring(0, 3);
12251
- var fourchars = path6.substring(0, 4);
12249
+ var twochars = path7.substring(0, 2);
12250
+ var threechars = path7.substring(0, 3);
12251
+ var fourchars = path7.substring(0, 4);
12252
12252
  if (threechars === "../") {
12253
- path6 = path6.substring(3);
12253
+ path7 = path7.substring(3);
12254
12254
  } else if (twochars === "./") {
12255
- path6 = path6.substring(2);
12255
+ path7 = path7.substring(2);
12256
12256
  } else if (threechars === "/./") {
12257
- path6 = "/" + path6.substring(3);
12258
- } else if (twochars === "/." && path6.length === 2) {
12259
- path6 = "/";
12260
- } else if (fourchars === "/../" || threechars === "/.." && path6.length === 3) {
12261
- path6 = "/" + path6.substring(4);
12257
+ path7 = "/" + path7.substring(3);
12258
+ } else if (twochars === "/." && path7.length === 2) {
12259
+ path7 = "/";
12260
+ } else if (fourchars === "/../" || threechars === "/.." && path7.length === 3) {
12261
+ path7 = "/" + path7.substring(4);
12262
12262
  output = output.replace(/\/?[^\/]*$/, "");
12263
12263
  } else {
12264
- var segment = path6.match(/(\/?([^\/]*))/)[0];
12264
+ var segment = path7.match(/(\/?([^\/]*))/)[0];
12265
12265
  output += segment;
12266
- path6 = path6.substring(segment.length);
12266
+ path7 = path7.substring(segment.length);
12267
12267
  }
12268
12268
  }
12269
12269
  return output;
@@ -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 join13(output, replacement);
24468
+ return join14(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 = join13(output, rule.append(self.options));
24475
+ output = join14(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 join13(output, replacement) {
24487
+ function join14(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: path6, errorMaps, issueData } = params;
24983
- const fullPath = [...path6, ...issueData.path || []];
24982
+ const { data, path: path7, errorMaps, issueData } = params;
24983
+ const fullPath = [...path7, ...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, path6, key) {
25099
+ constructor(parent, value, path7, key) {
25100
25100
  this._cachedPath = [];
25101
25101
  this.parent = parent;
25102
25102
  this.data = value;
25103
- this._path = path6;
25103
+ this._path = path7;
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, path6) {
28741
- if (!path6)
28740
+ function getElementAtPath(obj, path7) {
28741
+ if (!path7)
28742
28742
  return obj;
28743
- return path6.reduce((acc, key) => acc?.[key], obj);
28743
+ return path7.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(path6, issues) {
29063
+ function prefixIssues(path7, issues) {
29064
29064
  return issues.map((iss) => {
29065
29065
  var _a;
29066
29066
  (_a = iss).path ?? (_a.path = []);
29067
- iss.path.unshift(path6);
29067
+ iss.path.unshift(path7);
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(path6) {
32479
- if (path6.length === 0) {
32478
+ function getDotPath(path7) {
32479
+ if (path7.length === 0) {
32480
32480
  return "object root";
32481
32481
  }
32482
- return path6.reduce((acc, seg, index) => {
32482
+ return path7.reduce((acc, seg, index) => {
32483
32483
  if (index === 0) {
32484
32484
  return String(seg);
32485
32485
  }
@@ -39839,9 +39839,392 @@ function assertLinkedWrite(rich, content, format, allowLinkChanges = false) {
39839
39839
  }
39840
39840
  }
39841
39841
 
39842
+ // src/utils/smartFolders.ts
39843
+ import { execFileSync as execFileSync4 } from "child_process";
39844
+ import * as fs2 from "fs";
39845
+ import * as os2 from "os";
39846
+ import * as path2 from "path";
39847
+ var NOTES_DB_PATH2 = path2.join(
39848
+ os2.homedir(),
39849
+ "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
39850
+ );
39851
+ var FDA_MESSAGE = `Full Disk Access is required to read smart folders. 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.`;
39852
+ var COCOA_EPOCH_OFFSET = 978307200;
39853
+ var MAX_DEPTH = 32;
39854
+ var RELATIVE_RANGES = {
39855
+ 0: "today",
39856
+ 1: "yesterday",
39857
+ 2: "in the last 7 days",
39858
+ 3: "in the last 30 days",
39859
+ 4: "in the last 3 months",
39860
+ 5: "in the last 12 months"
39861
+ };
39862
+ var CUSTOM_UNITS = {
39863
+ 0: "hours",
39864
+ 1: "days",
39865
+ 2: "weeks",
39866
+ 3: "months",
39867
+ 4: "years"
39868
+ };
39869
+ var ATTACHMENT_SECTIONS = {
39870
+ 1: "Photos & Videos",
39871
+ 2: "Drawings",
39872
+ 3: "Maps",
39873
+ 4: "Websites",
39874
+ 5: "Audio",
39875
+ 6: "Documents",
39876
+ 7: "Scans"
39877
+ };
39878
+ var isClause = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
39879
+ var isDeletedWrapper = (value) => isClause(value) && Object.keys(value).length === 1 && typeof value.deleted === "boolean";
39880
+ function cocoaDate(value) {
39881
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
39882
+ const date3 = new Date((value + COCOA_EPOCH_OFFSET) * 1e3);
39883
+ return Number.isNaN(date3.getTime()) ? null : date3.toISOString();
39884
+ }
39885
+ var Decoder = class {
39886
+ constructor(context) {
39887
+ this.context = context;
39888
+ }
39889
+ context;
39890
+ fullyDecoded = true;
39891
+ unknown(key, value, excluded) {
39892
+ this.fullyDecoded = false;
39893
+ return {
39894
+ type: "unknown",
39895
+ ...key === null ? {} : { key },
39896
+ value,
39897
+ ...excluded ? { excluded: true } : {},
39898
+ description: "Unrecognized clause (see value)"
39899
+ };
39900
+ }
39901
+ /** Decode a list of sibling clauses, dropping inner `deleted` wrappers. */
39902
+ list(clauses, excluded, depth) {
39903
+ if (!Array.isArray(clauses)) return [this.clause(clauses, excluded, depth)];
39904
+ return clauses.filter((clause) => !isDeletedWrapper(clause)).map((clause) => this.clause(clause, excluded, depth));
39905
+ }
39906
+ group(match, clauses, excluded, depth) {
39907
+ const items = Array.isArray(clauses) ? clauses.filter((clause) => !isDeletedWrapper(clause)) : [clauses];
39908
+ if (items.length === 1) return this.clause(items[0], excluded, depth + 1);
39909
+ const filters = this.list(items, false, depth + 1);
39910
+ const joiner = match === "all" ? "all of" : "any of";
39911
+ return {
39912
+ type: "group",
39913
+ match,
39914
+ ...excluded ? { excluded: true } : {},
39915
+ filters,
39916
+ description: `${excluded ? "not " : ""}${joiner}: ${filters.map((f) => f.description).join("; ")}`
39917
+ };
39918
+ }
39919
+ /** Mark a positively described leaf as an Exclude rule. */
39920
+ negate(filter) {
39921
+ const text = filter.description;
39922
+ const negated = text.startsWith("has ") ? text.replace(/^has /, "does not have ") : text.startsWith("is ") ? text.replace(/^is /, "is not ") : text.startsWith("mentions ") ? text.replace(/^mentions /, "does not mention ") : `not ${text}`;
39923
+ return { ...filter, excluded: true, description: negated };
39924
+ }
39925
+ clause(clause, excluded, depth) {
39926
+ if (depth > MAX_DEPTH || !isClause(clause)) return this.unknown(null, clause, excluded);
39927
+ const keys = Object.keys(clause);
39928
+ if (keys.length !== 1) {
39929
+ return this.group(
39930
+ "all",
39931
+ keys.map((key2) => ({ [key2]: clause[key2] })),
39932
+ excluded,
39933
+ depth
39934
+ );
39935
+ }
39936
+ const [key] = keys;
39937
+ const value = clause[key];
39938
+ if (key === "and" || key === "or") {
39939
+ if (!Array.isArray(value)) return this.unknown(key, value, excluded);
39940
+ return this.group(key === "and" ? "all" : "any", value, excluded, depth);
39941
+ }
39942
+ if (key === "not") return this.clause(value, !excluded, depth + 1);
39943
+ const leaf = this.leaf(key, value, excluded);
39944
+ if (!leaf) return this.unknown(key, value, excluded);
39945
+ return excluded && !leaf.excluded ? this.negate(leaf) : leaf;
39946
+ }
39947
+ /**
39948
+ * Decode one leaf clause. Boolean flags describe their own negation (an
39949
+ * excluded `pinned: true` reads "is not pinned"); other leaves are described
39950
+ * positively and negated by the caller.
39951
+ */
39952
+ leaf(key, value, excluded) {
39953
+ const flag = (description, negative) => typeof value === "boolean" ? {
39954
+ type: key,
39955
+ value,
39956
+ ...excluded ? { excluded: true } : {},
39957
+ description: value !== excluded ? description : negative
39958
+ } : null;
39959
+ switch (key) {
39960
+ case "checklist":
39961
+ return flag("has a checklist", "has no checklist");
39962
+ case "checklistInProgress":
39963
+ return flag("has an unfinished checklist", "has no unfinished checklist");
39964
+ case "checklistCompleted":
39965
+ return flag("has a completed checklist", "has no completed checklist");
39966
+ case "attachment":
39967
+ return flag("has attachments", "has no attachments");
39968
+ case "pinned":
39969
+ return flag("is pinned", "is not pinned");
39970
+ case "systemPaper":
39971
+ return flag("is a Quick Note", "is not a Quick Note");
39972
+ case "passwordProtected":
39973
+ return flag("is locked", "is not locked");
39974
+ case "shared":
39975
+ return flag("is shared", "is not shared");
39976
+ case "mention":
39977
+ return flag("mentions anyone", "mentions no one");
39978
+ case "tagged":
39979
+ return flag("has any tag", "has no tags");
39980
+ case "attachmentSection": {
39981
+ if (typeof value !== "number" || !ATTACHMENT_SECTIONS[value]) return null;
39982
+ return {
39983
+ type: key,
39984
+ value,
39985
+ name: ATTACHMENT_SECTIONS[value],
39986
+ description: `has ${ATTACHMENT_SECTIONS[value]} attachments`
39987
+ };
39988
+ }
39989
+ case "tag": {
39990
+ if (typeof value !== "string") return null;
39991
+ const names = this.context.tags?.[value] ?? [];
39992
+ const name = names.length === 1 ? names[0] : void 0;
39993
+ return {
39994
+ type: key,
39995
+ value,
39996
+ ...name ? { name } : {},
39997
+ description: `has tag #${name ?? value}`
39998
+ };
39999
+ }
40000
+ case "folder": {
40001
+ if (typeof value !== "string") return null;
40002
+ const folder = this.context.folders?.[value];
40003
+ return {
40004
+ type: key,
40005
+ value,
40006
+ ...folder ? { folderId: folder.id } : {},
40007
+ ...folder?.title ? { name: folder.title } : {},
40008
+ description: folder?.title ? `is in folder "${folder.title}"` : `is in folder ${value}${folder ? "" : " (not found)"}`
40009
+ };
40010
+ }
40011
+ case "sharedParticipant":
40012
+ case "mentionParticipant":
40013
+ return typeof value === "string" ? {
40014
+ type: key,
40015
+ value,
40016
+ description: key === "sharedParticipant" ? `is shared with participant ${value}` : `mentions participant ${value}`
40017
+ } : null;
40018
+ case "creationDateRelativeRange":
40019
+ case "modificationDateRelativeRange": {
40020
+ if (!isClause(value) || typeof value.type !== "number") return null;
40021
+ const verb = key.startsWith("creation") ? "created" : "edited";
40022
+ if (value.type === 6) {
40023
+ const amount = value.customAmount;
40024
+ const unit = typeof value.customUnit === "number" ? CUSTOM_UNITS[value.customUnit] : null;
40025
+ if (typeof amount !== "number" || !unit) return null;
40026
+ return {
40027
+ type: key,
40028
+ value,
40029
+ description: `${verb} in the last ${amount} ${unit}`
40030
+ };
40031
+ }
40032
+ const range = RELATIVE_RANGES[value.type];
40033
+ return range ? { type: key, value, description: `${verb} ${range}` } : null;
40034
+ }
40035
+ case "creationDateRange":
40036
+ case "modificationDateRange": {
40037
+ if (!isClause(value)) return null;
40038
+ const from = cocoaDate(value.fromDate);
40039
+ const to = cocoaDate(value.toDate);
40040
+ if (!from || !to) return null;
40041
+ const verb = key.startsWith("creation") ? "created" : "edited";
40042
+ return {
40043
+ type: key,
40044
+ value,
40045
+ from,
40046
+ to,
40047
+ description: `${verb} between ${from} and ${to}`
40048
+ };
40049
+ }
40050
+ default:
40051
+ return null;
40052
+ }
40053
+ }
40054
+ };
40055
+ function decodeSmartFolderQuery(raw, context = {}) {
40056
+ if (raw === null || raw.trim() === "") {
40057
+ return { match: null, filters: [], query: null, fullyDecoded: false };
40058
+ }
40059
+ let parsed;
40060
+ try {
40061
+ parsed = JSON.parse(raw);
40062
+ } catch {
40063
+ return { match: null, filters: [], query: raw, fullyDecoded: false };
40064
+ }
40065
+ const decoder = new Decoder(context);
40066
+ const type = isClause(parsed) ? parsed.type : void 0;
40067
+ if (!isClause(type)) {
40068
+ return {
40069
+ match: null,
40070
+ filters: [decoder.unknown(null, parsed, false)],
40071
+ query: parsed,
40072
+ fullyDecoded: false
40073
+ };
40074
+ }
40075
+ let includesRecentlyDeleted;
40076
+ let inner = [type];
40077
+ if (Object.keys(type).length === 1 && Array.isArray(type.and)) {
40078
+ const wrapper = type.and.find(isDeletedWrapper);
40079
+ if (wrapper) {
40080
+ includesRecentlyDeleted = wrapper.deleted;
40081
+ inner = type.and.filter((clause) => !isDeletedWrapper(clause));
40082
+ }
40083
+ }
40084
+ const query = inner.length === 1 ? inner[0] : { and: inner };
40085
+ let match = "all";
40086
+ let filters;
40087
+ const only = inner.length === 1 && isClause(inner[0]) ? inner[0] : void 0;
40088
+ const onlyKey = only && Object.keys(only).length === 1 ? Object.keys(only)[0] : void 0;
40089
+ if (only && onlyKey === "and" && Array.isArray(only.and)) {
40090
+ filters = decoder.list(only.and, false, 1);
40091
+ } else if (only && onlyKey === "or" && Array.isArray(only.or)) {
40092
+ match = "any";
40093
+ filters = decoder.list(only.or, false, 1);
40094
+ } else if (only && onlyKey === "not" && isClause(only.not) && Object.keys(only.not).length === 1 && Array.isArray(only.not.or)) {
40095
+ match = "none";
40096
+ filters = decoder.list(only.not.or, false, 1);
40097
+ } else {
40098
+ filters = decoder.list(inner, false, 1);
40099
+ }
40100
+ return {
40101
+ match,
40102
+ filters,
40103
+ query,
40104
+ ...includesRecentlyDeleted === void 0 ? {} : { includesRecentlyDeleted },
40105
+ fullyDecoded: decoder.fullyDecoded
40106
+ };
40107
+ }
40108
+ var SMART_FOLDERS_SQL = `BEGIN;
40109
+ SELECT Z_UUID FROM Z_METADATA;
40110
+ SELECT json_group_array(json_object(
40111
+ 'pk', f.Z_PK,
40112
+ 'identifier', f.ZIDENTIFIER,
40113
+ 'title', f.ZTITLE2,
40114
+ 'folderType', f.ZFOLDERTYPE,
40115
+ 'query', f.ZSMARTFOLDERQUERYJSON,
40116
+ 'parentPk', p.Z_PK,
40117
+ 'parentIdentifier', p.ZIDENTIFIER,
40118
+ 'parentTitle', p.ZTITLE2,
40119
+ 'accountPk', a.Z_PK,
40120
+ 'accountIdentifier', a.ZIDENTIFIER,
40121
+ 'accountName', a.ZNAME))
40122
+ FROM ZICCLOUDSYNCINGOBJECT f
40123
+ LEFT JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = f.ZPARENT
40124
+ LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = f.ZOWNER
40125
+ WHERE f.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICFolder')
40126
+ AND (f.ZFOLDERTYPE = 2 OR f.ZSMARTFOLDERQUERYJSON IS NOT NULL)
40127
+ AND COALESCE(f.ZMARKEDFORDELETION, 0) = 0;
40128
+ SELECT json_group_object(ZIDENTIFIER, json_object('pk', Z_PK, 'title', ZTITLE2))
40129
+ FROM ZICCLOUDSYNCINGOBJECT
40130
+ WHERE Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICFolder')
40131
+ AND ZIDENTIFIER IS NOT NULL;
40132
+ SELECT json_group_array(json_object('standardized', ZSTANDARDIZEDCONTENT, 'display', ZDISPLAYTEXT))
40133
+ FROM ZICCLOUDSYNCINGOBJECT
40134
+ WHERE Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICHashtag')
40135
+ AND ZSTANDARDIZEDCONTENT IS NOT NULL
40136
+ AND COALESCE(ZMARKEDFORDELETION, 0) = 0;
40137
+ COMMIT;`;
40138
+ var REQUIRED_COLUMNS = [
40139
+ "ZIDENTIFIER",
40140
+ "ZTITLE2",
40141
+ "ZFOLDERTYPE",
40142
+ "ZSMARTFOLDERQUERYJSON",
40143
+ "ZPARENT",
40144
+ "ZOWNER",
40145
+ "ZNAME",
40146
+ "ZMARKEDFORDELETION",
40147
+ "ZSTANDARDIZEDCONTENT",
40148
+ "ZDISPLAYTEXT"
40149
+ ];
40150
+ function buildSmartFolders(output) {
40151
+ const lines = output.trim().split("\n");
40152
+ if (lines.length < 4) throw new Error("Unexpected smart folder query output");
40153
+ const [storeUuid, rowsJson, foldersJson, tagsJson] = lines;
40154
+ if (!/^[0-9A-F-]+$/i.test(storeUuid)) throw new Error("Unexpected Notes store identifier");
40155
+ const coreDataId2 = (entity2, pk) => `x-coredata://${storeUuid}/${entity2}/p${pk}`;
40156
+ const rows = JSON.parse(rowsJson);
40157
+ const folderRows = JSON.parse(foldersJson || "{}");
40158
+ const tagRows = JSON.parse(tagsJson || "[]");
40159
+ const folders = {};
40160
+ for (const [identifier, row] of Object.entries(folderRows)) {
40161
+ folders[identifier] = { id: coreDataId2("ICFolder", row.pk), title: row.title };
40162
+ }
40163
+ const tags = {};
40164
+ for (const tag of tagRows) {
40165
+ if (!tag.display) continue;
40166
+ const names = tags[tag.standardized] ||= [];
40167
+ if (!names.includes(tag.display)) names.push(tag.display);
40168
+ }
40169
+ return rows.map((row) => {
40170
+ const decoded = decodeSmartFolderQuery(row.query, { folders, tags });
40171
+ return {
40172
+ id: coreDataId2("ICFolder", row.pk),
40173
+ identifier: row.identifier,
40174
+ name: row.title,
40175
+ account: row.accountName,
40176
+ accountId: row.accountPk === null ? null : coreDataId2("ICAccount", row.accountPk),
40177
+ accountIdentifier: row.accountIdentifier,
40178
+ parent: row.parentTitle,
40179
+ parentId: row.parentPk === null ? null : coreDataId2("ICFolder", row.parentPk),
40180
+ parentIdentifier: row.parentIdentifier,
40181
+ match: decoded.match,
40182
+ filters: decoded.filters,
40183
+ ...decoded.includesRecentlyDeleted === void 0 ? {} : { includesRecentlyDeleted: decoded.includesRecentlyDeleted },
40184
+ fullyDecoded: decoded.fullyDecoded,
40185
+ query: decoded.query,
40186
+ rawQuery: row.query
40187
+ };
40188
+ }).sort(
40189
+ (a, b) => (a.account ?? "").localeCompare(b.account ?? "") || (a.name ?? "").localeCompare(b.name ?? "") || a.id.localeCompare(b.id)
40190
+ );
40191
+ }
40192
+ function runSqlite(dbPath2, sql) {
40193
+ return execFileSync4("sqlite3", ["-readonly", dbPath2, sql], {
40194
+ encoding: "utf8",
40195
+ timeout: 5e3,
40196
+ maxBuffer: 16 * 1024 * 1024,
40197
+ stdio: ["pipe", "pipe", "pipe"]
40198
+ });
40199
+ }
40200
+ function readSmartFolders(dbPath2 = NOTES_DB_PATH2) {
40201
+ if (!fs2.existsSync(dbPath2)) return { folders: null, error: "no_fda", message: FDA_MESSAGE };
40202
+ try {
40203
+ const columns = new Set(
40204
+ runSqlite(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").trim().split("\n")
40205
+ );
40206
+ const missing = REQUIRED_COLUMNS.filter((column) => !columns.has(column));
40207
+ if (missing.length) {
40208
+ return {
40209
+ folders: null,
40210
+ error: "unsupported_schema",
40211
+ message: `This Notes database does not have the columns smart folders need (${missing.join(", ")}).`
40212
+ };
40213
+ }
40214
+ return { folders: buildSmartFolders(runSqlite(dbPath2, SMART_FOLDERS_SQL)) };
40215
+ } catch (error2) {
40216
+ const message = error2 instanceof Error ? error2.message : String(error2);
40217
+ if (message.includes("authorization denied") || message.includes("unable to open database")) {
40218
+ return { folders: null, error: "no_fda", message: FDA_MESSAGE };
40219
+ }
40220
+ console.error(`Failed to read smart folders: ${message}`);
40221
+ return { folders: null, error: "query_error", message: "Failed to read smart folders." };
40222
+ }
40223
+ }
40224
+
39842
40225
  // src/utils/attachmentFs.ts
39843
40226
  import {
39844
- existsSync as existsSync2,
40227
+ existsSync as existsSync3,
39845
40228
  lstatSync,
39846
40229
  mkdirSync,
39847
40230
  mkdtempSync,
@@ -39850,11 +40233,11 @@ import {
39850
40233
  rmSync,
39851
40234
  statSync
39852
40235
  } from "fs";
39853
- import { dirname, isAbsolute, join as join3, relative, resolve, sep } from "path";
39854
- import { homedir as homedir3, tmpdir } from "os";
40236
+ import { dirname, isAbsolute, join as join4, relative, resolve, sep } from "path";
40237
+ import { homedir as homedir4, tmpdir } from "os";
39855
40238
  function allowedSaveRoots() {
39856
40239
  return [
39857
- resolve(homedir3()),
40240
+ resolve(homedir4()),
39858
40241
  resolve(tmpdir()),
39859
40242
  "/Volumes",
39860
40243
  "/private/var/folders",
@@ -39862,8 +40245,8 @@ function allowedSaveRoots() {
39862
40245
  "/private/tmp"
39863
40246
  ];
39864
40247
  }
39865
- function canonicalize(path6) {
39866
- return realpathSync.native(path6);
40248
+ function canonicalize(path7) {
40249
+ return realpathSync.native(path7);
39867
40250
  }
39868
40251
  function isWithinRoots(candidate, roots) {
39869
40252
  return roots.some((root) => {
@@ -39885,9 +40268,9 @@ function canonicalRoots(roots) {
39885
40268
  }
39886
40269
  return canonical;
39887
40270
  }
39888
- function entryExists(path6) {
40271
+ function entryExists(path7) {
39889
40272
  try {
39890
- lstatSync(path6);
40273
+ lstatSync(path7);
39891
40274
  return true;
39892
40275
  } catch (e) {
39893
40276
  const code = e.code;
@@ -39928,7 +40311,7 @@ function assertSafeSavePath(p, roots = allowedSaveRoots()) {
39928
40311
  if (suffix.split(sep).includes("..")) {
39929
40312
  throw new Error(`Refusing to write outside allowed locations (home, temp, /Volumes): "${abs}"`);
39930
40313
  }
39931
- const canonicalDest = suffix ? join3(canonicalAncestor, suffix) : canonicalAncestor;
40314
+ const canonicalDest = suffix ? join4(canonicalAncestor, suffix) : canonicalAncestor;
39932
40315
  const allowed = canonicalRoots(roots);
39933
40316
  if (!isWithinRoots(canonicalAncestor, allowed) || !isWithinRoots(canonicalDest, allowed)) {
39934
40317
  throw new Error(
@@ -39970,16 +40353,16 @@ function makeTempDir() {
39970
40353
  }
39971
40354
  function cleanupTempDir(dir) {
39972
40355
  try {
39973
- if (existsSync2(dir)) rmSync(dir, { recursive: true, force: true });
40356
+ if (existsSync3(dir)) rmSync(dir, { recursive: true, force: true });
39974
40357
  } catch {
39975
40358
  }
39976
40359
  }
39977
40360
 
39978
40361
  // src/services/appleNotesManager.ts
39979
40362
  var import_turndown = __toESM(require_turndown_cjs(), 1);
39980
- import { existsSync as existsSync3 } from "fs";
39981
- import { homedir as homedir4 } from "os";
39982
- import { join as join4 } from "path";
40363
+ import { existsSync as existsSync4 } from "fs";
40364
+ import { homedir as homedir5 } from "os";
40365
+ import { join as join5 } from "path";
39983
40366
  var FIELD_SEP = "";
39984
40367
  var RECORD_SEP = "";
39985
40368
  var AS_FIELD_SEP = "(character id 31)";
@@ -40207,8 +40590,8 @@ function getNoteLinkFromDB(coreDataId2) {
40207
40590
  const match = coreDataId2.match(/\/p(\d+)$/);
40208
40591
  if (!match) return null;
40209
40592
  const pk = parseInt(match[1], 10);
40210
- const dbPath2 = join4(homedir4(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
40211
- if (!existsSync3(dbPath2)) return null;
40593
+ const dbPath2 = join5(homedir5(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
40594
+ if (!existsSync4(dbPath2)) return null;
40212
40595
  try {
40213
40596
  const { DatabaseSync } = __require("node:sqlite");
40214
40597
  const db = new DatabaseSync(dbPath2, { readOnly: true });
@@ -40849,6 +41232,58 @@ var AppleNotesManager = class {
40849
41232
  return resultList as text
40850
41233
  `;
40851
41234
  }
41235
+ /**
41236
+ * Lists every smart folder with its decoded query, read-only.
41237
+ *
41238
+ * Folder metadata and queries come from the NoteStore database (Full Disk
41239
+ * Access). With `includeMatchingNotes`, Notes.app itself is asked which notes
41240
+ * each smart folder currently shows, so membership reflects Notes' own
41241
+ * evaluation of the query rather than a reimplementation of it.
41242
+ *
41243
+ * @param options.includeMatchingNotes - Also list the notes each folder shows
41244
+ * @param options.limit - Maximum matching notes per folder (default 50)
41245
+ * @throws Error when the database cannot be read (message says why)
41246
+ */
41247
+ listSmartFolders(options = {}) {
41248
+ const result = readSmartFolders();
41249
+ if (!result.folders) throw new Error(result.message || "Failed to read smart folders.");
41250
+ if (!options.includeMatchingNotes) return result.folders;
41251
+ const limit = options.limit ?? 50;
41252
+ return result.folders.map((folder) => {
41253
+ try {
41254
+ const { total, notes } = this.listSmartFolderNoteRefs(folder.id, limit);
41255
+ return { ...folder, matchingNoteCount: total, matchingNotes: notes };
41256
+ } catch (error2) {
41257
+ return {
41258
+ ...folder,
41259
+ matchingNotesError: error2 instanceof Error ? error2.message : String(error2)
41260
+ };
41261
+ }
41262
+ });
41263
+ }
41264
+ /**
41265
+ * Asks Notes.app for the notes one smart folder currently shows.
41266
+ *
41267
+ * @returns The folder's total note count and up to `limit` (title, id) pairs
41268
+ */
41269
+ listSmartFolderNoteRefs(folderId2, limit) {
41270
+ if (!/^x-coredata:\/\/[0-9a-f-]+\/ICFolder\/p\d+$/i.test(folderId2))
41271
+ throw new Error("An exact folder ID is required");
41272
+ const safeLimit = Math.max(1, Math.floor(limit));
41273
+ const script = buildAppLevelScript(
41274
+ this.buildBulkListCommand({ folderRef: `folder id "${folderId2}"`, sliceLimit: safeLimit })
41275
+ );
41276
+ const result = executeAppleScript(script);
41277
+ if (!result.success) {
41278
+ throw new Error(`Failed to list smart folder notes: ${result.error ?? "unknown error"}`);
41279
+ }
41280
+ const sepIdx = result.output.indexOf(RECORD_SEP);
41281
+ const header = sepIdx === -1 ? result.output : result.output.slice(0, sepIdx);
41282
+ const total = Number.parseInt(header.trim(), 10);
41283
+ if (Number.isNaN(total)) throw new Error("Unexpected smart folder listing output");
41284
+ const records = sepIdx === -1 ? "" : result.output.slice(sepIdx + 1);
41285
+ return { total, notes: this.parseBulkListOutput(records, safeLimit) };
41286
+ }
40852
41287
  /**
40853
41288
  * Parses bulk listing output into deduplicated (title, id) pairs.
40854
41289
  *
@@ -42009,7 +42444,7 @@ var AppleNotesManager = class {
42009
42444
  if (parts[0] !== "OK") {
42010
42445
  return { success: false, error: parts[1]?.trim() || "attachment not found" };
42011
42446
  }
42012
- if (!existsSync3(abs) || fileSize(abs) === 0) {
42447
+ if (!existsSync4(abs) || fileSize(abs) === 0) {
42013
42448
  return { success: false, error: `Notes reported success but no file was written to ${abs}` };
42014
42449
  }
42015
42450
  return {
@@ -42452,15 +42887,15 @@ var AppleNotesManager = class {
42452
42887
  };
42453
42888
 
42454
42889
  // src/utils/syncDetection.ts
42455
- import { execFileSync as execFileSync4 } from "child_process";
42456
- import * as fs2 from "fs";
42457
- import * as path2 from "path";
42458
- import * as os2 from "os";
42459
- var NOTES_DB_PATH2 = path2.join(
42460
- os2.homedir(),
42890
+ import { execFileSync as execFileSync5 } from "child_process";
42891
+ import * as fs3 from "fs";
42892
+ import * as path3 from "path";
42893
+ import * as os3 from "os";
42894
+ var NOTES_DB_PATH3 = path3.join(
42895
+ os3.homedir(),
42461
42896
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
42462
42897
  );
42463
- var WAL_PATH = `${NOTES_DB_PATH2}-wal`;
42898
+ var WAL_PATH = `${NOTES_DB_PATH3}-wal`;
42464
42899
  var RECENT_ACTIVITY_THRESHOLD_SECONDS = 5;
42465
42900
  var SYNC_STATUS_CACHE_TTL_MS = 2e3;
42466
42901
  var cachedSyncStatus = null;
@@ -42476,14 +42911,14 @@ function getSyncStatus(useCache = true) {
42476
42911
  recentActivity: false
42477
42912
  };
42478
42913
  try {
42479
- if (!fs2.existsSync(NOTES_DB_PATH2)) {
42914
+ if (!fs3.existsSync(NOTES_DB_PATH3)) {
42480
42915
  status.error = "Notes database not found";
42481
42916
  cachedSyncStatus = status;
42482
42917
  cacheTimestamp = Date.now();
42483
42918
  return status;
42484
42919
  }
42485
- if (fs2.existsSync(WAL_PATH)) {
42486
- const walStats = fs2.statSync(WAL_PATH);
42920
+ if (fs3.existsSync(WAL_PATH)) {
42921
+ const walStats = fs3.statSync(WAL_PATH);
42487
42922
  const secondsAgo = (Date.now() - walStats.mtimeMs) / 1e3;
42488
42923
  status.secondsSinceLastChange = Math.round(secondsAgo);
42489
42924
  status.recentActivity = secondsAgo < RECENT_ACTIVITY_THRESHOLD_SECONDS;
@@ -42497,9 +42932,9 @@ function getSyncStatus(useCache = true) {
42497
42932
  WHERE object.ZCLOUDSTATE = state.Z_PK
42498
42933
  );
42499
42934
  `;
42500
- const result = execFileSync4(
42935
+ const result = execFileSync5(
42501
42936
  "sqlite3",
42502
- ["-readonly", NOTES_DB_PATH2, query.replace(/\n/g, " ")],
42937
+ ["-readonly", NOTES_DB_PATH3, query.replace(/\n/g, " ")],
42503
42938
  {
42504
42939
  encoding: "utf8",
42505
42940
  timeout: 5e3,
@@ -42556,15 +42991,15 @@ function withSyncAwarenessSync(operation, fn) {
42556
42991
  }
42557
42992
 
42558
42993
  // src/utils/noteMetadata.ts
42559
- import { execFileSync as execFileSync5 } from "child_process";
42560
- import * as fs3 from "fs";
42561
- import * as path3 from "path";
42562
- import * as os3 from "os";
42563
- var NOTES_DB_PATH3 = path3.join(
42564
- os3.homedir(),
42994
+ import { execFileSync as execFileSync6 } from "child_process";
42995
+ import * as fs4 from "fs";
42996
+ import * as path4 from "path";
42997
+ import * as os4 from "os";
42998
+ var NOTES_DB_PATH4 = path4.join(
42999
+ os4.homedir(),
42565
43000
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
42566
43001
  );
42567
- var FDA_MESSAGE = `Full Disk Access is required to read note metadata. 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.`;
43002
+ var FDA_MESSAGE2 = `Full Disk Access is required to read note metadata. 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.`;
42568
43003
  var COLUMN_MAP = [
42569
43004
  { key: "pinned", column: "ZISPINNED", type: "bool" },
42570
43005
  { key: "hasChecklist", column: "ZHASCHECKLIST", type: "bool" },
@@ -42576,15 +43011,15 @@ var COLUMN_MAP = [
42576
43011
  { key: "widgetSnippet", column: "ZWIDGETSNIPPET", type: "text" },
42577
43012
  { key: "smartFolderQuery", column: "ZSMARTFOLDERQUERYJSON", type: "text" }
42578
43013
  ];
42579
- function runSqlite(query) {
42580
- return execFileSync5("sqlite3", ["-readonly", NOTES_DB_PATH3, query], {
43014
+ function runSqlite2(query) {
43015
+ return execFileSync6("sqlite3", ["-readonly", NOTES_DB_PATH4, query], {
42581
43016
  encoding: "utf8",
42582
43017
  timeout: 5e3,
42583
43018
  stdio: ["pipe", "pipe", "pipe"]
42584
43019
  }).trim();
42585
43020
  }
42586
43021
  function presentColumns() {
42587
- const out = runSqlite("PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);");
43022
+ const out = runSqlite2("PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);");
42588
43023
  const cols = /* @__PURE__ */ new Set();
42589
43024
  for (const line of out.split("\n")) {
42590
43025
  const name = line.split("|")[1];
@@ -42602,8 +43037,8 @@ function getNoteMetadata(noteId3) {
42602
43037
  };
42603
43038
  }
42604
43039
  const pk = pkMatch[1];
42605
- if (!fs3.existsSync(NOTES_DB_PATH3)) {
42606
- return { metadata: null, error: "no_fda", message: FDA_MESSAGE };
43040
+ if (!fs4.existsSync(NOTES_DB_PATH4)) {
43041
+ return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
42607
43042
  }
42608
43043
  try {
42609
43044
  const available = presentColumns();
@@ -42612,7 +43047,7 @@ function getNoteMetadata(noteId3) {
42612
43047
  return { metadata: {} };
42613
43048
  }
42614
43049
  const pairs = selected.map((c) => `'${c.key}', ${c.column}`).join(", ");
42615
- const row = runSqlite(
43050
+ const row = runSqlite2(
42616
43051
  `SELECT json_object(${pairs}) FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = ${pk};`
42617
43052
  );
42618
43053
  if (!row) {
@@ -42637,7 +43072,7 @@ function getNoteMetadata(noteId3) {
42637
43072
  } catch (error2) {
42638
43073
  const message = error2 instanceof Error ? error2.message : String(error2);
42639
43074
  if (message.includes("authorization denied") || message.includes("unable to open database")) {
42640
- return { metadata: null, error: "no_fda", message: FDA_MESSAGE };
43075
+ return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
42641
43076
  }
42642
43077
  console.error(`Failed to read note metadata: ${message}`);
42643
43078
  return { metadata: null, error: "query_error", message: "Failed to read note metadata." };
@@ -42645,12 +43080,12 @@ function getNoteMetadata(noteId3) {
42645
43080
  }
42646
43081
 
42647
43082
  // src/utils/noteIdentifiers.ts
42648
- import { execFileSync as execFileSync6 } from "child_process";
42649
- import * as fs4 from "fs";
42650
- import * as os4 from "os";
42651
- import * as path4 from "path";
42652
- var NOTES_DB_PATH4 = path4.join(
42653
- os4.homedir(),
43083
+ import { execFileSync as execFileSync7 } from "child_process";
43084
+ import * as fs5 from "fs";
43085
+ import * as os5 from "os";
43086
+ import * as path5 from "path";
43087
+ var NOTES_DB_PATH5 = path5.join(
43088
+ os5.homedir(),
42654
43089
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
42655
43090
  );
42656
43091
  var UUID_PATTERN = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;
@@ -42724,10 +43159,10 @@ function buildLookupSql(entity2, keys) {
42724
43159
  return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object(${fields})) FROM ZICCLOUDSYNCINGOBJECT o ${joins}WHERE o.Z_ENT = ${entityClause(entity2)} AND o.Z_PK IN (${inList})));`;
42725
43160
  }
42726
43161
  function runJsonQuery(sql, dbPath2) {
42727
- if (!fs4.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
43162
+ if (!fs5.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
42728
43163
  let out;
42729
43164
  try {
42730
- out = execFileSync6("sqlite3", ["-readonly", dbPath2, sql], {
43165
+ out = execFileSync7("sqlite3", ["-readonly", dbPath2, sql], {
42731
43166
  encoding: "utf8",
42732
43167
  timeout: 5e3,
42733
43168
  stdio: ["pipe", "pipe", "pipe"]
@@ -42747,7 +43182,7 @@ function runJsonQuery(sql, dbPath2) {
42747
43182
  function coreDataId(store, entity2, pk) {
42748
43183
  return `x-coredata://${store}/${entity2}/p${pk}`;
42749
43184
  }
42750
- function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH4) {
43185
+ function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH5) {
42751
43186
  const resolved = /* @__PURE__ */ new Map();
42752
43187
  const keys = [];
42753
43188
  const uuids = [];
@@ -42793,7 +43228,7 @@ function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH4) {
42793
43228
  return resolved;
42794
43229
  }
42795
43230
  var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
42796
- function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH4) {
43231
+ function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH5) {
42797
43232
  const result = /* @__PURE__ */ new Map();
42798
43233
  const wanted = /* @__PURE__ */ new Map();
42799
43234
  for (const id2 of ids) {
@@ -42829,7 +43264,7 @@ function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH4) {
42829
43264
  }
42830
43265
  return result;
42831
43266
  }
42832
- function withStableIdentifiers(items, entity2, dbPath2 = NOTES_DB_PATH4) {
43267
+ function withStableIdentifiers(items, entity2, dbPath2 = NOTES_DB_PATH5) {
42833
43268
  const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
42834
43269
  if (ids.length === 0) return items;
42835
43270
  const found = lookupStableIdentifiers(ids, entity2, dbPath2);
@@ -43417,13 +43852,13 @@ function positiveTextTerms(node, negated = false) {
43417
43852
  }
43418
43853
 
43419
43854
  // src/utils/noteQueryStore.ts
43420
- import { execFileSync as execFileSync7 } from "child_process";
43421
- import * as fs5 from "fs";
43422
- import * as os5 from "os";
43423
- import * as path5 from "path";
43855
+ import { execFileSync as execFileSync8 } from "child_process";
43856
+ import * as fs6 from "fs";
43857
+ import * as os6 from "os";
43858
+ import * as path6 from "path";
43424
43859
  import { gunzipSync as gunzipSync3 } from "zlib";
43425
- var NOTES_DB_PATH5 = path5.join(
43426
- os5.homedir(),
43860
+ var NOTES_DB_PATH6 = path6.join(
43861
+ os6.homedir(),
43427
43862
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
43428
43863
  );
43429
43864
  var QUERY_SCAN = { DEFAULT: 500, MAX: 5e3 };
@@ -43510,13 +43945,13 @@ function countWords(text) {
43510
43945
  }
43511
43946
  return count;
43512
43947
  }
43513
- var REQUIRED_COLUMNS = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
43948
+ var REQUIRED_COLUMNS2 = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
43514
43949
  function col(available, alias, name) {
43515
43950
  return available.has(name) ? `${alias}.${name}` : "NULL";
43516
43951
  }
43517
43952
  var entity = (name) => `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME='${name}')`;
43518
43953
  function buildScanSql(available, options) {
43519
- const missing = REQUIRED_COLUMNS.filter((c) => !available.has(c));
43954
+ const missing = REQUIRED_COLUMNS2.filter((c) => !available.has(c));
43520
43955
  if (missing.length) {
43521
43956
  throw new NoteQueryStoreError(
43522
43957
  `This macOS version's Notes database lacks columns query-notes needs (${missing.join(", ")}).`,
@@ -43557,8 +43992,8 @@ function buildScanSql(available, options) {
43557
43992
  "COMMIT;"
43558
43993
  ].join(" ");
43559
43994
  }
43560
- function runSqlite2(dbPath2, query) {
43561
- return execFileSync7("sqlite3", ["-readonly", dbPath2, query], {
43995
+ function runSqlite3(dbPath2, query) {
43996
+ return execFileSync8("sqlite3", ["-readonly", dbPath2, query], {
43562
43997
  encoding: "utf8",
43563
43998
  timeout: 3e4,
43564
43999
  maxBuffer: 512 * 1024 * 1024,
@@ -43567,7 +44002,7 @@ function runSqlite2(dbPath2, query) {
43567
44002
  }
43568
44003
  function presentColumns2(dbPath2) {
43569
44004
  const cols = /* @__PURE__ */ new Set();
43570
- for (const line of runSqlite2(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n")) {
44005
+ for (const line of runSqlite3(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n")) {
43571
44006
  const name = line.split("|")[1];
43572
44007
  if (name) cols.add(name);
43573
44008
  }
@@ -43585,12 +44020,12 @@ function resolveFolders(rows) {
43585
44020
  seen.add(pk);
43586
44021
  const parent = row.parent !== null ? resolve3(row.parent, seen) : void 0;
43587
44022
  const name = row.name ?? "";
43588
- const path6 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
44023
+ const path7 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
43589
44024
  const plainPath = parent ? `${parent.plainPath}/${name}` : name;
43590
44025
  const info = {
43591
- path: path6,
44026
+ path: path7,
43592
44027
  plainPath,
43593
- keys: [...new Set([name, plainPath, path6].map(normalizeForMatch))],
44028
+ keys: [...new Set([name, plainPath, path7].map(normalizeForMatch))],
43594
44029
  accountPk: row.owner ?? parent?.accountPk ?? null,
43595
44030
  shared: Boolean(row.shared) || Boolean(parent?.shared)
43596
44031
  };
@@ -43622,12 +44057,12 @@ function queryNotes(expression, options = {}) {
43622
44057
  const includeDeleted = options.includeDeleted ?? false;
43623
44058
  const withBodies = needsContent(ast);
43624
44059
  const withTags = needsTags(ast);
43625
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH5;
43626
- if (!fs5.existsSync(dbPath2)) throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
44060
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH6;
44061
+ if (!fs6.existsSync(dbPath2)) throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
43627
44062
  let output;
43628
44063
  try {
43629
44064
  const available = presentColumns2(dbPath2);
43630
- output = runSqlite2(
44065
+ output = runSqlite3(
43631
44066
  dbPath2,
43632
44067
  buildScanSql(available, { scanLimit, includeDeleted, withBodies, withTags })
43633
44068
  );
@@ -43758,10 +44193,10 @@ function queryNotes(expression, options = {}) {
43758
44193
  import { spawnSync } from "child_process";
43759
44194
 
43760
44195
  // src/services/nativeTags.ts
43761
- import { execFileSync as execFileSync8 } from "node:child_process";
44196
+ import { execFileSync as execFileSync9 } from "node:child_process";
43762
44197
  import { mkdtempSync as mkdtempSync2, writeFileSync, rmSync as rmSync2 } from "node:fs";
43763
44198
  import { tmpdir as tmpdir2 } from "node:os";
43764
- import { join as join9 } from "node:path";
44199
+ import { join as join10 } from "node:path";
43765
44200
 
43766
44201
  // src/services/shortcutConsent.ts
43767
44202
  function shortcutConsentHint(shortcut) {
@@ -43837,7 +44272,7 @@ function addNativeTags(request, deps) {
43837
44272
  };
43838
44273
  }
43839
44274
  function listInstalledShortcuts() {
43840
- return execFileSync8("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
44275
+ return execFileSync9("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
43841
44276
  encoding: "utf8",
43842
44277
  timeout: 15e3,
43843
44278
  maxBuffer: 1024 * 1024,
@@ -43863,11 +44298,11 @@ function runNativeTagsShortcut(input) {
43863
44298
  const status = nativeTagsStatus();
43864
44299
  if (!status.installed)
43865
44300
  throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
43866
- const directory = mkdtempSync2(join9(tmpdir2(), "apple-notes-native-tags-"));
44301
+ const directory = mkdtempSync2(join10(tmpdir2(), "apple-notes-native-tags-"));
43867
44302
  try {
43868
- const path6 = join9(directory, "request.json");
43869
- writeFileSync(path6, JSON.stringify(input), { mode: 384 });
43870
- execFileSync8("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path6], {
44303
+ const path7 = join10(directory, "request.json");
44304
+ writeFileSync(path7, JSON.stringify(input), { mode: 384 });
44305
+ execFileSync9("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path7], {
43871
44306
  encoding: "utf8",
43872
44307
  timeout: 6e4,
43873
44308
  maxBuffer: 1024 * 1024,
@@ -43886,10 +44321,10 @@ function runNativeTagsShortcut(input) {
43886
44321
  }
43887
44322
 
43888
44323
  // src/services/backgroundNotes.ts
43889
- import { execFileSync as execFileSync9 } from "node:child_process";
44324
+ import { execFileSync as execFileSync10 } from "node:child_process";
43890
44325
  import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
43891
44326
  import { tmpdir as tmpdir3 } from "node:os";
43892
- import { join as join10 } from "node:path";
44327
+ import { join as join11 } from "node:path";
43893
44328
 
43894
44329
  // src/utils/appendMarkdown.ts
43895
44330
  var escape2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -44078,9 +44513,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
44078
44513
  throw new Error(
44079
44514
  `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
44080
44515
  );
44081
- const directory = mkdtempSync3(join10(tmpdir3(), "apple-notes-background-"));
44516
+ const directory = mkdtempSync3(join11(tmpdir3(), "apple-notes-background-"));
44082
44517
  try {
44083
- const file = join10(directory, "request.json");
44518
+ const file = join11(directory, "request.json");
44084
44519
  writeFileSync2(
44085
44520
  file,
44086
44521
  JSON.stringify({
@@ -44100,7 +44535,7 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
44100
44535
  { mode: 384 }
44101
44536
  );
44102
44537
  try {
44103
- execFileSync9("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
44538
+ execFileSync10("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
44104
44539
  encoding: "utf8",
44105
44540
  timeout: 6e4,
44106
44541
  maxBuffer: 1024 * 1024,
@@ -44303,7 +44738,7 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
44303
44738
  throw new Error(
44304
44739
  `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
44305
44740
  );
44306
- const segments = (path6) => JSON.stringify(splitFolderPath(path6).map((part) => part.toLocaleLowerCase()));
44741
+ const segments = (path7) => JSON.stringify(splitFolderPath(path7).map((part) => part.toLocaleLowerCase()));
44307
44742
  if (request.folder) {
44308
44743
  const wanted = segments(request.folder);
44309
44744
  if (!manager.listAccounts().some(
@@ -44392,7 +44827,7 @@ ${request.content}`
44392
44827
  }
44393
44828
 
44394
44829
  // src/services/capabilityMatrix.ts
44395
- import { execFileSync as execFileSync10 } from "node:child_process";
44830
+ import { execFileSync as execFileSync11 } from "node:child_process";
44396
44831
  import { release } from "node:os";
44397
44832
  var MACOS_SHORTCUTS_CLI = "12.0";
44398
44833
  var MACOS_MARKDOWN_IMPORT = "26.0";
@@ -44599,7 +45034,7 @@ function evaluateFeatures(env, features = FEATURES) {
44599
45034
  function readMacOSVersion() {
44600
45035
  if (process.platform !== "darwin") return null;
44601
45036
  try {
44602
- const out = execFileSync10("/usr/bin/sw_vers", ["-productVersion"], {
45037
+ const out = execFileSync11("/usr/bin/sw_vers", ["-productVersion"], {
44603
45038
  encoding: "utf8",
44604
45039
  timeout: 3e3,
44605
45040
  stdio: ["ignore", "pipe", "ignore"]
@@ -44632,9 +45067,9 @@ function getCapabilityMatrix(probe = probeCapabilityEnvironment) {
44632
45067
  return evaluateFeatures(probe());
44633
45068
  }
44634
45069
  function formatCapabilityMatrix(matrix) {
44635
- const os6 = matrix.runtimeOS;
45070
+ const os7 = matrix.runtimeOS;
44636
45071
  const lines = [
44637
- `Feature matrix (macOS ${os6.macOSVersion ?? "unknown"}, Darwin ${os6.darwinRelease}):`
45072
+ `Feature matrix (macOS ${os7.macOSVersion ?? "unknown"}, Darwin ${os7.darwinRelease}):`
44638
45073
  ];
44639
45074
  for (const [name, status] of Object.entries(matrix.features)) {
44640
45075
  lines.push(
@@ -44752,19 +45187,19 @@ function formatDoctorReport(r) {
44752
45187
  }
44753
45188
 
44754
45189
  // src/services/fileConfig.ts
44755
- import { existsSync as existsSync8, readFileSync as readFileSync2 } from "fs";
44756
- import { join as join11 } from "path";
44757
- import { homedir as homedir9 } from "os";
45190
+ import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
45191
+ import { join as join12 } from "path";
45192
+ import { homedir as homedir10 } from "os";
44758
45193
  function fileConfigPath(env = process.env) {
44759
45194
  const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
44760
45195
  if (override && override.trim()) return override.trim();
44761
- return join11(homedir9(), "Library", "Application Support", "apple-notes-mcp", "config.json");
45196
+ return join12(homedir10(), "Library", "Application Support", "apple-notes-mcp", "config.json");
44762
45197
  }
44763
- function loadFileConfig(env = process.env, path6 = fileConfigPath(env)) {
45198
+ function loadFileConfig(env = process.env, path7 = fileConfigPath(env)) {
44764
45199
  const applied = [];
44765
45200
  try {
44766
- if (!existsSync8(path6)) return applied;
44767
- const parsed = JSON.parse(readFileSync2(path6, "utf8"));
45201
+ if (!existsSync9(path7)) return applied;
45202
+ const parsed = JSON.parse(readFileSync2(path7, "utf8"));
44768
45203
  if (!parsed || typeof parsed !== "object") return applied;
44769
45204
  for (const [k, v] of Object.entries(parsed)) {
44770
45205
  if (typeof v !== "string") continue;
@@ -44774,7 +45209,7 @@ function loadFileConfig(env = process.env, path6 = fileConfigPath(env)) {
44774
45209
  }
44775
45210
  }
44776
45211
  } catch (e) {
44777
- console.error(`Failed to load apple-notes-mcp config file ${path6}: ${String(e)}`);
45212
+ console.error(`Failed to load apple-notes-mcp config file ${path7}: ${String(e)}`);
44778
45213
  }
44779
45214
  return applied;
44780
45215
  }
@@ -45285,7 +45720,7 @@ import {
45285
45720
  writeFileSync as writeFileSync3
45286
45721
  } from "node:fs";
45287
45722
  import { tmpdir as tmpdir4 } from "node:os";
45288
- import { basename, isAbsolute as isAbsolute2, join as join12 } from "node:path";
45723
+ import { basename, isAbsolute as isAbsolute2, join as join13 } from "node:path";
45289
45724
  var noteId = exactIdInput(
45290
45725
  "ICNote",
45291
45726
  /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i,
@@ -45328,9 +45763,9 @@ function assertExistingContentPreserved(before, after) {
45328
45763
  throw new Error("Existing native object content or presentation changed");
45329
45764
  }
45330
45765
  }
45331
- function localAttachment(path6) {
45332
- if (!isAbsolute2(path6)) throw new Error("An absolute local file path is required");
45333
- const descriptor = openSync(path6, constants.O_RDONLY | constants.O_NOFOLLOW);
45766
+ function localAttachment(path7) {
45767
+ if (!isAbsolute2(path7)) throw new Error("An absolute local file path is required");
45768
+ const descriptor = openSync(path7, constants.O_RDONLY | constants.O_NOFOLLOW);
45334
45769
  try {
45335
45770
  const stat = fstatSync(descriptor);
45336
45771
  if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
@@ -45394,13 +45829,13 @@ function registerDirectOperations(server2, manager) {
45394
45829
  "add-attachment",
45395
45830
  "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.",
45396
45831
  { id: noteId, expectedContentHash: revision, path: external_exports.string().min(1).max(4096) },
45397
- ({ id: id2, expectedContentHash, path: path6 }) => {
45832
+ ({ id: id2, expectedContentHash, path: path7 }) => {
45398
45833
  const before = readSnapshot(manager, id2);
45399
45834
  if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
45400
- const bytes = localAttachment(path6);
45835
+ const bytes = localAttachment(path7);
45401
45836
  const beforeAttachments = manager.listAttachmentsById(id2);
45402
- const directory = mkdtempSync4(join12(tmpdir4(), "notes-attachment-add-"));
45403
- const temporaryFile = join12(directory, basename(path6));
45837
+ const directory = mkdtempSync4(join13(tmpdir4(), "notes-attachment-add-"));
45838
+ const temporaryFile = join13(directory, basename(path7));
45404
45839
  try {
45405
45840
  writeFileSync3(temporaryFile, bytes, { mode: 384 });
45406
45841
  if (readSnapshot(manager, id2).hash !== before.hash)
@@ -45946,7 +46381,7 @@ function registerNativeOperations(server2, manager) {
45946
46381
 
45947
46382
  // src/setupShortcuts.ts
45948
46383
  import { spawnSync as spawnSync2 } from "node:child_process";
45949
- import { existsSync as existsSync9 } from "node:fs";
46384
+ import { existsSync as existsSync10 } from "node:fs";
45950
46385
  import { release as release2 } from "node:os";
45951
46386
  import { dirname as dirname2, resolve as resolve2 } from "node:path";
45952
46387
  import { fileURLToPath } from "node:url";
@@ -45966,16 +46401,16 @@ var shortcutFiles = [
45966
46401
  ];
45967
46402
  function setupShortcuts(checkOnly, dependencies = {}) {
45968
46403
  const status = dependencies.status || nativeTagsStatus;
45969
- const exists = dependencies.exists || existsSync9;
45970
- const open = dependencies.open || ((path6) => {
45971
- const result = spawnSync2("/usr/bin/open", [path6], { encoding: "utf8" });
46404
+ const exists = dependencies.exists || existsSync10;
46405
+ const open = dependencies.open || ((path7) => {
46406
+ const result = spawnSync2("/usr/bin/open", [path7], { encoding: "utf8" });
45972
46407
  return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
45973
46408
  });
45974
46409
  const baseDirectory = dependencies.baseDirectory || resolve2(dirname2(fileURLToPath(import.meta.url)), "../shortcuts");
45975
46410
  const osRelease = (dependencies.osRelease || release2)();
45976
46411
  const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
45977
46412
  const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
45978
- const path6 = resolve2(baseDirectory, file);
46413
+ const path7 = resolve2(baseDirectory, file);
45979
46414
  let installed = false;
45980
46415
  let identifier;
45981
46416
  let error2;
@@ -45991,9 +46426,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
45991
46426
  if (!installed && !checkOnly) {
45992
46427
  if (optional2 && !(darwinMajor >= MARKDOWN_MIN_DARWIN_MAJOR))
45993
46428
  skipped = `requires macOS 26 or later (this Mac reports Darwin ${osRelease})`;
45994
- else if (!exists(path6)) error2 = `Packaged Shortcut is missing: ${path6}`;
46429
+ else if (!exists(path7)) error2 = `Packaged Shortcut is missing: ${path7}`;
45995
46430
  else {
45996
- const result = open(path6);
46431
+ const result = open(path7);
45997
46432
  opened = result.ok;
45998
46433
  if (!result.ok) error2 = result.error || `Could not open ${file}`;
45999
46434
  }
@@ -46002,7 +46437,7 @@ function setupShortcuts(checkOnly, dependencies = {}) {
46002
46437
  name,
46003
46438
  installed,
46004
46439
  identifier,
46005
- file: path6,
46440
+ file: path7,
46006
46441
  opened,
46007
46442
  ...optional2 ? { optional: optional2 } : {},
46008
46443
  ...skipped ? { skipped } : {},
@@ -47346,6 +47781,55 @@ ${folderList}${syncNote}`,
47346
47781
  );
47347
47782
  }, "Error listing folders")
47348
47783
  );
47784
+ registerTool(
47785
+ "list-smart-folders",
47786
+ {
47787
+ description: `Use when: listing Smart Folders and the rules that define them.
47788
+ Returns: each smart folder's name, ids, account, and parent, its rules decoded as match ("all"/"any"/"none") plus filters (each with a readable description), the stored query with the outer deleted wrapper removed, and the raw stored query JSON. With includeMatchingNotes, also the notes Notes.app currently shows in each folder.
47789
+ Do not use when: listing ordinary folders (list-folders) or searching notes (search-notes).
47790
+ Safety: read-only; reads the NoteStore database and requires Full Disk Access. includeMatchingNotes asks Notes.app (Automation permission) for each folder's current contents rather than re-evaluating the rules. Unrecognized rules are kept as "unknown" filters and set fullyDecoded false.`,
47791
+ inputSchema: {
47792
+ includeMatchingNotes: external_exports.boolean().optional().describe(
47793
+ "Also list the notes Notes.app currently shows in each smart folder (default false)"
47794
+ ),
47795
+ limit: external_exports.number().int().min(1).max(500).optional().describe("Maximum matching notes returned per folder (default 50; count is always total)")
47796
+ },
47797
+ outputSchema: {
47798
+ smartFolders: external_exports.array(external_exports.object({}).passthrough()).optional(),
47799
+ count: external_exports.number().optional(),
47800
+ fullyDecoded: external_exports.boolean().optional()
47801
+ },
47802
+ annotations: { readOnlyHint: true }
47803
+ },
47804
+ withErrorHandling(({ includeMatchingNotes = false, limit }) => {
47805
+ const folders = notesManager.listSmartFolders({ includeMatchingNotes, limit });
47806
+ const fullyDecoded = folders.every((folder) => folder.fullyDecoded);
47807
+ if (folders.length === 0) {
47808
+ return successResponse("No smart folders found.", {
47809
+ smartFolders: [],
47810
+ count: 0,
47811
+ fullyDecoded
47812
+ });
47813
+ }
47814
+ const lines = folders.map((folder) => {
47815
+ const joiner = folder.match === "any" ? " OR " : " AND ";
47816
+ const rules = folder.filters.map((filter) => filter.description).join(joiner);
47817
+ const prefix = folder.match === "none" ? "none of: " : "";
47818
+ const where = [folder.account, folder.parent].filter(Boolean).join(" / ");
47819
+ const matches = folder.matchingNoteCount !== void 0 ? ` [${folder.matchingNoteCount} note(s)]` : folder.matchingNotesError ? ` [notes unavailable: ${folder.matchingNotesError}]` : "";
47820
+ return ` - ${folder.name ?? "(untitled)"}${where ? ` (${where})` : ""}: ${prefix}${rules || "(no rules)"}${matches}`;
47821
+ });
47822
+ return successResponse(
47823
+ `Found ${folders.length} smart folder(s):
47824
+ ${lines.join("\n")}${fullyDecoded ? "" : '\n\nSome rules were not recognized; see filters of type "unknown".'}`,
47825
+ {
47826
+ smartFolders: folders,
47827
+ count: folders.length,
47828
+ fullyDecoded
47829
+ }
47830
+ );
47831
+ }, "Error listing smart folders")
47832
+ );
47349
47833
  registerTool(
47350
47834
  "create-folder",
47351
47835
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.8.26",
3
+ "version": "2.8.27",
4
4
  "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",