apple-notes-mcp 2.14.2 → 2.15.0
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 +67 -8
- package/build/index.js +1494 -298
- 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, """);
|
|
@@ -48843,18 +48857,12 @@ var NATIVE_APPEND_ELEMENTS = [
|
|
|
48843
48857
|
"s",
|
|
48844
48858
|
"span",
|
|
48845
48859
|
"strong",
|
|
48846
|
-
"table",
|
|
48847
|
-
"tbody",
|
|
48848
|
-
"td",
|
|
48849
|
-
"th",
|
|
48850
|
-
"thead",
|
|
48851
|
-
"tr",
|
|
48852
48860
|
"tt",
|
|
48853
48861
|
"u",
|
|
48854
48862
|
"ul"
|
|
48855
48863
|
];
|
|
48856
48864
|
var NATIVE_APPEND_SPAN_STYLE = /^font-size\s*:\s*\d{1,3}(?:\.\d+)?(?:px|pt)\s*;?$/i;
|
|
48857
|
-
var NATIVE_APPEND_HTML_SUBSET = `Native append accepts ${NATIVE_APPEND_ELEMENTS.map((e) => `<${e}>`).join(" ")}, with href on <a> and a font-size style on <span> as the only attributes; everything else needs update-note.`;
|
|
48865
|
+
var NATIVE_APPEND_HTML_SUBSET = `Native append accepts ${NATIVE_APPEND_ELEMENTS.map((e) => `<${e}>`).join(" ")}, with href on <a> and a font-size style on <span> as the only attributes; a native table is refused here \u2014 use create-table; everything else needs update-note.`;
|
|
48858
48866
|
var UNDERSCORES_OUTSIDE_A_WORD = "underscores outside a word";
|
|
48859
48867
|
var UNMODELED_MARKDOWN = [
|
|
48860
48868
|
[/(?<![\p{L}\p{N}])_|_(?![\p{L}\p{N}])/mu, UNDERSCORES_OUTSIDE_A_WORD],
|
|
@@ -48916,9 +48924,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
48916
48924
|
),
|
|
48917
48925
|
{ shortcut: status.shortcut, notInstalled: true }
|
|
48918
48926
|
);
|
|
48919
|
-
const directory = mkdtempSync4(
|
|
48927
|
+
const directory = mkdtempSync4(join18(tmpdir4(), "apple-notes-background-"));
|
|
48920
48928
|
try {
|
|
48921
|
-
const file =
|
|
48929
|
+
const file = join18(directory, "request.json");
|
|
48922
48930
|
writeFileSync3(
|
|
48923
48931
|
file,
|
|
48924
48932
|
JSON.stringify({
|
|
@@ -49125,9 +49133,9 @@ function assertAppendedVisibleText(beforeHtml, afterHtml, expected) {
|
|
|
49125
49133
|
if (!suffix || !suffix.includes(wanted)) throw new Error("Appended text not verified");
|
|
49126
49134
|
}
|
|
49127
49135
|
function appendNative(manager, request) {
|
|
49128
|
-
|
|
49129
|
-
if (request.format === "html" && /<table\b/i.test(request.content))
|
|
49136
|
+
if (request.format === "html" && /<\/?\s*(?:table|thead|tbody|tr|td|th)\b/i.test(request.content))
|
|
49130
49137
|
throw new Error("Use create-table for verified native table insertion");
|
|
49138
|
+
validateAppendContent(request.content, request.format);
|
|
49131
49139
|
const markdownHtml = request.format === "markdown" ? appendMarkdownHtml(request.content) : null;
|
|
49132
49140
|
const op = request.format === "plaintext" ? "append-text" : request.format === "markdown" ? "append-markdown" : "append-html";
|
|
49133
49141
|
const text2 = request.format === "html" ? "<div><br></div>" + request.content : "\n\n" + request.content;
|
|
@@ -49674,10 +49682,6 @@ function runDoctor(manager, capabilityMatrix = getCapabilityMatrix) {
|
|
|
49674
49682
|
}
|
|
49675
49683
|
return matrix ? { healthy, checks, runtimeOS: matrix.runtimeOS, features: matrix.features } : { healthy, checks };
|
|
49676
49684
|
}
|
|
49677
|
-
function fdaRemediation(execPath = process.execPath) {
|
|
49678
|
-
const versioned = /\/(\.nvm|\.fnm|\.volta|\.asdf|\.local\/share\/mise|\.nodenv|n\/versions)\//.test(execPath);
|
|
49679
|
-
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}`;
|
|
49680
|
-
}
|
|
49681
49685
|
function markdownBridgeDetail() {
|
|
49682
49686
|
const purpose = 'needed only for create-note format: "markdown" on macOS 26+';
|
|
49683
49687
|
try {
|
|
@@ -49727,14 +49731,15 @@ function formatDoctorReport(r) {
|
|
|
49727
49731
|
|
|
49728
49732
|
// src/services/fileConfig.ts
|
|
49729
49733
|
import { existsSync as existsSync12, readFileSync as readFileSync2 } from "fs";
|
|
49730
|
-
import { join as
|
|
49731
|
-
import { homedir as
|
|
49734
|
+
import { join as join19 } from "path";
|
|
49735
|
+
import { homedir as homedir16 } from "os";
|
|
49732
49736
|
function fileConfigPath(env = process.env) {
|
|
49733
49737
|
const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
|
|
49734
49738
|
if (override && override.trim()) return override.trim();
|
|
49735
|
-
return
|
|
49739
|
+
return join19(homedir16(), "Library", "Application Support", "apple-notes-mcp", "config.json");
|
|
49736
49740
|
}
|
|
49737
49741
|
function loadFileConfig(env = process.env, path10 = fileConfigPath(env)) {
|
|
49742
|
+
if (env.APPLE_NOTES_MCP_BROKERED === "1") return [];
|
|
49738
49743
|
const applied = [];
|
|
49739
49744
|
try {
|
|
49740
49745
|
if (!existsSync12(path10)) return applied;
|
|
@@ -50917,7 +50922,7 @@ function insertLink(request, deps) {
|
|
|
50917
50922
|
|
|
50918
50923
|
// src/services/notesExport.ts
|
|
50919
50924
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
50920
|
-
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";
|
|
50921
50926
|
|
|
50922
50927
|
// src/utils/exportAssets.ts
|
|
50923
50928
|
import {
|
|
@@ -50932,9 +50937,9 @@ import {
|
|
|
50932
50937
|
statSync as statSync3,
|
|
50933
50938
|
writeSync as writeSync3
|
|
50934
50939
|
} from "node:fs";
|
|
50935
|
-
import { homedir as
|
|
50936
|
-
import { basename as basename3, extname as extname3, join as
|
|
50937
|
-
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");
|
|
50938
50943
|
var MAX_EMBED_BYTES = 10 * 1024 * 1024;
|
|
50939
50944
|
var MAX_EMBED_TOTAL_BYTES = 256 * 1024 * 1024;
|
|
50940
50945
|
var MAX_DIRECTORY_ENTRIES = 2e4;
|
|
@@ -50990,11 +50995,11 @@ var AssetLocator = class {
|
|
|
50990
50995
|
constructor(container = NOTES_CONTAINER) {
|
|
50991
50996
|
let accountsRoot = "";
|
|
50992
50997
|
try {
|
|
50993
|
-
accountsRoot = realpathSync3.native(
|
|
50998
|
+
accountsRoot = realpathSync3.native(join20(container, "Accounts"));
|
|
50994
50999
|
} catch {
|
|
50995
51000
|
}
|
|
50996
51001
|
this.accounts = accountsRoot ? listDirectory(accountsRoot, 1e3).flatMap((name) => {
|
|
50997
|
-
const dir = safeComponent2(name) && confine(
|
|
51002
|
+
const dir = safeComponent2(name) && confine(join20(accountsRoot, name), accountsRoot);
|
|
50998
51003
|
return dir && isDirectory2(dir) ? [dir] : [];
|
|
50999
51004
|
}) : [];
|
|
51000
51005
|
}
|
|
@@ -51068,8 +51073,8 @@ var AssetLocator = class {
|
|
|
51068
51073
|
if (!mediaId || !filename) return void 0;
|
|
51069
51074
|
const generation = safeComponent2(source.mediaGeneration);
|
|
51070
51075
|
const candidates = [
|
|
51071
|
-
...generation ? [
|
|
51072
|
-
|
|
51076
|
+
...generation ? [join20(account, "Media", mediaId, generation, filename)] : [],
|
|
51077
|
+
join20(account, "Media", mediaId, filename)
|
|
51073
51078
|
];
|
|
51074
51079
|
for (const candidate of candidates) {
|
|
51075
51080
|
const path10 = confine(candidate, account);
|
|
@@ -51083,7 +51088,7 @@ var AssetLocator = class {
|
|
|
51083
51088
|
* somewhere else.
|
|
51084
51089
|
*/
|
|
51085
51090
|
fallback(account, dir, id2, generation, names) {
|
|
51086
|
-
const base =
|
|
51091
|
+
const base = join20(account, dir, id2);
|
|
51087
51092
|
const gen = safeComponent2(generation);
|
|
51088
51093
|
const generations = [
|
|
51089
51094
|
...gen ? [gen] : [],
|
|
@@ -51092,7 +51097,7 @@ var AssetLocator = class {
|
|
|
51092
51097
|
];
|
|
51093
51098
|
for (const g of generations)
|
|
51094
51099
|
for (const name of names) {
|
|
51095
|
-
const path10 = confine(g ?
|
|
51100
|
+
const path10 = confine(g ? join20(base, g, name) : join20(base, name), account);
|
|
51096
51101
|
if (path10 && isFile(path10)) return { path: path10, stale: Boolean(gen) && g !== gen };
|
|
51097
51102
|
}
|
|
51098
51103
|
return void 0;
|
|
@@ -51102,7 +51107,7 @@ var AssetLocator = class {
|
|
|
51102
51107
|
let index = this.previewIndex.get(account);
|
|
51103
51108
|
if (!index) {
|
|
51104
51109
|
index = /* @__PURE__ */ new Map();
|
|
51105
|
-
for (const name of listDirectory(
|
|
51110
|
+
for (const name of listDirectory(join20(account, "Previews"), MAX_DIRECTORY_ENTRIES)) {
|
|
51106
51111
|
const key = name.slice(0, 36).toUpperCase();
|
|
51107
51112
|
const entries3 = index.get(key) ?? [];
|
|
51108
51113
|
entries3.push(name);
|
|
@@ -51115,7 +51120,7 @@ var AssetLocator = class {
|
|
|
51115
51120
|
return !ext || IMAGE_EXTENSIONS.has(ext) || /^\.\d+$/.test(ext);
|
|
51116
51121
|
}).sort((a, b) => previewArea(b) - previewArea(a) || a.localeCompare(b));
|
|
51117
51122
|
for (const name of entries2) {
|
|
51118
|
-
const entry = confine(
|
|
51123
|
+
const entry = confine(join20(account, "Previews", name), account);
|
|
51119
51124
|
if (!entry) continue;
|
|
51120
51125
|
const file = isFile(entry) ? entry : this.bundleImage(entry, account);
|
|
51121
51126
|
if (file) return { path: file, name: "preview", role: "preview" };
|
|
@@ -51126,12 +51131,12 @@ var AssetLocator = class {
|
|
|
51126
51131
|
bundleImage(bundle, account) {
|
|
51127
51132
|
const found = [];
|
|
51128
51133
|
for (const child2 of listDirectory(bundle, MAX_BUNDLE_ENTRIES2)) {
|
|
51129
|
-
const path10 = confine(
|
|
51134
|
+
const path10 = confine(join20(bundle, child2), account);
|
|
51130
51135
|
if (!path10) continue;
|
|
51131
51136
|
if (isFile(path10)) found.push(path10);
|
|
51132
51137
|
else
|
|
51133
51138
|
for (const grandchild of listDirectory(path10, MAX_BUNDLE_ENTRIES2)) {
|
|
51134
|
-
const inner = confine(
|
|
51139
|
+
const inner = confine(join20(path10, grandchild), account);
|
|
51135
51140
|
if (inner && isFile(inner)) found.push(inner);
|
|
51136
51141
|
}
|
|
51137
51142
|
}
|
|
@@ -51192,11 +51197,11 @@ function canonicalForm(abs) {
|
|
|
51192
51197
|
for (; ; ) {
|
|
51193
51198
|
try {
|
|
51194
51199
|
const real = realpathSync3.native(current);
|
|
51195
|
-
return rest ?
|
|
51200
|
+
return rest ? join20(real, rest) : real;
|
|
51196
51201
|
} catch {
|
|
51197
51202
|
const parent = resolve3(current, "..");
|
|
51198
51203
|
if (parent === current) return abs;
|
|
51199
|
-
rest = rest ?
|
|
51204
|
+
rest = rest ? join20(basename3(current), rest) : basename3(current);
|
|
51200
51205
|
current = parent;
|
|
51201
51206
|
}
|
|
51202
51207
|
}
|
|
@@ -51323,7 +51328,7 @@ var SidecarWriter = class {
|
|
|
51323
51328
|
let target;
|
|
51324
51329
|
let out;
|
|
51325
51330
|
for (let n = 1; n <= 1e3 && out === void 0; n++) {
|
|
51326
|
-
target =
|
|
51331
|
+
target = join20(this.dir, n === 1 ? safe2 : `${stem}-${n}${ext}`);
|
|
51327
51332
|
try {
|
|
51328
51333
|
out = openSync4(target, CREATE_FLAGS, 420);
|
|
51329
51334
|
} catch (error2) {
|
|
@@ -51995,8 +52000,8 @@ import {
|
|
|
51995
52000
|
rmSync as rmSync5,
|
|
51996
52001
|
writeFileSync as writeFileSync4
|
|
51997
52002
|
} from "node:fs";
|
|
51998
|
-
import { homedir as
|
|
51999
|
-
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";
|
|
52000
52005
|
import { fileURLToPath } from "node:url";
|
|
52001
52006
|
var PUBLIC_HELPER_PROTOCOL = 1;
|
|
52002
52007
|
var PUBLIC_HELPER_DIR_ENV = "APPLE_NOTES_MCP_PUBLIC_HELPER_DIR";
|
|
@@ -52025,7 +52030,7 @@ var publicManifestSchema = external_exports.object({
|
|
|
52025
52030
|
function packageRoot(fromDir = dirname6(fileURLToPath(import.meta.url))) {
|
|
52026
52031
|
let dir = fromDir;
|
|
52027
52032
|
for (; ; ) {
|
|
52028
|
-
const candidate =
|
|
52033
|
+
const candidate = join21(dir, "package.json");
|
|
52029
52034
|
if (existsSync13(candidate)) {
|
|
52030
52035
|
try {
|
|
52031
52036
|
if (JSON.parse(readFileSync3(candidate, "utf8")).name === "apple-notes-mcp")
|
|
@@ -52042,7 +52047,7 @@ function defaultPublicHelperDeps(overrides = {}) {
|
|
|
52042
52047
|
return {
|
|
52043
52048
|
env: process.env,
|
|
52044
52049
|
platform: process.platform,
|
|
52045
|
-
sourcePath:
|
|
52050
|
+
sourcePath: join21(packageRoot(), PUBLIC_HELPER_SOURCE),
|
|
52046
52051
|
exists: existsSync13,
|
|
52047
52052
|
readFile: (path10) => readFileSync3(path10),
|
|
52048
52053
|
spawn: spawnSync2,
|
|
@@ -52053,14 +52058,14 @@ function defaultPublicHelperDeps(overrides = {}) {
|
|
|
52053
52058
|
function publicHelperInstallDir(env = process.env) {
|
|
52054
52059
|
const override = env[PUBLIC_HELPER_DIR_ENV]?.trim();
|
|
52055
52060
|
if (override) return override;
|
|
52056
|
-
return
|
|
52061
|
+
return join21(homedir18(), "Library", "Application Support", "apple-notes-mcp", "public-helper");
|
|
52057
52062
|
}
|
|
52058
52063
|
function sha256Hex(data) {
|
|
52059
52064
|
return createHash2("sha256").update(data).digest("hex");
|
|
52060
52065
|
}
|
|
52061
52066
|
function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
|
|
52062
52067
|
const installDir = publicHelperInstallDir(deps.env);
|
|
52063
|
-
const binaryPath =
|
|
52068
|
+
const binaryPath = join21(installDir, PUBLIC_HELPER_BINARY);
|
|
52064
52069
|
const base = { installDir, binaryPath, sourcePath: deps.sourcePath, manifest: null };
|
|
52065
52070
|
const fail = (reason, detail) => ({
|
|
52066
52071
|
...base,
|
|
@@ -52071,7 +52076,7 @@ function inspectPublicHelper(deps = defaultPublicHelperDeps()) {
|
|
|
52071
52076
|
if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
|
|
52072
52077
|
if (!deps.exists(deps.sourcePath))
|
|
52073
52078
|
return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
|
|
52074
|
-
const manifestPath =
|
|
52079
|
+
const manifestPath = join21(installDir, PUBLIC_HELPER_MANIFEST);
|
|
52075
52080
|
if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
|
|
52076
52081
|
return fail(
|
|
52077
52082
|
"helper_not_installed",
|
|
@@ -52348,11 +52353,11 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
|
|
|
52348
52353
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
52349
52354
|
const installDir = publicHelperInstallDir(deps.env);
|
|
52350
52355
|
mkdirSync5(installDir, { recursive: true, mode: 448 });
|
|
52351
|
-
const staging = mkdtempSync5(
|
|
52356
|
+
const staging = mkdtempSync5(join21(installDir, ".staging-"));
|
|
52352
52357
|
try {
|
|
52353
|
-
const stagedBinary =
|
|
52354
|
-
const digestPath =
|
|
52355
|
-
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");
|
|
52356
52361
|
writeFileSync4(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
|
|
52357
52362
|
writeFileSync4(plistPath, publicHelperInfoPlist(), { mode: 384 });
|
|
52358
52363
|
const compile = deps.spawn(
|
|
@@ -52415,9 +52420,9 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
|
|
|
52415
52420
|
compiler
|
|
52416
52421
|
};
|
|
52417
52422
|
chmodSync(stagedBinary, 448);
|
|
52418
|
-
renameSync(stagedBinary,
|
|
52423
|
+
renameSync(stagedBinary, join21(installDir, PUBLIC_HELPER_BINARY));
|
|
52419
52424
|
writeFileSync4(
|
|
52420
|
-
|
|
52425
|
+
join21(installDir, PUBLIC_HELPER_MANIFEST),
|
|
52421
52426
|
JSON.stringify(manifest, null, 2) + "\n",
|
|
52422
52427
|
{ mode: 384 }
|
|
52423
52428
|
);
|
|
@@ -52869,10 +52874,10 @@ function renderNotesMarkdown(notes, ctx, { wrap: wrap3 = 0 } = {}) {
|
|
|
52869
52874
|
|
|
52870
52875
|
// src/utils/noteExportData.ts
|
|
52871
52876
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
52872
|
-
import { homedir as
|
|
52873
|
-
import { join as
|
|
52874
|
-
var NOTES_DB_PATH11 =
|
|
52875
|
-
|
|
52877
|
+
import { homedir as homedir19 } from "node:os";
|
|
52878
|
+
import { join as join22 } from "node:path";
|
|
52879
|
+
var NOTES_DB_PATH11 = join22(
|
|
52880
|
+
homedir19(),
|
|
52876
52881
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
52877
52882
|
);
|
|
52878
52883
|
var IMAGE_UTIS2 = /* @__PURE__ */ new Set([
|
|
@@ -53183,16 +53188,16 @@ var NOTE_PLACEHOLDERS = [
|
|
|
53183
53188
|
"exportStem"
|
|
53184
53189
|
];
|
|
53185
53190
|
var PLACEHOLDER_MODIFIERS = ["raw", "yaml"];
|
|
53186
|
-
var wrap2 = (before, after = "",
|
|
53191
|
+
var wrap2 = (before, after = "", join35) => ({
|
|
53187
53192
|
mode: "wrap",
|
|
53188
53193
|
before,
|
|
53189
53194
|
after,
|
|
53190
|
-
...
|
|
53195
|
+
...join35 ? { join: join35 } : {}
|
|
53191
53196
|
});
|
|
53192
|
-
var pattern = (value,
|
|
53197
|
+
var pattern = (value, join35) => ({
|
|
53193
53198
|
mode: "pattern",
|
|
53194
53199
|
value,
|
|
53195
|
-
...
|
|
53200
|
+
...join35 ? { join: join35 } : {}
|
|
53196
53201
|
});
|
|
53197
53202
|
var STANDARD = {
|
|
53198
53203
|
schemaVersion: 1,
|
|
@@ -54080,7 +54085,7 @@ import {
|
|
|
54080
54085
|
unlinkSync as unlinkSync3,
|
|
54081
54086
|
writeSync as writeSync4
|
|
54082
54087
|
} from "node:fs";
|
|
54083
|
-
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";
|
|
54084
54089
|
var CHUNK = 1024 * 1024;
|
|
54085
54090
|
function openRegular(path10) {
|
|
54086
54091
|
const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
@@ -54151,7 +54156,7 @@ var HashedSidecarWriter = class {
|
|
|
54151
54156
|
if (directoryError) return { error: directoryError };
|
|
54152
54157
|
const name = safeAssetName(asset.name, mime);
|
|
54153
54158
|
const ext = extname4(name);
|
|
54154
|
-
const target =
|
|
54159
|
+
const target = join23(
|
|
54155
54160
|
this.dir,
|
|
54156
54161
|
`${name.slice(0, name.length - ext.length)}-${hash.slice(0, 8)}${ext}`
|
|
54157
54162
|
);
|
|
@@ -54188,7 +54193,7 @@ var HashedSidecarWriter = class {
|
|
|
54188
54193
|
* remove it if the copy fails.
|
|
54189
54194
|
*/
|
|
54190
54195
|
copy(source, target) {
|
|
54191
|
-
const temp =
|
|
54196
|
+
const temp = join23(this.dir, `.asset-${randomBytes(6).toString("hex")}.tmp`);
|
|
54192
54197
|
writeNew(source, temp);
|
|
54193
54198
|
try {
|
|
54194
54199
|
linkSync(temp, target);
|
|
@@ -54455,7 +54460,7 @@ function exportNotesMarkdown(request, deps) {
|
|
|
54455
54460
|
return { ...receipt, markdown };
|
|
54456
54461
|
}
|
|
54457
54462
|
function defaultSidecarDir(output) {
|
|
54458
|
-
return
|
|
54463
|
+
return join24(dirname7(output), `${basename4(output, extname5(output))}.assets`);
|
|
54459
54464
|
}
|
|
54460
54465
|
function exportNotesHtml(request, deps) {
|
|
54461
54466
|
if (!request.outputPath)
|
|
@@ -54702,7 +54707,7 @@ import {
|
|
|
54702
54707
|
writeFileSync as writeFileSync6
|
|
54703
54708
|
} from "node:fs";
|
|
54704
54709
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
54705
|
-
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";
|
|
54706
54711
|
|
|
54707
54712
|
// src/utils/pasteboardFreeze.ts
|
|
54708
54713
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
@@ -54718,7 +54723,7 @@ import {
|
|
|
54718
54723
|
writeFileSync as writeFileSync5
|
|
54719
54724
|
} from "node:fs";
|
|
54720
54725
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
54721
|
-
import { basename as basename5, extname as extname6, join as
|
|
54726
|
+
import { basename as basename5, extname as extname6, join as join25 } from "node:path";
|
|
54722
54727
|
var PASTEBOARD_NAME_ENV = "APPLE_NOTES_MCP_PASTEBOARD_NAME";
|
|
54723
54728
|
var MAX_PASTEBOARD_BYTES = 64 * 1024 * 1024;
|
|
54724
54729
|
var PASTEBOARD_DATA_TYPES = [
|
|
@@ -54894,7 +54899,7 @@ function readRegularFile(path10) {
|
|
|
54894
54899
|
}
|
|
54895
54900
|
}
|
|
54896
54901
|
function freezePasteboard(options = {}) {
|
|
54897
|
-
const directory = mkdtempSync6(
|
|
54902
|
+
const directory = mkdtempSync6(join25(tmpdir5(), "notes-pasteboard-"));
|
|
54898
54903
|
const cleanup = () => rmSync6(directory, { recursive: true, force: true });
|
|
54899
54904
|
try {
|
|
54900
54905
|
const prefs = JSON.stringify(PASTEBOARD_DATA_TYPES.map((t) => [t.type, t.ext]));
|
|
@@ -54923,15 +54928,15 @@ function freezePasteboard(options = {}) {
|
|
|
54923
54928
|
if (reply.kind === "file") {
|
|
54924
54929
|
const bytes = readRegularFile(reply.path);
|
|
54925
54930
|
const filename2 = basename5(reply.path);
|
|
54926
|
-
const path11 =
|
|
54931
|
+
const path11 = join25(directory, filename2);
|
|
54927
54932
|
writeFileSync5(path11, bytes, { mode: 384 });
|
|
54928
54933
|
return { kind: "file", type: reply.type, path: path11, filename: filename2, bytes: bytes.length, cleanup };
|
|
54929
54934
|
}
|
|
54930
|
-
if (
|
|
54935
|
+
if (join25(directory, basename5(reply.path)) !== reply.path)
|
|
54931
54936
|
throw new PasteboardError("write_failed", MESSAGES.write_failed);
|
|
54932
54937
|
const label = PASTEBOARD_DATA_TYPES.find((t) => t.type === reply.type)?.label ?? "item";
|
|
54933
54938
|
const filename = `Pasted ${label}${extname6(reply.path)}`;
|
|
54934
|
-
const path10 =
|
|
54939
|
+
const path10 = join25(directory, filename);
|
|
54935
54940
|
writeFileSync5(path10, readFileSync4(reply.path), { mode: 384 });
|
|
54936
54941
|
rmSync6(reply.path, { force: true });
|
|
54937
54942
|
return { kind: "data", type: reply.type, path: path10, filename, bytes: statSync4(path10).size, cleanup };
|
|
@@ -55290,8 +55295,8 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
|
|
|
55290
55295
|
const bytes = localAttachment(path10);
|
|
55291
55296
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
55292
55297
|
const beforeStored = storedAttachmentIds(manager, id2);
|
|
55293
|
-
const directory = mkdtempSync7(
|
|
55294
|
-
const temporaryFile =
|
|
55298
|
+
const directory = mkdtempSync7(join26(tmpdir6(), "notes-attachment-add-"));
|
|
55299
|
+
const temporaryFile = join26(directory, name);
|
|
55295
55300
|
try {
|
|
55296
55301
|
writeFileSync6(temporaryFile, bytes, { mode: 384 });
|
|
55297
55302
|
if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
|
|
@@ -55320,7 +55325,7 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
|
|
|
55320
55325
|
if (inserted.length === 1 && !(persistentReturnedId && returnedId !== inserted[0].id)) {
|
|
55321
55326
|
attachmentId = inserted[0].id;
|
|
55322
55327
|
reportedName = inserted[0].name;
|
|
55323
|
-
const verifyPath =
|
|
55328
|
+
const verifyPath = join26(directory, "verify", "attachment.bin");
|
|
55324
55329
|
const saved = manager.saveAttachmentById(id2, attachmentId, verifyPath);
|
|
55325
55330
|
if (!saved.success || !fileMatches(saved.savedPath ?? verifyPath, bytes.length, sha256(bytes)))
|
|
55326
55331
|
throw new Error("Attachment bytes were not verified; read the exact note before retrying");
|
|
@@ -55328,7 +55333,7 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
|
|
|
55328
55333
|
const stored = inserted.length === 0 && beforeStored ? storedInsertion(manager, id2, beforeStored, bytes, name, returnedId) : null;
|
|
55329
55334
|
if (!stored)
|
|
55330
55335
|
throw new Error(
|
|
55331
|
-
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
|
|
55332
55337
|
);
|
|
55333
55338
|
attachmentId = stored.attachmentId;
|
|
55334
55339
|
reportedName = stored.name;
|
|
@@ -55442,7 +55447,7 @@ function classify(error2) {
|
|
|
55442
55447
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
55443
55448
|
if (/authorization denied|unable to open database|not authorized/i.test(message)) {
|
|
55444
55449
|
return new FolderStoreError(
|
|
55445
|
-
|
|
55450
|
+
`Full Disk Access is required to verify folder type and contents before deleting. ${fdaRemediation()}`,
|
|
55446
55451
|
"no_fda"
|
|
55447
55452
|
);
|
|
55448
55453
|
}
|
|
@@ -55717,8 +55722,8 @@ import {
|
|
|
55717
55722
|
unlinkSync as unlinkSync4,
|
|
55718
55723
|
writeSync as writeSync5
|
|
55719
55724
|
} from "node:fs";
|
|
55720
|
-
import { homedir as
|
|
55721
|
-
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";
|
|
55722
55727
|
var TEMPLATE_SLUG = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
55723
55728
|
var TemplateStoreError = class extends Error {
|
|
55724
55729
|
constructor(code, message) {
|
|
@@ -55738,7 +55743,7 @@ function templateDir(env = process.env) {
|
|
|
55738
55743
|
);
|
|
55739
55744
|
return resolve6(override);
|
|
55740
55745
|
}
|
|
55741
|
-
return
|
|
55746
|
+
return join28(homedir21(), "Library/Application Support/apple-notes-mcp/templates");
|
|
55742
55747
|
}
|
|
55743
55748
|
function checkName(name, action) {
|
|
55744
55749
|
if (isBuiltinTemplate(name) && action !== "read")
|
|
@@ -55775,7 +55780,7 @@ var TemplateStore = class {
|
|
|
55775
55780
|
return this.dir;
|
|
55776
55781
|
}
|
|
55777
55782
|
file(name) {
|
|
55778
|
-
return
|
|
55783
|
+
return join28(this.dir, `${name}.json`);
|
|
55779
55784
|
}
|
|
55780
55785
|
/**
|
|
55781
55786
|
* Read a saved template's text, refusing symlinks and non-regular files.
|
|
@@ -55901,7 +55906,7 @@ var TemplateStore = class {
|
|
|
55901
55906
|
"template-exists",
|
|
55902
55907
|
`A template named "${name}" already exists. Pass force: true to replace it.`
|
|
55903
55908
|
);
|
|
55904
|
-
const temp =
|
|
55909
|
+
const temp = join28(this.dir, `.${name}.${randomBytes2(6).toString("hex")}.tmp`);
|
|
55905
55910
|
const fd = openSync8(
|
|
55906
55911
|
temp,
|
|
55907
55912
|
constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
|
|
@@ -57120,7 +57125,7 @@ function parseXml(source, limits) {
|
|
|
57120
57125
|
(own ??= /* @__PURE__ */ new Map()).set(prefix, a.value);
|
|
57121
57126
|
}
|
|
57122
57127
|
const scope2 = own ? { own, parent: parentScope } : parentScope ?? { own: null, parent: null };
|
|
57123
|
-
const
|
|
57128
|
+
const resolve11 = (qualified, isAttribute) => {
|
|
57124
57129
|
const colon = qualified.indexOf(":");
|
|
57125
57130
|
if (colon < 0)
|
|
57126
57131
|
return {
|
|
@@ -57134,12 +57139,12 @@ function parseXml(source, limits) {
|
|
|
57134
57139
|
if (ns === void 0) fail(`Undeclared namespace prefix "${prefix.slice(0, 40)}"`);
|
|
57135
57140
|
return { local, ns };
|
|
57136
57141
|
};
|
|
57137
|
-
const resolved =
|
|
57142
|
+
const resolved = resolve11(name, false);
|
|
57138
57143
|
const element = {
|
|
57139
57144
|
name,
|
|
57140
57145
|
local: resolved.local,
|
|
57141
57146
|
ns: resolved.ns,
|
|
57142
|
-
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) })),
|
|
57143
57148
|
children: [],
|
|
57144
57149
|
text: "",
|
|
57145
57150
|
line: tagLine
|
|
@@ -58312,6 +58317,897 @@ function registerNativeTagsBridge(server2, manager) {
|
|
|
58312
58317
|
);
|
|
58313
58318
|
}
|
|
58314
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
|
+
|
|
58315
59211
|
// src/tools/nativeOperations.ts
|
|
58316
59212
|
var VERIFIED_BACKGROUND = /* @__PURE__ */ new Set([
|
|
58317
59213
|
"append-native",
|
|
@@ -58530,7 +59426,9 @@ function registerNativeOperations(server2, manager) {
|
|
|
58530
59426
|
])
|
|
58531
59427
|
),
|
|
58532
59428
|
unavailable: UNAVAILABLE,
|
|
58533
|
-
...getCapabilityMatrix()
|
|
59429
|
+
...getCapabilityMatrix(),
|
|
59430
|
+
// Whether this process runs under the permission broker (#220).
|
|
59431
|
+
broker: brokerStatus()
|
|
58534
59432
|
};
|
|
58535
59433
|
},
|
|
58536
59434
|
true
|
|
@@ -58758,10 +59656,10 @@ function registerNativeOperations(server2, manager) {
|
|
|
58758
59656
|
}
|
|
58759
59657
|
|
|
58760
59658
|
// src/setupShortcuts.ts
|
|
58761
|
-
import { spawnSync as
|
|
58762
|
-
import { existsSync as
|
|
59659
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
59660
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
58763
59661
|
import { release as release3 } from "node:os";
|
|
58764
|
-
import { dirname as dirname8, resolve as
|
|
59662
|
+
import { dirname as dirname8, resolve as resolve8 } from "node:path";
|
|
58765
59663
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
58766
59664
|
var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
|
|
58767
59665
|
var MARKDOWN_MIN_DARWIN_MAJOR = 25;
|
|
@@ -58779,16 +59677,16 @@ var shortcutFiles = [
|
|
|
58779
59677
|
];
|
|
58780
59678
|
function setupShortcuts(checkOnly, dependencies = {}) {
|
|
58781
59679
|
const status = dependencies.status || nativeTagsStatus;
|
|
58782
|
-
const exists = dependencies.exists ||
|
|
59680
|
+
const exists = dependencies.exists || existsSync15;
|
|
58783
59681
|
const open = dependencies.open || ((path10) => {
|
|
58784
|
-
const result =
|
|
59682
|
+
const result = spawnSync4("/usr/bin/open", [path10], { encoding: "utf8" });
|
|
58785
59683
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
58786
59684
|
});
|
|
58787
|
-
const baseDirectory = dependencies.baseDirectory ||
|
|
59685
|
+
const baseDirectory = dependencies.baseDirectory || resolve8(dirname8(fileURLToPath2(import.meta.url)), "../shortcuts");
|
|
58788
59686
|
const osRelease = (dependencies.osRelease || release3)();
|
|
58789
59687
|
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
58790
59688
|
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
58791
|
-
const path10 =
|
|
59689
|
+
const path10 = resolve8(baseDirectory, file);
|
|
58792
59690
|
let installed = false;
|
|
58793
59691
|
let identifier;
|
|
58794
59692
|
let error2;
|
|
@@ -58848,7 +59746,7 @@ function formatShortcutSetup(report) {
|
|
|
58848
59746
|
|
|
58849
59747
|
// src/utils/noteAudio.ts
|
|
58850
59748
|
import { readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
|
|
58851
|
-
import { join as
|
|
59749
|
+
import { join as join30 } from "node:path";
|
|
58852
59750
|
var EXTRA_AUDIO_UTIS = [
|
|
58853
59751
|
"public.mp3",
|
|
58854
59752
|
"public.aiff-audio",
|
|
@@ -58873,7 +59771,7 @@ function accountDirsFor(containerDir, accountIdentifier) {
|
|
|
58873
59771
|
if (own) return [own];
|
|
58874
59772
|
let names;
|
|
58875
59773
|
try {
|
|
58876
|
-
names = readdirSync5(
|
|
59774
|
+
names = readdirSync5(join30(containerDir, "Accounts")).sort();
|
|
58877
59775
|
} catch {
|
|
58878
59776
|
return [];
|
|
58879
59777
|
}
|
|
@@ -58885,8 +59783,8 @@ function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_
|
|
|
58885
59783
|
if (!id2 || !filename) return null;
|
|
58886
59784
|
const generation = safeComponent(media?.generation);
|
|
58887
59785
|
for (const dir of accountDirsFor(containerDir, accountIdentifier)) {
|
|
58888
|
-
const base =
|
|
58889
|
-
const candidates = generation ? [
|
|
59786
|
+
const base = join30(dir, "Media", id2);
|
|
59787
|
+
const candidates = generation ? [join30(base, generation, filename), join30(base, filename)] : [join30(base, filename)];
|
|
58890
59788
|
for (const candidate of candidates) {
|
|
58891
59789
|
const real = realInside(candidate, dir);
|
|
58892
59790
|
if (real && isFile2(real)) return real;
|
|
@@ -59157,25 +60055,25 @@ function formatTranscription(result) {
|
|
|
59157
60055
|
}
|
|
59158
60056
|
|
|
59159
60057
|
// src/services/privateHelperBuild.ts
|
|
59160
|
-
import { spawnSync as
|
|
60058
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
59161
60059
|
import {
|
|
59162
|
-
chmodSync as
|
|
59163
|
-
existsSync as
|
|
59164
|
-
mkdirSync as
|
|
59165
|
-
mkdtempSync as
|
|
59166
|
-
renameSync as
|
|
59167
|
-
rmSync as
|
|
59168
|
-
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
|
|
59169
60067
|
} from "node:fs";
|
|
59170
60068
|
import { release as release4 } from "node:os";
|
|
59171
|
-
import { join as
|
|
60069
|
+
import { join as join32 } from "node:path";
|
|
59172
60070
|
|
|
59173
60071
|
// src/services/privateHelper.ts
|
|
59174
|
-
import { spawnSync as
|
|
60072
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
59175
60073
|
import { createHash as createHash7 } from "node:crypto";
|
|
59176
|
-
import { existsSync as
|
|
59177
|
-
import { homedir as
|
|
59178
|
-
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";
|
|
59179
60077
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
59180
60078
|
var PRIVATE_HELPER_PROTOCOL = 1;
|
|
59181
60079
|
var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
|
|
@@ -59203,16 +60101,16 @@ var manifestSchema = external_exports.object({
|
|
|
59203
60101
|
function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
|
|
59204
60102
|
let dir = fromDir;
|
|
59205
60103
|
for (; ; ) {
|
|
59206
|
-
const candidate =
|
|
59207
|
-
if (
|
|
60104
|
+
const candidate = join31(dir, "package.json");
|
|
60105
|
+
if (existsSync16(candidate)) {
|
|
59208
60106
|
try {
|
|
59209
|
-
const pkg = JSON.parse(
|
|
60107
|
+
const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
|
|
59210
60108
|
if (pkg.name === "apple-notes-mcp") return dir;
|
|
59211
60109
|
} catch {
|
|
59212
60110
|
}
|
|
59213
60111
|
}
|
|
59214
60112
|
const parent = dirname9(dir);
|
|
59215
|
-
if (parent === dir) return
|
|
60113
|
+
if (parent === dir) return resolve9(fromDir, "..");
|
|
59216
60114
|
dir = parent;
|
|
59217
60115
|
}
|
|
59218
60116
|
}
|
|
@@ -59220,10 +60118,10 @@ function defaultDeps2(overrides = {}) {
|
|
|
59220
60118
|
return {
|
|
59221
60119
|
env: process.env,
|
|
59222
60120
|
platform: process.platform,
|
|
59223
|
-
sourcePath:
|
|
59224
|
-
exists:
|
|
59225
|
-
readFile: (path10) =>
|
|
59226
|
-
spawn:
|
|
60121
|
+
sourcePath: join31(packageRoot2(), HELPER_SOURCE_RELATIVE),
|
|
60122
|
+
exists: existsSync16,
|
|
60123
|
+
readFile: (path10) => readFileSync6(path10),
|
|
60124
|
+
spawn: spawnSync5,
|
|
59227
60125
|
...overrides
|
|
59228
60126
|
};
|
|
59229
60127
|
}
|
|
@@ -59233,14 +60131,14 @@ function privateHelperEnabled(env = process.env) {
|
|
|
59233
60131
|
function helperInstallDir(env = process.env) {
|
|
59234
60132
|
const override = env[HELPER_DIR_ENV]?.trim();
|
|
59235
60133
|
if (override) return override;
|
|
59236
|
-
return
|
|
60134
|
+
return join31(homedir23(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
|
|
59237
60135
|
}
|
|
59238
60136
|
function sha256Hex2(data) {
|
|
59239
60137
|
return createHash7("sha256").update(data).digest("hex");
|
|
59240
60138
|
}
|
|
59241
60139
|
function inspectInstallation(deps = defaultDeps2()) {
|
|
59242
60140
|
const installDir = helperInstallDir(deps.env);
|
|
59243
|
-
const binaryPath =
|
|
60141
|
+
const binaryPath = join31(installDir, HELPER_BINARY_NAME);
|
|
59244
60142
|
const base = {
|
|
59245
60143
|
installDir,
|
|
59246
60144
|
binaryPath,
|
|
@@ -59258,7 +60156,7 @@ function inspectInstallation(deps = defaultDeps2()) {
|
|
|
59258
60156
|
if (!deps.exists(deps.sourcePath))
|
|
59259
60157
|
return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
|
|
59260
60158
|
base.expectedSourceSha256 = sha256Hex2(deps.readFile(deps.sourcePath));
|
|
59261
|
-
const manifestPath =
|
|
60159
|
+
const manifestPath = join31(installDir, MANIFEST_NAME);
|
|
59262
60160
|
if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
|
|
59263
60161
|
return fail(
|
|
59264
60162
|
"helper_not_installed",
|
|
@@ -59516,7 +60414,7 @@ function defaultBuildDeps() {
|
|
|
59516
60414
|
return {
|
|
59517
60415
|
...defaultDeps2(),
|
|
59518
60416
|
osVersion: () => {
|
|
59519
|
-
const r =
|
|
60417
|
+
const r = spawnSync6("/usr/bin/sw_vers", ["-productVersion"], { encoding: "utf8" });
|
|
59520
60418
|
return r.status === 0 ? r.stdout.trim() : `Darwin ${release4()}`;
|
|
59521
60419
|
},
|
|
59522
60420
|
now: () => /* @__PURE__ */ new Date()
|
|
@@ -59586,10 +60484,10 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
|
|
|
59586
60484
|
const compiler = String(clangVersion.stdout || "").split("\n")[0] || "clang";
|
|
59587
60485
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
59588
60486
|
const installDir = helperInstallDir(deps.env);
|
|
59589
|
-
|
|
59590
|
-
const staging =
|
|
60487
|
+
mkdirSync10(installDir, { recursive: true, mode: 448 });
|
|
60488
|
+
const staging = mkdtempSync9(join32(installDir, ".staging-"));
|
|
59591
60489
|
try {
|
|
59592
|
-
const stagedBinary =
|
|
60490
|
+
const stagedBinary = join32(staging, HELPER_BINARY_NAME);
|
|
59593
60491
|
const compile = deps.spawn(
|
|
59594
60492
|
"/usr/bin/xcrun",
|
|
59595
60493
|
compileArguments(deps.sourcePath, stagedBinary, sourceSha),
|
|
@@ -59665,14 +60563,14 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
|
|
|
59665
60563
|
osVersion: deps.osVersion(),
|
|
59666
60564
|
compiler
|
|
59667
60565
|
};
|
|
59668
|
-
|
|
59669
|
-
|
|
59670
|
-
|
|
60566
|
+
chmodSync3(stagedBinary, 448);
|
|
60567
|
+
renameSync4(stagedBinary, join32(installDir, HELPER_BINARY_NAME));
|
|
60568
|
+
writeFileSync8(join32(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
|
|
59671
60569
|
mode: 384
|
|
59672
60570
|
});
|
|
59673
60571
|
steps.push({ step: "install", ok: true, detail: installDir });
|
|
59674
60572
|
} finally {
|
|
59675
|
-
if (
|
|
60573
|
+
if (existsSync17(staging)) rmSync9(staging, { recursive: true, force: true });
|
|
59676
60574
|
}
|
|
59677
60575
|
const installation = inspectInstallation(deps);
|
|
59678
60576
|
steps.push({
|
|
@@ -59701,7 +60599,7 @@ function formatHelperBuild(report) {
|
|
|
59701
60599
|
}
|
|
59702
60600
|
|
|
59703
60601
|
// src/services/permissions.ts
|
|
59704
|
-
import { spawnSync as
|
|
60602
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
59705
60603
|
import { createInterface } from "node:readline";
|
|
59706
60604
|
var PRIVACY = "System Settings > Privacy & Security";
|
|
59707
60605
|
var PANE_ANCHORS = {
|
|
@@ -59737,7 +60635,7 @@ function checkPermissions(probes = defaultPermissionProbes(), options = defaultC
|
|
|
59737
60635
|
const launchingApp = safe(probes.launchingApp, null);
|
|
59738
60636
|
const who = launchingApp ?? "the app that launches the server";
|
|
59739
60637
|
const items = [
|
|
59740
|
-
fullDiskAccessItem(probes, macOSVersion),
|
|
60638
|
+
fullDiskAccessItem(probes, macOSVersion, options.env),
|
|
59741
60639
|
automationItem(probes, macOSVersion, who, automationSkipReason(options)),
|
|
59742
60640
|
shortcutsItem(probes),
|
|
59743
60641
|
speechItem(probes, macOSVersion, who)
|
|
@@ -59760,7 +60658,7 @@ function safe(fn, fallback) {
|
|
|
59760
60658
|
function errorText(error2) {
|
|
59761
60659
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
59762
60660
|
}
|
|
59763
|
-
function fullDiskAccessItem(probes, macOSVersion) {
|
|
60661
|
+
function fullDiskAccessItem(probes, macOSVersion, env) {
|
|
59764
60662
|
const base = {
|
|
59765
60663
|
id: "fullDiskAccess",
|
|
59766
60664
|
title: "Full Disk Access",
|
|
@@ -59775,7 +60673,7 @@ function fullDiskAccessItem(probes, macOSVersion) {
|
|
|
59775
60673
|
...base,
|
|
59776
60674
|
status: "unknown",
|
|
59777
60675
|
detail: `could not probe the Notes database: ${errorText(error2)}`,
|
|
59778
|
-
fix: fdaRemediation(probes.execPath)
|
|
60676
|
+
fix: fdaRemediation(probes.execPath, env)
|
|
59779
60677
|
};
|
|
59780
60678
|
}
|
|
59781
60679
|
return granted ? {
|
|
@@ -59787,7 +60685,7 @@ function fullDiskAccessItem(probes, macOSVersion) {
|
|
|
59787
60685
|
...base,
|
|
59788
60686
|
status: "missing",
|
|
59789
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",
|
|
59790
|
-
fix: fdaRemediation(probes.execPath)
|
|
60688
|
+
fix: fdaRemediation(probes.execPath, env)
|
|
59791
60689
|
};
|
|
59792
60690
|
}
|
|
59793
60691
|
var PROBE_AUTOMATION_COMMAND = "apple-notes-mcp setup --permissions --probe-automation";
|
|
@@ -59945,7 +60843,7 @@ function findLaunchingApp(startPid = process.ppid, readProcess = readProcessEntr
|
|
|
59945
60843
|
return null;
|
|
59946
60844
|
}
|
|
59947
60845
|
function readProcessEntry(pid) {
|
|
59948
|
-
const result =
|
|
60846
|
+
const result = spawnSync7("/bin/ps", ["-o", "ppid=", "-o", "comm=", "-p", String(pid)], {
|
|
59949
60847
|
encoding: "utf8",
|
|
59950
60848
|
timeout: 3e3
|
|
59951
60849
|
});
|
|
@@ -59986,7 +60884,7 @@ function openSettingsPane(item, open = openUrl) {
|
|
|
59986
60884
|
return open(item.settingsUrl);
|
|
59987
60885
|
}
|
|
59988
60886
|
function openUrl(url) {
|
|
59989
|
-
const result =
|
|
60887
|
+
const result = spawnSync7("/usr/bin/open", [url], { encoding: "utf8" });
|
|
59990
60888
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
59991
60889
|
}
|
|
59992
60890
|
function pendingItems(report) {
|
|
@@ -60098,19 +60996,19 @@ function defaultPermissionsCliDeps(options) {
|
|
|
60098
60996
|
}
|
|
60099
60997
|
|
|
60100
60998
|
// src/services/permissionsWindow.ts
|
|
60101
|
-
import { spawn as spawn2, spawnSync as
|
|
60999
|
+
import { spawn as spawn2, spawnSync as spawnSync8 } from "node:child_process";
|
|
60102
61000
|
import {
|
|
60103
|
-
chmodSync as
|
|
60104
|
-
existsSync as
|
|
60105
|
-
mkdirSync as
|
|
60106
|
-
mkdtempSync as
|
|
60107
|
-
readFileSync as
|
|
60108
|
-
renameSync as
|
|
60109
|
-
rmSync as
|
|
60110
|
-
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
|
|
60111
61009
|
} from "node:fs";
|
|
60112
|
-
import { homedir as
|
|
60113
|
-
import { join as
|
|
61010
|
+
import { homedir as homedir24 } from "node:os";
|
|
61011
|
+
import { join as join33 } from "node:path";
|
|
60114
61012
|
import { createInterface as createInterface2 } from "node:readline";
|
|
60115
61013
|
var PERMISSIONS_WINDOW_PROTOCOL = 1;
|
|
60116
61014
|
var PERMISSIONS_WINDOW_DIR_ENV = "APPLE_NOTES_MCP_PERMISSIONS_WINDOW_DIR";
|
|
@@ -60131,10 +61029,10 @@ function defaultPermissionsWindowDeps(overrides = {}) {
|
|
|
60131
61029
|
return {
|
|
60132
61030
|
env: process.env,
|
|
60133
61031
|
platform: process.platform,
|
|
60134
|
-
sourcePath:
|
|
60135
|
-
exists:
|
|
60136
|
-
readFile: (path10) =>
|
|
60137
|
-
spawn:
|
|
61032
|
+
sourcePath: join33(packageRoot(), PERMISSIONS_WINDOW_SOURCE),
|
|
61033
|
+
exists: existsSync18,
|
|
61034
|
+
readFile: (path10) => readFileSync7(path10),
|
|
61035
|
+
spawn: spawnSync8,
|
|
60138
61036
|
now: () => /* @__PURE__ */ new Date(),
|
|
60139
61037
|
...overrides
|
|
60140
61038
|
};
|
|
@@ -60142,17 +61040,17 @@ function defaultPermissionsWindowDeps(overrides = {}) {
|
|
|
60142
61040
|
function permissionsWindowInstallDir(env = process.env) {
|
|
60143
61041
|
const override = env[PERMISSIONS_WINDOW_DIR_ENV]?.trim();
|
|
60144
61042
|
if (override) return override;
|
|
60145
|
-
return
|
|
61043
|
+
return join33(homedir24(), "Library", "Application Support", "apple-notes-mcp", "permissions-window");
|
|
60146
61044
|
}
|
|
60147
61045
|
function inspectPermissionsWindow(deps = defaultPermissionsWindowDeps()) {
|
|
60148
61046
|
const installDir = permissionsWindowInstallDir(deps.env);
|
|
60149
|
-
const binaryPath =
|
|
61047
|
+
const binaryPath = join33(installDir, PERMISSIONS_WINDOW_BINARY);
|
|
60150
61048
|
const fail = (reason, detail) => ({ ready: false, reason, detail, installDir, binaryPath });
|
|
60151
61049
|
if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
|
|
60152
61050
|
const rebuild = `Run \`${PERMISSIONS_WINDOW_SETUP_COMMAND}\`.`;
|
|
60153
61051
|
if (!deps.exists(deps.sourcePath))
|
|
60154
61052
|
return fail("window_not_installed", `Packaged window source is missing: ${deps.sourcePath}`);
|
|
60155
|
-
const manifestPath =
|
|
61053
|
+
const manifestPath = join33(installDir, PERMISSIONS_WINDOW_MANIFEST);
|
|
60156
61054
|
if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
|
|
60157
61055
|
return fail("window_not_installed", `The permissions window is not built. ${rebuild}`);
|
|
60158
61056
|
let manifest;
|
|
@@ -60260,14 +61158,14 @@ function buildPermissionsWindow(checkOnly, deps = defaultPermissionsWindowDeps()
|
|
|
60260
61158
|
const compiler = String(version3.stdout || version3.stderr || "").split("\n").find((line) => line.includes("Swift version"))?.trim() || "swiftc";
|
|
60261
61159
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
60262
61160
|
const installDir = permissionsWindowInstallDir(deps.env);
|
|
60263
|
-
|
|
60264
|
-
const staging =
|
|
61161
|
+
mkdirSync11(installDir, { recursive: true, mode: 448 });
|
|
61162
|
+
const staging = mkdtempSync10(join33(installDir, ".staging-"));
|
|
60265
61163
|
try {
|
|
60266
|
-
const stagedBinary =
|
|
60267
|
-
const digestPath =
|
|
60268
|
-
const plistPath =
|
|
60269
|
-
|
|
60270
|
-
|
|
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 });
|
|
60271
61169
|
const compile = deps.spawn(
|
|
60272
61170
|
"/usr/bin/xcrun",
|
|
60273
61171
|
permissionsWindowCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
|
|
@@ -60321,16 +61219,16 @@ function buildPermissionsWindow(checkOnly, deps = defaultPermissionsWindowDeps()
|
|
|
60321
61219
|
builtAt: deps.now().toISOString(),
|
|
60322
61220
|
compiler
|
|
60323
61221
|
};
|
|
60324
|
-
|
|
60325
|
-
|
|
60326
|
-
|
|
60327
|
-
|
|
61222
|
+
chmodSync4(stagedBinary, 448);
|
|
61223
|
+
renameSync5(stagedBinary, join33(installDir, PERMISSIONS_WINDOW_BINARY));
|
|
61224
|
+
writeFileSync9(
|
|
61225
|
+
join33(installDir, PERMISSIONS_WINDOW_MANIFEST),
|
|
60328
61226
|
JSON.stringify(manifest, null, 2) + "\n",
|
|
60329
61227
|
{ mode: 384 }
|
|
60330
61228
|
);
|
|
60331
61229
|
steps.push({ step: "install", ok: true, detail: installDir });
|
|
60332
61230
|
} finally {
|
|
60333
|
-
|
|
61231
|
+
rmSync10(staging, { recursive: true, force: true });
|
|
60334
61232
|
}
|
|
60335
61233
|
const installation = inspectPermissionsWindow(deps);
|
|
60336
61234
|
steps.push({
|
|
@@ -60404,6 +61302,288 @@ function runPermissionsWindow(binaryPath, session) {
|
|
|
60404
61302
|
});
|
|
60405
61303
|
}
|
|
60406
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
|
+
|
|
60407
61587
|
// src/tools/privateHelperTools.ts
|
|
60408
61588
|
var coreDataId2 = external_exports.string().regex(/^x-coredata:\/\/[0-9A-F-]+\/ICNote\/p\d+$/i);
|
|
60409
61589
|
var notesUuid = external_exports.string().regex(UUID_PATTERN);
|
|
@@ -61091,24 +62271,24 @@ async function startTemplateEditor(options = {}) {
|
|
|
61091
62271
|
socket.on("close", () => sockets.delete(socket));
|
|
61092
62272
|
});
|
|
61093
62273
|
let resolveClosed;
|
|
61094
|
-
const closed = new Promise((
|
|
62274
|
+
const closed = new Promise((resolve11) => resolveClosed = resolve11);
|
|
61095
62275
|
let closing;
|
|
61096
62276
|
const close = () => {
|
|
61097
|
-
closing ??= new Promise((
|
|
62277
|
+
closing ??= new Promise((resolve11) => {
|
|
61098
62278
|
if (idleTimer) clearTimeout(idleTimer);
|
|
61099
62279
|
server2.close(() => {
|
|
61100
62280
|
resolveClosed(closeReason);
|
|
61101
|
-
|
|
62281
|
+
resolve11();
|
|
61102
62282
|
});
|
|
61103
62283
|
for (const socket of sockets) socket.destroy();
|
|
61104
62284
|
});
|
|
61105
62285
|
return closing;
|
|
61106
62286
|
};
|
|
61107
|
-
await new Promise((
|
|
62287
|
+
await new Promise((resolve11, reject) => {
|
|
61108
62288
|
server2.once("error", reject);
|
|
61109
62289
|
server2.listen({ host, port: options.port ?? 0, exclusive: true }, () => {
|
|
61110
62290
|
server2.off("error", reject);
|
|
61111
|
-
|
|
62291
|
+
resolve11();
|
|
61112
62292
|
});
|
|
61113
62293
|
});
|
|
61114
62294
|
const port = server2.address().port;
|
|
@@ -61552,13 +62732,13 @@ function createAnchorServer(options) {
|
|
|
61552
62732
|
}
|
|
61553
62733
|
function startAnchorServer(options) {
|
|
61554
62734
|
const server2 = createAnchorServer(options);
|
|
61555
|
-
return new Promise((
|
|
62735
|
+
return new Promise((resolve11, reject) => {
|
|
61556
62736
|
server2.once("error", reject);
|
|
61557
62737
|
server2.listen(options.port, options.host, () => {
|
|
61558
62738
|
server2.off("error", reject);
|
|
61559
62739
|
const address = server2.address();
|
|
61560
62740
|
const port = address && typeof address === "object" ? address.port : options.port;
|
|
61561
|
-
|
|
62741
|
+
resolve11({
|
|
61562
62742
|
server: server2,
|
|
61563
62743
|
baseUrl: `http://${hostAuthority(options.host, port)}`,
|
|
61564
62744
|
close: () => new Promise((done) => {
|
|
@@ -61596,7 +62776,7 @@ function parseAnchorsArgs(argv) {
|
|
|
61596
62776
|
}
|
|
61597
62777
|
async function runAnchorsCli(argv, {
|
|
61598
62778
|
env = process.env,
|
|
61599
|
-
resolve:
|
|
62779
|
+
resolve: resolve11,
|
|
61600
62780
|
out = (text2) => process.stdout.write(text2),
|
|
61601
62781
|
interfaces,
|
|
61602
62782
|
signals = process
|
|
@@ -61629,7 +62809,7 @@ ${ANCHORS_USAGE}
|
|
|
61629
62809
|
const token = fromEnv || newServerToken();
|
|
61630
62810
|
let started;
|
|
61631
62811
|
try {
|
|
61632
|
-
started = await startAnchorServer({ host, port: args.port, token, resolve:
|
|
62812
|
+
started = await startAnchorServer({ host, port: args.port, token, resolve: resolve11 });
|
|
61633
62813
|
} catch (error2) {
|
|
61634
62814
|
out(`Could not listen on ${host}:${args.port}: ${error2.message}
|
|
61635
62815
|
`);
|
|
@@ -61656,17 +62836,17 @@ import {
|
|
|
61656
62836
|
constants as constants9,
|
|
61657
62837
|
fstatSync as fstatSync9,
|
|
61658
62838
|
fsyncSync,
|
|
61659
|
-
lstatSync as
|
|
61660
|
-
mkdirSync as
|
|
62839
|
+
lstatSync as lstatSync7,
|
|
62840
|
+
mkdirSync as mkdirSync12,
|
|
61661
62841
|
openSync as openSync9,
|
|
61662
62842
|
readSync as readSync7,
|
|
61663
|
-
renameSync as
|
|
62843
|
+
renameSync as renameSync6,
|
|
61664
62844
|
statSync as statSync6,
|
|
61665
62845
|
unlinkSync as unlinkSync5,
|
|
61666
62846
|
writeSync as writeSync6
|
|
61667
62847
|
} from "node:fs";
|
|
61668
|
-
import { homedir as
|
|
61669
|
-
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";
|
|
61670
62850
|
var MAX_ANCHORS = 2e4;
|
|
61671
62851
|
var MAX_REGISTRY_BYTES = 32 * 1024 * 1024;
|
|
61672
62852
|
var LOCK_WAIT_MS = 3e3;
|
|
@@ -61690,14 +62870,14 @@ var AnchorRegistryError = class extends CodedError {
|
|
|
61690
62870
|
function anchorRegistryPath(env = process.env) {
|
|
61691
62871
|
const override = env.APPLE_NOTES_MCP_ANCHOR_FILE?.trim();
|
|
61692
62872
|
if (override) {
|
|
61693
|
-
if (!
|
|
62873
|
+
if (!isAbsolute7(override))
|
|
61694
62874
|
throw new AnchorRegistryError(
|
|
61695
62875
|
"unsafe-path",
|
|
61696
62876
|
"APPLE_NOTES_MCP_ANCHOR_FILE must be an absolute path."
|
|
61697
62877
|
);
|
|
61698
|
-
return
|
|
62878
|
+
return resolve10(override);
|
|
61699
62879
|
}
|
|
61700
|
-
return
|
|
62880
|
+
return join34(homedir25(), "Library/Application Support/apple-notes-mcp/paragraph-anchors.json");
|
|
61701
62881
|
}
|
|
61702
62882
|
var STRING_OR_NULL = (v) => v === null || typeof v === "string";
|
|
61703
62883
|
function isAnchor(v) {
|
|
@@ -61720,14 +62900,14 @@ var AnchorRegistry = class _AnchorRegistry {
|
|
|
61720
62900
|
ensureDir() {
|
|
61721
62901
|
const dir = dirname10(this.path);
|
|
61722
62902
|
try {
|
|
61723
|
-
if (!
|
|
62903
|
+
if (!lstatSync7(dir).isDirectory())
|
|
61724
62904
|
throw new AnchorRegistryError(
|
|
61725
62905
|
"unsafe-path",
|
|
61726
62906
|
`${dir} is not a directory (symlinks are refused).`
|
|
61727
62907
|
);
|
|
61728
62908
|
} catch (error2) {
|
|
61729
62909
|
if (error2 instanceof AnchorRegistryError) throw error2;
|
|
61730
|
-
|
|
62910
|
+
mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
61731
62911
|
}
|
|
61732
62912
|
}
|
|
61733
62913
|
/** Every anchor in the file, in the order recorded. A missing file is an empty registry. */
|
|
@@ -61817,7 +62997,7 @@ var AnchorRegistry = class _AnchorRegistry {
|
|
|
61817
62997
|
}
|
|
61818
62998
|
write(anchors) {
|
|
61819
62999
|
try {
|
|
61820
|
-
if (!
|
|
63000
|
+
if (!lstatSync7(this.path).isFile())
|
|
61821
63001
|
throw new AnchorRegistryError(
|
|
61822
63002
|
"unsafe-path",
|
|
61823
63003
|
`Refusing to replace ${this.path}: it is not a regular file.`
|
|
@@ -61825,7 +63005,7 @@ var AnchorRegistry = class _AnchorRegistry {
|
|
|
61825
63005
|
} catch (error2) {
|
|
61826
63006
|
if (error2 instanceof AnchorRegistryError) throw error2;
|
|
61827
63007
|
}
|
|
61828
|
-
const temp =
|
|
63008
|
+
const temp = join34(
|
|
61829
63009
|
dirname10(this.path),
|
|
61830
63010
|
`.paragraph-anchors.${randomBytes5(6).toString("hex")}.tmp`
|
|
61831
63011
|
);
|
|
@@ -61846,7 +63026,7 @@ var AnchorRegistry = class _AnchorRegistry {
|
|
|
61846
63026
|
}
|
|
61847
63027
|
closeSync9(fd);
|
|
61848
63028
|
try {
|
|
61849
|
-
|
|
63029
|
+
renameSync6(temp, this.path);
|
|
61850
63030
|
} catch (error2) {
|
|
61851
63031
|
unlinkSync5(temp);
|
|
61852
63032
|
throw error2;
|
|
@@ -62041,6 +63221,11 @@ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--permissions
|
|
|
62041
63221
|
cli.close();
|
|
62042
63222
|
process.exit(code);
|
|
62043
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
|
+
}
|
|
62044
63229
|
if (process.argv[2] === "setup") {
|
|
62045
63230
|
const report = setupShortcuts(process.argv.slice(3).includes("--check"));
|
|
62046
63231
|
process.stdout.write(formatShortcutSetup(report) + "\n");
|
|
@@ -62054,6 +63239,10 @@ if (process.argv[2] === "anchors") {
|
|
|
62054
63239
|
await runAnchorsCli(process.argv.slice(3), { resolve: registryLookup(new AnchorRegistry()) })
|
|
62055
63240
|
);
|
|
62056
63241
|
}
|
|
63242
|
+
if (await startBrokerProxy()) {
|
|
63243
|
+
await new Promise(() => {
|
|
63244
|
+
});
|
|
63245
|
+
}
|
|
62057
63246
|
var server = new McpServer({
|
|
62058
63247
|
name: "apple-notes",
|
|
62059
63248
|
version: version2,
|
|
@@ -62927,7 +64116,7 @@ registerTool(
|
|
|
62927
64116
|
registerTool(
|
|
62928
64117
|
"get-note-link",
|
|
62929
64118
|
{
|
|
62930
|
-
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.",
|
|
62931
64120
|
inputSchema: {
|
|
62932
64121
|
id: looseNoteId(external_exports.string()).optional().describe(`Note ID (preferred - more reliable than title): ${NOTE_ID_FORMS}`),
|
|
62933
64122
|
title: external_exports.string().max(MAX.TITLE).optional().describe("Note title (use id instead when available)"),
|
|
@@ -62953,7 +64142,7 @@ registerTool(
|
|
|
62953
64142
|
const url2 = notesManager.getNoteLinkById(id2);
|
|
62954
64143
|
if (!url2) {
|
|
62955
64144
|
return errorResponse(
|
|
62956
|
-
`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.)`
|
|
62957
64146
|
);
|
|
62958
64147
|
}
|
|
62959
64148
|
return successResponse(`Note link: ${url2}`, { id: id2, title: note2.title, url: url2 });
|
|
@@ -62973,7 +64162,7 @@ registerTool(
|
|
|
62973
64162
|
const url = notesManager.getNoteLink(title, account);
|
|
62974
64163
|
if (!url) {
|
|
62975
64164
|
return errorResponse(
|
|
62976
|
-
`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.)`
|
|
62977
64166
|
);
|
|
62978
64167
|
}
|
|
62979
64168
|
return successResponse(`Note link: ${url}`, { title, url });
|
|
@@ -63146,7 +64335,7 @@ registerTool(
|
|
|
63146
64335
|
page = pageNoteBlocks(readNoteBlocks(id2), { offset, limit });
|
|
63147
64336
|
} catch (error2) {
|
|
63148
64337
|
if (!(error2 instanceof NoteBlocksError)) throw error2;
|
|
63149
|
-
const hint = error2.code === "no-full-disk-access" ? `
|
|
64338
|
+
const hint = error2.code === "no-full-disk-access" ? ` ${fdaRemediation()}` : "";
|
|
63150
64339
|
return errorResponse(
|
|
63151
64340
|
`Error reading note blocks [${error2.code}]: ${error2.message}${hint}`,
|
|
63152
64341
|
error2
|
|
@@ -64475,12 +65664,19 @@ registerTool(
|
|
|
64475
65664
|
healthy: external_exports.boolean().optional(),
|
|
64476
65665
|
checks: external_exports.array(external_exports.object({}).passthrough()).optional(),
|
|
64477
65666
|
runtimeOS: external_exports.object({}).passthrough().optional(),
|
|
64478
|
-
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()
|
|
64479
65669
|
}
|
|
64480
65670
|
},
|
|
64481
65671
|
withErrorHandling(() => {
|
|
64482
65672
|
const report = runDoctor(notesManager);
|
|
64483
|
-
|
|
65673
|
+
const broker = brokerStatus();
|
|
65674
|
+
return successResponse(`${formatDoctorReport(report)}
|
|
65675
|
+
|
|
65676
|
+
Permission broker: ${broker.detail}`, {
|
|
65677
|
+
...report,
|
|
65678
|
+
broker
|
|
65679
|
+
});
|
|
64484
65680
|
}, "Error running doctor")
|
|
64485
65681
|
);
|
|
64486
65682
|
registerTool(
|
|
@@ -65163,7 +66359,7 @@ registerTool(
|
|
|
65163
66359
|
});
|
|
65164
66360
|
} catch (error2) {
|
|
65165
66361
|
if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
|
|
65166
|
-
const hint = error2.code === "no-full-disk-access" ? `
|
|
66362
|
+
const hint = error2.code === "no-full-disk-access" ? ` ${fdaRemediation()}` : "";
|
|
65167
66363
|
if (error2 instanceof NotesExportError && error2.details)
|
|
65168
66364
|
return errorResponse(
|
|
65169
66365
|
`Error exporting Markdown [${error2.code}]: the template is invalid:
|
|
@@ -65233,7 +66429,7 @@ registerTool(
|
|
|
65233
66429
|
});
|
|
65234
66430
|
} catch (error2) {
|
|
65235
66431
|
if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
|
|
65236
|
-
const hint = error2.code === "no-full-disk-access" ? `
|
|
66432
|
+
const hint = error2.code === "no-full-disk-access" ? ` ${fdaRemediation()}` : "";
|
|
65237
66433
|
return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
|
|
65238
66434
|
}
|
|
65239
66435
|
const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;
|