apple-notes-mcp 2.8.44 → 2.8.46
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 +56 -5
- package/build/index.js +660 -130
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1174,15 +1174,18 @@ Creates a new folder, including a whole nested hierarchy in one call.
|
|
|
1174
1174
|
|
|
1175
1175
|
**Returns:** Confirmation message. The call is **idempotent** — an already-existing folder (or path segment) is skipped rather than treated as an error, so it is safe to call before every `create-note` that targets a folder.
|
|
1176
1176
|
|
|
1177
|
+
Existence is decided by folder id, not by name: a folder deleted earlier in the same Notes session no longer counts as existing, and the call succeeds only once the created folder is confirmed by its id. Otherwise it reports failure.
|
|
1178
|
+
|
|
1177
1179
|
---
|
|
1178
1180
|
|
|
1179
1181
|
#### `get-folder-by-id`
|
|
1180
1182
|
|
|
1181
|
-
Reads one exact folder's current name
|
|
1182
|
-
`
|
|
1183
|
-
`
|
|
1184
|
-
|
|
1185
|
-
|
|
1183
|
+
Reads one exact folder's current name, parent ID, and account ID (`accountId`,
|
|
1184
|
+
plus `isRoot`, true when the folder sits at the account root). Use these values
|
|
1185
|
+
with `rename-folder` or `delete-folder-by-id`; this avoids relying on ambiguous
|
|
1186
|
+
folder names or paths. The `id` may also be the folder's Notes UUID or numeric
|
|
1187
|
+
key, and the result adds `identifier`, `parentIdentifier`, and
|
|
1188
|
+
`accountIdentifier` when Full Disk Access is granted.
|
|
1186
1189
|
|
|
1187
1190
|
---
|
|
1188
1191
|
|
|
@@ -1194,6 +1197,54 @@ and descendants. It refuses stale metadata and a conflicting sibling name.
|
|
|
1194
1197
|
|
|
1195
1198
|
---
|
|
1196
1199
|
|
|
1200
|
+
#### `delete-folder-by-id`
|
|
1201
|
+
|
|
1202
|
+
Deletes one exact, empty, ordinary folder through a plan-then-apply handshake.
|
|
1203
|
+
Read the folder first with `get-folder-by-id`, call once with `dryRun: true`,
|
|
1204
|
+
then repeat the same guards with `dryRun: false` and the returned `revision`.
|
|
1205
|
+
|
|
1206
|
+
| Parameter | Type | Required | Description |
|
|
1207
|
+
|-----------|------|----------|-------------|
|
|
1208
|
+
| `id` | string | Yes | Exact folder id (`x-coredata://…/ICFolder/pN`), or the folder's Notes UUID or numeric key |
|
|
1209
|
+
| `expectedName` | string | Yes | Current folder name (not a path), matched case-sensitively |
|
|
1210
|
+
| `expectedAccountId` | string | Yes | Owning account id (`x-coredata://…/ICAccount/pN`) |
|
|
1211
|
+
| `expectedParentId` | string | One of these two | Current parent folder id (same forms as `id`) |
|
|
1212
|
+
| `expectedRoot` | `true` | One of these two | The folder sits at the account root |
|
|
1213
|
+
| `dryRun` | boolean | Yes | `true` plans; `false` applies |
|
|
1214
|
+
| `expectedRevision` | string | On apply | The `revision` from the dry run |
|
|
1215
|
+
|
|
1216
|
+
**Example (plan, then apply with the returned revision):**
|
|
1217
|
+
```json
|
|
1218
|
+
{
|
|
1219
|
+
"id": "x-coredata://ABC/ICFolder/p42",
|
|
1220
|
+
"expectedName": "Old Projects",
|
|
1221
|
+
"expectedAccountId": "x-coredata://ABC/ICAccount/p3",
|
|
1222
|
+
"expectedRoot": true,
|
|
1223
|
+
"dryRun": true
|
|
1224
|
+
}
|
|
1225
|
+
```
|
|
1226
|
+
|
|
1227
|
+
**Returns:** `status: "planned"` with `wouldDelete`, `identifier`, `name`,
|
|
1228
|
+
`accountId`, `parentId`, `folderType: 0`, zero `childFolderCount` and
|
|
1229
|
+
`noteCount`, and `revision`; on apply, `status: "deleted"`, `committed: true`,
|
|
1230
|
+
`verified: true` (Notes.app no longer resolves the id), and `storeTombstoned`.
|
|
1231
|
+
|
|
1232
|
+
**Safety:** it always refuses Recently Deleted, smart folders, the account's
|
|
1233
|
+
default and other system folders, shared folders (or folders inside a shared
|
|
1234
|
+
folder), and any folder that still holds notes or subfolders. There is no
|
|
1235
|
+
override. It is **not atomic**: the guard is a pre-check followed by an
|
|
1236
|
+
AppleScript delete. The name, parent, account, sharing, and emptiness checks
|
|
1237
|
+
repeat inside the delete script, but the folder type and stable identifier come
|
|
1238
|
+
from the local Notes database just before it. It needs Full Disk Access and
|
|
1239
|
+
fails closed without it.
|
|
1240
|
+
|
|
1241
|
+
**Errors** carry the standard `code`: `revision_conflict` (a `Conflict:` message;
|
|
1242
|
+
read and plan again), `unsupported` (a `Refused:` message), `verification_failed`
|
|
1243
|
+
with `indeterminate: true` (read the folder before any retry), and
|
|
1244
|
+
`full_disk_access_missing`.
|
|
1245
|
+
|
|
1246
|
+
---
|
|
1247
|
+
|
|
1197
1248
|
#### `delete-folder`
|
|
1198
1249
|
|
|
1199
1250
|
Deletes a folder.
|
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;
|
|
@@ -42321,6 +42321,58 @@ function buildFolderReference(folderPath) {
|
|
|
42321
42321
|
return parts.reverse().map((part) => `folder "${escapePlainStringForAppleScript(part)}"`).join(" of ");
|
|
42322
42322
|
}
|
|
42323
42323
|
var AS_ACCOUNT_REF = "__acctRef";
|
|
42324
|
+
var LIVE_FOLDER_NOT_FOUND = "Folder not found";
|
|
42325
|
+
function buildLiveFolderResolution(folderPath, varName, opts = {}) {
|
|
42326
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(varName)) throw new Error("Invalid AppleScript variable");
|
|
42327
|
+
buildFolderReference(folderPath);
|
|
42328
|
+
const parts = splitFolderPath(folderPath);
|
|
42329
|
+
const c = `${varName}_c`;
|
|
42330
|
+
const cid = `${varName}_cid`;
|
|
42331
|
+
const any = `${varName}_any`;
|
|
42332
|
+
const parent = `${varName}_p`;
|
|
42333
|
+
const notFound = `error "${LIVE_FOLDER_NOT_FOUND}: ${escapePlainStringForAppleScript(folderPath)}" number -1728`;
|
|
42334
|
+
const lines = [];
|
|
42335
|
+
parts.forEach((part, i) => {
|
|
42336
|
+
const name = escapePlainStringForAppleScript(part);
|
|
42337
|
+
if (i === 0) {
|
|
42338
|
+
lines.push(
|
|
42339
|
+
`set ${varName} to missing value`,
|
|
42340
|
+
`set ${any} to missing value`,
|
|
42341
|
+
`repeat with ${c} in (folders of ${AS_ACCOUNT_REF} whose name is "${name}")`,
|
|
42342
|
+
` try`,
|
|
42343
|
+
` set ${cid} to id of ${c}`,
|
|
42344
|
+
` if exists folder id ${cid} then`,
|
|
42345
|
+
` if class of (container of ${c}) is not folder then`,
|
|
42346
|
+
` set ${varName} to folder id ${cid}`,
|
|
42347
|
+
` exit repeat`,
|
|
42348
|
+
` else if ${any} is missing value then`,
|
|
42349
|
+
` set ${any} to folder id ${cid}`,
|
|
42350
|
+
` end if`,
|
|
42351
|
+
` end if`,
|
|
42352
|
+
` end try`,
|
|
42353
|
+
`end repeat`
|
|
42354
|
+
);
|
|
42355
|
+
if (!opts.rootOnly)
|
|
42356
|
+
lines.push(`if ${varName} is missing value then set ${varName} to ${any}`);
|
|
42357
|
+
} else {
|
|
42358
|
+
lines.push(
|
|
42359
|
+
`set ${parent} to ${varName}`,
|
|
42360
|
+
`set ${varName} to missing value`,
|
|
42361
|
+
`repeat with ${c} in (folders of ${parent} whose name is "${name}")`,
|
|
42362
|
+
` try`,
|
|
42363
|
+
` set ${cid} to id of ${c}`,
|
|
42364
|
+
` if exists folder id ${cid} then`,
|
|
42365
|
+
` set ${varName} to folder id ${cid}`,
|
|
42366
|
+
` exit repeat`,
|
|
42367
|
+
` end if`,
|
|
42368
|
+
` end try`,
|
|
42369
|
+
`end repeat`
|
|
42370
|
+
);
|
|
42371
|
+
}
|
|
42372
|
+
lines.push(`if ${varName} is missing value then ${notFound}`);
|
|
42373
|
+
});
|
|
42374
|
+
return lines.join("\n");
|
|
42375
|
+
}
|
|
42324
42376
|
var ACCOUNT_RESOLUTION_ERROR = "AccountResolutionError:";
|
|
42325
42377
|
function throwIfAccountResolutionFailed(error2) {
|
|
42326
42378
|
if (!error2) return;
|
|
@@ -42373,6 +42425,34 @@ function buildAppLevelScript(command) {
|
|
|
42373
42425
|
end tell
|
|
42374
42426
|
`;
|
|
42375
42427
|
}
|
|
42428
|
+
function folderDeleteFactsScript(id2) {
|
|
42429
|
+
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
42430
|
+
return `
|
|
42431
|
+
set f to folder id "${id2}"
|
|
42432
|
+
set folderName to name of f
|
|
42433
|
+
set parentRef to container of f
|
|
42434
|
+
set parentId to ""
|
|
42435
|
+
if class of parentRef is folder then set parentId to id of parentRef
|
|
42436
|
+
set sharedAny to (shared of f)
|
|
42437
|
+
set acct to parentRef
|
|
42438
|
+
repeat while class of acct is folder
|
|
42439
|
+
if shared of acct then set sharedAny to true
|
|
42440
|
+
set acct to container of acct
|
|
42441
|
+
end repeat
|
|
42442
|
+
set acctId to id of acct
|
|
42443
|
+
set defaultId to ""
|
|
42444
|
+
try
|
|
42445
|
+
set defaultId to id of default folder of acct
|
|
42446
|
+
end try
|
|
42447
|
+
-- "folders of f" keeps listing a child deleted earlier in this Notes
|
|
42448
|
+
-- session, while "exists folder id" reports it gone, so count only
|
|
42449
|
+
-- children that still exist.
|
|
42450
|
+
set childCount to 0
|
|
42451
|
+
repeat with childRef in folders of f
|
|
42452
|
+
if exists folder id (id of childRef) then set childCount to childCount + 1
|
|
42453
|
+
end repeat
|
|
42454
|
+
set noteCount to count of notes of f`;
|
|
42455
|
+
}
|
|
42376
42456
|
function getNoteLinkFromDB(coreDataId2) {
|
|
42377
42457
|
const match = coreDataId2.match(/\/p(\d+)$/);
|
|
42378
42458
|
if (!match) return null;
|
|
@@ -42522,8 +42602,8 @@ var AppleNotesManager = class {
|
|
|
42522
42602
|
const safeBody = escapeHtmlForAppleScript(`<h1>${htmlTitle}</h1>${bodyContent}`);
|
|
42523
42603
|
let createCommand;
|
|
42524
42604
|
if (folder) {
|
|
42525
|
-
|
|
42526
|
-
|
|
42605
|
+
createCommand = `${buildLiveFolderResolution(folder, "__folder")}
|
|
42606
|
+
make new note at __folder with properties {body:"${safeBody}"}`;
|
|
42527
42607
|
} else {
|
|
42528
42608
|
createCommand = `
|
|
42529
42609
|
set newNote to make new note with properties {body:"${safeBody}"}
|
|
@@ -43402,18 +43482,134 @@ var AppleNotesManager = class {
|
|
|
43402
43482
|
);
|
|
43403
43483
|
return { id: id2, name: newName, parentId: expectedParentId };
|
|
43404
43484
|
}
|
|
43405
|
-
/** Read the exact name and
|
|
43485
|
+
/** Read the exact name, parent, and account identity used by guarded folder writes. */
|
|
43406
43486
|
getFolderById(id2) {
|
|
43407
43487
|
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICFolder\/p\d+$/i.test(id2))
|
|
43408
43488
|
throw new Error("An exact folder ID is required");
|
|
43409
43489
|
const result = executeAppleScript(`tell application "Notes"
|
|
43410
43490
|
set f to folder id "${id2}"
|
|
43411
|
-
|
|
43491
|
+
set acct to container of f
|
|
43492
|
+
repeat while class of acct is folder
|
|
43493
|
+
set acct to container of acct
|
|
43494
|
+
end repeat
|
|
43495
|
+
return (name of f) & ${AS_FIELD_SEP} & (id of container of f) & ${AS_FIELD_SEP} & (id of acct)
|
|
43412
43496
|
end tell`);
|
|
43413
43497
|
if (!result.success) throw new Error(result.error || "Folder not found");
|
|
43414
|
-
const [name, parentId] = result.output.replace(/\n$/, "").split(FIELD_SEP);
|
|
43498
|
+
const [name, parentId, accountId] = result.output.replace(/\n$/, "").split(FIELD_SEP);
|
|
43415
43499
|
if (!name || !parentId) throw new Error("Incomplete folder metadata");
|
|
43416
|
-
return { id: id2, name, parentId };
|
|
43500
|
+
return accountId ? { id: id2, name, parentId, accountId, isRoot: parentId === accountId } : { id: id2, name, parentId };
|
|
43501
|
+
}
|
|
43502
|
+
/**
|
|
43503
|
+
* Reads, in one AppleScript, everything Notes.app itself knows about a folder
|
|
43504
|
+
* that guarded deletion checks: name, parent (empty at an account root),
|
|
43505
|
+
* account, the account's default folder, shared state of the folder or any
|
|
43506
|
+
* ancestor, and the counts of direct child folders and notes.
|
|
43507
|
+
*/
|
|
43508
|
+
readFolderForDelete(id2) {
|
|
43509
|
+
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
43510
|
+
const result = executeAppleScript(
|
|
43511
|
+
buildAppLevelScript(`${folderDeleteFactsScript(id2)}
|
|
43512
|
+
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)`)
|
|
43513
|
+
);
|
|
43514
|
+
if (!result.success) throw new Error(result.error || "Folder not found");
|
|
43515
|
+
const parts = result.output.replace(/\n$/, "").split(FIELD_SEP);
|
|
43516
|
+
if (parts.length !== 7 || !parts[0] || !parts[2]) throw new Error("Incomplete folder metadata");
|
|
43517
|
+
const count = (value) => {
|
|
43518
|
+
const parsed = Number(value.trim());
|
|
43519
|
+
if (!Number.isInteger(parsed) || parsed < 0) throw new Error("Incomplete folder metadata");
|
|
43520
|
+
return parsed;
|
|
43521
|
+
};
|
|
43522
|
+
return {
|
|
43523
|
+
id: id2,
|
|
43524
|
+
name: parts[0],
|
|
43525
|
+
parentId: parts[1] || null,
|
|
43526
|
+
accountId: parts[2],
|
|
43527
|
+
defaultFolderId: parts[3] || null,
|
|
43528
|
+
shared: parts[4].trim() === "true",
|
|
43529
|
+
childFolderCount: count(parts[5]),
|
|
43530
|
+
noteCount: count(parts[6])
|
|
43531
|
+
};
|
|
43532
|
+
}
|
|
43533
|
+
/**
|
|
43534
|
+
* Deletes one empty folder by exact id, repeating every Notes.app-visible
|
|
43535
|
+
* guard inside the same AppleScript as the `delete` command: exact name
|
|
43536
|
+
* (case-sensitive), parent or account root, account, not the default folder,
|
|
43537
|
+
* not shared (itself or an ancestor), no child folders, no notes.
|
|
43538
|
+
*
|
|
43539
|
+
* Store-only facts (folder type, stable identifier) are checked by the caller
|
|
43540
|
+
* before this runs, so the whole guard is a pre-check followed by an
|
|
43541
|
+
* AppleScript delete, not one atomic transaction.
|
|
43542
|
+
*/
|
|
43543
|
+
deleteEmptyFolderIfUnchanged(id2, expected) {
|
|
43544
|
+
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
43545
|
+
const literal2 = (value) => `"${escapePlainStringForAppleScript(value)}"`;
|
|
43546
|
+
const script = buildAppLevelScript(`${folderDeleteFactsScript(id2)}
|
|
43547
|
+
considering case
|
|
43548
|
+
if folderName is not ${literal2(expected.name)} then return "SAFETY_CONFLICT:name"
|
|
43549
|
+
end considering
|
|
43550
|
+
if parentId is not ${literal2(expected.parentId ?? "")} then return "SAFETY_CONFLICT:parent"
|
|
43551
|
+
if acctId is not ${literal2(expected.accountId)} then return "SAFETY_CONFLICT:account"
|
|
43552
|
+
if defaultId is ${literal2(id2)} then return "SAFETY_REFUSED:default folder"
|
|
43553
|
+
if sharedAny then return "SAFETY_REFUSED:shared folder"
|
|
43554
|
+
if childCount is not 0 then return "SAFETY_REFUSED:folder has child folders"
|
|
43555
|
+
if noteCount is not 0 then return "SAFETY_REFUSED:folder has notes"
|
|
43556
|
+
delete f
|
|
43557
|
+
return "SAFETY_DELETED"`);
|
|
43558
|
+
const result = executeMutationAppleScript(script);
|
|
43559
|
+
if (!result.success) {
|
|
43560
|
+
console.error(`Failed guarded folder delete for "${id2}":`, result.error);
|
|
43561
|
+
return { status: "failed", reason: result.error || "AppleScript failed" };
|
|
43562
|
+
}
|
|
43563
|
+
const output = result.output.trim();
|
|
43564
|
+
const colon = output.indexOf(":");
|
|
43565
|
+
const status = colon === -1 ? output : output.slice(0, colon);
|
|
43566
|
+
const reason = colon === -1 ? "" : output.slice(colon + 1);
|
|
43567
|
+
if (status === "SAFETY_DELETED") return { status: "deleted" };
|
|
43568
|
+
if (status === "SAFETY_CONFLICT") return { status: "conflict", reason };
|
|
43569
|
+
if (status === "SAFETY_REFUSED") return { status: "refused", reason };
|
|
43570
|
+
return { status: "failed", reason: "Unexpected AppleScript result" };
|
|
43571
|
+
}
|
|
43572
|
+
/**
|
|
43573
|
+
* Counts how many of the given notes Notes.app currently places outside the
|
|
43574
|
+
* folder: in another folder, or in Recently Deleted (a note trashed this
|
|
43575
|
+
* session reports a non-folder container). A note Notes.app cannot resolve
|
|
43576
|
+
* is not counted, so the caller keeps treating it as present.
|
|
43577
|
+
*
|
|
43578
|
+
* The local store can keep a just-trashed note in its old folder for
|
|
43579
|
+
* minutes; this lets guarded folder deletion discount such notes using
|
|
43580
|
+
* Notes.app's live view instead of refusing until the store catches up.
|
|
43581
|
+
*/
|
|
43582
|
+
countNotesOutsideFolder(folderId2, noteIds) {
|
|
43583
|
+
if (!FOLDER_ID_PATTERN.test(folderId2)) throw new Error("An exact folder ID is required");
|
|
43584
|
+
if (noteIds.length === 0) return 0;
|
|
43585
|
+
const ids = noteIds.map((noteId3) => `"${sanitizeNoteId(noteId3)}"`).join(", ");
|
|
43586
|
+
const result = executeAppleScript(
|
|
43587
|
+
buildAppLevelScript(`
|
|
43588
|
+
set movedCount to 0
|
|
43589
|
+
repeat with noteIdRef in {${ids}}
|
|
43590
|
+
try
|
|
43591
|
+
set c to container of note id (contents of noteIdRef)
|
|
43592
|
+
if class of c is not folder then
|
|
43593
|
+
set movedCount to movedCount + 1
|
|
43594
|
+
else if (id of c) is not "${folderId2}" then
|
|
43595
|
+
set movedCount to movedCount + 1
|
|
43596
|
+
end if
|
|
43597
|
+
end try
|
|
43598
|
+
end repeat
|
|
43599
|
+
return movedCount as text`)
|
|
43600
|
+
);
|
|
43601
|
+
if (!result.success) return 0;
|
|
43602
|
+
const moved = Number(result.output.trim());
|
|
43603
|
+
return Number.isInteger(moved) && moved >= 0 && moved <= noteIds.length ? moved : 0;
|
|
43604
|
+
}
|
|
43605
|
+
/** Whether Notes.app still resolves an exact folder id. */
|
|
43606
|
+
folderExistsById(id2) {
|
|
43607
|
+
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
43608
|
+
const result = executeAppleScript(
|
|
43609
|
+
buildAppLevelScript(`return (exists folder id "${id2}") as text`)
|
|
43610
|
+
);
|
|
43611
|
+
if (!result.success) throw new Error(result.error || "Could not check folder existence");
|
|
43612
|
+
return result.output.trim() === "true";
|
|
43417
43613
|
}
|
|
43418
43614
|
/** Insert one file into an unchanged exact note and return Notes' attachment ID. */
|
|
43419
43615
|
addAttachmentById(id2, expectedBody, filePath) {
|
|
@@ -43519,10 +43715,10 @@ var AppleNotesManager = class {
|
|
|
43519
43715
|
}
|
|
43520
43716
|
for (let i = 0; i < parts.length; i++) {
|
|
43521
43717
|
const currentPath = parts.slice(0, i + 1).map((p) => escapeFolderName(p)).join("/");
|
|
43522
|
-
const currentRef = buildFolderReference(currentPath);
|
|
43523
43718
|
const checkScript = buildAccountScopedScript(
|
|
43524
43719
|
{ account: targetAccount },
|
|
43525
|
-
|
|
43720
|
+
`${buildLiveFolderResolution(currentPath, "__folder", { rootOnly: true })}
|
|
43721
|
+
return id of __folder`
|
|
43526
43722
|
);
|
|
43527
43723
|
const checkResult = executeAppleScript(checkScript);
|
|
43528
43724
|
if (checkResult.success) {
|
|
@@ -43534,8 +43730,8 @@ var AppleNotesManager = class {
|
|
|
43534
43730
|
createCommand = `make new folder with properties {name:"${segmentName}"}`;
|
|
43535
43731
|
} else {
|
|
43536
43732
|
const parentPath = parts.slice(0, i).map((p) => escapeFolderName(p)).join("/");
|
|
43537
|
-
|
|
43538
|
-
|
|
43733
|
+
createCommand = `${buildLiveFolderResolution(parentPath, "__parent", { rootOnly: true })}
|
|
43734
|
+
make new folder at __parent with properties {name:"${segmentName}"}`;
|
|
43539
43735
|
}
|
|
43540
43736
|
const script = buildAccountScopedScript({ account: targetAccount }, createCommand);
|
|
43541
43737
|
const result = executeMutationAppleScript(script);
|
|
@@ -43545,14 +43741,23 @@ var AppleNotesManager = class {
|
|
|
43545
43741
|
return null;
|
|
43546
43742
|
}
|
|
43547
43743
|
}
|
|
43548
|
-
const fullRef = buildFolderReference(name);
|
|
43549
43744
|
const idScript = buildAccountScopedScript(
|
|
43550
43745
|
{ account: targetAccount },
|
|
43551
|
-
|
|
43746
|
+
`${buildLiveFolderResolution(name, "__folder", { rootOnly: true })}
|
|
43747
|
+
return (id of __folder) & ${AS_FIELD_SEP} & (name of it)`
|
|
43552
43748
|
);
|
|
43553
43749
|
const idResult = executeAppleScript(idScript);
|
|
43554
43750
|
const [rawId = "", rawAccount = ""] = idResult.success ? idResult.output.split(FIELD_SEP) : [];
|
|
43555
|
-
const
|
|
43751
|
+
const bareId = rawId.trim();
|
|
43752
|
+
const folderId2 = !idResult.success ? "" : FOLDER_ID_PATTERN.test(bareId) ? bareId : extractCoreDataId(rawId, "folder");
|
|
43753
|
+
if (!folderId2) {
|
|
43754
|
+
throwIfAccountResolutionFailed(idResult.error);
|
|
43755
|
+
console.error(
|
|
43756
|
+
`Folder "${name}" could not be confirmed after creation:`,
|
|
43757
|
+
idResult.error ?? "no folder id returned"
|
|
43758
|
+
);
|
|
43759
|
+
return null;
|
|
43760
|
+
}
|
|
43556
43761
|
return {
|
|
43557
43762
|
id: folderId2,
|
|
43558
43763
|
name,
|
|
@@ -43570,7 +43775,8 @@ var AppleNotesManager = class {
|
|
|
43570
43775
|
*/
|
|
43571
43776
|
deleteFolder(name, account) {
|
|
43572
43777
|
const targetAccount = this.resolveAccount(account);
|
|
43573
|
-
const deleteCommand =
|
|
43778
|
+
const deleteCommand = `${buildLiveFolderResolution(name, "__folder")}
|
|
43779
|
+
delete __folder`;
|
|
43574
43780
|
const script = buildAccountScopedScript({ account: targetAccount }, deleteCommand);
|
|
43575
43781
|
const result = executeMutationAppleScript(script);
|
|
43576
43782
|
if (!result.success) {
|
|
@@ -43596,10 +43802,10 @@ var AppleNotesManager = class {
|
|
|
43596
43802
|
moveNoteById(id2, destinationFolder, account, scope2) {
|
|
43597
43803
|
const targetAccount = this.resolveAccount(account);
|
|
43598
43804
|
const safeId = sanitizeNoteId(id2);
|
|
43599
|
-
const
|
|
43805
|
+
const destFolderSetup = buildLiveFolderResolution(destinationFolder, "destFolder");
|
|
43600
43806
|
const moveCommand = `
|
|
43601
43807
|
${buildAccountResolution(targetAccount)}
|
|
43602
|
-
|
|
43808
|
+
${destFolderSetup}
|
|
43603
43809
|
set noteRef to note id "${safeId}"${buildScopeGuardScript("noteRef", scope2, "destFolder")}
|
|
43604
43810
|
move noteRef to destFolder
|
|
43605
43811
|
set movedNoteRef to note id "${safeId}"
|
|
@@ -44505,7 +44711,7 @@ var AppleNotesManager = class {
|
|
|
44505
44711
|
batchMoveNotes(ids, folder, account) {
|
|
44506
44712
|
if (ids.length === 0) return [];
|
|
44507
44713
|
const targetAccount = this.resolveAccount(account);
|
|
44508
|
-
const
|
|
44714
|
+
const destFolderSetup = buildLiveFolderResolution(folder, "destFolder");
|
|
44509
44715
|
const results = new Array(ids.length);
|
|
44510
44716
|
const runnable = [];
|
|
44511
44717
|
ids.forEach((id2, i) => {
|
|
@@ -44523,7 +44729,7 @@ var AppleNotesManager = class {
|
|
|
44523
44729
|
const idList2 = runnable.map((r) => `"${r.safe}"`).join(", ");
|
|
44524
44730
|
const script = buildAppLevelScript(`
|
|
44525
44731
|
${buildAccountResolution(targetAccount)}
|
|
44526
|
-
|
|
44732
|
+
${destFolderSetup}
|
|
44527
44733
|
set out to ""
|
|
44528
44734
|
repeat with rawId in {${idList2}}
|
|
44529
44735
|
set theId to (rawId as text)
|
|
@@ -46401,12 +46607,12 @@ function resolveFolders(rows) {
|
|
|
46401
46607
|
seen.add(pk);
|
|
46402
46608
|
const parent = row.parent !== null ? resolve5(row.parent, seen) : void 0;
|
|
46403
46609
|
const name = row.name ?? "";
|
|
46404
|
-
const
|
|
46610
|
+
const path10 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
|
|
46405
46611
|
const plainPath = parent ? `${parent.plainPath}/${name}` : name;
|
|
46406
46612
|
const info = {
|
|
46407
|
-
path:
|
|
46613
|
+
path: path10,
|
|
46408
46614
|
plainPath,
|
|
46409
|
-
keys: [...new Set([name, plainPath,
|
|
46615
|
+
keys: [...new Set([name, plainPath, path10].map(normalizeForMatch))],
|
|
46410
46616
|
accountPk: row.owner ?? parent?.accountPk ?? null,
|
|
46411
46617
|
shared: Boolean(row.shared) || Boolean(parent?.shared)
|
|
46412
46618
|
};
|
|
@@ -46747,9 +46953,9 @@ function runNativeTagsShortcut(input) {
|
|
|
46747
46953
|
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
46748
46954
|
const directory = mkdtempSync2(join15(tmpdir2(), "apple-notes-native-tags-"));
|
|
46749
46955
|
try {
|
|
46750
|
-
const
|
|
46751
|
-
writeFileSync(
|
|
46752
|
-
execFileSync14("/usr/bin/shortcuts", ["run", status.identifier, "--input-path",
|
|
46956
|
+
const path10 = join15(directory, "request.json");
|
|
46957
|
+
writeFileSync(path10, JSON.stringify(input), { mode: 384 });
|
|
46958
|
+
execFileSync14("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path10], {
|
|
46753
46959
|
encoding: "utf8",
|
|
46754
46960
|
timeout: 6e4,
|
|
46755
46961
|
maxBuffer: 1024 * 1024,
|
|
@@ -47353,7 +47559,7 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
|
|
|
47353
47559
|
throw new Error(
|
|
47354
47560
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
47355
47561
|
);
|
|
47356
|
-
const segments = (
|
|
47562
|
+
const segments = (path10) => JSON.stringify(splitFolderPath(path10).map((part) => part.toLocaleLowerCase()));
|
|
47357
47563
|
if (request.folder) {
|
|
47358
47564
|
const wanted = segments(request.folder);
|
|
47359
47565
|
if (!manager.listAccounts().some(
|
|
@@ -47683,9 +47889,9 @@ function getCapabilityMatrix(probe = probeCapabilityEnvironment) {
|
|
|
47683
47889
|
return evaluateFeatures(probe());
|
|
47684
47890
|
}
|
|
47685
47891
|
function formatCapabilityMatrix(matrix) {
|
|
47686
|
-
const
|
|
47892
|
+
const os10 = matrix.runtimeOS;
|
|
47687
47893
|
const lines = [
|
|
47688
|
-
`Feature matrix (macOS ${
|
|
47894
|
+
`Feature matrix (macOS ${os10.macOSVersion ?? "unknown"}, Darwin ${os10.darwinRelease}):`
|
|
47689
47895
|
];
|
|
47690
47896
|
for (const [name, status] of Object.entries(matrix.features)) {
|
|
47691
47897
|
lines.push(
|
|
@@ -47811,11 +48017,11 @@ function fileConfigPath(env = process.env) {
|
|
|
47811
48017
|
if (override && override.trim()) return override.trim();
|
|
47812
48018
|
return join17(homedir14(), "Library", "Application Support", "apple-notes-mcp", "config.json");
|
|
47813
48019
|
}
|
|
47814
|
-
function loadFileConfig(env = process.env,
|
|
48020
|
+
function loadFileConfig(env = process.env, path10 = fileConfigPath(env)) {
|
|
47815
48021
|
const applied = [];
|
|
47816
48022
|
try {
|
|
47817
|
-
if (!existsSync11(
|
|
47818
|
-
const parsed = JSON.parse(readFileSync2(
|
|
48023
|
+
if (!existsSync11(path10)) return applied;
|
|
48024
|
+
const parsed = JSON.parse(readFileSync2(path10, "utf8"));
|
|
47819
48025
|
if (!parsed || typeof parsed !== "object") return applied;
|
|
47820
48026
|
for (const [k, v] of Object.entries(parsed)) {
|
|
47821
48027
|
if (typeof v !== "string") continue;
|
|
@@ -47825,7 +48031,7 @@ function loadFileConfig(env = process.env, path9 = fileConfigPath(env)) {
|
|
|
47825
48031
|
}
|
|
47826
48032
|
}
|
|
47827
48033
|
} catch (e) {
|
|
47828
|
-
console.error(`Failed to load apple-notes-mcp config file ${
|
|
48034
|
+
console.error(`Failed to load apple-notes-mcp config file ${path10}: ${String(e)}`);
|
|
47829
48035
|
}
|
|
47830
48036
|
return applied;
|
|
47831
48037
|
}
|
|
@@ -48668,10 +48874,10 @@ function listDirectory(dir, limit) {
|
|
|
48668
48874
|
return [];
|
|
48669
48875
|
}
|
|
48670
48876
|
}
|
|
48671
|
-
function isFile(
|
|
48877
|
+
function isFile(path10) {
|
|
48672
48878
|
let fd;
|
|
48673
48879
|
try {
|
|
48674
|
-
fd = openSync4(
|
|
48880
|
+
fd = openSync4(path10, constants4.O_RDONLY | constants4.O_NOFOLLOW);
|
|
48675
48881
|
return fstatSync4(fd).isFile();
|
|
48676
48882
|
} catch {
|
|
48677
48883
|
return false;
|
|
@@ -48679,9 +48885,9 @@ function isFile(path9) {
|
|
|
48679
48885
|
if (fd !== void 0) closeSync4(fd);
|
|
48680
48886
|
}
|
|
48681
48887
|
}
|
|
48682
|
-
function isDirectory2(
|
|
48888
|
+
function isDirectory2(path10) {
|
|
48683
48889
|
try {
|
|
48684
|
-
return statSync3(
|
|
48890
|
+
return statSync3(path10).isDirectory();
|
|
48685
48891
|
} catch {
|
|
48686
48892
|
return false;
|
|
48687
48893
|
}
|
|
@@ -48765,8 +48971,8 @@ var AssetLocator = class {
|
|
|
48765
48971
|
join19(account, "Media", mediaId, filename)
|
|
48766
48972
|
];
|
|
48767
48973
|
for (const candidate of candidates) {
|
|
48768
|
-
const
|
|
48769
|
-
if (
|
|
48974
|
+
const path10 = confine(candidate, account);
|
|
48975
|
+
if (path10 && isFile(path10)) return { path: path10, name: filename, role: "original" };
|
|
48770
48976
|
}
|
|
48771
48977
|
return void 0;
|
|
48772
48978
|
}
|
|
@@ -48781,8 +48987,8 @@ var AssetLocator = class {
|
|
|
48781
48987
|
];
|
|
48782
48988
|
for (const g of generations)
|
|
48783
48989
|
for (const name of names) {
|
|
48784
|
-
const
|
|
48785
|
-
if (
|
|
48990
|
+
const path10 = confine(g ? join19(base, g, name) : join19(base, name), account);
|
|
48991
|
+
if (path10 && isFile(path10)) return path10;
|
|
48786
48992
|
}
|
|
48787
48993
|
return void 0;
|
|
48788
48994
|
}
|
|
@@ -48815,16 +49021,16 @@ var AssetLocator = class {
|
|
|
48815
49021
|
bundleImage(bundle, account) {
|
|
48816
49022
|
const found = [];
|
|
48817
49023
|
for (const child of listDirectory(bundle, MAX_BUNDLE_ENTRIES2)) {
|
|
48818
|
-
const
|
|
48819
|
-
if (!
|
|
48820
|
-
if (isFile(
|
|
49024
|
+
const path10 = confine(join19(bundle, child), account);
|
|
49025
|
+
if (!path10) continue;
|
|
49026
|
+
if (isFile(path10)) found.push(path10);
|
|
48821
49027
|
else
|
|
48822
|
-
for (const grandchild of listDirectory(
|
|
48823
|
-
const inner = confine(join19(
|
|
49028
|
+
for (const grandchild of listDirectory(path10, MAX_BUNDLE_ENTRIES2)) {
|
|
49029
|
+
const inner = confine(join19(path10, grandchild), account);
|
|
48824
49030
|
if (inner && isFile(inner)) found.push(inner);
|
|
48825
49031
|
}
|
|
48826
49032
|
}
|
|
48827
|
-
return found.find((
|
|
49033
|
+
return found.find((path10) => basename3(path10) === "Preview.png") ?? found.find((path10) => IMAGE_EXTENSIONS.has(extname3(path10).toLowerCase()));
|
|
48828
49034
|
}
|
|
48829
49035
|
};
|
|
48830
49036
|
function sniffMime(head, name) {
|
|
@@ -48872,8 +49078,8 @@ function safeAssetName(name, mime) {
|
|
|
48872
49078
|
if (clean.length > 120) clean = clean.slice(0, 120 - ext.length) + ext;
|
|
48873
49079
|
return clean;
|
|
48874
49080
|
}
|
|
48875
|
-
function encodePathUrl(
|
|
48876
|
-
return
|
|
49081
|
+
function encodePathUrl(path10) {
|
|
49082
|
+
return path10.split("/").map(encodeURIComponent).join("/");
|
|
48877
49083
|
}
|
|
48878
49084
|
function canonicalForm(abs) {
|
|
48879
49085
|
let current = abs;
|
|
@@ -48892,7 +49098,7 @@ function canonicalForm(abs) {
|
|
|
48892
49098
|
}
|
|
48893
49099
|
function assertExportPath(p, container = NOTES_CONTAINER) {
|
|
48894
49100
|
const abs = assertSafeSavePath(p);
|
|
48895
|
-
const within = (
|
|
49101
|
+
const within = (path10, root) => path10 === root || path10.startsWith(root + sep3);
|
|
48896
49102
|
const roots = [resolve3(container), canonicalForm(resolve3(container))];
|
|
48897
49103
|
const forms = [abs, canonicalForm(abs)];
|
|
48898
49104
|
if (forms.some((form) => roots.some((root) => within(form, root))))
|
|
@@ -48902,16 +49108,16 @@ function assertExportPath(p, container = NOTES_CONTAINER) {
|
|
|
48902
49108
|
var CREATE_FLAGS = constants4.O_WRONLY | constants4.O_CREAT | constants4.O_EXCL | constants4.O_NOFOLLOW;
|
|
48903
49109
|
var OutputExistsError = class extends Error {
|
|
48904
49110
|
code = "output_exists";
|
|
48905
|
-
constructor(
|
|
48906
|
-
super(`Output file already exists: ${
|
|
49111
|
+
constructor(path10) {
|
|
49112
|
+
super(`Output file already exists: ${path10}. Choose a new path or remove the old file first.`);
|
|
48907
49113
|
this.name = "OutputExistsError";
|
|
48908
49114
|
}
|
|
48909
49115
|
};
|
|
48910
|
-
function openCreateOnly(
|
|
49116
|
+
function openCreateOnly(path10) {
|
|
48911
49117
|
try {
|
|
48912
|
-
return openSync4(
|
|
49118
|
+
return openSync4(path10, CREATE_FLAGS, 420);
|
|
48913
49119
|
} catch (error2) {
|
|
48914
|
-
if (error2.code === "EEXIST") throw new OutputExistsError(
|
|
49120
|
+
if (error2.code === "EEXIST") throw new OutputExistsError(path10);
|
|
48915
49121
|
throw error2;
|
|
48916
49122
|
}
|
|
48917
49123
|
}
|
|
@@ -48925,8 +49131,8 @@ function writeAllAndClose(fd, content) {
|
|
|
48925
49131
|
closeSync4(fd);
|
|
48926
49132
|
}
|
|
48927
49133
|
}
|
|
48928
|
-
function openSource(
|
|
48929
|
-
const fd = openSync4(
|
|
49134
|
+
function openSource(path10) {
|
|
49135
|
+
const fd = openSync4(path10, constants4.O_RDONLY | constants4.O_NOFOLLOW);
|
|
48930
49136
|
const stat = fstatSync4(fd);
|
|
48931
49137
|
if (!stat.isFile()) {
|
|
48932
49138
|
closeSync4(fd);
|
|
@@ -49908,9 +50114,9 @@ var NotesExportError = class extends Error {
|
|
|
49908
50114
|
}
|
|
49909
50115
|
code;
|
|
49910
50116
|
};
|
|
49911
|
-
function validPath(
|
|
50117
|
+
function validPath(path10, what) {
|
|
49912
50118
|
try {
|
|
49913
|
-
return assertExportPath(
|
|
50119
|
+
return assertExportPath(path10);
|
|
49914
50120
|
} catch (error2) {
|
|
49915
50121
|
throw new NotesExportError(
|
|
49916
50122
|
"invalid-path",
|
|
@@ -50099,9 +50305,9 @@ function assertExistingContentPreserved(before, after) {
|
|
|
50099
50305
|
throw new Error("Existing native object content or presentation changed");
|
|
50100
50306
|
}
|
|
50101
50307
|
}
|
|
50102
|
-
function localAttachment(
|
|
50103
|
-
if (!isAbsolute3(
|
|
50104
|
-
const descriptor = openSync5(
|
|
50308
|
+
function localAttachment(path10) {
|
|
50309
|
+
if (!isAbsolute3(path10)) throw new Error("An absolute local file path is required");
|
|
50310
|
+
const descriptor = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW);
|
|
50105
50311
|
try {
|
|
50106
50312
|
const stat = fstatSync5(descriptor);
|
|
50107
50313
|
if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
|
|
@@ -50139,7 +50345,7 @@ function registerDirectOperations(server2, manager) {
|
|
|
50139
50345
|
}
|
|
50140
50346
|
tool(
|
|
50141
50347
|
"get-folder-by-id",
|
|
50142
|
-
"Use when: reading the exact folder name and
|
|
50348
|
+
"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.",
|
|
50143
50349
|
{ id: folderId },
|
|
50144
50350
|
({ id: id2 }) => {
|
|
50145
50351
|
const folder = manager.getFolderById(id2);
|
|
@@ -50223,8 +50429,8 @@ function registerDirectOperations(server2, manager) {
|
|
|
50223
50429
|
}
|
|
50224
50430
|
);
|
|
50225
50431
|
}
|
|
50226
|
-
function attachmentName(
|
|
50227
|
-
const source = basename5(
|
|
50432
|
+
function attachmentName(path10, filename) {
|
|
50433
|
+
const source = basename5(path10);
|
|
50228
50434
|
if (filename === void 0) return source;
|
|
50229
50435
|
if (Buffer.byteLength(filename, "utf8") > 255)
|
|
50230
50436
|
throw new Error("filename must be at most 255 bytes");
|
|
@@ -50239,11 +50445,11 @@ function attachmentName(path9, filename) {
|
|
|
50239
50445
|
return filename;
|
|
50240
50446
|
}
|
|
50241
50447
|
function attachFile(manager, args) {
|
|
50242
|
-
const { id: id2, expectedContentHash, path:
|
|
50243
|
-
const name = attachmentName(
|
|
50448
|
+
const { id: id2, expectedContentHash, path: path10 } = args;
|
|
50449
|
+
const name = attachmentName(path10, args.filename);
|
|
50244
50450
|
const before = readSnapshot(manager, id2);
|
|
50245
50451
|
if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
|
|
50246
|
-
const bytes = localAttachment(
|
|
50452
|
+
const bytes = localAttachment(path10);
|
|
50247
50453
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
50248
50454
|
const directory = mkdtempSync4(join22(tmpdir4(), "notes-attachment-add-"));
|
|
50249
50455
|
const temporaryFile = join22(directory, name);
|
|
@@ -50300,6 +50506,329 @@ function attachFile(manager, args) {
|
|
|
50300
50506
|
}
|
|
50301
50507
|
}
|
|
50302
50508
|
|
|
50509
|
+
// src/tools/folderDelete.ts
|
|
50510
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
50511
|
+
|
|
50512
|
+
// src/utils/folderStore.ts
|
|
50513
|
+
import { execFileSync as execFileSync19 } from "child_process";
|
|
50514
|
+
import * as os9 from "os";
|
|
50515
|
+
import * as path9 from "path";
|
|
50516
|
+
var NOTE_STORE_PATH = path9.join(
|
|
50517
|
+
os9.homedir(),
|
|
50518
|
+
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
50519
|
+
);
|
|
50520
|
+
var NOTE_KEY_LIMIT = 50;
|
|
50521
|
+
var FolderStoreError = class extends Error {
|
|
50522
|
+
constructor(message, reason) {
|
|
50523
|
+
super(message);
|
|
50524
|
+
this.reason = reason;
|
|
50525
|
+
this.name = "FolderStoreError";
|
|
50526
|
+
}
|
|
50527
|
+
reason;
|
|
50528
|
+
};
|
|
50529
|
+
var REQUIRED_FOLDER_COLUMNS = [
|
|
50530
|
+
"ZFOLDERTYPE",
|
|
50531
|
+
"ZIDENTIFIER",
|
|
50532
|
+
"ZMARKEDFORDELETION",
|
|
50533
|
+
"ZPARENT",
|
|
50534
|
+
"ZOWNER",
|
|
50535
|
+
"ZFOLDER",
|
|
50536
|
+
"ZSERVERSHAREDATA"
|
|
50537
|
+
];
|
|
50538
|
+
function buildFolderFactsSql(hasSmartQueryColumn) {
|
|
50539
|
+
const smart = hasSmartQueryColumn ? "coalesce(f.ZSMARTFOLDERQUERYJSON, '') <> ''" : "0";
|
|
50540
|
+
return `WITH RECURSIVE anc(pk, depth) AS (
|
|
50541
|
+
SELECT ZPARENT, 1 FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = @pk
|
|
50542
|
+
UNION ALL
|
|
50543
|
+
SELECT o.ZPARENT, anc.depth + 1 FROM ZICCLOUDSYNCINGOBJECT o JOIN anc ON o.Z_PK = anc.pk
|
|
50544
|
+
WHERE anc.pk IS NOT NULL AND anc.depth < 64
|
|
50545
|
+
)
|
|
50546
|
+
SELECT json_object(
|
|
50547
|
+
'pk', f.Z_PK,
|
|
50548
|
+
'entity', e.Z_NAME,
|
|
50549
|
+
'identifier', f.ZIDENTIFIER,
|
|
50550
|
+
'folderType', f.ZFOLDERTYPE,
|
|
50551
|
+
'markedForDeletion', coalesce(f.ZMARKEDFORDELETION, 0),
|
|
50552
|
+
'parentPk', f.ZPARENT,
|
|
50553
|
+
'accountPk', f.ZOWNER,
|
|
50554
|
+
'hasSmartQuery', ${smart},
|
|
50555
|
+
'sharedRecord', f.ZSERVERSHAREDATA IS NOT NULL,
|
|
50556
|
+
'sharedAncestor', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT a JOIN anc ON a.Z_PK = anc.pk
|
|
50557
|
+
WHERE a.ZSERVERSHAREDATA IS NOT NULL) > 0,
|
|
50558
|
+
'childFolderCount', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT c
|
|
50559
|
+
WHERE c.Z_ENT = f.Z_ENT AND c.ZPARENT = f.Z_PK
|
|
50560
|
+
AND coalesce(c.ZMARKEDFORDELETION, 0) = 0),
|
|
50561
|
+
'noteCount', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n
|
|
50562
|
+
WHERE n.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')
|
|
50563
|
+
AND n.ZFOLDER = f.Z_PK AND coalesce(n.ZMARKEDFORDELETION, 0) = 0),
|
|
50564
|
+
'noteKeys', json((SELECT json_group_array(k.Z_PK) FROM (
|
|
50565
|
+
SELECT n.Z_PK FROM ZICCLOUDSYNCINGOBJECT n
|
|
50566
|
+
WHERE n.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')
|
|
50567
|
+
AND n.ZFOLDER = f.Z_PK AND coalesce(n.ZMARKEDFORDELETION, 0) = 0
|
|
50568
|
+
ORDER BY n.Z_PK LIMIT ${NOTE_KEY_LIMIT}) k))
|
|
50569
|
+
)
|
|
50570
|
+
FROM ZICCLOUDSYNCINGOBJECT f JOIN Z_PRIMARYKEY e ON e.Z_ENT = f.Z_ENT
|
|
50571
|
+
WHERE f.Z_PK = @pk;`;
|
|
50572
|
+
}
|
|
50573
|
+
function runSqlite7(dbPath2, sql, pk) {
|
|
50574
|
+
const args = ["-readonly"];
|
|
50575
|
+
if (pk !== void 0) args.push("-cmd", `.parameter set @pk ${pk}`);
|
|
50576
|
+
args.push(dbPath2, sql);
|
|
50577
|
+
return execFileSync19("sqlite3", args, {
|
|
50578
|
+
encoding: "utf8",
|
|
50579
|
+
timeout: 5e3,
|
|
50580
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
50581
|
+
}).trim();
|
|
50582
|
+
}
|
|
50583
|
+
function classify(error2) {
|
|
50584
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
50585
|
+
if (/authorization denied|unable to open database|not authorized/i.test(message)) {
|
|
50586
|
+
return new FolderStoreError(
|
|
50587
|
+
"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.",
|
|
50588
|
+
"no_fda"
|
|
50589
|
+
);
|
|
50590
|
+
}
|
|
50591
|
+
return new FolderStoreError(`Failed to read folder facts: ${message}`, "query_error");
|
|
50592
|
+
}
|
|
50593
|
+
function numberOrNull(value) {
|
|
50594
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
50595
|
+
}
|
|
50596
|
+
function readFolderStoreFacts(pk, dbPath2 = NOTE_STORE_PATH) {
|
|
50597
|
+
if (!Number.isSafeInteger(pk) || pk <= 0) {
|
|
50598
|
+
throw new FolderStoreError("Folder key must be a positive integer", "query_error");
|
|
50599
|
+
}
|
|
50600
|
+
let columns;
|
|
50601
|
+
let row;
|
|
50602
|
+
try {
|
|
50603
|
+
columns = new Set(
|
|
50604
|
+
runSqlite7(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n").map((line) => line.split("|")[1]).filter(Boolean)
|
|
50605
|
+
);
|
|
50606
|
+
const missing = REQUIRED_FOLDER_COLUMNS.filter((column) => !columns.has(column));
|
|
50607
|
+
if (missing.length > 0) {
|
|
50608
|
+
throw new FolderStoreError(
|
|
50609
|
+
`This macOS Notes schema lacks ${missing.join(", ")}; folder safety cannot be verified`,
|
|
50610
|
+
"schema"
|
|
50611
|
+
);
|
|
50612
|
+
}
|
|
50613
|
+
row = runSqlite7(dbPath2, buildFolderFactsSql(columns.has("ZSMARTFOLDERQUERYJSON")), pk);
|
|
50614
|
+
} catch (error2) {
|
|
50615
|
+
if (error2 instanceof FolderStoreError) throw error2;
|
|
50616
|
+
throw classify(error2);
|
|
50617
|
+
}
|
|
50618
|
+
if (!row) return null;
|
|
50619
|
+
const raw = JSON.parse(row);
|
|
50620
|
+
if (raw.entity !== "ICFolder") {
|
|
50621
|
+
throw new FolderStoreError("That identifier does not name a folder", "not_folder");
|
|
50622
|
+
}
|
|
50623
|
+
return {
|
|
50624
|
+
pk,
|
|
50625
|
+
identifier: typeof raw.identifier === "string" ? raw.identifier : null,
|
|
50626
|
+
folderType: numberOrNull(raw.folderType),
|
|
50627
|
+
markedForDeletion: raw.markedForDeletion === 1,
|
|
50628
|
+
parentPk: numberOrNull(raw.parentPk),
|
|
50629
|
+
accountPk: numberOrNull(raw.accountPk),
|
|
50630
|
+
hasSmartQuery: raw.hasSmartQuery === 1,
|
|
50631
|
+
sharedRecord: raw.sharedRecord === 1,
|
|
50632
|
+
sharedAncestor: raw.sharedAncestor === 1,
|
|
50633
|
+
childFolderCount: numberOrNull(raw.childFolderCount) ?? 0,
|
|
50634
|
+
noteCount: numberOrNull(raw.noteCount) ?? 0,
|
|
50635
|
+
noteKeys: Array.isArray(raw.noteKeys) ? raw.noteKeys.filter((key) => typeof key === "number") : []
|
|
50636
|
+
};
|
|
50637
|
+
}
|
|
50638
|
+
|
|
50639
|
+
// src/tools/folderDelete.ts
|
|
50640
|
+
var folderIdSchema = external_exports.string().max(2e3).transform(looseIdTransform("ICFolder"));
|
|
50641
|
+
var accountIdSchema = external_exports.string().max(2e3).regex(/^x-coredata:\/\/[0-9a-f-]+\/ICAccount\/p\d+$/i, "An exact account ID is required");
|
|
50642
|
+
var revisionSchema = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
50643
|
+
var defaultDeps = {
|
|
50644
|
+
readStore: (pk) => readFolderStoreFacts(pk),
|
|
50645
|
+
sleep: (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)
|
|
50646
|
+
};
|
|
50647
|
+
function conflict(message) {
|
|
50648
|
+
return new CodedError(`Conflict: ${message}`, { code: "revision_conflict", committed: false });
|
|
50649
|
+
}
|
|
50650
|
+
function refused(message) {
|
|
50651
|
+
return new CodedError(`Refused: ${message}`, { code: "unsupported", committed: false });
|
|
50652
|
+
}
|
|
50653
|
+
function uncertain(message) {
|
|
50654
|
+
return new CodedError(message, { code: "verification_failed", indeterminate: true });
|
|
50655
|
+
}
|
|
50656
|
+
function invalid(message) {
|
|
50657
|
+
return new CodedError(message, { code: "validation_error", committed: false });
|
|
50658
|
+
}
|
|
50659
|
+
function coreDataPk(id2) {
|
|
50660
|
+
const match = /\/p(\d+)$/.exec(id2);
|
|
50661
|
+
if (!match) throw new Error(`Not an x-coredata id: ${id2}`);
|
|
50662
|
+
return Number(match[1]);
|
|
50663
|
+
}
|
|
50664
|
+
function folderDeleteRefusal(app, store) {
|
|
50665
|
+
if (store.folderType === 1 || store.identifier?.startsWith("TrashFolder-"))
|
|
50666
|
+
return "Refusing to delete the Recently Deleted folder";
|
|
50667
|
+
if (store.folderType === 2 || store.hasSmartQuery) return "Refusing to delete a smart folder";
|
|
50668
|
+
if (store.folderType !== 0)
|
|
50669
|
+
return `Refusing to delete a folder with unsupported folder type ${String(store.folderType)}`;
|
|
50670
|
+
if (!store.identifier)
|
|
50671
|
+
return "Refusing to delete a folder without a stable identifier (it cannot be verified)";
|
|
50672
|
+
if (store.identifier.startsWith("DefaultFolder-") || app.defaultFolderId === app.id)
|
|
50673
|
+
return "Refusing to delete a system or default folder";
|
|
50674
|
+
if (app.shared || store.sharedRecord || store.sharedAncestor)
|
|
50675
|
+
return "Refusing to delete a shared or collaborated folder";
|
|
50676
|
+
return null;
|
|
50677
|
+
}
|
|
50678
|
+
function folderDeleteRevision(app, store) {
|
|
50679
|
+
const state = [
|
|
50680
|
+
app.id,
|
|
50681
|
+
store.identifier,
|
|
50682
|
+
app.name,
|
|
50683
|
+
app.accountId,
|
|
50684
|
+
app.parentId,
|
|
50685
|
+
app.defaultFolderId === app.id,
|
|
50686
|
+
app.shared,
|
|
50687
|
+
app.childFolderCount,
|
|
50688
|
+
app.noteCount,
|
|
50689
|
+
store.folderType,
|
|
50690
|
+
store.markedForDeletion,
|
|
50691
|
+
store.hasSmartQuery,
|
|
50692
|
+
store.sharedRecord,
|
|
50693
|
+
store.sharedAncestor,
|
|
50694
|
+
store.childFolderCount,
|
|
50695
|
+
store.noteCount
|
|
50696
|
+
];
|
|
50697
|
+
return `sha256:${createHash3("sha256").update(JSON.stringify(state)).digest("hex")}`;
|
|
50698
|
+
}
|
|
50699
|
+
function checkFolder(manager, args, deps) {
|
|
50700
|
+
const app = manager.readFolderForDelete(args.id);
|
|
50701
|
+
const store = deps.readStore(coreDataPk(args.id));
|
|
50702
|
+
if (!store)
|
|
50703
|
+
throw conflict(
|
|
50704
|
+
"the folder is not in the local Notes store yet; wait for Notes to save it and plan again"
|
|
50705
|
+
);
|
|
50706
|
+
if (store.markedForDeletion) throw conflict("the folder is already deleted");
|
|
50707
|
+
const expectedParentPk = app.parentId ? coreDataPk(app.parentId) : null;
|
|
50708
|
+
if (store.accountPk !== coreDataPk(app.accountId) || store.parentPk !== expectedParentPk)
|
|
50709
|
+
throw conflict(
|
|
50710
|
+
"Notes.app and the local store disagree about this folder's location; wait a moment and plan again"
|
|
50711
|
+
);
|
|
50712
|
+
if (app.name !== args.expectedName) throw conflict("the folder name changed");
|
|
50713
|
+
if (app.accountId !== args.expectedAccountId)
|
|
50714
|
+
throw conflict("the folder is in a different account");
|
|
50715
|
+
if (args.expectedRoot ? app.parentId !== null : app.parentId !== args.expectedParentId)
|
|
50716
|
+
throw conflict("the folder's parent changed");
|
|
50717
|
+
const refusal = folderDeleteRefusal(app, store);
|
|
50718
|
+
if (refusal) throw refused(refusal);
|
|
50719
|
+
let storeNotes = store.noteCount;
|
|
50720
|
+
if (storeNotes > app.noteCount && store.noteKeys.length === storeNotes) {
|
|
50721
|
+
const notePrefix = args.id.replace(/\/ICFolder\/p\d+$/, "/ICNote/p");
|
|
50722
|
+
storeNotes -= manager.countNotesOutsideFolder(
|
|
50723
|
+
args.id,
|
|
50724
|
+
store.noteKeys.map((key) => `${notePrefix}${key}`)
|
|
50725
|
+
);
|
|
50726
|
+
}
|
|
50727
|
+
const children = Math.max(app.childFolderCount, store.childFolderCount);
|
|
50728
|
+
const notes = Math.max(app.noteCount, storeNotes);
|
|
50729
|
+
if (children > 0 || notes > 0)
|
|
50730
|
+
throw refused(
|
|
50731
|
+
`the folder is not empty (${children} child folder(s), ${notes} note(s)); move or delete them first`
|
|
50732
|
+
);
|
|
50733
|
+
return {
|
|
50734
|
+
app,
|
|
50735
|
+
store,
|
|
50736
|
+
identifier: store.identifier,
|
|
50737
|
+
revision: folderDeleteRevision(app, { ...store, noteCount: storeNotes })
|
|
50738
|
+
};
|
|
50739
|
+
}
|
|
50740
|
+
function runFolderDelete(manager, args, deps = defaultDeps) {
|
|
50741
|
+
if (Boolean(args.expectedRoot) === (args.expectedParentId !== void 0))
|
|
50742
|
+
throw invalid("Pass exactly one of expectedParentId or expectedRoot: true");
|
|
50743
|
+
if (args.dryRun && args.expectedRevision)
|
|
50744
|
+
throw invalid("expectedRevision belongs to the apply call (dryRun: false)");
|
|
50745
|
+
if (!args.dryRun && !args.expectedRevision)
|
|
50746
|
+
throw invalid("Apply requires expectedRevision from a matching dry run");
|
|
50747
|
+
const checked = checkFolder(manager, args, deps);
|
|
50748
|
+
const base = {
|
|
50749
|
+
ok: true,
|
|
50750
|
+
id: args.id,
|
|
50751
|
+
identifier: checked.identifier,
|
|
50752
|
+
name: checked.app.name,
|
|
50753
|
+
accountId: checked.app.accountId,
|
|
50754
|
+
parentId: checked.app.parentId,
|
|
50755
|
+
folderType: 0,
|
|
50756
|
+
childFolderCount: 0,
|
|
50757
|
+
noteCount: 0,
|
|
50758
|
+
revision: checked.revision
|
|
50759
|
+
};
|
|
50760
|
+
if (args.dryRun)
|
|
50761
|
+
return { ...base, status: "planned", dryRun: true, committed: false, wouldDelete: true };
|
|
50762
|
+
if (checked.revision !== args.expectedRevision)
|
|
50763
|
+
throw conflict("the folder changed since the dry run; plan again");
|
|
50764
|
+
const outcome = manager.deleteEmptyFolderIfUnchanged(args.id, {
|
|
50765
|
+
name: checked.app.name,
|
|
50766
|
+
parentId: checked.app.parentId,
|
|
50767
|
+
accountId: checked.app.accountId
|
|
50768
|
+
});
|
|
50769
|
+
if (outcome.status === "conflict")
|
|
50770
|
+
throw conflict(`the folder ${outcome.reason} changed before deletion; plan again`);
|
|
50771
|
+
if (outcome.status === "refused") throw refused(outcome.reason);
|
|
50772
|
+
if (outcome.status === "failed")
|
|
50773
|
+
throw uncertain(
|
|
50774
|
+
`The delete outcome is uncertain (${outcome.reason}); read folder ${args.id} before retrying`
|
|
50775
|
+
);
|
|
50776
|
+
if (manager.folderExistsById(args.id))
|
|
50777
|
+
throw uncertain(
|
|
50778
|
+
`The delete outcome is uncertain: Notes.app still resolves folder ${args.id}; read it before retrying`
|
|
50779
|
+
);
|
|
50780
|
+
let storeTombstoned = false;
|
|
50781
|
+
for (let attempt = 0; attempt < 5 && !storeTombstoned; attempt++) {
|
|
50782
|
+
if (attempt > 0) deps.sleep(200);
|
|
50783
|
+
const after = deps.readStore(coreDataPk(args.id));
|
|
50784
|
+
storeTombstoned = !after || after.markedForDeletion;
|
|
50785
|
+
}
|
|
50786
|
+
return {
|
|
50787
|
+
...base,
|
|
50788
|
+
status: "deleted",
|
|
50789
|
+
dryRun: false,
|
|
50790
|
+
committed: true,
|
|
50791
|
+
wouldDelete: true,
|
|
50792
|
+
verified: true,
|
|
50793
|
+
storeTombstoned
|
|
50794
|
+
};
|
|
50795
|
+
}
|
|
50796
|
+
function registerFolderDelete(server2, manager, deps = defaultDeps) {
|
|
50797
|
+
const inputSchema = {
|
|
50798
|
+
id: folderIdSchema.describe(
|
|
50799
|
+
"Exact folder id (x-coredata://\u2026/ICFolder/pN) from list-folders, or the folder's Notes UUID or numeric key"
|
|
50800
|
+
),
|
|
50801
|
+
expectedName: external_exports.string().min(1).max(1e3).describe("Current folder name (not a path), matched case-sensitively"),
|
|
50802
|
+
expectedAccountId: accountIdSchema.describe(
|
|
50803
|
+
"Owning account id, from get-folder-by-id or list-accounts"
|
|
50804
|
+
),
|
|
50805
|
+
expectedParentId: folderIdSchema.optional().describe("Current parent folder id; omit and pass expectedRoot for a top-level folder"),
|
|
50806
|
+
expectedRoot: external_exports.literal(true).optional().describe("Pass true when the folder sits at the account root (no parent folder)"),
|
|
50807
|
+
dryRun: external_exports.boolean().describe("true plans and returns a revision; false applies with expectedRevision"),
|
|
50808
|
+
expectedRevision: revisionSchema.optional().describe("The revision returned by the dry run; required when dryRun is false")
|
|
50809
|
+
};
|
|
50810
|
+
server2.registerTool(
|
|
50811
|
+
"delete-folder-by-id",
|
|
50812
|
+
{
|
|
50813
|
+
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.",
|
|
50814
|
+
inputSchema,
|
|
50815
|
+
outputSchema: external_exports.object({ ok: external_exports.boolean().optional() }).passthrough(),
|
|
50816
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false }
|
|
50817
|
+
},
|
|
50818
|
+
(async (args) => {
|
|
50819
|
+
try {
|
|
50820
|
+
const result = runFolderDelete(manager, args, deps);
|
|
50821
|
+
return {
|
|
50822
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
50823
|
+
structuredContent: result
|
|
50824
|
+
};
|
|
50825
|
+
} catch (error2) {
|
|
50826
|
+
return errorResult(error2 instanceof Error ? error2.message : String(error2), error2);
|
|
50827
|
+
}
|
|
50828
|
+
})
|
|
50829
|
+
);
|
|
50830
|
+
}
|
|
50831
|
+
|
|
50303
50832
|
// src/tools/nativeTagsBridge.ts
|
|
50304
50833
|
var noteId2 = exactIdInput(
|
|
50305
50834
|
"ICNote",
|
|
@@ -50825,15 +51354,15 @@ var shortcutFiles = [
|
|
|
50825
51354
|
function setupShortcuts(checkOnly, dependencies = {}) {
|
|
50826
51355
|
const status = dependencies.status || nativeTagsStatus;
|
|
50827
51356
|
const exists = dependencies.exists || existsSync13;
|
|
50828
|
-
const open = dependencies.open || ((
|
|
50829
|
-
const result = spawnSync2("/usr/bin/open", [
|
|
51357
|
+
const open = dependencies.open || ((path10) => {
|
|
51358
|
+
const result = spawnSync2("/usr/bin/open", [path10], { encoding: "utf8" });
|
|
50830
51359
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
50831
51360
|
});
|
|
50832
51361
|
const baseDirectory = dependencies.baseDirectory || resolve4(dirname5(fileURLToPath(import.meta.url)), "../shortcuts");
|
|
50833
51362
|
const osRelease = (dependencies.osRelease || release2)();
|
|
50834
51363
|
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
50835
51364
|
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
50836
|
-
const
|
|
51365
|
+
const path10 = resolve4(baseDirectory, file);
|
|
50837
51366
|
let installed = false;
|
|
50838
51367
|
let identifier;
|
|
50839
51368
|
let error2;
|
|
@@ -50849,9 +51378,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
50849
51378
|
if (!installed && !checkOnly) {
|
|
50850
51379
|
if (optional2 && !(darwinMajor >= MARKDOWN_MIN_DARWIN_MAJOR))
|
|
50851
51380
|
skipped = `requires macOS 26 or later (this Mac reports Darwin ${osRelease})`;
|
|
50852
|
-
else if (!exists(
|
|
51381
|
+
else if (!exists(path10)) error2 = `Packaged Shortcut is missing: ${path10}`;
|
|
50853
51382
|
else {
|
|
50854
|
-
const result = open(
|
|
51383
|
+
const result = open(path10);
|
|
50855
51384
|
opened = result.ok;
|
|
50856
51385
|
if (!result.ok) error2 = result.error || `Could not open ${file}`;
|
|
50857
51386
|
}
|
|
@@ -50860,7 +51389,7 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
50860
51389
|
name,
|
|
50861
51390
|
installed,
|
|
50862
51391
|
identifier,
|
|
50863
|
-
file:
|
|
51392
|
+
file: path10,
|
|
50864
51393
|
opened,
|
|
50865
51394
|
...optional2 ? { optional: optional2 } : {},
|
|
50866
51395
|
...skipped ? { skipped } : {},
|
|
@@ -50907,6 +51436,7 @@ var server = new McpServer({
|
|
|
50907
51436
|
});
|
|
50908
51437
|
var notesManager = new AppleNotesManager();
|
|
50909
51438
|
registerDirectOperations(server, notesManager);
|
|
51439
|
+
registerFolderDelete(server, notesManager);
|
|
50910
51440
|
registerNativeTagsBridge(server, notesManager);
|
|
50911
51441
|
registerNativeOperations(server, notesManager);
|
|
50912
51442
|
function successResponse(message, structured) {
|
package/package.json
CHANGED