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.
- package/README.md +100 -0
- package/build/index.js +1030 -92
- 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(
|
|
3272
|
-
let input =
|
|
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
|
|
3678
|
-
wsComponent.path =
|
|
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,
|
|
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,
|
|
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$/,
|
|
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
|
|
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 =
|
|
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,
|
|
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,
|
|
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(
|
|
12242
|
-
if (!
|
|
12241
|
+
function remove_dot_segments(path5) {
|
|
12242
|
+
if (!path5) return path5;
|
|
12243
12243
|
var output = "";
|
|
12244
|
-
while (
|
|
12245
|
-
if (
|
|
12246
|
-
|
|
12244
|
+
while (path5.length > 0) {
|
|
12245
|
+
if (path5 === "." || path5 === "..") {
|
|
12246
|
+
path5 = "";
|
|
12247
12247
|
break;
|
|
12248
12248
|
}
|
|
12249
|
-
var twochars =
|
|
12250
|
-
var threechars =
|
|
12251
|
-
var fourchars =
|
|
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
|
-
|
|
12253
|
+
path5 = path5.substring(3);
|
|
12254
12254
|
} else if (twochars === "./") {
|
|
12255
|
-
|
|
12255
|
+
path5 = path5.substring(2);
|
|
12256
12256
|
} else if (threechars === "/./") {
|
|
12257
|
-
|
|
12258
|
-
} else if (twochars === "/." &&
|
|
12259
|
-
|
|
12260
|
-
} else if (fourchars === "/../" || threechars === "/.." &&
|
|
12261
|
-
|
|
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 =
|
|
12264
|
+
var segment = path5.match(/(\/?([^\/]*))/)[0];
|
|
12265
12265
|
output += segment;
|
|
12266
|
-
|
|
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
|
|
24235
|
+
var Parser2 = root.DOMParser;
|
|
24236
24236
|
var canParse = false;
|
|
24237
24237
|
try {
|
|
24238
|
-
if (new
|
|
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
|
|
24246
|
+
var Parser2 = function() {
|
|
24247
24247
|
};
|
|
24248
24248
|
{
|
|
24249
24249
|
var domino = require_lib();
|
|
24250
|
-
|
|
24250
|
+
Parser2.prototype.parseFromString = function(string3) {
|
|
24251
24251
|
return domino.createDocument(string3);
|
|
24252
24252
|
};
|
|
24253
24253
|
}
|
|
24254
|
-
return
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
24983
|
-
const fullPath = [...
|
|
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,
|
|
25099
|
+
constructor(parent, value, path5, key) {
|
|
25100
25100
|
this._cachedPath = [];
|
|
25101
25101
|
this.parent = parent;
|
|
25102
25102
|
this.data = value;
|
|
25103
|
-
this._path =
|
|
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,
|
|
28741
|
-
if (!
|
|
28740
|
+
function getElementAtPath(obj, path5) {
|
|
28741
|
+
if (!path5)
|
|
28742
28742
|
return obj;
|
|
28743
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
32479
|
-
if (
|
|
32478
|
+
function getDotPath(path5) {
|
|
32479
|
+
if (path5.length === 0) {
|
|
32480
32480
|
return "object root";
|
|
32481
32481
|
}
|
|
32482
|
-
return
|
|
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(
|
|
39866
|
-
return realpathSync.native(
|
|
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(
|
|
39888
|
+
function entryExists(path5) {
|
|
39889
39889
|
try {
|
|
39890
|
-
lstatSync(
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
43622
|
+
const directory = mkdtempSync2(join8(tmpdir2(), "apple-notes-native-tags-"));
|
|
42840
43623
|
try {
|
|
42841
|
-
const
|
|
42842
|
-
writeFileSync(
|
|
42843
|
-
|
|
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
|
|
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
|
|
43648
|
+
import { join as join9 } from "node:path";
|
|
42866
43649
|
|
|
42867
43650
|
// src/utils/appendMarkdown.ts
|
|
42868
43651
|
var escape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -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(
|
|
43835
|
+
const directory = mkdtempSync3(join9(tmpdir3(), "apple-notes-background-"));
|
|
43053
43836
|
try {
|
|
43054
|
-
const file =
|
|
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
|
-
|
|
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 = (
|
|
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
|
|
43466
|
-
import { join as
|
|
43467
|
-
import { homedir as
|
|
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
|
|
44254
|
+
return join10(homedir8(), "Library", "Application Support", "apple-notes-mcp", "config.json");
|
|
43472
44255
|
}
|
|
43473
|
-
function loadFileConfig(env = process.env,
|
|
44256
|
+
function loadFileConfig(env = process.env, path5 = fileConfigPath(env)) {
|
|
43474
44257
|
const applied = [];
|
|
43475
44258
|
try {
|
|
43476
|
-
if (!
|
|
43477
|
-
const parsed = JSON.parse(readFileSync2(
|
|
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 ${
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
43966
|
-
if (!isAbsolute2(
|
|
43967
|
-
const descriptor = openSync(
|
|
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:
|
|
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(
|
|
44822
|
+
const bytes = localAttachment(path5);
|
|
44040
44823
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
44041
|
-
const directory = mkdtempSync4(
|
|
44042
|
-
const temporaryFile =
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
|
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 ||
|
|
44540
|
-
const open = dependencies.open || ((
|
|
44541
|
-
const result = spawnSync2("/usr/bin/open", [
|
|
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
|
|
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(
|
|
45427
|
+
else if (!exists(path5)) error2 = `Packaged Shortcut is missing: ${path5}`;
|
|
44565
45428
|
else {
|
|
44566
|
-
const result = open(
|
|
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:
|
|
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
|
{
|