apple-notes-mcp 2.8.6 → 2.8.8
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/build/index.js
CHANGED
|
@@ -417,11 +417,11 @@ var require_codegen = __commonJS({
|
|
|
417
417
|
const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
|
|
418
418
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
419
419
|
}
|
|
420
|
-
optimizeNames(names,
|
|
420
|
+
optimizeNames(names, constants2) {
|
|
421
421
|
if (!names[this.name.str])
|
|
422
422
|
return;
|
|
423
423
|
if (this.rhs)
|
|
424
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
424
|
+
this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
425
425
|
return this;
|
|
426
426
|
}
|
|
427
427
|
get names() {
|
|
@@ -438,10 +438,10 @@ var require_codegen = __commonJS({
|
|
|
438
438
|
render({ _n }) {
|
|
439
439
|
return `${this.lhs} = ${this.rhs};` + _n;
|
|
440
440
|
}
|
|
441
|
-
optimizeNames(names,
|
|
441
|
+
optimizeNames(names, constants2) {
|
|
442
442
|
if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
|
|
443
443
|
return;
|
|
444
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
444
|
+
this.rhs = optimizeExpr(this.rhs, names, constants2);
|
|
445
445
|
return this;
|
|
446
446
|
}
|
|
447
447
|
get names() {
|
|
@@ -502,8 +502,8 @@ var require_codegen = __commonJS({
|
|
|
502
502
|
optimizeNodes() {
|
|
503
503
|
return `${this.code}` ? this : void 0;
|
|
504
504
|
}
|
|
505
|
-
optimizeNames(names,
|
|
506
|
-
this.code = optimizeExpr(this.code, names,
|
|
505
|
+
optimizeNames(names, constants2) {
|
|
506
|
+
this.code = optimizeExpr(this.code, names, constants2);
|
|
507
507
|
return this;
|
|
508
508
|
}
|
|
509
509
|
get names() {
|
|
@@ -532,12 +532,12 @@ var require_codegen = __commonJS({
|
|
|
532
532
|
}
|
|
533
533
|
return nodes.length > 0 ? this : void 0;
|
|
534
534
|
}
|
|
535
|
-
optimizeNames(names,
|
|
535
|
+
optimizeNames(names, constants2) {
|
|
536
536
|
const { nodes } = this;
|
|
537
537
|
let i = nodes.length;
|
|
538
538
|
while (i--) {
|
|
539
539
|
const n = nodes[i];
|
|
540
|
-
if (n.optimizeNames(names,
|
|
540
|
+
if (n.optimizeNames(names, constants2))
|
|
541
541
|
continue;
|
|
542
542
|
subtractNames(names, n.names);
|
|
543
543
|
nodes.splice(i, 1);
|
|
@@ -590,12 +590,12 @@ var require_codegen = __commonJS({
|
|
|
590
590
|
return void 0;
|
|
591
591
|
return this;
|
|
592
592
|
}
|
|
593
|
-
optimizeNames(names,
|
|
593
|
+
optimizeNames(names, constants2) {
|
|
594
594
|
var _a;
|
|
595
|
-
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
596
|
-
if (!(super.optimizeNames(names,
|
|
595
|
+
this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
|
|
596
|
+
if (!(super.optimizeNames(names, constants2) || this.else))
|
|
597
597
|
return;
|
|
598
|
-
this.condition = optimizeExpr(this.condition, names,
|
|
598
|
+
this.condition = optimizeExpr(this.condition, names, constants2);
|
|
599
599
|
return this;
|
|
600
600
|
}
|
|
601
601
|
get names() {
|
|
@@ -618,10 +618,10 @@ var require_codegen = __commonJS({
|
|
|
618
618
|
render(opts) {
|
|
619
619
|
return `for(${this.iteration})` + super.render(opts);
|
|
620
620
|
}
|
|
621
|
-
optimizeNames(names,
|
|
622
|
-
if (!super.optimizeNames(names,
|
|
621
|
+
optimizeNames(names, constants2) {
|
|
622
|
+
if (!super.optimizeNames(names, constants2))
|
|
623
623
|
return;
|
|
624
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
624
|
+
this.iteration = optimizeExpr(this.iteration, names, constants2);
|
|
625
625
|
return this;
|
|
626
626
|
}
|
|
627
627
|
get names() {
|
|
@@ -657,10 +657,10 @@ var require_codegen = __commonJS({
|
|
|
657
657
|
render(opts) {
|
|
658
658
|
return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
|
|
659
659
|
}
|
|
660
|
-
optimizeNames(names,
|
|
661
|
-
if (!super.optimizeNames(names,
|
|
660
|
+
optimizeNames(names, constants2) {
|
|
661
|
+
if (!super.optimizeNames(names, constants2))
|
|
662
662
|
return;
|
|
663
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
663
|
+
this.iterable = optimizeExpr(this.iterable, names, constants2);
|
|
664
664
|
return this;
|
|
665
665
|
}
|
|
666
666
|
get names() {
|
|
@@ -702,11 +702,11 @@ var require_codegen = __commonJS({
|
|
|
702
702
|
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
|
|
703
703
|
return this;
|
|
704
704
|
}
|
|
705
|
-
optimizeNames(names,
|
|
705
|
+
optimizeNames(names, constants2) {
|
|
706
706
|
var _a, _b;
|
|
707
|
-
super.optimizeNames(names,
|
|
708
|
-
(_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names,
|
|
709
|
-
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names,
|
|
707
|
+
super.optimizeNames(names, constants2);
|
|
708
|
+
(_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
|
|
709
|
+
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants2);
|
|
710
710
|
return this;
|
|
711
711
|
}
|
|
712
712
|
get names() {
|
|
@@ -1007,7 +1007,7 @@ var require_codegen = __commonJS({
|
|
|
1007
1007
|
function addExprNames(names, from) {
|
|
1008
1008
|
return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
|
|
1009
1009
|
}
|
|
1010
|
-
function optimizeExpr(expr, names,
|
|
1010
|
+
function optimizeExpr(expr, names, constants2) {
|
|
1011
1011
|
if (expr instanceof code_1.Name)
|
|
1012
1012
|
return replaceName(expr);
|
|
1013
1013
|
if (!canOptimize(expr))
|
|
@@ -1022,14 +1022,14 @@ var require_codegen = __commonJS({
|
|
|
1022
1022
|
return items;
|
|
1023
1023
|
}, []));
|
|
1024
1024
|
function replaceName(n) {
|
|
1025
|
-
const c =
|
|
1025
|
+
const c = constants2[n.str];
|
|
1026
1026
|
if (c === void 0 || names[n.str] !== 1)
|
|
1027
1027
|
return n;
|
|
1028
1028
|
delete names[n.str];
|
|
1029
1029
|
return c;
|
|
1030
1030
|
}
|
|
1031
1031
|
function canOptimize(e) {
|
|
1032
|
-
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 &&
|
|
1032
|
+
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
|
|
1033
1033
|
}
|
|
1034
1034
|
}
|
|
1035
1035
|
function subtractNames(names, from) {
|
|
@@ -7422,7 +7422,7 @@ var require_DOMException = __commonJS({
|
|
|
7422
7422
|
"INVALID_NODE_TYPE_ERR (24): the supplied node is invalid or has an invalid ancestor for this operation",
|
|
7423
7423
|
"DATA_CLONE_ERR (25): the object can not be cloned."
|
|
7424
7424
|
];
|
|
7425
|
-
var
|
|
7425
|
+
var constants2 = {
|
|
7426
7426
|
INDEX_SIZE_ERR,
|
|
7427
7427
|
DOMSTRING_SIZE_ERR: 2,
|
|
7428
7428
|
// historical
|
|
@@ -7461,8 +7461,8 @@ var require_DOMException = __commonJS({
|
|
|
7461
7461
|
this.name = names[code];
|
|
7462
7462
|
}
|
|
7463
7463
|
DOMException.prototype.__proto__ = Error.prototype;
|
|
7464
|
-
for (c in
|
|
7465
|
-
v = { value:
|
|
7464
|
+
for (c in constants2) {
|
|
7465
|
+
v = { value: constants2[c] };
|
|
7466
7466
|
Object.defineProperty(DOMException, c, v);
|
|
7467
7467
|
Object.defineProperty(DOMException.prototype, c, v);
|
|
7468
7468
|
}
|
|
@@ -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 join10(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 = join10(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 join10(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);
|
|
@@ -39350,10 +39350,10 @@ function hasFullDiskAccess() {
|
|
|
39350
39350
|
return false;
|
|
39351
39351
|
}
|
|
39352
39352
|
}
|
|
39353
|
-
function queryNoteData(
|
|
39354
|
-
const pkMatch =
|
|
39353
|
+
function queryNoteData(noteId3) {
|
|
39354
|
+
const pkMatch = noteId3.match(/\/p(\d+)$/);
|
|
39355
39355
|
if (!pkMatch) {
|
|
39356
|
-
console.error(`Invalid note ID format: ${
|
|
39356
|
+
console.error(`Invalid note ID format: ${noteId3}`);
|
|
39357
39357
|
return { hex: null, error: "invalid_id" };
|
|
39358
39358
|
}
|
|
39359
39359
|
const pk = pkMatch[1];
|
|
@@ -39438,13 +39438,13 @@ function parseChecklistFromProtobuf(data) {
|
|
|
39438
39438
|
return null;
|
|
39439
39439
|
}
|
|
39440
39440
|
}
|
|
39441
|
-
function getChecklistItems(
|
|
39442
|
-
const { hex: hexData, error: queryError } = queryNoteData(
|
|
39441
|
+
function getChecklistItems(noteId3) {
|
|
39442
|
+
const { hex: hexData, error: queryError } = queryNoteData(noteId3);
|
|
39443
39443
|
if (queryError === "invalid_id") {
|
|
39444
39444
|
return {
|
|
39445
39445
|
items: null,
|
|
39446
39446
|
error: "invalid_id",
|
|
39447
|
-
message: `Invalid note ID format: "${
|
|
39447
|
+
message: `Invalid note ID format: "${noteId3}". Expected format: x-coredata://UUID/ICNote/pNNN`
|
|
39448
39448
|
};
|
|
39449
39449
|
}
|
|
39450
39450
|
if (queryError === "no_fda") {
|
|
@@ -40281,20 +40281,20 @@ var AppleNotesManager = class {
|
|
|
40281
40281
|
return null;
|
|
40282
40282
|
}
|
|
40283
40283
|
const rawOutput = result.output.trim();
|
|
40284
|
-
const
|
|
40285
|
-
if (!
|
|
40284
|
+
const noteId3 = extractCoreDataId(rawOutput, "note") || rawOutput;
|
|
40285
|
+
if (!noteId3) {
|
|
40286
40286
|
console.error(`Created note "${title}" but Notes.app returned no canonical note ID`);
|
|
40287
40287
|
return null;
|
|
40288
40288
|
}
|
|
40289
40289
|
try {
|
|
40290
|
-
sanitizeNoteId(
|
|
40290
|
+
sanitizeNoteId(noteId3);
|
|
40291
40291
|
} catch {
|
|
40292
40292
|
console.error(`Created note "${title}" but Notes.app returned an invalid note ID`);
|
|
40293
40293
|
return null;
|
|
40294
40294
|
}
|
|
40295
40295
|
const now = /* @__PURE__ */ new Date();
|
|
40296
40296
|
return {
|
|
40297
|
-
id:
|
|
40297
|
+
id: noteId3,
|
|
40298
40298
|
title,
|
|
40299
40299
|
content,
|
|
40300
40300
|
tags,
|
|
@@ -40408,11 +40408,11 @@ var AppleNotesManager = class {
|
|
|
40408
40408
|
for (const item of items) {
|
|
40409
40409
|
const [title, id, folder2, created, modified] = item.split(FIELD_SEP);
|
|
40410
40410
|
if (!title?.trim()) continue;
|
|
40411
|
-
const
|
|
40412
|
-
if (seenIds.has(
|
|
40413
|
-
seenIds.add(
|
|
40411
|
+
const noteId3 = id?.trim() || generateFallbackId();
|
|
40412
|
+
if (seenIds.has(noteId3)) continue;
|
|
40413
|
+
seenIds.add(noteId3);
|
|
40414
40414
|
notes.push({
|
|
40415
|
-
id:
|
|
40415
|
+
id: noteId3,
|
|
40416
40416
|
title: title.trim(),
|
|
40417
40417
|
content: "",
|
|
40418
40418
|
// Not fetched in search
|
|
@@ -40780,10 +40780,10 @@ var AppleNotesManager = class {
|
|
|
40780
40780
|
for (const item of output.split(RECORD_SEP)) {
|
|
40781
40781
|
const [title, id] = item.split(FIELD_SEP);
|
|
40782
40782
|
if (!title?.trim()) continue;
|
|
40783
|
-
const
|
|
40784
|
-
if (seenIds.has(
|
|
40785
|
-
seenIds.add(
|
|
40786
|
-
refs.push({ title: title.trim(), id:
|
|
40783
|
+
const noteId3 = id?.trim() || generateFallbackId();
|
|
40784
|
+
if (seenIds.has(noteId3)) continue;
|
|
40785
|
+
seenIds.add(noteId3);
|
|
40786
|
+
refs.push({ title: title.trim(), id: noteId3 });
|
|
40787
40787
|
if (safeLimit !== void 0 && refs.length >= safeLimit) break;
|
|
40788
40788
|
}
|
|
40789
40789
|
return refs;
|
|
@@ -40930,6 +40930,69 @@ var AppleNotesManager = class {
|
|
|
40930
40930
|
// ===========================================================================
|
|
40931
40931
|
// Folder Operations
|
|
40932
40932
|
// ===========================================================================
|
|
40933
|
+
/** Rename a folder in place while preserving its identity and contents. */
|
|
40934
|
+
renameFolderById(id, expectedName, expectedParentId, newName) {
|
|
40935
|
+
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICFolder\/p\d+$/i.test(id))
|
|
40936
|
+
throw new Error("An exact folder ID is required");
|
|
40937
|
+
if (!newName.trim() || newName.length > 1e3 || Array.from(newName).some((char) => char.charCodeAt(0) < 32))
|
|
40938
|
+
throw new Error("Invalid folder name");
|
|
40939
|
+
const literal2 = (value) => `"${escapePlainStringForAppleScript(value)}"`;
|
|
40940
|
+
const script = `tell application "Notes"
|
|
40941
|
+
set targetFolder to folder id ${literal2(id)}
|
|
40942
|
+
set parentRef to container of targetFolder
|
|
40943
|
+
set parentId to id of parentRef
|
|
40944
|
+
considering case
|
|
40945
|
+
if name of targetFolder is not ${literal2(expectedName)} or parentId is not ${literal2(expectedParentId)} then error "Folder changed; read it again"
|
|
40946
|
+
end considering
|
|
40947
|
+
repeat with sibling in folders of parentRef
|
|
40948
|
+
if name of sibling is ${literal2(newName)} and id of sibling is not ${literal2(id)} then error "A sibling folder already has this name"
|
|
40949
|
+
end repeat
|
|
40950
|
+
set name of targetFolder to ${literal2(newName)}
|
|
40951
|
+
considering case
|
|
40952
|
+
if id of targetFolder is not ${literal2(id)} or name of targetFolder is not ${literal2(newName)} or id of container of targetFolder is not parentId then error "Rename readback failed"
|
|
40953
|
+
end considering
|
|
40954
|
+
return id of targetFolder
|
|
40955
|
+
end tell`;
|
|
40956
|
+
const result = executeMutationAppleScript(script);
|
|
40957
|
+
if (!result.success)
|
|
40958
|
+
throw new Error(
|
|
40959
|
+
result.error || "Rename outcome uncertain; read folder by ID before retrying"
|
|
40960
|
+
);
|
|
40961
|
+
return { id, name: newName, parentId: expectedParentId };
|
|
40962
|
+
}
|
|
40963
|
+
/** Read the exact name and parent identity used by guarded folder rename. */
|
|
40964
|
+
getFolderById(id) {
|
|
40965
|
+
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICFolder\/p\d+$/i.test(id))
|
|
40966
|
+
throw new Error("An exact folder ID is required");
|
|
40967
|
+
const result = executeAppleScript(`tell application "Notes"
|
|
40968
|
+
set f to folder id "${id}"
|
|
40969
|
+
return (name of f) & ${AS_FIELD_SEP} & (id of container of f)
|
|
40970
|
+
end tell`);
|
|
40971
|
+
if (!result.success) throw new Error(result.error || "Folder not found");
|
|
40972
|
+
const [name, parentId] = result.output.replace(/\n$/, "").split(FIELD_SEP);
|
|
40973
|
+
if (!name || !parentId) throw new Error("Incomplete folder metadata");
|
|
40974
|
+
return { id, name, parentId };
|
|
40975
|
+
}
|
|
40976
|
+
/** Insert one file into an unchanged exact note and return Notes' attachment ID. */
|
|
40977
|
+
addAttachmentById(id, expectedBody, filePath) {
|
|
40978
|
+
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(id))
|
|
40979
|
+
throw new Error("Exact note ID required");
|
|
40980
|
+
const quote = (value) => `"${escapePlainStringForAppleScript(value)}"`;
|
|
40981
|
+
const result = executeMutationAppleScript(`tell application "Notes"
|
|
40982
|
+
set n to note id ${quote(id)}
|
|
40983
|
+
if password protected of n then error "Locked note"
|
|
40984
|
+
considering case
|
|
40985
|
+
if body of n is not ${quote(expectedBody)} and body of n is not (${quote(expectedBody)} & linefeed) then error "Note changed before attachment insertion"
|
|
40986
|
+
end considering
|
|
40987
|
+
set a to make new attachment at n with data (POSIX file ${quote(filePath)})
|
|
40988
|
+
return id of a
|
|
40989
|
+
end tell`);
|
|
40990
|
+
if (!result.success)
|
|
40991
|
+
throw new Error(
|
|
40992
|
+
result.error || "Attachment insertion outcome uncertain; read note before retrying"
|
|
40993
|
+
);
|
|
40994
|
+
return result.output.trim();
|
|
40995
|
+
}
|
|
40933
40996
|
/**
|
|
40934
40997
|
* Lists all folders in an account with full hierarchical paths.
|
|
40935
40998
|
*
|
|
@@ -41372,8 +41435,8 @@ var AppleNotesManager = class {
|
|
|
41372
41435
|
* @param separately - Open in a separate window when supported by Notes.app
|
|
41373
41436
|
* @returns true if Notes.app revealed the attachment, false otherwise
|
|
41374
41437
|
*/
|
|
41375
|
-
showAttachmentById(
|
|
41376
|
-
const safeNoteId = sanitizeId(
|
|
41438
|
+
showAttachmentById(noteId3, attachmentId, separately = false) {
|
|
41439
|
+
const safeNoteId = sanitizeId(noteId3);
|
|
41377
41440
|
const safeAttId = escapePlainStringForAppleScript(attachmentId);
|
|
41378
41441
|
const separatelyClause = separately ? " separately true" : "";
|
|
41379
41442
|
const script = `
|
|
@@ -41393,13 +41456,13 @@ var AppleNotesManager = class {
|
|
|
41393
41456
|
const result = executeMutationAppleScript(script);
|
|
41394
41457
|
if (!result.success) {
|
|
41395
41458
|
console.error(
|
|
41396
|
-
`Failed to show attachment "${attachmentId}" on note "${
|
|
41459
|
+
`Failed to show attachment "${attachmentId}" on note "${noteId3}":`,
|
|
41397
41460
|
result.error
|
|
41398
41461
|
);
|
|
41399
41462
|
return false;
|
|
41400
41463
|
}
|
|
41401
41464
|
if ((result.output ?? "").trim().startsWith("ERR")) {
|
|
41402
|
-
console.error(`Attachment "${attachmentId}" not found on note "${
|
|
41465
|
+
console.error(`Attachment "${attachmentId}" not found on note "${noteId3}"`);
|
|
41403
41466
|
return false;
|
|
41404
41467
|
}
|
|
41405
41468
|
return true;
|
|
@@ -41799,7 +41862,7 @@ var AppleNotesManager = class {
|
|
|
41799
41862
|
* @param savePath - absolute destination file path (within home / temp / /Volumes)
|
|
41800
41863
|
* @returns { success, savedPath?, name?, contentType?, error? }
|
|
41801
41864
|
*/
|
|
41802
|
-
saveAttachmentById(
|
|
41865
|
+
saveAttachmentById(noteId3, attachmentId, savePath) {
|
|
41803
41866
|
let abs;
|
|
41804
41867
|
try {
|
|
41805
41868
|
abs = assertSafeSavePath(savePath);
|
|
@@ -41807,7 +41870,7 @@ var AppleNotesManager = class {
|
|
|
41807
41870
|
} catch (e) {
|
|
41808
41871
|
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
|
41809
41872
|
}
|
|
41810
|
-
const safeNoteId = sanitizeId(
|
|
41873
|
+
const safeNoteId = sanitizeId(noteId3);
|
|
41811
41874
|
const safeAttId = escapePlainStringForAppleScript(attachmentId);
|
|
41812
41875
|
const safePath = escapePlainStringForAppleScript(abs);
|
|
41813
41876
|
const script = `
|
|
@@ -41871,11 +41934,11 @@ var AppleNotesManager = class {
|
|
|
41871
41934
|
* @param attachmentId - id of the attachment
|
|
41872
41935
|
* @returns { success, name?, contentType?, base64?, bytes?, error? }
|
|
41873
41936
|
*/
|
|
41874
|
-
getAttachmentBase64ById(
|
|
41937
|
+
getAttachmentBase64ById(noteId3, attachmentId) {
|
|
41875
41938
|
const dir = makeTempDir();
|
|
41876
41939
|
try {
|
|
41877
41940
|
const dest = `${dir}/attachment.bin`;
|
|
41878
|
-
const saved = this.saveAttachmentById(
|
|
41941
|
+
const saved = this.saveAttachmentById(noteId3, attachmentId, dest);
|
|
41879
41942
|
if (!saved.success || !saved.savedPath) {
|
|
41880
41943
|
return { success: false, error: saved.error };
|
|
41881
41944
|
}
|
|
@@ -42375,13 +42438,13 @@ function presentColumns() {
|
|
|
42375
42438
|
}
|
|
42376
42439
|
return cols;
|
|
42377
42440
|
}
|
|
42378
|
-
function getNoteMetadata(
|
|
42379
|
-
const pkMatch =
|
|
42441
|
+
function getNoteMetadata(noteId3) {
|
|
42442
|
+
const pkMatch = noteId3.match(/\/p(\d+)$/);
|
|
42380
42443
|
if (!pkMatch) {
|
|
42381
42444
|
return {
|
|
42382
42445
|
metadata: null,
|
|
42383
42446
|
error: "invalid_id",
|
|
42384
|
-
message: `Invalid note ID format: "${
|
|
42447
|
+
message: `Invalid note ID format: "${noteId3}". Expected format: x-coredata://UUID/ICNote/pNNN`
|
|
42385
42448
|
};
|
|
42386
42449
|
}
|
|
42387
42450
|
const pk = pkMatch[1];
|
|
@@ -42402,7 +42465,7 @@ function getNoteMetadata(noteId) {
|
|
|
42402
42465
|
return {
|
|
42403
42466
|
metadata: null,
|
|
42404
42467
|
error: "not_found",
|
|
42405
|
-
message: `No note found in the database for ID "${
|
|
42468
|
+
message: `No note found in the database for ID "${noteId3}".`
|
|
42406
42469
|
};
|
|
42407
42470
|
}
|
|
42408
42471
|
const raw = JSON.parse(row);
|
|
@@ -42949,6 +43012,380 @@ function parseNoteTable(compressed) {
|
|
|
42949
43012
|
return { rows: values, rowIds: rows.ids, columnIds: columns.ids };
|
|
42950
43013
|
}
|
|
42951
43014
|
|
|
43015
|
+
// src/tools/directOperations.ts
|
|
43016
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
43017
|
+
import {
|
|
43018
|
+
closeSync,
|
|
43019
|
+
constants,
|
|
43020
|
+
fstatSync,
|
|
43021
|
+
mkdtempSync as mkdtempSync2,
|
|
43022
|
+
openSync,
|
|
43023
|
+
readFileSync as readFileSync3,
|
|
43024
|
+
rmSync as rmSync2,
|
|
43025
|
+
writeFileSync
|
|
43026
|
+
} from "node:fs";
|
|
43027
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
43028
|
+
import { basename, isAbsolute as isAbsolute2, join as join8 } from "node:path";
|
|
43029
|
+
var noteId = external_exports.string().regex(/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i);
|
|
43030
|
+
var revision = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
43031
|
+
function readSnapshot(manager, id) {
|
|
43032
|
+
const note = manager.getNoteById(id);
|
|
43033
|
+
if (!note) throw new Error("Note not found");
|
|
43034
|
+
if (note.passwordProtected) throw new Error("Locked notes cannot receive attachments");
|
|
43035
|
+
const body = manager.getNoteContentById(id);
|
|
43036
|
+
if (!body) throw new Error("Note content is unavailable");
|
|
43037
|
+
const enriched = enrichNoteRead(id, body);
|
|
43038
|
+
const rich = readRichNote(id);
|
|
43039
|
+
if (!enriched.complete || enriched.revision !== rich.revision)
|
|
43040
|
+
throw new Error("Native metadata changed during read; read the note again");
|
|
43041
|
+
return { id, title: note.title, body, rich, hash: richContentHash(body, enriched) };
|
|
43042
|
+
}
|
|
43043
|
+
function assertExistingContentPreserved(before, after) {
|
|
43044
|
+
if (before.id !== after.id || before.title !== after.title)
|
|
43045
|
+
throw new Error("Note identity changed");
|
|
43046
|
+
const tidy = (text) => text.replace(/\r\n/g, "\n").replace(/[\s\ufffc]+$/gu, "");
|
|
43047
|
+
if (!tidy(after.rich.text).startsWith(tidy(before.rich.text)))
|
|
43048
|
+
throw new Error("Existing note text was not preserved");
|
|
43049
|
+
if (linkSignature(before.rich.links) !== linkSignature(after.rich.links.slice(0, before.rich.links.length)))
|
|
43050
|
+
throw new Error("Existing links were not preserved");
|
|
43051
|
+
if (before.rich.nativeObjectIds.some((id) => !after.rich.nativeObjectIds.includes(id)))
|
|
43052
|
+
throw new Error("Existing native object was lost");
|
|
43053
|
+
if (before.rich.nativeTags.some((tag) => !after.rich.nativeTags.includes(tag)))
|
|
43054
|
+
throw new Error("Existing native tag was lost");
|
|
43055
|
+
for (const item of before.rich.checklistItems || []) {
|
|
43056
|
+
const actual = after.rich.checklistItems?.find((current) => current.id === item.id);
|
|
43057
|
+
if (!actual || actual.text !== item.text || actual.done !== item.done)
|
|
43058
|
+
throw new Error("Existing checklist item identity or state changed");
|
|
43059
|
+
}
|
|
43060
|
+
for (const object3 of before.rich.objectData || []) {
|
|
43061
|
+
const actual = after.rich.objectData?.find((current) => current.id === object3.id);
|
|
43062
|
+
if (!actual || actual.mergeable !== object3.mergeable || actual.view !== object3.view)
|
|
43063
|
+
throw new Error("Existing native object content or presentation changed");
|
|
43064
|
+
}
|
|
43065
|
+
}
|
|
43066
|
+
function localAttachment(path4) {
|
|
43067
|
+
if (!isAbsolute2(path4)) throw new Error("An absolute local file path is required");
|
|
43068
|
+
const descriptor = openSync(path4, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
43069
|
+
try {
|
|
43070
|
+
const stat = fstatSync(descriptor);
|
|
43071
|
+
if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
|
|
43072
|
+
throw new Error("Attachment must be a nonempty regular file of at most 64 MiB");
|
|
43073
|
+
const bytes = readFileSync3(descriptor);
|
|
43074
|
+
if (bytes.length !== stat.size)
|
|
43075
|
+
throw new Error("Attachment changed while it was being read; try again");
|
|
43076
|
+
return bytes;
|
|
43077
|
+
} finally {
|
|
43078
|
+
closeSync(descriptor);
|
|
43079
|
+
}
|
|
43080
|
+
}
|
|
43081
|
+
function registerDirectOperations(server2, manager) {
|
|
43082
|
+
function tool(name, description, inputSchema, handler, readOnly = false) {
|
|
43083
|
+
server2.registerTool(
|
|
43084
|
+
name,
|
|
43085
|
+
{
|
|
43086
|
+
description,
|
|
43087
|
+
inputSchema,
|
|
43088
|
+
outputSchema: external_exports.object({ ok: external_exports.boolean().optional() }).passthrough(),
|
|
43089
|
+
annotations: { readOnlyHint: readOnly }
|
|
43090
|
+
},
|
|
43091
|
+
(async (args) => {
|
|
43092
|
+
try {
|
|
43093
|
+
const result = handler(args);
|
|
43094
|
+
return {
|
|
43095
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
43096
|
+
structuredContent: result
|
|
43097
|
+
};
|
|
43098
|
+
} catch (error2) {
|
|
43099
|
+
return {
|
|
43100
|
+
content: [
|
|
43101
|
+
{
|
|
43102
|
+
type: "text",
|
|
43103
|
+
text: error2 instanceof Error ? error2.message : String(error2)
|
|
43104
|
+
}
|
|
43105
|
+
],
|
|
43106
|
+
isError: true
|
|
43107
|
+
};
|
|
43108
|
+
}
|
|
43109
|
+
})
|
|
43110
|
+
);
|
|
43111
|
+
}
|
|
43112
|
+
tool(
|
|
43113
|
+
"get-folder-by-id",
|
|
43114
|
+
"Use when: reading the exact folder name and parent before a guarded rename.\nReturns: folder id, current name, and parent id.\nDo not use when: listing folders by path (list-folders).\nSafety: read-only.",
|
|
43115
|
+
{ id: external_exports.string().max(2e3) },
|
|
43116
|
+
({ id }) => manager.getFolderById(id),
|
|
43117
|
+
true
|
|
43118
|
+
);
|
|
43119
|
+
tool(
|
|
43120
|
+
"rename-folder",
|
|
43121
|
+
"Use when: renaming one previously read folder in place.\nReturns: the unchanged folder id, new name, and parent id after readback.\nDo not use when: creating, moving, or deleting a folder.\nSafety: requires the expected current name and parent; refuses stale metadata and sibling conflicts.",
|
|
43122
|
+
{
|
|
43123
|
+
id: external_exports.string().max(2e3),
|
|
43124
|
+
expectedName: external_exports.string().max(1e3),
|
|
43125
|
+
expectedParentId: external_exports.string().max(2e3),
|
|
43126
|
+
newName: external_exports.string().min(1).max(1e3)
|
|
43127
|
+
},
|
|
43128
|
+
(args) => ({
|
|
43129
|
+
ok: true,
|
|
43130
|
+
...manager.renameFolderById(args.id, args.expectedName, args.expectedParentId, args.newName)
|
|
43131
|
+
})
|
|
43132
|
+
);
|
|
43133
|
+
tool(
|
|
43134
|
+
"add-attachment",
|
|
43135
|
+
"Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes.",
|
|
43136
|
+
{ id: noteId, expectedContentHash: revision, path: external_exports.string().min(1).max(4096) },
|
|
43137
|
+
({ id, expectedContentHash, path: path4 }) => {
|
|
43138
|
+
const before = readSnapshot(manager, id);
|
|
43139
|
+
if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
|
|
43140
|
+
const bytes = localAttachment(path4);
|
|
43141
|
+
const beforeAttachments = manager.listAttachmentsById(id);
|
|
43142
|
+
const directory = mkdtempSync2(join8(tmpdir2(), "notes-attachment-add-"));
|
|
43143
|
+
const temporaryFile = join8(directory, basename(path4));
|
|
43144
|
+
try {
|
|
43145
|
+
writeFileSync(temporaryFile, bytes, { mode: 384 });
|
|
43146
|
+
if (readSnapshot(manager, id).hash !== before.hash)
|
|
43147
|
+
throw new Error("Note revision changed");
|
|
43148
|
+
let returnedId;
|
|
43149
|
+
let transportUncertain = false;
|
|
43150
|
+
try {
|
|
43151
|
+
returnedId = manager.addAttachmentById(id, before.body, temporaryFile);
|
|
43152
|
+
} catch {
|
|
43153
|
+
transportUncertain = true;
|
|
43154
|
+
}
|
|
43155
|
+
const after = readSnapshot(manager, id);
|
|
43156
|
+
assertExistingContentPreserved(before, after);
|
|
43157
|
+
const readInserted = () => [
|
|
43158
|
+
...new Map(manager.listAttachmentsById(id).map((item) => [item.id, item])).values()
|
|
43159
|
+
].filter((item) => !beforeAttachments.some((existing) => existing.id === item.id));
|
|
43160
|
+
let inserted = readInserted();
|
|
43161
|
+
for (let attempt = 0; attempt < 4 && (inserted.length !== 1 || returnedId && returnedId !== inserted[0].id); attempt++) {
|
|
43162
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);
|
|
43163
|
+
inserted = readInserted();
|
|
43164
|
+
}
|
|
43165
|
+
const persistentReturnedId = returnedId && /\/ICAttachment\/p\d+$/.test(returnedId);
|
|
43166
|
+
if (inserted.length !== 1 || persistentReturnedId && returnedId !== inserted[0].id)
|
|
43167
|
+
throw new Error(
|
|
43168
|
+
"Attachment insertion outcome uncertain; read the exact note before retrying"
|
|
43169
|
+
);
|
|
43170
|
+
const attachmentId = inserted[0].id;
|
|
43171
|
+
const fetched = manager.getAttachmentBase64ById(id, attachmentId);
|
|
43172
|
+
const actual = typeof fetched.base64 === "string" ? Buffer.from(fetched.base64, "base64") : null;
|
|
43173
|
+
if (!actual || createHash2("sha256").update(actual).digest("hex") !== createHash2("sha256").update(bytes).digest("hex"))
|
|
43174
|
+
throw new Error(
|
|
43175
|
+
"Attachment bytes were not verified; read the exact note before retrying"
|
|
43176
|
+
);
|
|
43177
|
+
return {
|
|
43178
|
+
ok: true,
|
|
43179
|
+
id,
|
|
43180
|
+
attachmentId,
|
|
43181
|
+
contentHash: after.hash,
|
|
43182
|
+
bytes: bytes.length,
|
|
43183
|
+
...transportUncertain ? {
|
|
43184
|
+
transportWarning: "Transport was uncertain; exact bytes and prior content were verified"
|
|
43185
|
+
} : {}
|
|
43186
|
+
};
|
|
43187
|
+
} finally {
|
|
43188
|
+
rmSync2(directory, { recursive: true, force: true });
|
|
43189
|
+
}
|
|
43190
|
+
}
|
|
43191
|
+
);
|
|
43192
|
+
}
|
|
43193
|
+
|
|
43194
|
+
// src/services/nativeTags.ts
|
|
43195
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
43196
|
+
import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
|
|
43197
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
43198
|
+
import { join as join9 } from "node:path";
|
|
43199
|
+
var NATIVE_TAGS_SHORTCUT = "Apple Notes MCP - Native Tags";
|
|
43200
|
+
function normalizeNativeTags(tags) {
|
|
43201
|
+
if (!tags.length || tags.length > 100) throw new Error("Provide between 1 and 100 tags");
|
|
43202
|
+
return [
|
|
43203
|
+
...new Set(
|
|
43204
|
+
tags.map((value) => {
|
|
43205
|
+
const tag = value.normalize("NFC").replace(/^#/, "");
|
|
43206
|
+
if (tag.length > 100 || !new RegExp("^(?=.*\\p{L})[\\p{L}\\p{N}_-]+$", "u").test(tag))
|
|
43207
|
+
throw new Error(
|
|
43208
|
+
"Tags must contain a letter and only letters, digits, hyphens or underscores"
|
|
43209
|
+
);
|
|
43210
|
+
return tag;
|
|
43211
|
+
})
|
|
43212
|
+
)
|
|
43213
|
+
];
|
|
43214
|
+
}
|
|
43215
|
+
function addNativeTags(request, deps) {
|
|
43216
|
+
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(request.id))
|
|
43217
|
+
throw new Error("An exact CoreData note ID is required");
|
|
43218
|
+
if (request.scopeText.length < 12 || request.scopeText.length > 500 || /[\r\n\0]/u.test(request.scopeText))
|
|
43219
|
+
throw new Error("scopeText must be a stable, single-line project marker of 12\u2013500 characters");
|
|
43220
|
+
const tags = normalizeNativeTags(request.tags);
|
|
43221
|
+
const before = deps.read(request.id);
|
|
43222
|
+
if (before.contentHash !== request.expectedContentHash)
|
|
43223
|
+
throw new Error("Note revision changed; read it again");
|
|
43224
|
+
if (before.title !== request.title || !before.plaintext.includes(request.scopeText))
|
|
43225
|
+
throw new Error("The exact note does not match the title and project marker");
|
|
43226
|
+
const missing = tags.filter((tag) => !before.rich.nativeTags.includes(tag));
|
|
43227
|
+
if (!missing.length)
|
|
43228
|
+
return { nativeTags: before.rich.nativeTags, added: [], contentHash: before.contentHash };
|
|
43229
|
+
const candidates = deps.candidates(request.title, request.scopeText);
|
|
43230
|
+
if (candidates.length !== 1 || candidates[0] !== request.id)
|
|
43231
|
+
throw new Error(
|
|
43232
|
+
"Shortcuts selection is ambiguous or points to a different note; nothing was changed"
|
|
43233
|
+
);
|
|
43234
|
+
if (deps.read(request.id).contentHash !== request.expectedContentHash)
|
|
43235
|
+
throw new Error("Note revision changed during preflight; nothing was changed");
|
|
43236
|
+
let transportWarning;
|
|
43237
|
+
try {
|
|
43238
|
+
deps.run({ title: request.title, scopeText: request.scopeText, tags: missing });
|
|
43239
|
+
} catch (error2) {
|
|
43240
|
+
transportWarning = error2 instanceof Error ? error2.message : "Shortcuts completion was uncertain";
|
|
43241
|
+
}
|
|
43242
|
+
const after = deps.read(request.id);
|
|
43243
|
+
const allTags = [.../* @__PURE__ */ new Set([...before.rich.nativeTags, ...tags])];
|
|
43244
|
+
const textWithoutTags = (text) => {
|
|
43245
|
+
for (const tag of [...allTags].sort((a, b) => b.length - a.length)) {
|
|
43246
|
+
const escaped = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43247
|
+
text = text.replace(new RegExp(`#${escaped}(?![\\p{L}\\p{N}_-])`, "gu"), "");
|
|
43248
|
+
}
|
|
43249
|
+
return text.replace(/[\s\ufffc]/gu, "");
|
|
43250
|
+
};
|
|
43251
|
+
if (after.title !== before.title || allTags.some((tag) => !after.rich.nativeTags.includes(tag)) || textWithoutTags(before.rich.text) !== textWithoutTags(after.rich.text) || linkSignature(before.rich.links) !== linkSignature(after.rich.links) || before.rich.nativeObjectIds.some((id) => !after.rich.nativeObjectIds.includes(id)) || before.rich.hasChecklist !== after.rich.hasChecklist) {
|
|
43252
|
+
throw new Error(
|
|
43253
|
+
"Shortcuts ran, but exact-ID tags/text/links readback was not verified. Read the note before any retry"
|
|
43254
|
+
);
|
|
43255
|
+
}
|
|
43256
|
+
return {
|
|
43257
|
+
nativeTags: after.rich.nativeTags,
|
|
43258
|
+
added: missing,
|
|
43259
|
+
contentHash: after.contentHash,
|
|
43260
|
+
...transportWarning ? {
|
|
43261
|
+
transportWarning: "Shortcuts completion was uncertain, but all requested native tags and preserved content were verified by exact ID."
|
|
43262
|
+
} : {}
|
|
43263
|
+
};
|
|
43264
|
+
}
|
|
43265
|
+
function nativeTagsStatus(shortcut = process.env.APPLE_NOTES_MCP_TAGS_SHORTCUT || NATIVE_TAGS_SHORTCUT) {
|
|
43266
|
+
const lines = execFileSync6("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
|
|
43267
|
+
encoding: "utf8",
|
|
43268
|
+
timeout: 15e3,
|
|
43269
|
+
maxBuffer: 1024 * 1024,
|
|
43270
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
43271
|
+
}).split(/\r?\n/u);
|
|
43272
|
+
const matches = lines.flatMap((line) => {
|
|
43273
|
+
const match = /^(.*) \(([0-9A-Fa-f-]{36})\)$/.exec(line);
|
|
43274
|
+
return match && (match[1] === shortcut || match[2].toLowerCase() === shortcut.toLowerCase()) ? [{ name: match[1], identifier: match[2] }] : [];
|
|
43275
|
+
});
|
|
43276
|
+
return {
|
|
43277
|
+
shortcut,
|
|
43278
|
+
installed: matches.length === 1,
|
|
43279
|
+
identifier: matches.length === 1 ? matches[0].identifier : void 0
|
|
43280
|
+
};
|
|
43281
|
+
}
|
|
43282
|
+
function runNativeTagsShortcut(input) {
|
|
43283
|
+
const status = nativeTagsStatus();
|
|
43284
|
+
if (!status.installed)
|
|
43285
|
+
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
43286
|
+
const directory = mkdtempSync3(join9(tmpdir3(), "apple-notes-native-tags-"));
|
|
43287
|
+
try {
|
|
43288
|
+
const path4 = join9(directory, "request.json");
|
|
43289
|
+
writeFileSync2(path4, JSON.stringify(input), { mode: 384 });
|
|
43290
|
+
execFileSync6("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path4], {
|
|
43291
|
+
encoding: "utf8",
|
|
43292
|
+
timeout: 6e4,
|
|
43293
|
+
maxBuffer: 1024 * 1024,
|
|
43294
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
43295
|
+
});
|
|
43296
|
+
} catch {
|
|
43297
|
+
throw new Error(
|
|
43298
|
+
"Native tag operation did not complete cleanly (possibly waiting for macOS permission). Do not retry automatically; read the exact note and check Shortcuts"
|
|
43299
|
+
);
|
|
43300
|
+
} finally {
|
|
43301
|
+
rmSync3(directory, { recursive: true, force: true });
|
|
43302
|
+
}
|
|
43303
|
+
}
|
|
43304
|
+
|
|
43305
|
+
// src/tools/nativeTagsBridge.ts
|
|
43306
|
+
var noteId2 = external_exports.string().regex(/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i);
|
|
43307
|
+
var revision2 = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
|
|
43308
|
+
function registerNativeTagsBridge(server2, manager) {
|
|
43309
|
+
function tool(name, description, inputSchema, handler) {
|
|
43310
|
+
server2.registerTool(
|
|
43311
|
+
name,
|
|
43312
|
+
{
|
|
43313
|
+
description,
|
|
43314
|
+
inputSchema,
|
|
43315
|
+
outputSchema: external_exports.object({ ok: external_exports.boolean().optional() }).passthrough()
|
|
43316
|
+
},
|
|
43317
|
+
(async (args) => {
|
|
43318
|
+
try {
|
|
43319
|
+
const result = handler(args);
|
|
43320
|
+
return {
|
|
43321
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
43322
|
+
structuredContent: result
|
|
43323
|
+
};
|
|
43324
|
+
} catch (error2) {
|
|
43325
|
+
return {
|
|
43326
|
+
content: [
|
|
43327
|
+
{
|
|
43328
|
+
type: "text",
|
|
43329
|
+
text: error2 instanceof Error ? error2.message : String(error2)
|
|
43330
|
+
}
|
|
43331
|
+
],
|
|
43332
|
+
isError: true
|
|
43333
|
+
};
|
|
43334
|
+
}
|
|
43335
|
+
})
|
|
43336
|
+
);
|
|
43337
|
+
}
|
|
43338
|
+
const read = (id) => {
|
|
43339
|
+
const note = manager.getNoteById(id);
|
|
43340
|
+
if (!note) throw new Error("Note not found");
|
|
43341
|
+
if (note.passwordProtected) throw new Error("Locked notes cannot receive native tags");
|
|
43342
|
+
const body = manager.getNoteContentById(id);
|
|
43343
|
+
if (!body) throw new Error("Note content is unavailable");
|
|
43344
|
+
const enriched = enrichNoteRead(id, body);
|
|
43345
|
+
const rich = readRichNote(id);
|
|
43346
|
+
if (!enriched.complete || enriched.revision !== rich.revision)
|
|
43347
|
+
throw new Error("Native metadata changed during read; read the note again");
|
|
43348
|
+
return {
|
|
43349
|
+
contentHash: richContentHash(body, enriched),
|
|
43350
|
+
title: note.title,
|
|
43351
|
+
plaintext: manager.getNotePlaintextById(id),
|
|
43352
|
+
rich
|
|
43353
|
+
};
|
|
43354
|
+
};
|
|
43355
|
+
tool(
|
|
43356
|
+
"native-tags-status",
|
|
43357
|
+
"Use when: checking whether the Native Tags Shortcut is installed before a tag write.\nReturns: the configured Shortcut name, unique installed identifier, and installed state.\nDo not use when: listing tags on notes (list-native-tags).\nSafety: read-only; installation does not by itself prove Notes permission or a successful live mutation.",
|
|
43358
|
+
{},
|
|
43359
|
+
() => nativeTagsStatus()
|
|
43360
|
+
);
|
|
43361
|
+
tool(
|
|
43362
|
+
"add-native-tags",
|
|
43363
|
+
"Use when: adding real clickable Apple Notes tags to one exact, freshly read note.\nReturns: verified native tags, additions, and the post-write content hash.\nDo not use when: textual #hashtags are sufficient or the note cannot be selected uniquely by its existing title and scope text.\nSafety: requires exact ID, fresh revision, and a distinctive existing scope phrase; verifies original text, links, and native objects after the Shortcut runs and never retries automatically.",
|
|
43364
|
+
{
|
|
43365
|
+
id: noteId2,
|
|
43366
|
+
expectedContentHash: revision2,
|
|
43367
|
+
scopeText: external_exports.string().min(12).max(500),
|
|
43368
|
+
tags: external_exports.array(external_exports.string().min(1).max(101)).min(1).max(100)
|
|
43369
|
+
},
|
|
43370
|
+
({ id, expectedContentHash, scopeText, tags }) => {
|
|
43371
|
+
const initial = read(id);
|
|
43372
|
+
const result = addNativeTags(
|
|
43373
|
+
{ id, expectedContentHash, scopeText, tags, title: initial.title },
|
|
43374
|
+
{
|
|
43375
|
+
read,
|
|
43376
|
+
candidates: (title, scope) => manager.listAccounts().flatMap(
|
|
43377
|
+
(account) => manager.searchNotes(title, false, account.name).filter(
|
|
43378
|
+
(note) => !note.passwordProtected && manager.getNotePlaintextById(note.id).toLocaleLowerCase().includes(scope.toLocaleLowerCase())
|
|
43379
|
+
).map((note) => note.id)
|
|
43380
|
+
),
|
|
43381
|
+
run: runNativeTagsShortcut
|
|
43382
|
+
}
|
|
43383
|
+
);
|
|
43384
|
+
return { ok: true, id, ...result };
|
|
43385
|
+
}
|
|
43386
|
+
);
|
|
43387
|
+
}
|
|
43388
|
+
|
|
42952
43389
|
// src/index.ts
|
|
42953
43390
|
loadFileConfig();
|
|
42954
43391
|
var require2 = createRequire(import.meta.url);
|
|
@@ -42959,6 +43396,8 @@ var server = new McpServer({
|
|
|
42959
43396
|
description: "MCP server for managing Apple Notes - create, search, update, and organize notes"
|
|
42960
43397
|
});
|
|
42961
43398
|
var notesManager = new AppleNotesManager();
|
|
43399
|
+
registerDirectOperations(server, notesManager);
|
|
43400
|
+
registerNativeTagsBridge(server, notesManager);
|
|
42962
43401
|
function successResponse(message, structured) {
|
|
42963
43402
|
const res = { content: [{ type: "text", text: message }] };
|
|
42964
43403
|
if (structured) res.structuredContent = structured;
|
|
@@ -44454,8 +44893,8 @@ registerTool(
|
|
|
44454
44893
|
contentType: external_exports.string().optional()
|
|
44455
44894
|
}
|
|
44456
44895
|
},
|
|
44457
|
-
withErrorHandling(({ noteId, attachmentId, savePath }) => {
|
|
44458
|
-
const r = notesManager.saveAttachmentById(
|
|
44896
|
+
withErrorHandling(({ noteId: noteId3, attachmentId, savePath }) => {
|
|
44897
|
+
const r = notesManager.saveAttachmentById(noteId3, attachmentId, savePath);
|
|
44459
44898
|
if (!r.success) {
|
|
44460
44899
|
return errorResponse(`Failed to save attachment: ${r.error ?? "unknown error"}`);
|
|
44461
44900
|
}
|
|
@@ -44481,8 +44920,8 @@ registerTool(
|
|
|
44481
44920
|
base64: external_exports.string().optional()
|
|
44482
44921
|
}
|
|
44483
44922
|
},
|
|
44484
|
-
withErrorHandling(({ noteId, attachmentId }) => {
|
|
44485
|
-
const r = notesManager.getAttachmentBase64ById(
|
|
44923
|
+
withErrorHandling(({ noteId: noteId3, attachmentId }) => {
|
|
44924
|
+
const r = notesManager.getAttachmentBase64ById(noteId3, attachmentId);
|
|
44486
44925
|
if (!r.success || !r.base64) {
|
|
44487
44926
|
return errorResponse(`Failed to fetch attachment: ${r.error ?? "unknown error"}`);
|
|
44488
44927
|
}
|
|
@@ -44507,13 +44946,13 @@ registerTool(
|
|
|
44507
44946
|
separately: external_exports.boolean().optional()
|
|
44508
44947
|
}
|
|
44509
44948
|
},
|
|
44510
|
-
withErrorHandling(({ noteId, attachmentId, separately = false }) => {
|
|
44511
|
-
const success = notesManager.showAttachmentById(
|
|
44949
|
+
withErrorHandling(({ noteId: noteId3, attachmentId, separately = false }) => {
|
|
44950
|
+
const success = notesManager.showAttachmentById(noteId3, attachmentId, separately);
|
|
44512
44951
|
if (!success) {
|
|
44513
|
-
return errorResponse(`Failed to show attachment "${attachmentId}" on note "${
|
|
44952
|
+
return errorResponse(`Failed to show attachment "${attachmentId}" on note "${noteId3}"`);
|
|
44514
44953
|
}
|
|
44515
44954
|
return successResponse(`Shown attachment "${attachmentId}" in Notes.app`, {
|
|
44516
|
-
noteId,
|
|
44955
|
+
noteId: noteId3,
|
|
44517
44956
|
attachmentId,
|
|
44518
44957
|
separately
|
|
44519
44958
|
});
|