apple-notes-mcp 2.9.10 → 2.9.12
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 +42 -4
- package/build/index.js +597 -99
- package/native/public-helper/apple-notes-public-helper.swift +331 -5
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -24465,14 +24465,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
24465
24465
|
} else if (node.nodeType === 1) {
|
|
24466
24466
|
replacement = replacementForNode.call(self, node);
|
|
24467
24467
|
}
|
|
24468
|
-
return
|
|
24468
|
+
return join28(output, replacement);
|
|
24469
24469
|
}, "");
|
|
24470
24470
|
}
|
|
24471
24471
|
function postProcess(output) {
|
|
24472
24472
|
var self = this;
|
|
24473
24473
|
this.rules.forEach(function(rule) {
|
|
24474
24474
|
if (typeof rule.append === "function") {
|
|
24475
|
-
output =
|
|
24475
|
+
output = join28(output, rule.append(self.options));
|
|
24476
24476
|
}
|
|
24477
24477
|
});
|
|
24478
24478
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -24484,7 +24484,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
24484
24484
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
24485
24485
|
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
|
|
24486
24486
|
}
|
|
24487
|
-
function
|
|
24487
|
+
function join28(output, replacement) {
|
|
24488
24488
|
var s1 = trimTrailingNewlines(output);
|
|
24489
24489
|
var s2 = trimLeadingNewlines(replacement);
|
|
24490
24490
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -38983,6 +38983,7 @@ var StdioServerTransport = class {
|
|
|
38983
38983
|
|
|
38984
38984
|
// src/utils/applescript.ts
|
|
38985
38985
|
import { execFileSync } from "child_process";
|
|
38986
|
+
import { constants as bufferConstants } from "node:buffer";
|
|
38986
38987
|
|
|
38987
38988
|
// src/utils/docsUrls.ts
|
|
38988
38989
|
var FULL_DISK_ACCESS_GUIDE_URL = "https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md";
|
|
@@ -39020,6 +39021,21 @@ function envPositiveNumber(name) {
|
|
|
39020
39021
|
function getMaxBuffer() {
|
|
39021
39022
|
return envPositiveNumber("APPLE_NOTES_MCP_MAX_BUFFER") ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
39022
39023
|
}
|
|
39024
|
+
var MAX_OUTPUT_BYTES = bufferConstants.MAX_STRING_LENGTH;
|
|
39025
|
+
var NOTE_BODY_MAX_BUFFER_BYTES = Math.min(512 * 1024 * 1024, MAX_OUTPUT_BYTES);
|
|
39026
|
+
function noteBodyMaxBuffer() {
|
|
39027
|
+
return Math.min(Math.max(getMaxBuffer(), NOTE_BODY_MAX_BUFFER_BYTES), MAX_OUTPUT_BYTES);
|
|
39028
|
+
}
|
|
39029
|
+
function isOutputOverflowError(error2) {
|
|
39030
|
+
const code = error2 instanceof Error ? error2.code : void 0;
|
|
39031
|
+
return code === "ENOBUFS" || code === "ERR_STRING_TOO_LONG";
|
|
39032
|
+
}
|
|
39033
|
+
function outputOverflowMessage(maxBufferBytes) {
|
|
39034
|
+
const mib = 1024 * 1024;
|
|
39035
|
+
const limit = maxBufferBytes >= mib ? `${Math.round(maxBufferBytes / mib)} MB` : `${maxBufferBytes} bytes`;
|
|
39036
|
+
const remedy = maxBufferBytes >= MAX_OUTPUT_BYTES ? "That is the longest string Node.js can hold, so raising APPLE_NOTES_MCP_MAX_BUFFER will not help; remove or shrink the large image or attachment in Notes.app." : "Raise APPLE_NOTES_MCP_MAX_BUFFER (in bytes) to allow more.";
|
|
39037
|
+
return `Notes.app returned more than ${limit} of output, the most this server accepts from one AppleScript call. A note whose body carries large inline images or attachments can reach this. ${remedy}`;
|
|
39038
|
+
}
|
|
39023
39039
|
var SCRIPT_TIMEOUT_HEADROOM_MS = 5e3;
|
|
39024
39040
|
var MIN_ATTEMPT_BUDGET_MS = 1e3;
|
|
39025
39041
|
function wrapWithTimeout(script, processTimeoutMs) {
|
|
@@ -39165,6 +39181,7 @@ function executeAppleScript(script, options = {}) {
|
|
|
39165
39181
|
const timeoutMs = options.timeoutMs ?? callTimeoutMs() ?? envPositiveNumber("APPLE_NOTES_MCP_TIMEOUT_MS") ?? DEFAULT_TIMEOUT_MS;
|
|
39166
39182
|
const maxRetries = options.maxRetries ?? envPositiveNumber("APPLE_NOTES_MCP_MAX_RETRIES") ?? DEFAULT_MAX_RETRIES;
|
|
39167
39183
|
const retryDelayMs = options.retryDelayMs ?? envPositiveNumber("APPLE_NOTES_MCP_RETRY_DELAY_MS") ?? DEFAULT_RETRY_DELAY_MS;
|
|
39184
|
+
const maxBufferBytes = Math.min(options.maxBufferBytes ?? getMaxBuffer(), MAX_OUTPUT_BYTES);
|
|
39168
39185
|
if (!script || !script.trim()) {
|
|
39169
39186
|
return {
|
|
39170
39187
|
success: false,
|
|
@@ -39195,7 +39212,7 @@ function executeAppleScript(script, options = {}) {
|
|
|
39195
39212
|
killSignal: "SIGKILL",
|
|
39196
39213
|
// Raise the output cap above Node's 1 MB default so large exports /
|
|
39197
39214
|
// long notes aren't truncated into an ENOBUFS failure. (#16)
|
|
39198
|
-
maxBuffer:
|
|
39215
|
+
maxBuffer: maxBufferBytes
|
|
39199
39216
|
});
|
|
39200
39217
|
const duration3 = Date.now() - attemptStart;
|
|
39201
39218
|
debugLog("AppleScript succeeded", {
|
|
@@ -39213,7 +39230,9 @@ function executeAppleScript(script, options = {}) {
|
|
|
39213
39230
|
let errorMessage;
|
|
39214
39231
|
let isTimeout = false;
|
|
39215
39232
|
let rawError;
|
|
39216
|
-
if (
|
|
39233
|
+
if (isOutputOverflowError(error2)) {
|
|
39234
|
+
errorMessage = outputOverflowMessage(maxBufferBytes);
|
|
39235
|
+
} else if (isTimeoutError(error2)) {
|
|
39217
39236
|
isTimeout = true;
|
|
39218
39237
|
const timeoutSecs = Math.round(timeoutMs / 1e3);
|
|
39219
39238
|
errorMessage = `Operation timed out after ${timeoutSecs} seconds. Notes.app may be unresponsive or the operation involves too many notes.`;
|
|
@@ -42130,8 +42149,8 @@ function scopeConflictMessage(reason) {
|
|
|
42130
42149
|
|
|
42131
42150
|
// src/services/appleNotesManager.ts
|
|
42132
42151
|
var import_turndown = __toESM(require_turndown_cjs(), 1);
|
|
42133
|
-
import { existsSync as existsSync5 } from "fs";
|
|
42134
|
-
import { homedir as homedir8 } from "os";
|
|
42152
|
+
import { existsSync as existsSync5, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
|
|
42153
|
+
import { homedir as homedir8, tmpdir as tmpdir2 } from "os";
|
|
42135
42154
|
import { join as join9 } from "path";
|
|
42136
42155
|
var FIELD_SEP = "";
|
|
42137
42156
|
var RECORD_SEP = "";
|
|
@@ -42442,6 +42461,34 @@ function buildAppLevelScript(command) {
|
|
|
42442
42461
|
end tell
|
|
42443
42462
|
`;
|
|
42444
42463
|
}
|
|
42464
|
+
var INLINE_EXPECTED_BODY_LIMIT = MAX_CONTENT_LENGTH;
|
|
42465
|
+
var ExpectedBodies = class {
|
|
42466
|
+
directory;
|
|
42467
|
+
files = 0;
|
|
42468
|
+
/**
|
|
42469
|
+
* Returns AppleScript `setup` to run before the comparison (empty for a
|
|
42470
|
+
* literal) and the `operand` to compare the note's body against.
|
|
42471
|
+
* `variable` names the AppleScript variable a file-backed body is read into.
|
|
42472
|
+
*/
|
|
42473
|
+
bind(body, variable) {
|
|
42474
|
+
if (body.length <= INLINE_EXPECTED_BODY_LIMIT) {
|
|
42475
|
+
return { setup: "", operand: `"${escapeHtmlForAppleScript(body)}"` };
|
|
42476
|
+
}
|
|
42477
|
+
this.directory ??= mkdtempSync2(join9(tmpdir2(), "apple-notes-expected-body-"));
|
|
42478
|
+
const file = join9(this.directory, `body-${this.files++}.html`);
|
|
42479
|
+
writeFileSync(file, body, { encoding: "utf8", mode: 384 });
|
|
42480
|
+
return {
|
|
42481
|
+
setup: `
|
|
42482
|
+
tell current application to set ${variable} to read (POSIX file "${escapePlainStringForAppleScript(file)}") as \xABclass utf8\xBB`,
|
|
42483
|
+
operand: variable
|
|
42484
|
+
};
|
|
42485
|
+
}
|
|
42486
|
+
/** Removes any temporary files. Safe to call more than once. */
|
|
42487
|
+
cleanup() {
|
|
42488
|
+
if (this.directory) rmSync2(this.directory, { recursive: true, force: true });
|
|
42489
|
+
this.directory = void 0;
|
|
42490
|
+
}
|
|
42491
|
+
};
|
|
42445
42492
|
function folderDeleteFactsScript(id2) {
|
|
42446
42493
|
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
42447
42494
|
return `
|
|
@@ -42803,7 +42850,7 @@ var AppleNotesManager = class {
|
|
|
42803
42850
|
const safeTitle = escapePlainStringForAppleScript(title);
|
|
42804
42851
|
const getCommand = `get body of note "${safeTitle}"`;
|
|
42805
42852
|
const script = buildAccountScopedScript({ account: targetAccount }, getCommand);
|
|
42806
|
-
const result = executeAppleScript(script);
|
|
42853
|
+
const result = executeAppleScript(script, { maxBufferBytes: noteBodyMaxBuffer() });
|
|
42807
42854
|
if (!result.success) {
|
|
42808
42855
|
throwIfAccountResolutionFailed(result.error);
|
|
42809
42856
|
console.error(`Failed to get content of note "${title}":`, result.error);
|
|
@@ -42840,7 +42887,7 @@ var AppleNotesManager = class {
|
|
|
42840
42887
|
const safeId = sanitizeId(id2);
|
|
42841
42888
|
const getCommand = `get body of note id "${safeId}"`;
|
|
42842
42889
|
const script = buildAppLevelScript(getCommand);
|
|
42843
|
-
const result = executeAppleScript(script);
|
|
42890
|
+
const result = executeAppleScript(script, { maxBufferBytes: noteBodyMaxBuffer() });
|
|
42844
42891
|
if (!result.success) {
|
|
42845
42892
|
console.error(`Failed to get content of note with ID "${id2}":`, result.error);
|
|
42846
42893
|
return { body: "", error: result.error };
|
|
@@ -43063,25 +43110,25 @@ var AppleNotesManager = class {
|
|
|
43063
43110
|
*/
|
|
43064
43111
|
deleteNoteByIdIfUnchanged(id2, expectedBody, scope2, guards = []) {
|
|
43065
43112
|
const safeId = sanitizeNoteId(id2);
|
|
43066
|
-
validateLength(expectedBody, MAX_CONTENT_LENGTH, "Expected note content");
|
|
43067
|
-
const safeExpectedBody = escapeHtmlForAppleScript(expectedBody);
|
|
43068
43113
|
const trashIds = trashFolderIdList();
|
|
43069
|
-
const
|
|
43070
|
-
|
|
43071
|
-
|
|
43072
|
-
const
|
|
43073
|
-
|
|
43074
|
-
|
|
43075
|
-
|
|
43076
|
-
|
|
43077
|
-
|
|
43078
|
-
|
|
43114
|
+
const bodies = new ExpectedBodies();
|
|
43115
|
+
let result;
|
|
43116
|
+
try {
|
|
43117
|
+
const guardChecks = guards.map((guard, index) => {
|
|
43118
|
+
const safeGuardId = sanitizeNoteId(guard.id);
|
|
43119
|
+
const ref = `__guardRef${index}`;
|
|
43120
|
+
const folderVar = `__guardFolder${index}`;
|
|
43121
|
+
const inactive = (reason) => `return "SAFETY_GUARD_INACTIVE:${index}:${reason}"`;
|
|
43122
|
+
let bodyCheck = "";
|
|
43123
|
+
if (guard.expectedBody !== void 0) {
|
|
43124
|
+
const expected2 = bodies.bind(guard.expectedBody, `__expectedGuardBody${index}`);
|
|
43125
|
+
bodyCheck = `${expected2.setup}
|
|
43079
43126
|
set __guardBody to body of ${ref}
|
|
43080
43127
|
considering case
|
|
43081
|
-
if __guardBody is not
|
|
43128
|
+
if __guardBody is not ${expected2.operand} and __guardBody is not ${expected2.operand} & linefeed then return "SAFETY_GUARD_CONFLICT:${index}"
|
|
43082
43129
|
end considering`;
|
|
43083
|
-
|
|
43084
|
-
|
|
43130
|
+
}
|
|
43131
|
+
return `
|
|
43085
43132
|
if not (exists note id "${safeGuardId}") then ${inactive("missing")}
|
|
43086
43133
|
set ${ref} to note id "${safeGuardId}"
|
|
43087
43134
|
if password protected of ${ref} then ${inactive("locked")}
|
|
@@ -43091,18 +43138,19 @@ var AppleNotesManager = class {
|
|
|
43091
43138
|
end try
|
|
43092
43139
|
if ${folderVar} is missing value then ${inactive("folder unknown")}${inRecentlyDeletedScript(folderVar, "__guardInTrash", trashIds)}
|
|
43093
43140
|
if __guardInTrash then ${inactive("in Recently Deleted")}${bodyCheck}`;
|
|
43094
|
-
|
|
43095
|
-
|
|
43141
|
+
}).join("");
|
|
43142
|
+
const expected = bodies.bind(expectedBody, "__expectedBody");
|
|
43143
|
+
const script = buildAppLevelScript(`
|
|
43096
43144
|
set noteRef to note id "${safeId}"${buildScopeGuardScript("noteRef", scope2)}
|
|
43097
43145
|
set originalFolder to missing value
|
|
43098
43146
|
try
|
|
43099
43147
|
set originalFolder to container of noteRef
|
|
43100
43148
|
end try
|
|
43101
43149
|
if originalFolder is missing value then return "SAFETY_CONTAINER_UNKNOWN"${inRecentlyDeletedScript("originalFolder", "__inTrash", trashIds)}
|
|
43102
|
-
if __inTrash then return "SAFETY_IN_RECENTLY_DELETED"${guardChecks}
|
|
43150
|
+
if __inTrash then return "SAFETY_IN_RECENTLY_DELETED"${guardChecks}${expected.setup}
|
|
43103
43151
|
set currentBody to body of noteRef
|
|
43104
43152
|
considering case
|
|
43105
|
-
if currentBody is not
|
|
43153
|
+
if currentBody is not ${expected.operand} and currentBody is not ${expected.operand} & linefeed then return "SAFETY_CONFLICT"
|
|
43106
43154
|
delete noteRef
|
|
43107
43155
|
end considering
|
|
43108
43156
|
try
|
|
@@ -43110,7 +43158,10 @@ var AppleNotesManager = class {
|
|
|
43110
43158
|
end try
|
|
43111
43159
|
return "SAFETY_DELETED"
|
|
43112
43160
|
`);
|
|
43113
|
-
|
|
43161
|
+
result = executeMutationAppleScript(script);
|
|
43162
|
+
} finally {
|
|
43163
|
+
bodies.cleanup();
|
|
43164
|
+
}
|
|
43114
43165
|
if (!result.success) {
|
|
43115
43166
|
console.error(`Failed guarded delete for note ID "${id2}":`, result.error);
|
|
43116
43167
|
return { status: "failed" };
|
|
@@ -47592,8 +47643,8 @@ import { spawnSync } from "child_process";
|
|
|
47592
47643
|
|
|
47593
47644
|
// src/services/nativeTags.ts
|
|
47594
47645
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
47595
|
-
import { mkdtempSync as
|
|
47596
|
-
import { tmpdir as
|
|
47646
|
+
import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
|
|
47647
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
47597
47648
|
import { join as join16 } from "node:path";
|
|
47598
47649
|
|
|
47599
47650
|
// src/services/shortcutConsent.ts
|
|
@@ -47696,10 +47747,10 @@ function runNativeTagsShortcut(input) {
|
|
|
47696
47747
|
const status = nativeTagsStatus();
|
|
47697
47748
|
if (!status.installed)
|
|
47698
47749
|
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
47699
|
-
const directory =
|
|
47750
|
+
const directory = mkdtempSync3(join16(tmpdir3(), "apple-notes-native-tags-"));
|
|
47700
47751
|
try {
|
|
47701
47752
|
const path10 = join16(directory, "request.json");
|
|
47702
|
-
|
|
47753
|
+
writeFileSync2(path10, JSON.stringify(input), { mode: 384 });
|
|
47703
47754
|
execFileSync15("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path10], {
|
|
47704
47755
|
encoding: "utf8",
|
|
47705
47756
|
timeout: 6e4,
|
|
@@ -47714,14 +47765,14 @@ function runNativeTagsShortcut(input) {
|
|
|
47714
47765
|
{ shortcut: status.shortcut, code: error2?.code }
|
|
47715
47766
|
);
|
|
47716
47767
|
} finally {
|
|
47717
|
-
|
|
47768
|
+
rmSync3(directory, { recursive: true, force: true });
|
|
47718
47769
|
}
|
|
47719
47770
|
}
|
|
47720
47771
|
|
|
47721
47772
|
// src/services/backgroundNotes.ts
|
|
47722
47773
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
47723
|
-
import { mkdtempSync as
|
|
47724
|
-
import { tmpdir as
|
|
47774
|
+
import { mkdtempSync as mkdtempSync4, writeFileSync as writeFileSync3, rmSync as rmSync4 } from "node:fs";
|
|
47775
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
47725
47776
|
import { join as join17 } from "node:path";
|
|
47726
47777
|
|
|
47727
47778
|
// src/utils/appendMarkdown.ts
|
|
@@ -48047,10 +48098,10 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
48047
48098
|
throw new Error(
|
|
48048
48099
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
48049
48100
|
);
|
|
48050
|
-
const directory =
|
|
48101
|
+
const directory = mkdtempSync4(join17(tmpdir4(), "apple-notes-background-"));
|
|
48051
48102
|
try {
|
|
48052
48103
|
const file = join17(directory, "request.json");
|
|
48053
|
-
|
|
48104
|
+
writeFileSync3(
|
|
48054
48105
|
file,
|
|
48055
48106
|
JSON.stringify({
|
|
48056
48107
|
operation: "",
|
|
@@ -48079,7 +48130,7 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
48079
48130
|
throw Object.assign(error2, { shortcut: status.shortcut });
|
|
48080
48131
|
}
|
|
48081
48132
|
} finally {
|
|
48082
|
-
|
|
48133
|
+
rmSync4(directory, { recursive: true, force: true });
|
|
48083
48134
|
}
|
|
48084
48135
|
}
|
|
48085
48136
|
function backgroundDependencies(manager) {
|
|
@@ -49607,9 +49658,12 @@ function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH7, includeText
|
|
|
49607
49658
|
|
|
49608
49659
|
// src/utils/bodyReadFailure.ts
|
|
49609
49660
|
var LARGE_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
49661
|
+
var OUTPUT_OVERFLOW = /Notes\.app returned more than [\d.]+ (?:MB|bytes) of output/;
|
|
49610
49662
|
function classifyBodyReadError(error2) {
|
|
49611
49663
|
if (!error2) return "other";
|
|
49612
|
-
if (/ENOBUFS|maxBuffer/i.test(error2))
|
|
49664
|
+
if (OUTPUT_OVERFLOW.test(error2) || /ENOBUFS|maxBuffer|ERR_STRING_TOO_LONG/i.test(error2)) {
|
|
49665
|
+
return "buffer";
|
|
49666
|
+
}
|
|
49613
49667
|
if (/timed out|timeout|-1712/i.test(error2)) return "timeout";
|
|
49614
49668
|
return "other";
|
|
49615
49669
|
}
|
|
@@ -49627,10 +49681,17 @@ function describeBodyReadFailure(title, error2, attachments) {
|
|
|
49627
49681
|
const kind = classifyBodyReadError(error2);
|
|
49628
49682
|
if (kind === "other") return base;
|
|
49629
49683
|
const large = attachments ? largeAttachments(attachments) : [];
|
|
49630
|
-
const
|
|
49631
|
-
|
|
49632
|
-
|
|
49633
|
-
|
|
49684
|
+
const listed = large.map((a) => `${a.name}, ${formatBytes2(a.bytes)}`).join("; ");
|
|
49685
|
+
const holds = large.length > 0 ? `The note holds ${large.length === 1 ? "a large attachment" : "large attachments"} (${listed}). ` : "";
|
|
49686
|
+
if (kind === "buffer") {
|
|
49687
|
+
const cause2 = `${holds}Notes.app returns images inside the note body as base64, so a large image makes the body bigger than this server can read in one call.`;
|
|
49688
|
+
const remedy2 = "Retrying, or a longer timeoutSeconds, will not help. Remove or shrink the attachment in Notes.app, or delete the note there; delete-note needs a successful read to verify the note first.";
|
|
49689
|
+
return `${base}
|
|
49690
|
+
|
|
49691
|
+
${cause2} ${remedy2}`;
|
|
49692
|
+
}
|
|
49693
|
+
const cause = large.length > 0 ? `${holds}Notes.app returns images inside the note body as base64, so a large image makes the body too big to read in time.` : "Notes.app returns images inside the note body as base64, so a note with a very large image can take longer to read than the timeout allows.";
|
|
49694
|
+
const remedy = "Retry with a longer timeoutSeconds (up to 120) or raise APPLE_NOTES_MCP_TIMEOUT_MS. If it still fails, delete or shrink the attachment in Notes.app; delete-note needs a successful read to verify the note first.";
|
|
49634
49695
|
return `${base}
|
|
49635
49696
|
|
|
49636
49697
|
${cause} ${remedy}`;
|
|
@@ -51386,13 +51447,13 @@ import {
|
|
|
51386
51447
|
closeSync as closeSync5,
|
|
51387
51448
|
constants as constants5,
|
|
51388
51449
|
fstatSync as fstatSync5,
|
|
51389
|
-
mkdtempSync as
|
|
51450
|
+
mkdtempSync as mkdtempSync5,
|
|
51390
51451
|
openSync as openSync5,
|
|
51391
51452
|
readFileSync as readFileSync3,
|
|
51392
|
-
rmSync as
|
|
51393
|
-
writeFileSync as
|
|
51453
|
+
rmSync as rmSync5,
|
|
51454
|
+
writeFileSync as writeFileSync4
|
|
51394
51455
|
} from "node:fs";
|
|
51395
|
-
import { tmpdir as
|
|
51456
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
51396
51457
|
import { basename as basename5, extname as extname5, isAbsolute as isAbsolute3, join as join22 } from "node:path";
|
|
51397
51458
|
var noteId = exactIdInput(
|
|
51398
51459
|
"ICNote",
|
|
@@ -51633,10 +51694,10 @@ function attachFile(manager, args) {
|
|
|
51633
51694
|
const bytes = localAttachment(path10);
|
|
51634
51695
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
51635
51696
|
const beforeStored = storedAttachmentIds(manager, id2);
|
|
51636
|
-
const directory =
|
|
51697
|
+
const directory = mkdtempSync5(join22(tmpdir5(), "notes-attachment-add-"));
|
|
51637
51698
|
const temporaryFile = join22(directory, name);
|
|
51638
51699
|
try {
|
|
51639
|
-
|
|
51700
|
+
writeFileSync4(temporaryFile, bytes, { mode: 384 });
|
|
51640
51701
|
if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
|
|
51641
51702
|
let returnedId;
|
|
51642
51703
|
let transportUncertain = false;
|
|
@@ -51696,7 +51757,7 @@ function attachFile(manager, args) {
|
|
|
51696
51757
|
} : {}
|
|
51697
51758
|
};
|
|
51698
51759
|
} finally {
|
|
51699
|
-
|
|
51760
|
+
rmSync5(directory, { recursive: true, force: true });
|
|
51700
51761
|
}
|
|
51701
51762
|
}
|
|
51702
51763
|
|
|
@@ -54680,17 +54741,17 @@ function formatShortcutSetup(report) {
|
|
|
54680
54741
|
}
|
|
54681
54742
|
|
|
54682
54743
|
// src/services/publicHelper.ts
|
|
54683
|
-
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
54744
|
+
import { spawn, spawnSync as spawnSync3 } from "node:child_process";
|
|
54684
54745
|
import { createHash as createHash5 } from "node:crypto";
|
|
54685
54746
|
import {
|
|
54686
54747
|
chmodSync,
|
|
54687
54748
|
existsSync as existsSync14,
|
|
54688
54749
|
mkdirSync as mkdirSync6,
|
|
54689
|
-
mkdtempSync as
|
|
54750
|
+
mkdtempSync as mkdtempSync6,
|
|
54690
54751
|
readFileSync as readFileSync4,
|
|
54691
54752
|
renameSync,
|
|
54692
|
-
rmSync as
|
|
54693
|
-
writeFileSync as
|
|
54753
|
+
rmSync as rmSync6,
|
|
54754
|
+
writeFileSync as writeFileSync5
|
|
54694
54755
|
} from "node:fs";
|
|
54695
54756
|
import { homedir as homedir19, release as release3 } from "node:os";
|
|
54696
54757
|
import { dirname as dirname8, join as join24, resolve as resolve5 } from "node:path";
|
|
@@ -54702,9 +54763,13 @@ var PUBLIC_HELPER_BINARY = "apple-notes-public-helper";
|
|
|
54702
54763
|
var PUBLIC_HELPER_SOURCE = "native/public-helper/apple-notes-public-helper.swift";
|
|
54703
54764
|
var PUBLIC_HELPER_MANIFEST = "manifest.json";
|
|
54704
54765
|
var PUBLIC_HELPER_SETUP_COMMAND = "apple-notes-mcp setup --public-helper";
|
|
54705
|
-
var PUBLIC_HELPER_ACTIONS = /* @__PURE__ */ new Set([
|
|
54766
|
+
var PUBLIC_HELPER_ACTIONS = /* @__PURE__ */ new Set([
|
|
54767
|
+
"hello",
|
|
54768
|
+
"decode_drawing",
|
|
54769
|
+
"transcribe"
|
|
54770
|
+
]);
|
|
54706
54771
|
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
54707
|
-
var
|
|
54772
|
+
var MAX_OUTPUT_BYTES2 = 256 * 1024 * 1024;
|
|
54708
54773
|
var publicManifestSchema = external_exports.object({
|
|
54709
54774
|
schemaVersion: external_exports.literal(1),
|
|
54710
54775
|
protocolVersion: external_exports.number().int(),
|
|
@@ -54738,6 +54803,7 @@ function defaultPublicHelperDeps(overrides = {}) {
|
|
|
54738
54803
|
exists: existsSync14,
|
|
54739
54804
|
readFile: (path10) => readFileSync4(path10),
|
|
54740
54805
|
spawn: spawnSync3,
|
|
54806
|
+
spawnAsync: spawn,
|
|
54741
54807
|
...overrides
|
|
54742
54808
|
};
|
|
54743
54809
|
}
|
|
@@ -54802,10 +54868,14 @@ var UNAVAILABLE_CODES = /* @__PURE__ */ new Set([
|
|
|
54802
54868
|
"helper_modified",
|
|
54803
54869
|
"helper_manifest_invalid"
|
|
54804
54870
|
]);
|
|
54871
|
+
var ENVELOPE_CODES = {
|
|
54872
|
+
invalid_request: "validation_error",
|
|
54873
|
+
attachment_not_found: "not_found"
|
|
54874
|
+
};
|
|
54805
54875
|
var PublicHelperError = class extends CodedError {
|
|
54806
54876
|
constructor(code, message) {
|
|
54807
54877
|
super(message, {
|
|
54808
|
-
code: UNAVAILABLE_CODES.has(code) ? "unsupported" : "operation_failed",
|
|
54878
|
+
code: UNAVAILABLE_CODES.has(code) ? "unsupported" : ENVELOPE_CODES[code] ?? "operation_failed",
|
|
54809
54879
|
helperCode: code
|
|
54810
54880
|
});
|
|
54811
54881
|
this.code = code;
|
|
@@ -54821,25 +54891,13 @@ var publicHelloSchema = external_exports.object({
|
|
|
54821
54891
|
actions: external_exports.array(external_exports.string())
|
|
54822
54892
|
}).passthrough();
|
|
54823
54893
|
function callPublicHelper(action, fields = {}, deps = defaultPublicHelperDeps(), options = {}) {
|
|
54824
|
-
|
|
54825
|
-
throw new PublicHelperError(
|
|
54826
|
-
"unknown_action",
|
|
54827
|
-
`"${action}" is not an action the server sends to the public helper.`
|
|
54828
|
-
);
|
|
54829
|
-
let binaryPath = options.binaryPath;
|
|
54830
|
-
if (!binaryPath) {
|
|
54831
|
-
const install = inspectPublicHelper(deps);
|
|
54832
|
-
if (!install.ready)
|
|
54833
|
-
throw new PublicHelperError(install.reason ?? "helper_not_installed", install.detail ?? "");
|
|
54834
|
-
binaryPath = install.binaryPath;
|
|
54835
|
-
}
|
|
54836
|
-
const timeout = Number.parseInt(deps.env[PUBLIC_HELPER_TIMEOUT_ENV] || "", 10) || options.timeoutMs || DEFAULT_TIMEOUT_MS2;
|
|
54894
|
+
const { binaryPath, timeout, input } = prepareCall(action, fields, deps, options);
|
|
54837
54895
|
const result = deps.spawn(binaryPath, [], {
|
|
54838
|
-
input
|
|
54896
|
+
input,
|
|
54839
54897
|
encoding: "utf8",
|
|
54840
54898
|
timeout,
|
|
54841
54899
|
killSignal: "SIGKILL",
|
|
54842
|
-
maxBuffer:
|
|
54900
|
+
maxBuffer: MAX_OUTPUT_BYTES2
|
|
54843
54901
|
});
|
|
54844
54902
|
const errno = result.error?.code;
|
|
54845
54903
|
if (errno === "ETIMEDOUT" || result.signal && result.status === null)
|
|
@@ -54849,24 +54907,100 @@ function callPublicHelper(action, fields = {}, deps = defaultPublicHelperDeps(),
|
|
|
54849
54907
|
"helper_unreachable",
|
|
54850
54908
|
`Could not run the helper: ${result.error.message}`
|
|
54851
54909
|
);
|
|
54910
|
+
return parseHelperOutput(result.status, String(result.stdout ?? ""));
|
|
54911
|
+
}
|
|
54912
|
+
async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHelperDeps(), options = {}) {
|
|
54913
|
+
const { binaryPath, timeout, input } = prepareCall(action, fields, deps, options);
|
|
54914
|
+
const { signal } = options;
|
|
54915
|
+
const aborted2 = () => new PublicHelperError("aborted", "The request was cancelled; the helper was stopped.");
|
|
54916
|
+
if (signal?.aborted) throw aborted2();
|
|
54917
|
+
return new Promise((resolvePromise, reject) => {
|
|
54918
|
+
const child = (deps.spawnAsync ?? spawn)(binaryPath, [], {
|
|
54919
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
54920
|
+
});
|
|
54921
|
+
const chunks = [];
|
|
54922
|
+
let size = 0;
|
|
54923
|
+
let failure = null;
|
|
54924
|
+
const stop = (error2) => {
|
|
54925
|
+
failure ??= error2;
|
|
54926
|
+
child.kill("SIGKILL");
|
|
54927
|
+
};
|
|
54928
|
+
const timer = setTimeout(
|
|
54929
|
+
() => stop(new PublicHelperError("timeout", `The helper did not answer within ${timeout} ms.`)),
|
|
54930
|
+
timeout
|
|
54931
|
+
);
|
|
54932
|
+
const onAbort = () => stop(aborted2());
|
|
54933
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
54934
|
+
const settle = () => {
|
|
54935
|
+
clearTimeout(timer);
|
|
54936
|
+
signal?.removeEventListener("abort", onAbort);
|
|
54937
|
+
};
|
|
54938
|
+
child.stdout?.on("data", (chunk) => {
|
|
54939
|
+
size += chunk.length;
|
|
54940
|
+
if (size > MAX_OUTPUT_BYTES2)
|
|
54941
|
+
stop(new PublicHelperError("invalid_response", "The helper response is too large."));
|
|
54942
|
+
else chunks.push(chunk);
|
|
54943
|
+
});
|
|
54944
|
+
child.on("error", (error2) => {
|
|
54945
|
+
settle();
|
|
54946
|
+
reject(
|
|
54947
|
+
failure ?? new PublicHelperError("helper_unreachable", `Could not run the helper: ${error2.message}`)
|
|
54948
|
+
);
|
|
54949
|
+
});
|
|
54950
|
+
child.on("close", (status, exitSignal) => {
|
|
54951
|
+
settle();
|
|
54952
|
+
if (failure) return reject(failure);
|
|
54953
|
+
if (status === null && exitSignal)
|
|
54954
|
+
return reject(
|
|
54955
|
+
new PublicHelperError("helper_crashed", `The helper stopped on signal ${exitSignal}.`)
|
|
54956
|
+
);
|
|
54957
|
+
try {
|
|
54958
|
+
resolvePromise(parseHelperOutput(status, Buffer.concat(chunks).toString("utf8")));
|
|
54959
|
+
} catch (error2) {
|
|
54960
|
+
reject(error2);
|
|
54961
|
+
}
|
|
54962
|
+
});
|
|
54963
|
+
child.stdin?.on("error", () => {
|
|
54964
|
+
});
|
|
54965
|
+
child.stdin?.end(input);
|
|
54966
|
+
});
|
|
54967
|
+
}
|
|
54968
|
+
function prepareCall(action, fields, deps, options) {
|
|
54969
|
+
if (!PUBLIC_HELPER_ACTIONS.has(action))
|
|
54970
|
+
throw new PublicHelperError(
|
|
54971
|
+
"unknown_action",
|
|
54972
|
+
`"${action}" is not an action the server sends to the public helper.`
|
|
54973
|
+
);
|
|
54974
|
+
let binaryPath = options.binaryPath;
|
|
54975
|
+
if (!binaryPath) {
|
|
54976
|
+
const install = inspectPublicHelper(deps);
|
|
54977
|
+
if (!install.ready)
|
|
54978
|
+
throw new PublicHelperError(install.reason ?? "helper_not_installed", install.detail ?? "");
|
|
54979
|
+
binaryPath = install.binaryPath;
|
|
54980
|
+
}
|
|
54981
|
+
const timeout = options.timeoutMs || Number.parseInt(deps.env[PUBLIC_HELPER_TIMEOUT_ENV] || "", 10) || DEFAULT_TIMEOUT_MS2;
|
|
54982
|
+
const input = JSON.stringify({ protocol: PUBLIC_HELPER_PROTOCOL, action, ...fields });
|
|
54983
|
+
return { binaryPath, timeout, input };
|
|
54984
|
+
}
|
|
54985
|
+
function parseHelperOutput(status, stdout) {
|
|
54852
54986
|
let parsed;
|
|
54853
54987
|
try {
|
|
54854
|
-
parsed = JSON.parse(
|
|
54988
|
+
parsed = JSON.parse(stdout.trim());
|
|
54855
54989
|
} catch {
|
|
54856
54990
|
throw new PublicHelperError(
|
|
54857
54991
|
"invalid_response",
|
|
54858
|
-
`The helper exited with status ${
|
|
54992
|
+
`The helper exited with status ${status} and no JSON response.`
|
|
54859
54993
|
);
|
|
54860
54994
|
}
|
|
54861
54995
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
54862
54996
|
throw new PublicHelperError("invalid_response", "The helper response is not a JSON object.");
|
|
54863
54997
|
const object3 = parsed;
|
|
54864
|
-
if (
|
|
54998
|
+
if (status !== 0 || object3.status !== "ok") {
|
|
54865
54999
|
const error2 = errorSchema.safeParse(object3);
|
|
54866
55000
|
if (!error2.success)
|
|
54867
55001
|
throw new PublicHelperError(
|
|
54868
55002
|
"invalid_response",
|
|
54869
|
-
`The helper failed with an unrecognized response (exit ${
|
|
55003
|
+
`The helper failed with an unrecognized response (exit ${status}).`
|
|
54870
55004
|
);
|
|
54871
55005
|
throw new PublicHelperError(error2.data.code, error2.data.message);
|
|
54872
55006
|
}
|
|
@@ -54913,6 +55047,10 @@ function publicHelperCompileArguments(sourcePath, digestPath, plistPath, outputP
|
|
|
54913
55047
|
"AppKit",
|
|
54914
55048
|
"-framework",
|
|
54915
55049
|
"PencilKit",
|
|
55050
|
+
"-framework",
|
|
55051
|
+
"AVFoundation",
|
|
55052
|
+
"-framework",
|
|
55053
|
+
"Speech",
|
|
54916
55054
|
"-Xlinker",
|
|
54917
55055
|
"-sectcreate",
|
|
54918
55056
|
"-Xlinker",
|
|
@@ -54967,13 +55105,13 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
|
|
|
54967
55105
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
54968
55106
|
const installDir = publicHelperInstallDir(deps.env);
|
|
54969
55107
|
mkdirSync6(installDir, { recursive: true, mode: 448 });
|
|
54970
|
-
const staging =
|
|
55108
|
+
const staging = mkdtempSync6(join24(installDir, ".staging-"));
|
|
54971
55109
|
try {
|
|
54972
55110
|
const stagedBinary = join24(staging, PUBLIC_HELPER_BINARY);
|
|
54973
55111
|
const digestPath = join24(staging, "source-digest.swift");
|
|
54974
55112
|
const plistPath = join24(staging, "Info.plist");
|
|
54975
|
-
|
|
54976
|
-
|
|
55113
|
+
writeFileSync5(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
|
|
55114
|
+
writeFileSync5(plistPath, publicHelperInfoPlist(), { mode: 384 });
|
|
54977
55115
|
const compile = deps.spawn(
|
|
54978
55116
|
"/usr/bin/xcrun",
|
|
54979
55117
|
publicHelperCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
|
|
@@ -55035,14 +55173,14 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
|
|
|
55035
55173
|
};
|
|
55036
55174
|
chmodSync(stagedBinary, 448);
|
|
55037
55175
|
renameSync(stagedBinary, join24(installDir, PUBLIC_HELPER_BINARY));
|
|
55038
|
-
|
|
55176
|
+
writeFileSync5(
|
|
55039
55177
|
join24(installDir, PUBLIC_HELPER_MANIFEST),
|
|
55040
55178
|
JSON.stringify(manifest, null, 2) + "\n",
|
|
55041
55179
|
{ mode: 384 }
|
|
55042
55180
|
);
|
|
55043
55181
|
steps.push({ step: "install", ok: true, detail: installDir });
|
|
55044
55182
|
} finally {
|
|
55045
|
-
|
|
55183
|
+
rmSync6(staging, { recursive: true, force: true });
|
|
55046
55184
|
}
|
|
55047
55185
|
const installation = inspectPublicHelper(deps);
|
|
55048
55186
|
steps.push({
|
|
@@ -55279,26 +55417,333 @@ function formatNoteDrawings(result) {
|
|
|
55279
55417
|
return lines.join("\n");
|
|
55280
55418
|
}
|
|
55281
55419
|
|
|
55420
|
+
// src/utils/noteAudio.ts
|
|
55421
|
+
import { readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
|
|
55422
|
+
import { join as join25 } from "node:path";
|
|
55423
|
+
var EXTRA_AUDIO_UTIS = [
|
|
55424
|
+
"public.mp3",
|
|
55425
|
+
"public.aiff-audio",
|
|
55426
|
+
"public.aifc-audio",
|
|
55427
|
+
"com.microsoft.waveform-audio"
|
|
55428
|
+
];
|
|
55429
|
+
function audioRowsSql(columns, generationColumn) {
|
|
55430
|
+
const generation = generationColumn ? `m.${generationColumn}` : "NULL";
|
|
55431
|
+
const media = (alias) => `json((SELECT json_object('identifier', m.ZIDENTIFIER, 'generation', ${generation}, 'filename', m.ZFILENAME) FROM ZICCLOUDSYNCINGOBJECT m WHERE m.Z_PK = ${alias}.ZMEDIA))`;
|
|
55432
|
+
const live = (alias) => notTombstonedSql(columns, alias);
|
|
55433
|
+
const utis = EXTRA_AUDIO_UTIS.map((u) => `'${u}'`).join(", ");
|
|
55434
|
+
const accountCols = [...columns].filter((c) => /^ZACCOUNT\d*$/.test(c)).sort();
|
|
55435
|
+
const account = accountCols.length ? `SELECT (SELECT acc.ZIDENTIFIER FROM ZICCLOUDSYNCINGOBJECT acc WHERE acc.Z_ENT = ${entity("ICAccount")} AND acc.Z_PK IN (${accountCols.map((c) => `n.${c}`).join(", ")}) LIMIT 1) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk;` : "SELECT NULL;";
|
|
55436
|
+
return [
|
|
55437
|
+
NOTE_STATE_SQL,
|
|
55438
|
+
`SELECT json_group_array(json_object('pk', a.Z_PK, 'identifier', a.ZIDENTIFIER, 'uti', a.ZTYPEUTI, 'duration', a.ZDURATION, 'media', ${media("a")}, 'children', json((SELECT json_group_array(json_object('pk', c.Z_PK, 'identifier', c.ZIDENTIFIER, 'duration', c.ZDURATION, 'media', ${media("c")})) FROM (SELECT * FROM ZICCLOUDSYNCINGOBJECT k WHERE k.ZPARENTATTACHMENT = a.Z_PK AND ${live("k")} ORDER BY k.Z_PK) c)))) FROM (SELECT * FROM ZICCLOUDSYNCINGOBJECT t WHERE t.ZNOTE = @pk AND t.ZPARENTATTACHMENT IS NULL AND ${live("t")} AND (t.ZTYPEUTI LIKE '%audio%' OR t.ZTYPEUTI IN (${utis})) ORDER BY t.Z_PK) a;`,
|
|
55439
|
+
account
|
|
55440
|
+
].join("\n");
|
|
55441
|
+
}
|
|
55442
|
+
function accountDirsFor(containerDir, accountIdentifier) {
|
|
55443
|
+
const own = resolveAccountDir(containerDir, accountIdentifier);
|
|
55444
|
+
if (own) return [own];
|
|
55445
|
+
let names;
|
|
55446
|
+
try {
|
|
55447
|
+
names = readdirSync4(join25(containerDir, "Accounts")).sort();
|
|
55448
|
+
} catch {
|
|
55449
|
+
return [];
|
|
55450
|
+
}
|
|
55451
|
+
return names.map((name) => safeComponent(name) ? resolveAccountDir(containerDir, name) : null).filter((dir) => dir !== null);
|
|
55452
|
+
}
|
|
55453
|
+
function resolveMediaPath(media, accountIdentifier = null, containerDir = NOTES_CONTAINER_DIR) {
|
|
55454
|
+
const id2 = safeComponent(media?.identifier);
|
|
55455
|
+
const filename = safeComponent(media?.filename);
|
|
55456
|
+
if (!id2 || !filename) return null;
|
|
55457
|
+
const generation = safeComponent(media?.generation);
|
|
55458
|
+
for (const dir of accountDirsFor(containerDir, accountIdentifier)) {
|
|
55459
|
+
const base = join25(dir, "Media", id2);
|
|
55460
|
+
const candidates = generation ? [join25(base, generation, filename), join25(base, filename)] : [join25(base, filename)];
|
|
55461
|
+
for (const candidate of candidates) {
|
|
55462
|
+
const real = realInside(candidate, dir);
|
|
55463
|
+
if (real && isFile2(real)) return real;
|
|
55464
|
+
}
|
|
55465
|
+
}
|
|
55466
|
+
return null;
|
|
55467
|
+
}
|
|
55468
|
+
function isFile2(path10) {
|
|
55469
|
+
try {
|
|
55470
|
+
return statSync4(path10).isFile();
|
|
55471
|
+
} catch {
|
|
55472
|
+
return false;
|
|
55473
|
+
}
|
|
55474
|
+
}
|
|
55475
|
+
var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
55476
|
+
function readAudioAssets(noteId3, options = {}) {
|
|
55477
|
+
const { store, pk } = parseNoteId2(noteId3);
|
|
55478
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
|
|
55479
|
+
const columns = readColumns(dbPath2);
|
|
55480
|
+
const missing = ["ZMEDIA", "ZPARENTATTACHMENT", "ZFILENAME"].filter((c) => !columns.has(c));
|
|
55481
|
+
if (missing.length)
|
|
55482
|
+
throw new NoteStoreError(
|
|
55483
|
+
`This macOS version's Notes database lacks ${missing.join(", ")}; audio files cannot be located.`,
|
|
55484
|
+
"schema"
|
|
55485
|
+
);
|
|
55486
|
+
const generation = columns.has("ZGENERATION1") ? "ZGENERATION1" : columns.has("ZGENERATION") ? "ZGENERATION" : null;
|
|
55487
|
+
const [stateLine, rowsLine, accountLine] = queryNoteScoped(
|
|
55488
|
+
audioRowsSql(columns, generation),
|
|
55489
|
+
pk,
|
|
55490
|
+
dbPath2
|
|
55491
|
+
);
|
|
55492
|
+
assertNoteReadable(stateLine, noteId3);
|
|
55493
|
+
const rows = JSON.parse(rowsLine || "[]");
|
|
55494
|
+
const account = accountLine?.trim() || null;
|
|
55495
|
+
const attachmentId = (rowPk) => `x-coredata://${store}/ICAttachment/p${rowPk}`;
|
|
55496
|
+
return rows.map((row) => {
|
|
55497
|
+
const sources = row.children?.length ? row.children : [{ pk: row.pk, identifier: row.identifier, duration: row.duration, media: row.media }];
|
|
55498
|
+
const takes = sources.map((take) => ({
|
|
55499
|
+
attachmentId: attachmentId(take.pk),
|
|
55500
|
+
identifier: take.identifier ?? "",
|
|
55501
|
+
durationSeconds: positive(take.duration),
|
|
55502
|
+
path: resolveMediaPath(take.media, account, options.containerDir)
|
|
55503
|
+
}));
|
|
55504
|
+
const total = takes.reduce((sum, t) => sum + (t.durationSeconds ?? 0), 0);
|
|
55505
|
+
return {
|
|
55506
|
+
pk: row.pk,
|
|
55507
|
+
attachmentId: attachmentId(row.pk),
|
|
55508
|
+
identifier: row.identifier ?? "",
|
|
55509
|
+
typeUti: row.uti,
|
|
55510
|
+
durationSeconds: positive(row.duration) ?? positive(total),
|
|
55511
|
+
takes
|
|
55512
|
+
};
|
|
55513
|
+
});
|
|
55514
|
+
}
|
|
55515
|
+
function countWords2(text2) {
|
|
55516
|
+
return text2.split(/\s+/u).filter((w) => /[\p{L}\p{N}]/u.test(w)).length;
|
|
55517
|
+
}
|
|
55518
|
+
|
|
55519
|
+
// src/services/noteTranscription.ts
|
|
55520
|
+
var DEFAULT_TRANSCRIPTION_LOCALE = "en-US";
|
|
55521
|
+
var LOCALE_PATTERN = /^[A-Za-z]{2,3}(?:[-_][A-Za-z0-9]{2,8}){0,3}$/;
|
|
55522
|
+
var TRANSCRIBE_MAX_SECONDS = { default: 900, min: 30, max: 3600 };
|
|
55523
|
+
function takeDeadlineSeconds(durationSeconds) {
|
|
55524
|
+
const duration3 = durationSeconds ?? 600;
|
|
55525
|
+
return Math.min(1800, Math.max(60, Math.ceil(duration3 * 1.5) + 60));
|
|
55526
|
+
}
|
|
55527
|
+
var KILL_GRACE_MS = 3e4;
|
|
55528
|
+
var BUDGET_MARGIN_MS = 1e4;
|
|
55529
|
+
var MIN_TAKE_SECONDS = 5;
|
|
55530
|
+
var CALL_WIDE_CODES = /* @__PURE__ */ new Set([
|
|
55531
|
+
"permission_required",
|
|
55532
|
+
"asset_unavailable",
|
|
55533
|
+
"unsupported_locale",
|
|
55534
|
+
"speech_unavailable"
|
|
55535
|
+
]);
|
|
55536
|
+
var helperTranscriptSchema = external_exports.object({
|
|
55537
|
+
status: external_exports.literal("ok"),
|
|
55538
|
+
transcript: external_exports.string(),
|
|
55539
|
+
complete: external_exports.boolean(),
|
|
55540
|
+
stopReason: external_exports.string().optional(),
|
|
55541
|
+
engine: external_exports.string().optional(),
|
|
55542
|
+
durationSeconds: external_exports.number().optional()
|
|
55543
|
+
});
|
|
55544
|
+
async function transcribeTake(take, ctx) {
|
|
55545
|
+
const base = {
|
|
55546
|
+
attachmentId: take.attachmentId,
|
|
55547
|
+
identifier: take.identifier,
|
|
55548
|
+
status: "error",
|
|
55549
|
+
...take.durationSeconds !== null ? { durationSeconds: Math.round(take.durationSeconds) } : {}
|
|
55550
|
+
};
|
|
55551
|
+
const failed = (code, message) => ({
|
|
55552
|
+
take: { ...base, code, message },
|
|
55553
|
+
text: ""
|
|
55554
|
+
});
|
|
55555
|
+
if (!take.path)
|
|
55556
|
+
return failed(
|
|
55557
|
+
"asset_unavailable",
|
|
55558
|
+
"The audio file is not on this Mac (it may not have downloaded from iCloud yet)."
|
|
55559
|
+
);
|
|
55560
|
+
if (ctx.callWide) return failed(ctx.callWide.code, ctx.callWide.message);
|
|
55561
|
+
const remainingMs = ctx.deadlineAt - ctx.now();
|
|
55562
|
+
const deadline = Math.min(
|
|
55563
|
+
takeDeadlineSeconds(take.durationSeconds),
|
|
55564
|
+
Math.floor((remainingMs - BUDGET_MARGIN_MS) / 1e3)
|
|
55565
|
+
);
|
|
55566
|
+
if (deadline < MIN_TAKE_SECONDS)
|
|
55567
|
+
return failed(
|
|
55568
|
+
"time_limit",
|
|
55569
|
+
`Not started: the call's ${ctx.maxSeconds}-second limit was reached. Transcribe this recording on its own with attachmentId, or raise maxSeconds.`
|
|
55570
|
+
);
|
|
55571
|
+
let raw;
|
|
55572
|
+
try {
|
|
55573
|
+
raw = await callPublicHelperAsync(
|
|
55574
|
+
"transcribe",
|
|
55575
|
+
{
|
|
55576
|
+
path: take.path,
|
|
55577
|
+
locale: ctx.locale,
|
|
55578
|
+
timeoutSeconds: deadline,
|
|
55579
|
+
downloadAssets: ctx.downloadAssets
|
|
55580
|
+
},
|
|
55581
|
+
ctx.deps,
|
|
55582
|
+
{ timeoutMs: Math.min(deadline * 1e3 + KILL_GRACE_MS, remainingMs), signal: ctx.signal }
|
|
55583
|
+
);
|
|
55584
|
+
} catch (error2) {
|
|
55585
|
+
const code = error2 instanceof PublicHelperError ? error2.code : "internal_error";
|
|
55586
|
+
if (code === "aborted") throw error2;
|
|
55587
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
55588
|
+
if (CALL_WIDE_CODES.has(code)) ctx.callWide = { code, message };
|
|
55589
|
+
return {
|
|
55590
|
+
take: {
|
|
55591
|
+
...base,
|
|
55592
|
+
// A timeout means the helper was killed mid-run: the outcome is unknown.
|
|
55593
|
+
status: code === "timeout" ? "indeterminate" : "error",
|
|
55594
|
+
code,
|
|
55595
|
+
message
|
|
55596
|
+
},
|
|
55597
|
+
text: ""
|
|
55598
|
+
};
|
|
55599
|
+
}
|
|
55600
|
+
const parsed = helperTranscriptSchema.safeParse(raw);
|
|
55601
|
+
if (!parsed.success) return failed("invalid_response", "Unexpected helper response.");
|
|
55602
|
+
const { transcript, complete, stopReason, engine, durationSeconds } = parsed.data;
|
|
55603
|
+
const text2 = transcript.trim();
|
|
55604
|
+
return {
|
|
55605
|
+
take: {
|
|
55606
|
+
...base,
|
|
55607
|
+
status: complete ? "ok" : text2 ? "partial" : "indeterminate",
|
|
55608
|
+
...complete ? {} : { code: "incomplete", message: stopReason ?? "stopped early" },
|
|
55609
|
+
...durationSeconds !== void 0 ? { durationSeconds } : {},
|
|
55610
|
+
wordCount: countWords2(text2),
|
|
55611
|
+
...engine ? { engine } : {}
|
|
55612
|
+
},
|
|
55613
|
+
text: text2
|
|
55614
|
+
};
|
|
55615
|
+
}
|
|
55616
|
+
function combineStatus(parts) {
|
|
55617
|
+
if (parts.length && parts.every((p) => p.status === "ok")) return "ok";
|
|
55618
|
+
if (parts.some((p) => p.hasText)) return "partial";
|
|
55619
|
+
if (parts.some((p) => p.status === "indeterminate")) return "indeterminate";
|
|
55620
|
+
return "error";
|
|
55621
|
+
}
|
|
55622
|
+
async function transcribeRecording(asset, includeText, ctx) {
|
|
55623
|
+
const outcomes = [];
|
|
55624
|
+
for (const take of asset.takes) outcomes.push(await transcribeTake(take, ctx));
|
|
55625
|
+
const transcript = outcomes.map((o) => o.text).filter(Boolean).join("\n\n");
|
|
55626
|
+
const status = combineStatus(
|
|
55627
|
+
outcomes.map((o) => ({ status: o.take.status, hasText: o.text.length > 0 }))
|
|
55628
|
+
);
|
|
55629
|
+
const firstProblem = outcomes.find((o) => o.take.status !== "ok")?.take;
|
|
55630
|
+
return {
|
|
55631
|
+
attachmentId: asset.attachmentId,
|
|
55632
|
+
identifier: asset.identifier,
|
|
55633
|
+
typeUti: asset.typeUti,
|
|
55634
|
+
status,
|
|
55635
|
+
...status !== "ok" && firstProblem?.code ? { code: firstProblem.code, message: firstProblem.message } : {},
|
|
55636
|
+
...asset.durationSeconds !== null ? { durationSeconds: Math.round(asset.durationSeconds) } : {},
|
|
55637
|
+
wordCount: countWords2(transcript),
|
|
55638
|
+
...includeText ? { transcript } : {},
|
|
55639
|
+
takes: outcomes.map((o) => o.take)
|
|
55640
|
+
};
|
|
55641
|
+
}
|
|
55642
|
+
async function transcribeNoteAudio(noteId3, options = {}) {
|
|
55643
|
+
const locale = options.locale ?? DEFAULT_TRANSCRIPTION_LOCALE;
|
|
55644
|
+
if (!LOCALE_PATTERN.test(locale))
|
|
55645
|
+
throw new PublicHelperError("invalid_request", `"${locale}" is not a BCP-47 locale.`);
|
|
55646
|
+
const maxSeconds = options.maxSeconds ?? TRANSCRIBE_MAX_SECONDS.default;
|
|
55647
|
+
if (!Number.isInteger(maxSeconds) || maxSeconds < TRANSCRIBE_MAX_SECONDS.min || maxSeconds > TRANSCRIBE_MAX_SECONDS.max)
|
|
55648
|
+
throw new PublicHelperError(
|
|
55649
|
+
"invalid_request",
|
|
55650
|
+
`maxSeconds must be a whole number from ${TRANSCRIBE_MAX_SECONDS.min} to ${TRANSCRIBE_MAX_SECONDS.max}.`
|
|
55651
|
+
);
|
|
55652
|
+
const deps = options.deps ?? defaultPublicHelperDeps();
|
|
55653
|
+
let assets = (options.readAssets ?? ((id2) => readAudioAssets(id2)))(noteId3);
|
|
55654
|
+
if (options.attachmentId) {
|
|
55655
|
+
assets = assets.filter((a) => a.attachmentId === options.attachmentId);
|
|
55656
|
+
if (assets.length === 0)
|
|
55657
|
+
throw new PublicHelperError(
|
|
55658
|
+
"attachment_not_found",
|
|
55659
|
+
`No audio attachment ${options.attachmentId} in note ${noteId3}.`
|
|
55660
|
+
);
|
|
55661
|
+
}
|
|
55662
|
+
const empty = { id: noteId3, locale, recordingCount: 0, recordings: [] };
|
|
55663
|
+
if (assets.length === 0) return { ...empty, status: "none" };
|
|
55664
|
+
const install = inspectPublicHelper(deps);
|
|
55665
|
+
if (!install.ready)
|
|
55666
|
+
throw new PublicHelperError(install.reason ?? "helper_not_installed", install.detail ?? "");
|
|
55667
|
+
const now = options.now ?? Date.now;
|
|
55668
|
+
const ctx = {
|
|
55669
|
+
locale,
|
|
55670
|
+
downloadAssets: options.downloadAssets ?? false,
|
|
55671
|
+
deps,
|
|
55672
|
+
signal: options.signal,
|
|
55673
|
+
now,
|
|
55674
|
+
deadlineAt: now() + maxSeconds * 1e3,
|
|
55675
|
+
maxSeconds,
|
|
55676
|
+
callWide: null
|
|
55677
|
+
};
|
|
55678
|
+
const recordings = [];
|
|
55679
|
+
for (const asset of assets)
|
|
55680
|
+
recordings.push(await transcribeRecording(asset, options.includeText ?? true, ctx));
|
|
55681
|
+
return {
|
|
55682
|
+
...empty,
|
|
55683
|
+
status: combineStatus(
|
|
55684
|
+
recordings.map((r) => ({ status: r.status, hasText: (r.wordCount ?? 0) > 0 }))
|
|
55685
|
+
),
|
|
55686
|
+
recordingCount: recordings.length,
|
|
55687
|
+
recordings
|
|
55688
|
+
};
|
|
55689
|
+
}
|
|
55690
|
+
function fitTranscriptions(result, maxBytes, measure = (r) => Buffer.byteLength(JSON.stringify(r))) {
|
|
55691
|
+
if (measure(result) <= maxBytes) return result;
|
|
55692
|
+
let next = result;
|
|
55693
|
+
for (let share = 0.5; measure(next) > maxBytes && share > 1e-4; share /= 2) {
|
|
55694
|
+
next = {
|
|
55695
|
+
...result,
|
|
55696
|
+
recordings: result.recordings.map(
|
|
55697
|
+
(r) => r.transcript ? {
|
|
55698
|
+
...r,
|
|
55699
|
+
transcript: r.transcript.slice(0, Math.floor(r.transcript.length * share)),
|
|
55700
|
+
transcriptTruncated: true
|
|
55701
|
+
} : r
|
|
55702
|
+
)
|
|
55703
|
+
};
|
|
55704
|
+
}
|
|
55705
|
+
return next;
|
|
55706
|
+
}
|
|
55707
|
+
function formatTranscription(result) {
|
|
55708
|
+
if (result.recordingCount === 0) return `No audio attachments in note ${result.id}.`;
|
|
55709
|
+
const lines = [
|
|
55710
|
+
`${result.recordingCount} audio attachment${result.recordingCount === 1 ? "" : "s"} in note ${result.id} (${result.status}, locale ${result.locale}):`
|
|
55711
|
+
];
|
|
55712
|
+
result.recordings.forEach((r, i) => {
|
|
55713
|
+
const facts = [
|
|
55714
|
+
`status ${r.status}`,
|
|
55715
|
+
...r.durationSeconds !== void 0 ? [`${r.durationSeconds} s`] : [],
|
|
55716
|
+
`${r.wordCount ?? 0} words`,
|
|
55717
|
+
...r.takes.length > 1 ? [`${r.takes.length} takes`] : [],
|
|
55718
|
+
...r.code ? [`${r.code}: ${r.message}`] : []
|
|
55719
|
+
];
|
|
55720
|
+
lines.push("", `[${i + 1}] ${r.attachmentId} (${facts.join(", ")})`);
|
|
55721
|
+
if (r.transcript)
|
|
55722
|
+
lines.push(r.transcriptTruncated ? `${r.transcript} [truncated]` : r.transcript);
|
|
55723
|
+
});
|
|
55724
|
+
return lines.join("\n");
|
|
55725
|
+
}
|
|
55726
|
+
|
|
55282
55727
|
// src/services/privateHelperBuild.ts
|
|
55283
55728
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
55284
55729
|
import {
|
|
55285
55730
|
chmodSync as chmodSync2,
|
|
55286
55731
|
existsSync as existsSync16,
|
|
55287
55732
|
mkdirSync as mkdirSync7,
|
|
55288
|
-
mkdtempSync as
|
|
55733
|
+
mkdtempSync as mkdtempSync7,
|
|
55289
55734
|
renameSync as renameSync2,
|
|
55290
|
-
rmSync as
|
|
55291
|
-
writeFileSync as
|
|
55735
|
+
rmSync as rmSync7,
|
|
55736
|
+
writeFileSync as writeFileSync6
|
|
55292
55737
|
} from "node:fs";
|
|
55293
55738
|
import { release as release4 } from "node:os";
|
|
55294
|
-
import { join as
|
|
55739
|
+
import { join as join27 } from "node:path";
|
|
55295
55740
|
|
|
55296
55741
|
// src/services/privateHelper.ts
|
|
55297
55742
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
55298
55743
|
import { createHash as createHash6 } from "node:crypto";
|
|
55299
55744
|
import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
|
|
55300
55745
|
import { homedir as homedir20 } from "node:os";
|
|
55301
|
-
import { dirname as dirname9, join as
|
|
55746
|
+
import { dirname as dirname9, join as join26, resolve as resolve6 } from "node:path";
|
|
55302
55747
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
55303
55748
|
var PRIVATE_HELPER_PROTOCOL = 1;
|
|
55304
55749
|
var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
|
|
@@ -55313,7 +55758,7 @@ var HELPER_BINARY_NAME = "apple-notes-private-helper";
|
|
|
55313
55758
|
var HELPER_SOURCE_RELATIVE = "native/private-helper/apple-notes-private-helper.m";
|
|
55314
55759
|
var MANIFEST_NAME = "manifest.json";
|
|
55315
55760
|
var DEFAULT_TIMEOUT_MS3 = 2e4;
|
|
55316
|
-
var
|
|
55761
|
+
var MAX_OUTPUT_BYTES3 = 4 * 1024 * 1024;
|
|
55317
55762
|
var manifestSchema = external_exports.object({
|
|
55318
55763
|
schemaVersion: external_exports.literal(1),
|
|
55319
55764
|
protocolVersion: external_exports.number().int(),
|
|
@@ -55326,7 +55771,7 @@ var manifestSchema = external_exports.object({
|
|
|
55326
55771
|
function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
|
|
55327
55772
|
let dir = fromDir;
|
|
55328
55773
|
for (; ; ) {
|
|
55329
|
-
const candidate =
|
|
55774
|
+
const candidate = join26(dir, "package.json");
|
|
55330
55775
|
if (existsSync15(candidate)) {
|
|
55331
55776
|
try {
|
|
55332
55777
|
const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
|
|
@@ -55343,7 +55788,7 @@ function defaultDeps2(overrides = {}) {
|
|
|
55343
55788
|
return {
|
|
55344
55789
|
env: process.env,
|
|
55345
55790
|
platform: process.platform,
|
|
55346
|
-
sourcePath:
|
|
55791
|
+
sourcePath: join26(packageRoot2(), HELPER_SOURCE_RELATIVE),
|
|
55347
55792
|
exists: existsSync15,
|
|
55348
55793
|
readFile: (path10) => readFileSync5(path10),
|
|
55349
55794
|
spawn: spawnSync4,
|
|
@@ -55356,14 +55801,14 @@ function privateHelperEnabled(env = process.env) {
|
|
|
55356
55801
|
function helperInstallDir(env = process.env) {
|
|
55357
55802
|
const override = env[HELPER_DIR_ENV]?.trim();
|
|
55358
55803
|
if (override) return override;
|
|
55359
|
-
return
|
|
55804
|
+
return join26(homedir20(), "Library", "Application Support", "apple-notes-mcp", "private-helper");
|
|
55360
55805
|
}
|
|
55361
55806
|
function sha256Hex2(data) {
|
|
55362
55807
|
return createHash6("sha256").update(data).digest("hex");
|
|
55363
55808
|
}
|
|
55364
55809
|
function inspectInstallation(deps = defaultDeps2()) {
|
|
55365
55810
|
const installDir = helperInstallDir(deps.env);
|
|
55366
|
-
const binaryPath =
|
|
55811
|
+
const binaryPath = join26(installDir, HELPER_BINARY_NAME);
|
|
55367
55812
|
const base = {
|
|
55368
55813
|
installDir,
|
|
55369
55814
|
binaryPath,
|
|
@@ -55381,7 +55826,7 @@ function inspectInstallation(deps = defaultDeps2()) {
|
|
|
55381
55826
|
if (!deps.exists(deps.sourcePath))
|
|
55382
55827
|
return fail("helper_not_installed", `Packaged helper source is missing: ${deps.sourcePath}`);
|
|
55383
55828
|
base.expectedSourceSha256 = sha256Hex2(deps.readFile(deps.sourcePath));
|
|
55384
|
-
const manifestPath =
|
|
55829
|
+
const manifestPath = join26(installDir, MANIFEST_NAME);
|
|
55385
55830
|
if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
|
|
55386
55831
|
return fail(
|
|
55387
55832
|
"helper_not_installed",
|
|
@@ -55497,7 +55942,7 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
|
|
|
55497
55942
|
encoding: "utf8",
|
|
55498
55943
|
timeout,
|
|
55499
55944
|
killSignal: "SIGKILL",
|
|
55500
|
-
maxBuffer:
|
|
55945
|
+
maxBuffer: MAX_OUTPUT_BYTES3,
|
|
55501
55946
|
env: deps.env
|
|
55502
55947
|
});
|
|
55503
55948
|
const errno = result.error?.code;
|
|
@@ -55696,9 +56141,9 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
|
|
|
55696
56141
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
55697
56142
|
const installDir = helperInstallDir(deps.env);
|
|
55698
56143
|
mkdirSync7(installDir, { recursive: true, mode: 448 });
|
|
55699
|
-
const staging =
|
|
56144
|
+
const staging = mkdtempSync7(join27(installDir, ".staging-"));
|
|
55700
56145
|
try {
|
|
55701
|
-
const stagedBinary =
|
|
56146
|
+
const stagedBinary = join27(staging, HELPER_BINARY_NAME);
|
|
55702
56147
|
const compile = deps.spawn(
|
|
55703
56148
|
"/usr/bin/xcrun",
|
|
55704
56149
|
compileArguments(deps.sourcePath, stagedBinary, sourceSha),
|
|
@@ -55775,13 +56220,13 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
|
|
|
55775
56220
|
compiler
|
|
55776
56221
|
};
|
|
55777
56222
|
chmodSync2(stagedBinary, 448);
|
|
55778
|
-
renameSync2(stagedBinary,
|
|
55779
|
-
|
|
56223
|
+
renameSync2(stagedBinary, join27(installDir, HELPER_BINARY_NAME));
|
|
56224
|
+
writeFileSync6(join27(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
|
|
55780
56225
|
mode: 384
|
|
55781
56226
|
});
|
|
55782
56227
|
steps.push({ step: "install", ok: true, detail: installDir });
|
|
55783
56228
|
} finally {
|
|
55784
|
-
if (existsSync16(staging))
|
|
56229
|
+
if (existsSync16(staging)) rmSync7(staging, { recursive: true, force: true });
|
|
55785
56230
|
}
|
|
55786
56231
|
const installation = inspectInstallation(deps);
|
|
55787
56232
|
steps.push({
|
|
@@ -55970,6 +56415,16 @@ function withErrorHandling(handler, errorPrefix) {
|
|
|
55970
56415
|
}
|
|
55971
56416
|
};
|
|
55972
56417
|
}
|
|
56418
|
+
function withAsyncErrorHandling(handler, errorPrefix) {
|
|
56419
|
+
return async (params, extra) => {
|
|
56420
|
+
try {
|
|
56421
|
+
return await handler(params, extra?.signal);
|
|
56422
|
+
} catch (error2) {
|
|
56423
|
+
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
56424
|
+
return errorResponse(`${errorPrefix}: ${message}`, error2);
|
|
56425
|
+
}
|
|
56426
|
+
};
|
|
56427
|
+
}
|
|
55973
56428
|
var MAX = {
|
|
55974
56429
|
TITLE: 2e3,
|
|
55975
56430
|
CONTENT: 5 * 1024 * 1024,
|
|
@@ -59006,6 +59461,49 @@ registerTool(
|
|
|
59006
59461
|
});
|
|
59007
59462
|
}, "Error reading drawings")
|
|
59008
59463
|
);
|
|
59464
|
+
registerTool(
|
|
59465
|
+
"transcribe-note-audio",
|
|
59466
|
+
{
|
|
59467
|
+
description: "Use when: you need the words spoken in a note's voice recordings or audio attachments, transcribed now on this Mac, by note id.\nReturns: per audio attachment a status (ok / partial / error / indeterminate), duration, word count, per-take results, and the transcript; overall status ok / partial / error / indeterminate / none.\nDo not use when: you only need the audio file (save-attachment) or a note has no audio.\nNote: read-only; recognition runs entirely on-device (never sent to a server). Needs Full Disk Access and the public native helper built once with `apple-notes-mcp setup --public-helper`. Long recordings take time: pass attachmentId to transcribe one at a time. An indeterminate result means the helper timed out; retrying may succeed. Never prompts: code permission_required means the user must allow the host app under System Settings > Privacy & Security > Speech Recognition. asset_unavailable can mean the language's speech model is not installed; pass downloadAssets: true only if the user agrees to the download.",
|
|
59468
|
+
inputSchema: {
|
|
59469
|
+
id: noteIdInput,
|
|
59470
|
+
locale: external_exports.string().max(35).optional().describe('BCP-47 language of the speech, e.g. "en-US" (default), "it-IT", "fr-FR"'),
|
|
59471
|
+
attachmentId: external_exports.string().max(MAX.ATTACHMENT_ID).optional().describe("Only transcribe this audio attachment (x-coredata ICAttachment id)"),
|
|
59472
|
+
includeText: external_exports.boolean().optional().describe("Include transcript text (default true); false returns statuses and counts only"),
|
|
59473
|
+
downloadAssets: external_exports.boolean().optional().describe(
|
|
59474
|
+
"Let macOS download the language's on-device speech model if it is missing (default false: returns asset_unavailable at once)"
|
|
59475
|
+
),
|
|
59476
|
+
maxSeconds: external_exports.number().int().min(TRANSCRIBE_MAX_SECONDS.min).max(TRANSCRIBE_MAX_SECONDS.max).optional().describe(
|
|
59477
|
+
`Total time budget for the call in seconds (default ${TRANSCRIBE_MAX_SECONDS.default}); recordings not started in time report code time_limit`
|
|
59478
|
+
)
|
|
59479
|
+
},
|
|
59480
|
+
outputSchema: {
|
|
59481
|
+
id: external_exports.string().optional(),
|
|
59482
|
+
locale: external_exports.string().optional(),
|
|
59483
|
+
status: external_exports.string().optional(),
|
|
59484
|
+
recordingCount: external_exports.number().optional(),
|
|
59485
|
+
recordings: external_exports.array(external_exports.object({}).passthrough()).optional()
|
|
59486
|
+
}
|
|
59487
|
+
},
|
|
59488
|
+
withAsyncErrorHandling(async (params, signal) => {
|
|
59489
|
+
const { id: id2, locale, attachmentId, includeText, downloadAssets, maxSeconds } = params;
|
|
59490
|
+
const result = fitTranscriptions(
|
|
59491
|
+
await transcribeNoteAudio(id2, {
|
|
59492
|
+
locale,
|
|
59493
|
+
attachmentId,
|
|
59494
|
+
includeText,
|
|
59495
|
+
downloadAssets,
|
|
59496
|
+
maxSeconds,
|
|
59497
|
+
signal
|
|
59498
|
+
}),
|
|
59499
|
+
exportMaxResponseBytes()
|
|
59500
|
+
);
|
|
59501
|
+
return successResponse(
|
|
59502
|
+
formatTranscription(result),
|
|
59503
|
+
result
|
|
59504
|
+
);
|
|
59505
|
+
}, "Error transcribing audio")
|
|
59506
|
+
);
|
|
59009
59507
|
registerTool(
|
|
59010
59508
|
"list-recent-notes",
|
|
59011
59509
|
{
|