apple-notes-mcp 2.8.25 → 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.
- package/README.md +66 -8
- package/build/index.js +965 -178
- 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(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
|
|
3678
|
-
wsComponent.path =
|
|
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,
|
|
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,
|
|
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(
|
|
12242
|
-
if (!
|
|
12241
|
+
function remove_dot_segments(path7) {
|
|
12242
|
+
if (!path7) return path7;
|
|
12243
12243
|
var output = "";
|
|
12244
|
-
while (
|
|
12245
|
-
if (
|
|
12246
|
-
|
|
12244
|
+
while (path7.length > 0) {
|
|
12245
|
+
if (path7 === "." || path7 === "..") {
|
|
12246
|
+
path7 = "";
|
|
12247
12247
|
break;
|
|
12248
12248
|
}
|
|
12249
|
-
var twochars =
|
|
12250
|
-
var threechars =
|
|
12251
|
-
var fourchars =
|
|
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
|
-
|
|
12253
|
+
path7 = path7.substring(3);
|
|
12254
12254
|
} else if (twochars === "./") {
|
|
12255
|
-
|
|
12255
|
+
path7 = path7.substring(2);
|
|
12256
12256
|
} else if (threechars === "/./") {
|
|
12257
|
-
|
|
12258
|
-
} else if (twochars === "/." &&
|
|
12259
|
-
|
|
12260
|
-
} else if (fourchars === "/../" || threechars === "/.." &&
|
|
12261
|
-
|
|
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 =
|
|
12264
|
+
var segment = path7.match(/(\/?([^\/]*))/)[0];
|
|
12265
12265
|
output += segment;
|
|
12266
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
24983
|
-
const fullPath = [...
|
|
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,
|
|
25099
|
+
constructor(parent, value, path7, key) {
|
|
25100
25100
|
this._cachedPath = [];
|
|
25101
25101
|
this.parent = parent;
|
|
25102
25102
|
this.data = value;
|
|
25103
|
-
this._path =
|
|
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,
|
|
28741
|
-
if (!
|
|
28740
|
+
function getElementAtPath(obj, path7) {
|
|
28741
|
+
if (!path7)
|
|
28742
28742
|
return obj;
|
|
28743
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
32479
|
-
if (
|
|
32478
|
+
function getDotPath(path7) {
|
|
32479
|
+
if (path7.length === 0) {
|
|
32480
32480
|
return "object root";
|
|
32481
32481
|
}
|
|
32482
|
-
return
|
|
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
|
|
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
|
|
39854
|
-
import { homedir as
|
|
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(
|
|
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(
|
|
39866
|
-
return realpathSync.native(
|
|
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(
|
|
40271
|
+
function entryExists(path7) {
|
|
39889
40272
|
try {
|
|
39890
|
-
lstatSync(
|
|
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 ?
|
|
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 (
|
|
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
|
|
39981
|
-
import { homedir as
|
|
39982
|
-
import { join as
|
|
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)";
|
|
@@ -40203,12 +40586,12 @@ function buildAppLevelScript(command) {
|
|
|
40203
40586
|
end tell
|
|
40204
40587
|
`;
|
|
40205
40588
|
}
|
|
40206
|
-
function getNoteLinkFromDB(
|
|
40207
|
-
const match =
|
|
40589
|
+
function getNoteLinkFromDB(coreDataId2) {
|
|
40590
|
+
const match = coreDataId2.match(/\/p(\d+)$/);
|
|
40208
40591
|
if (!match) return null;
|
|
40209
40592
|
const pk = parseInt(match[1], 10);
|
|
40210
|
-
const dbPath2 =
|
|
40211
|
-
if (!
|
|
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
|
*
|
|
@@ -41206,9 +41641,9 @@ var AppleNotesManager = class {
|
|
|
41206
41641
|
);
|
|
41207
41642
|
const idResult = executeAppleScript(idScript);
|
|
41208
41643
|
const [rawId = "", rawAccount = ""] = idResult.success ? idResult.output.split(FIELD_SEP) : [];
|
|
41209
|
-
const
|
|
41644
|
+
const folderId2 = idResult.success ? extractCoreDataId(rawId, "folder") : "";
|
|
41210
41645
|
return {
|
|
41211
|
-
id:
|
|
41646
|
+
id: folderId2,
|
|
41212
41647
|
name,
|
|
41213
41648
|
account: rawAccount.trim() || targetAccount || ""
|
|
41214
41649
|
};
|
|
@@ -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 (!
|
|
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
|
|
42456
|
-
import * as
|
|
42457
|
-
import * as
|
|
42458
|
-
import * as
|
|
42459
|
-
var
|
|
42460
|
-
|
|
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 = `${
|
|
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 (!
|
|
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 (
|
|
42486
|
-
const walStats =
|
|
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 =
|
|
42935
|
+
const result = execFileSync5(
|
|
42501
42936
|
"sqlite3",
|
|
42502
|
-
["-readonly",
|
|
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
|
|
42560
|
-
import * as
|
|
42561
|
-
import * as
|
|
42562
|
-
import * as
|
|
42563
|
-
var
|
|
42564
|
-
|
|
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
|
|
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
|
|
42580
|
-
return
|
|
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 =
|
|
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 (!
|
|
42606
|
-
return { metadata: null, error: "no_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 =
|
|
43050
|
+
const row = runSqlite2(
|
|
42616
43051
|
`SELECT json_object(${pairs}) FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = ${pk};`
|
|
42617
43052
|
);
|
|
42618
43053
|
if (!row) {
|
|
@@ -42637,13 +43072,251 @@ 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:
|
|
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." };
|
|
42644
43079
|
}
|
|
42645
43080
|
}
|
|
42646
43081
|
|
|
43082
|
+
// src/utils/noteIdentifiers.ts
|
|
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(),
|
|
43089
|
+
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
43090
|
+
);
|
|
43091
|
+
var UUID_PATTERN = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;
|
|
43092
|
+
var NUMERIC_KEY_PATTERN = /^\d{1,18}$/;
|
|
43093
|
+
var NOTE_ID_MESSAGE = "A canonical Apple Note ID is required (x-coredata://.../ICNote/p...), or the note's Notes UUID or numeric key";
|
|
43094
|
+
function identifierForm(value) {
|
|
43095
|
+
if (UUID_PATTERN.test(value)) return "uuid";
|
|
43096
|
+
if (NUMERIC_KEY_PATTERN.test(value)) return "key";
|
|
43097
|
+
return "other";
|
|
43098
|
+
}
|
|
43099
|
+
function isAlternateIdentifier(value) {
|
|
43100
|
+
return identifierForm(value) !== "other";
|
|
43101
|
+
}
|
|
43102
|
+
var IdentifierResolutionError = class extends Error {
|
|
43103
|
+
constructor(code, message) {
|
|
43104
|
+
super(message);
|
|
43105
|
+
this.code = code;
|
|
43106
|
+
this.name = "IdentifierResolutionError";
|
|
43107
|
+
}
|
|
43108
|
+
code;
|
|
43109
|
+
};
|
|
43110
|
+
var ENTITY_LABEL = {
|
|
43111
|
+
ICNote: "note",
|
|
43112
|
+
ICFolder: "folder",
|
|
43113
|
+
ICAccount: "account"
|
|
43114
|
+
};
|
|
43115
|
+
var NO_FDA_MESSAGE = `Resolving a Notes UUID or numeric key reads the Notes database, which needs Full Disk Access for the app that launches this server (System Settings > Privacy & Security > Full Disk Access, then fully quit and relaunch it). x-coredata ids from search-notes, list-notes, or list-folders work without it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
|
|
43116
|
+
function canonicalKey(value) {
|
|
43117
|
+
return BigInt(value).toString();
|
|
43118
|
+
}
|
|
43119
|
+
function assertAll(values, pattern, label) {
|
|
43120
|
+
for (const value of values) {
|
|
43121
|
+
if (!pattern.test(value)) throw new Error(`Refusing to query with an invalid ${label}`);
|
|
43122
|
+
}
|
|
43123
|
+
}
|
|
43124
|
+
function entityClause(entity2) {
|
|
43125
|
+
return `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = '${entity2}')`;
|
|
43126
|
+
}
|
|
43127
|
+
function buildResolveSql(entity2, keys, uuids) {
|
|
43128
|
+
assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
|
|
43129
|
+
assertAll(uuids, UUID_PATTERN, "UUID");
|
|
43130
|
+
const matches = [];
|
|
43131
|
+
if (keys.length > 0) {
|
|
43132
|
+
matches.push(`o.Z_PK IN (${[...new Set(keys.map(canonicalKey))].join(", ")})`);
|
|
43133
|
+
}
|
|
43134
|
+
if (uuids.length > 0) {
|
|
43135
|
+
const variants = /* @__PURE__ */ new Set();
|
|
43136
|
+
for (const uuid2 of uuids) {
|
|
43137
|
+
variants.add(uuid2);
|
|
43138
|
+
variants.add(uuid2.toUpperCase());
|
|
43139
|
+
variants.add(uuid2.toLowerCase());
|
|
43140
|
+
}
|
|
43141
|
+
matches.push(`o.ZIDENTIFIER IN (${[...variants].map((v) => `'${v}'`).join(", ")})`);
|
|
43142
|
+
}
|
|
43143
|
+
const where = matches.length > 0 ? matches.join(" OR ") : "0";
|
|
43144
|
+
return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object('pk', o.Z_PK, 'identifier', o.ZIDENTIFIER)) FROM ZICCLOUDSYNCINGOBJECT o WHERE o.Z_ENT = ${entityClause(entity2)} AND (${where})));`;
|
|
43145
|
+
}
|
|
43146
|
+
function buildLookupSql(entity2, keys) {
|
|
43147
|
+
assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
|
|
43148
|
+
const pks = [...new Set(keys.map(canonicalKey))];
|
|
43149
|
+
const inList = pks.length > 0 ? pks.join(", ") : "NULL";
|
|
43150
|
+
let fields = "'pk', o.Z_PK, 'identifier', o.ZIDENTIFIER";
|
|
43151
|
+
let joins = "";
|
|
43152
|
+
if (entity2 === "ICNote") {
|
|
43153
|
+
fields += ", 'folderIdentifier', f.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
|
|
43154
|
+
joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT f ON f.Z_PK = o.ZFOLDER LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = f.ZOWNER ";
|
|
43155
|
+
} else if (entity2 === "ICFolder") {
|
|
43156
|
+
fields += ", 'parentIdentifier', p.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
|
|
43157
|
+
joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = o.ZPARENT LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = o.ZOWNER ";
|
|
43158
|
+
}
|
|
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})));`;
|
|
43160
|
+
}
|
|
43161
|
+
function runJsonQuery(sql, dbPath2) {
|
|
43162
|
+
if (!fs5.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
43163
|
+
let out;
|
|
43164
|
+
try {
|
|
43165
|
+
out = execFileSync7("sqlite3", ["-readonly", dbPath2, sql], {
|
|
43166
|
+
encoding: "utf8",
|
|
43167
|
+
timeout: 5e3,
|
|
43168
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
43169
|
+
}).trim();
|
|
43170
|
+
} catch (error2) {
|
|
43171
|
+
const detail = error2 instanceof Error ? `${error2.message} ${String(error2.stderr ?? "")}` : String(error2);
|
|
43172
|
+
if (detail.includes("authorization denied") || detail.includes("unable to open database")) {
|
|
43173
|
+
throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
43174
|
+
}
|
|
43175
|
+
console.error(`Identifier query failed: ${detail}`);
|
|
43176
|
+
throw new IdentifierResolutionError("query_error", "Failed to read the Notes database.");
|
|
43177
|
+
}
|
|
43178
|
+
const parsed = JSON.parse(out || "{}");
|
|
43179
|
+
const rows = typeof parsed.rows === "string" ? JSON.parse(parsed.rows) : parsed.rows;
|
|
43180
|
+
return { store: parsed.store ?? null, rows: rows ?? [] };
|
|
43181
|
+
}
|
|
43182
|
+
function coreDataId(store, entity2, pk) {
|
|
43183
|
+
return `x-coredata://${store}/${entity2}/p${pk}`;
|
|
43184
|
+
}
|
|
43185
|
+
function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH5) {
|
|
43186
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
43187
|
+
const keys = [];
|
|
43188
|
+
const uuids = [];
|
|
43189
|
+
for (const value of values) {
|
|
43190
|
+
const form = identifierForm(value);
|
|
43191
|
+
if (form === "key") keys.push(value);
|
|
43192
|
+
else if (form === "uuid") uuids.push(value);
|
|
43193
|
+
else resolved.set(value, value);
|
|
43194
|
+
}
|
|
43195
|
+
if (keys.length === 0 && uuids.length === 0) return resolved;
|
|
43196
|
+
const { store, rows } = runJsonQuery(
|
|
43197
|
+
buildResolveSql(entity2, keys, uuids),
|
|
43198
|
+
dbPath2
|
|
43199
|
+
);
|
|
43200
|
+
if (!store) {
|
|
43201
|
+
throw new IdentifierResolutionError(
|
|
43202
|
+
"query_error",
|
|
43203
|
+
"The Notes database has no store UUID, so an x-coredata id cannot be built."
|
|
43204
|
+
);
|
|
43205
|
+
}
|
|
43206
|
+
const byKey = new Map(rows.map((row) => [String(row.pk), row]));
|
|
43207
|
+
const byUuid = new Map(
|
|
43208
|
+
rows.filter((row) => row.identifier).map((row) => [String(row.identifier).toUpperCase(), row])
|
|
43209
|
+
);
|
|
43210
|
+
const label = ENTITY_LABEL[entity2];
|
|
43211
|
+
const missing = [];
|
|
43212
|
+
for (const key of keys) {
|
|
43213
|
+
const row = byKey.get(canonicalKey(key));
|
|
43214
|
+
if (row) resolved.set(key, coreDataId(store, entity2, row.pk));
|
|
43215
|
+
else missing.push(`numeric key ${key}`);
|
|
43216
|
+
}
|
|
43217
|
+
for (const uuid2 of uuids) {
|
|
43218
|
+
const row = byUuid.get(uuid2.toUpperCase());
|
|
43219
|
+
if (row) resolved.set(uuid2, coreDataId(store, entity2, row.pk));
|
|
43220
|
+
else missing.push(`identifier ${uuid2}`);
|
|
43221
|
+
}
|
|
43222
|
+
if (missing.length > 0) {
|
|
43223
|
+
throw new IdentifierResolutionError(
|
|
43224
|
+
"not_found",
|
|
43225
|
+
`No ${label} found for ${missing.join(", ")}. A numeric key must belong to a ${label}, not another object type.`
|
|
43226
|
+
);
|
|
43227
|
+
}
|
|
43228
|
+
return resolved;
|
|
43229
|
+
}
|
|
43230
|
+
var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
|
|
43231
|
+
function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH5) {
|
|
43232
|
+
const result = /* @__PURE__ */ new Map();
|
|
43233
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
43234
|
+
for (const id2 of ids) {
|
|
43235
|
+
const match = COREDATA_PARTS.exec(id2);
|
|
43236
|
+
if (!match || match[2] !== entity2) continue;
|
|
43237
|
+
const pk = canonicalKey(match[3]);
|
|
43238
|
+
const list = wanted.get(pk) ?? [];
|
|
43239
|
+
list.push({ id: id2, store: match[1] });
|
|
43240
|
+
wanted.set(pk, list);
|
|
43241
|
+
}
|
|
43242
|
+
if (wanted.size === 0) return result;
|
|
43243
|
+
let query;
|
|
43244
|
+
try {
|
|
43245
|
+
query = runJsonQuery(buildLookupSql(entity2, [...wanted.keys()]), dbPath2);
|
|
43246
|
+
} catch {
|
|
43247
|
+
return result;
|
|
43248
|
+
}
|
|
43249
|
+
const store = query.store?.toUpperCase();
|
|
43250
|
+
for (const row of query.rows) {
|
|
43251
|
+
const fields = {};
|
|
43252
|
+
for (const key of [
|
|
43253
|
+
"identifier",
|
|
43254
|
+
"folderIdentifier",
|
|
43255
|
+
"parentIdentifier",
|
|
43256
|
+
"accountIdentifier"
|
|
43257
|
+
]) {
|
|
43258
|
+
const value = row[key];
|
|
43259
|
+
if (typeof value === "string" && value) fields[key] = value;
|
|
43260
|
+
}
|
|
43261
|
+
for (const target of wanted.get(String(row.pk)) ?? []) {
|
|
43262
|
+
if (target.store.toUpperCase() === store) result.set(target.id, fields);
|
|
43263
|
+
}
|
|
43264
|
+
}
|
|
43265
|
+
return result;
|
|
43266
|
+
}
|
|
43267
|
+
function withStableIdentifiers(items, entity2, dbPath2 = NOTES_DB_PATH5) {
|
|
43268
|
+
const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
|
|
43269
|
+
if (ids.length === 0) return items;
|
|
43270
|
+
const found = lookupStableIdentifiers(ids, entity2, dbPath2);
|
|
43271
|
+
if (found.size === 0) return items;
|
|
43272
|
+
return items.map(
|
|
43273
|
+
(item) => item.id && found.has(item.id) ? { ...item, ...found.get(item.id) } : item
|
|
43274
|
+
);
|
|
43275
|
+
}
|
|
43276
|
+
var defaultResolver = (values, entity2) => resolveIdentifiers(values, entity2);
|
|
43277
|
+
var DEFAULT_MAX_ID_LENGTH = 2e3;
|
|
43278
|
+
function acceptAlternateForms(pattern) {
|
|
43279
|
+
return new RegExp(
|
|
43280
|
+
`${pattern.source}|${UUID_PATTERN.source}|${NUMERIC_KEY_PATTERN.source}`,
|
|
43281
|
+
pattern.flags
|
|
43282
|
+
);
|
|
43283
|
+
}
|
|
43284
|
+
function resolveInSchema(values, entity2, ctx, resolver) {
|
|
43285
|
+
if (!values.some(isAlternateIdentifier)) return new Map(values.map((v) => [v, v]));
|
|
43286
|
+
try {
|
|
43287
|
+
return resolver(values, entity2);
|
|
43288
|
+
} catch (error2) {
|
|
43289
|
+
ctx.addIssue({
|
|
43290
|
+
code: external_exports.ZodIssueCode.custom,
|
|
43291
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
43292
|
+
});
|
|
43293
|
+
return null;
|
|
43294
|
+
}
|
|
43295
|
+
}
|
|
43296
|
+
function exactIdInput(entity2, pattern, message, options = {}) {
|
|
43297
|
+
const resolver = options.resolver ?? defaultResolver;
|
|
43298
|
+
return external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern), message).transform((value, ctx) => {
|
|
43299
|
+
const map = resolveInSchema([value], entity2, ctx, resolver);
|
|
43300
|
+
return map ? map.get(value) ?? value : external_exports.NEVER;
|
|
43301
|
+
});
|
|
43302
|
+
}
|
|
43303
|
+
function exactIdArrayInput(entity2, pattern, message, options = {}) {
|
|
43304
|
+
const resolver = options.resolver ?? defaultResolver;
|
|
43305
|
+
const item = external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern), message);
|
|
43306
|
+
const array2 = options.maxItems === void 0 ? external_exports.array(item) : external_exports.array(item).max(options.maxItems);
|
|
43307
|
+
return array2.transform((values, ctx) => {
|
|
43308
|
+
const map = resolveInSchema(values, entity2, ctx, resolver);
|
|
43309
|
+
return map ? values.map((value) => map.get(value) ?? value) : external_exports.NEVER;
|
|
43310
|
+
});
|
|
43311
|
+
}
|
|
43312
|
+
function looseIdTransform(entity2, resolver = defaultResolver) {
|
|
43313
|
+
return (value, ctx) => {
|
|
43314
|
+
if (!isAlternateIdentifier(value)) return value;
|
|
43315
|
+
const map = resolveInSchema([value], entity2, ctx, resolver);
|
|
43316
|
+
return map ? map.get(value) ?? value : external_exports.NEVER;
|
|
43317
|
+
};
|
|
43318
|
+
}
|
|
43319
|
+
|
|
42647
43320
|
// src/utils/contentWarnings.ts
|
|
42648
43321
|
function detectChecklistAttempt(content) {
|
|
42649
43322
|
if (!content) return null;
|
|
@@ -43179,13 +43852,13 @@ function positiveTextTerms(node, negated = false) {
|
|
|
43179
43852
|
}
|
|
43180
43853
|
|
|
43181
43854
|
// src/utils/noteQueryStore.ts
|
|
43182
|
-
import { execFileSync as
|
|
43183
|
-
import * as
|
|
43184
|
-
import * as
|
|
43185
|
-
import * as
|
|
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";
|
|
43186
43859
|
import { gunzipSync as gunzipSync3 } from "zlib";
|
|
43187
|
-
var
|
|
43188
|
-
|
|
43860
|
+
var NOTES_DB_PATH6 = path6.join(
|
|
43861
|
+
os6.homedir(),
|
|
43189
43862
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
43190
43863
|
);
|
|
43191
43864
|
var QUERY_SCAN = { DEFAULT: 500, MAX: 5e3 };
|
|
@@ -43272,13 +43945,13 @@ function countWords(text) {
|
|
|
43272
43945
|
}
|
|
43273
43946
|
return count;
|
|
43274
43947
|
}
|
|
43275
|
-
var
|
|
43948
|
+
var REQUIRED_COLUMNS2 = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
|
|
43276
43949
|
function col(available, alias, name) {
|
|
43277
43950
|
return available.has(name) ? `${alias}.${name}` : "NULL";
|
|
43278
43951
|
}
|
|
43279
43952
|
var entity = (name) => `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME='${name}')`;
|
|
43280
43953
|
function buildScanSql(available, options) {
|
|
43281
|
-
const missing =
|
|
43954
|
+
const missing = REQUIRED_COLUMNS2.filter((c) => !available.has(c));
|
|
43282
43955
|
if (missing.length) {
|
|
43283
43956
|
throw new NoteQueryStoreError(
|
|
43284
43957
|
`This macOS version's Notes database lacks columns query-notes needs (${missing.join(", ")}).`,
|
|
@@ -43319,8 +43992,8 @@ function buildScanSql(available, options) {
|
|
|
43319
43992
|
"COMMIT;"
|
|
43320
43993
|
].join(" ");
|
|
43321
43994
|
}
|
|
43322
|
-
function
|
|
43323
|
-
return
|
|
43995
|
+
function runSqlite3(dbPath2, query) {
|
|
43996
|
+
return execFileSync8("sqlite3", ["-readonly", dbPath2, query], {
|
|
43324
43997
|
encoding: "utf8",
|
|
43325
43998
|
timeout: 3e4,
|
|
43326
43999
|
maxBuffer: 512 * 1024 * 1024,
|
|
@@ -43329,7 +44002,7 @@ function runSqlite2(dbPath2, query) {
|
|
|
43329
44002
|
}
|
|
43330
44003
|
function presentColumns2(dbPath2) {
|
|
43331
44004
|
const cols = /* @__PURE__ */ new Set();
|
|
43332
|
-
for (const line of
|
|
44005
|
+
for (const line of runSqlite3(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n")) {
|
|
43333
44006
|
const name = line.split("|")[1];
|
|
43334
44007
|
if (name) cols.add(name);
|
|
43335
44008
|
}
|
|
@@ -43347,12 +44020,12 @@ function resolveFolders(rows) {
|
|
|
43347
44020
|
seen.add(pk);
|
|
43348
44021
|
const parent = row.parent !== null ? resolve3(row.parent, seen) : void 0;
|
|
43349
44022
|
const name = row.name ?? "";
|
|
43350
|
-
const
|
|
44023
|
+
const path7 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
|
|
43351
44024
|
const plainPath = parent ? `${parent.plainPath}/${name}` : name;
|
|
43352
44025
|
const info = {
|
|
43353
|
-
path:
|
|
44026
|
+
path: path7,
|
|
43354
44027
|
plainPath,
|
|
43355
|
-
keys: [...new Set([name, plainPath,
|
|
44028
|
+
keys: [...new Set([name, plainPath, path7].map(normalizeForMatch))],
|
|
43356
44029
|
accountPk: row.owner ?? parent?.accountPk ?? null,
|
|
43357
44030
|
shared: Boolean(row.shared) || Boolean(parent?.shared)
|
|
43358
44031
|
};
|
|
@@ -43384,12 +44057,12 @@ function queryNotes(expression, options = {}) {
|
|
|
43384
44057
|
const includeDeleted = options.includeDeleted ?? false;
|
|
43385
44058
|
const withBodies = needsContent(ast);
|
|
43386
44059
|
const withTags = needsTags(ast);
|
|
43387
|
-
const dbPath2 = options.dbPath ??
|
|
43388
|
-
if (!
|
|
44060
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH6;
|
|
44061
|
+
if (!fs6.existsSync(dbPath2)) throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
|
|
43389
44062
|
let output;
|
|
43390
44063
|
try {
|
|
43391
44064
|
const available = presentColumns2(dbPath2);
|
|
43392
|
-
output =
|
|
44065
|
+
output = runSqlite3(
|
|
43393
44066
|
dbPath2,
|
|
43394
44067
|
buildScanSql(available, { scanLimit, includeDeleted, withBodies, withTags })
|
|
43395
44068
|
);
|
|
@@ -43520,10 +44193,10 @@ function queryNotes(expression, options = {}) {
|
|
|
43520
44193
|
import { spawnSync } from "child_process";
|
|
43521
44194
|
|
|
43522
44195
|
// src/services/nativeTags.ts
|
|
43523
|
-
import { execFileSync as
|
|
44196
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
43524
44197
|
import { mkdtempSync as mkdtempSync2, writeFileSync, rmSync as rmSync2 } from "node:fs";
|
|
43525
44198
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
43526
|
-
import { join as
|
|
44199
|
+
import { join as join10 } from "node:path";
|
|
43527
44200
|
|
|
43528
44201
|
// src/services/shortcutConsent.ts
|
|
43529
44202
|
function shortcutConsentHint(shortcut) {
|
|
@@ -43599,7 +44272,7 @@ function addNativeTags(request, deps) {
|
|
|
43599
44272
|
};
|
|
43600
44273
|
}
|
|
43601
44274
|
function listInstalledShortcuts() {
|
|
43602
|
-
return
|
|
44275
|
+
return execFileSync9("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
|
|
43603
44276
|
encoding: "utf8",
|
|
43604
44277
|
timeout: 15e3,
|
|
43605
44278
|
maxBuffer: 1024 * 1024,
|
|
@@ -43625,11 +44298,11 @@ function runNativeTagsShortcut(input) {
|
|
|
43625
44298
|
const status = nativeTagsStatus();
|
|
43626
44299
|
if (!status.installed)
|
|
43627
44300
|
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
43628
|
-
const directory = mkdtempSync2(
|
|
44301
|
+
const directory = mkdtempSync2(join10(tmpdir2(), "apple-notes-native-tags-"));
|
|
43629
44302
|
try {
|
|
43630
|
-
const
|
|
43631
|
-
writeFileSync(
|
|
43632
|
-
|
|
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], {
|
|
43633
44306
|
encoding: "utf8",
|
|
43634
44307
|
timeout: 6e4,
|
|
43635
44308
|
maxBuffer: 1024 * 1024,
|
|
@@ -43648,10 +44321,10 @@ function runNativeTagsShortcut(input) {
|
|
|
43648
44321
|
}
|
|
43649
44322
|
|
|
43650
44323
|
// src/services/backgroundNotes.ts
|
|
43651
|
-
import { execFileSync as
|
|
44324
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
43652
44325
|
import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
|
|
43653
44326
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
43654
|
-
import { join as
|
|
44327
|
+
import { join as join11 } from "node:path";
|
|
43655
44328
|
|
|
43656
44329
|
// src/utils/appendMarkdown.ts
|
|
43657
44330
|
var escape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -43840,9 +44513,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
43840
44513
|
throw new Error(
|
|
43841
44514
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
43842
44515
|
);
|
|
43843
|
-
const directory = mkdtempSync3(
|
|
44516
|
+
const directory = mkdtempSync3(join11(tmpdir3(), "apple-notes-background-"));
|
|
43844
44517
|
try {
|
|
43845
|
-
const file =
|
|
44518
|
+
const file = join11(directory, "request.json");
|
|
43846
44519
|
writeFileSync2(
|
|
43847
44520
|
file,
|
|
43848
44521
|
JSON.stringify({
|
|
@@ -43862,7 +44535,7 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
43862
44535
|
{ mode: 384 }
|
|
43863
44536
|
);
|
|
43864
44537
|
try {
|
|
43865
|
-
|
|
44538
|
+
execFileSync10("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
|
|
43866
44539
|
encoding: "utf8",
|
|
43867
44540
|
timeout: 6e4,
|
|
43868
44541
|
maxBuffer: 1024 * 1024,
|
|
@@ -44065,7 +44738,7 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
|
|
|
44065
44738
|
throw new Error(
|
|
44066
44739
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
44067
44740
|
);
|
|
44068
|
-
const segments = (
|
|
44741
|
+
const segments = (path7) => JSON.stringify(splitFolderPath(path7).map((part) => part.toLocaleLowerCase()));
|
|
44069
44742
|
if (request.folder) {
|
|
44070
44743
|
const wanted = segments(request.folder);
|
|
44071
44744
|
if (!manager.listAccounts().some(
|
|
@@ -44154,7 +44827,7 @@ ${request.content}`
|
|
|
44154
44827
|
}
|
|
44155
44828
|
|
|
44156
44829
|
// src/services/capabilityMatrix.ts
|
|
44157
|
-
import { execFileSync as
|
|
44830
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
44158
44831
|
import { release } from "node:os";
|
|
44159
44832
|
var MACOS_SHORTCUTS_CLI = "12.0";
|
|
44160
44833
|
var MACOS_MARKDOWN_IMPORT = "26.0";
|
|
@@ -44361,7 +45034,7 @@ function evaluateFeatures(env, features = FEATURES) {
|
|
|
44361
45034
|
function readMacOSVersion() {
|
|
44362
45035
|
if (process.platform !== "darwin") return null;
|
|
44363
45036
|
try {
|
|
44364
|
-
const out =
|
|
45037
|
+
const out = execFileSync11("/usr/bin/sw_vers", ["-productVersion"], {
|
|
44365
45038
|
encoding: "utf8",
|
|
44366
45039
|
timeout: 3e3,
|
|
44367
45040
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -44394,9 +45067,9 @@ function getCapabilityMatrix(probe = probeCapabilityEnvironment) {
|
|
|
44394
45067
|
return evaluateFeatures(probe());
|
|
44395
45068
|
}
|
|
44396
45069
|
function formatCapabilityMatrix(matrix) {
|
|
44397
|
-
const
|
|
45070
|
+
const os7 = matrix.runtimeOS;
|
|
44398
45071
|
const lines = [
|
|
44399
|
-
`Feature matrix (macOS ${
|
|
45072
|
+
`Feature matrix (macOS ${os7.macOSVersion ?? "unknown"}, Darwin ${os7.darwinRelease}):`
|
|
44400
45073
|
];
|
|
44401
45074
|
for (const [name, status] of Object.entries(matrix.features)) {
|
|
44402
45075
|
lines.push(
|
|
@@ -44514,19 +45187,19 @@ function formatDoctorReport(r) {
|
|
|
44514
45187
|
}
|
|
44515
45188
|
|
|
44516
45189
|
// src/services/fileConfig.ts
|
|
44517
|
-
import { existsSync as
|
|
44518
|
-
import { join as
|
|
44519
|
-
import { homedir as
|
|
45190
|
+
import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
|
|
45191
|
+
import { join as join12 } from "path";
|
|
45192
|
+
import { homedir as homedir10 } from "os";
|
|
44520
45193
|
function fileConfigPath(env = process.env) {
|
|
44521
45194
|
const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
|
|
44522
45195
|
if (override && override.trim()) return override.trim();
|
|
44523
|
-
return
|
|
45196
|
+
return join12(homedir10(), "Library", "Application Support", "apple-notes-mcp", "config.json");
|
|
44524
45197
|
}
|
|
44525
|
-
function loadFileConfig(env = process.env,
|
|
45198
|
+
function loadFileConfig(env = process.env, path7 = fileConfigPath(env)) {
|
|
44526
45199
|
const applied = [];
|
|
44527
45200
|
try {
|
|
44528
|
-
if (!
|
|
44529
|
-
const parsed = JSON.parse(readFileSync2(
|
|
45201
|
+
if (!existsSync9(path7)) return applied;
|
|
45202
|
+
const parsed = JSON.parse(readFileSync2(path7, "utf8"));
|
|
44530
45203
|
if (!parsed || typeof parsed !== "object") return applied;
|
|
44531
45204
|
for (const [k, v] of Object.entries(parsed)) {
|
|
44532
45205
|
if (typeof v !== "string") continue;
|
|
@@ -44536,7 +45209,7 @@ function loadFileConfig(env = process.env, path5 = fileConfigPath(env)) {
|
|
|
44536
45209
|
}
|
|
44537
45210
|
}
|
|
44538
45211
|
} catch (e) {
|
|
44539
|
-
console.error(`Failed to load apple-notes-mcp config file ${
|
|
45212
|
+
console.error(`Failed to load apple-notes-mcp config file ${path7}: ${String(e)}`);
|
|
44540
45213
|
}
|
|
44541
45214
|
return applied;
|
|
44542
45215
|
}
|
|
@@ -45047,9 +45720,14 @@ import {
|
|
|
45047
45720
|
writeFileSync as writeFileSync3
|
|
45048
45721
|
} from "node:fs";
|
|
45049
45722
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
45050
|
-
import { basename, isAbsolute as isAbsolute2, join as
|
|
45051
|
-
var noteId =
|
|
45723
|
+
import { basename, isAbsolute as isAbsolute2, join as join13 } from "node:path";
|
|
45724
|
+
var noteId = exactIdInput(
|
|
45725
|
+
"ICNote",
|
|
45726
|
+
/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i,
|
|
45727
|
+
NOTE_ID_MESSAGE
|
|
45728
|
+
);
|
|
45052
45729
|
var revision = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
45730
|
+
var folderId = external_exports.string().max(2e3).transform(looseIdTransform("ICFolder"));
|
|
45053
45731
|
function readSnapshot(manager, id2) {
|
|
45054
45732
|
const note = manager.getNoteById(id2);
|
|
45055
45733
|
if (!note) throw new Error("Note not found");
|
|
@@ -45085,9 +45763,9 @@ function assertExistingContentPreserved(before, after) {
|
|
|
45085
45763
|
throw new Error("Existing native object content or presentation changed");
|
|
45086
45764
|
}
|
|
45087
45765
|
}
|
|
45088
|
-
function localAttachment(
|
|
45089
|
-
if (!isAbsolute2(
|
|
45090
|
-
const descriptor = openSync(
|
|
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);
|
|
45091
45769
|
try {
|
|
45092
45770
|
const stat = fstatSync(descriptor);
|
|
45093
45771
|
if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
|
|
@@ -45126,15 +45804,18 @@ function registerDirectOperations(server2, manager) {
|
|
|
45126
45804
|
tool(
|
|
45127
45805
|
"get-folder-by-id",
|
|
45128
45806
|
"Use when: reading the exact folder name and parent before a guarded rename.\nReturns: folder id, current name, and parent id.\nDo not use when: listing folders by path (list-folders).\nSafety: read-only.",
|
|
45129
|
-
{ id:
|
|
45130
|
-
({ id: id2 }) =>
|
|
45807
|
+
{ id: folderId },
|
|
45808
|
+
({ id: id2 }) => {
|
|
45809
|
+
const folder = manager.getFolderById(id2);
|
|
45810
|
+
return { ...folder, ...lookupStableIdentifiers([folder.id], "ICFolder").get(folder.id) };
|
|
45811
|
+
},
|
|
45131
45812
|
true
|
|
45132
45813
|
);
|
|
45133
45814
|
tool(
|
|
45134
45815
|
"rename-folder",
|
|
45135
45816
|
"Use when: renaming one previously read folder in place.\nReturns: the unchanged folder id, new name, and parent id after readback.\nDo not use when: creating, moving, or deleting a folder.\nSafety: requires the expected current name and parent; refuses stale metadata and sibling conflicts.",
|
|
45136
45817
|
{
|
|
45137
|
-
id:
|
|
45818
|
+
id: folderId,
|
|
45138
45819
|
expectedName: external_exports.string().max(1e3),
|
|
45139
45820
|
expectedParentId: external_exports.string().max(2e3),
|
|
45140
45821
|
newName: external_exports.string().min(1).max(1e3)
|
|
@@ -45148,13 +45829,13 @@ function registerDirectOperations(server2, manager) {
|
|
|
45148
45829
|
"add-attachment",
|
|
45149
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.",
|
|
45150
45831
|
{ id: noteId, expectedContentHash: revision, path: external_exports.string().min(1).max(4096) },
|
|
45151
|
-
({ id: id2, expectedContentHash, path:
|
|
45832
|
+
({ id: id2, expectedContentHash, path: path7 }) => {
|
|
45152
45833
|
const before = readSnapshot(manager, id2);
|
|
45153
45834
|
if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
|
|
45154
|
-
const bytes = localAttachment(
|
|
45835
|
+
const bytes = localAttachment(path7);
|
|
45155
45836
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
45156
|
-
const directory = mkdtempSync4(
|
|
45157
|
-
const temporaryFile =
|
|
45837
|
+
const directory = mkdtempSync4(join13(tmpdir4(), "notes-attachment-add-"));
|
|
45838
|
+
const temporaryFile = join13(directory, basename(path7));
|
|
45158
45839
|
try {
|
|
45159
45840
|
writeFileSync3(temporaryFile, bytes, { mode: 384 });
|
|
45160
45841
|
if (readSnapshot(manager, id2).hash !== before.hash)
|
|
@@ -45206,7 +45887,11 @@ function registerDirectOperations(server2, manager) {
|
|
|
45206
45887
|
}
|
|
45207
45888
|
|
|
45208
45889
|
// src/tools/nativeTagsBridge.ts
|
|
45209
|
-
var noteId2 =
|
|
45890
|
+
var noteId2 = exactIdInput(
|
|
45891
|
+
"ICNote",
|
|
45892
|
+
/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i,
|
|
45893
|
+
NOTE_ID_MESSAGE
|
|
45894
|
+
);
|
|
45210
45895
|
var revision2 = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
45211
45896
|
function registerNativeTagsBridge(server2, manager) {
|
|
45212
45897
|
function tool(name, description, inputSchema, handler) {
|
|
@@ -45318,7 +46003,7 @@ function requireValidated(name) {
|
|
|
45318
46003
|
UNAVAILABLE[name] || LIVE_VALIDATION_BLOCKERS[name] || `${name} has not passed live background validation in this build; see get-capabilities`
|
|
45319
46004
|
);
|
|
45320
46005
|
}
|
|
45321
|
-
var id =
|
|
46006
|
+
var id = exactIdInput("ICNote", /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i, NOTE_ID_MESSAGE);
|
|
45322
46007
|
var revision3 = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
45323
46008
|
var common = {
|
|
45324
46009
|
id,
|
|
@@ -45696,7 +46381,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
45696
46381
|
|
|
45697
46382
|
// src/setupShortcuts.ts
|
|
45698
46383
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
45699
|
-
import { existsSync as
|
|
46384
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
45700
46385
|
import { release as release2 } from "node:os";
|
|
45701
46386
|
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
45702
46387
|
import { fileURLToPath } from "node:url";
|
|
@@ -45716,16 +46401,16 @@ var shortcutFiles = [
|
|
|
45716
46401
|
];
|
|
45717
46402
|
function setupShortcuts(checkOnly, dependencies = {}) {
|
|
45718
46403
|
const status = dependencies.status || nativeTagsStatus;
|
|
45719
|
-
const exists = dependencies.exists ||
|
|
45720
|
-
const open = dependencies.open || ((
|
|
45721
|
-
const result = spawnSync2("/usr/bin/open", [
|
|
46404
|
+
const exists = dependencies.exists || existsSync10;
|
|
46405
|
+
const open = dependencies.open || ((path7) => {
|
|
46406
|
+
const result = spawnSync2("/usr/bin/open", [path7], { encoding: "utf8" });
|
|
45722
46407
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
45723
46408
|
});
|
|
45724
46409
|
const baseDirectory = dependencies.baseDirectory || resolve2(dirname2(fileURLToPath(import.meta.url)), "../shortcuts");
|
|
45725
46410
|
const osRelease = (dependencies.osRelease || release2)();
|
|
45726
46411
|
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
45727
46412
|
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
45728
|
-
const
|
|
46413
|
+
const path7 = resolve2(baseDirectory, file);
|
|
45729
46414
|
let installed = false;
|
|
45730
46415
|
let identifier;
|
|
45731
46416
|
let error2;
|
|
@@ -45741,9 +46426,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
45741
46426
|
if (!installed && !checkOnly) {
|
|
45742
46427
|
if (optional2 && !(darwinMajor >= MARKDOWN_MIN_DARWIN_MAJOR))
|
|
45743
46428
|
skipped = `requires macOS 26 or later (this Mac reports Darwin ${osRelease})`;
|
|
45744
|
-
else if (!exists(
|
|
46429
|
+
else if (!exists(path7)) error2 = `Packaged Shortcut is missing: ${path7}`;
|
|
45745
46430
|
else {
|
|
45746
|
-
const result = open(
|
|
46431
|
+
const result = open(path7);
|
|
45747
46432
|
opened = result.ok;
|
|
45748
46433
|
if (!result.ok) error2 = result.error || `Could not open ${file}`;
|
|
45749
46434
|
}
|
|
@@ -45752,7 +46437,7 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
45752
46437
|
name,
|
|
45753
46438
|
installed,
|
|
45754
46439
|
identifier,
|
|
45755
|
-
file:
|
|
46440
|
+
file: path7,
|
|
45756
46441
|
opened,
|
|
45757
46442
|
...optional2 ? { optional: optional2 } : {},
|
|
45758
46443
|
...skipped ? { skipped } : {},
|
|
@@ -45838,10 +46523,25 @@ var noteTitleSchema = {
|
|
|
45838
46523
|
"Account name (defaults to Notes.app's default account; exact or unique-prefix match)"
|
|
45839
46524
|
)
|
|
45840
46525
|
};
|
|
45841
|
-
var
|
|
45842
|
-
|
|
45843
|
-
|
|
45844
|
-
|
|
46526
|
+
var NOTE_COREDATA_ID = /^x-coredata:\/\/[0-9A-Fa-f-]+\/ICNote\/p\d+$/;
|
|
46527
|
+
var NOTE_ID_FORMS = "x-coredata id, Notes UUID, or numeric key";
|
|
46528
|
+
var noteIdInput = exactIdInput("ICNote", NOTE_COREDATA_ID, NOTE_ID_MESSAGE, {
|
|
46529
|
+
maxLength: MAX.ID
|
|
46530
|
+
}).describe(`Exact note ID returned by search-notes, list-notes, or create-note: ${NOTE_ID_FORMS}`);
|
|
46531
|
+
var noteIdArrayInput = exactIdArrayInput("ICNote", NOTE_COREDATA_ID, NOTE_ID_MESSAGE, {
|
|
46532
|
+
maxLength: MAX.ID,
|
|
46533
|
+
maxItems: MAX.BATCH_IDS
|
|
46534
|
+
});
|
|
46535
|
+
var looseNoteId = (base) => base.max(MAX.ID).transform(looseIdTransform("ICNote"));
|
|
46536
|
+
var looseFolderId = (base) => base.max(MAX.ID).transform(looseIdTransform("ICFolder"));
|
|
46537
|
+
var noteIdentifierOutput = {
|
|
46538
|
+
identifier: external_exports.string().optional(),
|
|
46539
|
+
folderIdentifier: external_exports.string().optional(),
|
|
46540
|
+
accountIdentifier: external_exports.string().optional()
|
|
46541
|
+
};
|
|
46542
|
+
function noteIdentifiers(id2) {
|
|
46543
|
+
return lookupStableIdentifiers([id2], "ICNote").get(id2) ?? {};
|
|
46544
|
+
}
|
|
45845
46545
|
var expectedContentHashInput = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/, "expectedContentHash must come from get-note-content").describe(
|
|
45846
46546
|
"Revision token returned by get-note-content for this exact ID. The mutation stops if the note changed since that read."
|
|
45847
46547
|
);
|
|
@@ -46017,7 +46717,7 @@ ${syncWarnings.join(" ")}` : "";
|
|
|
46017
46717
|
return successResponse(
|
|
46018
46718
|
`Found ${notes.length} notes (searched ${searchType}${folderInfo}${dateInfo}${limitInfo}):
|
|
46019
46719
|
${noteList}${truncationNote}${syncNote}`,
|
|
46020
|
-
{ notes, count: notes.length }
|
|
46720
|
+
{ notes: withStableIdentifiers(notes, "ICNote"), count: notes.length }
|
|
46021
46721
|
);
|
|
46022
46722
|
}, "Error searching notes")
|
|
46023
46723
|
);
|
|
@@ -46101,7 +46801,7 @@ registerTool(
|
|
|
46101
46801
|
{
|
|
46102
46802
|
description: "Use when: reading the full body text of one known note, by id (preferred) or title.\nReturns: the exact note id, content, contentHash revision token, parsed hashtags, nativeTags, restored links, richContentComplete/writable, and strippedImages/truncated when the body was capped. Read the warning when writable is false.\nDo not use when: you only need metadata (get-note-details) or Markdown with checklist state (get-note-markdown).\nNote: password-protected notes must be unlocked in Notes.app first.\nSafety: inline images larger than APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES (default 256 KB) are replaced with '[inline image omitted: ...]' text placeholders, so the returned body is lossy whenever truncated is true. Mutations refuse attachment-bearing notes; edit those in Notes.app.",
|
|
46103
46803
|
inputSchema: {
|
|
46104
|
-
id: external_exports.string()
|
|
46804
|
+
id: looseNoteId(external_exports.string()).optional().describe(`Note ID (preferred - more reliable than title): ${NOTE_ID_FORMS}`),
|
|
46105
46805
|
title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
|
|
46106
46806
|
account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
|
|
46107
46807
|
"Account name (defaults to Notes.app's default account; exact or unique-prefix match, ignored if id is provided)"
|
|
@@ -46109,6 +46809,7 @@ registerTool(
|
|
|
46109
46809
|
},
|
|
46110
46810
|
outputSchema: {
|
|
46111
46811
|
id: external_exports.string().optional(),
|
|
46812
|
+
...noteIdentifierOutput,
|
|
46112
46813
|
title: external_exports.string().optional(),
|
|
46113
46814
|
content: external_exports.string().optional(),
|
|
46114
46815
|
contentHash: external_exports.string().optional(),
|
|
@@ -46148,6 +46849,7 @@ registerTool(
|
|
|
46148
46849
|
const warning2 = [strippedImagesWarning(stripped2), rich2.warning].filter(Boolean).join("\n\n");
|
|
46149
46850
|
return successResponse(warning2 ? content2 + warning2 : content2, {
|
|
46150
46851
|
id: id2,
|
|
46852
|
+
...noteIdentifiers(id2),
|
|
46151
46853
|
title: note2.title,
|
|
46152
46854
|
content: content2,
|
|
46153
46855
|
contentHash: richContentHash(rawContent2, rich2),
|
|
@@ -46189,6 +46891,7 @@ registerTool(
|
|
|
46189
46891
|
const warning = [strippedImagesWarning(stripped), rich.warning].filter(Boolean).join("\n\n");
|
|
46190
46892
|
return successResponse(warning ? content + warning : content, {
|
|
46191
46893
|
id: note.id,
|
|
46894
|
+
...noteIdentifiers(note.id),
|
|
46192
46895
|
title,
|
|
46193
46896
|
content,
|
|
46194
46897
|
contentHash: richContentHash(rawContent, rich),
|
|
@@ -46213,7 +46916,7 @@ registerTool(
|
|
|
46213
46916
|
{
|
|
46214
46917
|
description: "Use when: reading one note's body as plain text with no HTML, by id (preferred) or title.\nReturns: the note's plaintext exactly as Notes exposes it.\nDo not use when: you need the HTML body (get-note-content) or Markdown with checklist state (get-note-markdown).\nNote: this reads the note's native plaintext property, so it skips the HTML-to-text conversion; password-protected notes must be unlocked in Notes.app first.",
|
|
46215
46918
|
inputSchema: {
|
|
46216
|
-
id: external_exports.string()
|
|
46919
|
+
id: looseNoteId(external_exports.string()).optional().describe(`Note ID (preferred - more reliable than title): ${NOTE_ID_FORMS}`),
|
|
46217
46920
|
title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
|
|
46218
46921
|
account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
|
|
46219
46922
|
"Account name (defaults to Notes.app's default account; exact or unique-prefix match, ignored if id is provided)"
|
|
@@ -46265,7 +46968,9 @@ registerTool(
|
|
|
46265
46968
|
{
|
|
46266
46969
|
description: "Use when: you have a note id and need its metadata only.\nReturns: id, title, created, modified, shared, passwordProtected.\nDo not use when: you need the body text (get-note-content) or only have a title (get-note-details).",
|
|
46267
46970
|
inputSchema: {
|
|
46268
|
-
id: external_exports.string().min(1, "Note ID is required").
|
|
46971
|
+
id: looseNoteId(external_exports.string().min(1, "Note ID is required")).describe(
|
|
46972
|
+
`Note ID: ${NOTE_ID_FORMS}`
|
|
46973
|
+
)
|
|
46269
46974
|
},
|
|
46270
46975
|
outputSchema: {
|
|
46271
46976
|
id: external_exports.string().optional(),
|
|
@@ -46273,7 +46978,8 @@ registerTool(
|
|
|
46273
46978
|
created: external_exports.string().optional(),
|
|
46274
46979
|
modified: external_exports.string().optional(),
|
|
46275
46980
|
shared: external_exports.boolean().optional(),
|
|
46276
|
-
passwordProtected: external_exports.boolean().optional()
|
|
46981
|
+
passwordProtected: external_exports.boolean().optional(),
|
|
46982
|
+
...noteIdentifierOutput
|
|
46277
46983
|
}
|
|
46278
46984
|
},
|
|
46279
46985
|
withErrorHandling(({ id: id2 }) => {
|
|
@@ -46287,7 +46993,8 @@ registerTool(
|
|
|
46287
46993
|
created: note.created.toISOString(),
|
|
46288
46994
|
modified: note.modified.toISOString(),
|
|
46289
46995
|
shared: note.shared,
|
|
46290
|
-
passwordProtected: note.passwordProtected
|
|
46996
|
+
passwordProtected: note.passwordProtected,
|
|
46997
|
+
...noteIdentifiers(note.id)
|
|
46291
46998
|
};
|
|
46292
46999
|
return successResponse(JSON.stringify(metadata, null, 2), metadata);
|
|
46293
47000
|
}, "Error retrieving note")
|
|
@@ -46304,7 +47011,8 @@ registerTool(
|
|
|
46304
47011
|
modified: external_exports.string().optional(),
|
|
46305
47012
|
shared: external_exports.boolean().optional(),
|
|
46306
47013
|
passwordProtected: external_exports.boolean().optional(),
|
|
46307
|
-
account: external_exports.string().optional()
|
|
47014
|
+
account: external_exports.string().optional(),
|
|
47015
|
+
...noteIdentifierOutput
|
|
46308
47016
|
}
|
|
46309
47017
|
},
|
|
46310
47018
|
withErrorHandling(({ title, account }) => {
|
|
@@ -46319,7 +47027,8 @@ registerTool(
|
|
|
46319
47027
|
modified: note.modified.toISOString(),
|
|
46320
47028
|
shared: note.shared,
|
|
46321
47029
|
passwordProtected: note.passwordProtected,
|
|
46322
|
-
account: note.account
|
|
47030
|
+
account: note.account,
|
|
47031
|
+
...noteIdentifiers(note.id)
|
|
46323
47032
|
};
|
|
46324
47033
|
return successResponse(JSON.stringify(metadata, null, 2), metadata);
|
|
46325
47034
|
}, "Error retrieving note details")
|
|
@@ -46329,7 +47038,9 @@ registerTool(
|
|
|
46329
47038
|
{
|
|
46330
47039
|
description: "Use when: the user wants to reveal a known note in Notes.app by id.\nReturns: confirmation that Notes.app accepted the show command.\nDo not use when: you only need note content (get-note-content) or metadata (get-note-by-id).\nNote: this opens or focuses the Notes UI.",
|
|
46331
47040
|
inputSchema: {
|
|
46332
|
-
id: external_exports.string().min(1, "Note ID is required").
|
|
47041
|
+
id: looseNoteId(external_exports.string().min(1, "Note ID is required")).describe(
|
|
47042
|
+
`Note ID: ${NOTE_ID_FORMS}`
|
|
47043
|
+
),
|
|
46333
47044
|
separately: external_exports.boolean().optional().describe("Open in a separate note window when supported by Notes.app")
|
|
46334
47045
|
},
|
|
46335
47046
|
outputSchema: {
|
|
@@ -46350,7 +47061,7 @@ registerTool(
|
|
|
46350
47061
|
{
|
|
46351
47062
|
description: "Use when: you need the notes:// deep-link URL for a note so it can be stored in a Reminders task, shared, or opened directly.\nReturns: a notes://showNote?identifier=<uuid> URL that opens the note in Notes.app on iOS and macOS.\nDo not use when: you only need the note's CoreData id (get-note-by-id) or want to reveal the note on screen (show-note).\nNote: the primary path reads the note's identifier from the Notes database, so it needs Full Disk Access for the app that launches this server; macOS 12-15 can fall back to the AppleScript 'note link' property, which macOS 26+ no longer exposes. Password-protected notes cannot be linked.",
|
|
46352
47063
|
inputSchema: {
|
|
46353
|
-
id: external_exports.string()
|
|
47064
|
+
id: looseNoteId(external_exports.string()).optional().describe(`Note ID (preferred - more reliable than title): ${NOTE_ID_FORMS}`),
|
|
46354
47065
|
title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
|
|
46355
47066
|
account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account containing the note (ignored if id is provided)")
|
|
46356
47067
|
},
|
|
@@ -46405,7 +47116,9 @@ registerTool(
|
|
|
46405
47116
|
{
|
|
46406
47117
|
description: "Use when: the user wants to reveal a known folder in Notes.app by id.\nReturns: confirmation that Notes.app accepted the show command.\nDo not use when: you only need the folder list (list-folders).\nNote: this opens or focuses the Notes UI. Get the id from list-folders.",
|
|
46407
47118
|
inputSchema: {
|
|
46408
|
-
id: external_exports.string().min(1, "Folder ID is required").
|
|
47119
|
+
id: looseFolderId(external_exports.string().min(1, "Folder ID is required")).describe(
|
|
47120
|
+
"Folder ID from list-folders: x-coredata id, Notes UUID, or numeric key"
|
|
47121
|
+
),
|
|
46409
47122
|
separately: external_exports.boolean().optional().describe("Open in a separate window when supported by Notes.app")
|
|
46410
47123
|
},
|
|
46411
47124
|
outputSchema: {
|
|
@@ -46949,7 +47662,15 @@ registerTool(
|
|
|
46949
47662
|
limit: external_exports.number().int().positive().optional().describe("Maximum number of notes to return")
|
|
46950
47663
|
},
|
|
46951
47664
|
outputSchema: {
|
|
46952
|
-
notes: external_exports.array(
|
|
47665
|
+
notes: external_exports.array(
|
|
47666
|
+
external_exports.object({
|
|
47667
|
+
title: external_exports.string(),
|
|
47668
|
+
id: external_exports.string(),
|
|
47669
|
+
identifier: external_exports.string().optional(),
|
|
47670
|
+
folderIdentifier: external_exports.string().optional(),
|
|
47671
|
+
accountIdentifier: external_exports.string().optional()
|
|
47672
|
+
})
|
|
47673
|
+
).optional(),
|
|
46953
47674
|
count: external_exports.number().optional()
|
|
46954
47675
|
}
|
|
46955
47676
|
},
|
|
@@ -46986,7 +47707,7 @@ ${syncWarnings.join(" ")}` : "";
|
|
|
46986
47707
|
return successResponse(
|
|
46987
47708
|
`Found ${notes.length} notes${location}${acct}${dateInfo}${limitInfo}:
|
|
46988
47709
|
${noteList}${syncNote}`,
|
|
46989
|
-
{ notes, count: notes.length }
|
|
47710
|
+
{ notes: withStableIdentifiers(notes, "ICNote"), count: notes.length }
|
|
46990
47711
|
);
|
|
46991
47712
|
}, "Error listing notes")
|
|
46992
47713
|
);
|
|
@@ -47010,7 +47731,10 @@ registerTool(
|
|
|
47010
47731
|
}
|
|
47011
47732
|
const noteList = notes.map((n) => ` - ${n.title} [id: ${n.id}]`).join("\n");
|
|
47012
47733
|
return successResponse(`Selected note(s):
|
|
47013
|
-
${noteList}`, {
|
|
47734
|
+
${noteList}`, {
|
|
47735
|
+
notes: withStableIdentifiers(notes, "ICNote"),
|
|
47736
|
+
count: notes.length
|
|
47737
|
+
});
|
|
47014
47738
|
}, "Error getting selected notes")
|
|
47015
47739
|
);
|
|
47016
47740
|
registerTool(
|
|
@@ -47051,12 +47775,61 @@ ${syncWarnings.join(" ")}` : "";
|
|
|
47051
47775
|
`Found ${folders.length} folders${resolvedAcct}:
|
|
47052
47776
|
${folderList}${syncNote}`,
|
|
47053
47777
|
{
|
|
47054
|
-
folders,
|
|
47778
|
+
folders: withStableIdentifiers(folders, "ICFolder"),
|
|
47055
47779
|
count: folders.length
|
|
47056
47780
|
}
|
|
47057
47781
|
);
|
|
47058
47782
|
}, "Error listing folders")
|
|
47059
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
|
+
);
|
|
47060
47833
|
registerTool(
|
|
47061
47834
|
"create-folder",
|
|
47062
47835
|
{
|
|
@@ -47127,7 +47900,7 @@ registerTool(
|
|
|
47127
47900
|
}).join("\n");
|
|
47128
47901
|
return successResponse(`Found ${accounts.length} accounts:
|
|
47129
47902
|
${accountList}`, {
|
|
47130
|
-
accounts,
|
|
47903
|
+
accounts: withStableIdentifiers(accounts, "ICAccount"),
|
|
47131
47904
|
count: accounts.length
|
|
47132
47905
|
});
|
|
47133
47906
|
}, "Error listing accounts")
|
|
@@ -47146,7 +47919,9 @@ registerTool(
|
|
|
47146
47919
|
const location = notesManager.getDefaultLocation();
|
|
47147
47920
|
const message = `Default account: ${location.account.name} [id: ${location.account.id}]
|
|
47148
47921
|
Default folder: ${location.folder.name} [id: ${location.folder.id}]`;
|
|
47149
|
-
|
|
47922
|
+
const [account] = withStableIdentifiers([location.account], "ICAccount");
|
|
47923
|
+
const [folder] = withStableIdentifiers([location.folder], "ICFolder");
|
|
47924
|
+
return successResponse(message, { account, folder });
|
|
47150
47925
|
}, "Error getting default Notes location")
|
|
47151
47926
|
);
|
|
47152
47927
|
registerTool(
|
|
@@ -47176,7 +47951,7 @@ registerTool(
|
|
|
47176
47951
|
${noteList}
|
|
47177
47952
|
|
|
47178
47953
|
\u26A0\uFE0F Changes to shared notes are visible to all collaborators.`,
|
|
47179
|
-
{ notes: sharedNotes, count: sharedNotes.length }
|
|
47954
|
+
{ notes: withStableIdentifiers(sharedNotes, "ICNote"), count: sharedNotes.length }
|
|
47180
47955
|
);
|
|
47181
47956
|
}, "Error listing shared notes")
|
|
47182
47957
|
);
|
|
@@ -47317,7 +48092,7 @@ registerTool(
|
|
|
47317
48092
|
{
|
|
47318
48093
|
description: "Use when: listing the attachments of one note, by id (preferred) or title.\nReturns: each attachment's name, content type, and id (use with save-attachment/fetch-attachment).\nDo not use when: you want the attachment bytes (fetch-attachment) or a file on disk (save-attachment).",
|
|
47319
48094
|
inputSchema: {
|
|
47320
|
-
id: external_exports.string()
|
|
48095
|
+
id: looseNoteId(external_exports.string()).optional().describe(`Note ID (preferred - more reliable than title): ${NOTE_ID_FORMS}`),
|
|
47321
48096
|
title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
|
|
47322
48097
|
account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account containing the note (ignored if id is provided)")
|
|
47323
48098
|
},
|
|
@@ -47425,7 +48200,9 @@ registerTool(
|
|
|
47425
48200
|
{
|
|
47426
48201
|
description: "Use when: moving multiple notes by id into one destination folder.\nReturns: per-id success/failure counts after destination-folder verification.\nDo not use when: moving a single note (move-note).\nSafety: each moved note's actual container ID is compared with the destination folder ID before success is reported. The destination folder must already exist (create-folder).",
|
|
47427
48202
|
inputSchema: {
|
|
47428
|
-
ids:
|
|
48203
|
+
ids: noteIdArrayInput.describe(
|
|
48204
|
+
`Array of note IDs to move (max ${MAX.BATCH_IDS} per request)`
|
|
48205
|
+
),
|
|
47429
48206
|
folder: external_exports.string().max(MAX.FOLDER).describe(
|
|
47430
48207
|
'Destination folder name or nested path (e.g. "Work/Clients"). Must already exist \u2014 create-folder first.'
|
|
47431
48208
|
),
|
|
@@ -47469,7 +48246,9 @@ registerTool(
|
|
|
47469
48246
|
{
|
|
47470
48247
|
description: "Use when: writing one note attachment to a file on disk.\nReturns: the saved path.\nDo not use when: you want the bytes in-memory as base64 (fetch-attachment).\nSafety: writes a file; savePath must be absolute and under the home directory, a temp dir, or /Volumes. Get the ids from list-attachments first.",
|
|
47471
48248
|
inputSchema: {
|
|
47472
|
-
noteId: external_exports.string().min(1, "noteId is required")
|
|
48249
|
+
noteId: looseNoteId(external_exports.string().min(1, "noteId is required")).describe(
|
|
48250
|
+
`Note id (from search/list): ${NOTE_ID_FORMS}`
|
|
48251
|
+
),
|
|
47473
48252
|
attachmentId: external_exports.string().min(1, "attachmentId is required").max(MAX.ATTACHMENT_ID).describe("Attachment id (from list-attachments)"),
|
|
47474
48253
|
savePath: external_exports.string().min(1, "savePath is required").max(MAX.SAVE_PATH).describe("Absolute destination file path (must be under home, temp, or /Volumes)")
|
|
47475
48254
|
},
|
|
@@ -47496,7 +48275,9 @@ registerTool(
|
|
|
47496
48275
|
{
|
|
47497
48276
|
description: "Use when: retrieving one note attachment's bytes inline as base64 (no file written).\nReturns: name, content type, byte count, and base64 data.\nDo not use when: you want it saved to disk (save-attachment).\nNote: get the ids from list-attachments first.",
|
|
47498
48277
|
inputSchema: {
|
|
47499
|
-
noteId: external_exports.string().min(1, "noteId is required")
|
|
48278
|
+
noteId: looseNoteId(external_exports.string().min(1, "noteId is required")).describe(
|
|
48279
|
+
`Note id (from search/list): ${NOTE_ID_FORMS}`
|
|
48280
|
+
),
|
|
47500
48281
|
attachmentId: external_exports.string().min(1, "attachmentId is required").max(MAX.ATTACHMENT_ID).describe("Attachment id (from list-attachments)")
|
|
47501
48282
|
},
|
|
47502
48283
|
outputSchema: {
|
|
@@ -47522,7 +48303,9 @@ registerTool(
|
|
|
47522
48303
|
{
|
|
47523
48304
|
description: "Use when: the user wants to reveal one note attachment in Notes.app.\nReturns: confirmation that Notes.app revealed the attachment.\nDo not use when: you want the bytes (fetch-attachment) or a file on disk (save-attachment).\nNote: this opens or focuses the Notes UI. Get the ids from list-attachments first.",
|
|
47524
48305
|
inputSchema: {
|
|
47525
|
-
noteId: external_exports.string().min(1, "noteId is required")
|
|
48306
|
+
noteId: looseNoteId(external_exports.string().min(1, "noteId is required")).describe(
|
|
48307
|
+
`Note id (from search/list): ${NOTE_ID_FORMS}`
|
|
48308
|
+
),
|
|
47526
48309
|
attachmentId: external_exports.string().min(1, "attachmentId is required").max(MAX.ATTACHMENT_ID).describe("Attachment id (from list-attachments)"),
|
|
47527
48310
|
separately: external_exports.boolean().optional().describe("Open in a separate window when supported by Notes.app")
|
|
47528
48311
|
},
|
|
@@ -47622,7 +48405,7 @@ registerTool(
|
|
|
47622
48405
|
{
|
|
47623
48406
|
description: "Use when: reading a note as Markdown, with checklist items annotated [x]/[ ] when Full Disk Access is granted.\nReturns: the note's Markdown.\nDo not use when: you need the raw HTML/plaintext body (get-note-content) or only metadata (get-note-details).\nNote: falls back to plain lists (no checkmarks) without Full Disk Access.",
|
|
47624
48407
|
inputSchema: {
|
|
47625
|
-
id: external_exports.string()
|
|
48408
|
+
id: looseNoteId(external_exports.string()).optional().describe(`Note ID (preferred - more reliable than title): ${NOTE_ID_FORMS}`),
|
|
47626
48409
|
title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
|
|
47627
48410
|
account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account containing the note (ignored if id is provided)")
|
|
47628
48411
|
},
|
|
@@ -47655,7 +48438,9 @@ registerTool(
|
|
|
47655
48438
|
{
|
|
47656
48439
|
description: "Use when: reading the checked/unchecked state of a note's checklist items, by id.\nReturns: each item's text and done state plus checked/total counts.\nDo not use when: you only have a title (get the id via search-notes first) or want the full body text (get-note-content).\nNote: requires Full Disk Access; reads the NoteStore database directly.",
|
|
47657
48440
|
inputSchema: {
|
|
47658
|
-
id:
|
|
48441
|
+
id: looseNoteId(
|
|
48442
|
+
external_exports.string().min(1, "Note ID is required. Use search-notes to find the note ID first.")
|
|
48443
|
+
).describe(`Note ID: ${NOTE_ID_FORMS}`)
|
|
47659
48444
|
},
|
|
47660
48445
|
outputSchema: {
|
|
47661
48446
|
items: external_exports.array(external_exports.object({}).passthrough()).optional(),
|
|
@@ -47691,7 +48476,9 @@ registerTool(
|
|
|
47691
48476
|
{
|
|
47692
48477
|
description: "[BETA] Use when: reading note metadata AppleScript cannot expose \u2014 pinned state, checklist flags, trash/recovery state, preview snippet, password hint \u2014 by id.\nReturns: a metadata object; fields vary by macOS version and are omitted when unavailable.\nDo not use when: you need the body (get-note-content) or per-item checklist state (get-checklist-state).\nNote: reads the NoteStore SQLite database read-only and requires Full Disk Access. BETA \u2014 the database schema changes between macOS releases, so some fields may be absent. Works on trashed notes that AppleScript can no longer resolve.",
|
|
47693
48478
|
inputSchema: {
|
|
47694
|
-
id:
|
|
48479
|
+
id: looseNoteId(
|
|
48480
|
+
external_exports.string().min(1, "Note ID is required. Use search-notes to find the note ID first.")
|
|
48481
|
+
).describe(`Note ID: ${NOTE_ID_FORMS}`)
|
|
47695
48482
|
},
|
|
47696
48483
|
outputSchema: {
|
|
47697
48484
|
pinned: external_exports.boolean().optional(),
|