apple-notes-mcp 2.14.3 → 2.15.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 +65 -6
- package/build/index.js +1492 -290
- package/docs/FULL-DISK-ACCESS.md +62 -12
- package/docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md +101 -31
- package/native/broker/apple-notes-mcp-broker.swift +820 -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 = resolve11.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 resolve11(root, ref) {
|
|
3022
3022
|
let sch;
|
|
3023
3023
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3024
3024
|
ref = sch;
|
|
@@ -3848,7 +3848,7 @@ var require_fast_uri = __commonJS({
|
|
|
3848
3848
|
}
|
|
3849
3849
|
return uri;
|
|
3850
3850
|
}
|
|
3851
|
-
function
|
|
3851
|
+
function resolve11(baseURI, relativeURI, options) {
|
|
3852
3852
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3853
3853
|
const {
|
|
3854
3854
|
parsed: baseParsed,
|
|
@@ -4217,7 +4217,7 @@ var require_fast_uri = __commonJS({
|
|
|
4217
4217
|
var fastUri = {
|
|
4218
4218
|
SCHEMES,
|
|
4219
4219
|
normalize,
|
|
4220
|
-
resolve:
|
|
4220
|
+
resolve: resolve11,
|
|
4221
4221
|
resolveComponent,
|
|
4222
4222
|
equal,
|
|
4223
4223
|
serialize,
|
|
@@ -24477,14 +24477,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
24477
24477
|
} else if (node.nodeType === 1) {
|
|
24478
24478
|
replacement = replacementForNode.call(self, node);
|
|
24479
24479
|
}
|
|
24480
|
-
return
|
|
24480
|
+
return join35(output, replacement);
|
|
24481
24481
|
}, "");
|
|
24482
24482
|
}
|
|
24483
24483
|
function postProcess(output) {
|
|
24484
24484
|
var self = this;
|
|
24485
24485
|
this.rules.forEach(function(rule) {
|
|
24486
24486
|
if (typeof rule.append === "function") {
|
|
24487
|
-
output =
|
|
24487
|
+
output = join35(output, rule.append(self.options));
|
|
24488
24488
|
}
|
|
24489
24489
|
});
|
|
24490
24490
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -24496,7 +24496,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
24496
24496
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
24497
24497
|
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
|
|
24498
24498
|
}
|
|
24499
|
-
function
|
|
24499
|
+
function join35(output, replacement) {
|
|
24500
24500
|
var s1 = trimTrailingNewlines(output);
|
|
24501
24501
|
var s2 = trimLeadingNewlines(replacement);
|
|
24502
24502
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -36620,7 +36620,7 @@ var Protocol = class {
|
|
|
36620
36620
|
return;
|
|
36621
36621
|
}
|
|
36622
36622
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
36623
|
-
await new Promise((
|
|
36623
|
+
await new Promise((resolve11) => setTimeout(resolve11, pollInterval));
|
|
36624
36624
|
options?.signal?.throwIfAborted();
|
|
36625
36625
|
}
|
|
36626
36626
|
} catch (error2) {
|
|
@@ -36637,7 +36637,7 @@ var Protocol = class {
|
|
|
36637
36637
|
*/
|
|
36638
36638
|
request(request, resultSchema, options) {
|
|
36639
36639
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
36640
|
-
return new Promise((
|
|
36640
|
+
return new Promise((resolve11, reject) => {
|
|
36641
36641
|
const earlyReject = (error2) => {
|
|
36642
36642
|
reject(error2);
|
|
36643
36643
|
};
|
|
@@ -36715,7 +36715,7 @@ var Protocol = class {
|
|
|
36715
36715
|
if (!parseResult.success) {
|
|
36716
36716
|
reject(parseResult.error);
|
|
36717
36717
|
} else {
|
|
36718
|
-
|
|
36718
|
+
resolve11(parseResult.data);
|
|
36719
36719
|
}
|
|
36720
36720
|
} catch (error2) {
|
|
36721
36721
|
reject(error2);
|
|
@@ -36976,12 +36976,12 @@ var Protocol = class {
|
|
|
36976
36976
|
}
|
|
36977
36977
|
} catch {
|
|
36978
36978
|
}
|
|
36979
|
-
return new Promise((
|
|
36979
|
+
return new Promise((resolve11, reject) => {
|
|
36980
36980
|
if (signal.aborted) {
|
|
36981
36981
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
36982
36982
|
return;
|
|
36983
36983
|
}
|
|
36984
|
-
const timeoutId = setTimeout(
|
|
36984
|
+
const timeoutId = setTimeout(resolve11, interval);
|
|
36985
36985
|
signal.addEventListener("abort", () => {
|
|
36986
36986
|
clearTimeout(timeoutId);
|
|
36987
36987
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -38294,7 +38294,7 @@ var McpServer = class {
|
|
|
38294
38294
|
let task = createTaskResult.task;
|
|
38295
38295
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
38296
38296
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
38297
|
-
await new Promise((
|
|
38297
|
+
await new Promise((resolve11) => setTimeout(resolve11, pollInterval));
|
|
38298
38298
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
38299
38299
|
if (!updatedTask) {
|
|
38300
38300
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -38982,12 +38982,12 @@ var StdioServerTransport = class {
|
|
|
38982
38982
|
this.onclose?.();
|
|
38983
38983
|
}
|
|
38984
38984
|
send(message) {
|
|
38985
|
-
return new Promise((
|
|
38985
|
+
return new Promise((resolve11) => {
|
|
38986
38986
|
const json2 = serializeMessage(message);
|
|
38987
38987
|
if (this._stdout.write(json2)) {
|
|
38988
|
-
|
|
38988
|
+
resolve11();
|
|
38989
38989
|
} else {
|
|
38990
|
-
this._stdout.once("drain",
|
|
38990
|
+
this._stdout.once("drain", resolve11);
|
|
38991
38991
|
}
|
|
38992
38992
|
});
|
|
38993
38993
|
}
|
|
@@ -39448,6 +39448,16 @@ function fixed32Float(field) {
|
|
|
39448
39448
|
return new DataView(bytes.buffer, bytes.byteOffset, 4).getFloat32(0, true);
|
|
39449
39449
|
}
|
|
39450
39450
|
|
|
39451
|
+
// src/utils/fullDiskAccess.ts
|
|
39452
|
+
import { homedir } from "node:os";
|
|
39453
|
+
import { join } from "node:path";
|
|
39454
|
+
function fdaRemediation(execPath = process.execPath, env = process.env) {
|
|
39455
|
+
if (env.APPLE_NOTES_MCP_BROKERED === "1")
|
|
39456
|
+
return `This server runs under the permission broker, so the grant belongs to the broker app, not to Node or the MCP host. In System Settings > Privacy & Security > Full Disk Access, click + and add ${env.APPLE_NOTES_MCP_BROKER_APP || join(homedir(), "Applications", "Apple Notes MCP Broker.app")} (press Cmd+Shift+G in the file picker to paste the path), turn it on, and re-run doctor. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
|
|
39457
|
+
const versioned = /\/(\.nvm|\.fnm|\.volta|\.asdf|\.local\/share\/mise|\.nodenv|n\/versions)\//.test(execPath);
|
|
39458
|
+
return `In System Settings > Privacy & Security > Full Disk Access, click + and add the Node binary running this server: ${execPath} (press Cmd+Shift+G in the file picker to paste the path). Under Claude Desktop that entry is required: Claude Desktop launches servers as their own responsible process, so a grant on Claude.app does not reach them. When the server runs from a terminal (Terminal, iTerm2) or an editor, granting that app is enough. Then fully quit (Cmd+Q) and relaunch the host app and re-run doctor; if it still reports not granted, restart the Mac. ` + (versioned ? "This Node lives under a version manager, so the path changes with each Node version and the grant has to be added again after switching; pointing the MCP config at one fixed Node path avoids that. " : "") + `Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
|
|
39459
|
+
}
|
|
39460
|
+
|
|
39451
39461
|
// src/utils/checklistRuns.ts
|
|
39452
39462
|
function checklistRunLineStart(text2, position, length) {
|
|
39453
39463
|
const end = position + length;
|
|
@@ -39575,7 +39585,7 @@ function getChecklistItems(noteId3) {
|
|
|
39575
39585
|
return {
|
|
39576
39586
|
items: null,
|
|
39577
39587
|
error: "no_fda",
|
|
39578
|
-
message: `Full Disk Access is required to read checklist state.
|
|
39588
|
+
message: `Full Disk Access is required to read checklist state. ${fdaRemediation()}`
|
|
39579
39589
|
};
|
|
39580
39590
|
}
|
|
39581
39591
|
if (!hexData) {
|
|
@@ -39641,7 +39651,7 @@ var ENTITY_LABEL = {
|
|
|
39641
39651
|
ICFolder: "folder",
|
|
39642
39652
|
ICAccount: "account"
|
|
39643
39653
|
};
|
|
39644
|
-
var NO_FDA_MESSAGE =
|
|
39654
|
+
var NO_FDA_MESSAGE = "Resolving a Notes UUID or numeric key reads the Notes database, which needs Full Disk Access. x-coredata ids from search-notes, list-notes, or list-folders work without it.";
|
|
39645
39655
|
function identifierFailureIn(message) {
|
|
39646
39656
|
if (message.includes(NO_FDA_MESSAGE)) return "no_fda";
|
|
39647
39657
|
if (/\bNo (?:note|folder|account) found for (?:numeric key|identifier) /.test(message))
|
|
@@ -39700,7 +39710,8 @@ function buildLookupSql(entity3, keys) {
|
|
|
39700
39710
|
return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object(${fields})) FROM ZICCLOUDSYNCINGOBJECT o ${joins}WHERE o.Z_ENT = ${entityClause(entity3)} AND o.Z_PK IN (${inList})));`;
|
|
39701
39711
|
}
|
|
39702
39712
|
function runJsonQuery(sql, dbPath2) {
|
|
39703
|
-
if (!fs2.existsSync(dbPath2))
|
|
39713
|
+
if (!fs2.existsSync(dbPath2))
|
|
39714
|
+
throw new IdentifierResolutionError("no_fda", `${NO_FDA_MESSAGE} ${fdaRemediation()}`);
|
|
39704
39715
|
let out;
|
|
39705
39716
|
try {
|
|
39706
39717
|
out = execFileSync3("sqlite3", ["-readonly", dbPath2, sql], {
|
|
@@ -39711,7 +39722,7 @@ function runJsonQuery(sql, dbPath2) {
|
|
|
39711
39722
|
} catch (error2) {
|
|
39712
39723
|
const detail = error2 instanceof Error ? `${error2.message} ${String(error2.stderr ?? "")}` : String(error2);
|
|
39713
39724
|
if (detail.includes("authorization denied") || detail.includes("unable to open database")) {
|
|
39714
|
-
throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
39725
|
+
throw new IdentifierResolutionError("no_fda", `${NO_FDA_MESSAGE} ${fdaRemediation()}`);
|
|
39715
39726
|
}
|
|
39716
39727
|
console.error(`Identifier query failed: ${detail}`);
|
|
39717
39728
|
throw new IdentifierResolutionError("query_error", "Failed to read the Notes database.");
|
|
@@ -39996,8 +40007,8 @@ function strippedImagesWarning(stripped) {
|
|
|
39996
40007
|
// src/utils/noteRichText.ts
|
|
39997
40008
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
39998
40009
|
import { createHash } from "node:crypto";
|
|
39999
|
-
import { homedir as
|
|
40000
|
-
import { join as
|
|
40010
|
+
import { homedir as homedir4 } from "node:os";
|
|
40011
|
+
import { join as join4 } from "node:path";
|
|
40001
40012
|
import { gunzipSync as gunzipSync2 } from "node:zlib";
|
|
40002
40013
|
|
|
40003
40014
|
// src/utils/uniqueById.ts
|
|
@@ -40331,7 +40342,7 @@ var HTML_LOSSY_ORDER = [
|
|
|
40331
40342
|
"alignment",
|
|
40332
40343
|
"highlight"
|
|
40333
40344
|
];
|
|
40334
|
-
var dbPath =
|
|
40345
|
+
var dbPath = join4(homedir4(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
|
|
40335
40346
|
var safeUrl = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
|
|
40336
40347
|
var escapeAttribute = (text2) => text2.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
40337
40348
|
var normalized = (text2) => text2.replace(/[\s\ufffc]/gu, "");
|
|
@@ -40662,11 +40673,11 @@ function assertLinkedWrite(rich, content, format, allowLinkChanges = false) {
|
|
|
40662
40673
|
|
|
40663
40674
|
// src/utils/audioTranscripts.ts
|
|
40664
40675
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
40665
|
-
import { homedir as
|
|
40666
|
-
import { join as
|
|
40676
|
+
import { homedir as homedir5 } from "node:os";
|
|
40677
|
+
import { join as join5 } from "node:path";
|
|
40667
40678
|
import { gunzipSync as gunzipSync3 } from "node:zlib";
|
|
40668
|
-
var NOTES_DB_PATH3 =
|
|
40669
|
-
|
|
40679
|
+
var NOTES_DB_PATH3 = join5(
|
|
40680
|
+
homedir5(),
|
|
40670
40681
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
40671
40682
|
);
|
|
40672
40683
|
var DEFAULT_MAX_SEGMENTS = 2e3;
|
|
@@ -40896,7 +40907,7 @@ function readAudioTranscripts(noteId3, options = {}) {
|
|
|
40896
40907
|
if (/authorization denied|unable to open database/i.test(message))
|
|
40897
40908
|
throw new AudioTranscriptError(
|
|
40898
40909
|
"no_fda",
|
|
40899
|
-
`Full Disk Access is required to read stored transcripts.
|
|
40910
|
+
`Full Disk Access is required to read stored transcripts. ${fdaRemediation()}`
|
|
40900
40911
|
);
|
|
40901
40912
|
throw new AudioTranscriptError("query_error", "Failed to read the Notes database.");
|
|
40902
40913
|
}
|
|
@@ -41213,7 +41224,7 @@ var NOTES_DB_PATH4 = path3.join(
|
|
|
41213
41224
|
os3.homedir(),
|
|
41214
41225
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
41215
41226
|
);
|
|
41216
|
-
var FDA_MESSAGE =
|
|
41227
|
+
var FDA_MESSAGE = "Full Disk Access is required to read smart folders.";
|
|
41217
41228
|
var COCOA_EPOCH_OFFSET = 978307200;
|
|
41218
41229
|
var MAX_DEPTH = 32;
|
|
41219
41230
|
var RELATIVE_RANGES = {
|
|
@@ -41563,7 +41574,8 @@ function runSqlite2(dbPath2, sql) {
|
|
|
41563
41574
|
});
|
|
41564
41575
|
}
|
|
41565
41576
|
function readSmartFolders(dbPath2 = NOTES_DB_PATH4) {
|
|
41566
|
-
if (!fs3.existsSync(dbPath2))
|
|
41577
|
+
if (!fs3.existsSync(dbPath2))
|
|
41578
|
+
return { folders: null, error: "no_fda", message: `${FDA_MESSAGE} ${fdaRemediation()}` };
|
|
41567
41579
|
try {
|
|
41568
41580
|
const columns = new Set(
|
|
41569
41581
|
runSqlite2(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").trim().split("\n")
|
|
@@ -41580,7 +41592,7 @@ function readSmartFolders(dbPath2 = NOTES_DB_PATH4) {
|
|
|
41580
41592
|
} catch (error2) {
|
|
41581
41593
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
41582
41594
|
if (message.includes("authorization denied") || message.includes("unable to open database")) {
|
|
41583
|
-
return { folders: null, error: "no_fda", message: FDA_MESSAGE };
|
|
41595
|
+
return { folders: null, error: "no_fda", message: `${FDA_MESSAGE} ${fdaRemediation()}` };
|
|
41584
41596
|
}
|
|
41585
41597
|
console.error(`Failed to read smart folders: ${message}`);
|
|
41586
41598
|
return { folders: null, error: "query_error", message: "Failed to read smart folders." };
|
|
@@ -41646,11 +41658,11 @@ import {
|
|
|
41646
41658
|
rmSync,
|
|
41647
41659
|
statSync
|
|
41648
41660
|
} from "fs";
|
|
41649
|
-
import { dirname, isAbsolute, join as
|
|
41650
|
-
import { homedir as
|
|
41661
|
+
import { dirname, isAbsolute, join as join8, relative, resolve, sep } from "path";
|
|
41662
|
+
import { homedir as homedir8, tmpdir } from "os";
|
|
41651
41663
|
function allowedSaveRoots() {
|
|
41652
41664
|
return [
|
|
41653
|
-
resolve(
|
|
41665
|
+
resolve(homedir8()),
|
|
41654
41666
|
resolve(tmpdir()),
|
|
41655
41667
|
"/Volumes",
|
|
41656
41668
|
"/private/var/folders",
|
|
@@ -41659,7 +41671,7 @@ function allowedSaveRoots() {
|
|
|
41659
41671
|
];
|
|
41660
41672
|
}
|
|
41661
41673
|
function deniedSaveRoots() {
|
|
41662
|
-
return [
|
|
41674
|
+
return [join8(homedir8(), "Library/Group Containers/group.com.apple.notes")];
|
|
41663
41675
|
}
|
|
41664
41676
|
function canonicalize(path10) {
|
|
41665
41677
|
return realpathSync.native(path10);
|
|
@@ -41727,7 +41739,7 @@ function assertSafeSavePath(p, roots = allowedSaveRoots(), denied = deniedSaveRo
|
|
|
41727
41739
|
if (suffix.split(sep).includes("..")) {
|
|
41728
41740
|
throw new Error(`Refusing to write outside allowed locations (home, temp, /Volumes): "${abs}"`);
|
|
41729
41741
|
}
|
|
41730
|
-
const canonicalDest = suffix ?
|
|
41742
|
+
const canonicalDest = suffix ? join8(canonicalAncestor, suffix) : canonicalAncestor;
|
|
41731
41743
|
const allowed = canonicalRoots(roots);
|
|
41732
41744
|
if (!isWithinRoots(canonicalAncestor, allowed) || !isWithinRoots(canonicalDest, allowed)) {
|
|
41733
41745
|
throw new Error(
|
|
@@ -41796,13 +41808,13 @@ function privateContentReason(p, roots) {
|
|
|
41796
41808
|
const containing = roots.map((r) => r.endsWith(sep) ? r.slice(0, -1) : r).filter((r) => isWithinRoots(p, [r])).sort((a, b) => b.length - a.length)[0];
|
|
41797
41809
|
const below = containing === void 0 ? p : relative(containing, p);
|
|
41798
41810
|
if (below.split(sep).some((part) => part.startsWith("."))) return "a hidden file or directory";
|
|
41799
|
-
const home = resolve(
|
|
41811
|
+
const home = resolve(homedir8());
|
|
41800
41812
|
const libraries = [
|
|
41801
|
-
|
|
41802
|
-
...canonicalRoots([home]).map((h) =>
|
|
41813
|
+
join8(home, "Library"),
|
|
41814
|
+
...canonicalRoots([home]).map((h) => join8(h, "Library"))
|
|
41803
41815
|
];
|
|
41804
41816
|
if (!isWithinRoots(p, libraries)) return null;
|
|
41805
|
-
const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) =>
|
|
41817
|
+
const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) => join8(l, d)));
|
|
41806
41818
|
return isWithinRoots(p, cloudDocuments) ? null : "~/Library";
|
|
41807
41819
|
}
|
|
41808
41820
|
var CLOUD_DOCUMENT_DIRS = ["Mobile Documents", "CloudStorage"];
|
|
@@ -41886,7 +41898,7 @@ import {
|
|
|
41886
41898
|
unlinkSync as unlinkSync2,
|
|
41887
41899
|
writeSync as writeSync2
|
|
41888
41900
|
} from "node:fs";
|
|
41889
|
-
import { basename as basename2, dirname as dirname3, extname as extname2, join as
|
|
41901
|
+
import { basename as basename2, dirname as dirname3, extname as extname2, join as join10 } from "node:path";
|
|
41890
41902
|
|
|
41891
41903
|
// src/utils/attachmentAssets.ts
|
|
41892
41904
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
@@ -41903,11 +41915,11 @@ import {
|
|
|
41903
41915
|
unlinkSync,
|
|
41904
41916
|
writeSync
|
|
41905
41917
|
} from "node:fs";
|
|
41906
|
-
import { homedir as
|
|
41907
|
-
import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as
|
|
41918
|
+
import { homedir as homedir9 } from "node:os";
|
|
41919
|
+
import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join9, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
41908
41920
|
import { gunzipSync as gunzipSync5 } from "node:zlib";
|
|
41909
|
-
var NOTES_CONTAINER_DIR =
|
|
41910
|
-
|
|
41921
|
+
var NOTES_CONTAINER_DIR = join9(
|
|
41922
|
+
homedir9(),
|
|
41911
41923
|
"Library/Group Containers/group.com.apple.notes"
|
|
41912
41924
|
);
|
|
41913
41925
|
var AttachmentStoreError = class extends Error {
|
|
@@ -42023,7 +42035,7 @@ function runSqlite3(dbPath2, sql) {
|
|
|
42023
42035
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
42024
42036
|
if (/authorization denied|unable to open database/i.test(message)) {
|
|
42025
42037
|
throw new AttachmentStoreError(
|
|
42026
|
-
|
|
42038
|
+
`Full Disk Access is required to read attachment paths. ${fdaRemediation()}`,
|
|
42027
42039
|
"no_fda"
|
|
42028
42040
|
);
|
|
42029
42041
|
}
|
|
@@ -42082,7 +42094,7 @@ function attachmentOrderFromNoteData(hex3) {
|
|
|
42082
42094
|
return null;
|
|
42083
42095
|
}
|
|
42084
42096
|
}
|
|
42085
|
-
function readNoteAttachmentRows(noteId3, dbPath2 =
|
|
42097
|
+
function readNoteAttachmentRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
|
|
42086
42098
|
const { pk } = parseNoteId2(noteId3);
|
|
42087
42099
|
const columnList = runSqlite3(
|
|
42088
42100
|
dbPath2,
|
|
@@ -42143,7 +42155,7 @@ function boundedEntries(dir, limit) {
|
|
|
42143
42155
|
}
|
|
42144
42156
|
}
|
|
42145
42157
|
function resolveAccountDir(containerDir, accountIdentifier) {
|
|
42146
|
-
const accountsDir =
|
|
42158
|
+
const accountsDir = join9(containerDir, "Accounts");
|
|
42147
42159
|
let accountsReal;
|
|
42148
42160
|
try {
|
|
42149
42161
|
accountsReal = realpathSync2.native(accountsDir);
|
|
@@ -42152,14 +42164,14 @@ function resolveAccountDir(containerDir, accountIdentifier) {
|
|
|
42152
42164
|
}
|
|
42153
42165
|
const account = safeComponent(accountIdentifier);
|
|
42154
42166
|
if (account) {
|
|
42155
|
-
const dir = realInside(
|
|
42167
|
+
const dir = realInside(join9(accountsReal, account), accountsReal);
|
|
42156
42168
|
if (dir && isDirectory(dir)) return dir;
|
|
42157
42169
|
}
|
|
42158
42170
|
const entries2 = (boundedEntries(accountsReal, 64) ?? []).filter(
|
|
42159
|
-
(e) => isDirectory(
|
|
42171
|
+
(e) => isDirectory(join9(accountsReal, e))
|
|
42160
42172
|
);
|
|
42161
42173
|
if (entries2.length !== 1) return null;
|
|
42162
|
-
return realInside(
|
|
42174
|
+
return realInside(join9(accountsReal, entries2[0]), accountsReal);
|
|
42163
42175
|
}
|
|
42164
42176
|
function generationRank(name) {
|
|
42165
42177
|
const m = /^(\d+)_/.exec(name);
|
|
@@ -42167,24 +42179,24 @@ function generationRank(name) {
|
|
|
42167
42179
|
}
|
|
42168
42180
|
function generationDirs(base, accountDir) {
|
|
42169
42181
|
const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
|
|
42170
|
-
return entries2.map((e) => realInside(
|
|
42182
|
+
return entries2.map((e) => realInside(join9(base, e), accountDir)).filter((p) => p !== null && isDirectory(p)).sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
|
|
42171
42183
|
}
|
|
42172
42184
|
function fallbackFiles(accountDir, rootName, identifier, generation, names, onStale) {
|
|
42173
|
-
const base =
|
|
42185
|
+
const base = join9(accountDir, rootName, identifier);
|
|
42174
42186
|
const found = [];
|
|
42175
42187
|
const add = (candidate) => {
|
|
42176
42188
|
const real = realInside(candidate, accountDir);
|
|
42177
42189
|
if (real && isRegularFile(real) && !found.includes(real)) found.push(real);
|
|
42178
42190
|
};
|
|
42179
42191
|
const gen = safeComponent(generation);
|
|
42180
|
-
if (gen) for (const name of names) add(
|
|
42192
|
+
if (gen) for (const name of names) add(join9(base, gen, name));
|
|
42181
42193
|
const recorded = found.length;
|
|
42182
42194
|
if (isDirectory(base)) {
|
|
42183
42195
|
for (const dir of generationDirs(base, accountDir))
|
|
42184
|
-
for (const name of names) add(
|
|
42185
|
-
for (const name of names) add(
|
|
42196
|
+
for (const name of names) add(join9(dir, name));
|
|
42197
|
+
for (const name of names) add(join9(base, name));
|
|
42186
42198
|
}
|
|
42187
|
-
for (const name of names) add(
|
|
42199
|
+
for (const name of names) add(join9(accountDir, rootName, `${identifier}${extname(name)}`));
|
|
42188
42200
|
if (gen && recorded === 0 && found.length > 0) onStale?.();
|
|
42189
42201
|
return found;
|
|
42190
42202
|
}
|
|
@@ -42197,7 +42209,7 @@ function previewFileInBundle(bundle, accountDir) {
|
|
|
42197
42209
|
const direct = [];
|
|
42198
42210
|
const nested = [];
|
|
42199
42211
|
for (const entry of boundedEntries(bundle, MAX_BUNDLE_ENTRIES) ?? []) {
|
|
42200
|
-
const child2 = realInside(
|
|
42212
|
+
const child2 = realInside(join9(bundle, entry), accountDir);
|
|
42201
42213
|
if (!child2) continue;
|
|
42202
42214
|
if (isRegularFile(child2)) direct.push(child2);
|
|
42203
42215
|
else if (isDirectory(child2)) nested.push(child2);
|
|
@@ -42205,14 +42217,14 @@ function previewFileInBundle(bundle, accountDir) {
|
|
|
42205
42217
|
nested.sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
|
|
42206
42218
|
const pick2 = (files) => files.find((f) => basename(f) === "Preview.png") ?? files.find((f) => PREVIEW_IMAGE_SUFFIXES.has(extname(f).toLowerCase())) ?? null;
|
|
42207
42219
|
for (const dir of nested) {
|
|
42208
|
-
const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(
|
|
42220
|
+
const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(join9(dir, e), accountDir)).filter((p) => p !== null && isRegularFile(p));
|
|
42209
42221
|
const chosen = pick2(files);
|
|
42210
42222
|
if (chosen) return chosen;
|
|
42211
42223
|
}
|
|
42212
42224
|
return pick2(direct);
|
|
42213
42225
|
}
|
|
42214
42226
|
function listPreviewEntries(accountDir) {
|
|
42215
|
-
return boundedEntries(
|
|
42227
|
+
return boundedEntries(join9(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
|
|
42216
42228
|
}
|
|
42217
42229
|
function previewPaths(accountDir, identifier, entries2) {
|
|
42218
42230
|
const id2 = safeComponent(identifier);
|
|
@@ -42224,7 +42236,7 @@ function previewPaths(accountDir, identifier, entries2) {
|
|
|
42224
42236
|
}).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
|
|
42225
42237
|
const files = [];
|
|
42226
42238
|
for (const name of candidates) {
|
|
42227
|
-
const real = realInside(
|
|
42239
|
+
const real = realInside(join9(accountDir, "Previews", name), accountDir);
|
|
42228
42240
|
if (!real) continue;
|
|
42229
42241
|
const file = isRegularFile(real) ? real : isDirectory(real) ? previewFileInBundle(real, accountDir) : null;
|
|
42230
42242
|
if (file && !files.includes(file)) files.push(file);
|
|
@@ -42241,13 +42253,13 @@ function assetPathsFor(accountDir, row, onStale) {
|
|
|
42241
42253
|
const mediaName = safeComponent(row.mediaFilename);
|
|
42242
42254
|
const mediaGen = safeComponent(row.mediaGeneration);
|
|
42243
42255
|
if (mediaId && mediaName) {
|
|
42244
|
-
if (mediaGen) add(
|
|
42245
|
-
add(
|
|
42256
|
+
if (mediaGen) add(join9(accountDir, "Media", mediaId, mediaGen, mediaName));
|
|
42257
|
+
add(join9(accountDir, "Media", mediaId, mediaName));
|
|
42246
42258
|
}
|
|
42247
42259
|
const id2 = safeComponent(row.identifier);
|
|
42248
42260
|
if (!id2) return found;
|
|
42249
42261
|
const ownName = safeComponent(row.filename);
|
|
42250
|
-
if (ownName) add(
|
|
42262
|
+
if (ownName) add(join9(accountDir, "Media", id2, ownName));
|
|
42251
42263
|
for (const file of fallbackFiles(
|
|
42252
42264
|
accountDir,
|
|
42253
42265
|
"FallbackImages",
|
|
@@ -42350,10 +42362,10 @@ function canonicalTail(p) {
|
|
|
42350
42362
|
const tail = [];
|
|
42351
42363
|
for (; ; ) {
|
|
42352
42364
|
try {
|
|
42353
|
-
return
|
|
42365
|
+
return join9(realpathSync2.native(current), ...tail);
|
|
42354
42366
|
} catch {
|
|
42355
42367
|
const parent = dirname2(current);
|
|
42356
|
-
if (parent === current) return
|
|
42368
|
+
if (parent === current) return join9(current, ...tail);
|
|
42357
42369
|
tail.unshift(basename(current));
|
|
42358
42370
|
current = parent;
|
|
42359
42371
|
}
|
|
@@ -42442,7 +42454,7 @@ function exportOneAttachment(record2, dir, source, inBody) {
|
|
|
42442
42454
|
if (!source) return base;
|
|
42443
42455
|
const name = exportFileName(record2, source.path, source.kind);
|
|
42444
42456
|
for (let attempt = 1; attempt <= MAX_COLLISION_SUFFIX; attempt++) {
|
|
42445
|
-
const dest =
|
|
42457
|
+
const dest = join9(dir, collisionName(name, attempt));
|
|
42446
42458
|
try {
|
|
42447
42459
|
if (dirname2(dest) !== resolve2(dir))
|
|
42448
42460
|
throw new Error(`Refusing to write outside the export directory: "${dest}"`);
|
|
@@ -42597,7 +42609,7 @@ function runSqlite4(dbPath2, sql) {
|
|
|
42597
42609
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
42598
42610
|
if (/authorization denied|unable to open database/i.test(message)) {
|
|
42599
42611
|
throw new AttachmentStoreError(
|
|
42600
|
-
|
|
42612
|
+
`Full Disk Access is required to read drawing attachments. ${fdaRemediation()}`,
|
|
42601
42613
|
"no_fda"
|
|
42602
42614
|
);
|
|
42603
42615
|
}
|
|
@@ -42627,7 +42639,7 @@ function parseDrawingRows(json2) {
|
|
|
42627
42639
|
}
|
|
42628
42640
|
return rows.sort((a, b) => a.pk - b.pk);
|
|
42629
42641
|
}
|
|
42630
|
-
function readDrawingRows(noteId3, dbPath2 =
|
|
42642
|
+
function readDrawingRows(noteId3, dbPath2 = join10(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
|
|
42631
42643
|
const { pk } = parseNoteId2(noteId3);
|
|
42632
42644
|
const columns = new Set(
|
|
42633
42645
|
runSqlite4(
|
|
@@ -42674,23 +42686,23 @@ function locateFallbackImage(accountDir, identifier, generation) {
|
|
|
42674
42686
|
if (!path10 || !gen) return { path: path10, stale: false };
|
|
42675
42687
|
const id2 = safeComponent(identifier);
|
|
42676
42688
|
const recorded = ["FallbackImage.png", "FallbackImage.jpg"].map(
|
|
42677
|
-
(n) => id2 ? realInside(
|
|
42689
|
+
(n) => id2 ? realInside(join10(accountDir, "FallbackImages", id2, gen, n), accountDir) : null
|
|
42678
42690
|
).filter((p) => p !== null);
|
|
42679
42691
|
return { path: path10, stale: !recorded.includes(path10) };
|
|
42680
42692
|
}
|
|
42681
42693
|
function searchFallbackImage(accountDir, identifier, generation) {
|
|
42682
42694
|
const id2 = safeComponent(identifier);
|
|
42683
42695
|
if (!id2) return null;
|
|
42684
|
-
const base =
|
|
42696
|
+
const base = join10(accountDir, "FallbackImages", id2);
|
|
42685
42697
|
const names = ["FallbackImage.png", "FallbackImage.jpg"];
|
|
42686
42698
|
const candidates = [];
|
|
42687
42699
|
const gen = safeComponent(generation);
|
|
42688
|
-
if (gen) for (const n of names) candidates.push(
|
|
42689
|
-
const gens = entries(base, MAX_DIR_ENTRIES).map((e) =>
|
|
42690
|
-
for (const g of gens) for (const n of names) candidates.push(
|
|
42691
|
-
for (const n of names) candidates.push(
|
|
42692
|
-
candidates.push(
|
|
42693
|
-
candidates.push(
|
|
42700
|
+
if (gen) for (const n of names) candidates.push(join10(base, gen, n));
|
|
42701
|
+
const gens = entries(base, MAX_DIR_ENTRIES).map((e) => join10(base, e)).sort(byGenerationDesc);
|
|
42702
|
+
for (const g of gens) for (const n of names) candidates.push(join10(g, n));
|
|
42703
|
+
for (const n of names) candidates.push(join10(base, n));
|
|
42704
|
+
candidates.push(join10(accountDir, "FallbackImages", `${id2}.png`));
|
|
42705
|
+
candidates.push(join10(accountDir, "FallbackImages", `${id2}.jpg`));
|
|
42694
42706
|
for (const c of candidates) {
|
|
42695
42707
|
const real = realInside(c, accountDir);
|
|
42696
42708
|
if (real && kindOf(real) === "file") return real;
|
|
@@ -42700,21 +42712,21 @@ function searchFallbackImage(accountDir, identifier, generation) {
|
|
|
42700
42712
|
function findLargestPreview(accountDir, identifier) {
|
|
42701
42713
|
const id2 = safeComponent(identifier);
|
|
42702
42714
|
if (!id2) return null;
|
|
42703
|
-
const dir =
|
|
42715
|
+
const dir = join10(accountDir, "Previews");
|
|
42704
42716
|
const prefix = `${id2}-`.toLowerCase();
|
|
42705
42717
|
const names = entries(dir, MAX_PREVIEW_DIR_ENTRIES2).filter((n) => n.toLowerCase().startsWith(prefix)).filter((n) => {
|
|
42706
42718
|
const ext = extname2(n).toLowerCase();
|
|
42707
42719
|
return !ext || IMAGE_SUFFIXES.has(ext) || /^\.\d+$/.test(ext);
|
|
42708
42720
|
}).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
|
|
42709
42721
|
for (const name of names) {
|
|
42710
|
-
const real = realInside(
|
|
42722
|
+
const real = realInside(join10(dir, name), accountDir);
|
|
42711
42723
|
if (!real) continue;
|
|
42712
42724
|
const kind = kindOf(real);
|
|
42713
42725
|
if (kind === "file") return real;
|
|
42714
42726
|
if (kind !== "dir") continue;
|
|
42715
|
-
const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(
|
|
42727
|
+
const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(join10(real, e), accountDir)).filter((p) => p !== null && kindOf(p) === "dir").sort(byGenerationDesc);
|
|
42716
42728
|
for (const g of [...gens, real]) {
|
|
42717
|
-
const file = realInside(
|
|
42729
|
+
const file = realInside(join10(g, "Preview.png"), accountDir);
|
|
42718
42730
|
if (file && kindOf(file) === "file") return file;
|
|
42719
42731
|
}
|
|
42720
42732
|
}
|
|
@@ -42728,7 +42740,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
|
|
|
42728
42740
|
const fallback = accountDir ? locateFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : { path: null, stale: false };
|
|
42729
42741
|
const fallbackImagePath = fallback.path;
|
|
42730
42742
|
const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
|
|
42731
|
-
const bundle = accountDir && id2 && kind === "paper" ? realInside(
|
|
42743
|
+
const bundle = accountDir && id2 && kind === "paper" ? realInside(join10(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
|
|
42732
42744
|
let raster = null;
|
|
42733
42745
|
for (const [path10, source] of [
|
|
42734
42746
|
[fallbackImagePath, "fallback"],
|
|
@@ -42934,8 +42946,8 @@ function scopeConflictMessage(reason) {
|
|
|
42934
42946
|
// src/services/appleNotesManager.ts
|
|
42935
42947
|
var import_turndown = __toESM(require_turndown_cjs(), 1);
|
|
42936
42948
|
import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
|
|
42937
|
-
import { homedir as
|
|
42938
|
-
import { join as
|
|
42949
|
+
import { homedir as homedir10, tmpdir as tmpdir2 } from "os";
|
|
42950
|
+
import { join as join11 } from "path";
|
|
42939
42951
|
var FIELD_SEP = "";
|
|
42940
42952
|
var RECORD_SEP = "";
|
|
42941
42953
|
var AS_FIELD_SEP = "(character id 31)";
|
|
@@ -43297,8 +43309,8 @@ var ExpectedBodies = class {
|
|
|
43297
43309
|
if (body.length <= INLINE_EXPECTED_BODY_LIMIT) {
|
|
43298
43310
|
return { setup: "", operand: `"${escapeHtmlForAppleScript(body)}"` };
|
|
43299
43311
|
}
|
|
43300
|
-
this.directory ??= mkdtempSync2(
|
|
43301
|
-
const file =
|
|
43312
|
+
this.directory ??= mkdtempSync2(join11(tmpdir2(), "apple-notes-expected-body-"));
|
|
43313
|
+
const file = join11(this.directory, `body-${this.files++}.html`);
|
|
43302
43314
|
writeFileSync(file, body, { encoding: "utf8", mode: 384 });
|
|
43303
43315
|
return {
|
|
43304
43316
|
setup: `
|
|
@@ -43344,7 +43356,7 @@ function getNoteLinkFromDB(coreDataId3) {
|
|
|
43344
43356
|
const match = coreDataId3.match(/\/p(\d+)$/);
|
|
43345
43357
|
if (!match) return null;
|
|
43346
43358
|
const pk = parseInt(match[1], 10);
|
|
43347
|
-
const dbPath2 =
|
|
43359
|
+
const dbPath2 = join11(homedir10(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
|
|
43348
43360
|
if (!existsSync6(dbPath2)) return null;
|
|
43349
43361
|
try {
|
|
43350
43362
|
const { DatabaseSync } = __require("node:sqlite");
|
|
@@ -46165,7 +46177,7 @@ var NOTES_DB_PATH7 = path6.join(
|
|
|
46165
46177
|
os6.homedir(),
|
|
46166
46178
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46167
46179
|
);
|
|
46168
|
-
var FDA_MESSAGE2 =
|
|
46180
|
+
var FDA_MESSAGE2 = "Full Disk Access is required to read note metadata.";
|
|
46169
46181
|
var COLUMN_MAP = [
|
|
46170
46182
|
{ key: "pinned", column: "ZISPINNED", type: "bool" },
|
|
46171
46183
|
{ key: "hasChecklist", column: "ZHASCHECKLIST", type: "bool" },
|
|
@@ -46204,7 +46216,7 @@ function getNoteMetadata(noteId3) {
|
|
|
46204
46216
|
}
|
|
46205
46217
|
const pk = pkMatch[1];
|
|
46206
46218
|
if (!fs6.existsSync(NOTES_DB_PATH7)) {
|
|
46207
|
-
return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
|
|
46219
|
+
return { metadata: null, error: "no_fda", message: `${FDA_MESSAGE2} ${fdaRemediation()}` };
|
|
46208
46220
|
}
|
|
46209
46221
|
try {
|
|
46210
46222
|
const available = presentColumns();
|
|
@@ -46238,7 +46250,7 @@ function getNoteMetadata(noteId3) {
|
|
|
46238
46250
|
} catch (error2) {
|
|
46239
46251
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
46240
46252
|
if (message.includes("authorization denied") || message.includes("unable to open database")) {
|
|
46241
|
-
return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
|
|
46253
|
+
return { metadata: null, error: "no_fda", message: `${FDA_MESSAGE2} ${fdaRemediation()}` };
|
|
46242
46254
|
}
|
|
46243
46255
|
console.error(`Failed to read note metadata: ${message}`);
|
|
46244
46256
|
return { metadata: null, error: "query_error", message: "Failed to read note metadata." };
|
|
@@ -46258,7 +46270,7 @@ var NOTES_DB_PATH8 = path7.join(
|
|
|
46258
46270
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46259
46271
|
);
|
|
46260
46272
|
var CORE_DATA_EPOCH_MS = Date.UTC(2001, 0, 1);
|
|
46261
|
-
var STORE_FDA_MESSAGE =
|
|
46273
|
+
var STORE_FDA_MESSAGE = "Full Disk Access is required to read the Notes database.";
|
|
46262
46274
|
var NoteStoreError = class extends Error {
|
|
46263
46275
|
constructor(message, kind) {
|
|
46264
46276
|
super(message);
|
|
@@ -46292,7 +46304,8 @@ function renderValue(value) {
|
|
|
46292
46304
|
return `ieee754_from_blob(x'${doubleToHex(double)}')`;
|
|
46293
46305
|
}
|
|
46294
46306
|
function runReadOnlySql(dbPath2, sql, params = {}) {
|
|
46295
|
-
if (!fs7.existsSync(dbPath2))
|
|
46307
|
+
if (!fs7.existsSync(dbPath2))
|
|
46308
|
+
throw new NoteStoreError(`${STORE_FDA_MESSAGE} ${fdaRemediation()}`, "no_fda");
|
|
46296
46309
|
const args = ["-readonly"];
|
|
46297
46310
|
const names = Object.keys(params);
|
|
46298
46311
|
if (names.length) args.push("-cmd", ".parameter init");
|
|
@@ -46313,7 +46326,7 @@ function runReadOnlySql(dbPath2, sql, params = {}) {
|
|
|
46313
46326
|
} catch (error2) {
|
|
46314
46327
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
46315
46328
|
if (/authorization denied|unable to open database/i.test(message)) {
|
|
46316
|
-
throw new NoteStoreError(STORE_FDA_MESSAGE
|
|
46329
|
+
throw new NoteStoreError(`${STORE_FDA_MESSAGE} ${fdaRemediation()}`, "no_fda");
|
|
46317
46330
|
}
|
|
46318
46331
|
console.error(`Notes database query failed: ${message}`);
|
|
46319
46332
|
throw new NoteStoreError("Failed to query the Notes database.", "query_error");
|
|
@@ -46669,8 +46682,8 @@ function assembleInventory(rows, accounts, scope2) {
|
|
|
46669
46682
|
// src/utils/noteBlocks.ts
|
|
46670
46683
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
46671
46684
|
import { existsSync as existsSync10 } from "node:fs";
|
|
46672
|
-
import { homedir as
|
|
46673
|
-
import { join as
|
|
46685
|
+
import { homedir as homedir14 } from "node:os";
|
|
46686
|
+
import { join as join15 } from "node:path";
|
|
46674
46687
|
import { gunzipSync as gunzipSync7 } from "node:zlib";
|
|
46675
46688
|
var NOTE_BLOCKS_ERROR_CODES = {
|
|
46676
46689
|
"invalid-id": "validation_error",
|
|
@@ -47026,8 +47039,8 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
|
|
|
47026
47039
|
undecodedFields: doc.undecodedFields
|
|
47027
47040
|
};
|
|
47028
47041
|
}
|
|
47029
|
-
var NOTES_DB_PATH9 =
|
|
47030
|
-
|
|
47042
|
+
var NOTES_DB_PATH9 = join15(
|
|
47043
|
+
homedir14(),
|
|
47031
47044
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
47032
47045
|
);
|
|
47033
47046
|
function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
|
|
@@ -47966,7 +47979,7 @@ var QUERY_RESULTS = { DEFAULT: 50, MAX: 500 };
|
|
|
47966
47979
|
var CORE_DATA_EPOCH_MS2 = Date.UTC(2001, 0, 1);
|
|
47967
47980
|
var SNIPPET_BEFORE = 60;
|
|
47968
47981
|
var SNIPPET_LENGTH = 180;
|
|
47969
|
-
var QUERY_FDA_MESSAGE =
|
|
47982
|
+
var QUERY_FDA_MESSAGE = "Full Disk Access is required to query notes.";
|
|
47970
47983
|
var NoteQueryStoreError = class extends Error {
|
|
47971
47984
|
constructor(message, kind) {
|
|
47972
47985
|
super(message);
|
|
@@ -48112,14 +48125,15 @@ function presentColumns2(dbPath2) {
|
|
|
48112
48125
|
return cols;
|
|
48113
48126
|
}
|
|
48114
48127
|
function readStore(dbPath2, build) {
|
|
48115
|
-
if (!fs8.existsSync(dbPath2))
|
|
48128
|
+
if (!fs8.existsSync(dbPath2))
|
|
48129
|
+
throw new NoteQueryStoreError(`${QUERY_FDA_MESSAGE} ${fdaRemediation()}`, "no_fda");
|
|
48116
48130
|
try {
|
|
48117
48131
|
return runSqlite6(dbPath2, build(presentColumns2(dbPath2)));
|
|
48118
48132
|
} catch (error2) {
|
|
48119
48133
|
if (error2 instanceof NoteQueryStoreError) throw error2;
|
|
48120
48134
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
48121
48135
|
if (message.includes("authorization denied") || message.includes("unable to open database")) {
|
|
48122
|
-
throw new NoteQueryStoreError(QUERY_FDA_MESSAGE
|
|
48136
|
+
throw new NoteQueryStoreError(`${QUERY_FDA_MESSAGE} ${fdaRemediation()}`, "no_fda");
|
|
48123
48137
|
}
|
|
48124
48138
|
console.error(`query-notes: database read failed: ${message}`);
|
|
48125
48139
|
throw new NoteQueryStoreError("Failed to read the Notes database.", "query_error");
|
|
@@ -48176,13 +48190,13 @@ var escapeSegment = escapeFolderName;
|
|
|
48176
48190
|
function resolveFolders(rows) {
|
|
48177
48191
|
const byPk = new Map(rows.map((row) => [row.pk, row]));
|
|
48178
48192
|
const resolved = /* @__PURE__ */ new Map();
|
|
48179
|
-
const
|
|
48193
|
+
const resolve11 = (pk, seen) => {
|
|
48180
48194
|
const cached2 = resolved.get(pk);
|
|
48181
48195
|
if (cached2) return cached2;
|
|
48182
48196
|
const row = byPk.get(pk);
|
|
48183
48197
|
if (!row || seen.has(pk)) return void 0;
|
|
48184
48198
|
seen.add(pk);
|
|
48185
|
-
const parent = row.parent !== null ?
|
|
48199
|
+
const parent = row.parent !== null ? resolve11(row.parent, seen) : void 0;
|
|
48186
48200
|
const name = row.name ?? "";
|
|
48187
48201
|
const path10 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
|
|
48188
48202
|
const plainPath = parent ? `${parent.plainPath}/${name}` : name;
|
|
@@ -48196,7 +48210,7 @@ function resolveFolders(rows) {
|
|
|
48196
48210
|
resolved.set(pk, info);
|
|
48197
48211
|
return info;
|
|
48198
48212
|
};
|
|
48199
|
-
for (const row of rows)
|
|
48213
|
+
for (const row of rows) resolve11(row.pk, /* @__PURE__ */ new Set());
|
|
48200
48214
|
return resolved;
|
|
48201
48215
|
}
|
|
48202
48216
|
var coreDataMs = (seconds) => seconds === null || !Number.isFinite(seconds) ? void 0 : CORE_DATA_EPOCH_MS2 + seconds * 1e3;
|
|
@@ -48411,7 +48425,7 @@ function describeContentScan(scan) {
|
|
|
48411
48425
|
}
|
|
48412
48426
|
function contentSearchFailureHint(message, dbUnavailable) {
|
|
48413
48427
|
if (!/timed out/i.test(message)) return message;
|
|
48414
|
-
const remedy = dbUnavailable === "no_fda" ?
|
|
48428
|
+
const remedy = dbUnavailable === "no_fda" ? ` Full Disk Access lets search-notes search note bodies through the Notes database instead. ${fdaRemediation()}` : "";
|
|
48415
48429
|
return `${message} Body search through AppleScript scans every note body before the result limit applies, so a broad term can exceed the time budget on a large library.${remedy} Otherwise narrow the search with \`folder\` or \`modifiedSince\`, or use a more specific term.`;
|
|
48416
48430
|
}
|
|
48417
48431
|
var NOTE_ID = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/ICNote\/p(\d{1,15})$/;
|
|
@@ -48471,7 +48485,7 @@ import { spawnSync } from "child_process";
|
|
|
48471
48485
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
48472
48486
|
import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
|
|
48473
48487
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
48474
|
-
import { join as
|
|
48488
|
+
import { join as join17 } from "node:path";
|
|
48475
48489
|
|
|
48476
48490
|
// src/services/shortcutConsent.ts
|
|
48477
48491
|
function shortcutConsentHint(shortcut) {
|
|
@@ -48573,9 +48587,9 @@ function runNativeTagsShortcut(input) {
|
|
|
48573
48587
|
const status = nativeTagsStatus();
|
|
48574
48588
|
if (!status.installed)
|
|
48575
48589
|
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
48576
|
-
const directory = mkdtempSync3(
|
|
48590
|
+
const directory = mkdtempSync3(join17(tmpdir3(), "apple-notes-native-tags-"));
|
|
48577
48591
|
try {
|
|
48578
|
-
const path10 =
|
|
48592
|
+
const path10 = join17(directory, "request.json");
|
|
48579
48593
|
writeFileSync2(path10, JSON.stringify(input), { mode: 384 });
|
|
48580
48594
|
execFileSync15("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path10], {
|
|
48581
48595
|
encoding: "utf8",
|
|
@@ -48599,7 +48613,7 @@ function runNativeTagsShortcut(input) {
|
|
|
48599
48613
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
48600
48614
|
import { mkdtempSync as mkdtempSync4, writeFileSync as writeFileSync3, rmSync as rmSync4 } from "node:fs";
|
|
48601
48615
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
48602
|
-
import { join as
|
|
48616
|
+
import { join as join18 } from "node:path";
|
|
48603
48617
|
|
|
48604
48618
|
// src/utils/appendMarkdown.ts
|
|
48605
48619
|
var escape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -48788,7 +48802,7 @@ function appendMarkdownHtml(markdown, options = {}) {
|
|
|
48788
48802
|
}
|
|
48789
48803
|
|
|
48790
48804
|
// src/utils/noteRevision.ts
|
|
48791
|
-
var INLINE_TAG = /^<\/?(?:b|i|u|s|strike|em|strong|span|a|font|sub|sup|code|tt|small|big|mark)\b/i;
|
|
48805
|
+
var INLINE_TAG = /^<\/?(?:b|i|u|s|strike|del|em|strong|span|a|font|sub|sup|code|tt|small|big|mark)\b/i;
|
|
48792
48806
|
function comparableVisibleText(html) {
|
|
48793
48807
|
return decodeHtmlEntities(
|
|
48794
48808
|
html.replace(/<br\s*\/?\s*>/gi, " ").replace(/<[^>]*>/g, (tag) => INLINE_TAG.test(tag) ? "" : " ")
|
|
@@ -48910,9 +48924,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
48910
48924
|
),
|
|
48911
48925
|
{ shortcut: status.shortcut, notInstalled: true }
|
|
48912
48926
|
);
|
|
48913
|
-
const directory = mkdtempSync4(
|
|
48927
|
+
const directory = mkdtempSync4(join18(tmpdir4(), "apple-notes-background-"));
|
|
48914
48928
|
try {
|
|
48915
|
-
const file =
|
|
48929
|
+
const file = join18(directory, "request.json");
|
|
48916
48930
|
writeFileSync3(
|
|
48917
48931
|
file,
|
|
48918
48932
|
JSON.stringify({
|
|
@@ -49668,10 +49682,6 @@ function runDoctor(manager, capabilityMatrix = getCapabilityMatrix) {
|
|
|
49668
49682
|
}
|
|
49669
49683
|
return matrix ? { healthy, checks, runtimeOS: matrix.runtimeOS, features: matrix.features } : { healthy, checks };
|
|
49670
49684
|
}
|
|
49671
|
-
function fdaRemediation(execPath = process.execPath) {
|
|
49672
|
-
const versioned = /\/(\.nvm|\.fnm|\.volta|\.asdf|\.local\/share\/mise|\.nodenv|n\/versions)\//.test(execPath);
|
|
49673
|
-
return `In System Settings > Privacy & Security > Full Disk Access, click + and add the Node binary running this server: ${execPath} (press Cmd+Shift+G in the file picker to paste the path). Under Claude Desktop that entry is required: Claude Desktop launches servers as their own responsible process, so a grant on Claude.app does not reach them. When the server runs from a terminal (Terminal, iTerm2) or an editor, granting that app is enough. Then fully quit (Cmd+Q) and relaunch the host app and re-run doctor; if it still reports not granted, restart the Mac. ` + (versioned ? "This Node lives under a version manager, so the path changes with each Node version and the grant has to be added again after switching; pointing the MCP config at one fixed Node path avoids that. " : "") + `Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
|
|
49674
|
-
}
|
|
49675
49685
|
function markdownBridgeDetail() {
|
|
49676
49686
|
const purpose = 'needed only for create-note format: "markdown" on macOS 26+';
|
|
49677
49687
|
try {
|
|
@@ -49721,14 +49731,15 @@ function formatDoctorReport(r) {
|
|
|
49721
49731
|
|
|
49722
49732
|
// src/services/fileConfig.ts
|
|
49723
49733
|
import { existsSync as existsSync12, readFileSync as readFileSync2 } from "fs";
|
|
49724
|
-
import { join as
|
|
49725
|
-
import { homedir as
|
|
49734
|
+
import { join as join19 } from "path";
|
|
49735
|
+
import { homedir as homedir16 } from "os";
|
|
49726
49736
|
function fileConfigPath(env = process.env) {
|
|
49727
49737
|
const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
|
|
49728
49738
|
if (override && override.trim()) return override.trim();
|
|
49729
|
-
return
|
|
49739
|
+
return join19(homedir16(), "Library", "Application Support", "apple-notes-mcp", "config.json");
|
|
49730
49740
|
}
|
|
49731
49741
|
function loadFileConfig(env = process.env, path10 = fileConfigPath(env)) {
|
|
49742
|
+
if (env.APPLE_NOTES_MCP_BROKERED === "1") return [];
|
|
49732
49743
|
const applied = [];
|
|
49733
49744
|
try {
|
|
49734
49745
|
if (!existsSync12(path10)) return applied;
|
|
@@ -50911,7 +50922,7 @@ function insertLink(request, deps) {
|
|
|
50911
50922
|
|
|
50912
50923
|
// src/services/notesExport.ts
|
|
50913
50924
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
50914
|
-
import { basename as basename4, dirname as dirname7, extname as extname5, join as
|
|
50925
|
+
import { basename as basename4, dirname as dirname7, extname as extname5, join as join24 } from "node:path";
|
|
50915
50926
|
|
|
50916
50927
|
// src/utils/exportAssets.ts
|
|
50917
50928
|
import {
|
|
@@ -50926,9 +50937,9 @@ import {
|
|
|
50926
50937
|
statSync as statSync3,
|
|
50927
50938
|
writeSync as writeSync3
|
|
50928
50939
|
} from "node:fs";
|
|
50929
|
-
import { homedir as
|
|
50930
|
-
import { basename as basename3, extname as extname3, join as
|
|
50931
|
-
var NOTES_CONTAINER =
|
|
50940
|
+
import { homedir as homedir17 } from "node:os";
|
|
50941
|
+
import { basename as basename3, extname as extname3, join as join20, relative as relative3, resolve as resolve3, sep as sep3 } from "node:path";
|
|
50942
|
+
var NOTES_CONTAINER = join20(homedir17(), "Library/Group Containers/group.com.apple.notes");
|
|
50932
50943
|
var MAX_EMBED_BYTES = 10 * 1024 * 1024;
|
|
50933
50944
|
var MAX_EMBED_TOTAL_BYTES = 256 * 1024 * 1024;
|
|
50934
50945
|
var MAX_DIRECTORY_ENTRIES = 2e4;
|
|
@@ -50984,11 +50995,11 @@ var AssetLocator = class {
|
|
|
50984
50995
|
constructor(container = NOTES_CONTAINER) {
|
|
50985
50996
|
let accountsRoot = "";
|
|
50986
50997
|
try {
|
|
50987
|
-
accountsRoot = realpathSync3.native(
|
|
50998
|
+
accountsRoot = realpathSync3.native(join20(container, "Accounts"));
|
|
50988
50999
|
} catch {
|
|
50989
51000
|
}
|
|
50990
51001
|
this.accounts = accountsRoot ? listDirectory(accountsRoot, 1e3).flatMap((name) => {
|
|
50991
|
-
const dir = safeComponent2(name) && confine(
|
|
51002
|
+
const dir = safeComponent2(name) && confine(join20(accountsRoot, name), accountsRoot);
|
|
50992
51003
|
return dir && isDirectory2(dir) ? [dir] : [];
|
|
50993
51004
|
}) : [];
|
|
50994
51005
|
}
|
|
@@ -51062,8 +51073,8 @@ var AssetLocator = class {
|
|
|
51062
51073
|
if (!mediaId || !filename) return void 0;
|
|
51063
51074
|
const generation = safeComponent2(source.mediaGeneration);
|
|
51064
51075
|
const candidates = [
|
|
51065
|
-
...generation ? [
|
|
51066
|
-
|
|
51076
|
+
...generation ? [join20(account, "Media", mediaId, generation, filename)] : [],
|
|
51077
|
+
join20(account, "Media", mediaId, filename)
|
|
51067
51078
|
];
|
|
51068
51079
|
for (const candidate of candidates) {
|
|
51069
51080
|
const path10 = confine(candidate, account);
|
|
@@ -51077,7 +51088,7 @@ var AssetLocator = class {
|
|
|
51077
51088
|
* somewhere else.
|
|
51078
51089
|
*/
|
|
51079
51090
|
fallback(account, dir, id2, generation, names) {
|
|
51080
|
-
const base =
|
|
51091
|
+
const base = join20(account, dir, id2);
|
|
51081
51092
|
const gen = safeComponent2(generation);
|
|
51082
51093
|
const generations = [
|
|
51083
51094
|
...gen ? [gen] : [],
|
|
@@ -51086,7 +51097,7 @@ var AssetLocator = class {
|
|
|
51086
51097
|
];
|
|
51087
51098
|
for (const g of generations)
|
|
51088
51099
|
for (const name of names) {
|
|
51089
|
-
const path10 = confine(g ?
|
|
51100
|
+
const path10 = confine(g ? join20(base, g, name) : join20(base, name), account);
|
|
51090
51101
|
if (path10 && isFile(path10)) return { path: path10, stale: Boolean(gen) && g !== gen };
|
|
51091
51102
|
}
|
|
51092
51103
|
return void 0;
|
|
@@ -51096,7 +51107,7 @@ var AssetLocator = class {
|
|
|
51096
51107
|
let index = this.previewIndex.get(account);
|
|
51097
51108
|
if (!index) {
|
|
51098
51109
|
index = /* @__PURE__ */ new Map();
|
|
51099
|
-
for (const name of listDirectory(
|
|
51110
|
+
for (const name of listDirectory(join20(account, "Previews"), MAX_DIRECTORY_ENTRIES)) {
|
|
51100
51111
|
const key = name.slice(0, 36).toUpperCase();
|
|
51101
51112
|
const entries3 = index.get(key) ?? [];
|
|
51102
51113
|
entries3.push(name);
|
|
@@ -51109,7 +51120,7 @@ var AssetLocator = class {
|
|
|
51109
51120
|
return !ext || IMAGE_EXTENSIONS.has(ext) || /^\.\d+$/.test(ext);
|
|
51110
51121
|
}).sort((a, b) => previewArea(b) - previewArea(a) || a.localeCompare(b));
|
|
51111
51122
|
for (const name of entries2) {
|
|
51112
|
-
const entry = confine(
|
|
51123
|
+
const entry = confine(join20(account, "Previews", name), account);
|
|
51113
51124
|
if (!entry) continue;
|
|
51114
51125
|
const file = isFile(entry) ? entry : this.bundleImage(entry, account);
|
|
51115
51126
|
if (file) return { path: file, name: "preview", role: "preview" };
|
|
@@ -51120,12 +51131,12 @@ var AssetLocator = class {
|
|
|
51120
51131
|
bundleImage(bundle, account) {
|
|
51121
51132
|
const found = [];
|
|
51122
51133
|
for (const child2 of listDirectory(bundle, MAX_BUNDLE_ENTRIES2)) {
|
|
51123
|
-
const path10 = confine(
|
|
51134
|
+
const path10 = confine(join20(bundle, child2), account);
|
|
51124
51135
|
if (!path10) continue;
|
|
51125
51136
|
if (isFile(path10)) found.push(path10);
|
|
51126
51137
|
else
|
|
51127
51138
|
for (const grandchild of listDirectory(path10, MAX_BUNDLE_ENTRIES2)) {
|
|
51128
|
-
const inner = confine(
|
|
51139
|
+
const inner = confine(join20(path10, grandchild), account);
|
|
51129
51140
|
if (inner && isFile(inner)) found.push(inner);
|
|
51130
51141
|
}
|
|
51131
51142
|
}
|
|
@@ -51186,11 +51197,11 @@ function canonicalForm(abs) {
|
|
|
51186
51197
|
for (; ; ) {
|
|
51187
51198
|
try {
|
|
51188
51199
|
const real = realpathSync3.native(current);
|
|
51189
|
-
return rest ?
|
|
51200
|
+
return rest ? join20(real, rest) : real;
|
|
51190
51201
|
} catch {
|
|
51191
51202
|
const parent = resolve3(current, "..");
|
|
51192
51203
|
if (parent === current) return abs;
|
|
51193
|
-
rest = rest ?
|
|
51204
|
+
rest = rest ? join20(basename3(current), rest) : basename3(current);
|
|
51194
51205
|
current = parent;
|
|
51195
51206
|
}
|
|
51196
51207
|
}
|
|
@@ -51317,7 +51328,7 @@ var SidecarWriter = class {
|
|
|
51317
51328
|
let target;
|
|
51318
51329
|
let out;
|
|
51319
51330
|
for (let n = 1; n <= 1e3 && out === void 0; n++) {
|
|
51320
|
-
target =
|
|
51331
|
+
target = join20(this.dir, n === 1 ? safe2 : `${stem}-${n}${ext}`);
|
|
51321
51332
|
try {
|
|
51322
51333
|
out = openSync4(target, CREATE_FLAGS, 420);
|
|
51323
51334
|
} catch (error2) {
|
|
@@ -51989,8 +52000,8 @@ import {
|
|
|
51989
52000
|
rmSync as rmSync5,
|
|
51990
52001
|
writeFileSync as writeFileSync4
|
|
51991
52002
|
} from "node:fs";
|
|
51992
|
-
import { homedir as
|
|
51993
|
-
import { dirname as dirname6, join as
|
|
52003
|
+
import { homedir as homedir18, release as release2 } from "node:os";
|
|
52004
|
+
import { dirname as dirname6, join as join21, resolve as resolve4 } from "node:path";
|
|
51994
52005
|
import { fileURLToPath } from "node:url";
|
|
51995
52006
|
var PUBLIC_HELPER_PROTOCOL = 1;
|
|
51996
52007
|
var PUBLIC_HELPER_DIR_ENV = "APPLE_NOTES_MCP_PUBLIC_HELPER_DIR";
|
|
@@ -52019,7 +52030,7 @@ var publicManifestSchema = external_exports.object({
|
|
|
52019
52030
|
function packageRoot(fromDir = dirname6(fileURLToPath(import.meta.url))) {
|
|
52020
52031
|
let dir = fromDir;
|
|
52021
52032
|
for (; ; ) {
|
|
52022
|
-
const candidate =
|
|
52033
|
+
const candidate = join21(dir, "package.json");
|
|
52023
52034
|
if (existsSync13(candidate)) {
|
|
52024
52035
|
try {
|
|
52025
52036
|
if (JSON.parse(readFileSync3(candidate, "utf8")).name === "apple-notes-mcp")
|
|
@@ -52036,7 +52047,7 @@ function defaultPublicHelperDeps(overrides = {}) {
|
|
|
52036
52047
|
return {
|
|
52037
52048
|
env: process.env,
|
|
52038
52049
|
platform: process.platform,
|
|
52039
|
-
sourcePath:
|
|
52050
|
+
sourcePath: join21(packageRoot(), PUBLIC_HELPER_SOURCE),
|
|
52040
52051
|
exists: existsSync13,
|
|
52041
52052
|
readFile: (path10) => readFileSync3(path10),
|
|
52042
52053
|
spawn: spawnSync2,
|
|
@@ -52047,14 +52058,14 @@ function defaultPublicHelperDeps(overrides = {}) {
|
|
|
52047
52058
|
function publicHelperInstallDir(env = process.env) {
|
|
52048
52059
|
const override = env[PUBLIC_HELPER_DIR_ENV]?.trim();
|
|
52049
52060
|
if (override) return override;
|
|
52050
|
-
return
|
|
52061
|
+
return join21(homedir18(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
|
|
52051
52062
|
}
|
|
52052
52063
|
function sha256Hex(data) {
|
|
52053
52064
|
return createHash2("sha256").update(data).digest("hex");
|
|
52054
52065
|
}
|
|
52055
52066
|
function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
|
|
52056
52067
|
const installDir = publicHelperInstallDir(deps.env);
|
|
52057
|
-
const binaryPath =
|
|
52068
|
+
const binaryPath = join21(installDir, PUBLIC_HELPER_BINARY);
|
|
52058
52069
|
const base = { installDir, binaryPath, sourcePath: deps.sourcePath, manifest: null };
|
|
52059
52070
|
const fail = (reason, detail) => ({
|
|
52060
52071
|
...base,
|
|
@@ -52065,7 +52076,7 @@ function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
|
|
|
52065
52076
|
if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
|
|
52066
52077
|
if (!deps.exists(deps.sourcePath))
|
|
52067
52078
|
return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
|
|
52068
|
-
const manifestPath =
|
|
52079
|
+
const manifestPath = join21(installDir, PUBLIC_HELPER_MANIFEST);
|
|
52069
52080
|
if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
|
|
52070
52081
|
return fail(
|
|
52071
52082
|
"helper_not_installed",
|
|
@@ -52342,11 +52353,11 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
|
|
|
52342
52353
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
52343
52354
|
const installDir = publicHelperInstallDir(deps.env);
|
|
52344
52355
|
mkdirSync5(installDir, { recursive: true, mode: 448 });
|
|
52345
|
-
const staging = mkdtempSync5(
|
|
52356
|
+
const staging = mkdtempSync5(join21(installDir, ".staging-"));
|
|
52346
52357
|
try {
|
|
52347
|
-
const stagedBinary =
|
|
52348
|
-
const digestPath =
|
|
52349
|
-
const plistPath =
|
|
52358
|
+
const stagedBinary = join21(staging, PUBLIC_HELPER_BINARY);
|
|
52359
|
+
const digestPath = join21(staging, "source-digest.swift");
|
|
52360
|
+
const plistPath = join21(staging, "Info.plist");
|
|
52350
52361
|
writeFileSync4(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
|
|
52351
52362
|
writeFileSync4(plistPath, publicHelperInfoPlist(), { mode: 384 });
|
|
52352
52363
|
const compile = deps.spawn(
|
|
@@ -52409,9 +52420,9 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
|
|
|
52409
52420
|
compiler
|
|
52410
52421
|
};
|
|
52411
52422
|
chmodSync(stagedBinary, 448);
|
|
52412
|
-
renameSync(stagedBinary,
|
|
52423
|
+
renameSync(stagedBinary, join21(installDir, PUBLIC_HELPER_BINARY));
|
|
52413
52424
|
writeFileSync4(
|
|
52414
|
-
|
|
52425
|
+
join21(installDir, PUBLIC_HELPER_MANIFEST),
|
|
52415
52426
|
JSON.stringify(manifest, null, 2) + "\n",
|
|
52416
52427
|
{ mode: 384 }
|
|
52417
52428
|
);
|
|
@@ -52863,10 +52874,10 @@ function renderNotesMarkdown(notes, ctx, { wrap: wrap3 = 0 } = {}) {
|
|
|
52863
52874
|
|
|
52864
52875
|
// src/utils/noteExportData.ts
|
|
52865
52876
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
52866
|
-
import { homedir as
|
|
52867
|
-
import { join as
|
|
52868
|
-
var NOTES_DB_PATH11 =
|
|
52869
|
-
|
|
52877
|
+
import { homedir as homedir19 } from "node:os";
|
|
52878
|
+
import { join as join22 } from "node:path";
|
|
52879
|
+
var NOTES_DB_PATH11 = join22(
|
|
52880
|
+
homedir19(),
|
|
52870
52881
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
52871
52882
|
);
|
|
52872
52883
|
var IMAGE_UTIS2 = /* @__PURE__ */ new Set([
|
|
@@ -53177,16 +53188,16 @@ var NOTE_PLACEHOLDERS = [
|
|
|
53177
53188
|
"exportStem"
|
|
53178
53189
|
];
|
|
53179
53190
|
var PLACEHOLDER_MODIFIERS = ["raw", "yaml"];
|
|
53180
|
-
var wrap2 = (before, after = "",
|
|
53191
|
+
var wrap2 = (before, after = "", join35) => ({
|
|
53181
53192
|
mode: "wrap",
|
|
53182
53193
|
before,
|
|
53183
53194
|
after,
|
|
53184
|
-
...
|
|
53195
|
+
...join35 ? { join: join35 } : {}
|
|
53185
53196
|
});
|
|
53186
|
-
var pattern = (value,
|
|
53197
|
+
var pattern = (value, join35) => ({
|
|
53187
53198
|
mode: "pattern",
|
|
53188
53199
|
value,
|
|
53189
|
-
...
|
|
53200
|
+
...join35 ? { join: join35 } : {}
|
|
53190
53201
|
});
|
|
53191
53202
|
var STANDARD = {
|
|
53192
53203
|
schemaVersion: 1,
|
|
@@ -54074,7 +54085,7 @@ import {
|
|
|
54074
54085
|
unlinkSync as unlinkSync3,
|
|
54075
54086
|
writeSync as writeSync4
|
|
54076
54087
|
} from "node:fs";
|
|
54077
|
-
import { extname as extname4, isAbsolute as isAbsolute3, join as
|
|
54088
|
+
import { extname as extname4, isAbsolute as isAbsolute3, join as join23, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
|
|
54078
54089
|
var CHUNK = 1024 * 1024;
|
|
54079
54090
|
function openRegular(path10) {
|
|
54080
54091
|
const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
@@ -54145,7 +54156,7 @@ var HashedSidecarWriter = class {
|
|
|
54145
54156
|
if (directoryError) return { error: directoryError };
|
|
54146
54157
|
const name = safeAssetName(asset.name, mime);
|
|
54147
54158
|
const ext = extname4(name);
|
|
54148
|
-
const target =
|
|
54159
|
+
const target = join23(
|
|
54149
54160
|
this.dir,
|
|
54150
54161
|
`${name.slice(0, name.length - ext.length)}-${hash.slice(0, 8)}${ext}`
|
|
54151
54162
|
);
|
|
@@ -54182,7 +54193,7 @@ var HashedSidecarWriter = class {
|
|
|
54182
54193
|
* remove it if the copy fails.
|
|
54183
54194
|
*/
|
|
54184
54195
|
copy(source, target) {
|
|
54185
|
-
const temp =
|
|
54196
|
+
const temp = join23(this.dir, `.asset-${randomBytes(6).toString("hex")}.tmp`);
|
|
54186
54197
|
writeNew(source, temp);
|
|
54187
54198
|
try {
|
|
54188
54199
|
linkSync(temp, target);
|
|
@@ -54449,7 +54460,7 @@ function exportNotesMarkdown(request, deps) {
|
|
|
54449
54460
|
return { ...receipt, markdown };
|
|
54450
54461
|
}
|
|
54451
54462
|
function defaultSidecarDir(output) {
|
|
54452
|
-
return
|
|
54463
|
+
return join24(dirname7(output), `${basename4(output, extname5(output))}.assets`);
|
|
54453
54464
|
}
|
|
54454
54465
|
function exportNotesHtml(request, deps) {
|
|
54455
54466
|
if (!request.outputPath)
|
|
@@ -54696,7 +54707,7 @@ import {
|
|
|
54696
54707
|
writeFileSync as writeFileSync6
|
|
54697
54708
|
} from "node:fs";
|
|
54698
54709
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
54699
|
-
import { basename as basename6, extname as extname7, isAbsolute as isAbsolute4, join as
|
|
54710
|
+
import { basename as basename6, extname as extname7, isAbsolute as isAbsolute4, join as join26 } from "node:path";
|
|
54700
54711
|
|
|
54701
54712
|
// src/utils/pasteboardFreeze.ts
|
|
54702
54713
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
@@ -54712,7 +54723,7 @@ import {
|
|
|
54712
54723
|
writeFileSync as writeFileSync5
|
|
54713
54724
|
} from "node:fs";
|
|
54714
54725
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
54715
|
-
import { basename as basename5, extname as extname6, join as
|
|
54726
|
+
import { basename as basename5, extname as extname6, join as join25 } from "node:path";
|
|
54716
54727
|
var PASTEBOARD_NAME_ENV = "APPLE_NOTES_MCP_PASTEBOARD_NAME";
|
|
54717
54728
|
var MAX_PASTEBOARD_BYTES = 64 * 1024 * 1024;
|
|
54718
54729
|
var PASTEBOARD_DATA_TYPES = [
|
|
@@ -54888,7 +54899,7 @@ function readRegularFile(path10) {
|
|
|
54888
54899
|
}
|
|
54889
54900
|
}
|
|
54890
54901
|
function freezePasteboard(options = {}) {
|
|
54891
|
-
const directory = mkdtempSync6(
|
|
54902
|
+
const directory = mkdtempSync6(join25(tmpdir5(), "notes-pasteboard-"));
|
|
54892
54903
|
const cleanup = () => rmSync6(directory, { recursive: true, force: true });
|
|
54893
54904
|
try {
|
|
54894
54905
|
const prefs = JSON.stringify(PASTEBOARD_DATA_TYPES.map((t) => [t.type, t.ext]));
|
|
@@ -54917,15 +54928,15 @@ function freezePasteboard(options = {}) {
|
|
|
54917
54928
|
if (reply.kind === "file") {
|
|
54918
54929
|
const bytes = readRegularFile(reply.path);
|
|
54919
54930
|
const filename2 = basename5(reply.path);
|
|
54920
|
-
const path11 =
|
|
54931
|
+
const path11 = join25(directory, filename2);
|
|
54921
54932
|
writeFileSync5(path11, bytes, { mode: 384 });
|
|
54922
54933
|
return { kind: "file", type: reply.type, path: path11, filename: filename2, bytes: bytes.length, cleanup };
|
|
54923
54934
|
}
|
|
54924
|
-
if (
|
|
54935
|
+
if (join25(directory, basename5(reply.path)) !== reply.path)
|
|
54925
54936
|
throw new PasteboardError("write_failed", MESSAGES.write_failed);
|
|
54926
54937
|
const label = PASTEBOARD_DATA_TYPES.find((t) => t.type === reply.type)?.label ?? "item";
|
|
54927
54938
|
const filename = `Pasted ${label}${extname6(reply.path)}`;
|
|
54928
|
-
const path10 =
|
|
54939
|
+
const path10 = join25(directory, filename);
|
|
54929
54940
|
writeFileSync5(path10, readFileSync4(reply.path), { mode: 384 });
|
|
54930
54941
|
rmSync6(reply.path, { force: true });
|
|
54931
54942
|
return { kind: "data", type: reply.type, path: path10, filename, bytes: statSync4(path10).size, cleanup };
|
|
@@ -55284,8 +55295,8 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
|
|
|
55284
55295
|
const bytes = localAttachment(path10);
|
|
55285
55296
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
55286
55297
|
const beforeStored = storedAttachmentIds(manager, id2);
|
|
55287
|
-
const directory = mkdtempSync7(
|
|
55288
|
-
const temporaryFile =
|
|
55298
|
+
const directory = mkdtempSync7(join26(tmpdir6(), "notes-attachment-add-"));
|
|
55299
|
+
const temporaryFile = join26(directory, name);
|
|
55289
55300
|
try {
|
|
55290
55301
|
writeFileSync6(temporaryFile, bytes, { mode: 384 });
|
|
55291
55302
|
if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
|
|
@@ -55314,7 +55325,7 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
|
|
|
55314
55325
|
if (inserted.length === 1 && !(persistentReturnedId && returnedId !== inserted[0].id)) {
|
|
55315
55326
|
attachmentId = inserted[0].id;
|
|
55316
55327
|
reportedName = inserted[0].name;
|
|
55317
|
-
const verifyPath =
|
|
55328
|
+
const verifyPath = join26(directory, "verify", "attachment.bin");
|
|
55318
55329
|
const saved = manager.saveAttachmentById(id2, attachmentId, verifyPath);
|
|
55319
55330
|
if (!saved.success || !fileMatches(saved.savedPath ?? verifyPath, bytes.length, sha256(bytes)))
|
|
55320
55331
|
throw new Error("Attachment bytes were not verified; read the exact note before retrying");
|
|
@@ -55322,7 +55333,7 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
|
|
|
55322
55333
|
const stored = inserted.length === 0 && beforeStored ? storedInsertion(manager, id2, beforeStored, bytes, name, returnedId) : null;
|
|
55323
55334
|
if (!stored)
|
|
55324
55335
|
throw new Error(
|
|
55325
|
-
inserted.length === 0 && !beforeStored ? `${UNCERTAIN}. Notes' AppleScript does not list some attachments (PDFs on macOS 27);
|
|
55336
|
+
inserted.length === 0 && !beforeStored ? `${UNCERTAIN}. Notes' AppleScript does not list some attachments (PDFs on macOS 27); Full Disk Access is required to verify through the Notes database. ${fdaRemediation()}` : UNCERTAIN
|
|
55326
55337
|
);
|
|
55327
55338
|
attachmentId = stored.attachmentId;
|
|
55328
55339
|
reportedName = stored.name;
|
|
@@ -55436,7 +55447,7 @@ function classify(error2) {
|
|
|
55436
55447
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
55437
55448
|
if (/authorization denied|unable to open database|not authorized/i.test(message)) {
|
|
55438
55449
|
return new FolderStoreError(
|
|
55439
|
-
|
|
55450
|
+
`Full Disk Access is required to verify folder type and contents before deleting. ${fdaRemediation()}`,
|
|
55440
55451
|
"no_fda"
|
|
55441
55452
|
);
|
|
55442
55453
|
}
|
|
@@ -55711,8 +55722,8 @@ import {
|
|
|
55711
55722
|
unlinkSync as unlinkSync4,
|
|
55712
55723
|
writeSync as writeSync5
|
|
55713
55724
|
} from "node:fs";
|
|
55714
|
-
import { homedir as
|
|
55715
|
-
import { isAbsolute as isAbsolute5, join as
|
|
55725
|
+
import { homedir as homedir21 } from "node:os";
|
|
55726
|
+
import { isAbsolute as isAbsolute5, join as join28, resolve as resolve6 } from "node:path";
|
|
55716
55727
|
var TEMPLATE_SLUG = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
55717
55728
|
var TemplateStoreError = class extends Error {
|
|
55718
55729
|
constructor(code, message) {
|
|
@@ -55732,7 +55743,7 @@ function templateDir(env = process.env) {
|
|
|
55732
55743
|
);
|
|
55733
55744
|
return resolve6(override);
|
|
55734
55745
|
}
|
|
55735
|
-
return
|
|
55746
|
+
return join28(homedir21(), "Library/Application Support/apple-notes-mcp/templates");
|
|
55736
55747
|
}
|
|
55737
55748
|
function checkName(name, action) {
|
|
55738
55749
|
if (isBuiltinTemplate(name) && action !== "read")
|
|
@@ -55769,7 +55780,7 @@ var TemplateStore = class {
|
|
|
55769
55780
|
return this.dir;
|
|
55770
55781
|
}
|
|
55771
55782
|
file(name) {
|
|
55772
|
-
return
|
|
55783
|
+
return join28(this.dir, `${name}.json`);
|
|
55773
55784
|
}
|
|
55774
55785
|
/**
|
|
55775
55786
|
* Read a saved template's text, refusing symlinks and non-regular files.
|
|
@@ -55895,7 +55906,7 @@ var TemplateStore = class {
|
|
|
55895
55906
|
"template-exists",
|
|
55896
55907
|
`A template named "${name}" already exists. Pass force: true to replace it.`
|
|
55897
55908
|
);
|
|
55898
|
-
const temp =
|
|
55909
|
+
const temp = join28(this.dir, `.${name}.${randomBytes2(6).toString("hex")}.tmp`);
|
|
55899
55910
|
const fd = openSync8(
|
|
55900
55911
|
temp,
|
|
55901
55912
|
constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
|
|
@@ -57114,7 +57125,7 @@ function parseXml(source, limits) {
|
|
|
57114
57125
|
(own ??= /* @__PURE__ */ new Map()).set(prefix, a.value);
|
|
57115
57126
|
}
|
|
57116
57127
|
const scope2 = own ? { own, parent: parentScope } : parentScope ?? { own: null, parent: null };
|
|
57117
|
-
const
|
|
57128
|
+
const resolve11 = (qualified, isAttribute) => {
|
|
57118
57129
|
const colon = qualified.indexOf(":");
|
|
57119
57130
|
if (colon < 0)
|
|
57120
57131
|
return {
|
|
@@ -57128,12 +57139,12 @@ function parseXml(source, limits) {
|
|
|
57128
57139
|
if (ns === void 0) fail(`Undeclared namespace prefix "${prefix.slice(0, 40)}"`);
|
|
57129
57140
|
return { local, ns };
|
|
57130
57141
|
};
|
|
57131
|
-
const resolved =
|
|
57142
|
+
const resolved = resolve11(name, false);
|
|
57132
57143
|
const element = {
|
|
57133
57144
|
name,
|
|
57134
57145
|
local: resolved.local,
|
|
57135
57146
|
ns: resolved.ns,
|
|
57136
|
-
attributes: rawAttributes.filter((a) => a.name !== "xmlns" && !a.name.startsWith("xmlns:")).map((a) => ({ name: a.name, value: a.value, ...
|
|
57147
|
+
attributes: rawAttributes.filter((a) => a.name !== "xmlns" && !a.name.startsWith("xmlns:")).map((a) => ({ name: a.name, value: a.value, ...resolve11(a.name, true) })),
|
|
57137
57148
|
children: [],
|
|
57138
57149
|
text: "",
|
|
57139
57150
|
line: tagLine
|
|
@@ -58306,6 +58317,897 @@ function registerNativeTagsBridge(server2, manager) {
|
|
|
58306
58317
|
);
|
|
58307
58318
|
}
|
|
58308
58319
|
|
|
58320
|
+
// src/services/broker.ts
|
|
58321
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
58322
|
+
import {
|
|
58323
|
+
chmodSync as chmodSync2,
|
|
58324
|
+
existsSync as existsSync14,
|
|
58325
|
+
lstatSync as lstatSync6,
|
|
58326
|
+
mkdirSync as mkdirSync9,
|
|
58327
|
+
mkdtempSync as mkdtempSync8,
|
|
58328
|
+
readFileSync as readFileSync5,
|
|
58329
|
+
realpathSync as realpathSync4,
|
|
58330
|
+
renameSync as renameSync3,
|
|
58331
|
+
rmdirSync,
|
|
58332
|
+
rmSync as rmSync8,
|
|
58333
|
+
writeFileSync as writeFileSync7
|
|
58334
|
+
} from "node:fs";
|
|
58335
|
+
import { homedir as homedir22 } from "node:os";
|
|
58336
|
+
import { isAbsolute as isAbsolute6, join as join29, resolve as resolve7 } from "node:path";
|
|
58337
|
+
|
|
58338
|
+
// src/services/brokerClient.ts
|
|
58339
|
+
import { connect as netConnect } from "node:net";
|
|
58340
|
+
var BrokerUnreachableError = class extends Error {
|
|
58341
|
+
constructor(message, code) {
|
|
58342
|
+
super(message);
|
|
58343
|
+
this.code = code;
|
|
58344
|
+
this.name = "BrokerUnreachableError";
|
|
58345
|
+
}
|
|
58346
|
+
code;
|
|
58347
|
+
};
|
|
58348
|
+
var MAX_ANSWER_BYTES = 65536;
|
|
58349
|
+
function requestBroker(socketPath, request, timeoutMs, connect = (path10) => netConnect({ path: path10, allowHalfOpen: true })) {
|
|
58350
|
+
return new Promise((resolve11, reject) => {
|
|
58351
|
+
const socket = connect(socketPath);
|
|
58352
|
+
const buffered = Buffer.allocUnsafe(MAX_ANSWER_BYTES);
|
|
58353
|
+
let bufferedBytes = 0;
|
|
58354
|
+
let settled = false;
|
|
58355
|
+
const fail = (message, code) => {
|
|
58356
|
+
if (settled) return;
|
|
58357
|
+
settled = true;
|
|
58358
|
+
clearTimeout(timer);
|
|
58359
|
+
socket.destroy();
|
|
58360
|
+
reject(new BrokerUnreachableError(message, code));
|
|
58361
|
+
};
|
|
58362
|
+
const timer = setTimeout(
|
|
58363
|
+
() => fail(`The broker did not answer within ${timeoutMs} ms.`, "timeout"),
|
|
58364
|
+
timeoutMs
|
|
58365
|
+
);
|
|
58366
|
+
socket.once("connect", () => {
|
|
58367
|
+
socket.write(JSON.stringify(request) + "\n");
|
|
58368
|
+
});
|
|
58369
|
+
const onError = (error2) => fail(`Could not reach the broker at ${socketPath}: ${error2.message}`, error2.code ?? "error");
|
|
58370
|
+
const onClose = () => fail("The broker closed the connection without answering.", "closed");
|
|
58371
|
+
socket.on("error", onError);
|
|
58372
|
+
socket.on("close", onClose);
|
|
58373
|
+
socket.on("end", onClose);
|
|
58374
|
+
const onData = (chunk) => {
|
|
58375
|
+
if (settled) return;
|
|
58376
|
+
const newline = chunk.indexOf(10);
|
|
58377
|
+
const answerBytes = newline < 0 ? chunk.length : newline;
|
|
58378
|
+
if (bufferedBytes + answerBytes > MAX_ANSWER_BYTES) {
|
|
58379
|
+
fail("The broker's answer was too long.", "bad_answer");
|
|
58380
|
+
return;
|
|
58381
|
+
}
|
|
58382
|
+
chunk.copy(buffered, bufferedBytes, 0, answerBytes);
|
|
58383
|
+
bufferedBytes += answerBytes;
|
|
58384
|
+
if (newline < 0) return;
|
|
58385
|
+
let answer;
|
|
58386
|
+
try {
|
|
58387
|
+
answer = JSON.parse(buffered.subarray(0, bufferedBytes).toString("utf8"));
|
|
58388
|
+
} catch {
|
|
58389
|
+
fail("The broker's answer was not JSON.", "bad_answer");
|
|
58390
|
+
return;
|
|
58391
|
+
}
|
|
58392
|
+
if (!answer || typeof answer !== "object" || Array.isArray(answer)) {
|
|
58393
|
+
fail("The broker's answer was not a JSON object.", "bad_answer");
|
|
58394
|
+
return;
|
|
58395
|
+
}
|
|
58396
|
+
settled = true;
|
|
58397
|
+
clearTimeout(timer);
|
|
58398
|
+
socket.off("data", onData);
|
|
58399
|
+
socket.off("close", onClose);
|
|
58400
|
+
socket.off("end", onClose);
|
|
58401
|
+
socket.off("error", onError);
|
|
58402
|
+
socket.pause();
|
|
58403
|
+
resolve11({
|
|
58404
|
+
answer,
|
|
58405
|
+
socket,
|
|
58406
|
+
leftover: chunk.subarray(newline + 1)
|
|
58407
|
+
});
|
|
58408
|
+
};
|
|
58409
|
+
socket.on("data", onData);
|
|
58410
|
+
});
|
|
58411
|
+
}
|
|
58412
|
+
async function pingBroker(socketPath, timeoutMs = 1500) {
|
|
58413
|
+
try {
|
|
58414
|
+
const { answer, socket } = await requestBroker(socketPath, { type: "ping" }, timeoutMs);
|
|
58415
|
+
socket.destroy();
|
|
58416
|
+
return answer.type === "pong";
|
|
58417
|
+
} catch {
|
|
58418
|
+
return false;
|
|
58419
|
+
}
|
|
58420
|
+
}
|
|
58421
|
+
|
|
58422
|
+
// src/services/broker.ts
|
|
58423
|
+
var BROKER_PROTOCOL = 3;
|
|
58424
|
+
var BROKER_BUNDLE_ID = "apple-notes-mcp.broker";
|
|
58425
|
+
var BROKER_LABEL = BROKER_BUNDLE_ID;
|
|
58426
|
+
var BROKER_APP_NAME = "Apple Notes MCP Broker.app";
|
|
58427
|
+
var BROKER_EXECUTABLE = "apple-notes-mcp-broker";
|
|
58428
|
+
var BROKER_SOURCE = "native/broker/apple-notes-mcp-broker.swift";
|
|
58429
|
+
var BROKER_MANIFEST = "manifest.json";
|
|
58430
|
+
var BROKER_SOCKET = "broker.sock";
|
|
58431
|
+
var BROKER_SETUP_COMMAND = "apple-notes-mcp setup --broker";
|
|
58432
|
+
var BROKER_LOG_COMMAND = `log show --last 10m --predicate 'subsystem == "${BROKER_BUNDLE_ID}"'`;
|
|
58433
|
+
var BROKERED_ENV = "APPLE_NOTES_MCP_BROKERED";
|
|
58434
|
+
var BROKER_APP_ENV = "APPLE_NOTES_MCP_BROKER_APP";
|
|
58435
|
+
var BROKER_MODE_ENV = "APPLE_NOTES_MCP_BROKER";
|
|
58436
|
+
var BROKER_DIR_ENV = "APPLE_NOTES_MCP_BROKER_DIR";
|
|
58437
|
+
var BROKER_APP_DIR_ENV = "APPLE_NOTES_MCP_BROKER_APP_DIR";
|
|
58438
|
+
var BROKER_AGENT_DIR_ENV = "APPLE_NOTES_MCP_BROKER_AGENT_DIR";
|
|
58439
|
+
var BROKER_SIGN_IDENTITY_ENV = "APPLE_NOTES_MCP_BROKER_SIGN_IDENTITY";
|
|
58440
|
+
var MAX_SOCKET_PATH_BYTES = 103;
|
|
58441
|
+
var brokerManifestSchema = external_exports.object({
|
|
58442
|
+
schemaVersion: external_exports.literal(2),
|
|
58443
|
+
protocolVersion: external_exports.number().int(),
|
|
58444
|
+
packageVersion: external_exports.string(),
|
|
58445
|
+
sourceSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
58446
|
+
binarySha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
58447
|
+
nodeSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
58448
|
+
entrySha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
58449
|
+
appPath: external_exports.string(),
|
|
58450
|
+
agentPath: external_exports.string(),
|
|
58451
|
+
socketPath: external_exports.string(),
|
|
58452
|
+
logPath: external_exports.string(),
|
|
58453
|
+
nodePath: external_exports.string(),
|
|
58454
|
+
entryPath: external_exports.string(),
|
|
58455
|
+
signing: external_exports.object({
|
|
58456
|
+
identity: external_exports.string(),
|
|
58457
|
+
teamId: external_exports.string().nullable(),
|
|
58458
|
+
stable: external_exports.boolean()
|
|
58459
|
+
}),
|
|
58460
|
+
builtAt: external_exports.string(),
|
|
58461
|
+
compiler: external_exports.string()
|
|
58462
|
+
});
|
|
58463
|
+
var brokerConfigSchema = external_exports.object({
|
|
58464
|
+
schemaVersion: external_exports.literal(1),
|
|
58465
|
+
nodePath: external_exports.string(),
|
|
58466
|
+
nodeSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
58467
|
+
packageVersion: external_exports.string(),
|
|
58468
|
+
entrySha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
|
|
58469
|
+
}).strict();
|
|
58470
|
+
function brokerResources(appPath) {
|
|
58471
|
+
const resources = join29(appPath, "Contents", "Resources");
|
|
58472
|
+
return {
|
|
58473
|
+
resources,
|
|
58474
|
+
configPath: join29(resources, "broker-config.json"),
|
|
58475
|
+
entryPath: join29(resources, "server", "build", "index.js"),
|
|
58476
|
+
packagePath: join29(resources, "server", "package.json"),
|
|
58477
|
+
sourcePath: join29(resources, "server", BROKER_SOURCE),
|
|
58478
|
+
serverConfigPath: join29(resources, "config.json"),
|
|
58479
|
+
disabledHelpers: join29(resources, "disabled-helpers")
|
|
58480
|
+
};
|
|
58481
|
+
}
|
|
58482
|
+
function brokerPaths(env = process.env) {
|
|
58483
|
+
const home = homedir22();
|
|
58484
|
+
const stateDir = resolve7(
|
|
58485
|
+
env[BROKER_DIR_ENV]?.trim() || join29(home, "Library", "Application Support", "apple-notes-mcp", "broker")
|
|
58486
|
+
);
|
|
58487
|
+
const appDir = env[BROKER_APP_DIR_ENV]?.trim() || join29(home, "Applications");
|
|
58488
|
+
const agentDir = env[BROKER_AGENT_DIR_ENV]?.trim() || join29(home, "Library", "LaunchAgents");
|
|
58489
|
+
const appPath = join29(appDir, BROKER_APP_NAME);
|
|
58490
|
+
return {
|
|
58491
|
+
stateDir,
|
|
58492
|
+
manifestPath: join29(stateDir, BROKER_MANIFEST),
|
|
58493
|
+
socketPath: join29(stateDir, BROKER_SOCKET),
|
|
58494
|
+
appDir,
|
|
58495
|
+
appPath,
|
|
58496
|
+
executablePath: join29(appPath, "Contents", "MacOS", BROKER_EXECUTABLE),
|
|
58497
|
+
agentPath: join29(agentDir, `${BROKER_LABEL}.plist`),
|
|
58498
|
+
logPath: join29(home, "Library", "Logs", "apple-notes-mcp-broker.log")
|
|
58499
|
+
};
|
|
58500
|
+
}
|
|
58501
|
+
function defaultBrokerDeps(overrides = {}) {
|
|
58502
|
+
const root = packageRoot();
|
|
58503
|
+
let packageVersion = "0.0.0";
|
|
58504
|
+
try {
|
|
58505
|
+
packageVersion = JSON.parse(readFileSync5(join29(root, "package.json"), "utf8")).version ?? packageVersion;
|
|
58506
|
+
} catch {
|
|
58507
|
+
}
|
|
58508
|
+
return {
|
|
58509
|
+
env: process.env,
|
|
58510
|
+
platform: process.platform,
|
|
58511
|
+
sourcePath: join29(root, BROKER_SOURCE),
|
|
58512
|
+
entryPath: join29(root, "build", "index.js"),
|
|
58513
|
+
execPath: process.execPath,
|
|
58514
|
+
packageVersion,
|
|
58515
|
+
uid: process.getuid?.() ?? -1,
|
|
58516
|
+
exists: existsSync14,
|
|
58517
|
+
readFile: (path10) => readFileSync5(path10),
|
|
58518
|
+
realpath: (path10) => realpathSync4(path10),
|
|
58519
|
+
removePath: rmSync8,
|
|
58520
|
+
removeEmptyDirectory: (path10) => rmdirSync(path10),
|
|
58521
|
+
spawn: spawnSync3,
|
|
58522
|
+
ping: (socketPath) => pingBroker(socketPath),
|
|
58523
|
+
sleep: (ms) => new Promise((resolve11) => setTimeout(resolve11, ms)),
|
|
58524
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
58525
|
+
...overrides
|
|
58526
|
+
};
|
|
58527
|
+
}
|
|
58528
|
+
function inspectBroker(deps = defaultBrokerDeps()) {
|
|
58529
|
+
const paths = brokerPaths(deps.env);
|
|
58530
|
+
const result = (reason, detail, manifest2) => ({
|
|
58531
|
+
installed: manifest2 !== null,
|
|
58532
|
+
ready: reason === null,
|
|
58533
|
+
reason,
|
|
58534
|
+
detail,
|
|
58535
|
+
paths,
|
|
58536
|
+
manifest: manifest2
|
|
58537
|
+
});
|
|
58538
|
+
if (deps.platform !== "darwin") return result("unsupported_platform", "macOS only", null);
|
|
58539
|
+
const rebuild = `Run \`${BROKER_SETUP_COMMAND}\` again.`;
|
|
58540
|
+
if (!deps.exists(paths.manifestPath))
|
|
58541
|
+
return result("broker_not_installed", "The permission broker is not installed.", null);
|
|
58542
|
+
let manifest;
|
|
58543
|
+
try {
|
|
58544
|
+
manifest = brokerManifestSchema.parse(
|
|
58545
|
+
JSON.parse(deps.readFile(paths.manifestPath).toString("utf8"))
|
|
58546
|
+
);
|
|
58547
|
+
} catch {
|
|
58548
|
+
return result("broker_manifest_invalid", `The broker manifest is unreadable. ${rebuild}`, null);
|
|
58549
|
+
}
|
|
58550
|
+
const resources = brokerResources(paths.appPath);
|
|
58551
|
+
if (manifest.appPath !== paths.appPath || manifest.agentPath !== paths.agentPath || manifest.socketPath !== paths.socketPath || manifest.logPath !== paths.logPath || manifest.entryPath !== resources.entryPath)
|
|
58552
|
+
return result(
|
|
58553
|
+
"broker_modified",
|
|
58554
|
+
`The broker manifest contains unexpected paths. ${rebuild}`,
|
|
58555
|
+
manifest
|
|
58556
|
+
);
|
|
58557
|
+
if (!deps.exists(paths.appPath) || !deps.exists(paths.executablePath))
|
|
58558
|
+
return result(
|
|
58559
|
+
"broker_not_installed",
|
|
58560
|
+
`The broker app is missing from ${paths.appPath}. ${rebuild}`,
|
|
58561
|
+
manifest
|
|
58562
|
+
);
|
|
58563
|
+
try {
|
|
58564
|
+
if (!deps.exists(deps.sourcePath) || !deps.exists(deps.entryPath) || manifest.sourceSha256 !== sha256Hex(deps.readFile(deps.sourcePath)) || manifest.entrySha256 !== sha256Hex(deps.readFile(deps.entryPath)) || manifest.packageVersion !== deps.packageVersion || manifest.protocolVersion !== BROKER_PROTOCOL)
|
|
58565
|
+
return result(
|
|
58566
|
+
"broker_stale",
|
|
58567
|
+
`The installed broker serves a different package or build. ${rebuild}`,
|
|
58568
|
+
manifest
|
|
58569
|
+
);
|
|
58570
|
+
if (!deps.exists(manifest.agentPath))
|
|
58571
|
+
return result(
|
|
58572
|
+
"broker_agent_missing",
|
|
58573
|
+
`The broker's LaunchAgent is missing (${manifest.agentPath}). ${rebuild}`,
|
|
58574
|
+
manifest
|
|
58575
|
+
);
|
|
58576
|
+
if (!deps.exists(manifest.nodePath))
|
|
58577
|
+
return result(
|
|
58578
|
+
"broker_node_missing",
|
|
58579
|
+
`The Node binary the broker launches is gone (${manifest.nodePath}). ${rebuild} Your grants stay with the broker app.`,
|
|
58580
|
+
manifest
|
|
58581
|
+
);
|
|
58582
|
+
if (!deps.exists(resources.entryPath))
|
|
58583
|
+
return result(
|
|
58584
|
+
"broker_entry_missing",
|
|
58585
|
+
`The bundled server entry point is gone (${resources.entryPath}). ${rebuild}`,
|
|
58586
|
+
manifest
|
|
58587
|
+
);
|
|
58588
|
+
const config2 = brokerConfigSchema.parse(
|
|
58589
|
+
JSON.parse(deps.readFile(resources.configPath).toString("utf8"))
|
|
58590
|
+
);
|
|
58591
|
+
if (sha256Hex(deps.readFile(paths.executablePath)) !== manifest.binarySha256 || sha256Hex(deps.readFile(resources.entryPath)) !== manifest.entrySha256 || sha256Hex(deps.readFile(resources.sourcePath)) !== manifest.sourceSha256 || deps.readFile(resources.serverConfigPath).toString("utf8") !== "{}\n" || !isAbsolute6(manifest.nodePath) || deps.realpath(manifest.nodePath) !== manifest.nodePath || sha256Hex(deps.readFile(manifest.nodePath)) !== manifest.nodeSha256 || config2.nodePath !== manifest.nodePath || config2.nodeSha256 !== manifest.nodeSha256 || config2.packageVersion !== manifest.packageVersion || config2.entrySha256 !== manifest.entrySha256)
|
|
58592
|
+
return result(
|
|
58593
|
+
"broker_modified",
|
|
58594
|
+
`The broker bundle, sealed configuration, or Node runtime no longer matches its installation. ${rebuild}`,
|
|
58595
|
+
manifest
|
|
58596
|
+
);
|
|
58597
|
+
const bundledPackage = JSON.parse(
|
|
58598
|
+
deps.readFile(resources.packagePath).toString("utf8")
|
|
58599
|
+
);
|
|
58600
|
+
if (bundledPackage.name !== "apple-notes-mcp" || bundledPackage.type !== "module" || bundledPackage.version !== manifest.packageVersion)
|
|
58601
|
+
return result(
|
|
58602
|
+
"broker_modified",
|
|
58603
|
+
`The bundled package metadata changed. ${rebuild}`,
|
|
58604
|
+
manifest
|
|
58605
|
+
);
|
|
58606
|
+
const verified = deps.spawn(
|
|
58607
|
+
"/usr/bin/codesign",
|
|
58608
|
+
["--verify", "--strict", "--deep", paths.appPath],
|
|
58609
|
+
{
|
|
58610
|
+
encoding: "utf8",
|
|
58611
|
+
timeout: 3e4
|
|
58612
|
+
}
|
|
58613
|
+
);
|
|
58614
|
+
if (verified.status !== 0)
|
|
58615
|
+
return result("broker_modified", `The broker app signature is invalid. ${rebuild}`, manifest);
|
|
58616
|
+
} catch {
|
|
58617
|
+
return result(
|
|
58618
|
+
"broker_modified",
|
|
58619
|
+
`The broker installation could not be verified. ${rebuild}`,
|
|
58620
|
+
manifest
|
|
58621
|
+
);
|
|
58622
|
+
}
|
|
58623
|
+
return result(null, null, manifest);
|
|
58624
|
+
}
|
|
58625
|
+
function brokerInfoPlist(packageVersion) {
|
|
58626
|
+
const escape3 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
58627
|
+
return [
|
|
58628
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
58629
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
58630
|
+
'<plist version="1.0">',
|
|
58631
|
+
"<dict>",
|
|
58632
|
+
" <key>CFBundleIdentifier</key>",
|
|
58633
|
+
` <string>${BROKER_BUNDLE_ID}</string>`,
|
|
58634
|
+
" <key>CFBundleName</key>",
|
|
58635
|
+
" <string>Apple Notes MCP Broker</string>",
|
|
58636
|
+
" <key>CFBundleDisplayName</key>",
|
|
58637
|
+
" <string>Apple Notes MCP Broker</string>",
|
|
58638
|
+
" <key>CFBundleExecutable</key>",
|
|
58639
|
+
` <string>${BROKER_EXECUTABLE}</string>`,
|
|
58640
|
+
" <key>CFBundlePackageType</key>",
|
|
58641
|
+
" <string>APPL</string>",
|
|
58642
|
+
" <key>CFBundleInfoDictionaryVersion</key>",
|
|
58643
|
+
" <string>6.0</string>",
|
|
58644
|
+
" <key>CFBundleShortVersionString</key>",
|
|
58645
|
+
` <string>${escape3(packageVersion)}</string>`,
|
|
58646
|
+
" <key>CFBundleVersion</key>",
|
|
58647
|
+
` <string>${escape3(packageVersion)}</string>`,
|
|
58648
|
+
" <key>LSUIElement</key>",
|
|
58649
|
+
" <true/>",
|
|
58650
|
+
" <key>NSAppleEventsUsageDescription</key>",
|
|
58651
|
+
" <string>apple-notes-mcp reads and organizes your notes through Notes when an MCP client asks it to.</string>",
|
|
58652
|
+
"</dict>",
|
|
58653
|
+
"</plist>",
|
|
58654
|
+
""
|
|
58655
|
+
].join("\n");
|
|
58656
|
+
}
|
|
58657
|
+
function brokerCompileArguments(sourcePath, digestPath, outputPath) {
|
|
58658
|
+
return ["swiftc", "-O", "-parse-as-library", sourcePath, digestPath, "-o", outputPath];
|
|
58659
|
+
}
|
|
58660
|
+
function brokerLaunchAgentPlist(args) {
|
|
58661
|
+
const escape3 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
58662
|
+
const programArguments = [args.executablePath, "serve", "--socket", args.socketPath];
|
|
58663
|
+
return [
|
|
58664
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
58665
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
58666
|
+
'<plist version="1.0">',
|
|
58667
|
+
"<dict>",
|
|
58668
|
+
" <key>Label</key>",
|
|
58669
|
+
` <string>${BROKER_LABEL}</string>`,
|
|
58670
|
+
" <key>AssociatedBundleIdentifiers</key>",
|
|
58671
|
+
" <array>",
|
|
58672
|
+
` <string>${BROKER_BUNDLE_ID}</string>`,
|
|
58673
|
+
" </array>",
|
|
58674
|
+
" <key>ProgramArguments</key>",
|
|
58675
|
+
" <array>",
|
|
58676
|
+
...programArguments.map((arg) => ` <string>${escape3(arg)}</string>`),
|
|
58677
|
+
" </array>",
|
|
58678
|
+
" <key>RunAtLoad</key>",
|
|
58679
|
+
" <true/>",
|
|
58680
|
+
" <key>KeepAlive</key>",
|
|
58681
|
+
" <true/>",
|
|
58682
|
+
" <key>StandardErrorPath</key>",
|
|
58683
|
+
" <string>/dev/null</string>",
|
|
58684
|
+
"</dict>",
|
|
58685
|
+
"</plist>",
|
|
58686
|
+
""
|
|
58687
|
+
].join("\n");
|
|
58688
|
+
}
|
|
58689
|
+
function chooseSigningIdentity(securityOutput, explicit) {
|
|
58690
|
+
const identities = [...securityOutput.matchAll(/^\s*\d+\)\s+([0-9A-F]{40})\s+"([^"]+)"/gm)].map(
|
|
58691
|
+
(m) => ({ identity: m[1], name: m[2] })
|
|
58692
|
+
);
|
|
58693
|
+
if (explicit && explicit.trim()) {
|
|
58694
|
+
const value = explicit.trim();
|
|
58695
|
+
const found = identities.find(
|
|
58696
|
+
(identity) => identity.identity === value || identity.name === value
|
|
58697
|
+
);
|
|
58698
|
+
return found ?? { identity: value, name: value === "-" ? "ad-hoc" : value };
|
|
58699
|
+
}
|
|
58700
|
+
const qualifying = identities.filter(
|
|
58701
|
+
(identity) => /^(Developer ID Application:|Apple Development:)/.test(identity.name)
|
|
58702
|
+
);
|
|
58703
|
+
if (qualifying.length > 1)
|
|
58704
|
+
throw new Error(
|
|
58705
|
+
"Multiple signing identities qualify. Choose one explicitly with --sign-identity <certificate SHA-1 or name>."
|
|
58706
|
+
);
|
|
58707
|
+
return qualifying[0] ?? { identity: "-", name: "ad-hoc" };
|
|
58708
|
+
}
|
|
58709
|
+
function parseTeamId(codesignOutput) {
|
|
58710
|
+
if (/Signature=adhoc/.test(codesignOutput)) return null;
|
|
58711
|
+
const match = codesignOutput.match(/^TeamIdentifier=(.+)$/m);
|
|
58712
|
+
const team = match?.[1]?.trim();
|
|
58713
|
+
return team && team !== "not set" ? team : null;
|
|
58714
|
+
}
|
|
58715
|
+
function chooseNodePath(deps) {
|
|
58716
|
+
const nodePath = deps.realpath(deps.execPath);
|
|
58717
|
+
if (!isAbsolute6(nodePath))
|
|
58718
|
+
throw new Error("The Node executable must resolve to an absolute path.");
|
|
58719
|
+
return nodePath;
|
|
58720
|
+
}
|
|
58721
|
+
function validateNodeLibraries(output) {
|
|
58722
|
+
const lines = output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
58723
|
+
let libraries = 0;
|
|
58724
|
+
for (const line of lines) {
|
|
58725
|
+
if (line.endsWith(":")) continue;
|
|
58726
|
+
const match = line.match(/^(.+?) \(compatibility version [^)]+\)$/);
|
|
58727
|
+
if (!match || !/^\/(?:usr\/lib|System\/Library)\//.test(match[1])) return false;
|
|
58728
|
+
if (match[1].split("/").includes("..")) return false;
|
|
58729
|
+
libraries++;
|
|
58730
|
+
}
|
|
58731
|
+
return libraries > 0;
|
|
58732
|
+
}
|
|
58733
|
+
function parseBrokerArgs(args) {
|
|
58734
|
+
const index = args.indexOf("--sign-identity");
|
|
58735
|
+
return {
|
|
58736
|
+
checkOnly: args.includes("--check"),
|
|
58737
|
+
uninstall: args.includes("--uninstall"),
|
|
58738
|
+
signIdentity: index >= 0 ? args[index + 1] : void 0
|
|
58739
|
+
};
|
|
58740
|
+
}
|
|
58741
|
+
function launchctl(deps, args) {
|
|
58742
|
+
return deps.spawn("/bin/launchctl", args, { encoding: "utf8", timeout: 3e4 });
|
|
58743
|
+
}
|
|
58744
|
+
async function stopBroker(deps, domain) {
|
|
58745
|
+
const target = `${domain}/${BROKER_LABEL}`;
|
|
58746
|
+
const step = "stop LaunchAgent";
|
|
58747
|
+
try {
|
|
58748
|
+
const stopped = deps.spawn("/bin/launchctl", ["bootout", target], {
|
|
58749
|
+
encoding: "utf8",
|
|
58750
|
+
timeout: 1e4,
|
|
58751
|
+
killSignal: "SIGKILL"
|
|
58752
|
+
});
|
|
58753
|
+
const stopDetail = stopped.status === 0 ? "" : `bootout: ${String(stopped.stderr || stopped.stdout || stopped.error?.message || `exit ${stopped.status}`).trim()}. `;
|
|
58754
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
58755
|
+
const probe = deps.spawn("/bin/launchctl", ["print", target], {
|
|
58756
|
+
encoding: "utf8",
|
|
58757
|
+
timeout: 1e3,
|
|
58758
|
+
killSignal: "SIGKILL"
|
|
58759
|
+
});
|
|
58760
|
+
const output = String(probe.stderr || probe.stdout || "");
|
|
58761
|
+
const absent = !probe.error && !probe.signal && probe.status === 113 && output.split("\n").some(
|
|
58762
|
+
(line) => line.trim().startsWith(`Could not find service "${BROKER_LABEL}" in domain`)
|
|
58763
|
+
);
|
|
58764
|
+
if (absent)
|
|
58765
|
+
return { step, ok: true, detail: `${stopDetail}Confirmed ${target} is not loaded.` };
|
|
58766
|
+
if (probe.status !== 0 || probe.error || probe.signal)
|
|
58767
|
+
return {
|
|
58768
|
+
step,
|
|
58769
|
+
ok: false,
|
|
58770
|
+
detail: `${stopDetail}Cannot confirm service removal: ${String(output || probe.error?.message || `exit ${probe.status}`).trim()}. Broker files were preserved.`
|
|
58771
|
+
};
|
|
58772
|
+
if (attempt < 19) await deps.sleep(250);
|
|
58773
|
+
}
|
|
58774
|
+
return {
|
|
58775
|
+
step,
|
|
58776
|
+
ok: false,
|
|
58777
|
+
detail: `${stopDetail}${target} is still loaded after 20 checks. Broker files were preserved.`
|
|
58778
|
+
};
|
|
58779
|
+
} catch (error2) {
|
|
58780
|
+
return {
|
|
58781
|
+
step,
|
|
58782
|
+
ok: false,
|
|
58783
|
+
detail: `Cannot confirm service removal: ${error2 instanceof Error ? error2.message : String(error2)}. Broker files were preserved.`
|
|
58784
|
+
};
|
|
58785
|
+
}
|
|
58786
|
+
}
|
|
58787
|
+
function removeBroker(deps, paths, warnings) {
|
|
58788
|
+
const failures = [];
|
|
58789
|
+
let stateIsSymlink = false;
|
|
58790
|
+
try {
|
|
58791
|
+
stateIsSymlink = lstatSync6(paths.stateDir).isSymbolicLink();
|
|
58792
|
+
} catch (error2) {
|
|
58793
|
+
if (error2.code !== "ENOENT")
|
|
58794
|
+
return {
|
|
58795
|
+
step: "remove broker",
|
|
58796
|
+
ok: false,
|
|
58797
|
+
detail: `${paths.stateDir}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
58798
|
+
};
|
|
58799
|
+
}
|
|
58800
|
+
const remove = (path10) => {
|
|
58801
|
+
try {
|
|
58802
|
+
deps.removePath(path10, { force: true, recursive: path10 === paths.appPath });
|
|
58803
|
+
if (deps.exists(path10)) throw new Error("the path still exists after removal");
|
|
58804
|
+
} catch (error2) {
|
|
58805
|
+
failures.push(`${path10}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
58806
|
+
}
|
|
58807
|
+
};
|
|
58808
|
+
for (const path10 of [
|
|
58809
|
+
paths.agentPath,
|
|
58810
|
+
paths.appPath,
|
|
58811
|
+
...stateIsSymlink ? [] : [paths.socketPath],
|
|
58812
|
+
paths.logPath
|
|
58813
|
+
]) {
|
|
58814
|
+
remove(path10);
|
|
58815
|
+
}
|
|
58816
|
+
if (!failures.length) remove(stateIsSymlink ? paths.stateDir : paths.manifestPath);
|
|
58817
|
+
if (!failures.length && stateIsSymlink)
|
|
58818
|
+
warnings.push(
|
|
58819
|
+
`Removed the state-directory symlink ${paths.stateDir}; files in its target were left untouched.`
|
|
58820
|
+
);
|
|
58821
|
+
if (!failures.length && !stateIsSymlink) {
|
|
58822
|
+
try {
|
|
58823
|
+
deps.removeEmptyDirectory(paths.stateDir);
|
|
58824
|
+
if (deps.exists(paths.stateDir)) throw new Error("the directory still exists after removal");
|
|
58825
|
+
} catch (error2) {
|
|
58826
|
+
const code = error2.code;
|
|
58827
|
+
if (code === "ENOTEMPTY" || code === "EEXIST")
|
|
58828
|
+
warnings.push(
|
|
58829
|
+
`Kept ${paths.stateDir} because it contains other files. Only known broker artifacts were removed.`
|
|
58830
|
+
);
|
|
58831
|
+
else if (code !== "ENOENT")
|
|
58832
|
+
failures.push(
|
|
58833
|
+
`${paths.stateDir}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
58834
|
+
);
|
|
58835
|
+
}
|
|
58836
|
+
}
|
|
58837
|
+
return {
|
|
58838
|
+
step: "remove broker",
|
|
58839
|
+
ok: failures.length === 0,
|
|
58840
|
+
detail: failures.length ? failures.join("\n") : paths.appPath
|
|
58841
|
+
};
|
|
58842
|
+
}
|
|
58843
|
+
async function waitForBroker(deps, socketPath) {
|
|
58844
|
+
for (let attempt = 0; attempt < 20; attempt++) {
|
|
58845
|
+
if (await deps.ping(socketPath)) return true;
|
|
58846
|
+
await deps.sleep(250);
|
|
58847
|
+
}
|
|
58848
|
+
return false;
|
|
58849
|
+
}
|
|
58850
|
+
async function setupBroker(options, deps = defaultBrokerDeps()) {
|
|
58851
|
+
const steps = [];
|
|
58852
|
+
const warnings = [];
|
|
58853
|
+
const paths = brokerPaths(deps.env);
|
|
58854
|
+
const mode = options.uninstall ? "uninstall" : options.checkOnly ? "check" : "install";
|
|
58855
|
+
const finish = async () => {
|
|
58856
|
+
const installation = inspectBroker(deps);
|
|
58857
|
+
const stopFailed = steps.some((step) => step.step === "stop LaunchAgent" && !step.ok);
|
|
58858
|
+
const running = installation.installed || stopFailed ? await deps.ping(paths.socketPath) : false;
|
|
58859
|
+
const ok2 = steps.every((s) => s.ok) && (mode === "uninstall" ? !installation.installed : installation.ready && running);
|
|
58860
|
+
return { ok: ok2, mode, steps, installation, running, warnings };
|
|
58861
|
+
};
|
|
58862
|
+
const domain = `gui/${deps.uid}`;
|
|
58863
|
+
if (deps.platform !== "darwin") {
|
|
58864
|
+
steps.push({ step: "platform", ok: false, detail: "macOS only" });
|
|
58865
|
+
return finish();
|
|
58866
|
+
}
|
|
58867
|
+
if (mode === "check") {
|
|
58868
|
+
const installation = inspectBroker(deps);
|
|
58869
|
+
steps.push({
|
|
58870
|
+
step: "inspect installed broker",
|
|
58871
|
+
ok: installation.ready,
|
|
58872
|
+
detail: installation.ready ? installation.paths.appPath : installation.detail ?? void 0
|
|
58873
|
+
});
|
|
58874
|
+
if (installation.manifest && !installation.manifest.signing.stable)
|
|
58875
|
+
warnings.push(signingWarning(installation.manifest.signing.teamId));
|
|
58876
|
+
return finish();
|
|
58877
|
+
}
|
|
58878
|
+
if (mode === "uninstall") {
|
|
58879
|
+
const stopped = await stopBroker(deps, domain);
|
|
58880
|
+
steps.push(stopped);
|
|
58881
|
+
if (!stopped.ok) return finish();
|
|
58882
|
+
steps.push(removeBroker(deps, paths, warnings));
|
|
58883
|
+
warnings.push(
|
|
58884
|
+
`The Full Disk Access and Automation entries for "Apple Notes MCP Broker" stay in System Settings until you remove them, or run \`tccutil reset All ${BROKER_BUNDLE_ID}\`.`
|
|
58885
|
+
);
|
|
58886
|
+
return finish();
|
|
58887
|
+
}
|
|
58888
|
+
if (Buffer.byteLength(paths.socketPath) > MAX_SOCKET_PATH_BYTES) {
|
|
58889
|
+
steps.push({
|
|
58890
|
+
step: "socket path",
|
|
58891
|
+
ok: false,
|
|
58892
|
+
detail: `${paths.socketPath} is longer than ${MAX_SOCKET_PATH_BYTES} bytes. Set ${BROKER_DIR_ENV} to a shorter directory.`
|
|
58893
|
+
});
|
|
58894
|
+
return finish();
|
|
58895
|
+
}
|
|
58896
|
+
if (!deps.exists(deps.sourcePath)) {
|
|
58897
|
+
steps.push({ step: "locate source", ok: false, detail: deps.sourcePath });
|
|
58898
|
+
return finish();
|
|
58899
|
+
}
|
|
58900
|
+
if (!deps.exists(deps.entryPath)) {
|
|
58901
|
+
steps.push({
|
|
58902
|
+
step: "locate server entry point",
|
|
58903
|
+
ok: false,
|
|
58904
|
+
detail: `${deps.entryPath} is missing. Build the package first.`
|
|
58905
|
+
});
|
|
58906
|
+
return finish();
|
|
58907
|
+
}
|
|
58908
|
+
const sourceSha = sha256Hex(deps.readFile(deps.sourcePath));
|
|
58909
|
+
steps.push({ step: "locate source", ok: true, detail: `sha256 ${sourceSha}` });
|
|
58910
|
+
let nodePath;
|
|
58911
|
+
let nodeSha256;
|
|
58912
|
+
const entry = deps.readFile(deps.entryPath);
|
|
58913
|
+
const entrySha256 = sha256Hex(entry);
|
|
58914
|
+
try {
|
|
58915
|
+
nodePath = chooseNodePath(deps);
|
|
58916
|
+
nodeSha256 = sha256Hex(deps.readFile(nodePath));
|
|
58917
|
+
const libraries = deps.spawn("/usr/bin/otool", ["-L", nodePath], {
|
|
58918
|
+
encoding: "utf8",
|
|
58919
|
+
timeout: 3e4
|
|
58920
|
+
});
|
|
58921
|
+
if (libraries.status !== 0 || !validateNodeLibraries(String(libraries.stdout ?? "")))
|
|
58922
|
+
throw new Error(
|
|
58923
|
+
"Node must link only to absolute /usr/lib or /System/Library libraries. Install a self-contained Node runtime (for example the official Node distribution), then run setup with it."
|
|
58924
|
+
);
|
|
58925
|
+
steps.push({ step: "verify Node runtime", ok: true, detail: nodePath });
|
|
58926
|
+
} catch (error2) {
|
|
58927
|
+
steps.push({
|
|
58928
|
+
step: "verify Node runtime",
|
|
58929
|
+
ok: false,
|
|
58930
|
+
detail: error2 instanceof Error ? error2.message : String(error2)
|
|
58931
|
+
});
|
|
58932
|
+
return finish();
|
|
58933
|
+
}
|
|
58934
|
+
const version3 = deps.spawn("/usr/bin/xcrun", ["swiftc", "--version"], { encoding: "utf8" });
|
|
58935
|
+
if (version3.status !== 0) {
|
|
58936
|
+
steps.push({
|
|
58937
|
+
step: "find compiler",
|
|
58938
|
+
ok: false,
|
|
58939
|
+
detail: "No Swift compiler found. Install the Command Line Tools with `xcode-select --install`."
|
|
58940
|
+
});
|
|
58941
|
+
return finish();
|
|
58942
|
+
}
|
|
58943
|
+
const compiler = String(version3.stdout || version3.stderr || "").split("\n").find((line) => line.includes("Swift version"))?.trim() || "swiftc";
|
|
58944
|
+
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
58945
|
+
mkdirSync9(paths.stateDir, { recursive: true, mode: 448 });
|
|
58946
|
+
chmodSync2(paths.stateDir, 448);
|
|
58947
|
+
mkdirSync9(paths.appDir, { recursive: true });
|
|
58948
|
+
const staging = mkdtempSync8(join29(paths.appDir, ".apple-notes-mcp-broker-staging-"));
|
|
58949
|
+
try {
|
|
58950
|
+
const stagedApp = join29(staging, BROKER_APP_NAME);
|
|
58951
|
+
const macosDir = join29(stagedApp, "Contents", "MacOS");
|
|
58952
|
+
mkdirSync9(macosDir, { recursive: true });
|
|
58953
|
+
const stagedBinary = join29(macosDir, BROKER_EXECUTABLE);
|
|
58954
|
+
const digestPath = join29(staging, "source-digest.swift");
|
|
58955
|
+
writeFileSync7(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
|
|
58956
|
+
writeFileSync7(join29(stagedApp, "Contents", "Info.plist"), brokerInfoPlist(deps.packageVersion));
|
|
58957
|
+
const resources = brokerResources(stagedApp);
|
|
58958
|
+
mkdirSync9(join29(resources.entryPath, ".."), { recursive: true });
|
|
58959
|
+
mkdirSync9(join29(resources.sourcePath, ".."), { recursive: true });
|
|
58960
|
+
mkdirSync9(join29(resources.disabledHelpers, "public"), { recursive: true });
|
|
58961
|
+
mkdirSync9(join29(resources.disabledHelpers, "private"), { recursive: true });
|
|
58962
|
+
writeFileSync7(resources.entryPath, entry);
|
|
58963
|
+
writeFileSync7(resources.sourcePath, deps.readFile(deps.sourcePath));
|
|
58964
|
+
writeFileSync7(
|
|
58965
|
+
resources.packagePath,
|
|
58966
|
+
JSON.stringify({ name: "apple-notes-mcp", type: "module", version: deps.packageVersion }) + "\n"
|
|
58967
|
+
);
|
|
58968
|
+
writeFileSync7(resources.serverConfigPath, "{}\n");
|
|
58969
|
+
writeFileSync7(
|
|
58970
|
+
resources.configPath,
|
|
58971
|
+
JSON.stringify(
|
|
58972
|
+
{
|
|
58973
|
+
schemaVersion: 1,
|
|
58974
|
+
nodePath,
|
|
58975
|
+
nodeSha256,
|
|
58976
|
+
packageVersion: deps.packageVersion,
|
|
58977
|
+
entrySha256
|
|
58978
|
+
},
|
|
58979
|
+
null,
|
|
58980
|
+
2
|
|
58981
|
+
) + "\n"
|
|
58982
|
+
);
|
|
58983
|
+
const compile = deps.spawn(
|
|
58984
|
+
"/usr/bin/xcrun",
|
|
58985
|
+
brokerCompileArguments(deps.sourcePath, digestPath, stagedBinary),
|
|
58986
|
+
{ encoding: "utf8", timeout: 3e5 }
|
|
58987
|
+
);
|
|
58988
|
+
if (compile.status !== 0) {
|
|
58989
|
+
steps.push({
|
|
58990
|
+
step: "compile",
|
|
58991
|
+
ok: false,
|
|
58992
|
+
detail: String(compile.stderr || compile.error?.message || "swiftc failed").slice(0, 4e3)
|
|
58993
|
+
});
|
|
58994
|
+
return finish();
|
|
58995
|
+
}
|
|
58996
|
+
rmSync8(digestPath, { force: true });
|
|
58997
|
+
steps.push({ step: "compile", ok: true });
|
|
58998
|
+
const identities = deps.spawn(
|
|
58999
|
+
"/usr/bin/security",
|
|
59000
|
+
["find-identity", "-v", "-p", "codesigning"],
|
|
59001
|
+
{ encoding: "utf8" }
|
|
59002
|
+
);
|
|
59003
|
+
let signing;
|
|
59004
|
+
try {
|
|
59005
|
+
signing = chooseSigningIdentity(
|
|
59006
|
+
String(identities.stdout ?? ""),
|
|
59007
|
+
options.signIdentity ?? deps.env[BROKER_SIGN_IDENTITY_ENV]
|
|
59008
|
+
);
|
|
59009
|
+
} catch (error2) {
|
|
59010
|
+
steps.push({
|
|
59011
|
+
step: "choose signing identity",
|
|
59012
|
+
ok: false,
|
|
59013
|
+
detail: error2 instanceof Error ? error2.message : String(error2)
|
|
59014
|
+
});
|
|
59015
|
+
return finish();
|
|
59016
|
+
}
|
|
59017
|
+
const sign = deps.spawn(
|
|
59018
|
+
"/usr/bin/codesign",
|
|
59019
|
+
[
|
|
59020
|
+
"--force",
|
|
59021
|
+
"--sign",
|
|
59022
|
+
signing.identity,
|
|
59023
|
+
"--identifier",
|
|
59024
|
+
BROKER_BUNDLE_ID,
|
|
59025
|
+
"--timestamp=none",
|
|
59026
|
+
"--options",
|
|
59027
|
+
"runtime",
|
|
59028
|
+
stagedApp
|
|
59029
|
+
],
|
|
59030
|
+
{ encoding: "utf8", timeout: 6e4 }
|
|
59031
|
+
);
|
|
59032
|
+
if (sign.status !== 0) {
|
|
59033
|
+
steps.push({
|
|
59034
|
+
step: "sign",
|
|
59035
|
+
ok: false,
|
|
59036
|
+
detail: `${signing.name}: ${String(sign.stderr || "codesign failed").trim()}`
|
|
59037
|
+
});
|
|
59038
|
+
return finish();
|
|
59039
|
+
}
|
|
59040
|
+
const described = deps.spawn("/usr/bin/codesign", ["-dv", "--verbose=2", stagedApp], {
|
|
59041
|
+
encoding: "utf8"
|
|
59042
|
+
});
|
|
59043
|
+
const description = String(described.stderr ?? "") + String(described.stdout ?? "");
|
|
59044
|
+
const teamId = parseTeamId(description);
|
|
59045
|
+
const stable = teamId !== null && /^Authority=Developer ID Application:/m.test(description);
|
|
59046
|
+
steps.push({
|
|
59047
|
+
step: "sign",
|
|
59048
|
+
ok: true,
|
|
59049
|
+
detail: teamId ? `${signing.name} (team ${teamId})` : "ad-hoc"
|
|
59050
|
+
});
|
|
59051
|
+
if (!stable) warnings.push(signingWarning(teamId));
|
|
59052
|
+
const verified = deps.spawn(
|
|
59053
|
+
"/usr/bin/codesign",
|
|
59054
|
+
["--verify", "--strict", "--deep", stagedApp],
|
|
59055
|
+
{
|
|
59056
|
+
encoding: "utf8",
|
|
59057
|
+
timeout: 3e4
|
|
59058
|
+
}
|
|
59059
|
+
);
|
|
59060
|
+
if (verified.status !== 0) {
|
|
59061
|
+
steps.push({
|
|
59062
|
+
step: "verify signature",
|
|
59063
|
+
ok: false,
|
|
59064
|
+
detail: String(verified.stderr || "The signed bundle failed verification.").trim()
|
|
59065
|
+
});
|
|
59066
|
+
return finish();
|
|
59067
|
+
}
|
|
59068
|
+
steps.push({ step: "verify signature", ok: true });
|
|
59069
|
+
const hello = deps.spawn(stagedBinary, [], {
|
|
59070
|
+
input: JSON.stringify({ type: "hello" }) + "\n",
|
|
59071
|
+
encoding: "utf8",
|
|
59072
|
+
timeout: 1e4,
|
|
59073
|
+
killSignal: "SIGKILL"
|
|
59074
|
+
});
|
|
59075
|
+
let handshake = null;
|
|
59076
|
+
try {
|
|
59077
|
+
handshake = JSON.parse(String(hello.stdout ?? "").trim());
|
|
59078
|
+
} catch {
|
|
59079
|
+
handshake = null;
|
|
59080
|
+
}
|
|
59081
|
+
if (hello.status !== 0 || handshake?.protocolVersion !== BROKER_PROTOCOL || handshake?.sourceSha256 !== sourceSha || handshake?.packageVersion !== deps.packageVersion || handshake?.entrySha256 !== entrySha256) {
|
|
59082
|
+
steps.push({
|
|
59083
|
+
step: "handshake",
|
|
59084
|
+
ok: false,
|
|
59085
|
+
detail: handshake ? `broker reported protocol ${String(handshake.protocolVersion)}, source ${String(handshake.sourceSha256)}` : `no valid hello (exit ${hello.status})`
|
|
59086
|
+
});
|
|
59087
|
+
return finish();
|
|
59088
|
+
}
|
|
59089
|
+
steps.push({ step: "handshake", ok: true });
|
|
59090
|
+
const stopped = await stopBroker(deps, domain);
|
|
59091
|
+
steps.push(stopped);
|
|
59092
|
+
if (!stopped.ok) return finish();
|
|
59093
|
+
rmSync8(paths.appPath, { recursive: true, force: true });
|
|
59094
|
+
renameSync3(stagedApp, paths.appPath);
|
|
59095
|
+
const manifest = {
|
|
59096
|
+
schemaVersion: 2,
|
|
59097
|
+
protocolVersion: BROKER_PROTOCOL,
|
|
59098
|
+
packageVersion: deps.packageVersion,
|
|
59099
|
+
sourceSha256: sourceSha,
|
|
59100
|
+
binarySha256: sha256Hex(deps.readFile(paths.executablePath)),
|
|
59101
|
+
nodeSha256,
|
|
59102
|
+
entrySha256,
|
|
59103
|
+
appPath: paths.appPath,
|
|
59104
|
+
agentPath: paths.agentPath,
|
|
59105
|
+
socketPath: paths.socketPath,
|
|
59106
|
+
logPath: paths.logPath,
|
|
59107
|
+
nodePath,
|
|
59108
|
+
entryPath: brokerResources(paths.appPath).entryPath,
|
|
59109
|
+
signing: { identity: signing.name, teamId, stable },
|
|
59110
|
+
builtAt: deps.now().toISOString(),
|
|
59111
|
+
compiler
|
|
59112
|
+
};
|
|
59113
|
+
writeFileSync7(paths.manifestPath, JSON.stringify(manifest, null, 2) + "\n", { mode: 384 });
|
|
59114
|
+
steps.push({ step: "install app", ok: true, detail: paths.appPath });
|
|
59115
|
+
mkdirSync9(join29(paths.agentPath, ".."), { recursive: true });
|
|
59116
|
+
writeFileSync7(
|
|
59117
|
+
paths.agentPath,
|
|
59118
|
+
brokerLaunchAgentPlist({
|
|
59119
|
+
executablePath: paths.executablePath,
|
|
59120
|
+
socketPath: paths.socketPath
|
|
59121
|
+
}),
|
|
59122
|
+
{ mode: 420 }
|
|
59123
|
+
);
|
|
59124
|
+
const bootstrap = launchctl(deps, ["bootstrap", domain, paths.agentPath]);
|
|
59125
|
+
if (bootstrap.status !== 0) {
|
|
59126
|
+
steps.push({
|
|
59127
|
+
step: "start LaunchAgent",
|
|
59128
|
+
ok: false,
|
|
59129
|
+
detail: String(bootstrap.stderr || bootstrap.stdout || "launchctl bootstrap failed").trim()
|
|
59130
|
+
});
|
|
59131
|
+
return finish();
|
|
59132
|
+
}
|
|
59133
|
+
steps.push({ step: "start LaunchAgent", ok: true, detail: paths.agentPath });
|
|
59134
|
+
} finally {
|
|
59135
|
+
rmSync8(staging, { recursive: true, force: true });
|
|
59136
|
+
}
|
|
59137
|
+
const answered = await waitForBroker(deps, paths.socketPath);
|
|
59138
|
+
steps.push({
|
|
59139
|
+
step: "broker answers",
|
|
59140
|
+
ok: answered,
|
|
59141
|
+
detail: answered ? paths.socketPath : `no answer on ${paths.socketPath}; inspect diagnostics with \`${BROKER_LOG_COMMAND}\``
|
|
59142
|
+
});
|
|
59143
|
+
return finish();
|
|
59144
|
+
}
|
|
59145
|
+
function signingWarning(teamId) {
|
|
59146
|
+
return teamId ? "This signing identity does not establish Developer ID continuity. Apple Development certificate renewal can change the designated requirement and require granting permissions again." : "The broker is ad-hoc signed, so macOS ties its grants to this exact build: after every rebuild, grant Full Disk Access and Automation again. A Developer ID Application identity can retain the bundle and team designated requirement across rebuilds.";
|
|
59147
|
+
}
|
|
59148
|
+
function formatBrokerSetup(report) {
|
|
59149
|
+
const lines = ["Apple Notes MCP permission broker", ""];
|
|
59150
|
+
for (const step of report.steps)
|
|
59151
|
+
lines.push(`${step.ok ? "\u2713" : "\u2717"} ${step.step}${step.detail ? `: ${step.detail}` : ""}`);
|
|
59152
|
+
for (const warning of report.warnings) lines.push(`! ${warning}`);
|
|
59153
|
+
lines.push("");
|
|
59154
|
+
const app = report.installation.paths.appPath;
|
|
59155
|
+
if (report.mode === "uninstall") {
|
|
59156
|
+
lines.push(
|
|
59157
|
+
report.ok ? "The broker is removed. MCP clients now run the server in-process again." : "The broker was not fully removed. Fix the failed step above and run it again."
|
|
59158
|
+
);
|
|
59159
|
+
} else if (report.ok) {
|
|
59160
|
+
lines.push(
|
|
59161
|
+
`The broker is ${report.mode === "check" ? "installed and" : "installed and"} running. MCP clients that launch apple-notes-mcp now reach it through the broker.`,
|
|
59162
|
+
"",
|
|
59163
|
+
"Grant it access once:",
|
|
59164
|
+
` 1. System Settings > Privacy & Security > Full Disk Access: click +, add ${app}, and turn it on.`,
|
|
59165
|
+
" 2. The first Notes request through the broker asks to let Apple Notes MCP Broker control Notes. Click Allow.",
|
|
59166
|
+
"",
|
|
59167
|
+
"Restart your MCP client so it starts a fresh apple-notes-mcp process."
|
|
59168
|
+
);
|
|
59169
|
+
} else if (report.mode === "check") {
|
|
59170
|
+
lines.push(
|
|
59171
|
+
report.installation.installed && !report.running && report.installation.ready ? `The broker is installed but not answering. Inspect diagnostics with \`${BROKER_LOG_COMMAND}\`, or run \`${BROKER_SETUP_COMMAND}\` again.` : `Run \`${BROKER_SETUP_COMMAND}\` to install it.`
|
|
59172
|
+
);
|
|
59173
|
+
} else {
|
|
59174
|
+
lines.push("The broker was not installed. Fix the failed step above and run setup again.");
|
|
59175
|
+
}
|
|
59176
|
+
return lines.join("\n");
|
|
59177
|
+
}
|
|
59178
|
+
var proxyFallbackReason = null;
|
|
59179
|
+
function recordBrokerFallback(reason) {
|
|
59180
|
+
proxyFallbackReason = reason;
|
|
59181
|
+
}
|
|
59182
|
+
function brokerStatus(deps = defaultBrokerDeps()) {
|
|
59183
|
+
const inUse = deps.env[BROKERED_ENV] === "1";
|
|
59184
|
+
let installation = null;
|
|
59185
|
+
try {
|
|
59186
|
+
installation = inspectBroker(deps);
|
|
59187
|
+
} catch {
|
|
59188
|
+
installation = null;
|
|
59189
|
+
}
|
|
59190
|
+
const manifest = installation?.manifest ?? null;
|
|
59191
|
+
const base = {
|
|
59192
|
+
inUse,
|
|
59193
|
+
installed: installation?.installed ?? false,
|
|
59194
|
+
ready: installation?.ready ?? false,
|
|
59195
|
+
appPath: inUse ? deps.env[BROKER_APP_ENV] ?? manifest?.appPath ?? null : manifest?.appPath ?? null,
|
|
59196
|
+
stableSigning: manifest ? manifest.signing.stable : null,
|
|
59197
|
+
fallbackReason: inUse ? null : proxyFallbackReason
|
|
59198
|
+
};
|
|
59199
|
+
let detail;
|
|
59200
|
+
if (inUse)
|
|
59201
|
+
detail = `In use: this server runs under ${base.appPath ?? "the broker app"}, which holds the Full Disk Access and Automation grants.`;
|
|
59202
|
+
else if (!base.installed)
|
|
59203
|
+
detail = `Not installed. Grants apply to the app or Node binary that launches this server. \`${BROKER_SETUP_COMMAND}\` moves them to one signed app.`;
|
|
59204
|
+
else if (deps.env[BROKER_MODE_ENV] === "off")
|
|
59205
|
+
detail = `Installed but turned off with ${BROKER_MODE_ENV}=off; running in-process.`;
|
|
59206
|
+
else
|
|
59207
|
+
detail = `Installed but not in use; running in-process.${base.fallbackReason ? ` ${base.fallbackReason}` : ""}${installation?.detail ? ` ${installation.detail}` : ""}`;
|
|
59208
|
+
return { ...base, detail };
|
|
59209
|
+
}
|
|
59210
|
+
|
|
58309
59211
|
// src/tools/nativeOperations.ts
|
|
58310
59212
|
var VERIFIED_BACKGROUND = /* @__PURE__ */ new Set([
|
|
58311
59213
|
"append-native",
|
|
@@ -58524,7 +59426,9 @@ function registerNativeOperations(server2, manager) {
|
|
|
58524
59426
|
])
|
|
58525
59427
|
),
|
|
58526
59428
|
unavailable: UNAVAILABLE,
|
|
58527
|
-
...getCapabilityMatrix()
|
|
59429
|
+
...getCapabilityMatrix(),
|
|
59430
|
+
// Whether this process runs under the permission broker (#220).
|
|
59431
|
+
broker: brokerStatus()
|
|
58528
59432
|
};
|
|
58529
59433
|
},
|
|
58530
59434
|
true
|
|
@@ -58752,10 +59656,10 @@ function registerNativeOperations(server2, manager) {
|
|
|
58752
59656
|
}
|
|
58753
59657
|
|
|
58754
59658
|
// src/setupShortcuts.ts
|
|
58755
|
-
import { spawnSync as
|
|
58756
|
-
import { existsSync as
|
|
59659
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
59660
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
58757
59661
|
import { release as release3 } from "node:os";
|
|
58758
|
-
import { dirname as dirname8, resolve as
|
|
59662
|
+
import { dirname as dirname8, resolve as resolve8 } from "node:path";
|
|
58759
59663
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
58760
59664
|
var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
|
|
58761
59665
|
var MARKDOWN_MIN_DARWIN_MAJOR = 25;
|
|
@@ -58773,16 +59677,16 @@ var shortcutFiles = [
|
|
|
58773
59677
|
];
|
|
58774
59678
|
function setupShortcuts(checkOnly, dependencies = {}) {
|
|
58775
59679
|
const status = dependencies.status || nativeTagsStatus;
|
|
58776
|
-
const exists = dependencies.exists ||
|
|
59680
|
+
const exists = dependencies.exists || existsSync15;
|
|
58777
59681
|
const open = dependencies.open || ((path10) => {
|
|
58778
|
-
const result =
|
|
59682
|
+
const result = spawnSync4("/usr/bin/open", [path10], { encoding: "utf8" });
|
|
58779
59683
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
58780
59684
|
});
|
|
58781
|
-
const baseDirectory = dependencies.baseDirectory ||
|
|
59685
|
+
const baseDirectory = dependencies.baseDirectory || resolve8(dirname8(fileURLToPath2(import.meta.url)), "../shortcuts");
|
|
58782
59686
|
const osRelease = (dependencies.osRelease || release3)();
|
|
58783
59687
|
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
58784
59688
|
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
58785
|
-
const path10 =
|
|
59689
|
+
const path10 = resolve8(baseDirectory, file);
|
|
58786
59690
|
let installed = false;
|
|
58787
59691
|
let identifier;
|
|
58788
59692
|
let error2;
|
|
@@ -58842,7 +59746,7 @@ function formatShortcutSetup(report) {
|
|
|
58842
59746
|
|
|
58843
59747
|
// src/utils/noteAudio.ts
|
|
58844
59748
|
import { readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
|
|
58845
|
-
import { join as
|
|
59749
|
+
import { join as join30 } from "node:path";
|
|
58846
59750
|
var EXTRA_AUDIO_UTIS = [
|
|
58847
59751
|
"public.mp3",
|
|
58848
59752
|
"public.aiff-audio",
|
|
@@ -58867,7 +59771,7 @@ function accountDirsFor(containerDir, accountIdentifier) {
|
|
|
58867
59771
|
if (own) return [own];
|
|
58868
59772
|
let names;
|
|
58869
59773
|
try {
|
|
58870
|
-
names = readdirSync5(
|
|
59774
|
+
names = readdirSync5(join30(containerDir, "Accounts")).sort();
|
|
58871
59775
|
} catch {
|
|
58872
59776
|
return [];
|
|
58873
59777
|
}
|
|
@@ -58879,8 +59783,8 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
|
|
|
58879
59783
|
if (!id2 || !filename) return null;
|
|
58880
59784
|
const generation = safeComponent(media?.generation);
|
|
58881
59785
|
for (const dir of accountDirsFor(containerDir, accountIdentifier)) {
|
|
58882
|
-
const base =
|
|
58883
|
-
const candidates = generation ? [
|
|
59786
|
+
const base = join30(dir, "Media", id2);
|
|
59787
|
+
const candidates = generation ? [join30(base, generation, filename), join30(base, filename)] : [join30(base, filename)];
|
|
58884
59788
|
for (const candidate of candidates) {
|
|
58885
59789
|
const real = realInside(candidate, dir);
|
|
58886
59790
|
if (real && isFile2(real)) return real;
|
|
@@ -59151,25 +60055,25 @@ function formatTranscription(result) {
|
|
|
59151
60055
|
}
|
|
59152
60056
|
|
|
59153
60057
|
// src/services/privateHelperBuild.ts
|
|
59154
|
-
import { spawnSync as
|
|
60058
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
59155
60059
|
import {
|
|
59156
|
-
chmodSync as
|
|
59157
|
-
existsSync as
|
|
59158
|
-
mkdirSync as
|
|
59159
|
-
mkdtempSync as
|
|
59160
|
-
renameSync as
|
|
59161
|
-
rmSync as
|
|
59162
|
-
writeFileSync as
|
|
60060
|
+
chmodSync as chmodSync3,
|
|
60061
|
+
existsSync as existsSync17,
|
|
60062
|
+
mkdirSync as mkdirSync10,
|
|
60063
|
+
mkdtempSync as mkdtempSync9,
|
|
60064
|
+
renameSync as renameSync4,
|
|
60065
|
+
rmSync as rmSync9,
|
|
60066
|
+
writeFileSync as writeFileSync8
|
|
59163
60067
|
} from "node:fs";
|
|
59164
60068
|
import { release as release4 } from "node:os";
|
|
59165
|
-
import { join as
|
|
60069
|
+
import { join as join32 } from "node:path";
|
|
59166
60070
|
|
|
59167
60071
|
// src/services/privateHelper.ts
|
|
59168
|
-
import { spawnSync as
|
|
60072
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
59169
60073
|
import { createHash as createHash7 } from "node:crypto";
|
|
59170
|
-
import { existsSync as
|
|
59171
|
-
import { homedir as
|
|
59172
|
-
import { dirname as dirname9, join as
|
|
60074
|
+
import { existsSync as existsSync16, readFileSync as readFileSync6 } from "node:fs";
|
|
60075
|
+
import { homedir as homedir23 } from "node:os";
|
|
60076
|
+
import { dirname as dirname9, join as join31, resolve as resolve9 } from "node:path";
|
|
59173
60077
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
59174
60078
|
var PRIVATE_HELPER_PROTOCOL = 1;
|
|
59175
60079
|
var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
|
|
@@ -59197,16 +60101,16 @@ var manifestSchema = external_exports.object({
|
|
|
59197
60101
|
function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
|
|
59198
60102
|
let dir = fromDir;
|
|
59199
60103
|
for (; ; ) {
|
|
59200
|
-
const candidate =
|
|
59201
|
-
if (
|
|
60104
|
+
const candidate = join31(dir, "package.json");
|
|
60105
|
+
if (existsSync16(candidate)) {
|
|
59202
60106
|
try {
|
|
59203
|
-
const pkg = JSON.parse(
|
|
60107
|
+
const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
|
|
59204
60108
|
if (pkg.name === "apple-notes-mcp") return dir;
|
|
59205
60109
|
} catch {
|
|
59206
60110
|
}
|
|
59207
60111
|
}
|
|
59208
60112
|
const parent = dirname9(dir);
|
|
59209
|
-
if (parent === dir) return
|
|
60113
|
+
if (parent === dir) return resolve9(fromDir, "..");
|
|
59210
60114
|
dir = parent;
|
|
59211
60115
|
}
|
|
59212
60116
|
}
|
|
@@ -59214,10 +60118,10 @@ function defaultDeps2(overrides = {}) {
|
|
|
59214
60118
|
return {
|
|
59215
60119
|
env: process.env,
|
|
59216
60120
|
platform: process.platform,
|
|
59217
|
-
sourcePath:
|
|
59218
|
-
exists:
|
|
59219
|
-
readFile: (path10) =>
|
|
59220
|
-
spawn:
|
|
60121
|
+
sourcePath: join31(packageRoot2(), HELPER_SOURCE_RELATIVE),
|
|
60122
|
+
exists: existsSync16,
|
|
60123
|
+
readFile: (path10) => readFileSync6(path10),
|
|
60124
|
+
spawn: spawnSync5,
|
|
59221
60125
|
...overrides
|
|
59222
60126
|
};
|
|
59223
60127
|
}
|
|
@@ -59227,14 +60131,14 @@ function privateHelperEnabled(env = process.env) {
|
|
|
59227
60131
|
function helperInstallDir(env = process.env) {
|
|
59228
60132
|
const override = env[HELPER_DIR_ENV]?.trim();
|
|
59229
60133
|
if (override) return override;
|
|
59230
|
-
return
|
|
60134
|
+
return join31(homedir23(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
|
|
59231
60135
|
}
|
|
59232
60136
|
function sha256Hex2(data) {
|
|
59233
60137
|
return createHash7("sha256").update(data).digest("hex");
|
|
59234
60138
|
}
|
|
59235
60139
|
function inspectInstallation(deps = defaultDeps2()) {
|
|
59236
60140
|
const installDir = helperInstallDir(deps.env);
|
|
59237
|
-
const binaryPath =
|
|
60141
|
+
const binaryPath = join31(installDir, HELPER_BINARY_NAME);
|
|
59238
60142
|
const base = {
|
|
59239
60143
|
installDir,
|
|
59240
60144
|
binaryPath,
|
|
@@ -59252,7 +60156,7 @@ function inspectInstallation(deps = defaultDeps2()) {
|
|
|
59252
60156
|
if (!deps.exists(deps.sourcePath))
|
|
59253
60157
|
return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
|
|
59254
60158
|
base.expectedSourceSha256 = sha256Hex2(deps.readFile(deps.sourcePath));
|
|
59255
|
-
const manifestPath =
|
|
60159
|
+
const manifestPath = join31(installDir, MANIFEST_NAME);
|
|
59256
60160
|
if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
|
|
59257
60161
|
return fail(
|
|
59258
60162
|
"helper_not_installed",
|
|
@@ -59510,7 +60414,7 @@ function defaultBuildDeps() {
|
|
|
59510
60414
|
return {
|
|
59511
60415
|
...defaultDeps2(),
|
|
59512
60416
|
osVersion: () => {
|
|
59513
|
-
const r =
|
|
60417
|
+
const r = spawnSync6("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
|
|
59514
60418
|
return r.status === 0 ? r.stdout.trim() : `Darwin ${release4()}`;
|
|
59515
60419
|
},
|
|
59516
60420
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -59580,10 +60484,10 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
|
|
|
59580
60484
|
const compiler = String(clangVersion.stdout || "").split("\n")[0] || "clang";
|
|
59581
60485
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
59582
60486
|
const installDir = helperInstallDir(deps.env);
|
|
59583
|
-
|
|
59584
|
-
const staging =
|
|
60487
|
+
mkdirSync10(installDir, { recursive: true, mode: 448 });
|
|
60488
|
+
const staging = mkdtempSync9(join32(installDir, ".staging-"));
|
|
59585
60489
|
try {
|
|
59586
|
-
const stagedBinary =
|
|
60490
|
+
const stagedBinary = join32(staging, HELPER_BINARY_NAME);
|
|
59587
60491
|
const compile = deps.spawn(
|
|
59588
60492
|
"/usr/bin/xcrun",
|
|
59589
60493
|
compileArguments(deps.sourcePath, stagedBinary, sourceSha),
|
|
@@ -59659,14 +60563,14 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
|
|
|
59659
60563
|
osVersion: deps.osVersion(),
|
|
59660
60564
|
compiler
|
|
59661
60565
|
};
|
|
59662
|
-
|
|
59663
|
-
|
|
59664
|
-
|
|
60566
|
+
chmodSync3(stagedBinary, 448);
|
|
60567
|
+
renameSync4(stagedBinary, join32(installDir, HELPER_BINARY_NAME));
|
|
60568
|
+
writeFileSync8(join32(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
|
|
59665
60569
|
mode: 384
|
|
59666
60570
|
});
|
|
59667
60571
|
steps.push({ step: "install", ok: true, detail: installDir });
|
|
59668
60572
|
} finally {
|
|
59669
|
-
if (
|
|
60573
|
+
if (existsSync17(staging)) rmSync9(staging, { recursive: true, force: true });
|
|
59670
60574
|
}
|
|
59671
60575
|
const installation = inspectInstallation(deps);
|
|
59672
60576
|
steps.push({
|
|
@@ -59695,7 +60599,7 @@ function formatHelperBuild(report) {
|
|
|
59695
60599
|
}
|
|
59696
60600
|
|
|
59697
60601
|
// src/services/permissions.ts
|
|
59698
|
-
import { spawnSync as
|
|
60602
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
59699
60603
|
import { createInterface } from "node:readline";
|
|
59700
60604
|
var PRIVACY = "System Settings > Privacy & Security";
|
|
59701
60605
|
var PANE_ANCHORS = {
|
|
@@ -59731,7 +60635,7 @@ function checkPermissions(probes = defaultPermissionProbes(), options = defaultC
|
|
|
59731
60635
|
const launchingApp = safe(probes.launchingApp, null);
|
|
59732
60636
|
const who = launchingApp ?? "the app that launches the server";
|
|
59733
60637
|
const items = [
|
|
59734
|
-
fullDiskAccessItem(probes, macOSVersion),
|
|
60638
|
+
fullDiskAccessItem(probes, macOSVersion, options.env),
|
|
59735
60639
|
automationItem(probes, macOSVersion, who, automationSkipReason(options)),
|
|
59736
60640
|
shortcutsItem(probes),
|
|
59737
60641
|
speechItem(probes, macOSVersion, who)
|
|
@@ -59754,7 +60658,7 @@ function safe(fn, fallback) {
|
|
|
59754
60658
|
function errorText(error2) {
|
|
59755
60659
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
59756
60660
|
}
|
|
59757
|
-
function fullDiskAccessItem(probes, macOSVersion) {
|
|
60661
|
+
function fullDiskAccessItem(probes, macOSVersion, env) {
|
|
59758
60662
|
const base = {
|
|
59759
60663
|
id: "fullDiskAccess",
|
|
59760
60664
|
title: "Full Disk Access",
|
|
@@ -59769,7 +60673,7 @@ function fullDiskAccessItem(probes, macOSVersion) {
|
|
|
59769
60673
|
...base,
|
|
59770
60674
|
status: "unknown",
|
|
59771
60675
|
detail: `could not probe the Notes database: ${errorText(error2)}`,
|
|
59772
|
-
fix: fdaRemediation(probes.execPath)
|
|
60676
|
+
fix: fdaRemediation(probes.execPath, env)
|
|
59773
60677
|
};
|
|
59774
60678
|
}
|
|
59775
60679
|
return granted ? {
|
|
@@ -59781,7 +60685,7 @@ function fullDiskAccessItem(probes, macOSVersion) {
|
|
|
59781
60685
|
...base,
|
|
59782
60686
|
status: "missing",
|
|
59783
60687
|
detail: "the Notes database is not readable, so query-notes, checklist state, note metadata, note links, native objects, exports and the bridges' readback do not work",
|
|
59784
|
-
fix: fdaRemediation(probes.execPath)
|
|
60688
|
+
fix: fdaRemediation(probes.execPath, env)
|
|
59785
60689
|
};
|
|
59786
60690
|
}
|
|
59787
60691
|
var PROBE_AUTOMATION_COMMAND = "apple-notes-mcp setup --permissions --probe-automation";
|
|
@@ -59939,7 +60843,7 @@ function findLaunchingApp(startPid = process.ppid, readProcess = readProcessEntr
|
|
|
59939
60843
|
return null;
|
|
59940
60844
|
}
|
|
59941
60845
|
function readProcessEntry(pid) {
|
|
59942
|
-
const result =
|
|
60846
|
+
const result = spawnSync7("/bin/ps", ["-o", "ppid=", "-o", "comm=", "-p", String(pid)], {
|
|
59943
60847
|
encoding: "utf8",
|
|
59944
60848
|
timeout: 3e3
|
|
59945
60849
|
});
|
|
@@ -59980,7 +60884,7 @@ function openSettingsPane(item, open = openUrl) {
|
|
|
59980
60884
|
return open(item.settingsUrl);
|
|
59981
60885
|
}
|
|
59982
60886
|
function openUrl(url) {
|
|
59983
|
-
const result =
|
|
60887
|
+
const result = spawnSync7("/usr/bin/open", [url], { encoding: "utf8" });
|
|
59984
60888
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
59985
60889
|
}
|
|
59986
60890
|
function pendingItems(report) {
|
|
@@ -60092,19 +60996,19 @@ function defaultPermissionsCliDeps(options) {
|
|
|
60092
60996
|
}
|
|
60093
60997
|
|
|
60094
60998
|
// src/services/permissionsWindow.ts
|
|
60095
|
-
import { spawn as spawn2, spawnSync as
|
|
60999
|
+
import { spawn as spawn2, spawnSync as spawnSync8 } from "node:child_process";
|
|
60096
61000
|
import {
|
|
60097
|
-
chmodSync as
|
|
60098
|
-
existsSync as
|
|
60099
|
-
mkdirSync as
|
|
60100
|
-
mkdtempSync as
|
|
60101
|
-
readFileSync as
|
|
60102
|
-
renameSync as
|
|
60103
|
-
rmSync as
|
|
60104
|
-
writeFileSync as
|
|
61001
|
+
chmodSync as chmodSync4,
|
|
61002
|
+
existsSync as existsSync18,
|
|
61003
|
+
mkdirSync as mkdirSync11,
|
|
61004
|
+
mkdtempSync as mkdtempSync10,
|
|
61005
|
+
readFileSync as readFileSync7,
|
|
61006
|
+
renameSync as renameSync5,
|
|
61007
|
+
rmSync as rmSync10,
|
|
61008
|
+
writeFileSync as writeFileSync9
|
|
60105
61009
|
} from "node:fs";
|
|
60106
|
-
import { homedir as
|
|
60107
|
-
import { join as
|
|
61010
|
+
import { homedir as homedir24 } from "node:os";
|
|
61011
|
+
import { join as join33 } from "node:path";
|
|
60108
61012
|
import { createInterface as createInterface2 } from "node:readline";
|
|
60109
61013
|
var PERMISSIONS_WINDOW_PROTOCOL = 1;
|
|
60110
61014
|
var PERMISSIONS_WINDOW_DIR_ENV = "APPLE_NOTES_MCP_PERMISSIONS_WINDOW_DIR";
|
|
@@ -60125,10 +61029,10 @@ function defaultPermissionsWindowDeps(overrides = {}) {
|
|
|
60125
61029
|
return {
|
|
60126
61030
|
env: process.env,
|
|
60127
61031
|
platform: process.platform,
|
|
60128
|
-
sourcePath:
|
|
60129
|
-
exists:
|
|
60130
|
-
readFile: (path10) =>
|
|
60131
|
-
spawn:
|
|
61032
|
+
sourcePath: join33(packageRoot(), PERMISSIONS_WINDOW_SOURCE),
|
|
61033
|
+
exists: existsSync18,
|
|
61034
|
+
readFile: (path10) => readFileSync7(path10),
|
|
61035
|
+
spawn: spawnSync8,
|
|
60132
61036
|
now: () => /* @__PURE__ */ new Date(),
|
|
60133
61037
|
...overrides
|
|
60134
61038
|
};
|
|
@@ -60136,17 +61040,17 @@ function defaultPermissionsWindowDeps(overrides = {}) {
|
|
|
60136
61040
|
function permissionsWindowInstallDir(env = process.env) {
|
|
60137
61041
|
const override = env[PERMISSIONS_WINDOW_DIR_ENV]?.trim();
|
|
60138
61042
|
if (override) return override;
|
|
60139
|
-
return
|
|
61043
|
+
return join33(homedir24(), "Library", "Application Support", "apple-notes-mcp", "permissions-window");
|
|
60140
61044
|
}
|
|
60141
61045
|
function inspectPermissionsWindow(deps = defaultPermissionsWindowDeps()) {
|
|
60142
61046
|
const installDir = permissionsWindowInstallDir(deps.env);
|
|
60143
|
-
const binaryPath =
|
|
61047
|
+
const binaryPath = join33(installDir, PERMISSIONS_WINDOW_BINARY);
|
|
60144
61048
|
const fail = (reason, detail) => ({ ready: false, reason, detail, installDir, binaryPath });
|
|
60145
61049
|
if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
|
|
60146
61050
|
const rebuild = `Run \`${PERMISSIONS_WINDOW_SETUP_COMMAND}\`.`;
|
|
60147
61051
|
if (!deps.exists(deps.sourcePath))
|
|
60148
61052
|
return fail("window_not_installed", `Packaged window source is missing: ${deps.sourcePath}`);
|
|
60149
|
-
const manifestPath =
|
|
61053
|
+
const manifestPath = join33(installDir, PERMISSIONS_WINDOW_MANIFEST);
|
|
60150
61054
|
if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
|
|
60151
61055
|
return fail("window_not_installed", `The permissions window is not built. ${rebuild}`);
|
|
60152
61056
|
let manifest;
|
|
@@ -60254,14 +61158,14 @@ function buildPermissionsWindow(checkOnly, deps = defaultPermissionsWindowDeps()
|
|
|
60254
61158
|
const compiler = String(version3.stdout || version3.stderr || "").split("\n").find((line) => line.includes("Swift version"))?.trim() || "swiftc";
|
|
60255
61159
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
60256
61160
|
const installDir = permissionsWindowInstallDir(deps.env);
|
|
60257
|
-
|
|
60258
|
-
const staging =
|
|
61161
|
+
mkdirSync11(installDir, { recursive: true, mode: 448 });
|
|
61162
|
+
const staging = mkdtempSync10(join33(installDir, ".staging-"));
|
|
60259
61163
|
try {
|
|
60260
|
-
const stagedBinary =
|
|
60261
|
-
const digestPath =
|
|
60262
|
-
const plistPath =
|
|
60263
|
-
|
|
60264
|
-
|
|
61164
|
+
const stagedBinary = join33(staging, PERMISSIONS_WINDOW_BINARY);
|
|
61165
|
+
const digestPath = join33(staging, "source-digest.swift");
|
|
61166
|
+
const plistPath = join33(staging, "Info.plist");
|
|
61167
|
+
writeFileSync9(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
|
|
61168
|
+
writeFileSync9(plistPath, permissionsWindowInfoPlist(), { mode: 384 });
|
|
60265
61169
|
const compile = deps.spawn(
|
|
60266
61170
|
"/usr/bin/xcrun",
|
|
60267
61171
|
permissionsWindowCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
|
|
@@ -60315,16 +61219,16 @@ function buildPermissionsWindow(checkOnly, deps = defaultPermissionsWindowDeps()
|
|
|
60315
61219
|
builtAt: deps.now().toISOString(),
|
|
60316
61220
|
compiler
|
|
60317
61221
|
};
|
|
60318
|
-
|
|
60319
|
-
|
|
60320
|
-
|
|
60321
|
-
|
|
61222
|
+
chmodSync4(stagedBinary, 448);
|
|
61223
|
+
renameSync5(stagedBinary, join33(installDir, PERMISSIONS_WINDOW_BINARY));
|
|
61224
|
+
writeFileSync9(
|
|
61225
|
+
join33(installDir, PERMISSIONS_WINDOW_MANIFEST),
|
|
60322
61226
|
JSON.stringify(manifest, null, 2) + "\n",
|
|
60323
61227
|
{ mode: 384 }
|
|
60324
61228
|
);
|
|
60325
61229
|
steps.push({ step: "install", ok: true, detail: installDir });
|
|
60326
61230
|
} finally {
|
|
60327
|
-
|
|
61231
|
+
rmSync10(staging, { recursive: true, force: true });
|
|
60328
61232
|
}
|
|
60329
61233
|
const installation = inspectPermissionsWindow(deps);
|
|
60330
61234
|
steps.push({
|
|
@@ -60398,6 +61302,288 @@ function runPermissionsWindow(binaryPath, session) {
|
|
|
60398
61302
|
});
|
|
60399
61303
|
}
|
|
60400
61304
|
|
|
61305
|
+
// src/services/brokerProxy.ts
|
|
61306
|
+
var BROKER_CONNECT_TIMEOUT_MS = 5e3;
|
|
61307
|
+
var MAX_FRAME_BYTES = 65536;
|
|
61308
|
+
var OUTPUT_FLUSH_TIMEOUT_MS = 2e3;
|
|
61309
|
+
var OUTPUT_STALL_TIMEOUT_MS = 3e4;
|
|
61310
|
+
var BROKER_PASSED_ENV_KEYS = [
|
|
61311
|
+
"APPLE_NOTES_MCP_BLOCKS_MAX_BYTES",
|
|
61312
|
+
"APPLE_NOTES_MCP_EXPORT_MAX_BYTES",
|
|
61313
|
+
"APPLE_NOTES_MCP_MAX_ATTACHMENT_BYTES",
|
|
61314
|
+
"APPLE_NOTES_MCP_MAX_BUFFER",
|
|
61315
|
+
"APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES",
|
|
61316
|
+
"APPLE_NOTES_MCP_MAX_RETRIES",
|
|
61317
|
+
"APPLE_NOTES_MCP_PRIVATE_HELPER_TIMEOUT_MS",
|
|
61318
|
+
"APPLE_NOTES_MCP_PUBLIC_HELPER_TIMEOUT_MS",
|
|
61319
|
+
"APPLE_NOTES_MCP_RETRY_DELAY_MS",
|
|
61320
|
+
"APPLE_NOTES_MCP_TIMEOUT_MS"
|
|
61321
|
+
];
|
|
61322
|
+
var passedEnvironmentKeys = new Set(BROKER_PASSED_ENV_KEYS);
|
|
61323
|
+
var MAX_PASSED_VALUE = 2147483647;
|
|
61324
|
+
var MAX_VARIABLE_BYTES = 8192;
|
|
61325
|
+
function defaultBrokerProxyDeps(overrides = {}) {
|
|
61326
|
+
return {
|
|
61327
|
+
env: process.env,
|
|
61328
|
+
inspect: () => inspectBroker(),
|
|
61329
|
+
request: requestBroker,
|
|
61330
|
+
stdin: process.stdin,
|
|
61331
|
+
stdout: process.stdout,
|
|
61332
|
+
stderr: process.stderr,
|
|
61333
|
+
exit: (code) => process.exit(code),
|
|
61334
|
+
log: (message) => process.stderr.write(`[apple-notes-mcp] ${message}
|
|
61335
|
+
`),
|
|
61336
|
+
...overrides
|
|
61337
|
+
};
|
|
61338
|
+
}
|
|
61339
|
+
function passedEnvironment(env) {
|
|
61340
|
+
const passed = {};
|
|
61341
|
+
for (const [key, value] of Object.entries(env)) {
|
|
61342
|
+
if (!passedEnvironmentKeys.has(key) || typeof value !== "string" || value.length > MAX_VARIABLE_BYTES || !/^[0-9]+$/.test(value))
|
|
61343
|
+
continue;
|
|
61344
|
+
const numeric = Number(value);
|
|
61345
|
+
if (!Number.isInteger(numeric) || numeric < 1 || numeric > MAX_PASSED_VALUE) continue;
|
|
61346
|
+
passed[key] = value;
|
|
61347
|
+
}
|
|
61348
|
+
return passed;
|
|
61349
|
+
}
|
|
61350
|
+
async function startBrokerProxy(deps = defaultBrokerProxyDeps()) {
|
|
61351
|
+
if (deps.env[BROKERED_ENV] === "1" || deps.env[BROKER_MODE_ENV] === "off") return false;
|
|
61352
|
+
let installation;
|
|
61353
|
+
try {
|
|
61354
|
+
installation = deps.inspect();
|
|
61355
|
+
} catch {
|
|
61356
|
+
return false;
|
|
61357
|
+
}
|
|
61358
|
+
if (!installation.installed) return false;
|
|
61359
|
+
const fallBack = (reason) => {
|
|
61360
|
+
recordBrokerFallback(reason);
|
|
61361
|
+
deps.log(`Permission broker not used, running in-process: ${reason}`);
|
|
61362
|
+
return false;
|
|
61363
|
+
};
|
|
61364
|
+
if (!installation.ready) return fallBack(installation.detail ?? "the broker is not ready.");
|
|
61365
|
+
const manifest = installation.manifest;
|
|
61366
|
+
if (!manifest) return fallBack("the broker has no verified runtime manifest.");
|
|
61367
|
+
let socket;
|
|
61368
|
+
let leftover;
|
|
61369
|
+
try {
|
|
61370
|
+
const result = await deps.request(
|
|
61371
|
+
installation.paths.socketPath,
|
|
61372
|
+
{
|
|
61373
|
+
type: "connect",
|
|
61374
|
+
protocolVersion: BROKER_PROTOCOL,
|
|
61375
|
+
packageVersion: manifest.packageVersion,
|
|
61376
|
+
entrySha256: manifest.entrySha256,
|
|
61377
|
+
env: passedEnvironment(deps.env)
|
|
61378
|
+
},
|
|
61379
|
+
BROKER_CONNECT_TIMEOUT_MS
|
|
61380
|
+
);
|
|
61381
|
+
if (result.answer.type !== "ready") {
|
|
61382
|
+
result.socket.destroy();
|
|
61383
|
+
const message = typeof result.answer.message === "string" ? result.answer.message : "no reason given";
|
|
61384
|
+
return fallBack(`the broker refused the connection (${message})`);
|
|
61385
|
+
}
|
|
61386
|
+
if (result.answer.protocolVersion !== BROKER_PROTOCOL || result.answer.packageVersion !== manifest.packageVersion || result.answer.entrySha256 !== manifest.entrySha256) {
|
|
61387
|
+
result.socket.destroy();
|
|
61388
|
+
return fallBack(
|
|
61389
|
+
"the running broker serves a different server version. Run `apple-notes-mcp setup --broker` again."
|
|
61390
|
+
);
|
|
61391
|
+
}
|
|
61392
|
+
socket = result.socket;
|
|
61393
|
+
leftover = result.leftover;
|
|
61394
|
+
} catch (error2) {
|
|
61395
|
+
return fallBack(
|
|
61396
|
+
error2 instanceof BrokerUnreachableError ? error2.message : `unexpected error: ${String(error2)}`
|
|
61397
|
+
);
|
|
61398
|
+
}
|
|
61399
|
+
relayBroker(socket, leftover, deps);
|
|
61400
|
+
return true;
|
|
61401
|
+
}
|
|
61402
|
+
function relayBroker(socket, leftover, deps) {
|
|
61403
|
+
const header = Buffer.allocUnsafe(5);
|
|
61404
|
+
let headerBytes = 0;
|
|
61405
|
+
let payload = null;
|
|
61406
|
+
let payloadBytes = 0;
|
|
61407
|
+
let channel2 = 0;
|
|
61408
|
+
let initialBytes = leftover;
|
|
61409
|
+
let initialOffset = 0;
|
|
61410
|
+
let pendingWrites = 0;
|
|
61411
|
+
let exitCode = null;
|
|
61412
|
+
let finishingCode = null;
|
|
61413
|
+
let exited = false;
|
|
61414
|
+
let failing = false;
|
|
61415
|
+
let pumping = false;
|
|
61416
|
+
let remoteEnded = false;
|
|
61417
|
+
let deadline;
|
|
61418
|
+
let stallDeadline;
|
|
61419
|
+
const blocked = /* @__PURE__ */ new Set();
|
|
61420
|
+
const log = (message) => {
|
|
61421
|
+
try {
|
|
61422
|
+
deps.log(message);
|
|
61423
|
+
} catch {
|
|
61424
|
+
}
|
|
61425
|
+
};
|
|
61426
|
+
const stopInput = () => {
|
|
61427
|
+
deps.stdin.unpipe(socket);
|
|
61428
|
+
deps.stdin.pause();
|
|
61429
|
+
};
|
|
61430
|
+
const exit = () => {
|
|
61431
|
+
if (exited || finishingCode === null || pendingWrites > 0) return;
|
|
61432
|
+
exited = true;
|
|
61433
|
+
clearTimeout(deadline);
|
|
61434
|
+
clearTimeout(stallDeadline);
|
|
61435
|
+
socket.off("readable", pump);
|
|
61436
|
+
deps.stdout.off("drain", stdoutDrained);
|
|
61437
|
+
deps.stderr.off("drain", stderrDrained);
|
|
61438
|
+
deps.exit(finishingCode);
|
|
61439
|
+
};
|
|
61440
|
+
const startDeadline = () => {
|
|
61441
|
+
if (deadline) return;
|
|
61442
|
+
deadline = setTimeout(() => {
|
|
61443
|
+
if (exited) return;
|
|
61444
|
+
log("Broker output did not finish within 2000 ms.");
|
|
61445
|
+
finishingCode = 1;
|
|
61446
|
+
pendingWrites = 0;
|
|
61447
|
+
stopInput();
|
|
61448
|
+
socket.destroy();
|
|
61449
|
+
exit();
|
|
61450
|
+
}, OUTPUT_FLUSH_TIMEOUT_MS);
|
|
61451
|
+
};
|
|
61452
|
+
const finish = (code) => {
|
|
61453
|
+
if (exited) return;
|
|
61454
|
+
if (finishingCode === null || code !== 0) finishingCode = code;
|
|
61455
|
+
stopInput();
|
|
61456
|
+
socket.destroy();
|
|
61457
|
+
startDeadline();
|
|
61458
|
+
exit();
|
|
61459
|
+
};
|
|
61460
|
+
const fail = (message) => {
|
|
61461
|
+
if (exited || failing) return;
|
|
61462
|
+
failing = true;
|
|
61463
|
+
log(message);
|
|
61464
|
+
finish(1);
|
|
61465
|
+
};
|
|
61466
|
+
const updateStallDeadline = (progress = false) => {
|
|
61467
|
+
if (progress || pendingWrites === 0 && blocked.size === 0) {
|
|
61468
|
+
clearTimeout(stallDeadline);
|
|
61469
|
+
stallDeadline = void 0;
|
|
61470
|
+
}
|
|
61471
|
+
if (exited || stallDeadline || pendingWrites === 0 && blocked.size === 0) return;
|
|
61472
|
+
stallDeadline = setTimeout(() => {
|
|
61473
|
+
if (exited) return;
|
|
61474
|
+
log("Broker output stalled for 30000 ms.");
|
|
61475
|
+
pendingWrites = 0;
|
|
61476
|
+
finish(1);
|
|
61477
|
+
}, OUTPUT_STALL_TIMEOUT_MS);
|
|
61478
|
+
};
|
|
61479
|
+
const writeOutput = (stream, bytes) => {
|
|
61480
|
+
pendingWrites++;
|
|
61481
|
+
try {
|
|
61482
|
+
const accepted = stream.write(bytes, (error2) => {
|
|
61483
|
+
if (exited) return;
|
|
61484
|
+
pendingWrites--;
|
|
61485
|
+
updateStallDeadline(true);
|
|
61486
|
+
if (error2) fail(`Broker output error: ${error2.message}`);
|
|
61487
|
+
else exit();
|
|
61488
|
+
});
|
|
61489
|
+
if (!accepted) blocked.add(stream);
|
|
61490
|
+
updateStallDeadline();
|
|
61491
|
+
} catch (error2) {
|
|
61492
|
+
pendingWrites--;
|
|
61493
|
+
fail(`Broker output error: ${String(error2)}`);
|
|
61494
|
+
}
|
|
61495
|
+
};
|
|
61496
|
+
const read = (length) => {
|
|
61497
|
+
if (initialOffset < initialBytes.length) {
|
|
61498
|
+
const end = Math.min(initialBytes.length, initialOffset + length);
|
|
61499
|
+
const bytes = initialBytes.subarray(initialOffset, end);
|
|
61500
|
+
initialOffset = end;
|
|
61501
|
+
if (initialOffset === initialBytes.length) initialBytes = Buffer.alloc(0);
|
|
61502
|
+
return bytes;
|
|
61503
|
+
}
|
|
61504
|
+
const available = socket.readableLength;
|
|
61505
|
+
return socket.read(Math.min(length, available));
|
|
61506
|
+
};
|
|
61507
|
+
function pump() {
|
|
61508
|
+
if (pumping || finishingCode !== null || exited) return;
|
|
61509
|
+
pumping = true;
|
|
61510
|
+
try {
|
|
61511
|
+
while (blocked.size === 0 && finishingCode === null) {
|
|
61512
|
+
if (exitCode !== null) {
|
|
61513
|
+
if (read(1)) fail("The broker sent bytes after its exit frame.");
|
|
61514
|
+
break;
|
|
61515
|
+
}
|
|
61516
|
+
if (payload === null) {
|
|
61517
|
+
const bytes2 = read(header.length - headerBytes);
|
|
61518
|
+
if (!bytes2) break;
|
|
61519
|
+
headerBytes += bytes2.copy(header, headerBytes);
|
|
61520
|
+
if (headerBytes !== header.length) continue;
|
|
61521
|
+
channel2 = header[0];
|
|
61522
|
+
const length = header.readUInt32BE(1);
|
|
61523
|
+
if (channel2 !== 1 && channel2 !== 2 && channel2 !== 3 || length === 0 || length > MAX_FRAME_BYTES || channel2 === 3 && length !== 4) {
|
|
61524
|
+
fail("The broker sent an invalid output frame.");
|
|
61525
|
+
break;
|
|
61526
|
+
}
|
|
61527
|
+
payload = Buffer.allocUnsafe(length);
|
|
61528
|
+
payloadBytes = 0;
|
|
61529
|
+
}
|
|
61530
|
+
const bytes = read(payload.length - payloadBytes);
|
|
61531
|
+
if (!bytes) break;
|
|
61532
|
+
payloadBytes += bytes.copy(payload, payloadBytes);
|
|
61533
|
+
if (payloadBytes !== payload.length) continue;
|
|
61534
|
+
const complete = payload;
|
|
61535
|
+
payload = null;
|
|
61536
|
+
headerBytes = 0;
|
|
61537
|
+
if (channel2 === 3) {
|
|
61538
|
+
const code = complete.readUInt32BE(0);
|
|
61539
|
+
if (code > 255) {
|
|
61540
|
+
fail("The broker sent an invalid exit status.");
|
|
61541
|
+
break;
|
|
61542
|
+
}
|
|
61543
|
+
exitCode = code;
|
|
61544
|
+
stopInput();
|
|
61545
|
+
startDeadline();
|
|
61546
|
+
} else {
|
|
61547
|
+
writeOutput(channel2 === 1 ? deps.stdout : deps.stderr, complete);
|
|
61548
|
+
}
|
|
61549
|
+
}
|
|
61550
|
+
if (remoteEnded && blocked.size === 0 && finishingCode === null) {
|
|
61551
|
+
if (exitCode === null || headerBytes !== 0 || payload !== null)
|
|
61552
|
+
fail("The broker closed without a complete exit frame.");
|
|
61553
|
+
else finish(exitCode);
|
|
61554
|
+
}
|
|
61555
|
+
} finally {
|
|
61556
|
+
pumping = false;
|
|
61557
|
+
}
|
|
61558
|
+
}
|
|
61559
|
+
const drained = (stream) => {
|
|
61560
|
+
blocked.delete(stream);
|
|
61561
|
+
updateStallDeadline(true);
|
|
61562
|
+
pump();
|
|
61563
|
+
};
|
|
61564
|
+
const stdoutDrained = () => drained(deps.stdout);
|
|
61565
|
+
const stderrDrained = () => drained(deps.stderr);
|
|
61566
|
+
deps.stdout.on("drain", stdoutDrained);
|
|
61567
|
+
deps.stderr.on("drain", stderrDrained);
|
|
61568
|
+
deps.stdout.on("error", (error2) => fail(`Broker stdout error: ${error2.message}`));
|
|
61569
|
+
deps.stderr.on("error", (error2) => fail(`Broker stderr error: ${error2.message}`));
|
|
61570
|
+
deps.stdout.on("close", () => fail("Broker stdout closed before the session finished."));
|
|
61571
|
+
deps.stderr.on("close", () => fail("Broker stderr closed before the session finished."));
|
|
61572
|
+
deps.stdin.on("error", (error2) => fail(`Broker stdin error: ${error2.message}`));
|
|
61573
|
+
socket.on("error", (error2) => fail(`Broker connection error: ${error2.message}`));
|
|
61574
|
+
socket.on("readable", pump);
|
|
61575
|
+
socket.on("end", () => {
|
|
61576
|
+
remoteEnded = true;
|
|
61577
|
+
pump();
|
|
61578
|
+
});
|
|
61579
|
+
socket.on("close", () => {
|
|
61580
|
+
if (finishingCode === null && !remoteEnded)
|
|
61581
|
+
fail("The broker connection closed before the session finished.");
|
|
61582
|
+
});
|
|
61583
|
+
deps.stdin.pipe(socket);
|
|
61584
|
+
pump();
|
|
61585
|
+
}
|
|
61586
|
+
|
|
60401
61587
|
// src/tools/privateHelperTools.ts
|
|
60402
61588
|
var coreDataId2 = external_exports.string().regex(/^x-coredata:\/\/[0-9A-F-]+\/ICNote\/p\d+$/i);
|
|
60403
61589
|
var notesUuid = external_exports.string().regex(UUID_PATTERN);
|
|
@@ -61085,24 +62271,24 @@ async function startTemplateEditor(options = {}) {
|
|
|
61085
62271
|
socket.on("close", () => sockets.delete(socket));
|
|
61086
62272
|
});
|
|
61087
62273
|
let resolveClosed;
|
|
61088
|
-
const closed = new Promise((
|
|
62274
|
+
const closed = new Promise((resolve11) => resolveClosed = resolve11);
|
|
61089
62275
|
let closing;
|
|
61090
62276
|
const close = () => {
|
|
61091
|
-
closing ??= new Promise((
|
|
62277
|
+
closing ??= new Promise((resolve11) => {
|
|
61092
62278
|
if (idleTimer) clearTimeout(idleTimer);
|
|
61093
62279
|
server2.close(() => {
|
|
61094
62280
|
resolveClosed(closeReason);
|
|
61095
|
-
|
|
62281
|
+
resolve11();
|
|
61096
62282
|
});
|
|
61097
62283
|
for (const socket of sockets) socket.destroy();
|
|
61098
62284
|
});
|
|
61099
62285
|
return closing;
|
|
61100
62286
|
};
|
|
61101
|
-
await new Promise((
|
|
62287
|
+
await new Promise((resolve11, reject) => {
|
|
61102
62288
|
server2.once("error", reject);
|
|
61103
62289
|
server2.listen({ host, port: options.port ?? 0, exclusive: true }, () => {
|
|
61104
62290
|
server2.off("error", reject);
|
|
61105
|
-
|
|
62291
|
+
resolve11();
|
|
61106
62292
|
});
|
|
61107
62293
|
});
|
|
61108
62294
|
const port = server2.address().port;
|
|
@@ -61546,13 +62732,13 @@ function createAnchorServer(options) {
|
|
|
61546
62732
|
}
|
|
61547
62733
|
function startAnchorServer(options) {
|
|
61548
62734
|
const server2 = createAnchorServer(options);
|
|
61549
|
-
return new Promise((
|
|
62735
|
+
return new Promise((resolve11, reject) => {
|
|
61550
62736
|
server2.once("error", reject);
|
|
61551
62737
|
server2.listen(options.port, options.host, () => {
|
|
61552
62738
|
server2.off("error", reject);
|
|
61553
62739
|
const address = server2.address();
|
|
61554
62740
|
const port = address && typeof address === "object" ? address.port : options.port;
|
|
61555
|
-
|
|
62741
|
+
resolve11({
|
|
61556
62742
|
server: server2,
|
|
61557
62743
|
baseUrl: `http://${hostAuthority(options.host, port)}`,
|
|
61558
62744
|
close: () => new Promise((done) => {
|
|
@@ -61590,7 +62776,7 @@ function parseAnchorsArgs(argv) {
|
|
|
61590
62776
|
}
|
|
61591
62777
|
async function runAnchorsCli(argv, {
|
|
61592
62778
|
env = process.env,
|
|
61593
|
-
resolve:
|
|
62779
|
+
resolve: resolve11,
|
|
61594
62780
|
out = (text2) => process.stdout.write(text2),
|
|
61595
62781
|
interfaces,
|
|
61596
62782
|
signals = process
|
|
@@ -61623,7 +62809,7 @@ ${ANCHORS_USAGE}
|
|
|
61623
62809
|
const token = fromEnv || newServerToken();
|
|
61624
62810
|
let started;
|
|
61625
62811
|
try {
|
|
61626
|
-
started = await startAnchorServer({ host, port: args.port, token, resolve:
|
|
62812
|
+
started = await startAnchorServer({ host, port: args.port, token, resolve: resolve11 });
|
|
61627
62813
|
} catch (error2) {
|
|
61628
62814
|
out(`Could not listen on ${host}:${args.port}: ${error2.message}
|
|
61629
62815
|
`);
|
|
@@ -61650,17 +62836,17 @@ import {
|
|
|
61650
62836
|
constants as constants9,
|
|
61651
62837
|
fstatSync as fstatSync9,
|
|
61652
62838
|
fsyncSync,
|
|
61653
|
-
lstatSync as
|
|
61654
|
-
mkdirSync as
|
|
62839
|
+
lstatSync as lstatSync7,
|
|
62840
|
+
mkdirSync as mkdirSync12,
|
|
61655
62841
|
openSync as openSync9,
|
|
61656
62842
|
readSync as readSync7,
|
|
61657
|
-
renameSync as
|
|
62843
|
+
renameSync as renameSync6,
|
|
61658
62844
|
statSync as statSync6,
|
|
61659
62845
|
unlinkSync as unlinkSync5,
|
|
61660
62846
|
writeSync as writeSync6
|
|
61661
62847
|
} from "node:fs";
|
|
61662
|
-
import { homedir as
|
|
61663
|
-
import { dirname as dirname10, isAbsolute as
|
|
62848
|
+
import { homedir as homedir25 } from "node:os";
|
|
62849
|
+
import { dirname as dirname10, isAbsolute as isAbsolute7, join as join34, resolve as resolve10 } from "node:path";
|
|
61664
62850
|
var MAX_ANCHORS = 2e4;
|
|
61665
62851
|
var MAX_REGISTRY_BYTES = 32 * 1024 * 1024;
|
|
61666
62852
|
var LOCK_WAIT_MS = 3e3;
|
|
@@ -61684,14 +62870,14 @@ var AnchorRegistryError = class extends CodedError {
|
|
|
61684
62870
|
function anchorRegistryPath(env = process.env) {
|
|
61685
62871
|
const override = env.APPLE_NOTES_MCP_ANCHOR_FILE?.trim();
|
|
61686
62872
|
if (override) {
|
|
61687
|
-
if (!
|
|
62873
|
+
if (!isAbsolute7(override))
|
|
61688
62874
|
throw new AnchorRegistryError(
|
|
61689
62875
|
"unsafe-path",
|
|
61690
62876
|
"APPLE_NOTES_MCP_ANCHOR_FILE must be an absolute path."
|
|
61691
62877
|
);
|
|
61692
|
-
return
|
|
62878
|
+
return resolve10(override);
|
|
61693
62879
|
}
|
|
61694
|
-
return
|
|
62880
|
+
return join34(homedir25(), "Library/Application Support/apple-notes-mcp/paragraph-anchors.json");
|
|
61695
62881
|
}
|
|
61696
62882
|
var STRING_OR_NULL = (v) => v === null || typeof v === "string";
|
|
61697
62883
|
function isAnchor(v) {
|
|
@@ -61714,14 +62900,14 @@ var AnchorRegistry = class _AnchorRegistry {
|
|
|
61714
62900
|
ensureDir() {
|
|
61715
62901
|
const dir = dirname10(this.path);
|
|
61716
62902
|
try {
|
|
61717
|
-
if (!
|
|
62903
|
+
if (!lstatSync7(dir).isDirectory())
|
|
61718
62904
|
throw new AnchorRegistryError(
|
|
61719
62905
|
"unsafe-path",
|
|
61720
62906
|
`${dir} is not a directory (symlinks are refused).`
|
|
61721
62907
|
);
|
|
61722
62908
|
} catch (error2) {
|
|
61723
62909
|
if (error2 instanceof AnchorRegistryError) throw error2;
|
|
61724
|
-
|
|
62910
|
+
mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
61725
62911
|
}
|
|
61726
62912
|
}
|
|
61727
62913
|
/** Every anchor in the file, in the order recorded. A missing file is an empty registry. */
|
|
@@ -61811,7 +62997,7 @@ var AnchorRegistry = class _AnchorRegistry {
|
|
|
61811
62997
|
}
|
|
61812
62998
|
write(anchors) {
|
|
61813
62999
|
try {
|
|
61814
|
-
if (!
|
|
63000
|
+
if (!lstatSync7(this.path).isFile())
|
|
61815
63001
|
throw new AnchorRegistryError(
|
|
61816
63002
|
"unsafe-path",
|
|
61817
63003
|
`Refusing to replace ${this.path}: it is not a regular file.`
|
|
@@ -61819,7 +63005,7 @@ var AnchorRegistry = class _AnchorRegistry {
|
|
|
61819
63005
|
} catch (error2) {
|
|
61820
63006
|
if (error2 instanceof AnchorRegistryError) throw error2;
|
|
61821
63007
|
}
|
|
61822
|
-
const temp =
|
|
63008
|
+
const temp = join34(
|
|
61823
63009
|
dirname10(this.path),
|
|
61824
63010
|
`.paragraph-anchors.${randomBytes5(6).toString("hex")}.tmp`
|
|
61825
63011
|
);
|
|
@@ -61840,7 +63026,7 @@ var AnchorRegistry = class _AnchorRegistry {
|
|
|
61840
63026
|
}
|
|
61841
63027
|
closeSync9(fd);
|
|
61842
63028
|
try {
|
|
61843
|
-
|
|
63029
|
+
renameSync6(temp, this.path);
|
|
61844
63030
|
} catch (error2) {
|
|
61845
63031
|
unlinkSync5(temp);
|
|
61846
63032
|
throw error2;
|
|
@@ -62035,6 +63221,11 @@ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--permissions
|
|
|
62035
63221
|
cli.close();
|
|
62036
63222
|
process.exit(code);
|
|
62037
63223
|
}
|
|
63224
|
+
if (process.argv[2] === "setup" && process.argv.slice(3).includes("--broker")) {
|
|
63225
|
+
const report = await setupBroker(parseBrokerArgs(process.argv.slice(3)));
|
|
63226
|
+
process.stdout.write(formatBrokerSetup(report) + "\n");
|
|
63227
|
+
process.exit(report.ok ? 0 : 1);
|
|
63228
|
+
}
|
|
62038
63229
|
if (process.argv[2] === "setup") {
|
|
62039
63230
|
const report = setupShortcuts(process.argv.slice(3).includes("--check"));
|
|
62040
63231
|
process.stdout.write(formatShortcutSetup(report) + "\n");
|
|
@@ -62048,6 +63239,10 @@ if (process.argv[2] === "anchors") {
|
|
|
62048
63239
|
await runAnchorsCli(process.argv.slice(3), { resolve: registryLookup(new AnchorRegistry()) })
|
|
62049
63240
|
);
|
|
62050
63241
|
}
|
|
63242
|
+
if (await startBrokerProxy()) {
|
|
63243
|
+
await new Promise(() => {
|
|
63244
|
+
});
|
|
63245
|
+
}
|
|
62051
63246
|
var server = new McpServer({
|
|
62052
63247
|
name: "apple-notes",
|
|
62053
63248
|
version: version2,
|
|
@@ -62921,7 +64116,7 @@ registerTool(
|
|
|
62921
64116
|
registerTool(
|
|
62922
64117
|
"get-note-link",
|
|
62923
64118
|
{
|
|
62924
|
-
description: "Use when: you need the notes:// deep-link URL for a note so it can be stored in a Reminders task, shared, or opened directly.\nReturns: a notes://showNote?identifier=<uuid> URL that opens the note in Notes.app on iOS and macOS.\nDo not use when: you only need the note's CoreData id (get-note-by-id) or want to reveal the note on screen (show-note).\nNote: the primary path reads the note's identifier from the Notes database, so it needs Full Disk Access for the
|
|
64119
|
+
description: "Use when: you need the notes:// deep-link URL for a note so it can be stored in a Reminders task, shared, or opened directly.\nReturns: a notes://showNote?identifier=<uuid> URL that opens the note in Notes.app on iOS and macOS.\nDo not use when: you only need the note's CoreData id (get-note-by-id) or want to reveal the note on screen (show-note).\nNote: the primary path reads the note's identifier from the Notes database, so it needs Full Disk Access for the responsible process (run doctor to identify it); macOS 12-15 can fall back to the AppleScript 'note link' property, which macOS 26+ no longer exposes. Password-protected notes cannot be linked.",
|
|
62925
64120
|
inputSchema: {
|
|
62926
64121
|
id: looseNoteId(external_exports.string()).optional().describe(`Note ID (preferred - more reliable than title): ${NOTE_ID_FORMS}`),
|
|
62927
64122
|
title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
|
|
@@ -62947,7 +64142,7 @@ registerTool(
|
|
|
62947
64142
|
const url2 = notesManager.getNoteLinkById(id2);
|
|
62948
64143
|
if (!url2) {
|
|
62949
64144
|
return errorResponse(
|
|
62950
|
-
`Failed to get note link for "${note2.title}". The Notes database may not be accessible
|
|
64145
|
+
`Failed to get note link for "${note2.title}". The Notes database may not be accessible. ${fdaRemediation()} (On macOS 12\u201315 this also falls back to the AppleScript note link property.)`
|
|
62951
64146
|
);
|
|
62952
64147
|
}
|
|
62953
64148
|
return successResponse(`Note link: ${url2}`, { id: id2, title: note2.title, url: url2 });
|
|
@@ -62967,7 +64162,7 @@ registerTool(
|
|
|
62967
64162
|
const url = notesManager.getNoteLink(title, account);
|
|
62968
64163
|
if (!url) {
|
|
62969
64164
|
return errorResponse(
|
|
62970
|
-
`Failed to get note link for "${title}". The Notes database may not be accessible
|
|
64165
|
+
`Failed to get note link for "${title}". The Notes database may not be accessible. ${fdaRemediation()} (On macOS 12\u201315 this also falls back to the AppleScript note link property.)`
|
|
62971
64166
|
);
|
|
62972
64167
|
}
|
|
62973
64168
|
return successResponse(`Note link: ${url}`, { title, url });
|
|
@@ -63140,7 +64335,7 @@ registerTool(
|
|
|
63140
64335
|
page = pageNoteBlocks(readNoteBlocks(id2), { offset, limit });
|
|
63141
64336
|
} catch (error2) {
|
|
63142
64337
|
if (!(error2 instanceof NoteBlocksError)) throw error2;
|
|
63143
|
-
const hint = error2.code === "no-full-disk-access" ? `
|
|
64338
|
+
const hint = error2.code === "no-full-disk-access" ? ` ${fdaRemediation()}` : "";
|
|
63144
64339
|
return errorResponse(
|
|
63145
64340
|
`Error reading note blocks [${error2.code}]: ${error2.message}${hint}`,
|
|
63146
64341
|
error2
|
|
@@ -64469,12 +65664,19 @@ registerTool(
|
|
|
64469
65664
|
healthy: external_exports.boolean().optional(),
|
|
64470
65665
|
checks: external_exports.array(external_exports.object({}).passthrough()).optional(),
|
|
64471
65666
|
runtimeOS: external_exports.object({}).passthrough().optional(),
|
|
64472
|
-
features: external_exports.record(external_exports.string(), external_exports.object({}).passthrough()).optional()
|
|
65667
|
+
features: external_exports.record(external_exports.string(), external_exports.object({}).passthrough()).optional(),
|
|
65668
|
+
broker: external_exports.object({}).passthrough().optional()
|
|
64473
65669
|
}
|
|
64474
65670
|
},
|
|
64475
65671
|
withErrorHandling(() => {
|
|
64476
65672
|
const report = runDoctor(notesManager);
|
|
64477
|
-
|
|
65673
|
+
const broker = brokerStatus();
|
|
65674
|
+
return successResponse(`${formatDoctorReport(report)}
|
|
65675
|
+
|
|
65676
|
+
Permission broker: ${broker.detail}`, {
|
|
65677
|
+
...report,
|
|
65678
|
+
broker
|
|
65679
|
+
});
|
|
64478
65680
|
}, "Error running doctor")
|
|
64479
65681
|
);
|
|
64480
65682
|
registerTool(
|
|
@@ -65157,7 +66359,7 @@ registerTool(
|
|
|
65157
66359
|
});
|
|
65158
66360
|
} catch (error2) {
|
|
65159
66361
|
if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
|
|
65160
|
-
const hint = error2.code === "no-full-disk-access" ? `
|
|
66362
|
+
const hint = error2.code === "no-full-disk-access" ? ` ${fdaRemediation()}` : "";
|
|
65161
66363
|
if (error2 instanceof NotesExportError && error2.details)
|
|
65162
66364
|
return errorResponse(
|
|
65163
66365
|
`Error exporting Markdown [${error2.code}]: the template is invalid:
|
|
@@ -65227,7 +66429,7 @@ registerTool(
|
|
|
65227
66429
|
});
|
|
65228
66430
|
} catch (error2) {
|
|
65229
66431
|
if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
|
|
65230
|
-
const hint = error2.code === "no-full-disk-access" ? `
|
|
66432
|
+
const hint = error2.code === "no-full-disk-access" ? ` ${fdaRemediation()}` : "";
|
|
65231
66433
|
return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
|
|
65232
66434
|
}
|
|
65233
66435
|
const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;
|