apple-notes-mcp 2.8.43 → 2.8.45
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 +87 -5
- package/build/index.js +754 -140
- 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(path10) {
|
|
3272
|
+
let input = path10;
|
|
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 path10 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
|
|
3678
|
+
wsComponent.path = path10 && path10 !== "/" ? path10 : void 0;
|
|
3679
3679
|
wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
|
|
3680
3680
|
wsComponent.resourceName = void 0;
|
|
3681
3681
|
}
|
|
@@ -5049,8 +5049,8 @@ var require_multipleOf = __commonJS({
|
|
|
5049
5049
|
const { gen, data, schemaCode, it } = cxt;
|
|
5050
5050
|
const prec = it.opts.multipleOfPrecision;
|
|
5051
5051
|
const res = gen.let("res");
|
|
5052
|
-
const
|
|
5053
|
-
cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${
|
|
5052
|
+
const invalid2 = prec ? (0, codegen_1._)`Math.abs(Math.round(${res}) - ${res}) > 1e-${prec}` : (0, codegen_1._)`${res} !== parseInt(${res})`;
|
|
5053
|
+
cxt.fail$data((0, codegen_1._)`(${schemaCode} === 0 || (${res} = ${data}/${schemaCode}, ${invalid2}))`);
|
|
5054
5054
|
}
|
|
5055
5055
|
};
|
|
5056
5056
|
exports.default = def;
|
|
@@ -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(path10) {
|
|
12242
|
+
if (!path10) return path10;
|
|
12243
12243
|
var output = "";
|
|
12244
|
-
while (
|
|
12245
|
-
if (
|
|
12246
|
-
|
|
12244
|
+
while (path10.length > 0) {
|
|
12245
|
+
if (path10 === "." || path10 === "..") {
|
|
12246
|
+
path10 = "";
|
|
12247
12247
|
break;
|
|
12248
12248
|
}
|
|
12249
|
-
var twochars =
|
|
12250
|
-
var threechars =
|
|
12251
|
-
var fourchars =
|
|
12249
|
+
var twochars = path10.substring(0, 2);
|
|
12250
|
+
var threechars = path10.substring(0, 3);
|
|
12251
|
+
var fourchars = path10.substring(0, 4);
|
|
12252
12252
|
if (threechars === "../") {
|
|
12253
|
-
|
|
12253
|
+
path10 = path10.substring(3);
|
|
12254
12254
|
} else if (twochars === "./") {
|
|
12255
|
-
|
|
12255
|
+
path10 = path10.substring(2);
|
|
12256
12256
|
} else if (threechars === "/./") {
|
|
12257
|
-
|
|
12258
|
-
} else if (twochars === "/." &&
|
|
12259
|
-
|
|
12260
|
-
} else if (fourchars === "/../" || threechars === "/.." &&
|
|
12261
|
-
|
|
12257
|
+
path10 = "/" + path10.substring(3);
|
|
12258
|
+
} else if (twochars === "/." && path10.length === 2) {
|
|
12259
|
+
path10 = "/";
|
|
12260
|
+
} else if (fourchars === "/../" || threechars === "/.." && path10.length === 3) {
|
|
12261
|
+
path10 = "/" + path10.substring(4);
|
|
12262
12262
|
output = output.replace(/\/?[^\/]*$/, "");
|
|
12263
12263
|
} else {
|
|
12264
|
-
var segment =
|
|
12264
|
+
var segment = path10.match(/(\/?([^\/]*))/)[0];
|
|
12265
12265
|
output += segment;
|
|
12266
|
-
|
|
12266
|
+
path10 = path10.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 join24(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 = join24(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 join24(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: path10, errorMaps, issueData } = params;
|
|
24983
|
+
const fullPath = [...path10, ...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, path10, key) {
|
|
25100
25100
|
this._cachedPath = [];
|
|
25101
25101
|
this.parent = parent;
|
|
25102
25102
|
this.data = value;
|
|
25103
|
-
this._path =
|
|
25103
|
+
this._path = path10;
|
|
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, path10) {
|
|
28741
|
+
if (!path10)
|
|
28742
28742
|
return obj;
|
|
28743
|
-
return
|
|
28743
|
+
return path10.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(path10, 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(path10);
|
|
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(path10) {
|
|
32479
|
+
if (path10.length === 0) {
|
|
32480
32480
|
return "object root";
|
|
32481
32481
|
}
|
|
32482
|
-
return
|
|
32482
|
+
return path10.reduce((acc, seg, index) => {
|
|
32483
32483
|
if (index === 0) {
|
|
32484
32484
|
return String(seg);
|
|
32485
32485
|
}
|
|
@@ -40964,8 +40964,8 @@ function allowedSaveRoots() {
|
|
|
40964
40964
|
function deniedSaveRoots() {
|
|
40965
40965
|
return [join6(homedir6(), "Library/Group Containers/group.com.apple.notes")];
|
|
40966
40966
|
}
|
|
40967
|
-
function canonicalize(
|
|
40968
|
-
return realpathSync.native(
|
|
40967
|
+
function canonicalize(path10) {
|
|
40968
|
+
return realpathSync.native(path10);
|
|
40969
40969
|
}
|
|
40970
40970
|
function isWithinRoots(candidate, roots) {
|
|
40971
40971
|
return roots.some((root) => {
|
|
@@ -40987,9 +40987,9 @@ function canonicalRoots(roots) {
|
|
|
40987
40987
|
}
|
|
40988
40988
|
return canonical;
|
|
40989
40989
|
}
|
|
40990
|
-
function entryExists(
|
|
40990
|
+
function entryExists(path10) {
|
|
40991
40991
|
try {
|
|
40992
|
-
lstatSync(
|
|
40992
|
+
lstatSync(path10);
|
|
40993
40993
|
return true;
|
|
40994
40994
|
} catch (e) {
|
|
40995
40995
|
const code = e.code;
|
|
@@ -41363,24 +41363,24 @@ function safeComponent(value) {
|
|
|
41363
41363
|
if (value === "." || value === ".." || value.includes("/") || value.includes("\0")) return null;
|
|
41364
41364
|
return value;
|
|
41365
41365
|
}
|
|
41366
|
-
function realInside(
|
|
41366
|
+
function realInside(path10, rootReal) {
|
|
41367
41367
|
try {
|
|
41368
|
-
const real = realpathSync2.native(
|
|
41368
|
+
const real = realpathSync2.native(path10);
|
|
41369
41369
|
return real === rootReal || real.startsWith(rootReal + sep2) ? real : null;
|
|
41370
41370
|
} catch {
|
|
41371
41371
|
return null;
|
|
41372
41372
|
}
|
|
41373
41373
|
}
|
|
41374
|
-
function isRegularFile(
|
|
41374
|
+
function isRegularFile(path10) {
|
|
41375
41375
|
try {
|
|
41376
|
-
return lstatSync2(
|
|
41376
|
+
return lstatSync2(path10).isFile();
|
|
41377
41377
|
} catch {
|
|
41378
41378
|
return false;
|
|
41379
41379
|
}
|
|
41380
41380
|
}
|
|
41381
|
-
function isDirectory(
|
|
41381
|
+
function isDirectory(path10) {
|
|
41382
41382
|
try {
|
|
41383
|
-
return lstatSync2(
|
|
41383
|
+
return lstatSync2(path10).isDirectory();
|
|
41384
41384
|
} catch {
|
|
41385
41385
|
return false;
|
|
41386
41386
|
}
|
|
@@ -41760,10 +41760,10 @@ function readImageInfoFd(fd) {
|
|
|
41760
41760
|
const read = readSync2(fd, buf, 0, buf.length, 0);
|
|
41761
41761
|
return parseImageHeader(buf.subarray(0, read));
|
|
41762
41762
|
}
|
|
41763
|
-
function readImageInfo(
|
|
41763
|
+
function readImageInfo(path10) {
|
|
41764
41764
|
let fd;
|
|
41765
41765
|
try {
|
|
41766
|
-
fd = openSync3(
|
|
41766
|
+
fd = openSync3(path10, constants3.O_RDONLY | constants3.O_NOFOLLOW);
|
|
41767
41767
|
} catch {
|
|
41768
41768
|
return null;
|
|
41769
41769
|
}
|
|
@@ -41863,9 +41863,9 @@ function readDrawingRows(noteId3, dbPath2 = join8(NOTES_CONTAINER_DIR, "NoteStor
|
|
|
41863
41863
|
throw new AttachmentStoreError("Drawing rows could not be parsed", "query_error");
|
|
41864
41864
|
}
|
|
41865
41865
|
}
|
|
41866
|
-
function kindOf(
|
|
41866
|
+
function kindOf(path10) {
|
|
41867
41867
|
try {
|
|
41868
|
-
const st = lstatSync3(
|
|
41868
|
+
const st = lstatSync3(path10);
|
|
41869
41869
|
return st.isFile() ? "file" : st.isDirectory() ? "dir" : null;
|
|
41870
41870
|
} catch {
|
|
41871
41871
|
return null;
|
|
@@ -41931,13 +41931,13 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
|
|
|
41931
41931
|
const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
|
|
41932
41932
|
const bundle = accountDir && id2 && kind === "paper" ? realInside(join8(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
|
|
41933
41933
|
let raster = null;
|
|
41934
|
-
for (const [
|
|
41934
|
+
for (const [path10, source] of [
|
|
41935
41935
|
[fallbackImagePath, "fallback"],
|
|
41936
41936
|
[previewPath, "preview"]
|
|
41937
41937
|
]) {
|
|
41938
|
-
const info =
|
|
41939
|
-
if (
|
|
41940
|
-
raster = { path:
|
|
41938
|
+
const info = path10 ? readImageInfo(path10) : null;
|
|
41939
|
+
if (path10 && info) {
|
|
41940
|
+
raster = { path: path10, source, ...info };
|
|
41941
41941
|
break;
|
|
41942
41942
|
}
|
|
41943
41943
|
}
|
|
@@ -41954,10 +41954,10 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
|
|
|
41954
41954
|
};
|
|
41955
41955
|
});
|
|
41956
41956
|
}
|
|
41957
|
-
function verifyWrittenImage(fd,
|
|
41957
|
+
function verifyWrittenImage(fd, path10, expected) {
|
|
41958
41958
|
const check2 = readImageInfoFd(fd);
|
|
41959
41959
|
if (!check2 || check2.format !== expected.format || check2.width !== expected.width || check2.height !== expected.height) {
|
|
41960
|
-
unlinkSync2(
|
|
41960
|
+
unlinkSync2(path10);
|
|
41961
41961
|
throw new Error("The exported image failed validation and was removed.");
|
|
41962
41962
|
}
|
|
41963
41963
|
return check2;
|
|
@@ -42053,6 +42053,77 @@ function exportDrawingRaster(drawing, savePath, containerDir = NOTES_CONTAINER_D
|
|
|
42053
42053
|
}
|
|
42054
42054
|
}
|
|
42055
42055
|
|
|
42056
|
+
// src/utils/scopeGuard.ts
|
|
42057
|
+
var SCOPE_FOLDER_ID = /^x-coredata:\/\/[0-9a-f-]+\/ICFolder\/p\d+$/i;
|
|
42058
|
+
var MAX_FORBIDDEN_FOLDERS = 50;
|
|
42059
|
+
var SCOPE_MARKER = "SAFETY_SCOPE";
|
|
42060
|
+
function hasScopeGuard(guard) {
|
|
42061
|
+
return Boolean(
|
|
42062
|
+
guard && (guard.ifFolderId || guard.ifAncestorFolderId || guard.forbiddenAncestorFolderIds && guard.forbiddenAncestorFolderIds.length > 0)
|
|
42063
|
+
);
|
|
42064
|
+
}
|
|
42065
|
+
function validateScopeGuard(guard) {
|
|
42066
|
+
const ids = [
|
|
42067
|
+
guard.ifFolderId,
|
|
42068
|
+
guard.ifAncestorFolderId,
|
|
42069
|
+
...guard.forbiddenAncestorFolderIds ?? []
|
|
42070
|
+
].filter((value) => value !== void 0);
|
|
42071
|
+
for (const id2 of ids) {
|
|
42072
|
+
if (!SCOPE_FOLDER_ID.test(id2)) throw new Error(`Scope guard needs exact folder ids: ${id2}`);
|
|
42073
|
+
}
|
|
42074
|
+
if ((guard.forbiddenAncestorFolderIds?.length ?? 0) > MAX_FORBIDDEN_FOLDERS)
|
|
42075
|
+
throw new Error(`At most ${MAX_FORBIDDEN_FOLDERS} forbidden folder ids are allowed`);
|
|
42076
|
+
}
|
|
42077
|
+
function chainScript(chainVar, startExpr) {
|
|
42078
|
+
return `
|
|
42079
|
+
set ${chainVar} to {}
|
|
42080
|
+
set ${chainVar}Cursor to ${startExpr}
|
|
42081
|
+
repeat while class of ${chainVar}Cursor is folder
|
|
42082
|
+
set end of ${chainVar} to (id of ${chainVar}Cursor)
|
|
42083
|
+
set ${chainVar}Cursor to container of ${chainVar}Cursor
|
|
42084
|
+
end repeat`;
|
|
42085
|
+
}
|
|
42086
|
+
function idList(ids) {
|
|
42087
|
+
return `{${ids.map((id2) => `"${id2}"`).join(", ")}}`;
|
|
42088
|
+
}
|
|
42089
|
+
function buildScopeGuardScript(noteVar, guard, destinationVar) {
|
|
42090
|
+
if (!hasScopeGuard(guard)) return "";
|
|
42091
|
+
validateScopeGuard(guard);
|
|
42092
|
+
const forbidden = guard.forbiddenAncestorFolderIds ?? [];
|
|
42093
|
+
let script = `
|
|
42094
|
+
set scopeFolder to container of ${noteVar}
|
|
42095
|
+
if class of scopeFolder is not folder then return "${SCOPE_MARKER}:the note is not in a folder"`;
|
|
42096
|
+
if (guard.ifFolderId)
|
|
42097
|
+
script += `
|
|
42098
|
+
if (id of scopeFolder) is not "${guard.ifFolderId}" then return "${SCOPE_MARKER}:the note is not in the expected folder"`;
|
|
42099
|
+
if (guard.ifAncestorFolderId || forbidden.length > 0)
|
|
42100
|
+
script += chainScript("scopeChain", "scopeFolder");
|
|
42101
|
+
if (guard.ifAncestorFolderId)
|
|
42102
|
+
script += `
|
|
42103
|
+
if scopeChain does not contain "${guard.ifAncestorFolderId}" then return "${SCOPE_MARKER}:the note is not inside the expected ancestor folder"`;
|
|
42104
|
+
if (forbidden.length > 0) {
|
|
42105
|
+
script += `
|
|
42106
|
+
repeat with forbiddenId in ${idList(forbidden)}
|
|
42107
|
+
if scopeChain contains (contents of forbiddenId) then return "${SCOPE_MARKER}:the note is inside a forbidden folder"
|
|
42108
|
+
end repeat`;
|
|
42109
|
+
if (destinationVar) {
|
|
42110
|
+
script += chainScript("scopeDestChain", destinationVar);
|
|
42111
|
+
script += `
|
|
42112
|
+
repeat with forbiddenId in ${idList(forbidden)}
|
|
42113
|
+
if scopeDestChain contains (contents of forbiddenId) then return "${SCOPE_MARKER}:the destination is inside a forbidden folder"
|
|
42114
|
+
end repeat`;
|
|
42115
|
+
}
|
|
42116
|
+
}
|
|
42117
|
+
return script;
|
|
42118
|
+
}
|
|
42119
|
+
function parseScopeFailure(output) {
|
|
42120
|
+
const trimmed = output.trim();
|
|
42121
|
+
return trimmed.startsWith(`${SCOPE_MARKER}:`) ? trimmed.slice(SCOPE_MARKER.length + 1) : null;
|
|
42122
|
+
}
|
|
42123
|
+
function scopeConflictMessage(reason) {
|
|
42124
|
+
return `Scope guard failed: ${reason}. Nothing was changed; read the note's current folder and review before retrying.`;
|
|
42125
|
+
}
|
|
42126
|
+
|
|
42056
42127
|
// src/services/appleNotesManager.ts
|
|
42057
42128
|
var import_turndown = __toESM(require_turndown_cjs(), 1);
|
|
42058
42129
|
import { existsSync as existsSync5 } from "fs";
|
|
@@ -42302,6 +42373,34 @@ function buildAppLevelScript(command) {
|
|
|
42302
42373
|
end tell
|
|
42303
42374
|
`;
|
|
42304
42375
|
}
|
|
42376
|
+
function folderDeleteFactsScript(id2) {
|
|
42377
|
+
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
42378
|
+
return `
|
|
42379
|
+
set f to folder id "${id2}"
|
|
42380
|
+
set folderName to name of f
|
|
42381
|
+
set parentRef to container of f
|
|
42382
|
+
set parentId to ""
|
|
42383
|
+
if class of parentRef is folder then set parentId to id of parentRef
|
|
42384
|
+
set sharedAny to (shared of f)
|
|
42385
|
+
set acct to parentRef
|
|
42386
|
+
repeat while class of acct is folder
|
|
42387
|
+
if shared of acct then set sharedAny to true
|
|
42388
|
+
set acct to container of acct
|
|
42389
|
+
end repeat
|
|
42390
|
+
set acctId to id of acct
|
|
42391
|
+
set defaultId to ""
|
|
42392
|
+
try
|
|
42393
|
+
set defaultId to id of default folder of acct
|
|
42394
|
+
end try
|
|
42395
|
+
-- "folders of f" keeps listing a child deleted earlier in this Notes
|
|
42396
|
+
-- session, while "exists folder id" reports it gone, so count only
|
|
42397
|
+
-- children that still exist.
|
|
42398
|
+
set childCount to 0
|
|
42399
|
+
repeat with childRef in folders of f
|
|
42400
|
+
if exists folder id (id of childRef) then set childCount to childCount + 1
|
|
42401
|
+
end repeat
|
|
42402
|
+
set noteCount to count of notes of f`;
|
|
42403
|
+
}
|
|
42305
42404
|
function getNoteLinkFromDB(coreDataId2) {
|
|
42306
42405
|
const match = coreDataId2.match(/\/p(\d+)$/);
|
|
42307
42406
|
if (!match) return null;
|
|
@@ -42826,7 +42925,7 @@ var AppleNotesManager = class {
|
|
|
42826
42925
|
* race that would exist if JavaScript checked the note and then issued a
|
|
42827
42926
|
* separate unconditional `set body` command.
|
|
42828
42927
|
*/
|
|
42829
|
-
updateNoteByIdIfUnchanged(id2, currentTitle, expectedBody, newTitle, newContent, format = "plaintext", expectedRichRevision) {
|
|
42928
|
+
updateNoteByIdIfUnchanged(id2, currentTitle, expectedBody, newTitle, newContent, format = "plaintext", expectedRichRevision, scope2) {
|
|
42830
42929
|
const safeId = sanitizeNoteId(id2);
|
|
42831
42930
|
if (expectedRichRevision) {
|
|
42832
42931
|
const rich = readRichNote(id2);
|
|
@@ -42847,7 +42946,7 @@ var AppleNotesManager = class {
|
|
|
42847
42946
|
const safeExpectedBody = escapeHtmlForAppleScript(expectedBody);
|
|
42848
42947
|
const safeWrittenBody = escapeHtmlForAppleScript(writtenBody);
|
|
42849
42948
|
const script = buildAppLevelScript(`
|
|
42850
|
-
set noteRef to note id "${safeId}"
|
|
42949
|
+
set noteRef to note id "${safeId}"${buildScopeGuardScript("noteRef", scope2)}
|
|
42851
42950
|
if (count of attachments of noteRef) is greater than 0 then return "SAFETY_ATTACHMENTS"
|
|
42852
42951
|
set currentBody to body of noteRef
|
|
42853
42952
|
considering case
|
|
@@ -42861,6 +42960,8 @@ var AppleNotesManager = class {
|
|
|
42861
42960
|
console.error(`Failed guarded update for note ID "${id2}":`, result.error);
|
|
42862
42961
|
return { status: "failed" };
|
|
42863
42962
|
}
|
|
42963
|
+
const updateScopeFailure = parseScopeFailure(result.output);
|
|
42964
|
+
if (updateScopeFailure) return { status: "scope_conflict", reason: updateScopeFailure };
|
|
42864
42965
|
const status = result.output.trim();
|
|
42865
42966
|
if (status === "SAFETY_CONFLICT") return { status: "conflict" };
|
|
42866
42967
|
if (status === "SAFETY_ATTACHMENTS") return { status: "attachments" };
|
|
@@ -42872,12 +42973,12 @@ var AppleNotesManager = class {
|
|
|
42872
42973
|
* the caller reviewed. The comparison and delete are one AppleScript action,
|
|
42873
42974
|
* so a concurrent edit cannot slip between the guard and deletion.
|
|
42874
42975
|
*/
|
|
42875
|
-
deleteNoteByIdIfUnchanged(id2, expectedBody) {
|
|
42976
|
+
deleteNoteByIdIfUnchanged(id2, expectedBody, scope2) {
|
|
42876
42977
|
const safeId = sanitizeNoteId(id2);
|
|
42877
42978
|
validateLength(expectedBody, MAX_CONTENT_LENGTH, "Expected note content");
|
|
42878
42979
|
const safeExpectedBody = escapeHtmlForAppleScript(expectedBody);
|
|
42879
42980
|
const script = buildAppLevelScript(`
|
|
42880
|
-
set noteRef to note id "${safeId}"
|
|
42981
|
+
set noteRef to note id "${safeId}"${buildScopeGuardScript("noteRef", scope2)}
|
|
42881
42982
|
set originalFolder to missing value
|
|
42882
42983
|
try
|
|
42883
42984
|
set originalFolder to container of noteRef
|
|
@@ -42909,12 +43010,31 @@ var AppleNotesManager = class {
|
|
|
42909
43010
|
console.error(`Failed guarded delete for note ID "${id2}":`, result.error);
|
|
42910
43011
|
return { status: "failed" };
|
|
42911
43012
|
}
|
|
43013
|
+
const deleteScopeFailure = parseScopeFailure(result.output);
|
|
43014
|
+
if (deleteScopeFailure) return { status: "scope_conflict", reason: deleteScopeFailure };
|
|
42912
43015
|
const status = result.output.trim();
|
|
42913
43016
|
if (status === "SAFETY_CONFLICT") return { status: "conflict" };
|
|
42914
43017
|
if (status === "SAFETY_IN_RECENTLY_DELETED") return { status: "in-recently-deleted" };
|
|
42915
43018
|
if (status === "SAFETY_NOT_DELETED") return { status: "not-deleted" };
|
|
42916
43019
|
return status === "SAFETY_DELETED" ? { status: "deleted" } : { status: "failed" };
|
|
42917
43020
|
}
|
|
43021
|
+
/**
|
|
43022
|
+
* Read-only scope pre-check for writes that do not run as AppleScript (the
|
|
43023
|
+
* native Shortcuts operations). Runs the same checks the AppleScript writes
|
|
43024
|
+
* embed, but in a separate script, so it is not atomic with the write.
|
|
43025
|
+
*
|
|
43026
|
+
* @returns null when every precondition holds, otherwise the failure reason
|
|
43027
|
+
*/
|
|
43028
|
+
checkNoteScope(id2, scope2) {
|
|
43029
|
+
const safeId = sanitizeNoteId(id2);
|
|
43030
|
+
const result = executeAppleScript(
|
|
43031
|
+
buildAppLevelScript(`
|
|
43032
|
+
set noteRef to note id "${safeId}"${buildScopeGuardScript("noteRef", scope2)}
|
|
43033
|
+
return "SCOPE_OK"`)
|
|
43034
|
+
);
|
|
43035
|
+
if (!result.success) return result.error || "the note's folder could not be read";
|
|
43036
|
+
return parseScopeFailure(result.output);
|
|
43037
|
+
}
|
|
42918
43038
|
/**
|
|
42919
43039
|
* Builds the AppleScript body for a bulk note listing.
|
|
42920
43040
|
*
|
|
@@ -43310,18 +43430,134 @@ var AppleNotesManager = class {
|
|
|
43310
43430
|
);
|
|
43311
43431
|
return { id: id2, name: newName, parentId: expectedParentId };
|
|
43312
43432
|
}
|
|
43313
|
-
/** Read the exact name and
|
|
43433
|
+
/** Read the exact name, parent, and account identity used by guarded folder writes. */
|
|
43314
43434
|
getFolderById(id2) {
|
|
43315
43435
|
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICFolder\/p\d+$/i.test(id2))
|
|
43316
43436
|
throw new Error("An exact folder ID is required");
|
|
43317
43437
|
const result = executeAppleScript(`tell application "Notes"
|
|
43318
43438
|
set f to folder id "${id2}"
|
|
43319
|
-
|
|
43439
|
+
set acct to container of f
|
|
43440
|
+
repeat while class of acct is folder
|
|
43441
|
+
set acct to container of acct
|
|
43442
|
+
end repeat
|
|
43443
|
+
return (name of f) & ${AS_FIELD_SEP} & (id of container of f) & ${AS_FIELD_SEP} & (id of acct)
|
|
43320
43444
|
end tell`);
|
|
43321
43445
|
if (!result.success) throw new Error(result.error || "Folder not found");
|
|
43322
|
-
const [name, parentId] = result.output.replace(/\n$/, "").split(FIELD_SEP);
|
|
43446
|
+
const [name, parentId, accountId] = result.output.replace(/\n$/, "").split(FIELD_SEP);
|
|
43323
43447
|
if (!name || !parentId) throw new Error("Incomplete folder metadata");
|
|
43324
|
-
return { id: id2, name, parentId };
|
|
43448
|
+
return accountId ? { id: id2, name, parentId, accountId, isRoot: parentId === accountId } : { id: id2, name, parentId };
|
|
43449
|
+
}
|
|
43450
|
+
/**
|
|
43451
|
+
* Reads, in one AppleScript, everything Notes.app itself knows about a folder
|
|
43452
|
+
* that guarded deletion checks: name, parent (empty at an account root),
|
|
43453
|
+
* account, the account's default folder, shared state of the folder or any
|
|
43454
|
+
* ancestor, and the counts of direct child folders and notes.
|
|
43455
|
+
*/
|
|
43456
|
+
readFolderForDelete(id2) {
|
|
43457
|
+
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
43458
|
+
const result = executeAppleScript(
|
|
43459
|
+
buildAppLevelScript(`${folderDeleteFactsScript(id2)}
|
|
43460
|
+
return folderName & ${AS_FIELD_SEP} & parentId & ${AS_FIELD_SEP} & acctId & ${AS_FIELD_SEP} & defaultId & ${AS_FIELD_SEP} & (sharedAny as text) & ${AS_FIELD_SEP} & (childCount as text) & ${AS_FIELD_SEP} & (noteCount as text)`)
|
|
43461
|
+
);
|
|
43462
|
+
if (!result.success) throw new Error(result.error || "Folder not found");
|
|
43463
|
+
const parts = result.output.replace(/\n$/, "").split(FIELD_SEP);
|
|
43464
|
+
if (parts.length !== 7 || !parts[0] || !parts[2]) throw new Error("Incomplete folder metadata");
|
|
43465
|
+
const count = (value) => {
|
|
43466
|
+
const parsed = Number(value.trim());
|
|
43467
|
+
if (!Number.isInteger(parsed) || parsed < 0) throw new Error("Incomplete folder metadata");
|
|
43468
|
+
return parsed;
|
|
43469
|
+
};
|
|
43470
|
+
return {
|
|
43471
|
+
id: id2,
|
|
43472
|
+
name: parts[0],
|
|
43473
|
+
parentId: parts[1] || null,
|
|
43474
|
+
accountId: parts[2],
|
|
43475
|
+
defaultFolderId: parts[3] || null,
|
|
43476
|
+
shared: parts[4].trim() === "true",
|
|
43477
|
+
childFolderCount: count(parts[5]),
|
|
43478
|
+
noteCount: count(parts[6])
|
|
43479
|
+
};
|
|
43480
|
+
}
|
|
43481
|
+
/**
|
|
43482
|
+
* Deletes one empty folder by exact id, repeating every Notes.app-visible
|
|
43483
|
+
* guard inside the same AppleScript as the `delete` command: exact name
|
|
43484
|
+
* (case-sensitive), parent or account root, account, not the default folder,
|
|
43485
|
+
* not shared (itself or an ancestor), no child folders, no notes.
|
|
43486
|
+
*
|
|
43487
|
+
* Store-only facts (folder type, stable identifier) are checked by the caller
|
|
43488
|
+
* before this runs, so the whole guard is a pre-check followed by an
|
|
43489
|
+
* AppleScript delete, not one atomic transaction.
|
|
43490
|
+
*/
|
|
43491
|
+
deleteEmptyFolderIfUnchanged(id2, expected) {
|
|
43492
|
+
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
43493
|
+
const literal2 = (value) => `"${escapePlainStringForAppleScript(value)}"`;
|
|
43494
|
+
const script = buildAppLevelScript(`${folderDeleteFactsScript(id2)}
|
|
43495
|
+
considering case
|
|
43496
|
+
if folderName is not ${literal2(expected.name)} then return "SAFETY_CONFLICT:name"
|
|
43497
|
+
end considering
|
|
43498
|
+
if parentId is not ${literal2(expected.parentId ?? "")} then return "SAFETY_CONFLICT:parent"
|
|
43499
|
+
if acctId is not ${literal2(expected.accountId)} then return "SAFETY_CONFLICT:account"
|
|
43500
|
+
if defaultId is ${literal2(id2)} then return "SAFETY_REFUSED:default folder"
|
|
43501
|
+
if sharedAny then return "SAFETY_REFUSED:shared folder"
|
|
43502
|
+
if childCount is not 0 then return "SAFETY_REFUSED:folder has child folders"
|
|
43503
|
+
if noteCount is not 0 then return "SAFETY_REFUSED:folder has notes"
|
|
43504
|
+
delete f
|
|
43505
|
+
return "SAFETY_DELETED"`);
|
|
43506
|
+
const result = executeMutationAppleScript(script);
|
|
43507
|
+
if (!result.success) {
|
|
43508
|
+
console.error(`Failed guarded folder delete for "${id2}":`, result.error);
|
|
43509
|
+
return { status: "failed", reason: result.error || "AppleScript failed" };
|
|
43510
|
+
}
|
|
43511
|
+
const output = result.output.trim();
|
|
43512
|
+
const colon = output.indexOf(":");
|
|
43513
|
+
const status = colon === -1 ? output : output.slice(0, colon);
|
|
43514
|
+
const reason = colon === -1 ? "" : output.slice(colon + 1);
|
|
43515
|
+
if (status === "SAFETY_DELETED") return { status: "deleted" };
|
|
43516
|
+
if (status === "SAFETY_CONFLICT") return { status: "conflict", reason };
|
|
43517
|
+
if (status === "SAFETY_REFUSED") return { status: "refused", reason };
|
|
43518
|
+
return { status: "failed", reason: "Unexpected AppleScript result" };
|
|
43519
|
+
}
|
|
43520
|
+
/**
|
|
43521
|
+
* Counts how many of the given notes Notes.app currently places outside the
|
|
43522
|
+
* folder: in another folder, or in Recently Deleted (a note trashed this
|
|
43523
|
+
* session reports a non-folder container). A note Notes.app cannot resolve
|
|
43524
|
+
* is not counted, so the caller keeps treating it as present.
|
|
43525
|
+
*
|
|
43526
|
+
* The local store can keep a just-trashed note in its old folder for
|
|
43527
|
+
* minutes; this lets guarded folder deletion discount such notes using
|
|
43528
|
+
* Notes.app's live view instead of refusing until the store catches up.
|
|
43529
|
+
*/
|
|
43530
|
+
countNotesOutsideFolder(folderId2, noteIds) {
|
|
43531
|
+
if (!FOLDER_ID_PATTERN.test(folderId2)) throw new Error("An exact folder ID is required");
|
|
43532
|
+
if (noteIds.length === 0) return 0;
|
|
43533
|
+
const ids = noteIds.map((noteId3) => `"${sanitizeNoteId(noteId3)}"`).join(", ");
|
|
43534
|
+
const result = executeAppleScript(
|
|
43535
|
+
buildAppLevelScript(`
|
|
43536
|
+
set movedCount to 0
|
|
43537
|
+
repeat with noteIdRef in {${ids}}
|
|
43538
|
+
try
|
|
43539
|
+
set c to container of note id (contents of noteIdRef)
|
|
43540
|
+
if class of c is not folder then
|
|
43541
|
+
set movedCount to movedCount + 1
|
|
43542
|
+
else if (id of c) is not "${folderId2}" then
|
|
43543
|
+
set movedCount to movedCount + 1
|
|
43544
|
+
end if
|
|
43545
|
+
end try
|
|
43546
|
+
end repeat
|
|
43547
|
+
return movedCount as text`)
|
|
43548
|
+
);
|
|
43549
|
+
if (!result.success) return 0;
|
|
43550
|
+
const moved = Number(result.output.trim());
|
|
43551
|
+
return Number.isInteger(moved) && moved >= 0 && moved <= noteIds.length ? moved : 0;
|
|
43552
|
+
}
|
|
43553
|
+
/** Whether Notes.app still resolves an exact folder id. */
|
|
43554
|
+
folderExistsById(id2) {
|
|
43555
|
+
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
43556
|
+
const result = executeAppleScript(
|
|
43557
|
+
buildAppLevelScript(`return (exists folder id "${id2}") as text`)
|
|
43558
|
+
);
|
|
43559
|
+
if (!result.success) throw new Error(result.error || "Could not check folder existence");
|
|
43560
|
+
return result.output.trim() === "true";
|
|
43325
43561
|
}
|
|
43326
43562
|
/** Insert one file into an unchanged exact note and return Notes' attachment ID. */
|
|
43327
43563
|
addAttachmentById(id2, expectedBody, filePath) {
|
|
@@ -43501,14 +43737,14 @@ var AppleNotesManager = class {
|
|
|
43501
43737
|
* @param account - Account containing the destination folder (defaults to Notes.app's default account)
|
|
43502
43738
|
* @returns true if the move succeeded, false otherwise
|
|
43503
43739
|
*/
|
|
43504
|
-
moveNoteById(id2, destinationFolder, account) {
|
|
43740
|
+
moveNoteById(id2, destinationFolder, account, scope2) {
|
|
43505
43741
|
const targetAccount = this.resolveAccount(account);
|
|
43506
43742
|
const safeId = sanitizeNoteId(id2);
|
|
43507
43743
|
const destFolderRef = `${buildFolderReference(destinationFolder)} of ${AS_ACCOUNT_REF}`;
|
|
43508
43744
|
const moveCommand = `
|
|
43509
43745
|
${buildAccountResolution(targetAccount)}
|
|
43510
43746
|
set destFolder to ${destFolderRef}
|
|
43511
|
-
set noteRef to note id "${safeId}"
|
|
43747
|
+
set noteRef to note id "${safeId}"${buildScopeGuardScript("noteRef", scope2, "destFolder")}
|
|
43512
43748
|
move noteRef to destFolder
|
|
43513
43749
|
set movedNoteRef to note id "${safeId}"
|
|
43514
43750
|
set actualFolder to container of movedNoteRef
|
|
@@ -43525,6 +43761,8 @@ var AppleNotesManager = class {
|
|
|
43525
43761
|
);
|
|
43526
43762
|
return false;
|
|
43527
43763
|
}
|
|
43764
|
+
const scopeFailure = parseScopeFailure(result.output);
|
|
43765
|
+
if (scopeFailure) throw new Error(scopeConflictMessage(scopeFailure));
|
|
43528
43766
|
if (result.output.trim() !== "SAFETY_MOVED") {
|
|
43529
43767
|
console.error(
|
|
43530
43768
|
`Move result for note ID "${id2}" did not verify destination "${destinationFolder}"`
|
|
@@ -44426,12 +44664,12 @@ var AppleNotesManager = class {
|
|
|
44426
44664
|
}
|
|
44427
44665
|
});
|
|
44428
44666
|
if (runnable.length > 0) {
|
|
44429
|
-
const
|
|
44667
|
+
const idList2 = runnable.map((r) => `"${r.safe}"`).join(", ");
|
|
44430
44668
|
const script = buildAppLevelScript(`
|
|
44431
44669
|
${buildAccountResolution(targetAccount)}
|
|
44432
44670
|
set destFolder to ${destFolderRef}
|
|
44433
44671
|
set out to ""
|
|
44434
|
-
repeat with rawId in {${
|
|
44672
|
+
repeat with rawId in {${idList2}}
|
|
44435
44673
|
set theId to (rawId as text)
|
|
44436
44674
|
set noteRef to missing value
|
|
44437
44675
|
try
|
|
@@ -46307,12 +46545,12 @@ function resolveFolders(rows) {
|
|
|
46307
46545
|
seen.add(pk);
|
|
46308
46546
|
const parent = row.parent !== null ? resolve5(row.parent, seen) : void 0;
|
|
46309
46547
|
const name = row.name ?? "";
|
|
46310
|
-
const
|
|
46548
|
+
const path10 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
|
|
46311
46549
|
const plainPath = parent ? `${parent.plainPath}/${name}` : name;
|
|
46312
46550
|
const info = {
|
|
46313
|
-
path:
|
|
46551
|
+
path: path10,
|
|
46314
46552
|
plainPath,
|
|
46315
|
-
keys: [...new Set([name, plainPath,
|
|
46553
|
+
keys: [...new Set([name, plainPath, path10].map(normalizeForMatch))],
|
|
46316
46554
|
accountPk: row.owner ?? parent?.accountPk ?? null,
|
|
46317
46555
|
shared: Boolean(row.shared) || Boolean(parent?.shared)
|
|
46318
46556
|
};
|
|
@@ -46653,9 +46891,9 @@ function runNativeTagsShortcut(input) {
|
|
|
46653
46891
|
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
46654
46892
|
const directory = mkdtempSync2(join15(tmpdir2(), "apple-notes-native-tags-"));
|
|
46655
46893
|
try {
|
|
46656
|
-
const
|
|
46657
|
-
writeFileSync(
|
|
46658
|
-
execFileSync14("/usr/bin/shortcuts", ["run", status.identifier, "--input-path",
|
|
46894
|
+
const path10 = join15(directory, "request.json");
|
|
46895
|
+
writeFileSync(path10, JSON.stringify(input), { mode: 384 });
|
|
46896
|
+
execFileSync14("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path10], {
|
|
46659
46897
|
encoding: "utf8",
|
|
46660
46898
|
timeout: 6e4,
|
|
46661
46899
|
maxBuffer: 1024 * 1024,
|
|
@@ -47259,7 +47497,7 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
|
|
|
47259
47497
|
throw new Error(
|
|
47260
47498
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
47261
47499
|
);
|
|
47262
|
-
const segments = (
|
|
47500
|
+
const segments = (path10) => JSON.stringify(splitFolderPath(path10).map((part) => part.toLocaleLowerCase()));
|
|
47263
47501
|
if (request.folder) {
|
|
47264
47502
|
const wanted = segments(request.folder);
|
|
47265
47503
|
if (!manager.listAccounts().some(
|
|
@@ -47589,9 +47827,9 @@ function getCapabilityMatrix(probe = probeCapabilityEnvironment) {
|
|
|
47589
47827
|
return evaluateFeatures(probe());
|
|
47590
47828
|
}
|
|
47591
47829
|
function formatCapabilityMatrix(matrix) {
|
|
47592
|
-
const
|
|
47830
|
+
const os10 = matrix.runtimeOS;
|
|
47593
47831
|
const lines = [
|
|
47594
|
-
`Feature matrix (macOS ${
|
|
47832
|
+
`Feature matrix (macOS ${os10.macOSVersion ?? "unknown"}, Darwin ${os10.darwinRelease}):`
|
|
47595
47833
|
];
|
|
47596
47834
|
for (const [name, status] of Object.entries(matrix.features)) {
|
|
47597
47835
|
lines.push(
|
|
@@ -47717,11 +47955,11 @@ function fileConfigPath(env = process.env) {
|
|
|
47717
47955
|
if (override && override.trim()) return override.trim();
|
|
47718
47956
|
return join17(homedir14(), "Library", "Application Support", "apple-notes-mcp", "config.json");
|
|
47719
47957
|
}
|
|
47720
|
-
function loadFileConfig(env = process.env,
|
|
47958
|
+
function loadFileConfig(env = process.env, path10 = fileConfigPath(env)) {
|
|
47721
47959
|
const applied = [];
|
|
47722
47960
|
try {
|
|
47723
|
-
if (!existsSync11(
|
|
47724
|
-
const parsed = JSON.parse(readFileSync2(
|
|
47961
|
+
if (!existsSync11(path10)) return applied;
|
|
47962
|
+
const parsed = JSON.parse(readFileSync2(path10, "utf8"));
|
|
47725
47963
|
if (!parsed || typeof parsed !== "object") return applied;
|
|
47726
47964
|
for (const [k, v] of Object.entries(parsed)) {
|
|
47727
47965
|
if (typeof v !== "string") continue;
|
|
@@ -47731,7 +47969,7 @@ function loadFileConfig(env = process.env, path9 = fileConfigPath(env)) {
|
|
|
47731
47969
|
}
|
|
47732
47970
|
}
|
|
47733
47971
|
} catch (e) {
|
|
47734
|
-
console.error(`Failed to load apple-notes-mcp config file ${
|
|
47972
|
+
console.error(`Failed to load apple-notes-mcp config file ${path10}: ${String(e)}`);
|
|
47735
47973
|
}
|
|
47736
47974
|
return applied;
|
|
47737
47975
|
}
|
|
@@ -48574,10 +48812,10 @@ function listDirectory(dir, limit) {
|
|
|
48574
48812
|
return [];
|
|
48575
48813
|
}
|
|
48576
48814
|
}
|
|
48577
|
-
function isFile(
|
|
48815
|
+
function isFile(path10) {
|
|
48578
48816
|
let fd;
|
|
48579
48817
|
try {
|
|
48580
|
-
fd = openSync4(
|
|
48818
|
+
fd = openSync4(path10, constants4.O_RDONLY | constants4.O_NOFOLLOW);
|
|
48581
48819
|
return fstatSync4(fd).isFile();
|
|
48582
48820
|
} catch {
|
|
48583
48821
|
return false;
|
|
@@ -48585,9 +48823,9 @@ function isFile(path9) {
|
|
|
48585
48823
|
if (fd !== void 0) closeSync4(fd);
|
|
48586
48824
|
}
|
|
48587
48825
|
}
|
|
48588
|
-
function isDirectory2(
|
|
48826
|
+
function isDirectory2(path10) {
|
|
48589
48827
|
try {
|
|
48590
|
-
return statSync3(
|
|
48828
|
+
return statSync3(path10).isDirectory();
|
|
48591
48829
|
} catch {
|
|
48592
48830
|
return false;
|
|
48593
48831
|
}
|
|
@@ -48671,8 +48909,8 @@ var AssetLocator = class {
|
|
|
48671
48909
|
join19(account, "Media", mediaId, filename)
|
|
48672
48910
|
];
|
|
48673
48911
|
for (const candidate of candidates) {
|
|
48674
|
-
const
|
|
48675
|
-
if (
|
|
48912
|
+
const path10 = confine(candidate, account);
|
|
48913
|
+
if (path10 && isFile(path10)) return { path: path10, name: filename, role: "original" };
|
|
48676
48914
|
}
|
|
48677
48915
|
return void 0;
|
|
48678
48916
|
}
|
|
@@ -48687,8 +48925,8 @@ var AssetLocator = class {
|
|
|
48687
48925
|
];
|
|
48688
48926
|
for (const g of generations)
|
|
48689
48927
|
for (const name of names) {
|
|
48690
|
-
const
|
|
48691
|
-
if (
|
|
48928
|
+
const path10 = confine(g ? join19(base, g, name) : join19(base, name), account);
|
|
48929
|
+
if (path10 && isFile(path10)) return path10;
|
|
48692
48930
|
}
|
|
48693
48931
|
return void 0;
|
|
48694
48932
|
}
|
|
@@ -48721,16 +48959,16 @@ var AssetLocator = class {
|
|
|
48721
48959
|
bundleImage(bundle, account) {
|
|
48722
48960
|
const found = [];
|
|
48723
48961
|
for (const child of listDirectory(bundle, MAX_BUNDLE_ENTRIES2)) {
|
|
48724
|
-
const
|
|
48725
|
-
if (!
|
|
48726
|
-
if (isFile(
|
|
48962
|
+
const path10 = confine(join19(bundle, child), account);
|
|
48963
|
+
if (!path10) continue;
|
|
48964
|
+
if (isFile(path10)) found.push(path10);
|
|
48727
48965
|
else
|
|
48728
|
-
for (const grandchild of listDirectory(
|
|
48729
|
-
const inner = confine(join19(
|
|
48966
|
+
for (const grandchild of listDirectory(path10, MAX_BUNDLE_ENTRIES2)) {
|
|
48967
|
+
const inner = confine(join19(path10, grandchild), account);
|
|
48730
48968
|
if (inner && isFile(inner)) found.push(inner);
|
|
48731
48969
|
}
|
|
48732
48970
|
}
|
|
48733
|
-
return found.find((
|
|
48971
|
+
return found.find((path10) => basename3(path10) === "Preview.png") ?? found.find((path10) => IMAGE_EXTENSIONS.has(extname3(path10).toLowerCase()));
|
|
48734
48972
|
}
|
|
48735
48973
|
};
|
|
48736
48974
|
function sniffMime(head, name) {
|
|
@@ -48778,8 +49016,8 @@ function safeAssetName(name, mime) {
|
|
|
48778
49016
|
if (clean.length > 120) clean = clean.slice(0, 120 - ext.length) + ext;
|
|
48779
49017
|
return clean;
|
|
48780
49018
|
}
|
|
48781
|
-
function encodePathUrl(
|
|
48782
|
-
return
|
|
49019
|
+
function encodePathUrl(path10) {
|
|
49020
|
+
return path10.split("/").map(encodeURIComponent).join("/");
|
|
48783
49021
|
}
|
|
48784
49022
|
function canonicalForm(abs) {
|
|
48785
49023
|
let current = abs;
|
|
@@ -48798,7 +49036,7 @@ function canonicalForm(abs) {
|
|
|
48798
49036
|
}
|
|
48799
49037
|
function assertExportPath(p, container = NOTES_CONTAINER) {
|
|
48800
49038
|
const abs = assertSafeSavePath(p);
|
|
48801
|
-
const within = (
|
|
49039
|
+
const within = (path10, root) => path10 === root || path10.startsWith(root + sep3);
|
|
48802
49040
|
const roots = [resolve3(container), canonicalForm(resolve3(container))];
|
|
48803
49041
|
const forms = [abs, canonicalForm(abs)];
|
|
48804
49042
|
if (forms.some((form) => roots.some((root) => within(form, root))))
|
|
@@ -48808,16 +49046,16 @@ function assertExportPath(p, container = NOTES_CONTAINER) {
|
|
|
48808
49046
|
var CREATE_FLAGS = constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | constants4.O_NOFOLLOW;
|
|
48809
49047
|
var OutputExistsError = class extends Error {
|
|
48810
49048
|
code = "output_exists";
|
|
48811
|
-
constructor(
|
|
48812
|
-
super(`Output file already exists: ${
|
|
49049
|
+
constructor(path10) {
|
|
49050
|
+
super(`Output file already exists: ${path10}. Choose a new path or remove the old file first.`);
|
|
48813
49051
|
this.name = "OutputExistsError";
|
|
48814
49052
|
}
|
|
48815
49053
|
};
|
|
48816
|
-
function openCreateOnly(
|
|
49054
|
+
function openCreateOnly(path10) {
|
|
48817
49055
|
try {
|
|
48818
|
-
return openSync4(
|
|
49056
|
+
return openSync4(path10, CREATE_FLAGS, 420);
|
|
48819
49057
|
} catch (error2) {
|
|
48820
|
-
if (error2.code === "EEXIST") throw new OutputExistsError(
|
|
49058
|
+
if (error2.code === "EEXIST") throw new OutputExistsError(path10);
|
|
48821
49059
|
throw error2;
|
|
48822
49060
|
}
|
|
48823
49061
|
}
|
|
@@ -48831,8 +49069,8 @@ function writeAllAndClose(fd, content) {
|
|
|
48831
49069
|
closeSync4(fd);
|
|
48832
49070
|
}
|
|
48833
49071
|
}
|
|
48834
|
-
function openSource(
|
|
48835
|
-
const fd = openSync4(
|
|
49072
|
+
function openSource(path10) {
|
|
49073
|
+
const fd = openSync4(path10, constants4.O_RDONLY | constants4.O_NOFOLLOW);
|
|
48836
49074
|
const stat = fstatSync4(fd);
|
|
48837
49075
|
if (!stat.isFile()) {
|
|
48838
49076
|
closeSync4(fd);
|
|
@@ -49814,9 +50052,9 @@ var NotesExportError = class extends Error {
|
|
|
49814
50052
|
}
|
|
49815
50053
|
code;
|
|
49816
50054
|
};
|
|
49817
|
-
function validPath(
|
|
50055
|
+
function validPath(path10, what) {
|
|
49818
50056
|
try {
|
|
49819
|
-
return assertExportPath(
|
|
50057
|
+
return assertExportPath(path10);
|
|
49820
50058
|
} catch (error2) {
|
|
49821
50059
|
throw new NotesExportError(
|
|
49822
50060
|
"invalid-path",
|
|
@@ -50005,9 +50243,9 @@ function assertExistingContentPreserved(before, after) {
|
|
|
50005
50243
|
throw new Error("Existing native object content or presentation changed");
|
|
50006
50244
|
}
|
|
50007
50245
|
}
|
|
50008
|
-
function localAttachment(
|
|
50009
|
-
if (!isAbsolute3(
|
|
50010
|
-
const descriptor = openSync5(
|
|
50246
|
+
function localAttachment(path10) {
|
|
50247
|
+
if (!isAbsolute3(path10)) throw new Error("An absolute local file path is required");
|
|
50248
|
+
const descriptor = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW);
|
|
50011
50249
|
try {
|
|
50012
50250
|
const stat = fstatSync5(descriptor);
|
|
50013
50251
|
if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
|
|
@@ -50045,7 +50283,7 @@ function registerDirectOperations(server2, manager) {
|
|
|
50045
50283
|
}
|
|
50046
50284
|
tool(
|
|
50047
50285
|
"get-folder-by-id",
|
|
50048
|
-
"Use when: reading the exact folder name and
|
|
50286
|
+
"Use when: reading the exact folder name, parent, and account before a guarded rename or delete-folder-by-id.\nReturns: folder id, current name, parent id, accountId, and isRoot (true when the folder sits at the account root; pass expectedRoot: true to delete-folder-by-id then, otherwise parentId as expectedParentId).\nDo not use when: listing folders by path (list-folders).\nSafety: read-only.",
|
|
50049
50287
|
{ id: folderId },
|
|
50050
50288
|
({ id: id2 }) => {
|
|
50051
50289
|
const folder = manager.getFolderById(id2);
|
|
@@ -50129,8 +50367,8 @@ function registerDirectOperations(server2, manager) {
|
|
|
50129
50367
|
}
|
|
50130
50368
|
);
|
|
50131
50369
|
}
|
|
50132
|
-
function attachmentName(
|
|
50133
|
-
const source = basename5(
|
|
50370
|
+
function attachmentName(path10, filename) {
|
|
50371
|
+
const source = basename5(path10);
|
|
50134
50372
|
if (filename === void 0) return source;
|
|
50135
50373
|
if (Buffer.byteLength(filename, "utf8") > 255)
|
|
50136
50374
|
throw new Error("filename must be at most 255 bytes");
|
|
@@ -50145,11 +50383,11 @@ function attachmentName(path9, filename) {
|
|
|
50145
50383
|
return filename;
|
|
50146
50384
|
}
|
|
50147
50385
|
function attachFile(manager, args) {
|
|
50148
|
-
const { id: id2, expectedContentHash, path:
|
|
50149
|
-
const name = attachmentName(
|
|
50386
|
+
const { id: id2, expectedContentHash, path: path10 } = args;
|
|
50387
|
+
const name = attachmentName(path10, args.filename);
|
|
50150
50388
|
const before = readSnapshot(manager, id2);
|
|
50151
50389
|
if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
|
|
50152
|
-
const bytes = localAttachment(
|
|
50390
|
+
const bytes = localAttachment(path10);
|
|
50153
50391
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
50154
50392
|
const directory = mkdtempSync4(join22(tmpdir4(), "notes-attachment-add-"));
|
|
50155
50393
|
const temporaryFile = join22(directory, name);
|
|
@@ -50206,6 +50444,329 @@ function attachFile(manager, args) {
|
|
|
50206
50444
|
}
|
|
50207
50445
|
}
|
|
50208
50446
|
|
|
50447
|
+
// src/tools/folderDelete.ts
|
|
50448
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
50449
|
+
|
|
50450
|
+
// src/utils/folderStore.ts
|
|
50451
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
50452
|
+
import * as os9 from "os";
|
|
50453
|
+
import * as path9 from "path";
|
|
50454
|
+
var NOTE_STORE_PATH = path9.join(
|
|
50455
|
+
os9.homedir(),
|
|
50456
|
+
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
50457
|
+
);
|
|
50458
|
+
var NOTE_KEY_LIMIT = 50;
|
|
50459
|
+
var FolderStoreError = class extends Error {
|
|
50460
|
+
constructor(message, reason) {
|
|
50461
|
+
super(message);
|
|
50462
|
+
this.reason = reason;
|
|
50463
|
+
this.name = "FolderStoreError";
|
|
50464
|
+
}
|
|
50465
|
+
reason;
|
|
50466
|
+
};
|
|
50467
|
+
var REQUIRED_FOLDER_COLUMNS = [
|
|
50468
|
+
"ZFOLDERTYPE",
|
|
50469
|
+
"ZIDENTIFIER",
|
|
50470
|
+
"ZMARKEDFORDELETION",
|
|
50471
|
+
"ZPARENT",
|
|
50472
|
+
"ZOWNER",
|
|
50473
|
+
"ZFOLDER",
|
|
50474
|
+
"ZSERVERSHAREDATA"
|
|
50475
|
+
];
|
|
50476
|
+
function buildFolderFactsSql(hasSmartQueryColumn) {
|
|
50477
|
+
const smart = hasSmartQueryColumn ? "coalesce(f.ZSMARTFOLDERQUERYJSON, '') <> ''" : "0";
|
|
50478
|
+
return `WITH RECURSIVE anc(pk, depth) AS (
|
|
50479
|
+
SELECT ZPARENT, 1 FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = @pk
|
|
50480
|
+
UNION ALL
|
|
50481
|
+
SELECT o.ZPARENT, anc.depth + 1 FROM ZICCLOUDSYNCINGOBJECT o JOIN anc ON o.Z_PK = anc.pk
|
|
50482
|
+
WHERE anc.pk IS NOT NULL AND anc.depth < 64
|
|
50483
|
+
)
|
|
50484
|
+
SELECT json_object(
|
|
50485
|
+
'pk', f.Z_PK,
|
|
50486
|
+
'entity', e.Z_NAME,
|
|
50487
|
+
'identifier', f.ZIDENTIFIER,
|
|
50488
|
+
'folderType', f.ZFOLDERTYPE,
|
|
50489
|
+
'markedForDeletion', coalesce(f.ZMARKEDFORDELETION, 0),
|
|
50490
|
+
'parentPk', f.ZPARENT,
|
|
50491
|
+
'accountPk', f.ZOWNER,
|
|
50492
|
+
'hasSmartQuery', ${smart},
|
|
50493
|
+
'sharedRecord', f.ZSERVERSHAREDATA IS NOT NULL,
|
|
50494
|
+
'sharedAncestor', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT a JOIN anc ON a.Z_PK = anc.pk
|
|
50495
|
+
WHERE a.ZSERVERSHAREDATA IS NOT NULL) > 0,
|
|
50496
|
+
'childFolderCount', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT c
|
|
50497
|
+
WHERE c.Z_ENT = f.Z_ENT AND c.ZPARENT = f.Z_PK
|
|
50498
|
+
AND coalesce(c.ZMARKEDFORDELETION, 0) = 0),
|
|
50499
|
+
'noteCount', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n
|
|
50500
|
+
WHERE n.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')
|
|
50501
|
+
AND n.ZFOLDER = f.Z_PK AND coalesce(n.ZMARKEDFORDELETION, 0) = 0),
|
|
50502
|
+
'noteKeys', json((SELECT json_group_array(k.Z_PK) FROM (
|
|
50503
|
+
SELECT n.Z_PK FROM ZICCLOUDSYNCINGOBJECT n
|
|
50504
|
+
WHERE n.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')
|
|
50505
|
+
AND n.ZFOLDER = f.Z_PK AND coalesce(n.ZMARKEDFORDELETION, 0) = 0
|
|
50506
|
+
ORDER BY n.Z_PK LIMIT ${NOTE_KEY_LIMIT}) k))
|
|
50507
|
+
)
|
|
50508
|
+
FROM ZICCLOUDSYNCINGOBJECT f JOIN Z_PRIMARYKEY e ON e.Z_ENT = f.Z_ENT
|
|
50509
|
+
WHERE f.Z_PK = @pk;`;
|
|
50510
|
+
}
|
|
50511
|
+
function runSqlite7(dbPath2, sql, pk) {
|
|
50512
|
+
const args = ["-readonly"];
|
|
50513
|
+
if (pk !== void 0) args.push("-cmd", `.parameter set @pk ${pk}`);
|
|
50514
|
+
args.push(dbPath2, sql);
|
|
50515
|
+
return execFileSync19("sqlite3", args, {
|
|
50516
|
+
encoding: "utf8",
|
|
50517
|
+
timeout: 5e3,
|
|
50518
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
50519
|
+
}).trim();
|
|
50520
|
+
}
|
|
50521
|
+
function classify(error2) {
|
|
50522
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
50523
|
+
if (/authorization denied|unable to open database|not authorized/i.test(message)) {
|
|
50524
|
+
return new FolderStoreError(
|
|
50525
|
+
"Full Disk Access is required to verify folder type and contents before deleting. Grant it to the app that launches this server, then relaunch it.",
|
|
50526
|
+
"no_fda"
|
|
50527
|
+
);
|
|
50528
|
+
}
|
|
50529
|
+
return new FolderStoreError(`Failed to read folder facts: ${message}`, "query_error");
|
|
50530
|
+
}
|
|
50531
|
+
function numberOrNull(value) {
|
|
50532
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
50533
|
+
}
|
|
50534
|
+
function readFolderStoreFacts(pk, dbPath2 = NOTE_STORE_PATH) {
|
|
50535
|
+
if (!Number.isSafeInteger(pk) || pk <= 0) {
|
|
50536
|
+
throw new FolderStoreError("Folder key must be a positive integer", "query_error");
|
|
50537
|
+
}
|
|
50538
|
+
let columns;
|
|
50539
|
+
let row;
|
|
50540
|
+
try {
|
|
50541
|
+
columns = new Set(
|
|
50542
|
+
runSqlite7(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n").map((line) => line.split("|")[1]).filter(Boolean)
|
|
50543
|
+
);
|
|
50544
|
+
const missing = REQUIRED_FOLDER_COLUMNS.filter((column) => !columns.has(column));
|
|
50545
|
+
if (missing.length > 0) {
|
|
50546
|
+
throw new FolderStoreError(
|
|
50547
|
+
`This macOS Notes schema lacks ${missing.join(", ")}; folder safety cannot be verified`,
|
|
50548
|
+
"schema"
|
|
50549
|
+
);
|
|
50550
|
+
}
|
|
50551
|
+
row = runSqlite7(dbPath2, buildFolderFactsSql(columns.has("ZSMARTFOLDERQUERYJSON")), pk);
|
|
50552
|
+
} catch (error2) {
|
|
50553
|
+
if (error2 instanceof FolderStoreError) throw error2;
|
|
50554
|
+
throw classify(error2);
|
|
50555
|
+
}
|
|
50556
|
+
if (!row) return null;
|
|
50557
|
+
const raw = JSON.parse(row);
|
|
50558
|
+
if (raw.entity !== "ICFolder") {
|
|
50559
|
+
throw new FolderStoreError("That identifier does not name a folder", "not_folder");
|
|
50560
|
+
}
|
|
50561
|
+
return {
|
|
50562
|
+
pk,
|
|
50563
|
+
identifier: typeof raw.identifier === "string" ? raw.identifier : null,
|
|
50564
|
+
folderType: numberOrNull(raw.folderType),
|
|
50565
|
+
markedForDeletion: raw.markedForDeletion === 1,
|
|
50566
|
+
parentPk: numberOrNull(raw.parentPk),
|
|
50567
|
+
accountPk: numberOrNull(raw.accountPk),
|
|
50568
|
+
hasSmartQuery: raw.hasSmartQuery === 1,
|
|
50569
|
+
sharedRecord: raw.sharedRecord === 1,
|
|
50570
|
+
sharedAncestor: raw.sharedAncestor === 1,
|
|
50571
|
+
childFolderCount: numberOrNull(raw.childFolderCount) ?? 0,
|
|
50572
|
+
noteCount: numberOrNull(raw.noteCount) ?? 0,
|
|
50573
|
+
noteKeys: Array.isArray(raw.noteKeys) ? raw.noteKeys.filter((key) => typeof key === "number") : []
|
|
50574
|
+
};
|
|
50575
|
+
}
|
|
50576
|
+
|
|
50577
|
+
// src/tools/folderDelete.ts
|
|
50578
|
+
var folderIdSchema = external_exports.string().max(2e3).transform(looseIdTransform("ICFolder"));
|
|
50579
|
+
var accountIdSchema = external_exports.string().max(2e3).regex(/^x-coredata:\/\/[0-9a-f-]+\/ICAccount\/p\d+$/i, "An exact account ID is required");
|
|
50580
|
+
var revisionSchema = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
50581
|
+
var defaultDeps = {
|
|
50582
|
+
readStore: (pk) => readFolderStoreFacts(pk),
|
|
50583
|
+
sleep: (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)
|
|
50584
|
+
};
|
|
50585
|
+
function conflict(message) {
|
|
50586
|
+
return new CodedError(`Conflict: ${message}`, { code: "revision_conflict", committed: false });
|
|
50587
|
+
}
|
|
50588
|
+
function refused(message) {
|
|
50589
|
+
return new CodedError(`Refused: ${message}`, { code: "unsupported", committed: false });
|
|
50590
|
+
}
|
|
50591
|
+
function uncertain(message) {
|
|
50592
|
+
return new CodedError(message, { code: "verification_failed", indeterminate: true });
|
|
50593
|
+
}
|
|
50594
|
+
function invalid(message) {
|
|
50595
|
+
return new CodedError(message, { code: "validation_error", committed: false });
|
|
50596
|
+
}
|
|
50597
|
+
function coreDataPk(id2) {
|
|
50598
|
+
const match = /\/p(\d+)$/.exec(id2);
|
|
50599
|
+
if (!match) throw new Error(`Not an x-coredata id: ${id2}`);
|
|
50600
|
+
return Number(match[1]);
|
|
50601
|
+
}
|
|
50602
|
+
function folderDeleteRefusal(app, store) {
|
|
50603
|
+
if (store.folderType === 1 || store.identifier?.startsWith("TrashFolder-"))
|
|
50604
|
+
return "Refusing to delete the Recently Deleted folder";
|
|
50605
|
+
if (store.folderType === 2 || store.hasSmartQuery) return "Refusing to delete a smart folder";
|
|
50606
|
+
if (store.folderType !== 0)
|
|
50607
|
+
return `Refusing to delete a folder with unsupported folder type ${String(store.folderType)}`;
|
|
50608
|
+
if (!store.identifier)
|
|
50609
|
+
return "Refusing to delete a folder without a stable identifier (it cannot be verified)";
|
|
50610
|
+
if (store.identifier.startsWith("DefaultFolder-") || app.defaultFolderId === app.id)
|
|
50611
|
+
return "Refusing to delete a system or default folder";
|
|
50612
|
+
if (app.shared || store.sharedRecord || store.sharedAncestor)
|
|
50613
|
+
return "Refusing to delete a shared or collaborated folder";
|
|
50614
|
+
return null;
|
|
50615
|
+
}
|
|
50616
|
+
function folderDeleteRevision(app, store) {
|
|
50617
|
+
const state = [
|
|
50618
|
+
app.id,
|
|
50619
|
+
store.identifier,
|
|
50620
|
+
app.name,
|
|
50621
|
+
app.accountId,
|
|
50622
|
+
app.parentId,
|
|
50623
|
+
app.defaultFolderId === app.id,
|
|
50624
|
+
app.shared,
|
|
50625
|
+
app.childFolderCount,
|
|
50626
|
+
app.noteCount,
|
|
50627
|
+
store.folderType,
|
|
50628
|
+
store.markedForDeletion,
|
|
50629
|
+
store.hasSmartQuery,
|
|
50630
|
+
store.sharedRecord,
|
|
50631
|
+
store.sharedAncestor,
|
|
50632
|
+
store.childFolderCount,
|
|
50633
|
+
store.noteCount
|
|
50634
|
+
];
|
|
50635
|
+
return `sha256:${createHash3("sha256").update(JSON.stringify(state)).digest("hex")}`;
|
|
50636
|
+
}
|
|
50637
|
+
function checkFolder(manager, args, deps) {
|
|
50638
|
+
const app = manager.readFolderForDelete(args.id);
|
|
50639
|
+
const store = deps.readStore(coreDataPk(args.id));
|
|
50640
|
+
if (!store)
|
|
50641
|
+
throw conflict(
|
|
50642
|
+
"the folder is not in the local Notes store yet; wait for Notes to save it and plan again"
|
|
50643
|
+
);
|
|
50644
|
+
if (store.markedForDeletion) throw conflict("the folder is already deleted");
|
|
50645
|
+
const expectedParentPk = app.parentId ? coreDataPk(app.parentId) : null;
|
|
50646
|
+
if (store.accountPk !== coreDataPk(app.accountId) || store.parentPk !== expectedParentPk)
|
|
50647
|
+
throw conflict(
|
|
50648
|
+
"Notes.app and the local store disagree about this folder's location; wait a moment and plan again"
|
|
50649
|
+
);
|
|
50650
|
+
if (app.name !== args.expectedName) throw conflict("the folder name changed");
|
|
50651
|
+
if (app.accountId !== args.expectedAccountId)
|
|
50652
|
+
throw conflict("the folder is in a different account");
|
|
50653
|
+
if (args.expectedRoot ? app.parentId !== null : app.parentId !== args.expectedParentId)
|
|
50654
|
+
throw conflict("the folder's parent changed");
|
|
50655
|
+
const refusal = folderDeleteRefusal(app, store);
|
|
50656
|
+
if (refusal) throw refused(refusal);
|
|
50657
|
+
let storeNotes = store.noteCount;
|
|
50658
|
+
if (storeNotes > app.noteCount && store.noteKeys.length === storeNotes) {
|
|
50659
|
+
const notePrefix = args.id.replace(/\/ICFolder\/p\d+$/, "/ICNote/p");
|
|
50660
|
+
storeNotes -= manager.countNotesOutsideFolder(
|
|
50661
|
+
args.id,
|
|
50662
|
+
store.noteKeys.map((key) => `${notePrefix}${key}`)
|
|
50663
|
+
);
|
|
50664
|
+
}
|
|
50665
|
+
const children = Math.max(app.childFolderCount, store.childFolderCount);
|
|
50666
|
+
const notes = Math.max(app.noteCount, storeNotes);
|
|
50667
|
+
if (children > 0 || notes > 0)
|
|
50668
|
+
throw refused(
|
|
50669
|
+
`the folder is not empty (${children} child folder(s), ${notes} note(s)); move or delete them first`
|
|
50670
|
+
);
|
|
50671
|
+
return {
|
|
50672
|
+
app,
|
|
50673
|
+
store,
|
|
50674
|
+
identifier: store.identifier,
|
|
50675
|
+
revision: folderDeleteRevision(app, { ...store, noteCount: storeNotes })
|
|
50676
|
+
};
|
|
50677
|
+
}
|
|
50678
|
+
function runFolderDelete(manager, args, deps = defaultDeps) {
|
|
50679
|
+
if (Boolean(args.expectedRoot) === (args.expectedParentId !== void 0))
|
|
50680
|
+
throw invalid("Pass exactly one of expectedParentId or expectedRoot: true");
|
|
50681
|
+
if (args.dryRun && args.expectedRevision)
|
|
50682
|
+
throw invalid("expectedRevision belongs to the apply call (dryRun: false)");
|
|
50683
|
+
if (!args.dryRun && !args.expectedRevision)
|
|
50684
|
+
throw invalid("Apply requires expectedRevision from a matching dry run");
|
|
50685
|
+
const checked = checkFolder(manager, args, deps);
|
|
50686
|
+
const base = {
|
|
50687
|
+
ok: true,
|
|
50688
|
+
id: args.id,
|
|
50689
|
+
identifier: checked.identifier,
|
|
50690
|
+
name: checked.app.name,
|
|
50691
|
+
accountId: checked.app.accountId,
|
|
50692
|
+
parentId: checked.app.parentId,
|
|
50693
|
+
folderType: 0,
|
|
50694
|
+
childFolderCount: 0,
|
|
50695
|
+
noteCount: 0,
|
|
50696
|
+
revision: checked.revision
|
|
50697
|
+
};
|
|
50698
|
+
if (args.dryRun)
|
|
50699
|
+
return { ...base, status: "planned", dryRun: true, committed: false, wouldDelete: true };
|
|
50700
|
+
if (checked.revision !== args.expectedRevision)
|
|
50701
|
+
throw conflict("the folder changed since the dry run; plan again");
|
|
50702
|
+
const outcome = manager.deleteEmptyFolderIfUnchanged(args.id, {
|
|
50703
|
+
name: checked.app.name,
|
|
50704
|
+
parentId: checked.app.parentId,
|
|
50705
|
+
accountId: checked.app.accountId
|
|
50706
|
+
});
|
|
50707
|
+
if (outcome.status === "conflict")
|
|
50708
|
+
throw conflict(`the folder ${outcome.reason} changed before deletion; plan again`);
|
|
50709
|
+
if (outcome.status === "refused") throw refused(outcome.reason);
|
|
50710
|
+
if (outcome.status === "failed")
|
|
50711
|
+
throw uncertain(
|
|
50712
|
+
`The delete outcome is uncertain (${outcome.reason}); read folder ${args.id} before retrying`
|
|
50713
|
+
);
|
|
50714
|
+
if (manager.folderExistsById(args.id))
|
|
50715
|
+
throw uncertain(
|
|
50716
|
+
`The delete outcome is uncertain: Notes.app still resolves folder ${args.id}; read it before retrying`
|
|
50717
|
+
);
|
|
50718
|
+
let storeTombstoned = false;
|
|
50719
|
+
for (let attempt = 0; attempt < 5 && !storeTombstoned; attempt++) {
|
|
50720
|
+
if (attempt > 0) deps.sleep(200);
|
|
50721
|
+
const after = deps.readStore(coreDataPk(args.id));
|
|
50722
|
+
storeTombstoned = !after || after.markedForDeletion;
|
|
50723
|
+
}
|
|
50724
|
+
return {
|
|
50725
|
+
...base,
|
|
50726
|
+
status: "deleted",
|
|
50727
|
+
dryRun: false,
|
|
50728
|
+
committed: true,
|
|
50729
|
+
wouldDelete: true,
|
|
50730
|
+
verified: true,
|
|
50731
|
+
storeTombstoned
|
|
50732
|
+
};
|
|
50733
|
+
}
|
|
50734
|
+
function registerFolderDelete(server2, manager, deps = defaultDeps) {
|
|
50735
|
+
const inputSchema = {
|
|
50736
|
+
id: folderIdSchema.describe(
|
|
50737
|
+
"Exact folder id (x-coredata://\u2026/ICFolder/pN) from list-folders, or the folder's Notes UUID or numeric key"
|
|
50738
|
+
),
|
|
50739
|
+
expectedName: external_exports.string().min(1).max(1e3).describe("Current folder name (not a path), matched case-sensitively"),
|
|
50740
|
+
expectedAccountId: accountIdSchema.describe(
|
|
50741
|
+
"Owning account id, from get-folder-by-id or list-accounts"
|
|
50742
|
+
),
|
|
50743
|
+
expectedParentId: folderIdSchema.optional().describe("Current parent folder id; omit and pass expectedRoot for a top-level folder"),
|
|
50744
|
+
expectedRoot: external_exports.literal(true).optional().describe("Pass true when the folder sits at the account root (no parent folder)"),
|
|
50745
|
+
dryRun: external_exports.boolean().describe("true plans and returns a revision; false applies with expectedRevision"),
|
|
50746
|
+
expectedRevision: revisionSchema.optional().describe("The revision returned by the dry run; required when dryRun is false")
|
|
50747
|
+
};
|
|
50748
|
+
server2.registerTool(
|
|
50749
|
+
"delete-folder-by-id",
|
|
50750
|
+
{
|
|
50751
|
+
description: "Use when: deleting one exact, empty, ordinary folder by id, with a plan-then-apply handshake.\nReturns: a plan (status planned, wouldDelete, identity fields, zero counts, revision) or, on apply, status deleted with verified readback.\nDo not use when: the folder holds notes or subfolders, or you only have a name or path (delete-folder).\nSafety: requires the exact id plus expectedName, expectedAccountId, and expectedParentId or expectedRoot. Call with dryRun true, then repeat the same guards with dryRun false and expectedRevision. Always refuses Recently Deleted, smart folders, the account's default and other system folders, shared folders, and non-empty folders; there is no override. Not atomic: the guard is a pre-check followed by an AppleScript delete. The Notes.app-visible checks repeat inside the delete script, but folder type is read from the local store just before it. Needs Full Disk Access and fails closed without it.",
|
|
50752
|
+
inputSchema,
|
|
50753
|
+
outputSchema: external_exports.object({ ok: external_exports.boolean().optional() }).passthrough(),
|
|
50754
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false }
|
|
50755
|
+
},
|
|
50756
|
+
(async (args) => {
|
|
50757
|
+
try {
|
|
50758
|
+
const result = runFolderDelete(manager, args, deps);
|
|
50759
|
+
return {
|
|
50760
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
50761
|
+
structuredContent: result
|
|
50762
|
+
};
|
|
50763
|
+
} catch (error2) {
|
|
50764
|
+
return errorResult(error2 instanceof Error ? error2.message : String(error2), error2);
|
|
50765
|
+
}
|
|
50766
|
+
})
|
|
50767
|
+
);
|
|
50768
|
+
}
|
|
50769
|
+
|
|
50209
50770
|
// src/tools/nativeTagsBridge.ts
|
|
50210
50771
|
var noteId2 = exactIdInput(
|
|
50211
50772
|
"ICNote",
|
|
@@ -50731,15 +51292,15 @@ var shortcutFiles = [
|
|
|
50731
51292
|
function setupShortcuts(checkOnly, dependencies = {}) {
|
|
50732
51293
|
const status = dependencies.status || nativeTagsStatus;
|
|
50733
51294
|
const exists = dependencies.exists || existsSync13;
|
|
50734
|
-
const open = dependencies.open || ((
|
|
50735
|
-
const result = spawnSync2("/usr/bin/open", [
|
|
51295
|
+
const open = dependencies.open || ((path10) => {
|
|
51296
|
+
const result = spawnSync2("/usr/bin/open", [path10], { encoding: "utf8" });
|
|
50736
51297
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
50737
51298
|
});
|
|
50738
51299
|
const baseDirectory = dependencies.baseDirectory || resolve4(dirname5(fileURLToPath(import.meta.url)), "../shortcuts");
|
|
50739
51300
|
const osRelease = (dependencies.osRelease || release2)();
|
|
50740
51301
|
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
50741
51302
|
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
50742
|
-
const
|
|
51303
|
+
const path10 = resolve4(baseDirectory, file);
|
|
50743
51304
|
let installed = false;
|
|
50744
51305
|
let identifier;
|
|
50745
51306
|
let error2;
|
|
@@ -50755,9 +51316,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
50755
51316
|
if (!installed && !checkOnly) {
|
|
50756
51317
|
if (optional2 && !(darwinMajor >= MARKDOWN_MIN_DARWIN_MAJOR))
|
|
50757
51318
|
skipped = `requires macOS 26 or later (this Mac reports Darwin ${osRelease})`;
|
|
50758
|
-
else if (!exists(
|
|
51319
|
+
else if (!exists(path10)) error2 = `Packaged Shortcut is missing: ${path10}`;
|
|
50759
51320
|
else {
|
|
50760
|
-
const result = open(
|
|
51321
|
+
const result = open(path10);
|
|
50761
51322
|
opened = result.ok;
|
|
50762
51323
|
if (!result.ok) error2 = result.error || `Could not open ${file}`;
|
|
50763
51324
|
}
|
|
@@ -50766,7 +51327,7 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
50766
51327
|
name,
|
|
50767
51328
|
installed,
|
|
50768
51329
|
identifier,
|
|
50769
|
-
file:
|
|
51330
|
+
file: path10,
|
|
50770
51331
|
opened,
|
|
50771
51332
|
...optional2 ? { optional: optional2 } : {},
|
|
50772
51333
|
...skipped ? { skipped } : {},
|
|
@@ -50813,6 +51374,7 @@ var server = new McpServer({
|
|
|
50813
51374
|
});
|
|
50814
51375
|
var notesManager = new AppleNotesManager();
|
|
50815
51376
|
registerDirectOperations(server, notesManager);
|
|
51377
|
+
registerFolderDelete(server, notesManager);
|
|
50816
51378
|
registerNativeTagsBridge(server, notesManager);
|
|
50817
51379
|
registerNativeOperations(server, notesManager);
|
|
50818
51380
|
function successResponse(message, structured) {
|
|
@@ -50879,6 +51441,36 @@ function noteIdentifiers(id2) {
|
|
|
50879
51441
|
var expectedContentHashInput = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/, "expectedContentHash must come from get-note-content").describe(
|
|
50880
51442
|
"Revision token returned by get-note-content for this exact ID. The mutation stops if the note changed since that read."
|
|
50881
51443
|
);
|
|
51444
|
+
var SCOPE_FOLDER_ID_MESSAGE = "An exact folder id is required: x-coredata://.../ICFolder/p..., Notes UUID, or numeric key (from list-folders)";
|
|
51445
|
+
var scopeFolderIdInput = exactIdInput("ICFolder", SCOPE_FOLDER_ID, SCOPE_FOLDER_ID_MESSAGE, {
|
|
51446
|
+
maxLength: MAX.ID
|
|
51447
|
+
});
|
|
51448
|
+
var scopeGuardInputs = {
|
|
51449
|
+
ifFolderId: scopeFolderIdInput.optional().describe(
|
|
51450
|
+
"Precondition: the note must currently be in exactly this folder (id from list-folders). Re-checked immediately before the write."
|
|
51451
|
+
),
|
|
51452
|
+
ifAncestorFolderId: scopeFolderIdInput.optional().describe(
|
|
51453
|
+
"Precondition: the note must be inside this folder or any of its subfolders. Re-checked immediately before the write."
|
|
51454
|
+
),
|
|
51455
|
+
forbiddenAncestorFolderIds: exactIdArrayInput(
|
|
51456
|
+
"ICFolder",
|
|
51457
|
+
SCOPE_FOLDER_ID,
|
|
51458
|
+
SCOPE_FOLDER_ID_MESSAGE,
|
|
51459
|
+
{
|
|
51460
|
+
maxLength: MAX.ID,
|
|
51461
|
+
maxItems: MAX_FORBIDDEN_FOLDERS
|
|
51462
|
+
}
|
|
51463
|
+
).optional().describe(
|
|
51464
|
+
"Precondition: the note must not be inside any of these folders or their subfolders (for move-note, neither may the destination). Re-checked immediately before the write."
|
|
51465
|
+
)
|
|
51466
|
+
};
|
|
51467
|
+
function scopeFrom(args) {
|
|
51468
|
+
return {
|
|
51469
|
+
ifFolderId: args.ifFolderId,
|
|
51470
|
+
ifAncestorFolderId: args.ifAncestorFolderId,
|
|
51471
|
+
forbiddenAncestorFolderIds: args.forbiddenAncestorFolderIds
|
|
51472
|
+
};
|
|
51473
|
+
}
|
|
50882
51474
|
var timeoutSecondsInput = external_exports.number().int().min(CALL_TIMEOUT_SECONDS.min).max(CALL_TIMEOUT_SECONDS.max).optional().describe(
|
|
50883
51475
|
`Per-call timeout in seconds (${CALL_TIMEOUT_SECONDS.min}-${CALL_TIMEOUT_SECONDS.max}) for each Notes.app automation step this call runs, overriding APPLE_NOTES_MCP_TIMEOUT_MS. A timed-out write is uncertain, not failed: read the note by id before any retry.`
|
|
50884
51476
|
);
|
|
@@ -51805,7 +52397,7 @@ ${lines.join("\n")}` : ".") + partial2,
|
|
|
51805
52397
|
registerTool(
|
|
51806
52398
|
"update-note",
|
|
51807
52399
|
{
|
|
51808
|
-
description: "Use when: replacing the body of one exact Apple Note after reading it by id.\nReturns: exact id, new content hash, and visible-text readback verification.\nDo not use when: you only have a title, the note changed since the read, or the note has attachments.\nSafety: requires the exact note id and expectedContentHash from get-note-content. The server checks rich metadata revision, atomically checks the AppleScript body, blocks native objects/checklists, and verifies actual link destinations after saving. Preserve returned HTML links unless allowLinkChanges is explicitly requested. Notes.app normalizes HTML, so rich formatting is not claimed as byte-identical.",
|
|
52400
|
+
description: "Use when: replacing the body of one exact Apple Note after reading it by id.\nReturns: exact id, new content hash, and visible-text readback verification.\nDo not use when: you only have a title, the note changed since the read, or the note has attachments.\nSafety: requires the exact note id and expectedContentHash from get-note-content. The server checks rich metadata revision, atomically checks the AppleScript body, blocks native objects/checklists, and verifies actual link destinations after saving. Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds are re-checked inside the same AppleScript as the write. Preserve returned HTML links unless allowLinkChanges is explicitly requested. Notes.app normalizes HTML, so rich formatting is not claimed as byte-identical.",
|
|
51809
52401
|
inputSchema: {
|
|
51810
52402
|
id: noteIdInput,
|
|
51811
52403
|
expectedContentHash: expectedContentHashInput,
|
|
@@ -51819,7 +52411,8 @@ registerTool(
|
|
|
51819
52411
|
"New note body. AppleScript cannot produce true Apple Notes checklists; checkbox inputs and `- [ ]` markdown do not render as checkable items. Use a plain list and convert in Notes.app with \u21E7\u2318L."
|
|
51820
52412
|
),
|
|
51821
52413
|
format: external_exports.enum(["plaintext", "html"]).optional().default("plaintext").describe("Content format: 'plaintext' (default) or 'html' for rich formatting"),
|
|
51822
|
-
timeoutSeconds: timeoutSecondsInput
|
|
52414
|
+
timeoutSeconds: timeoutSecondsInput,
|
|
52415
|
+
...scopeGuardInputs
|
|
51823
52416
|
},
|
|
51824
52417
|
outputSchema: {
|
|
51825
52418
|
ok: external_exports.boolean().optional(),
|
|
@@ -51838,7 +52431,8 @@ registerTool(
|
|
|
51838
52431
|
newTitle,
|
|
51839
52432
|
newContent,
|
|
51840
52433
|
format = "plaintext",
|
|
51841
|
-
allowLinkChanges = false
|
|
52434
|
+
allowLinkChanges = false,
|
|
52435
|
+
...scopeArgs
|
|
51842
52436
|
}) => {
|
|
51843
52437
|
const snapshot = readExactNoteSnapshot(id2);
|
|
51844
52438
|
if ("error" in snapshot) return errorResponse(snapshot.error);
|
|
@@ -51859,11 +52453,15 @@ registerTool(
|
|
|
51859
52453
|
newTitle,
|
|
51860
52454
|
newContent,
|
|
51861
52455
|
format,
|
|
51862
|
-
snapshot.rich.revision
|
|
52456
|
+
snapshot.rich.revision,
|
|
52457
|
+
scopeFrom(scopeArgs)
|
|
51863
52458
|
);
|
|
51864
52459
|
if (result.status === "conflict") {
|
|
51865
52460
|
return errorResponse(revisionConflictMessage(snapshot.note.title));
|
|
51866
52461
|
}
|
|
52462
|
+
if (result.status === "scope_conflict") {
|
|
52463
|
+
return errorResponse(scopeConflictMessage(result.reason));
|
|
52464
|
+
}
|
|
51867
52465
|
if (result.status === "attachments") {
|
|
51868
52466
|
return errorResponse(
|
|
51869
52467
|
`Note "${snapshot.note.title}" gained an attachment before saving. No content was replaced.`
|
|
@@ -51919,7 +52517,8 @@ function guardedAppend({
|
|
|
51919
52517
|
separator = "\n\n",
|
|
51920
52518
|
format = "plaintext",
|
|
51921
52519
|
scopeText,
|
|
51922
|
-
htmlSeparator
|
|
52520
|
+
htmlSeparator,
|
|
52521
|
+
...scopeArgs
|
|
51923
52522
|
}, onRoute) {
|
|
51924
52523
|
const contentToHtml = (text2) => {
|
|
51925
52524
|
if (format === "html") return text2;
|
|
@@ -51947,6 +52546,11 @@ function guardedAppend({
|
|
|
51947
52546
|
return errorResponse("Provide scopeText: a unique existing phrase for native append");
|
|
51948
52547
|
if (!VERIFIED_BACKGROUND.has("append-native"))
|
|
51949
52548
|
return errorResponse("Native append has not passed live validation; see get-capabilities");
|
|
52549
|
+
const nativeScope = scopeFrom(scopeArgs);
|
|
52550
|
+
if (hasScopeGuard(nativeScope)) {
|
|
52551
|
+
const reason = notesManager.checkNoteScope(id2, nativeScope);
|
|
52552
|
+
if (reason) return errorResponse(scopeConflictMessage(reason));
|
|
52553
|
+
}
|
|
51950
52554
|
onRoute?.("native");
|
|
51951
52555
|
const result2 = appendNative(notesManager, {
|
|
51952
52556
|
id: id2,
|
|
@@ -51978,11 +52582,15 @@ function guardedAppend({
|
|
|
51978
52582
|
void 0,
|
|
51979
52583
|
combinedBody,
|
|
51980
52584
|
"html",
|
|
51981
|
-
snapshot.rich.revision
|
|
52585
|
+
snapshot.rich.revision,
|
|
52586
|
+
scopeFrom(scopeArgs)
|
|
51982
52587
|
);
|
|
51983
52588
|
if (result.status === "conflict") {
|
|
51984
52589
|
return errorResponse(revisionConflictMessage(snapshot.note.title));
|
|
51985
52590
|
}
|
|
52591
|
+
if (result.status === "scope_conflict") {
|
|
52592
|
+
return errorResponse(scopeConflictMessage(result.reason));
|
|
52593
|
+
}
|
|
51986
52594
|
if (result.status === "attachments") {
|
|
51987
52595
|
return errorResponse(
|
|
51988
52596
|
`Note "${snapshot.note.title}" gained an attachment before saving. No content was appended.`
|
|
@@ -52023,7 +52631,7 @@ function guardedAppend({
|
|
|
52023
52631
|
registerTool(
|
|
52024
52632
|
"append-to-note",
|
|
52025
52633
|
{
|
|
52026
|
-
description: "Use when: adding content to one exact note after reading it by id.\nReturns: exact id, new content hash, and visible-text readback verification.\nDo not use when: you only have a title or the note changed since the read.\nSafety: protected native-object notes use native end-append with scopeText; ordinary notes retain guarded HTML editing. Notes.app normalizes HTML, so rich formatting is not claimed as byte-identical.\nNative-append HTML subset (protected notes only; ordinary notes accept any HTML Notes.app renders): " + NATIVE_APPEND_HTML_SUBSET + " Native append also requires scopeText, the default blank-line separator and position 'after'.",
|
|
52634
|
+
description: "Use when: adding content to one exact note after reading it by id.\nReturns: exact id, new content hash, and visible-text readback verification.\nDo not use when: you only have a title or the note changed since the read.\nSafety: protected native-object notes use native end-append with scopeText; ordinary notes retain guarded HTML editing. Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds are re-checked inside the write AppleScript for ordinary notes, and as a separate read just before a native append. Notes.app normalizes HTML, so rich formatting is not claimed as byte-identical.\nNative-append HTML subset (protected notes only; ordinary notes accept any HTML Notes.app renders): " + NATIVE_APPEND_HTML_SUBSET + " Native append also requires scopeText, the default blank-line separator and position 'after'.",
|
|
52027
52635
|
inputSchema: {
|
|
52028
52636
|
id: noteIdInput,
|
|
52029
52637
|
expectedContentHash: expectedContentHashInput,
|
|
@@ -52034,7 +52642,8 @@ registerTool(
|
|
|
52034
52642
|
),
|
|
52035
52643
|
separator: external_exports.string().max(20).optional().default("\n\n").describe("String placed between existing content and new content (default: two newlines)"),
|
|
52036
52644
|
format: external_exports.enum(["plaintext", "html"]).optional().default("plaintext").describe("Format of the content being appended: 'plaintext' (default) or 'html'"),
|
|
52037
|
-
timeoutSeconds: timeoutSecondsInput
|
|
52645
|
+
timeoutSeconds: timeoutSecondsInput,
|
|
52646
|
+
...scopeGuardInputs
|
|
52038
52647
|
},
|
|
52039
52648
|
outputSchema: {
|
|
52040
52649
|
ok: external_exports.boolean().optional(),
|
|
@@ -52120,11 +52729,12 @@ registerTool(
|
|
|
52120
52729
|
registerTool(
|
|
52121
52730
|
"delete-note",
|
|
52122
52731
|
{
|
|
52123
|
-
description: "Use when: moving one exact note to Recently Deleted after reading and reviewing it.\nReturns: confirmation with the exact id.\nDo not use when: you only have a title or the note changed since review.\nSafety: requires id and expectedContentHash from get-note-content. The body comparison and delete happen in one AppleScript, so a newer edit is preserved. Refuses a note already in Recently Deleted, where a delete would be permanent.",
|
|
52732
|
+
description: "Use when: moving one exact note to Recently Deleted after reading and reviewing it.\nReturns: confirmation with the exact id.\nDo not use when: you only have a title or the note changed since review.\nSafety: requires id and expectedContentHash from get-note-content. The body comparison and delete happen in one AppleScript, so a newer edit is preserved. Refuses a note already in Recently Deleted, where a delete would be permanent. Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds are re-checked inside that AppleScript too.",
|
|
52124
52733
|
inputSchema: {
|
|
52125
52734
|
id: noteIdInput,
|
|
52126
52735
|
expectedContentHash: expectedContentHashInput,
|
|
52127
|
-
timeoutSeconds: timeoutSecondsInput
|
|
52736
|
+
timeoutSeconds: timeoutSecondsInput,
|
|
52737
|
+
...scopeGuardInputs
|
|
52128
52738
|
},
|
|
52129
52739
|
outputSchema: {
|
|
52130
52740
|
ok: external_exports.boolean().optional(),
|
|
@@ -52134,16 +52744,19 @@ registerTool(
|
|
|
52134
52744
|
previousContentHash: external_exports.string().optional()
|
|
52135
52745
|
}
|
|
52136
52746
|
},
|
|
52137
|
-
withErrorHandling(({ id: id2, expectedContentHash }) => {
|
|
52747
|
+
withErrorHandling(({ id: id2, expectedContentHash, ...scopeArgs }) => {
|
|
52138
52748
|
const snapshot = readExactNoteSnapshot(id2);
|
|
52139
52749
|
if ("error" in snapshot) return errorResponse(snapshot.error);
|
|
52140
52750
|
if (snapshot.contentHash !== expectedContentHash) {
|
|
52141
52751
|
return errorResponse(revisionConflictMessage(snapshot.note.title));
|
|
52142
52752
|
}
|
|
52143
|
-
const result = notesManager.deleteNoteByIdIfUnchanged(id2, snapshot.body);
|
|
52753
|
+
const result = notesManager.deleteNoteByIdIfUnchanged(id2, snapshot.body, scopeFrom(scopeArgs));
|
|
52144
52754
|
if (result.status === "conflict") {
|
|
52145
52755
|
return errorResponse(revisionConflictMessage(snapshot.note.title));
|
|
52146
52756
|
}
|
|
52757
|
+
if (result.status === "scope_conflict") {
|
|
52758
|
+
return errorResponse(scopeConflictMessage(result.reason));
|
|
52759
|
+
}
|
|
52147
52760
|
if (result.status === "in-recently-deleted") {
|
|
52148
52761
|
return errorResponse(inRecentlyDeletedMessage(snapshot.note.title));
|
|
52149
52762
|
}
|
|
@@ -52171,12 +52784,13 @@ registerTool(
|
|
|
52171
52784
|
registerTool(
|
|
52172
52785
|
"move-note",
|
|
52173
52786
|
{
|
|
52174
|
-
description: "Use when: moving one exact note to a different folder by id.\nReturns: confirmation and exact-ID readback.\nDo not use when: you only have a title or want to move many notes (batch-move-notes).\nNote: Notes.app's native move preserves the note id, creation date, body, and attachments. The destination folder must already exist.",
|
|
52787
|
+
description: "Use when: moving one exact note to a different folder by id.\nReturns: confirmation and exact-ID readback.\nDo not use when: you only have a title or want to move many notes (batch-move-notes).\nNote: Notes.app's native move preserves the note id, creation date, body, and attachments. The destination folder must already exist. Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds (which also covers the destination) are re-checked inside the move AppleScript.",
|
|
52175
52788
|
inputSchema: {
|
|
52176
52789
|
id: noteIdInput,
|
|
52177
52790
|
folder: external_exports.string().min(1, "Destination folder is required").max(MAX.FOLDER),
|
|
52178
52791
|
account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account containing the note/folder"),
|
|
52179
|
-
timeoutSeconds: timeoutSecondsInput
|
|
52792
|
+
timeoutSeconds: timeoutSecondsInput,
|
|
52793
|
+
...scopeGuardInputs
|
|
52180
52794
|
},
|
|
52181
52795
|
outputSchema: {
|
|
52182
52796
|
ok: external_exports.boolean().optional(),
|
|
@@ -52186,12 +52800,12 @@ registerTool(
|
|
|
52186
52800
|
verified: external_exports.boolean().optional()
|
|
52187
52801
|
}
|
|
52188
52802
|
},
|
|
52189
|
-
withErrorHandling(({ id: id2, folder, account }) => {
|
|
52803
|
+
withErrorHandling(({ id: id2, folder, account, ...scopeArgs }) => {
|
|
52190
52804
|
const note = notesManager.getNoteById(id2);
|
|
52191
52805
|
if (!note) {
|
|
52192
52806
|
return errorResponse(`Note with ID "${id2}" not found`);
|
|
52193
52807
|
}
|
|
52194
|
-
const success = notesManager.moveNoteById(id2, folder, account);
|
|
52808
|
+
const success = notesManager.moveNoteById(id2, folder, account, scopeFrom(scopeArgs));
|
|
52195
52809
|
if (!success) {
|
|
52196
52810
|
return errorResponse(
|
|
52197
52811
|
`Failed to move note "${note.title}" to folder "${folder}". Folder may not exist.`
|