apple-notes-mcp 2.8.49 → 2.9.1
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 +72 -1
- package/build/index.js +888 -86
- package/native/private-helper/apple-notes-private-helper.m +641 -0
- package/package.json +2 -1
package/build/index.js
CHANGED
|
@@ -2991,7 +2991,7 @@ var require_compile = __commonJS({
|
|
|
2991
2991
|
const schOrFunc = root.refs[ref];
|
|
2992
2992
|
if (schOrFunc)
|
|
2993
2993
|
return schOrFunc;
|
|
2994
|
-
let _sch =
|
|
2994
|
+
let _sch = resolve6.call(this, root, ref);
|
|
2995
2995
|
if (_sch === void 0) {
|
|
2996
2996
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
2997
2997
|
const { schemaId } = this.opts;
|
|
@@ -3018,7 +3018,7 @@ var require_compile = __commonJS({
|
|
|
3018
3018
|
function sameSchemaEnv(s1, s2) {
|
|
3019
3019
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3020
3020
|
}
|
|
3021
|
-
function
|
|
3021
|
+
function resolve6(root, ref) {
|
|
3022
3022
|
let sch;
|
|
3023
3023
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3024
3024
|
ref = sch;
|
|
@@ -3843,7 +3843,7 @@ var require_fast_uri = __commonJS({
|
|
|
3843
3843
|
}
|
|
3844
3844
|
return uri;
|
|
3845
3845
|
}
|
|
3846
|
-
function
|
|
3846
|
+
function resolve6(baseURI, relativeURI, options) {
|
|
3847
3847
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3848
3848
|
const {
|
|
3849
3849
|
parsed: baseParsed,
|
|
@@ -4205,7 +4205,7 @@ var require_fast_uri = __commonJS({
|
|
|
4205
4205
|
var fastUri = {
|
|
4206
4206
|
SCHEMES,
|
|
4207
4207
|
normalize,
|
|
4208
|
-
resolve:
|
|
4208
|
+
resolve: resolve6,
|
|
4209
4209
|
resolveComponent,
|
|
4210
4210
|
equal,
|
|
4211
4211
|
serialize,
|
|
@@ -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 join26(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 = join26(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 join26(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);
|
|
@@ -36608,7 +36608,7 @@ var Protocol = class {
|
|
|
36608
36608
|
return;
|
|
36609
36609
|
}
|
|
36610
36610
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
36611
|
-
await new Promise((
|
|
36611
|
+
await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
|
|
36612
36612
|
options?.signal?.throwIfAborted();
|
|
36613
36613
|
}
|
|
36614
36614
|
} catch (error2) {
|
|
@@ -36625,7 +36625,7 @@ var Protocol = class {
|
|
|
36625
36625
|
*/
|
|
36626
36626
|
request(request, resultSchema, options) {
|
|
36627
36627
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
36628
|
-
return new Promise((
|
|
36628
|
+
return new Promise((resolve6, reject) => {
|
|
36629
36629
|
const earlyReject = (error2) => {
|
|
36630
36630
|
reject(error2);
|
|
36631
36631
|
};
|
|
@@ -36703,7 +36703,7 @@ var Protocol = class {
|
|
|
36703
36703
|
if (!parseResult.success) {
|
|
36704
36704
|
reject(parseResult.error);
|
|
36705
36705
|
} else {
|
|
36706
|
-
|
|
36706
|
+
resolve6(parseResult.data);
|
|
36707
36707
|
}
|
|
36708
36708
|
} catch (error2) {
|
|
36709
36709
|
reject(error2);
|
|
@@ -36964,12 +36964,12 @@ var Protocol = class {
|
|
|
36964
36964
|
}
|
|
36965
36965
|
} catch {
|
|
36966
36966
|
}
|
|
36967
|
-
return new Promise((
|
|
36967
|
+
return new Promise((resolve6, reject) => {
|
|
36968
36968
|
if (signal.aborted) {
|
|
36969
36969
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
36970
36970
|
return;
|
|
36971
36971
|
}
|
|
36972
|
-
const timeoutId = setTimeout(
|
|
36972
|
+
const timeoutId = setTimeout(resolve6, interval);
|
|
36973
36973
|
signal.addEventListener("abort", () => {
|
|
36974
36974
|
clearTimeout(timeoutId);
|
|
36975
36975
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -38282,7 +38282,7 @@ var McpServer = class {
|
|
|
38282
38282
|
let task = createTaskResult.task;
|
|
38283
38283
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
38284
38284
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
38285
|
-
await new Promise((
|
|
38285
|
+
await new Promise((resolve6) => setTimeout(resolve6, pollInterval));
|
|
38286
38286
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
38287
38287
|
if (!updatedTask) {
|
|
38288
38288
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -38970,12 +38970,12 @@ var StdioServerTransport = class {
|
|
|
38970
38970
|
this.onclose?.();
|
|
38971
38971
|
}
|
|
38972
38972
|
send(message) {
|
|
38973
|
-
return new Promise((
|
|
38973
|
+
return new Promise((resolve6) => {
|
|
38974
38974
|
const json2 = serializeMessage(message);
|
|
38975
38975
|
if (this._stdout.write(json2)) {
|
|
38976
|
-
|
|
38976
|
+
resolve6();
|
|
38977
38977
|
} else {
|
|
38978
|
-
this._stdout.once("drain",
|
|
38978
|
+
this._stdout.once("drain", resolve6);
|
|
38979
38979
|
}
|
|
38980
38980
|
});
|
|
38981
38981
|
}
|
|
@@ -40820,13 +40820,13 @@ function buildSmartFolders(output) {
|
|
|
40820
40820
|
if (lines.length < 4) throw new Error("Unexpected smart folder query output");
|
|
40821
40821
|
const [storeUuid, rowsJson, foldersJson, tagsJson] = lines;
|
|
40822
40822
|
if (!/^[0-9A-F-]+$/i.test(storeUuid)) throw new Error("Unexpected Notes store identifier");
|
|
40823
|
-
const
|
|
40823
|
+
const coreDataId3 = (entity3, pk) => `x-coredata://${storeUuid}/${entity3}/p${pk}`;
|
|
40824
40824
|
const rows = JSON.parse(rowsJson);
|
|
40825
40825
|
const folderRows = JSON.parse(foldersJson || "{}");
|
|
40826
40826
|
const tagRows = JSON.parse(tagsJson || "[]");
|
|
40827
40827
|
const folders = {};
|
|
40828
40828
|
for (const [identifier, row] of Object.entries(folderRows)) {
|
|
40829
|
-
folders[identifier] = { id:
|
|
40829
|
+
folders[identifier] = { id: coreDataId3("ICFolder", row.pk), title: row.title };
|
|
40830
40830
|
}
|
|
40831
40831
|
const tags = {};
|
|
40832
40832
|
for (const tag of tagRows) {
|
|
@@ -40837,14 +40837,14 @@ function buildSmartFolders(output) {
|
|
|
40837
40837
|
return rows.map((row) => {
|
|
40838
40838
|
const decoded = decodeSmartFolderQuery(row.query, { folders, tags });
|
|
40839
40839
|
return {
|
|
40840
|
-
id:
|
|
40840
|
+
id: coreDataId3("ICFolder", row.pk),
|
|
40841
40841
|
identifier: row.identifier,
|
|
40842
40842
|
name: row.title,
|
|
40843
40843
|
account: row.accountName,
|
|
40844
|
-
accountId: row.accountPk === null ? null :
|
|
40844
|
+
accountId: row.accountPk === null ? null : coreDataId3("ICAccount", row.accountPk),
|
|
40845
40845
|
accountIdentifier: row.accountIdentifier,
|
|
40846
40846
|
parent: row.parentTitle,
|
|
40847
|
-
parentId: row.parentPk === null ? null :
|
|
40847
|
+
parentId: row.parentPk === null ? null : coreDataId3("ICFolder", row.parentPk),
|
|
40848
40848
|
parentIdentifier: row.parentIdentifier,
|
|
40849
40849
|
match: decoded.match,
|
|
40850
40850
|
filters: decoded.filters,
|
|
@@ -42140,6 +42140,19 @@ function trashFolderIdList() {
|
|
|
42140
42140
|
const ids = readTrashFolderIds().filter((id2) => FOLDER_ID_PATTERN.test(id2));
|
|
42141
42141
|
return `{${ids.map((id2) => `"${id2}"`).join(", ")}}`;
|
|
42142
42142
|
}
|
|
42143
|
+
function inRecentlyDeletedScript(containerVar, flagVar, trashIds) {
|
|
42144
|
+
return `
|
|
42145
|
+
set ${flagVar} to true
|
|
42146
|
+
try
|
|
42147
|
+
if (class of ${containerVar}) is folder then set ${flagVar} to false
|
|
42148
|
+
end try
|
|
42149
|
+
try
|
|
42150
|
+
if ${trashIds} contains (id of ${containerVar}) then set ${flagVar} to true
|
|
42151
|
+
end try
|
|
42152
|
+
try
|
|
42153
|
+
if (name of ${containerVar}) is "${RECENTLY_DELETED_FOLDER_NAME}" then set ${flagVar} to true
|
|
42154
|
+
end try`;
|
|
42155
|
+
}
|
|
42143
42156
|
function buildTrashNoteIdsCollector() {
|
|
42144
42157
|
return `
|
|
42145
42158
|
set __trashNoteIds to {}
|
|
@@ -42453,8 +42466,8 @@ function folderDeleteFactsScript(id2) {
|
|
|
42453
42466
|
end repeat
|
|
42454
42467
|
set noteCount to count of notes of f`;
|
|
42455
42468
|
}
|
|
42456
|
-
function getNoteLinkFromDB(
|
|
42457
|
-
const match =
|
|
42469
|
+
function getNoteLinkFromDB(coreDataId3) {
|
|
42470
|
+
const match = coreDataId3.match(/\/p(\d+)$/);
|
|
42458
42471
|
if (!match) return null;
|
|
42459
42472
|
const pk = parseInt(match[1], 10);
|
|
42460
42473
|
const dbPath2 = join9(homedir8(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
|
|
@@ -43024,29 +43037,53 @@ var AppleNotesManager = class {
|
|
|
43024
43037
|
* Deletes one exact note only when its complete body still matches the body
|
|
43025
43038
|
* the caller reviewed. The comparison and delete are one AppleScript action,
|
|
43026
43039
|
* so a concurrent edit cannot slip between the guard and deletion.
|
|
43027
|
-
|
|
43028
|
-
|
|
43040
|
+
*
|
|
43041
|
+
* `guards` are other notes that must still be active when the delete runs
|
|
43042
|
+
* (the copy-then-retire guard of delete-note): each must exist, be unlocked,
|
|
43043
|
+
* and sit in a folder other than Recently Deleted, and one with
|
|
43044
|
+
* `expectedBody` must still have that body. They are checked in the same
|
|
43045
|
+
* script, just before the delete; `index` in a guard outcome points into
|
|
43046
|
+
* `guards`.
|
|
43047
|
+
*/
|
|
43048
|
+
deleteNoteByIdIfUnchanged(id2, expectedBody, scope2, guards = []) {
|
|
43029
43049
|
const safeId = sanitizeNoteId(id2);
|
|
43030
43050
|
validateLength(expectedBody, MAX_CONTENT_LENGTH, "Expected note content");
|
|
43031
43051
|
const safeExpectedBody = escapeHtmlForAppleScript(expectedBody);
|
|
43052
|
+
const trashIds = trashFolderIdList();
|
|
43053
|
+
const guardChecks = guards.map((guard, index) => {
|
|
43054
|
+
const safeGuardId = sanitizeNoteId(guard.id);
|
|
43055
|
+
const ref = `__guardRef${index}`;
|
|
43056
|
+
const folderVar = `__guardFolder${index}`;
|
|
43057
|
+
const inactive = (reason) => `return "SAFETY_GUARD_INACTIVE:${index}:${reason}"`;
|
|
43058
|
+
let bodyCheck = "";
|
|
43059
|
+
if (guard.expectedBody !== void 0) {
|
|
43060
|
+
validateLength(guard.expectedBody, MAX_CONTENT_LENGTH, "Expected guard note content");
|
|
43061
|
+
const safeGuardBody = escapeHtmlForAppleScript(guard.expectedBody);
|
|
43062
|
+
bodyCheck = `
|
|
43063
|
+
set __guardBody to body of ${ref}
|
|
43064
|
+
considering case
|
|
43065
|
+
if __guardBody is not "${safeGuardBody}" and __guardBody is not "${safeGuardBody}" & linefeed then return "SAFETY_GUARD_CONFLICT:${index}"
|
|
43066
|
+
end considering`;
|
|
43067
|
+
}
|
|
43068
|
+
return `
|
|
43069
|
+
if not (exists note id "${safeGuardId}") then ${inactive("missing")}
|
|
43070
|
+
set ${ref} to note id "${safeGuardId}"
|
|
43071
|
+
if password protected of ${ref} then ${inactive("locked")}
|
|
43072
|
+
set ${folderVar} to missing value
|
|
43073
|
+
try
|
|
43074
|
+
set ${folderVar} to container of ${ref}
|
|
43075
|
+
end try
|
|
43076
|
+
if ${folderVar} is missing value then ${inactive("folder unknown")}${inRecentlyDeletedScript(folderVar, "__guardInTrash", trashIds)}
|
|
43077
|
+
if __guardInTrash then ${inactive("in Recently Deleted")}${bodyCheck}`;
|
|
43078
|
+
}).join("");
|
|
43032
43079
|
const script = buildAppLevelScript(`
|
|
43033
43080
|
set noteRef to note id "${safeId}"${buildScopeGuardScript("noteRef", scope2)}
|
|
43034
43081
|
set originalFolder to missing value
|
|
43035
43082
|
try
|
|
43036
43083
|
set originalFolder to container of noteRef
|
|
43037
43084
|
end try
|
|
43038
|
-
if originalFolder is missing value then return "SAFETY_CONTAINER_UNKNOWN"
|
|
43039
|
-
|
|
43040
|
-
try
|
|
43041
|
-
if (class of originalFolder) is folder then set __inTrash to false
|
|
43042
|
-
end try
|
|
43043
|
-
try
|
|
43044
|
-
if ${trashFolderIdList()} contains (id of originalFolder) then set __inTrash to true
|
|
43045
|
-
end try
|
|
43046
|
-
try
|
|
43047
|
-
if (name of originalFolder) is "${RECENTLY_DELETED_FOLDER_NAME}" then set __inTrash to true
|
|
43048
|
-
end try
|
|
43049
|
-
if __inTrash then return "SAFETY_IN_RECENTLY_DELETED"
|
|
43085
|
+
if originalFolder is missing value then return "SAFETY_CONTAINER_UNKNOWN"${inRecentlyDeletedScript("originalFolder", "__inTrash", trashIds)}
|
|
43086
|
+
if __inTrash then return "SAFETY_IN_RECENTLY_DELETED"${guardChecks}
|
|
43050
43087
|
set currentBody to body of noteRef
|
|
43051
43088
|
considering case
|
|
43052
43089
|
if currentBody is not "${safeExpectedBody}" and currentBody is not "${safeExpectedBody}" & linefeed then return "SAFETY_CONFLICT"
|
|
@@ -43065,6 +43102,11 @@ var AppleNotesManager = class {
|
|
|
43065
43102
|
const deleteScopeFailure = parseScopeFailure(result.output);
|
|
43066
43103
|
if (deleteScopeFailure) return { status: "scope_conflict", reason: deleteScopeFailure };
|
|
43067
43104
|
const status = result.output.trim();
|
|
43105
|
+
const guardOutcome = /^SAFETY_GUARD_(CONFLICT|INACTIVE):(\d+)(?::(.+))?$/.exec(status);
|
|
43106
|
+
if (guardOutcome && Number(guardOutcome[2]) < guards.length) {
|
|
43107
|
+
const index = Number(guardOutcome[2]);
|
|
43108
|
+
return guardOutcome[1] === "CONFLICT" ? { status: "guard-conflict", index } : { status: "guard-inactive", index, reason: guardOutcome[3] ?? "inactive" };
|
|
43109
|
+
}
|
|
43068
43110
|
if (status === "SAFETY_CONFLICT") return { status: "conflict" };
|
|
43069
43111
|
if (status === "SAFETY_IN_RECENTLY_DELETED") return { status: "in-recently-deleted" };
|
|
43070
43112
|
if (status === "SAFETY_CONTAINER_UNKNOWN") return { status: "container-unknown" };
|
|
@@ -45568,6 +45610,23 @@ function toSpecialRow(row, uuid2, paths, accountNames, kind) {
|
|
|
45568
45610
|
if (kind === "locked" && row.hint) note.passwordHint = row.hint;
|
|
45569
45611
|
return note;
|
|
45570
45612
|
}
|
|
45613
|
+
var EXACT_NOTE_ID = /^x-coredata:\/\/([0-9A-F-]+)\/ICNote\/p(\d+)$/i;
|
|
45614
|
+
function quickNoteFlag(noteId3, dbPath2 = NOTES_DB_PATH7) {
|
|
45615
|
+
const match = EXACT_NOTE_ID.exec(noteId3);
|
|
45616
|
+
if (!match) throw new NoteStoreError(`Not an exact note id: ${noteId3}`, "invalid_input");
|
|
45617
|
+
const columns = readColumns(dbPath2);
|
|
45618
|
+
const sql = `SELECT json_object('uuid', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'quick', (SELECT ${flag(columns, "n", "ZISSYSTEMPAPER")} FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk AND n.Z_ENT = ${entity("ICNote")}));`;
|
|
45619
|
+
const [row] = parseJsonLines(
|
|
45620
|
+
runReadOnlySql(dbPath2, sql, { pk: { int: Number(match[2]) } })
|
|
45621
|
+
);
|
|
45622
|
+
if (row?.uuid?.toUpperCase() !== match[1].toUpperCase()) {
|
|
45623
|
+
throw new NoteStoreError(
|
|
45624
|
+
`Note id ${noteId3} belongs to a different Notes database.`,
|
|
45625
|
+
"invalid_input"
|
|
45626
|
+
);
|
|
45627
|
+
}
|
|
45628
|
+
return row.quick === null ? null : row.quick === 1;
|
|
45629
|
+
}
|
|
45571
45630
|
var TAG_REQUIRED = ["Z_PK", "Z_ENT", "ZFOLDER", "ZNOTE1", "ZTYPEUTI1", "ZALTTEXT", "ZIDENTIFIER"];
|
|
45572
45631
|
function buildTagInventorySql(columns, scoped) {
|
|
45573
45632
|
requireColumns(columns, TAG_REQUIRED, "list-native-tags");
|
|
@@ -46600,13 +46659,13 @@ var escapeSegment = escapeFolderName;
|
|
|
46600
46659
|
function resolveFolders(rows) {
|
|
46601
46660
|
const byPk = new Map(rows.map((row) => [row.pk, row]));
|
|
46602
46661
|
const resolved = /* @__PURE__ */ new Map();
|
|
46603
|
-
const
|
|
46662
|
+
const resolve6 = (pk, seen) => {
|
|
46604
46663
|
const cached2 = resolved.get(pk);
|
|
46605
46664
|
if (cached2) return cached2;
|
|
46606
46665
|
const row = byPk.get(pk);
|
|
46607
46666
|
if (!row || seen.has(pk)) return void 0;
|
|
46608
46667
|
seen.add(pk);
|
|
46609
|
-
const parent = row.parent !== null ?
|
|
46668
|
+
const parent = row.parent !== null ? resolve6(row.parent, seen) : void 0;
|
|
46610
46669
|
const name = row.name ?? "";
|
|
46611
46670
|
const path10 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
|
|
46612
46671
|
const plainPath = parent ? `${parent.plainPath}/${name}` : name;
|
|
@@ -46620,7 +46679,7 @@ function resolveFolders(rows) {
|
|
|
46620
46679
|
resolved.set(pk, info);
|
|
46621
46680
|
return info;
|
|
46622
46681
|
};
|
|
46623
|
-
for (const row of rows)
|
|
46682
|
+
for (const row of rows) resolve6(row.pk, /* @__PURE__ */ new Set());
|
|
46624
46683
|
return resolved;
|
|
46625
46684
|
}
|
|
46626
46685
|
var coreDataMs = (seconds) => seconds === null || !Number.isFinite(seconds) ? void 0 : CORE_DATA_EPOCH_MS2 + seconds * 1e3;
|
|
@@ -47742,35 +47801,37 @@ var FEATURES = [
|
|
|
47742
47801
|
{ kind: "shortcut", name: markdownShortcutName }
|
|
47743
47802
|
]
|
|
47744
47803
|
},
|
|
47745
|
-
// Placeholders for features that need a native helper this
|
|
47746
|
-
// ship.
|
|
47804
|
+
// Placeholders for features that need a native WRITE helper, which this
|
|
47805
|
+
// server does not ship. The opt-in private helper is read-only (write
|
|
47806
|
+
// support was deliberately deferred), so enabling or installing it changes
|
|
47807
|
+
// nothing here: these always report `not_implemented`.
|
|
47747
47808
|
{
|
|
47748
47809
|
name: "checklistToggle",
|
|
47749
47810
|
description: "Check or uncheck an existing checklist item in place",
|
|
47750
47811
|
tools: [],
|
|
47751
47812
|
minimumMacOSVersion: null,
|
|
47752
|
-
requirements: [{ kind: "
|
|
47813
|
+
requirements: [{ kind: "native_write_helper" }]
|
|
47753
47814
|
},
|
|
47754
47815
|
{
|
|
47755
47816
|
name: "smartFolders",
|
|
47756
47817
|
description: "Create or edit Smart Folders and their tag rules",
|
|
47757
47818
|
tools: [],
|
|
47758
47819
|
minimumMacOSVersion: null,
|
|
47759
|
-
requirements: [{ kind: "
|
|
47820
|
+
requirements: [{ kind: "native_write_helper" }]
|
|
47760
47821
|
},
|
|
47761
47822
|
{
|
|
47762
47823
|
name: "paragraphLinks",
|
|
47763
47824
|
description: "Link to a specific paragraph or heading inside a note",
|
|
47764
47825
|
tools: [],
|
|
47765
47826
|
minimumMacOSVersion: null,
|
|
47766
|
-
requirements: [{ kind: "
|
|
47827
|
+
requirements: [{ kind: "native_write_helper" }]
|
|
47767
47828
|
},
|
|
47768
47829
|
{
|
|
47769
47830
|
name: "audioTranscription",
|
|
47770
47831
|
description: "Transcribe audio recordings attached to a note",
|
|
47771
47832
|
tools: [],
|
|
47772
47833
|
minimumMacOSVersion: null,
|
|
47773
|
-
requirements: [{ kind: "
|
|
47834
|
+
requirements: [{ kind: "native_write_helper" }]
|
|
47774
47835
|
}
|
|
47775
47836
|
];
|
|
47776
47837
|
function requirementLabel(requirement) {
|
|
@@ -47818,7 +47879,7 @@ function evaluateFeature(feature, env) {
|
|
|
47818
47879
|
failures.add("shortcut_not_installed");
|
|
47819
47880
|
}
|
|
47820
47881
|
break;
|
|
47821
|
-
case "
|
|
47882
|
+
case "native_write_helper":
|
|
47822
47883
|
missing.push(label);
|
|
47823
47884
|
failures.add("not_implemented");
|
|
47824
47885
|
break;
|
|
@@ -51425,10 +51486,653 @@ function formatShortcutSetup(report) {
|
|
|
51425
51486
|
return lines.join("\n");
|
|
51426
51487
|
}
|
|
51427
51488
|
|
|
51489
|
+
// src/services/privateHelperBuild.ts
|
|
51490
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
51491
|
+
import {
|
|
51492
|
+
chmodSync,
|
|
51493
|
+
existsSync as existsSync15,
|
|
51494
|
+
mkdirSync as mkdirSync6,
|
|
51495
|
+
mkdtempSync as mkdtempSync5,
|
|
51496
|
+
renameSync,
|
|
51497
|
+
rmSync as rmSync5,
|
|
51498
|
+
writeFileSync as writeFileSync4
|
|
51499
|
+
} from "node:fs";
|
|
51500
|
+
import { release as release3 } from "node:os";
|
|
51501
|
+
import { join as join25 } from "node:path";
|
|
51502
|
+
|
|
51503
|
+
// src/services/privateHelper.ts
|
|
51504
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
51505
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
51506
|
+
import { existsSync as existsSync14, readFileSync as readFileSync4 } from "node:fs";
|
|
51507
|
+
import { homedir as homedir19 } from "node:os";
|
|
51508
|
+
import { dirname as dirname6, join as join24, resolve as resolve5 } from "node:path";
|
|
51509
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
51510
|
+
var PRIVATE_HELPER_PROTOCOL = 1;
|
|
51511
|
+
var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
|
|
51512
|
+
var HELPER_DIR_ENV = "APPLE_NOTES_MCP_PRIVATE_HELPER_DIR";
|
|
51513
|
+
var TIMEOUT_ENV = "APPLE_NOTES_MCP_PRIVATE_HELPER_TIMEOUT_MS";
|
|
51514
|
+
var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set([
|
|
51515
|
+
"hello",
|
|
51516
|
+
"probe",
|
|
51517
|
+
"read_note_state"
|
|
51518
|
+
]);
|
|
51519
|
+
var HELPER_BINARY_NAME = "apple-notes-private-helper";
|
|
51520
|
+
var HELPER_SOURCE_RELATIVE = "native/private-helper/apple-notes-private-helper.m";
|
|
51521
|
+
var MANIFEST_NAME = "manifest.json";
|
|
51522
|
+
var DEFAULT_TIMEOUT_MS2 = 2e4;
|
|
51523
|
+
var MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
51524
|
+
var manifestSchema = external_exports.object({
|
|
51525
|
+
schemaVersion: external_exports.literal(1),
|
|
51526
|
+
protocolVersion: external_exports.number().int(),
|
|
51527
|
+
sourceSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
51528
|
+
binarySha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
51529
|
+
builtAt: external_exports.string(),
|
|
51530
|
+
osVersion: external_exports.string(),
|
|
51531
|
+
compiler: external_exports.string()
|
|
51532
|
+
});
|
|
51533
|
+
function packageRoot(fromDir = dirname6(fileURLToPath2(import.meta.url))) {
|
|
51534
|
+
let dir = fromDir;
|
|
51535
|
+
for (; ; ) {
|
|
51536
|
+
const candidate = join24(dir, "package.json");
|
|
51537
|
+
if (existsSync14(candidate)) {
|
|
51538
|
+
try {
|
|
51539
|
+
const pkg = JSON.parse(readFileSync4(candidate, "utf8"));
|
|
51540
|
+
if (pkg.name === "apple-notes-mcp") return dir;
|
|
51541
|
+
} catch {
|
|
51542
|
+
}
|
|
51543
|
+
}
|
|
51544
|
+
const parent = dirname6(dir);
|
|
51545
|
+
if (parent === dir) return resolve5(fromDir, "..");
|
|
51546
|
+
dir = parent;
|
|
51547
|
+
}
|
|
51548
|
+
}
|
|
51549
|
+
function defaultDeps2(overrides = {}) {
|
|
51550
|
+
return {
|
|
51551
|
+
env: process.env,
|
|
51552
|
+
platform: process.platform,
|
|
51553
|
+
sourcePath: join24(packageRoot(), HELPER_SOURCE_RELATIVE),
|
|
51554
|
+
exists: existsSync14,
|
|
51555
|
+
readFile: (path10) => readFileSync4(path10),
|
|
51556
|
+
spawn: spawnSync3,
|
|
51557
|
+
...overrides
|
|
51558
|
+
};
|
|
51559
|
+
}
|
|
51560
|
+
function privateHelperEnabled(env = process.env) {
|
|
51561
|
+
return env[ENABLE_ENV] === "1";
|
|
51562
|
+
}
|
|
51563
|
+
function helperInstallDir(env = process.env) {
|
|
51564
|
+
const override = env[HELPER_DIR_ENV]?.trim();
|
|
51565
|
+
if (override) return override;
|
|
51566
|
+
return join24(homedir19(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
|
|
51567
|
+
}
|
|
51568
|
+
function sha256Hex(data) {
|
|
51569
|
+
return createHash4("sha256").update(data).digest("hex");
|
|
51570
|
+
}
|
|
51571
|
+
function inspectInstallation(deps = defaultDeps2()) {
|
|
51572
|
+
const installDir = helperInstallDir(deps.env);
|
|
51573
|
+
const binaryPath = join24(installDir, HELPER_BINARY_NAME);
|
|
51574
|
+
const base = {
|
|
51575
|
+
installDir,
|
|
51576
|
+
binaryPath,
|
|
51577
|
+
sourcePath: deps.sourcePath,
|
|
51578
|
+
expectedSourceSha256: null,
|
|
51579
|
+
manifest: null
|
|
51580
|
+
};
|
|
51581
|
+
const fail = (reason, detail) => ({
|
|
51582
|
+
...base,
|
|
51583
|
+
ready: false,
|
|
51584
|
+
reason,
|
|
51585
|
+
detail
|
|
51586
|
+
});
|
|
51587
|
+
if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
|
|
51588
|
+
if (!deps.exists(deps.sourcePath))
|
|
51589
|
+
return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
|
|
51590
|
+
base.expectedSourceSha256 = sha256Hex(deps.readFile(deps.sourcePath));
|
|
51591
|
+
const manifestPath = join24(installDir, MANIFEST_NAME);
|
|
51592
|
+
if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
|
|
51593
|
+
return fail(
|
|
51594
|
+
"helper_not_installed",
|
|
51595
|
+
"The private helper is not built. Run `apple-notes-mcp setup --native-helper`."
|
|
51596
|
+
);
|
|
51597
|
+
let manifest;
|
|
51598
|
+
try {
|
|
51599
|
+
manifest = manifestSchema.parse(JSON.parse(deps.readFile(manifestPath).toString("utf8")));
|
|
51600
|
+
} catch (error2) {
|
|
51601
|
+
return fail(
|
|
51602
|
+
"helper_manifest_invalid",
|
|
51603
|
+
`Unreadable helper manifest (${error2 instanceof Error ? error2.message : String(error2)}). Run \`apple-notes-mcp setup --native-helper\`.`
|
|
51604
|
+
);
|
|
51605
|
+
}
|
|
51606
|
+
base.manifest = manifest;
|
|
51607
|
+
if (manifest.sourceSha256 !== base.expectedSourceSha256 || manifest.protocolVersion !== PRIVATE_HELPER_PROTOCOL)
|
|
51608
|
+
return fail(
|
|
51609
|
+
"helper_stale",
|
|
51610
|
+
"The installed helper was built from a different helper source or protocol than this apple-notes-mcp version ships. Run `apple-notes-mcp setup --native-helper` again."
|
|
51611
|
+
);
|
|
51612
|
+
if (sha256Hex(deps.readFile(binaryPath)) !== manifest.binarySha256)
|
|
51613
|
+
return fail(
|
|
51614
|
+
"helper_modified",
|
|
51615
|
+
"The helper binary does not match the checksum recorded when it was built. Run `apple-notes-mcp setup --native-helper` to rebuild it."
|
|
51616
|
+
);
|
|
51617
|
+
return { ...base, ready: true, reason: null, detail: null };
|
|
51618
|
+
}
|
|
51619
|
+
var PrivateHelperError = class extends Error {
|
|
51620
|
+
constructor(code, message, details = {}) {
|
|
51621
|
+
super(message);
|
|
51622
|
+
this.code = code;
|
|
51623
|
+
this.details = details;
|
|
51624
|
+
this.name = "PrivateHelperError";
|
|
51625
|
+
}
|
|
51626
|
+
code;
|
|
51627
|
+
details;
|
|
51628
|
+
};
|
|
51629
|
+
var errorSchema = external_exports.object({
|
|
51630
|
+
status: external_exports.literal("error"),
|
|
51631
|
+
code: external_exports.string(),
|
|
51632
|
+
message: external_exports.string()
|
|
51633
|
+
}).passthrough();
|
|
51634
|
+
var featureSchema = external_exports.object({
|
|
51635
|
+
available: external_exports.boolean(),
|
|
51636
|
+
reason: external_exports.string().nullable(),
|
|
51637
|
+
missing: external_exports.array(external_exports.string())
|
|
51638
|
+
});
|
|
51639
|
+
var helloSchema = external_exports.object({
|
|
51640
|
+
status: external_exports.literal("ok"),
|
|
51641
|
+
protocolVersion: external_exports.number().int(),
|
|
51642
|
+
sourceSha256: external_exports.string(),
|
|
51643
|
+
readOnly: external_exports.literal(true),
|
|
51644
|
+
actions: external_exports.array(external_exports.string())
|
|
51645
|
+
}).passthrough();
|
|
51646
|
+
var probeSchema = external_exports.object({
|
|
51647
|
+
status: external_exports.literal("ok"),
|
|
51648
|
+
protocolVersion: external_exports.number().int(),
|
|
51649
|
+
readOnly: external_exports.literal(true),
|
|
51650
|
+
os: external_exports.object({ version: external_exports.string(), notesAppVersion: external_exports.string().nullable() }).passthrough(),
|
|
51651
|
+
framework: external_exports.object({ loaded: external_exports.boolean(), error: external_exports.string().nullable() }).passthrough(),
|
|
51652
|
+
store: external_exports.object({
|
|
51653
|
+
kind: external_exports.enum(["live", "copy"]).nullable(),
|
|
51654
|
+
opened: external_exports.boolean(),
|
|
51655
|
+
reason: external_exports.string().nullable(),
|
|
51656
|
+
noteRows: external_exports.number().int().nullable()
|
|
51657
|
+
}).passthrough(),
|
|
51658
|
+
syncHostRunning: external_exports.boolean(),
|
|
51659
|
+
features: external_exports.object({ readNoteState: featureSchema }).passthrough()
|
|
51660
|
+
}).passthrough();
|
|
51661
|
+
var cloudSyncSchema = external_exports.object({
|
|
51662
|
+
available: external_exports.boolean(),
|
|
51663
|
+
inICloudAccount: external_exports.boolean(),
|
|
51664
|
+
currentLocalVersion: external_exports.number().int().optional(),
|
|
51665
|
+
latestVersionSyncedToCloud: external_exports.number().int().optional(),
|
|
51666
|
+
uploadPending: external_exports.boolean().optional()
|
|
51667
|
+
}).passthrough();
|
|
51668
|
+
var noteStateSchema = external_exports.object({
|
|
51669
|
+
status: external_exports.literal("ok"),
|
|
51670
|
+
identifier: external_exports.string(),
|
|
51671
|
+
objectURI: external_exports.string(),
|
|
51672
|
+
title: external_exports.string().nullable(),
|
|
51673
|
+
modificationDate: external_exports.string().nullable(),
|
|
51674
|
+
folderIdentifier: external_exports.string().nullable(),
|
|
51675
|
+
passwordProtected: external_exports.boolean(),
|
|
51676
|
+
deletedOrInTrash: external_exports.boolean(),
|
|
51677
|
+
sharedViaICloud: external_exports.boolean(),
|
|
51678
|
+
editable: external_exports.boolean(),
|
|
51679
|
+
revision: external_exports.string().regex(/^r1:[a-f0-9]{64}$/),
|
|
51680
|
+
cloudSync: cloudSyncSchema,
|
|
51681
|
+
syncHostRunning: external_exports.boolean()
|
|
51682
|
+
}).passthrough();
|
|
51683
|
+
function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options = {}) {
|
|
51684
|
+
if (!READ_ONLY_ACTIONS.has(action))
|
|
51685
|
+
throw new PrivateHelperError(
|
|
51686
|
+
"unknown_action",
|
|
51687
|
+
`The private helper is read-only; "${action}" is not a supported action.`
|
|
51688
|
+
);
|
|
51689
|
+
if (!options.allowDisabled && !privateHelperEnabled(deps.env))
|
|
51690
|
+
throw new PrivateHelperError(
|
|
51691
|
+
"disabled",
|
|
51692
|
+
`The private helper is off. Set ${ENABLE_ENV}=1 to opt in.`
|
|
51693
|
+
);
|
|
51694
|
+
let binaryPath = options.binaryPath;
|
|
51695
|
+
if (!binaryPath) {
|
|
51696
|
+
const install = inspectInstallation(deps);
|
|
51697
|
+
if (!install.ready)
|
|
51698
|
+
throw new PrivateHelperError(install.reason || "helper_not_installed", install.detail || "");
|
|
51699
|
+
binaryPath = install.binaryPath;
|
|
51700
|
+
}
|
|
51701
|
+
const timeout = Number.parseInt(deps.env[TIMEOUT_ENV] || "", 10) || DEFAULT_TIMEOUT_MS2;
|
|
51702
|
+
const result = deps.spawn(binaryPath, [], {
|
|
51703
|
+
input: JSON.stringify({ protocol: PRIVATE_HELPER_PROTOCOL, action, ...fields }),
|
|
51704
|
+
encoding: "utf8",
|
|
51705
|
+
timeout,
|
|
51706
|
+
killSignal: "SIGKILL",
|
|
51707
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
51708
|
+
env: deps.env
|
|
51709
|
+
});
|
|
51710
|
+
const errno = result.error?.code;
|
|
51711
|
+
if (errno === "ETIMEDOUT" || result.signal && result.status === null)
|
|
51712
|
+
throw new PrivateHelperError("timeout", `The helper did not answer within ${timeout} ms.`);
|
|
51713
|
+
if (result.error)
|
|
51714
|
+
throw new PrivateHelperError(
|
|
51715
|
+
"helper_unreachable",
|
|
51716
|
+
`Could not run the helper: ${result.error.message}`
|
|
51717
|
+
);
|
|
51718
|
+
const stdout = String(result.stdout ?? "").trim();
|
|
51719
|
+
let parsed;
|
|
51720
|
+
try {
|
|
51721
|
+
parsed = JSON.parse(stdout);
|
|
51722
|
+
} catch {
|
|
51723
|
+
throw new PrivateHelperError(
|
|
51724
|
+
"invalid_response",
|
|
51725
|
+
`The helper exited with status ${result.status} and no JSON response.`
|
|
51726
|
+
);
|
|
51727
|
+
}
|
|
51728
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
51729
|
+
throw new PrivateHelperError("invalid_response", "The helper response is not a JSON object");
|
|
51730
|
+
const object3 = parsed;
|
|
51731
|
+
if (result.status !== 0 || object3.status === "error") {
|
|
51732
|
+
const error2 = errorSchema.safeParse(object3);
|
|
51733
|
+
if (!error2.success)
|
|
51734
|
+
throw new PrivateHelperError(
|
|
51735
|
+
"invalid_response",
|
|
51736
|
+
`The helper failed with an unrecognized error shape (exit ${result.status})`
|
|
51737
|
+
);
|
|
51738
|
+
const { status: _status, code, message, ...details } = error2.data;
|
|
51739
|
+
void _status;
|
|
51740
|
+
throw new PrivateHelperError(code, message, details);
|
|
51741
|
+
}
|
|
51742
|
+
return object3;
|
|
51743
|
+
}
|
|
51744
|
+
function parseOrThrow(schema, value) {
|
|
51745
|
+
const parsed = schema.safeParse(value);
|
|
51746
|
+
if (!parsed.success)
|
|
51747
|
+
throw new PrivateHelperError(
|
|
51748
|
+
"invalid_response",
|
|
51749
|
+
`Unexpected helper response: ${parsed.error.issues.map((i) => i.path.join(".") + " " + i.message).join("; ")}`
|
|
51750
|
+
);
|
|
51751
|
+
return parsed.data;
|
|
51752
|
+
}
|
|
51753
|
+
var UUID = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
|
|
51754
|
+
function assertNoteIdentifier(identifier) {
|
|
51755
|
+
if (!UUID.test(identifier))
|
|
51756
|
+
throw new PrivateHelperError("invalid_request", "identifier must be a Notes UUID");
|
|
51757
|
+
}
|
|
51758
|
+
function probePrivateHelper(deps = defaultDeps2()) {
|
|
51759
|
+
return parseOrThrow(probeSchema, callPrivateHelper("probe", {}, deps));
|
|
51760
|
+
}
|
|
51761
|
+
function readNoteState(identifier, deps = defaultDeps2()) {
|
|
51762
|
+
assertNoteIdentifier(identifier);
|
|
51763
|
+
return parseOrThrow(noteStateSchema, callPrivateHelper("read_note_state", { identifier }, deps));
|
|
51764
|
+
}
|
|
51765
|
+
function featureFromProbe(feature) {
|
|
51766
|
+
if (!feature) return { available: false, reason: "private_api_unavailable", detail: null };
|
|
51767
|
+
if (feature.available) return { available: true, reason: null, detail: null };
|
|
51768
|
+
const reason = feature.reason === "store_unavailable" || feature.reason === "disabled" ? feature.reason : "private_api_unavailable";
|
|
51769
|
+
return {
|
|
51770
|
+
available: false,
|
|
51771
|
+
reason,
|
|
51772
|
+
detail: feature.missing.length ? `missing: ${feature.missing.join(", ")}` : feature.reason
|
|
51773
|
+
};
|
|
51774
|
+
}
|
|
51775
|
+
function privateHelperCapabilities(deps = defaultDeps2()) {
|
|
51776
|
+
const enabled = privateHelperEnabled(deps.env);
|
|
51777
|
+
const installation = inspectInstallation(deps);
|
|
51778
|
+
const off = (reason, detail) => ({
|
|
51779
|
+
available: false,
|
|
51780
|
+
reason,
|
|
51781
|
+
detail
|
|
51782
|
+
});
|
|
51783
|
+
const both = (status) => ({ readNoteState: status });
|
|
51784
|
+
if (installation.reason === "unsupported_platform")
|
|
51785
|
+
return {
|
|
51786
|
+
enabled,
|
|
51787
|
+
readOnly: true,
|
|
51788
|
+
installation,
|
|
51789
|
+
probe: null,
|
|
51790
|
+
features: both(off("unsupported_platform", null))
|
|
51791
|
+
};
|
|
51792
|
+
if (!enabled)
|
|
51793
|
+
return {
|
|
51794
|
+
enabled,
|
|
51795
|
+
readOnly: true,
|
|
51796
|
+
installation,
|
|
51797
|
+
probe: null,
|
|
51798
|
+
features: both(off("disabled", `Set ${ENABLE_ENV}=1 to opt in to the private helper.`))
|
|
51799
|
+
};
|
|
51800
|
+
if (!installation.ready)
|
|
51801
|
+
return {
|
|
51802
|
+
enabled,
|
|
51803
|
+
readOnly: true,
|
|
51804
|
+
installation,
|
|
51805
|
+
probe: null,
|
|
51806
|
+
features: both(off(installation.reason || "helper_not_installed", installation.detail))
|
|
51807
|
+
};
|
|
51808
|
+
let probe;
|
|
51809
|
+
try {
|
|
51810
|
+
probe = probePrivateHelper(deps);
|
|
51811
|
+
} catch (error2) {
|
|
51812
|
+
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
51813
|
+
return {
|
|
51814
|
+
enabled,
|
|
51815
|
+
readOnly: true,
|
|
51816
|
+
installation,
|
|
51817
|
+
probe: null,
|
|
51818
|
+
features: both(off("helper_unreachable", detail))
|
|
51819
|
+
};
|
|
51820
|
+
}
|
|
51821
|
+
return {
|
|
51822
|
+
enabled,
|
|
51823
|
+
readOnly: true,
|
|
51824
|
+
installation,
|
|
51825
|
+
probe,
|
|
51826
|
+
features: { readNoteState: featureFromProbe(probe.features.readNoteState) }
|
|
51827
|
+
};
|
|
51828
|
+
}
|
|
51829
|
+
|
|
51830
|
+
// src/services/privateHelperBuild.ts
|
|
51831
|
+
function defaultBuildDeps() {
|
|
51832
|
+
return {
|
|
51833
|
+
...defaultDeps2(),
|
|
51834
|
+
osVersion: () => {
|
|
51835
|
+
const r = spawnSync4("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
|
|
51836
|
+
return r.status === 0 ? r.stdout.trim() : `Darwin ${release3()}`;
|
|
51837
|
+
},
|
|
51838
|
+
now: () => /* @__PURE__ */ new Date()
|
|
51839
|
+
};
|
|
51840
|
+
}
|
|
51841
|
+
function compileArguments(sourcePath, outputPath, sourceSha) {
|
|
51842
|
+
return [
|
|
51843
|
+
"clang",
|
|
51844
|
+
"-fobjc-arc",
|
|
51845
|
+
"-O2",
|
|
51846
|
+
"-Wall",
|
|
51847
|
+
"-framework",
|
|
51848
|
+
"Foundation",
|
|
51849
|
+
"-framework",
|
|
51850
|
+
"CoreData",
|
|
51851
|
+
"-framework",
|
|
51852
|
+
"AppKit",
|
|
51853
|
+
// Embedded so `hello` can prove which source the binary came from.
|
|
51854
|
+
`-DHELPER_SOURCE_SHA256="${sourceSha}"`,
|
|
51855
|
+
"-o",
|
|
51856
|
+
outputPath,
|
|
51857
|
+
sourcePath
|
|
51858
|
+
];
|
|
51859
|
+
}
|
|
51860
|
+
function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
|
|
51861
|
+
const steps = [];
|
|
51862
|
+
const done = (ok) => ({
|
|
51863
|
+
ok,
|
|
51864
|
+
checkOnly,
|
|
51865
|
+
steps,
|
|
51866
|
+
installation: inspectInstallation(deps)
|
|
51867
|
+
});
|
|
51868
|
+
if (checkOnly) {
|
|
51869
|
+
const installation2 = inspectInstallation(deps);
|
|
51870
|
+
steps.push({
|
|
51871
|
+
step: "inspect installed helper",
|
|
51872
|
+
ok: installation2.ready,
|
|
51873
|
+
detail: installation2.ready ? installation2.binaryPath : installation2.detail || void 0
|
|
51874
|
+
});
|
|
51875
|
+
return { ok: installation2.ready, checkOnly, steps, installation: installation2 };
|
|
51876
|
+
}
|
|
51877
|
+
if (deps.platform !== "darwin") {
|
|
51878
|
+
steps.push({ step: "platform", ok: false, detail: "macOS only" });
|
|
51879
|
+
return done(false);
|
|
51880
|
+
}
|
|
51881
|
+
if (!deps.exists(deps.sourcePath)) {
|
|
51882
|
+
steps.push({ step: "locate source", ok: false, detail: deps.sourcePath });
|
|
51883
|
+
return done(false);
|
|
51884
|
+
}
|
|
51885
|
+
const source = deps.readFile(deps.sourcePath);
|
|
51886
|
+
const sourceSha = sha256Hex(source);
|
|
51887
|
+
steps.push({
|
|
51888
|
+
step: "locate source",
|
|
51889
|
+
ok: true,
|
|
51890
|
+
detail: `${deps.sourcePath} (sha256 ${sourceSha})`
|
|
51891
|
+
});
|
|
51892
|
+
const clang = deps.spawn("/usr/bin/xcrun", ["--find", "clang"], { encoding: "utf8" });
|
|
51893
|
+
if (clang.status !== 0) {
|
|
51894
|
+
steps.push({
|
|
51895
|
+
step: "find compiler",
|
|
51896
|
+
ok: false,
|
|
51897
|
+
detail: "No clang found. Install the Command Line Tools with `xcode-select --install`."
|
|
51898
|
+
});
|
|
51899
|
+
return done(false);
|
|
51900
|
+
}
|
|
51901
|
+
const clangVersion = deps.spawn("/usr/bin/xcrun", ["clang", "--version"], { encoding: "utf8" });
|
|
51902
|
+
const compiler = String(clangVersion.stdout || "").split("\n")[0] || "clang";
|
|
51903
|
+
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
51904
|
+
const installDir = helperInstallDir(deps.env);
|
|
51905
|
+
mkdirSync6(installDir, { recursive: true, mode: 448 });
|
|
51906
|
+
const staging = mkdtempSync5(join25(installDir, ".staging-"));
|
|
51907
|
+
try {
|
|
51908
|
+
const stagedBinary = join25(staging, HELPER_BINARY_NAME);
|
|
51909
|
+
const compile = deps.spawn(
|
|
51910
|
+
"/usr/bin/xcrun",
|
|
51911
|
+
compileArguments(deps.sourcePath, stagedBinary, sourceSha),
|
|
51912
|
+
{
|
|
51913
|
+
encoding: "utf8",
|
|
51914
|
+
timeout: 18e4
|
|
51915
|
+
}
|
|
51916
|
+
);
|
|
51917
|
+
if (compile.status !== 0) {
|
|
51918
|
+
steps.push({
|
|
51919
|
+
step: "compile",
|
|
51920
|
+
ok: false,
|
|
51921
|
+
detail: String(compile.stderr || compile.error?.message || "clang failed").slice(0, 4e3)
|
|
51922
|
+
});
|
|
51923
|
+
return done(false);
|
|
51924
|
+
}
|
|
51925
|
+
steps.push({ step: "compile", ok: true });
|
|
51926
|
+
const sign = deps.spawn(
|
|
51927
|
+
"/usr/bin/codesign",
|
|
51928
|
+
["--force", "--sign", "-", "--identifier", "apple-notes-mcp.private-helper", stagedBinary],
|
|
51929
|
+
{ encoding: "utf8" }
|
|
51930
|
+
);
|
|
51931
|
+
if (sign.status !== 0) {
|
|
51932
|
+
steps.push({
|
|
51933
|
+
step: "ad-hoc sign",
|
|
51934
|
+
ok: false,
|
|
51935
|
+
detail: String(sign.stderr || "codesign failed")
|
|
51936
|
+
});
|
|
51937
|
+
return done(false);
|
|
51938
|
+
}
|
|
51939
|
+
steps.push({ step: "ad-hoc sign", ok: true });
|
|
51940
|
+
let hello;
|
|
51941
|
+
try {
|
|
51942
|
+
hello = helloSchema.parse(
|
|
51943
|
+
callPrivateHelper("hello", {}, deps, { allowDisabled: true, binaryPath: stagedBinary })
|
|
51944
|
+
);
|
|
51945
|
+
} catch (error2) {
|
|
51946
|
+
steps.push({
|
|
51947
|
+
step: "handshake",
|
|
51948
|
+
ok: false,
|
|
51949
|
+
detail: error2 instanceof Error ? error2.message : String(error2)
|
|
51950
|
+
});
|
|
51951
|
+
return done(false);
|
|
51952
|
+
}
|
|
51953
|
+
if (hello.protocolVersion !== PRIVATE_HELPER_PROTOCOL || hello.sourceSha256 !== sourceSha) {
|
|
51954
|
+
steps.push({
|
|
51955
|
+
step: "handshake",
|
|
51956
|
+
ok: false,
|
|
51957
|
+
detail: `helper reported protocol ${hello.protocolVersion}, source ${hello.sourceSha256}`
|
|
51958
|
+
});
|
|
51959
|
+
return done(false);
|
|
51960
|
+
}
|
|
51961
|
+
const writeActions = hello.actions.filter((action) => !READ_ONLY_ACTIONS.has(action));
|
|
51962
|
+
if (writeActions.length) {
|
|
51963
|
+
steps.push({
|
|
51964
|
+
step: "handshake",
|
|
51965
|
+
ok: false,
|
|
51966
|
+
detail: `helper offers non-read-only actions (${writeActions.join(", ")}); refusing to install`
|
|
51967
|
+
});
|
|
51968
|
+
return done(false);
|
|
51969
|
+
}
|
|
51970
|
+
steps.push({
|
|
51971
|
+
step: "handshake",
|
|
51972
|
+
ok: true,
|
|
51973
|
+
detail: `protocol ${hello.protocolVersion}, read-only`
|
|
51974
|
+
});
|
|
51975
|
+
const manifest = {
|
|
51976
|
+
schemaVersion: 1,
|
|
51977
|
+
protocolVersion: hello.protocolVersion,
|
|
51978
|
+
sourceSha256: sourceSha,
|
|
51979
|
+
binarySha256: sha256Hex(deps.readFile(stagedBinary)),
|
|
51980
|
+
builtAt: deps.now().toISOString(),
|
|
51981
|
+
osVersion: deps.osVersion(),
|
|
51982
|
+
compiler
|
|
51983
|
+
};
|
|
51984
|
+
chmodSync(stagedBinary, 448);
|
|
51985
|
+
renameSync(stagedBinary, join25(installDir, HELPER_BINARY_NAME));
|
|
51986
|
+
writeFileSync4(join25(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
|
|
51987
|
+
mode: 384
|
|
51988
|
+
});
|
|
51989
|
+
steps.push({ step: "install", ok: true, detail: installDir });
|
|
51990
|
+
} finally {
|
|
51991
|
+
if (existsSync15(staging)) rmSync5(staging, { recursive: true, force: true });
|
|
51992
|
+
}
|
|
51993
|
+
const installation = inspectInstallation(deps);
|
|
51994
|
+
steps.push({
|
|
51995
|
+
step: "verify installation",
|
|
51996
|
+
ok: installation.ready,
|
|
51997
|
+
detail: installation.ready ? void 0 : installation.detail || void 0
|
|
51998
|
+
});
|
|
51999
|
+
return { ok: installation.ready, checkOnly, steps, installation };
|
|
52000
|
+
}
|
|
52001
|
+
function formatHelperBuild(report) {
|
|
52002
|
+
const lines = ["Apple Notes MCP private helper", ""];
|
|
52003
|
+
for (const step of report.steps)
|
|
52004
|
+
lines.push(`${step.ok ? "\u2713" : "\u2717"} ${step.step}${step.detail ? `: ${step.detail}` : ""}`);
|
|
52005
|
+
lines.push("");
|
|
52006
|
+
if (report.ok) {
|
|
52007
|
+
lines.push(`Installed at ${report.installation.binaryPath}.`);
|
|
52008
|
+
lines.push(
|
|
52009
|
+
"The helper stays off until you set APPLE_NOTES_MCP_ENABLE_PRIVATE=1 for the MCP server, and it needs the same Full Disk Access grant as the server's database reads."
|
|
52010
|
+
);
|
|
52011
|
+
} else if (report.checkOnly) {
|
|
52012
|
+
lines.push("Run `apple-notes-mcp setup --native-helper` to build it.");
|
|
52013
|
+
} else {
|
|
52014
|
+
lines.push("The helper was not installed. Fix the failed step above and run setup again.");
|
|
52015
|
+
}
|
|
52016
|
+
return lines.join("\n");
|
|
52017
|
+
}
|
|
52018
|
+
|
|
52019
|
+
// src/tools/privateHelperTools.ts
|
|
52020
|
+
var coreDataId2 = external_exports.string().regex(/^x-coredata:\/\/[0-9A-F-]+\/ICNote\/p\d+$/i);
|
|
52021
|
+
var notesUuid = external_exports.string().regex(UUID_PATTERN);
|
|
52022
|
+
function resolveIdentifier(manager, args) {
|
|
52023
|
+
if (args.identifier && args.id)
|
|
52024
|
+
throw new PrivateHelperError("invalid_request", "Pass identifier or id, not both");
|
|
52025
|
+
if (args.identifier) return args.identifier;
|
|
52026
|
+
if (!args.id) throw new PrivateHelperError("invalid_request", "identifier or id is required");
|
|
52027
|
+
const link = manager.getNoteLinkById(args.id);
|
|
52028
|
+
const match = link?.match(/identifier=([0-9A-F-]{36})$/i);
|
|
52029
|
+
if (!match)
|
|
52030
|
+
throw new PrivateHelperError(
|
|
52031
|
+
"not_found",
|
|
52032
|
+
"Could not resolve that id to a Notes UUID (needs Full Disk Access); pass identifier instead"
|
|
52033
|
+
);
|
|
52034
|
+
return match[1];
|
|
52035
|
+
}
|
|
52036
|
+
function envelopeCode(helperCode, message) {
|
|
52037
|
+
switch (helperCode) {
|
|
52038
|
+
case "not_found":
|
|
52039
|
+
return "not_found";
|
|
52040
|
+
case "timeout":
|
|
52041
|
+
return "timeout_indeterminate";
|
|
52042
|
+
case "invalid_request":
|
|
52043
|
+
case "invalid_json":
|
|
52044
|
+
case "input_too_large":
|
|
52045
|
+
case "unknown_action":
|
|
52046
|
+
return "validation_error";
|
|
52047
|
+
case "store_unavailable":
|
|
52048
|
+
return /Full Disk Access/i.test(message) ? "full_disk_access_missing" : "operation_failed";
|
|
52049
|
+
case "disabled":
|
|
52050
|
+
case "unsupported_platform":
|
|
52051
|
+
case "unsupported_note":
|
|
52052
|
+
case "private_api_unavailable":
|
|
52053
|
+
case "protocol_mismatch":
|
|
52054
|
+
case "helper_not_installed":
|
|
52055
|
+
case "helper_stale":
|
|
52056
|
+
case "helper_modified":
|
|
52057
|
+
case "helper_manifest_invalid":
|
|
52058
|
+
return "unsupported";
|
|
52059
|
+
default:
|
|
52060
|
+
return "operation_failed";
|
|
52061
|
+
}
|
|
52062
|
+
}
|
|
52063
|
+
function helperErrorResult(error2) {
|
|
52064
|
+
if (!(error2 instanceof PrivateHelperError)) {
|
|
52065
|
+
const message2 = error2 instanceof Error ? error2.message : String(error2);
|
|
52066
|
+
return errorResult(`native helper: ${message2}`, error2);
|
|
52067
|
+
}
|
|
52068
|
+
const message = `native helper (${error2.code}): ${error2.message}`;
|
|
52069
|
+
return errorResult(
|
|
52070
|
+
message,
|
|
52071
|
+
new CodedError(message, {
|
|
52072
|
+
...error2.details,
|
|
52073
|
+
code: envelopeCode(error2.code, error2.message),
|
|
52074
|
+
helperCode: error2.code,
|
|
52075
|
+
committed: false
|
|
52076
|
+
})
|
|
52077
|
+
);
|
|
52078
|
+
}
|
|
52079
|
+
function registerPrivateHelperTools(server2, manager, depsFactory = () => defaultDeps2()) {
|
|
52080
|
+
function tool(name, description, inputSchema, annotations, handler) {
|
|
52081
|
+
server2.registerTool(
|
|
52082
|
+
name,
|
|
52083
|
+
{
|
|
52084
|
+
description,
|
|
52085
|
+
inputSchema,
|
|
52086
|
+
annotations,
|
|
52087
|
+
outputSchema: external_exports.object({ ok: external_exports.boolean().optional() }).passthrough()
|
|
52088
|
+
},
|
|
52089
|
+
(async (args) => {
|
|
52090
|
+
try {
|
|
52091
|
+
const result = { ok: true, ...handler(args, depsFactory()) };
|
|
52092
|
+
return {
|
|
52093
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
52094
|
+
structuredContent: result
|
|
52095
|
+
};
|
|
52096
|
+
} catch (error2) {
|
|
52097
|
+
return helperErrorResult(error2);
|
|
52098
|
+
}
|
|
52099
|
+
})
|
|
52100
|
+
);
|
|
52101
|
+
}
|
|
52102
|
+
tool(
|
|
52103
|
+
"native-helper-status",
|
|
52104
|
+
"Use when: checking whether the opt-in, read-only native private helper is enabled, built, current, and working on this macOS before calling native-note-state.\nReturns: enabled flag, installation state (path, manifest, stale/modified checks), the live probe (macOS and Notes versions, framework, store access), and per-feature availability with a machine reason.\nDo not use when: checking the Shortcuts bridges (native-tags-status, get-capabilities).\nSafety: read-only. The helper is read-only by design (write support was deliberately deferred by the maintainer); the probe opens the Notes store read-only and only when the helper is enabled and installed.",
|
|
52105
|
+
{},
|
|
52106
|
+
{ readOnlyHint: true, openWorldHint: false },
|
|
52107
|
+
(_args, deps) => {
|
|
52108
|
+
const capabilities = privateHelperCapabilities(deps);
|
|
52109
|
+
return {
|
|
52110
|
+
...capabilities,
|
|
52111
|
+
...capabilities.installation.ready ? {} : { setupCommand: "apple-notes-mcp setup --native-helper" }
|
|
52112
|
+
};
|
|
52113
|
+
}
|
|
52114
|
+
);
|
|
52115
|
+
tool(
|
|
52116
|
+
"native-note-state",
|
|
52117
|
+
"Use when: you need a note's native title, modification date, folder identifier, lock/trash/shared/editable flags, or iCloud upload state as Notes' own data model reports them.\nReturns: identifier, title, modificationDate, folderIdentifier, lock/trash/shared/editable flags, `revision` (an opaque change token; compare two reads to detect a change), and cloudSync versions.\nDo not use when: reading note content (get-note-content, get-note-markdown).\nSafety: read-only; the helper opens the store with Core Data's read-only option and has no write action. Requires APPLE_NOTES_MCP_ENABLE_PRIVATE=1 and a built helper.",
|
|
52118
|
+
{
|
|
52119
|
+
identifier: notesUuid.optional().describe("Notes UUID (the notes://showNote identifier)"),
|
|
52120
|
+
id: coreDataId2.optional().describe("x-coredata note id; resolved to a UUID via the database")
|
|
52121
|
+
},
|
|
52122
|
+
{ readOnlyHint: true, openWorldHint: false },
|
|
52123
|
+
(args, deps) => ({ ...readNoteState(resolveIdentifier(manager, args), deps) })
|
|
52124
|
+
);
|
|
52125
|
+
}
|
|
52126
|
+
|
|
51428
52127
|
// src/index.ts
|
|
51429
52128
|
loadFileConfig();
|
|
51430
52129
|
var require2 = createRequire(import.meta.url);
|
|
51431
52130
|
var { version: version2 } = require2("../package.json");
|
|
52131
|
+
if (process.argv[2] === "setup" && process.argv.slice(3).includes("--native-helper")) {
|
|
52132
|
+
const report = buildPrivateHelper(process.argv.slice(3).includes("--check"));
|
|
52133
|
+
process.stdout.write(formatHelperBuild(report) + "\n");
|
|
52134
|
+
process.exit(report.ok ? 0 : 1);
|
|
52135
|
+
}
|
|
51432
52136
|
if (process.argv[2] === "setup") {
|
|
51433
52137
|
const report = setupShortcuts(process.argv.slice(3).includes("--check"));
|
|
51434
52138
|
process.stdout.write(formatShortcutSetup(report) + "\n");
|
|
@@ -51444,6 +52148,7 @@ registerDirectOperations(server, notesManager);
|
|
|
51444
52148
|
registerFolderDelete(server, notesManager);
|
|
51445
52149
|
registerNativeTagsBridge(server, notesManager);
|
|
51446
52150
|
registerNativeOperations(server, notesManager);
|
|
52151
|
+
registerPrivateHelperTools(server, notesManager);
|
|
51447
52152
|
function successResponse(message, structured) {
|
|
51448
52153
|
const res = { content: [{ type: "text", text: message }] };
|
|
51449
52154
|
if (structured) res.structuredContent = structured;
|
|
@@ -51564,6 +52269,59 @@ function inRecentlyDeletedMessage(title) {
|
|
|
51564
52269
|
function revisionConflictMessage(title) {
|
|
51565
52270
|
return `Note "${title}" changed after it was read. Read it again and review the newer version before retrying.`;
|
|
51566
52271
|
}
|
|
52272
|
+
function quickNoteGuardRefusal(label, id2) {
|
|
52273
|
+
let quick;
|
|
52274
|
+
try {
|
|
52275
|
+
quick = quickNoteFlag(id2);
|
|
52276
|
+
} catch (error2) {
|
|
52277
|
+
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
52278
|
+
if (error2 instanceof NoteStoreError && error2.kind === "no_fda") {
|
|
52279
|
+
return `${label} note cannot be checked: the delete-note guard needs Full Disk Access to rule out a Quick Note. Nothing was deleted. ${detail}`;
|
|
52280
|
+
}
|
|
52281
|
+
return `${label} note could not be checked (${detail}). Nothing was deleted.`;
|
|
52282
|
+
}
|
|
52283
|
+
return quick ? `${label} note is a Quick Note; a guard must be an ordinary note. Nothing was deleted.` : null;
|
|
52284
|
+
}
|
|
52285
|
+
function prepareDeleteGuards(args) {
|
|
52286
|
+
const { id: id2, guardNoteId, expectedGuardContentHash, requireActiveNoteId } = args;
|
|
52287
|
+
if (guardNoteId === void 0 !== (expectedGuardContentHash === void 0)) {
|
|
52288
|
+
return { error: "Pass guardNoteId and expectedGuardContentHash together." };
|
|
52289
|
+
}
|
|
52290
|
+
if (guardNoteId === id2 || requireActiveNoteId === id2) {
|
|
52291
|
+
return { error: "A guard note must be a different note from the one being deleted." };
|
|
52292
|
+
}
|
|
52293
|
+
if (guardNoteId !== void 0 && guardNoteId === requireActiveNoteId) {
|
|
52294
|
+
return { error: "requireActiveNoteId repeats guardNoteId; pass only guardNoteId." };
|
|
52295
|
+
}
|
|
52296
|
+
const prepared = { guards: [], labels: [] };
|
|
52297
|
+
if (guardNoteId !== void 0) {
|
|
52298
|
+
const refusal = quickNoteGuardRefusal("Guard", guardNoteId);
|
|
52299
|
+
if (refusal) return { error: refusal };
|
|
52300
|
+
const guard = readExactNoteSnapshot(guardNoteId);
|
|
52301
|
+
if ("error" in guard) return { error: `Guard note: ${guard.error}` };
|
|
52302
|
+
if (guard.contentHash !== expectedGuardContentHash) {
|
|
52303
|
+
return {
|
|
52304
|
+
error: `Guard note "${guard.note.title}" changed after it was read. Verify the copy again before retiring the original. Nothing was deleted.`
|
|
52305
|
+
};
|
|
52306
|
+
}
|
|
52307
|
+
prepared.guards.push({ id: guardNoteId, expectedBody: guard.body });
|
|
52308
|
+
prepared.labels.push("Guard");
|
|
52309
|
+
prepared.guardContentHash = guard.contentHash;
|
|
52310
|
+
}
|
|
52311
|
+
if (requireActiveNoteId !== void 0) {
|
|
52312
|
+
const refusal = quickNoteGuardRefusal("Required active", requireActiveNoteId);
|
|
52313
|
+
if (refusal) return { error: refusal };
|
|
52314
|
+
const active = notesManager.getNoteById(requireActiveNoteId);
|
|
52315
|
+
if (!active)
|
|
52316
|
+
return { error: `Required active note with ID "${requireActiveNoteId}" not found.` };
|
|
52317
|
+
if (active.passwordProtected) {
|
|
52318
|
+
return { error: `Required active note "${active.title}" is password-protected.` };
|
|
52319
|
+
}
|
|
52320
|
+
prepared.guards.push({ id: requireActiveNoteId });
|
|
52321
|
+
prepared.labels.push("Required active");
|
|
52322
|
+
}
|
|
52323
|
+
return prepared;
|
|
52324
|
+
}
|
|
51567
52325
|
var folderNameSchema = {
|
|
51568
52326
|
name: external_exports.string().min(1, "Folder name is required").max(MAX.FOLDER),
|
|
51569
52327
|
account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
|
|
@@ -52799,10 +53557,17 @@ registerTool(
|
|
|
52799
53557
|
registerTool(
|
|
52800
53558
|
"delete-note",
|
|
52801
53559
|
{
|
|
52802
|
-
description: "Use when: moving one exact note to Recently Deleted after reading and reviewing it.\nReturns: confirmation with the exact id.\nDo not use when: you only have a title or the note changed since review.\nSafety: requires id and expectedContentHash from get-note-content. The body comparison and delete happen in one AppleScript, so a newer edit is preserved. Refuses a note already in Recently Deleted, where a delete would be permanent. Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds are re-checked inside that AppleScript too.",
|
|
53560
|
+
description: "Use when: moving one exact note to Recently Deleted after reading and reviewing it.\nReturns: confirmation with the exact id.\nDo not use when: you only have a title or the note changed since review.\nSafety: requires id and expectedContentHash from get-note-content. The body comparison and delete happen in one AppleScript, so a newer edit is preserved. Refuses a note already in Recently Deleted, where a delete would be permanent. Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds are re-checked inside that AppleScript too. Copy-then-retire: pass guardNoteId and expectedGuardContentHash (the verified copy's contentHash) to delete only while the copy still has that revision, is unlocked, is outside Recently Deleted, and is not a Quick Note; requireActiveNoteId requires the same of a second note without fingerprinting it. The guard needs Full Disk Access to rule out a Quick Note. The copy's body, lock state, and folder are checked again inside the delete AppleScript, but the pair is not one transaction.",
|
|
52803
53561
|
inputSchema: {
|
|
52804
53562
|
id: noteIdInput,
|
|
52805
53563
|
expectedContentHash: expectedContentHashInput,
|
|
53564
|
+
guardNoteId: noteIdInput.optional().describe(
|
|
53565
|
+
"A second note (usually the verified copy) that must still match expectedGuardContentHash, be unlocked, stay outside Recently Deleted, and not be a Quick Note. Needs Full Disk Access"
|
|
53566
|
+
),
|
|
53567
|
+
expectedGuardContentHash: expectedContentHashInput.optional().describe("get-note-content contentHash of guardNoteId; required with guardNoteId"),
|
|
53568
|
+
requireActiveNoteId: noteIdInput.optional().describe(
|
|
53569
|
+
"A second note that must still exist, be unlocked, stay outside Recently Deleted, and not be a Quick Note. Its content is not fingerprinted. Needs Full Disk Access"
|
|
53570
|
+
),
|
|
52806
53571
|
timeoutSeconds: timeoutSecondsInput,
|
|
52807
53572
|
...scopeGuardInputs
|
|
52808
53573
|
},
|
|
@@ -52811,48 +53576,85 @@ registerTool(
|
|
|
52811
53576
|
id: external_exports.string().optional(),
|
|
52812
53577
|
title: external_exports.string().optional(),
|
|
52813
53578
|
wasShared: external_exports.boolean().optional(),
|
|
52814
|
-
previousContentHash: external_exports.string().optional()
|
|
53579
|
+
previousContentHash: external_exports.string().optional(),
|
|
53580
|
+
guardNoteId: external_exports.string().optional(),
|
|
53581
|
+
guardContentHash: external_exports.string().optional(),
|
|
53582
|
+
requireActiveNoteId: external_exports.string().optional()
|
|
52815
53583
|
}
|
|
52816
53584
|
},
|
|
52817
|
-
withErrorHandling(
|
|
52818
|
-
|
|
52819
|
-
|
|
52820
|
-
|
|
52821
|
-
|
|
52822
|
-
|
|
52823
|
-
|
|
52824
|
-
|
|
52825
|
-
|
|
52826
|
-
|
|
52827
|
-
if (result.status === "scope_conflict") {
|
|
52828
|
-
return errorResponse(scopeConflictMessage(result.reason));
|
|
52829
|
-
}
|
|
52830
|
-
if (result.status === "in-recently-deleted") {
|
|
52831
|
-
return errorResponse(inRecentlyDeletedMessage(snapshot.note.title));
|
|
52832
|
-
}
|
|
52833
|
-
if (result.status === "container-unknown") {
|
|
52834
|
-
return errorResponse(containerUnknownMessage(snapshot.note.title));
|
|
52835
|
-
}
|
|
52836
|
-
if (result.status === "not-deleted") {
|
|
52837
|
-
return errorResponse(NOT_DELETED_MESSAGE);
|
|
52838
|
-
}
|
|
52839
|
-
if (result.status !== "deleted") {
|
|
52840
|
-
return errorResponse(
|
|
52841
|
-
`The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
|
|
52842
|
-
);
|
|
52843
|
-
}
|
|
52844
|
-
const sharedWarning = snapshot.note.shared ? "\n\n\u26A0\uFE0F This note was shared with collaborators. They will no longer have access." : "";
|
|
52845
|
-
return successResponse(
|
|
52846
|
-
`Note moved to Recently Deleted: "${snapshot.note.title}"${sharedWarning}`,
|
|
52847
|
-
{
|
|
52848
|
-
ok: true,
|
|
53585
|
+
withErrorHandling(
|
|
53586
|
+
({
|
|
53587
|
+
id: id2,
|
|
53588
|
+
expectedContentHash,
|
|
53589
|
+
guardNoteId,
|
|
53590
|
+
expectedGuardContentHash,
|
|
53591
|
+
requireActiveNoteId,
|
|
53592
|
+
...scopeArgs
|
|
53593
|
+
}) => {
|
|
53594
|
+
const prepared = prepareDeleteGuards({
|
|
52849
53595
|
id: id2,
|
|
52850
|
-
|
|
52851
|
-
|
|
52852
|
-
|
|
53596
|
+
guardNoteId,
|
|
53597
|
+
expectedGuardContentHash,
|
|
53598
|
+
requireActiveNoteId
|
|
53599
|
+
});
|
|
53600
|
+
if ("error" in prepared) return errorResponse(prepared.error);
|
|
53601
|
+
const snapshot = readExactNoteSnapshot(id2);
|
|
53602
|
+
if ("error" in snapshot) return errorResponse(snapshot.error);
|
|
53603
|
+
if (snapshot.contentHash !== expectedContentHash) {
|
|
53604
|
+
return errorResponse(revisionConflictMessage(snapshot.note.title));
|
|
52853
53605
|
}
|
|
52854
|
-
|
|
52855
|
-
|
|
53606
|
+
const result = notesManager.deleteNoteByIdIfUnchanged(
|
|
53607
|
+
id2,
|
|
53608
|
+
snapshot.body,
|
|
53609
|
+
scopeFrom(scopeArgs),
|
|
53610
|
+
prepared.guards
|
|
53611
|
+
);
|
|
53612
|
+
if (result.status === "guard-conflict") {
|
|
53613
|
+
return errorResponse(
|
|
53614
|
+
`${prepared.labels[result.index]} note changed just before the delete. Nothing was deleted; verify it again before retrying.`
|
|
53615
|
+
);
|
|
53616
|
+
}
|
|
53617
|
+
if (result.status === "guard-inactive") {
|
|
53618
|
+
return errorResponse(
|
|
53619
|
+
`${prepared.labels[result.index]} note is no longer active (${result.reason}). Nothing was deleted.`
|
|
53620
|
+
);
|
|
53621
|
+
}
|
|
53622
|
+
if (result.status === "conflict") {
|
|
53623
|
+
return errorResponse(revisionConflictMessage(snapshot.note.title));
|
|
53624
|
+
}
|
|
53625
|
+
if (result.status === "scope_conflict") {
|
|
53626
|
+
return errorResponse(scopeConflictMessage(result.reason));
|
|
53627
|
+
}
|
|
53628
|
+
if (result.status === "in-recently-deleted") {
|
|
53629
|
+
return errorResponse(inRecentlyDeletedMessage(snapshot.note.title));
|
|
53630
|
+
}
|
|
53631
|
+
if (result.status === "container-unknown") {
|
|
53632
|
+
return errorResponse(containerUnknownMessage(snapshot.note.title));
|
|
53633
|
+
}
|
|
53634
|
+
if (result.status === "not-deleted") {
|
|
53635
|
+
return errorResponse(NOT_DELETED_MESSAGE);
|
|
53636
|
+
}
|
|
53637
|
+
if (result.status !== "deleted") {
|
|
53638
|
+
return errorResponse(
|
|
53639
|
+
`The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
|
|
53640
|
+
);
|
|
53641
|
+
}
|
|
53642
|
+
const sharedWarning = snapshot.note.shared ? "\n\n\u26A0\uFE0F This note was shared with collaborators. They will no longer have access." : "";
|
|
53643
|
+
return successResponse(
|
|
53644
|
+
`Note moved to Recently Deleted: "${snapshot.note.title}"${sharedWarning}`,
|
|
53645
|
+
{
|
|
53646
|
+
ok: true,
|
|
53647
|
+
id: id2,
|
|
53648
|
+
title: snapshot.note.title,
|
|
53649
|
+
wasShared: snapshot.note.shared ?? false,
|
|
53650
|
+
previousContentHash: expectedContentHash,
|
|
53651
|
+
...guardNoteId ? { guardNoteId, guardContentHash: prepared.guardContentHash } : {},
|
|
53652
|
+
...requireActiveNoteId ? { requireActiveNoteId } : {}
|
|
53653
|
+
}
|
|
53654
|
+
);
|
|
53655
|
+
},
|
|
53656
|
+
"Error deleting note"
|
|
53657
|
+
)
|
|
52856
53658
|
);
|
|
52857
53659
|
registerTool(
|
|
52858
53660
|
"move-note",
|