apple-notes-mcp 2.6.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.js +136 -74
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -38706,12 +38706,14 @@ function isTimeoutError(error2) {
|
|
|
38706
38706
|
}
|
|
38707
38707
|
return false;
|
|
38708
38708
|
}
|
|
38709
|
+
var BULK_LIST_MUTATION_ERROR = "Notes changed during listing";
|
|
38709
38710
|
var RETRYABLE_ERROR_PATTERNS = [
|
|
38710
38711
|
/timed? out/i,
|
|
38711
38712
|
/not responding/i,
|
|
38712
38713
|
/connection.*invalid/i,
|
|
38713
38714
|
/lost connection/i,
|
|
38714
|
-
/busy/i
|
|
38715
|
+
/busy/i,
|
|
38716
|
+
/changed during listing/i
|
|
38715
38717
|
];
|
|
38716
38718
|
function isRetryableError(errorMessage) {
|
|
38717
38719
|
return RETRYABLE_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage));
|
|
@@ -38770,6 +38772,13 @@ var ERROR_MAPPINGS = [
|
|
|
38770
38772
|
pattern: /password protected|locked note/i,
|
|
38771
38773
|
message: "Note is password-protected. Unlock it in Notes.app first."
|
|
38772
38774
|
},
|
|
38775
|
+
// Mid-listing library mutation detected by a bulk-list count guard (#86).
|
|
38776
|
+
// The message must keep the phrase "changed during listing" so
|
|
38777
|
+
// RETRYABLE_ERROR_PATTERNS still matches it after this mapping.
|
|
38778
|
+
{
|
|
38779
|
+
pattern: /changed during listing/i,
|
|
38780
|
+
message: "Notes changed during listing (an iCloud sync may have landed mid-read). The operation is retried automatically; run it again if this persists."
|
|
38781
|
+
},
|
|
38773
38782
|
// Syntax/script errors (usually programming bugs)
|
|
38774
38783
|
{
|
|
38775
38784
|
pattern: /syntax error|expected/i,
|
|
@@ -39284,6 +39293,12 @@ function buildAppleScriptDateVar(date3, varName = "thresholdDate") {
|
|
|
39284
39293
|
const timeInSeconds = date3.getHours() * 3600 + date3.getMinutes() * 60 + date3.getSeconds();
|
|
39285
39294
|
return [
|
|
39286
39295
|
`set ${varName} to current date`,
|
|
39296
|
+
// Reset day to 1 BEFORE assigning month: AppleScript date components roll
|
|
39297
|
+
// over, so setting month to (say) June while the variable still holds the
|
|
39298
|
+
// 31st inherited from `current date` produces July 1 (June 31 doesn't
|
|
39299
|
+
// exist), and the day assignment below then lands in the wrong month.
|
|
39300
|
+
// Day 1 exists in every month, so year/month can never roll over. (#86)
|
|
39301
|
+
`set day of ${varName} to 1`,
|
|
39287
39302
|
`set year of ${varName} to ${year}`,
|
|
39288
39303
|
`set month of ${varName} to ${month}`,
|
|
39289
39304
|
`set day of ${varName} to ${day}`,
|
|
@@ -39917,6 +39932,101 @@ var AppleNotesManager = class {
|
|
|
39917
39932
|
}
|
|
39918
39933
|
return true;
|
|
39919
39934
|
}
|
|
39935
|
+
/**
|
|
39936
|
+
* Builds the AppleScript body for a bulk note listing.
|
|
39937
|
+
*
|
|
39938
|
+
* Names, ids, and (when date-filtering) modification dates are fetched as
|
|
39939
|
+
* whole-list Apple Events instead of two events per note; per-note round
|
|
39940
|
+
* trips scale linearly and push large libraries past client tool timeouts
|
|
39941
|
+
* (#86). The lists are separate snapshots of a live, syncing collection, so
|
|
39942
|
+
* every script guards that they are the same length before zipping them by
|
|
39943
|
+
* index — a mid-listing mutation would otherwise silently mispair names and
|
|
39944
|
+
* ids (grow) or read past the end of a list (shrink). On mismatch the
|
|
39945
|
+
* script raises BULK_LIST_MUTATION_ERROR, which executeAppleScript treats
|
|
39946
|
+
* as retryable, re-running the whole script on a fresh snapshot. A length
|
|
39947
|
+
* check cannot see an exactly-offsetting delete+create landing in the
|
|
39948
|
+
* milliseconds between two fetches; that residual window is accepted —
|
|
39949
|
+
* closing it would cost an extra whole-list fetch per listing.
|
|
39950
|
+
*
|
|
39951
|
+
* @param folderRef - Optional AppleScript folder reference to scope to
|
|
39952
|
+
* @param dateSetup - AppleScript defining thresholdDate; enables date filtering
|
|
39953
|
+
* @param sliceLimit - Fetch only the first N notes (mutually exclusive with
|
|
39954
|
+
* dateSetup: a date filter must scan every note's date). The script then
|
|
39955
|
+
* returns the total note count as a leading record so the caller can
|
|
39956
|
+
* detect a dedup shortfall and fall back to a full fetch.
|
|
39957
|
+
*/
|
|
39958
|
+
buildBulkListCommand(opts) {
|
|
39959
|
+
const { folderRef, dateSetup, sliceLimit } = opts;
|
|
39960
|
+
const fullSource = folderRef ? `notes of ${folderRef}` : "notes";
|
|
39961
|
+
const countGuard = (listVar) => `if (count of ${listVar}) is not (count of noteNames) then error "${BULK_LIST_MUTATION_ERROR}"`;
|
|
39962
|
+
if (sliceLimit !== void 0) {
|
|
39963
|
+
const slicedSource = folderRef ? `(notes 1 thru fetchCount of ${folderRef})` : `(notes 1 thru fetchCount)`;
|
|
39964
|
+
return `
|
|
39965
|
+
set totalCount to count of ${fullSource}
|
|
39966
|
+
set fetchCount to ${sliceLimit}
|
|
39967
|
+
if fetchCount > totalCount then set fetchCount to totalCount
|
|
39968
|
+
set resultList to {}
|
|
39969
|
+
if fetchCount > 0 then
|
|
39970
|
+
try
|
|
39971
|
+
set noteNames to name of ${slicedSource}
|
|
39972
|
+
set noteIds to id of ${slicedSource}
|
|
39973
|
+
on error errMsg number errNum
|
|
39974
|
+
if errNum is -1719 or errNum is -1728 then
|
|
39975
|
+
error "${BULK_LIST_MUTATION_ERROR}"
|
|
39976
|
+
else
|
|
39977
|
+
error errMsg number errNum
|
|
39978
|
+
end if
|
|
39979
|
+
end try
|
|
39980
|
+
${countGuard("noteIds")}
|
|
39981
|
+
repeat with i from 1 to count of noteNames
|
|
39982
|
+
set end of resultList to (item i of noteNames) & ${AS_FIELD_SEP} & (item i of noteIds)
|
|
39983
|
+
end repeat
|
|
39984
|
+
end if
|
|
39985
|
+
set AppleScript's text item delimiters to ${AS_RECORD_SEP}
|
|
39986
|
+
return (totalCount as text) & ${AS_RECORD_SEP} & (resultList as text)
|
|
39987
|
+
`;
|
|
39988
|
+
}
|
|
39989
|
+
const dateFetch = dateSetup ? `set noteDates to modification date of ${fullSource}
|
|
39990
|
+
` : "";
|
|
39991
|
+
const dateCountGuard = dateSetup ? `${countGuard("noteDates")}
|
|
39992
|
+
` : "";
|
|
39993
|
+
const dateGuardOpen = dateSetup ? `if (item i of noteDates) >= thresholdDate then
|
|
39994
|
+
` : "";
|
|
39995
|
+
const dateGuardClose = dateSetup ? `
|
|
39996
|
+
end if` : "";
|
|
39997
|
+
return `
|
|
39998
|
+
${dateSetup ?? ""}set noteNames to name of ${fullSource}
|
|
39999
|
+
set noteIds to id of ${fullSource}
|
|
40000
|
+
${dateFetch}${countGuard("noteIds")}
|
|
40001
|
+
${dateCountGuard}set resultList to {}
|
|
40002
|
+
repeat with i from 1 to count of noteNames
|
|
40003
|
+
${dateGuardOpen}set end of resultList to (item i of noteNames) & ${AS_FIELD_SEP} & (item i of noteIds)${dateGuardClose}
|
|
40004
|
+
end repeat
|
|
40005
|
+
set AppleScript's text item delimiters to ${AS_RECORD_SEP}
|
|
40006
|
+
return resultList as text
|
|
40007
|
+
`;
|
|
40008
|
+
}
|
|
40009
|
+
/**
|
|
40010
|
+
* Parses bulk listing output into deduplicated note titles.
|
|
40011
|
+
*
|
|
40012
|
+
* Duplicate CoreData references are deduped by id; the limit is applied
|
|
40013
|
+
* after dedup so duplicates never count against it.
|
|
40014
|
+
*/
|
|
40015
|
+
parseBulkListOutput(output, safeLimit) {
|
|
40016
|
+
if (!output.trim()) return [];
|
|
40017
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
40018
|
+
const titles = [];
|
|
40019
|
+
for (const item of output.split(RECORD_SEP)) {
|
|
40020
|
+
const [title, id] = item.split(FIELD_SEP);
|
|
40021
|
+
if (!title?.trim()) continue;
|
|
40022
|
+
const noteId = id?.trim() || generateFallbackId();
|
|
40023
|
+
if (seenIds.has(noteId)) continue;
|
|
40024
|
+
seenIds.add(noteId);
|
|
40025
|
+
titles.push(title.trim());
|
|
40026
|
+
if (safeLimit !== void 0 && titles.length >= safeLimit) break;
|
|
40027
|
+
}
|
|
40028
|
+
return titles;
|
|
40029
|
+
}
|
|
39920
40030
|
/**
|
|
39921
40031
|
* Lists all notes in an account, optionally filtered by folder, date, and limit.
|
|
39922
40032
|
*
|
|
@@ -39929,89 +40039,41 @@ var AppleNotesManager = class {
|
|
|
39929
40039
|
listNotes(account, folder, modifiedSince, limit) {
|
|
39930
40040
|
const targetAccount = this.resolveAccount(account);
|
|
39931
40041
|
const safeLimit = limit !== void 0 && limit > 0 ? Math.floor(limit) : void 0;
|
|
39932
|
-
|
|
39933
|
-
|
|
39934
|
-
|
|
39935
|
-
|
|
39936
|
-
if (
|
|
39937
|
-
|
|
39938
|
-
|
|
39939
|
-
|
|
39940
|
-
|
|
39941
|
-
|
|
39942
|
-
|
|
39943
|
-
|
|
39944
|
-
|
|
39945
|
-
const listCommand2 = `
|
|
39946
|
-
${dateSetup}set resultList to {}
|
|
39947
|
-
set seenIds to {}
|
|
39948
|
-
repeat with n in ${notesSource}
|
|
39949
|
-
try
|
|
39950
|
-
set noteName to name of n
|
|
39951
|
-
set noteId to id of n
|
|
39952
|
-
if seenIds does not contain noteId then
|
|
39953
|
-
set end of seenIds to noteId
|
|
39954
|
-
set end of resultList to noteName & ${AS_FIELD_SEP} & noteId${limitCheck}
|
|
39955
|
-
end if
|
|
39956
|
-
end try
|
|
39957
|
-
end repeat
|
|
39958
|
-
set AppleScript's text item delimiters to ${AS_RECORD_SEP}
|
|
39959
|
-
return resultList as text
|
|
39960
|
-
`;
|
|
39961
|
-
const script2 = buildAccountScopedScript({ account: targetAccount }, listCommand2);
|
|
40042
|
+
const folderRef = folder ? buildFolderReference(folder) : void 0;
|
|
40043
|
+
let dateSetup;
|
|
40044
|
+
if (modifiedSince) {
|
|
40045
|
+
const date3 = new Date(modifiedSince);
|
|
40046
|
+
if (!isNaN(date3.getTime())) {
|
|
40047
|
+
dateSetup = buildAppleScriptDateVar(date3) + "\n";
|
|
40048
|
+
}
|
|
40049
|
+
}
|
|
40050
|
+
if (safeLimit !== void 0 && !dateSetup) {
|
|
40051
|
+
const script2 = buildAccountScopedScript(
|
|
40052
|
+
{ account: targetAccount },
|
|
40053
|
+
this.buildBulkListCommand({ folderRef, sliceLimit: safeLimit })
|
|
40054
|
+
);
|
|
39962
40055
|
const result2 = executeAppleScript(script2);
|
|
39963
40056
|
if (!result2.success) {
|
|
39964
40057
|
throw new Error(`Failed to list notes: ${result2.error ?? "unknown error"}`);
|
|
39965
40058
|
}
|
|
39966
|
-
|
|
39967
|
-
|
|
39968
|
-
|
|
39969
|
-
const
|
|
39970
|
-
const
|
|
39971
|
-
|
|
39972
|
-
|
|
39973
|
-
if (!title?.trim()) continue;
|
|
39974
|
-
const noteId = id?.trim() || generateFallbackId();
|
|
39975
|
-
if (seenIds2.has(noteId)) continue;
|
|
39976
|
-
seenIds2.add(noteId);
|
|
39977
|
-
titles2.push(title.trim());
|
|
40059
|
+
const sepIdx = result2.output.indexOf(RECORD_SEP);
|
|
40060
|
+
const header = sepIdx === -1 ? result2.output : result2.output.slice(0, sepIdx);
|
|
40061
|
+
const totalCount = Number.parseInt(header.trim(), 10);
|
|
40062
|
+
const records = sepIdx === -1 ? "" : result2.output.slice(sepIdx + 1);
|
|
40063
|
+
const titles = this.parseBulkListOutput(records, safeLimit);
|
|
40064
|
+
if (!Number.isNaN(totalCount) && (titles.length >= safeLimit || totalCount <= safeLimit)) {
|
|
40065
|
+
return titles;
|
|
39978
40066
|
}
|
|
39979
|
-
return titles2;
|
|
39980
40067
|
}
|
|
39981
|
-
const
|
|
39982
|
-
|
|
39983
|
-
|
|
39984
|
-
|
|
39985
|
-
repeat with n in ${notesRef}
|
|
39986
|
-
try
|
|
39987
|
-
set noteName to name of n
|
|
39988
|
-
set noteId to id of n
|
|
39989
|
-
if seenIds does not contain noteId then
|
|
39990
|
-
set end of seenIds to noteId
|
|
39991
|
-
set end of resultList to noteName & ${AS_FIELD_SEP} & noteId
|
|
39992
|
-
end if
|
|
39993
|
-
end try
|
|
39994
|
-
end repeat
|
|
39995
|
-
set AppleScript's text item delimiters to ${AS_RECORD_SEP}
|
|
39996
|
-
return resultList as text
|
|
39997
|
-
`;
|
|
39998
|
-
const script = buildAccountScopedScript({ account: targetAccount }, listCommand);
|
|
40068
|
+
const script = buildAccountScopedScript(
|
|
40069
|
+
{ account: targetAccount },
|
|
40070
|
+
this.buildBulkListCommand({ folderRef, dateSetup })
|
|
40071
|
+
);
|
|
39999
40072
|
const result = executeAppleScript(script);
|
|
40000
40073
|
if (!result.success) {
|
|
40001
40074
|
throw new Error(`Failed to list notes: ${result.error ?? "unknown error"}`);
|
|
40002
40075
|
}
|
|
40003
|
-
|
|
40004
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
40005
|
-
const titles = [];
|
|
40006
|
-
for (const item of result.output.split(RECORD_SEP)) {
|
|
40007
|
-
const [title, id] = item.split(FIELD_SEP);
|
|
40008
|
-
if (!title?.trim()) continue;
|
|
40009
|
-
const noteId = id?.trim() || generateFallbackId();
|
|
40010
|
-
if (seenIds.has(noteId)) continue;
|
|
40011
|
-
seenIds.add(noteId);
|
|
40012
|
-
titles.push(title.trim());
|
|
40013
|
-
}
|
|
40014
|
-
return titles;
|
|
40076
|
+
return this.parseBulkListOutput(result.output, safeLimit);
|
|
40015
40077
|
}
|
|
40016
40078
|
/**
|
|
40017
40079
|
* Lists all shared (collaborative) notes across all accounts.
|
package/package.json
CHANGED