apple-notes-mcp 2.15.5 → 2.15.7
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 +62 -11
- package/build/index.js +348 -190
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -761,7 +761,7 @@ Disk Access.
|
|
|
761
761
|
|
|
762
762
|
#### `get-note-tables`
|
|
763
763
|
|
|
764
|
-
Reads
|
|
764
|
+
Reads a page of native tables in one exact note, in body order, from the NoteStore
|
|
765
765
|
database. Each table is returned as GitHub-flavored Markdown and as JSON `rows`
|
|
766
766
|
with stable `rowIds` and `columnIds`. Notes tables have no header row, so the
|
|
767
767
|
Markdown uses the first row as the header. Pipes are escaped as `\|`,
|
|
@@ -770,8 +770,26 @@ backslashes are doubled, and line breaks inside a cell become `<br>`.
|
|
|
770
770
|
| Parameter | Type | Required | Description |
|
|
771
771
|
|-----------|------|----------|-------------|
|
|
772
772
|
| `id` | string | Yes | Exact note ID (`x-coredata://…/ICNote/pNNN`) |
|
|
773
|
-
|
|
774
|
-
`
|
|
773
|
+
| `offset` | integer | No | Zero-based position of the first table (default 0); use `page.nextOffset` |
|
|
774
|
+
| `limit` | integer | No | Maximum tables per page (default and maximum 500) |
|
|
775
|
+
| `maxBytes` | integer | No | Full serialized response budget in UTF-8 bytes (default 4 MiB, minimum 1024 bytes, maximum 8 MiB) |
|
|
776
|
+
|
|
777
|
+
The byte budget includes both the text response and structured data, including
|
|
778
|
+
repeated Markdown and JSON escaping. Tables are returned whole. `tableCount`
|
|
779
|
+
and `page.totalAvailable` count all tables in the note; `page.returned` counts
|
|
780
|
+
the entries in this response. While `page.hasMore` is true, call again with
|
|
781
|
+
`offset: page.nextOffset` and the same `limit` and `maxBytes`. Each call reads
|
|
782
|
+
the note again, so finish paging before editing it.
|
|
783
|
+
|
|
784
|
+
If one table exceeds an otherwise empty page, its entry contains identifying
|
|
785
|
+
metadata, `complete: false`, `contentOmitted: true`, and a `reason`, without
|
|
786
|
+
partial rows or row/column IDs. The next offset advances past that entry. To
|
|
787
|
+
retrieve its content, retry that offset with a larger `maxBytes`, or export the
|
|
788
|
+
note to a file. If even the metadata exceeds the budget, the tool returns a
|
|
789
|
+
validation error. `page.stoppedAtSizeLimit` reports a byte-budget stop.
|
|
790
|
+
|
|
791
|
+
`tableCellsComplete` is false when any table or cell could not be decoded, or
|
|
792
|
+
when the current page omits a table's content for its byte budget. A
|
|
775
793
|
cell holding an embedded object is `null` in `rows`, listed in
|
|
776
794
|
`incompleteCells`, and shown as `[undecoded cell]` in Markdown. A table whose
|
|
777
795
|
data cannot be decoded at all has `complete: false`, a `reason`, and no rows.
|
|
@@ -1358,21 +1376,28 @@ actual destination folder ID matches the requested folder.
|
|
|
1358
1376
|
|
|
1359
1377
|
#### Folder scope guards
|
|
1360
1378
|
|
|
1361
|
-
`update-note`, `append-to-note`, `delete-note`,
|
|
1362
|
-
optional folder preconditions. Use exact
|
|
1379
|
+
`update-note`, `append-to-note`, `delete-note`, `move-note`, and
|
|
1380
|
+
`batch-move-notes` accept three optional folder preconditions. Use exact
|
|
1381
|
+
folder ids from `list-folders`.
|
|
1363
1382
|
|
|
1364
1383
|
- `ifFolderId`: the note must currently be in exactly this folder.
|
|
1365
1384
|
- `ifAncestorFolderId`: the note must be inside this folder or any of its
|
|
1366
1385
|
subfolders.
|
|
1367
1386
|
- `forbiddenAncestorFolderIds` (up to 50): the note must not be inside any of
|
|
1368
|
-
these folders or their subfolders. For `move-note
|
|
1369
|
-
be either.
|
|
1387
|
+
these folders or their subfolders. For `move-note` and `batch-move-notes`,
|
|
1388
|
+
the destination must not be either.
|
|
1370
1389
|
|
|
1371
1390
|
The checks read Notes.app's live folders inside the same AppleScript as the
|
|
1372
|
-
write, immediately before it
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1391
|
+
write, immediately before it. If the folder observed by the check fails a
|
|
1392
|
+
precondition, the write is refused with `Scope guard failed: …`. The check and
|
|
1393
|
+
write are separate Notes operations; another actor can move the note between
|
|
1394
|
+
them. A native append to a protected note runs through Shortcuts, so its
|
|
1395
|
+
precheck runs in a separate AppleScript before the append.
|
|
1396
|
+
|
|
1397
|
+
For `batch-move-notes`, the same preconditions apply to every supplied note ID.
|
|
1398
|
+
Each note is checked independently immediately before its move. A scope mismatch
|
|
1399
|
+
leaves that note unchanged and appears in its failure result; the remaining
|
|
1400
|
+
items continue. A batch can therefore move some notes and refuse others.
|
|
1376
1401
|
|
|
1377
1402
|
**Example - retire a note only while it is still in the inbox:**
|
|
1378
1403
|
```json
|
|
@@ -1897,9 +1922,35 @@ Moves multiple notes to a folder.
|
|
|
1897
1922
|
| `ids` | string[] | Yes | Array of note IDs to move (max 500 per request) |
|
|
1898
1923
|
| `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`). Must already exist — create it with [`create-folder`](#create-folder). A smart folder is refused for the whole call before any note moves |
|
|
1899
1924
|
| `account` | string | No | Account containing the folder |
|
|
1925
|
+
| `ifFolderId`, `ifAncestorFolderId`, `forbiddenAncestorFolderIds` | string, string, string[] | No | Shared folder preconditions applied independently to each note; see [Folder scope guards](#folder-scope-guards). Forbidden folders also apply to the destination |
|
|
1900
1926
|
|
|
1901
1927
|
**Returns:** Summary of successes and failures. Each success is reported only
|
|
1902
1928
|
after the note's actual container folder ID matches the destination folder ID.
|
|
1929
|
+
If every note fails, the call returns its existing error response; the exact
|
|
1930
|
+
note IDs and reasons remain in the failure text.
|
|
1931
|
+
|
|
1932
|
+
Failure rows carry `committed: false, indeterminate: false` only when that
|
|
1933
|
+
item was refused before its move. Move/readback failures and an unknown
|
|
1934
|
+
whole-script outcome carry `indeterminate: true`, with `committed` omitted.
|
|
1935
|
+
If every item fails, the batch reports `committed: false` only when every row
|
|
1936
|
+
is a proven pre-move refusal. Otherwise its error result is indeterminate;
|
|
1937
|
+
read each exact note ID before retrying, even if another row has a scope refusal.
|
|
1938
|
+
|
|
1939
|
+
**Example - move only notes that are still in the reviewed inbox:**
|
|
1940
|
+
```json
|
|
1941
|
+
{
|
|
1942
|
+
"ids": ["x-coredata://ABC123/ICNote/p456", "x-coredata://ABC123/ICNote/p457"],
|
|
1943
|
+
"folder": "Archive",
|
|
1944
|
+
"ifFolderId": "x-coredata://ABC123/ICFolder/p12"
|
|
1945
|
+
}
|
|
1946
|
+
```
|
|
1947
|
+
|
|
1948
|
+
The source-folder check runs immediately before each move in the same
|
|
1949
|
+
AppleScript. If the check finds that a note is no longer in that inbox, its
|
|
1950
|
+
result reports `Scope guard failed: …` while a note still there can move.
|
|
1951
|
+
The check does not lock the note against another actor moving it before the
|
|
1952
|
+
move. The batch is not a transaction, and successful moves are not rolled
|
|
1953
|
+
back after a refusal.
|
|
1903
1954
|
|
|
1904
1955
|
---
|
|
1905
1956
|
|
package/build/index.js
CHANGED
|
@@ -27885,8 +27885,8 @@ var ZodSet = class _ZodSet extends ZodType {
|
|
|
27885
27885
|
maxSize: { value: maxSize, message: errorUtil.toString(message) }
|
|
27886
27886
|
});
|
|
27887
27887
|
}
|
|
27888
|
-
size(
|
|
27889
|
-
return this.min(
|
|
27888
|
+
size(size2, message) {
|
|
27889
|
+
return this.min(size2, message).max(size2, message);
|
|
27890
27890
|
}
|
|
27891
27891
|
nonempty(message) {
|
|
27892
27892
|
return this.min(1, message);
|
|
@@ -36551,7 +36551,7 @@ var Protocol = class {
|
|
|
36551
36551
|
if (abortController.signal.aborted) {
|
|
36552
36552
|
return;
|
|
36553
36553
|
}
|
|
36554
|
-
const
|
|
36554
|
+
const response2 = {
|
|
36555
36555
|
result,
|
|
36556
36556
|
jsonrpc: "2.0",
|
|
36557
36557
|
id: request.id
|
|
@@ -36559,11 +36559,11 @@ var Protocol = class {
|
|
|
36559
36559
|
if (relatedTaskId && this._taskMessageQueue) {
|
|
36560
36560
|
await this._enqueueTaskMessage(relatedTaskId, {
|
|
36561
36561
|
type: "response",
|
|
36562
|
-
message:
|
|
36562
|
+
message: response2,
|
|
36563
36563
|
timestamp: Date.now()
|
|
36564
36564
|
}, capturedTransport?.sessionId);
|
|
36565
36565
|
} else {
|
|
36566
|
-
await capturedTransport?.send(
|
|
36566
|
+
await capturedTransport?.send(response2);
|
|
36567
36567
|
}
|
|
36568
36568
|
}, async (error2) => {
|
|
36569
36569
|
if (abortController.signal.aborted) {
|
|
@@ -36616,29 +36616,29 @@ var Protocol = class {
|
|
|
36616
36616
|
}
|
|
36617
36617
|
handler(params);
|
|
36618
36618
|
}
|
|
36619
|
-
_onresponse(
|
|
36620
|
-
const messageId = Number(
|
|
36619
|
+
_onresponse(response2) {
|
|
36620
|
+
const messageId = Number(response2.id);
|
|
36621
36621
|
const resolver = this._requestResolvers.get(messageId);
|
|
36622
36622
|
if (resolver) {
|
|
36623
36623
|
this._requestResolvers.delete(messageId);
|
|
36624
|
-
if (isJSONRPCResultResponse(
|
|
36625
|
-
resolver(
|
|
36624
|
+
if (isJSONRPCResultResponse(response2)) {
|
|
36625
|
+
resolver(response2);
|
|
36626
36626
|
} else {
|
|
36627
|
-
const error2 = new McpError(
|
|
36627
|
+
const error2 = new McpError(response2.error.code, response2.error.message, response2.error.data);
|
|
36628
36628
|
resolver(error2);
|
|
36629
36629
|
}
|
|
36630
36630
|
return;
|
|
36631
36631
|
}
|
|
36632
36632
|
const handler = this._responseHandlers.get(messageId);
|
|
36633
36633
|
if (handler === void 0) {
|
|
36634
|
-
this._onerror(new Error(`Received a response for an unknown message ID: ${JSON.stringify(
|
|
36634
|
+
this._onerror(new Error(`Received a response for an unknown message ID: ${JSON.stringify(response2)}`));
|
|
36635
36635
|
return;
|
|
36636
36636
|
}
|
|
36637
36637
|
this._responseHandlers.delete(messageId);
|
|
36638
36638
|
this._cleanupTimeout(messageId);
|
|
36639
36639
|
let isTaskResponse = false;
|
|
36640
|
-
if (isJSONRPCResultResponse(
|
|
36641
|
-
const result =
|
|
36640
|
+
if (isJSONRPCResultResponse(response2) && response2.result && typeof response2.result === "object") {
|
|
36641
|
+
const result = response2.result;
|
|
36642
36642
|
if (result.task && typeof result.task === "object") {
|
|
36643
36643
|
const task = result.task;
|
|
36644
36644
|
if (typeof task.taskId === "string") {
|
|
@@ -36650,10 +36650,10 @@ var Protocol = class {
|
|
|
36650
36650
|
if (!isTaskResponse) {
|
|
36651
36651
|
this._progressHandlers.delete(messageId);
|
|
36652
36652
|
}
|
|
36653
|
-
if (isJSONRPCResultResponse(
|
|
36654
|
-
handler(
|
|
36653
|
+
if (isJSONRPCResultResponse(response2)) {
|
|
36654
|
+
handler(response2);
|
|
36655
36655
|
} else {
|
|
36656
|
-
const error2 = McpError.fromError(
|
|
36656
|
+
const error2 = McpError.fromError(response2.error.code, response2.error.message, response2.error.data);
|
|
36657
36657
|
handler(error2);
|
|
36658
36658
|
}
|
|
36659
36659
|
}
|
|
@@ -36825,15 +36825,15 @@ var Protocol = class {
|
|
|
36825
36825
|
const error2 = reason instanceof McpError ? reason : new McpError(ErrorCode.RequestTimeout, String(reason));
|
|
36826
36826
|
reject(error2);
|
|
36827
36827
|
};
|
|
36828
|
-
this._responseHandlers.set(messageId, (
|
|
36828
|
+
this._responseHandlers.set(messageId, (response2) => {
|
|
36829
36829
|
if (options?.signal?.aborted) {
|
|
36830
36830
|
return;
|
|
36831
36831
|
}
|
|
36832
|
-
if (
|
|
36833
|
-
return reject(
|
|
36832
|
+
if (response2 instanceof Error) {
|
|
36833
|
+
return reject(response2);
|
|
36834
36834
|
}
|
|
36835
36835
|
try {
|
|
36836
|
-
const parseResult = safeParse2(resultSchema,
|
|
36836
|
+
const parseResult = safeParse2(resultSchema, response2.result);
|
|
36837
36837
|
if (!parseResult.success) {
|
|
36838
36838
|
reject(parseResult.error);
|
|
36839
36839
|
} else {
|
|
@@ -36851,10 +36851,10 @@ var Protocol = class {
|
|
|
36851
36851
|
this._setupTimeout(messageId, timeout, options?.maxTotalTimeout, timeoutHandler, options?.resetTimeoutOnProgress ?? false);
|
|
36852
36852
|
const relatedTaskId = relatedTask?.taskId;
|
|
36853
36853
|
if (relatedTaskId) {
|
|
36854
|
-
const responseResolver = (
|
|
36854
|
+
const responseResolver = (response2) => {
|
|
36855
36855
|
const handler = this._responseHandlers.get(messageId);
|
|
36856
36856
|
if (handler) {
|
|
36857
|
-
handler(
|
|
36857
|
+
handler(response2);
|
|
36858
36858
|
} else {
|
|
36859
36859
|
this._onerror(new Error(`Response handler missing for side-channeled request ${messageId}`));
|
|
36860
36860
|
}
|
|
@@ -41977,10 +41977,10 @@ function readFileBase64(p) {
|
|
|
41977
41977
|
return readFileSync(p).toString("base64");
|
|
41978
41978
|
}
|
|
41979
41979
|
function readFileBase64Capped(p, maxBytes = maxAttachmentBytes()) {
|
|
41980
|
-
const
|
|
41981
|
-
if (
|
|
41980
|
+
const size2 = fileSize(p);
|
|
41981
|
+
if (size2 > maxBytes) {
|
|
41982
41982
|
throw new Error(
|
|
41983
|
-
`Attachment is ${
|
|
41983
|
+
`Attachment is ${size2} bytes, exceeding the ${maxBytes}-byte fetch limit (APPLE_NOTES_MCP_MAX_ATTACHMENT_BYTES). Use save-attachment to export it to disk instead.`
|
|
41984
41984
|
);
|
|
41985
41985
|
}
|
|
41986
41986
|
return readFileBase64(p);
|
|
@@ -43149,8 +43149,9 @@ function parseScopeFailure(output) {
|
|
|
43149
43149
|
const trimmed = output.trim();
|
|
43150
43150
|
return trimmed.startsWith(`${SCOPE_MARKER}:`) ? trimmed.slice(SCOPE_MARKER.length + 1) : null;
|
|
43151
43151
|
}
|
|
43152
|
-
function scopeConflictMessage(reason) {
|
|
43153
|
-
|
|
43152
|
+
function scopeConflictMessage(reason, operation) {
|
|
43153
|
+
const refusal = operation === "batch-move" ? "This note's move was refused" : "Nothing was changed";
|
|
43154
|
+
return `Scope guard failed: ${reason}. ${refusal}; read the note's current folder and review before retrying.`;
|
|
43154
43155
|
}
|
|
43155
43156
|
|
|
43156
43157
|
// src/services/appleNotesManager.ts
|
|
@@ -43499,8 +43500,9 @@ function buildAccountScopedScript(scope2, command2) {
|
|
|
43499
43500
|
end tell
|
|
43500
43501
|
`;
|
|
43501
43502
|
}
|
|
43502
|
-
function buildAppLevelScript(command2) {
|
|
43503
|
+
function buildAppLevelScript(command2, handlers = "") {
|
|
43503
43504
|
return `
|
|
43505
|
+
${handlers}
|
|
43504
43506
|
tell application "Notes"
|
|
43505
43507
|
${command2}
|
|
43506
43508
|
end tell
|
|
@@ -45881,8 +45883,8 @@ var AppleNotesManager = class {
|
|
|
45881
45883
|
/**
|
|
45882
45884
|
* Result of a batch operation on a single item.
|
|
45883
45885
|
*/
|
|
45884
|
-
createBatchResult(id2, success, error2) {
|
|
45885
|
-
return error2 ? { id: id2, success, error: error2 } : { id: id2, success };
|
|
45886
|
+
createBatchResult(id2, success, error2, certainty) {
|
|
45887
|
+
return { ...error2 ? { id: id2, success, error: error2 } : { id: id2, success }, ...certainty };
|
|
45886
45888
|
}
|
|
45887
45889
|
/**
|
|
45888
45890
|
* Maps a per-item status token emitted by a batch AppleScript loop to a
|
|
@@ -45890,23 +45892,39 @@ var AppleNotesManager = class {
|
|
|
45890
45892
|
* per-note implementation. See {@link batchMoveNotes}.
|
|
45891
45893
|
*/
|
|
45892
45894
|
mapBatchStatus(id2, status, op) {
|
|
45895
|
+
const beforeMove = op === "move" ? { committed: false, indeterminate: false } : void 0;
|
|
45896
|
+
const uncertainMove = op === "move" ? { indeterminate: true } : void 0;
|
|
45897
|
+
const scopeFailure = parseScopeFailure(status ?? "");
|
|
45898
|
+
if (scopeFailure !== null)
|
|
45899
|
+
return this.createBatchResult(
|
|
45900
|
+
id2,
|
|
45901
|
+
false,
|
|
45902
|
+
scopeConflictMessage(scopeFailure, op === "move" ? "batch-move" : void 0),
|
|
45903
|
+
beforeMove
|
|
45904
|
+
);
|
|
45893
45905
|
switch (status) {
|
|
45894
45906
|
case "ok":
|
|
45895
45907
|
return this.createBatchResult(id2, true);
|
|
45896
45908
|
case "pw":
|
|
45897
|
-
return this.createBatchResult(id2, false, "Note is password-protected");
|
|
45909
|
+
return this.createBatchResult(id2, false, "Note is password-protected", beforeMove);
|
|
45898
45910
|
case "missing":
|
|
45899
|
-
return this.createBatchResult(id2, false, "Note not found");
|
|
45911
|
+
return this.createBatchResult(id2, false, "Note not found", beforeMove);
|
|
45900
45912
|
case "fail":
|
|
45901
45913
|
return this.createBatchResult(
|
|
45902
45914
|
id2,
|
|
45903
45915
|
false,
|
|
45904
|
-
op === "delete" ? "Deletion failed" : "Move failed"
|
|
45916
|
+
op === "delete" ? "Deletion failed" : "Move failed",
|
|
45917
|
+
uncertainMove
|
|
45905
45918
|
);
|
|
45906
45919
|
case "wrongfolder":
|
|
45907
|
-
return this.createBatchResult(
|
|
45920
|
+
return this.createBatchResult(
|
|
45921
|
+
id2,
|
|
45922
|
+
false,
|
|
45923
|
+
"Destination folder verification failed",
|
|
45924
|
+
uncertainMove
|
|
45925
|
+
);
|
|
45908
45926
|
default:
|
|
45909
|
-
return this.createBatchResult(id2, false, "Unknown error");
|
|
45927
|
+
return this.createBatchResult(id2, false, "Unknown error", uncertainMove);
|
|
45910
45928
|
}
|
|
45911
45929
|
}
|
|
45912
45930
|
/**
|
|
@@ -45918,6 +45936,7 @@ var AppleNotesManager = class {
|
|
|
45918
45936
|
* @param ids - Array of CoreData URL identifiers for notes to move
|
|
45919
45937
|
* @param folder - Destination folder name
|
|
45920
45938
|
* @param account - Account containing the folder (defaults to Notes.app's default account)
|
|
45939
|
+
* @param scope - Shared folder preconditions checked independently before each move
|
|
45921
45940
|
* @returns Array of results with id, success status, and optional error message
|
|
45922
45941
|
*
|
|
45923
45942
|
* @example
|
|
@@ -45928,8 +45947,26 @@ var AppleNotesManager = class {
|
|
|
45928
45947
|
* );
|
|
45929
45948
|
* ```
|
|
45930
45949
|
*/
|
|
45931
|
-
batchMoveNotes(ids, folder, account) {
|
|
45950
|
+
batchMoveNotes(ids, folder, account, scope2) {
|
|
45932
45951
|
if (ids.length === 0) return [];
|
|
45952
|
+
const scopeChecks = buildScopeGuardScript("noteRef", scope2, "destFolder");
|
|
45953
|
+
const moveHandler = `
|
|
45954
|
+
script batchMoveWorker
|
|
45955
|
+
on moveBatchNote(noteRef, destFolder, theId)
|
|
45956
|
+
tell application "Notes"
|
|
45957
|
+
${scopeChecks}
|
|
45958
|
+
move noteRef to destFolder
|
|
45959
|
+
set movedNoteRef to note id theId
|
|
45960
|
+
set actualFolder to container of movedNoteRef
|
|
45961
|
+
if (id of actualFolder) is (id of destFolder) then
|
|
45962
|
+
return "ok"
|
|
45963
|
+
else
|
|
45964
|
+
return "wrongfolder"
|
|
45965
|
+
end if
|
|
45966
|
+
end tell
|
|
45967
|
+
end moveBatchNote
|
|
45968
|
+
end script
|
|
45969
|
+
`;
|
|
45933
45970
|
const targetAccount = this.resolveAccount(account);
|
|
45934
45971
|
const destFolderSetup = buildLiveFolderResolution(folder, "destFolder", {
|
|
45935
45972
|
excludeFolderIds: this.smartFolderIds()
|
|
@@ -45943,13 +45980,15 @@ var AppleNotesManager = class {
|
|
|
45943
45980
|
results[i] = this.createBatchResult(
|
|
45944
45981
|
id2,
|
|
45945
45982
|
false,
|
|
45946
|
-
e instanceof Error ? e.message : "Invalid note ID"
|
|
45983
|
+
e instanceof Error ? e.message : "Invalid note ID",
|
|
45984
|
+
{ committed: false, indeterminate: false }
|
|
45947
45985
|
);
|
|
45948
45986
|
}
|
|
45949
45987
|
});
|
|
45950
45988
|
if (runnable.length > 0) {
|
|
45951
45989
|
const idList2 = runnable.map((r) => `"${r.safe}"`).join(", ");
|
|
45952
|
-
const script = buildAppLevelScript(
|
|
45990
|
+
const script = buildAppLevelScript(
|
|
45991
|
+
`
|
|
45953
45992
|
${buildAccountResolution(targetAccount)}
|
|
45954
45993
|
${destFolderSetup}
|
|
45955
45994
|
set out to ""
|
|
@@ -45970,14 +46009,8 @@ var AppleNotesManager = class {
|
|
|
45970
46009
|
set out to out & "pw" & ${AS_RECORD_SEP}
|
|
45971
46010
|
else
|
|
45972
46011
|
try
|
|
45973
|
-
|
|
45974
|
-
set
|
|
45975
|
-
set actualFolder to container of movedNoteRef
|
|
45976
|
-
if (id of actualFolder) is (id of destFolder) then
|
|
45977
|
-
set out to out & "ok" & ${AS_RECORD_SEP}
|
|
45978
|
-
else
|
|
45979
|
-
set out to out & "wrongfolder" & ${AS_RECORD_SEP}
|
|
45980
|
-
end if
|
|
46012
|
+
set itemStatus to batchMoveWorker's moveBatchNote(noteRef, destFolder, theId)
|
|
46013
|
+
set out to out & itemStatus & ${AS_RECORD_SEP}
|
|
45981
46014
|
on error
|
|
45982
46015
|
set out to out & "fail" & ${AS_RECORD_SEP}
|
|
45983
46016
|
end try
|
|
@@ -45985,7 +46018,9 @@ var AppleNotesManager = class {
|
|
|
45985
46018
|
end if
|
|
45986
46019
|
end repeat
|
|
45987
46020
|
return out
|
|
45988
|
-
|
|
46021
|
+
`,
|
|
46022
|
+
moveHandler
|
|
46023
|
+
);
|
|
45989
46024
|
const res = executeMutationAppleScript(script);
|
|
45990
46025
|
if (!res.success) {
|
|
45991
46026
|
throwIfAccountResolutionFailed(res.error);
|
|
@@ -45994,11 +46029,12 @@ var AppleNotesManager = class {
|
|
|
45994
46029
|
results[r.index] = this.createBatchResult(
|
|
45995
46030
|
ids[r.index],
|
|
45996
46031
|
false,
|
|
45997
|
-
res.error ?? "Batch move failed"
|
|
46032
|
+
res.error ?? "Batch move failed",
|
|
46033
|
+
{ indeterminate: true }
|
|
45998
46034
|
);
|
|
45999
46035
|
}
|
|
46000
46036
|
} else {
|
|
46001
|
-
const statuses = res.output.split(RECORD_SEP).map((s) => s.trim())
|
|
46037
|
+
const statuses = res.output.split(RECORD_SEP).map((s) => s.trim());
|
|
46002
46038
|
runnable.forEach((r, k) => {
|
|
46003
46039
|
results[r.index] = this.mapBatchStatus(ids[r.index], statuses[k], "move");
|
|
46004
46040
|
});
|
|
@@ -47039,11 +47075,11 @@ function decodeInline(fields) {
|
|
|
47039
47075
|
}
|
|
47040
47076
|
if (font) {
|
|
47041
47077
|
const name = stringOf(font, 1);
|
|
47042
|
-
const
|
|
47043
|
-
if (name ||
|
|
47078
|
+
const size2 = fixed32Float(first(font, 2));
|
|
47079
|
+
if (name || size2 !== void 0)
|
|
47044
47080
|
inline.font = {
|
|
47045
47081
|
...name ? { name } : {},
|
|
47046
|
-
...
|
|
47082
|
+
...size2 !== void 0 && Number.isFinite(size2) ? { size: size2 } : {}
|
|
47047
47083
|
};
|
|
47048
47084
|
}
|
|
47049
47085
|
const attachment = sub3(fields, 12);
|
|
@@ -47236,8 +47272,8 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
|
|
|
47236
47272
|
let bytes = 0;
|
|
47237
47273
|
for (let i = start; i < total && blocks.length < limit; i++) {
|
|
47238
47274
|
let block = doc.blocks[i];
|
|
47239
|
-
let
|
|
47240
|
-
if (bytes +
|
|
47275
|
+
let size2 = Buffer.byteLength(JSON.stringify(block));
|
|
47276
|
+
if (bytes + size2 > maxBytes) {
|
|
47241
47277
|
if (blocks.length) break;
|
|
47242
47278
|
block = {
|
|
47243
47279
|
...block,
|
|
@@ -47245,10 +47281,10 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
|
|
|
47245
47281
|
textOmitted: true,
|
|
47246
47282
|
runs: block.runs.map((run) => ({ ...run, text: "" }))
|
|
47247
47283
|
};
|
|
47248
|
-
|
|
47284
|
+
size2 = Buffer.byteLength(JSON.stringify(block));
|
|
47249
47285
|
}
|
|
47250
47286
|
blocks.push(block);
|
|
47251
|
-
bytes +=
|
|
47287
|
+
bytes += size2;
|
|
47252
47288
|
}
|
|
47253
47289
|
const next = start + blocks.length;
|
|
47254
47290
|
return {
|
|
@@ -50065,7 +50101,7 @@ async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHe
|
|
|
50065
50101
|
stdio: ["pipe", "pipe", "ignore"]
|
|
50066
50102
|
});
|
|
50067
50103
|
const chunks = [];
|
|
50068
|
-
let
|
|
50104
|
+
let size2 = 0;
|
|
50069
50105
|
let failure2 = null;
|
|
50070
50106
|
const stop = (error2) => {
|
|
50071
50107
|
failure2 ??= error2;
|
|
@@ -50082,8 +50118,8 @@ async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHe
|
|
|
50082
50118
|
signal?.removeEventListener("abort", onAbort);
|
|
50083
50119
|
};
|
|
50084
50120
|
child2.stdout?.on("data", (chunk) => {
|
|
50085
|
-
|
|
50086
|
-
if (
|
|
50121
|
+
size2 += chunk.length;
|
|
50122
|
+
if (size2 > MAX_OUTPUT_BYTES2)
|
|
50087
50123
|
stop(new PublicHelperError("invalid_response", "The helper response is too large."));
|
|
50088
50124
|
else chunks.push(chunk);
|
|
50089
50125
|
});
|
|
@@ -51181,6 +51217,199 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS, pen
|
|
|
51181
51217
|
};
|
|
51182
51218
|
}
|
|
51183
51219
|
|
|
51220
|
+
// src/utils/structuredText.ts
|
|
51221
|
+
import { isDeepStrictEqual } from "node:util";
|
|
51222
|
+
var STRUCTURED_TEXT_PREFIX = "structuredContent: ";
|
|
51223
|
+
var SHOWN_ABOVE = "[shown in full above]";
|
|
51224
|
+
var MIN_ELIDED_CHARS = 64;
|
|
51225
|
+
var MAX_MIRROR_CHARS = 16384;
|
|
51226
|
+
var MAX_FIELD_CHARS = 1024;
|
|
51227
|
+
function textOf(content) {
|
|
51228
|
+
return content.map(
|
|
51229
|
+
(item) => item && typeof item === "object" && item.type === "text" ? String(item.text ?? "") : ""
|
|
51230
|
+
).join("\n");
|
|
51231
|
+
}
|
|
51232
|
+
function elide(value, text2) {
|
|
51233
|
+
if (typeof value === "string") {
|
|
51234
|
+
return value.length >= MIN_ELIDED_CHARS && text2.includes(value) ? SHOWN_ABOVE : value;
|
|
51235
|
+
}
|
|
51236
|
+
if (Array.isArray(value)) return value.map((item) => elide(item, text2));
|
|
51237
|
+
if (value && typeof value === "object") {
|
|
51238
|
+
const out = {};
|
|
51239
|
+
for (const [key, item] of Object.entries(value)) {
|
|
51240
|
+
if (item !== void 0) out[key] = elide(item, text2);
|
|
51241
|
+
}
|
|
51242
|
+
return out;
|
|
51243
|
+
}
|
|
51244
|
+
return value;
|
|
51245
|
+
}
|
|
51246
|
+
function textIsTheJson(content, structured) {
|
|
51247
|
+
const plain = JSON.parse(JSON.stringify(structured));
|
|
51248
|
+
return content.some((item) => {
|
|
51249
|
+
const text2 = item?.text;
|
|
51250
|
+
if (item?.type !== "text" || typeof text2 !== "string") return false;
|
|
51251
|
+
const trimmed = text2.trim();
|
|
51252
|
+
if (!trimmed.startsWith("{")) return false;
|
|
51253
|
+
try {
|
|
51254
|
+
return isDeepStrictEqual(JSON.parse(trimmed), plain);
|
|
51255
|
+
} catch {
|
|
51256
|
+
return false;
|
|
51257
|
+
}
|
|
51258
|
+
});
|
|
51259
|
+
}
|
|
51260
|
+
function structuredTextLine(structured, text2) {
|
|
51261
|
+
const elided = elide(structured, text2);
|
|
51262
|
+
let json2 = JSON.stringify(elided);
|
|
51263
|
+
if (json2.length > MAX_MIRROR_CHARS) {
|
|
51264
|
+
const kept = {};
|
|
51265
|
+
const omitted = [];
|
|
51266
|
+
for (const [key, value] of Object.entries(elided)) {
|
|
51267
|
+
if (JSON.stringify(value).length > MAX_FIELD_CHARS) omitted.push(key);
|
|
51268
|
+
else kept[key] = value;
|
|
51269
|
+
}
|
|
51270
|
+
json2 = JSON.stringify({ ...kept, _omitted: omitted });
|
|
51271
|
+
}
|
|
51272
|
+
return STRUCTURED_TEXT_PREFIX + json2;
|
|
51273
|
+
}
|
|
51274
|
+
function withStructuredText(result) {
|
|
51275
|
+
const r = result;
|
|
51276
|
+
if (!r || typeof r !== "object") return result;
|
|
51277
|
+
const structured = r.structuredContent;
|
|
51278
|
+
if (!structured || typeof structured !== "object" || Array.isArray(structured)) return result;
|
|
51279
|
+
const content = Array.isArray(r.content) ? r.content : [];
|
|
51280
|
+
if (textIsTheJson(content, structured)) return result;
|
|
51281
|
+
const line = structuredTextLine(structured, textOf(content));
|
|
51282
|
+
return { ...r, content: [...content, { type: "text", text: line }] };
|
|
51283
|
+
}
|
|
51284
|
+
function installStructuredText(server2) {
|
|
51285
|
+
const target = server2;
|
|
51286
|
+
const register = target.registerTool;
|
|
51287
|
+
if (typeof register === "function") {
|
|
51288
|
+
target.registerTool = function(name, config2, cb) {
|
|
51289
|
+
const wrapped = typeof cb === "function" ? async (...args) => withStructuredText(await cb(...args)) : cb;
|
|
51290
|
+
return register.call(this, name, config2, wrapped);
|
|
51291
|
+
};
|
|
51292
|
+
}
|
|
51293
|
+
const createError = target.createToolError;
|
|
51294
|
+
if (typeof createError === "function") {
|
|
51295
|
+
target.createToolError = (message) => withStructuredText(createError.call(target, message));
|
|
51296
|
+
}
|
|
51297
|
+
}
|
|
51298
|
+
|
|
51299
|
+
// src/services/tableResponse.ts
|
|
51300
|
+
var DEFAULT_TABLE_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
51301
|
+
var MAX_TABLE_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
51302
|
+
var MAX_TABLES_PER_PAGE = 500;
|
|
51303
|
+
var MIN_TABLE_RESPONSE_BYTES = 1024;
|
|
51304
|
+
function validateTablePageOptions(options) {
|
|
51305
|
+
const {
|
|
51306
|
+
offset = 0,
|
|
51307
|
+
limit = MAX_TABLES_PER_PAGE,
|
|
51308
|
+
maxBytes = DEFAULT_TABLE_RESPONSE_BYTES
|
|
51309
|
+
} = options;
|
|
51310
|
+
if (!Number.isSafeInteger(offset) || offset < 0 || !Number.isSafeInteger(limit) || limit < 1 || limit > MAX_TABLES_PER_PAGE || !Number.isSafeInteger(maxBytes) || maxBytes < MIN_TABLE_RESPONSE_BYTES || maxBytes > MAX_TABLE_RESPONSE_BYTES) {
|
|
51311
|
+
throw new CodedError("Invalid table page offset, limit or maxBytes.", {
|
|
51312
|
+
code: "validation_error"
|
|
51313
|
+
});
|
|
51314
|
+
}
|
|
51315
|
+
return { offset, limit, maxBytes };
|
|
51316
|
+
}
|
|
51317
|
+
function response(id2, result, tables, options, stoppedAtSizeLimit) {
|
|
51318
|
+
const tableCount = result.tables.length;
|
|
51319
|
+
const next = options.offset + tables.length;
|
|
51320
|
+
const hasMore = next < tableCount;
|
|
51321
|
+
const complete = result.tableCellsComplete && tables.every((table) => table.complete);
|
|
51322
|
+
const markdown = tables.map(
|
|
51323
|
+
(table) => table.markdown ?? `[table ${table.index} could not be ${table.contentOmitted ? "returned" : "decoded"}: ${table.reason}]`
|
|
51324
|
+
).join("\n\n");
|
|
51325
|
+
let summary = tableCount === 0 ? "This note has no native tables." : `${tables.length === tableCount ? tableCount : `${tables.length} of ${tableCount}`} table(s)${complete ? "" : " (some content could not be returned; see tables[].reason)"}:
|
|
51326
|
+
|
|
51327
|
+
${markdown}`;
|
|
51328
|
+
if (hasMore)
|
|
51329
|
+
summary += `
|
|
51330
|
+
|
|
51331
|
+
More tables remain: call get-note-tables with offset ${next} and the same limit and maxBytes.`;
|
|
51332
|
+
return {
|
|
51333
|
+
content: [{ type: "text", text: summary }],
|
|
51334
|
+
structuredContent: {
|
|
51335
|
+
id: id2,
|
|
51336
|
+
tables,
|
|
51337
|
+
tableCount,
|
|
51338
|
+
tableCellsComplete: complete,
|
|
51339
|
+
markdown,
|
|
51340
|
+
page: {
|
|
51341
|
+
...options,
|
|
51342
|
+
totalAvailable: tableCount,
|
|
51343
|
+
returned: tables.length,
|
|
51344
|
+
...hasMore ? { nextOffset: next } : {},
|
|
51345
|
+
hasMore,
|
|
51346
|
+
stoppedAtSizeLimit
|
|
51347
|
+
}
|
|
51348
|
+
}
|
|
51349
|
+
};
|
|
51350
|
+
}
|
|
51351
|
+
var size = (value) => Buffer.byteLength(JSON.stringify(withStructuredText(value)));
|
|
51352
|
+
function tablePageResponse(id2, result, request = {}) {
|
|
51353
|
+
const options = validateTablePageOptions(request);
|
|
51354
|
+
const available = Math.max(0, Math.min(options.limit, result.tables.length - options.offset));
|
|
51355
|
+
const fits = (count) => {
|
|
51356
|
+
const candidate = result.tables.slice(options.offset, options.offset + count);
|
|
51357
|
+
const couldStopLater = count < available;
|
|
51358
|
+
const candidateBytes = Math.max(
|
|
51359
|
+
size(response(id2, result, candidate, options, false)),
|
|
51360
|
+
couldStopLater ? size(response(id2, result, candidate, options, true)) : 0
|
|
51361
|
+
);
|
|
51362
|
+
return candidateBytes <= options.maxBytes;
|
|
51363
|
+
};
|
|
51364
|
+
let accepted = 0;
|
|
51365
|
+
let trial = 1;
|
|
51366
|
+
while (trial <= available) {
|
|
51367
|
+
if (fits(trial)) {
|
|
51368
|
+
accepted = trial;
|
|
51369
|
+
if (trial === available) break;
|
|
51370
|
+
trial = Math.min(available, trial * 2);
|
|
51371
|
+
continue;
|
|
51372
|
+
}
|
|
51373
|
+
let low = accepted + 1;
|
|
51374
|
+
let high = trial - 1;
|
|
51375
|
+
while (low <= high) {
|
|
51376
|
+
const middle = Math.floor((low + high) / 2);
|
|
51377
|
+
if (fits(middle)) {
|
|
51378
|
+
accepted = middle;
|
|
51379
|
+
low = middle + 1;
|
|
51380
|
+
} else {
|
|
51381
|
+
high = middle - 1;
|
|
51382
|
+
}
|
|
51383
|
+
}
|
|
51384
|
+
break;
|
|
51385
|
+
}
|
|
51386
|
+
let tables = result.tables.slice(options.offset, options.offset + accepted);
|
|
51387
|
+
const stoppedAtSizeLimit = accepted < available;
|
|
51388
|
+
if (stoppedAtSizeLimit && accepted === 0) {
|
|
51389
|
+
const table = result.tables[options.offset];
|
|
51390
|
+
tables = [
|
|
51391
|
+
{
|
|
51392
|
+
index: table.index,
|
|
51393
|
+
id: table.id,
|
|
51394
|
+
...table.attachmentId !== void 0 ? { attachmentId: table.attachmentId } : {},
|
|
51395
|
+
...table.rowCount !== void 0 ? { rowCount: table.rowCount } : {},
|
|
51396
|
+
...table.columnCount !== void 0 ? { columnCount: table.columnCount } : {},
|
|
51397
|
+
complete: false,
|
|
51398
|
+
contentOmitted: true,
|
|
51399
|
+
reason: "Table content exceeds this response size limit. Increase maxBytes or export the note to a file."
|
|
51400
|
+
}
|
|
51401
|
+
];
|
|
51402
|
+
}
|
|
51403
|
+
const output = response(id2, result, tables, options, stoppedAtSizeLimit);
|
|
51404
|
+
if (size(output) > options.maxBytes) {
|
|
51405
|
+
throw new CodedError(
|
|
51406
|
+
"Table metadata is too large for this response limit; increase maxBytes.",
|
|
51407
|
+
{ code: "validation_error" }
|
|
51408
|
+
);
|
|
51409
|
+
}
|
|
51410
|
+
return output;
|
|
51411
|
+
}
|
|
51412
|
+
|
|
51184
51413
|
// src/utils/noteParagraphs.ts
|
|
51185
51414
|
import { gunzipSync as gunzipSync9 } from "node:zlib";
|
|
51186
51415
|
var ENVELOPE_CODE = {
|
|
@@ -51392,10 +51621,10 @@ function pageParagraphs(paragraphs, { offset = 0, limit = 500, maxBytes = 4 * 10
|
|
|
51392
51621
|
const out = [];
|
|
51393
51622
|
let bytes = 0;
|
|
51394
51623
|
for (let i = start; i < list.length && out.length < limit; i++) {
|
|
51395
|
-
const
|
|
51396
|
-
if (out.length && bytes +
|
|
51624
|
+
const size2 = Buffer.byteLength(JSON.stringify(list[i]));
|
|
51625
|
+
if (out.length && bytes + size2 > maxBytes) break;
|
|
51397
51626
|
out.push(list[i]);
|
|
51398
|
-
bytes +=
|
|
51627
|
+
bytes += size2;
|
|
51399
51628
|
}
|
|
51400
51629
|
const next = start + out.length;
|
|
51401
51630
|
return {
|
|
@@ -51931,10 +52160,10 @@ function listNoteLinks(options = {}) {
|
|
|
51931
52160
|
const page = [];
|
|
51932
52161
|
let bytes = 0;
|
|
51933
52162
|
for (let i = start; i < links.length && page.length < limit; i++) {
|
|
51934
|
-
const
|
|
51935
|
-
if (page.length && bytes +
|
|
52163
|
+
const size2 = Buffer.byteLength(JSON.stringify(links[i]));
|
|
52164
|
+
if (page.length && bytes + size2 > maxBytes) break;
|
|
51936
52165
|
page.push(links[i]);
|
|
51937
|
-
bytes +=
|
|
52166
|
+
bytes += size2;
|
|
51938
52167
|
}
|
|
51939
52168
|
const next = start + page.length;
|
|
51940
52169
|
return {
|
|
@@ -52545,8 +52774,8 @@ var DataUrlWriter = class {
|
|
|
52545
52774
|
if (!this.fits(data.length)) return { error: "too-large" };
|
|
52546
52775
|
return this.embed(`generated:${key}`, data, mime);
|
|
52547
52776
|
}
|
|
52548
|
-
fits(
|
|
52549
|
-
return
|
|
52777
|
+
fits(size2) {
|
|
52778
|
+
return size2 <= this.maxBytes && this.embeddedBytes + size2 <= this.totalBytes;
|
|
52550
52779
|
}
|
|
52551
52780
|
embed(key, data, mime) {
|
|
52552
52781
|
const result = { url: `data:${mime};base64,${data.toString("base64")}`, mime };
|
|
@@ -55307,85 +55536,6 @@ function exportWithTemplate(request, deps, chosen, notes, skipped, { output, ass
|
|
|
55307
55536
|
return { ...receipt, markdown };
|
|
55308
55537
|
}
|
|
55309
55538
|
|
|
55310
|
-
// src/utils/structuredText.ts
|
|
55311
|
-
import { isDeepStrictEqual } from "node:util";
|
|
55312
|
-
var STRUCTURED_TEXT_PREFIX = "structuredContent: ";
|
|
55313
|
-
var SHOWN_ABOVE = "[shown in full above]";
|
|
55314
|
-
var MIN_ELIDED_CHARS = 64;
|
|
55315
|
-
var MAX_MIRROR_CHARS = 16384;
|
|
55316
|
-
var MAX_FIELD_CHARS = 1024;
|
|
55317
|
-
function textOf(content) {
|
|
55318
|
-
return content.map(
|
|
55319
|
-
(item) => item && typeof item === "object" && item.type === "text" ? String(item.text ?? "") : ""
|
|
55320
|
-
).join("\n");
|
|
55321
|
-
}
|
|
55322
|
-
function elide(value, text2) {
|
|
55323
|
-
if (typeof value === "string") {
|
|
55324
|
-
return value.length >= MIN_ELIDED_CHARS && text2.includes(value) ? SHOWN_ABOVE : value;
|
|
55325
|
-
}
|
|
55326
|
-
if (Array.isArray(value)) return value.map((item) => elide(item, text2));
|
|
55327
|
-
if (value && typeof value === "object") {
|
|
55328
|
-
const out = {};
|
|
55329
|
-
for (const [key, item] of Object.entries(value)) {
|
|
55330
|
-
if (item !== void 0) out[key] = elide(item, text2);
|
|
55331
|
-
}
|
|
55332
|
-
return out;
|
|
55333
|
-
}
|
|
55334
|
-
return value;
|
|
55335
|
-
}
|
|
55336
|
-
function textIsTheJson(content, structured) {
|
|
55337
|
-
const plain = JSON.parse(JSON.stringify(structured));
|
|
55338
|
-
return content.some((item) => {
|
|
55339
|
-
const text2 = item?.text;
|
|
55340
|
-
if (item?.type !== "text" || typeof text2 !== "string") return false;
|
|
55341
|
-
const trimmed = text2.trim();
|
|
55342
|
-
if (!trimmed.startsWith("{")) return false;
|
|
55343
|
-
try {
|
|
55344
|
-
return isDeepStrictEqual(JSON.parse(trimmed), plain);
|
|
55345
|
-
} catch {
|
|
55346
|
-
return false;
|
|
55347
|
-
}
|
|
55348
|
-
});
|
|
55349
|
-
}
|
|
55350
|
-
function structuredTextLine(structured, text2) {
|
|
55351
|
-
const elided = elide(structured, text2);
|
|
55352
|
-
let json2 = JSON.stringify(elided);
|
|
55353
|
-
if (json2.length > MAX_MIRROR_CHARS) {
|
|
55354
|
-
const kept = {};
|
|
55355
|
-
const omitted = [];
|
|
55356
|
-
for (const [key, value] of Object.entries(elided)) {
|
|
55357
|
-
if (JSON.stringify(value).length > MAX_FIELD_CHARS) omitted.push(key);
|
|
55358
|
-
else kept[key] = value;
|
|
55359
|
-
}
|
|
55360
|
-
json2 = JSON.stringify({ ...kept, _omitted: omitted });
|
|
55361
|
-
}
|
|
55362
|
-
return STRUCTURED_TEXT_PREFIX + json2;
|
|
55363
|
-
}
|
|
55364
|
-
function withStructuredText(result) {
|
|
55365
|
-
const r = result;
|
|
55366
|
-
if (!r || typeof r !== "object") return result;
|
|
55367
|
-
const structured = r.structuredContent;
|
|
55368
|
-
if (!structured || typeof structured !== "object" || Array.isArray(structured)) return result;
|
|
55369
|
-
const content = Array.isArray(r.content) ? r.content : [];
|
|
55370
|
-
if (textIsTheJson(content, structured)) return result;
|
|
55371
|
-
const line = structuredTextLine(structured, textOf(content));
|
|
55372
|
-
return { ...r, content: [...content, { type: "text", text: line }] };
|
|
55373
|
-
}
|
|
55374
|
-
function installStructuredText(server2) {
|
|
55375
|
-
const target = server2;
|
|
55376
|
-
const register = target.registerTool;
|
|
55377
|
-
if (typeof register === "function") {
|
|
55378
|
-
target.registerTool = function(name, config2, cb) {
|
|
55379
|
-
const wrapped = typeof cb === "function" ? async (...args) => withStructuredText(await cb(...args)) : cb;
|
|
55380
|
-
return register.call(this, name, config2, wrapped);
|
|
55381
|
-
};
|
|
55382
|
-
}
|
|
55383
|
-
const createError = target.createToolError;
|
|
55384
|
-
if (typeof createError === "function") {
|
|
55385
|
-
target.createToolError = (message) => withStructuredText(createError.call(target, message));
|
|
55386
|
-
}
|
|
55387
|
-
}
|
|
55388
|
-
|
|
55389
55539
|
// src/tools/directOperations.ts
|
|
55390
55540
|
import { createHash as createHash4 } from "node:crypto";
|
|
55391
55541
|
import {
|
|
@@ -55677,12 +55827,12 @@ function localAttachment(path10) {
|
|
|
55677
55827
|
}
|
|
55678
55828
|
}
|
|
55679
55829
|
var MAX_ADD_ATTACHMENT_BYTES = 64 * 1024 * 1024;
|
|
55680
|
-
function fileMatches(path10,
|
|
55830
|
+
function fileMatches(path10, size2, expected) {
|
|
55681
55831
|
let descriptor;
|
|
55682
55832
|
try {
|
|
55683
55833
|
descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
|
|
55684
55834
|
const stat = fstatSync7(descriptor);
|
|
55685
|
-
if (!stat.isFile() || stat.size !==
|
|
55835
|
+
if (!stat.isFile() || stat.size !== size2) return false;
|
|
55686
55836
|
const hash = createHash4("sha256");
|
|
55687
55837
|
const chunk = Buffer.allocUnsafe(1024 * 1024);
|
|
55688
55838
|
let total = 0;
|
|
@@ -55690,10 +55840,10 @@ function fileMatches(path10, size, expected) {
|
|
|
55690
55840
|
const read = readSync5(descriptor, chunk, 0, chunk.length, null);
|
|
55691
55841
|
if (read === 0) break;
|
|
55692
55842
|
total += read;
|
|
55693
|
-
if (total >
|
|
55843
|
+
if (total > size2) return false;
|
|
55694
55844
|
hash.update(chunk.subarray(0, read));
|
|
55695
55845
|
}
|
|
55696
|
-
return total ===
|
|
55846
|
+
return total === size2 && hash.digest("hex") === expected;
|
|
55697
55847
|
} catch {
|
|
55698
55848
|
return false;
|
|
55699
55849
|
} finally {
|
|
@@ -62842,10 +62992,10 @@ async function readEditorJson(req) {
|
|
|
62842
62992
|
if (!/^application\/json(?:\s*;|$)/i.test(type))
|
|
62843
62993
|
throw new EditorHttpError(415, "unsupported-media-type", "Send JSON (application/json).");
|
|
62844
62994
|
const chunks = [];
|
|
62845
|
-
let
|
|
62995
|
+
let size2 = 0;
|
|
62846
62996
|
for await (const chunk of req) {
|
|
62847
|
-
|
|
62848
|
-
if (
|
|
62997
|
+
size2 += chunk.length;
|
|
62998
|
+
if (size2 > MAX_EDITOR_BODY_BYTES)
|
|
62849
62999
|
throw new EditorHttpError(413, "too-large", "Request body too large.");
|
|
62850
63000
|
chunks.push(chunk);
|
|
62851
63001
|
}
|
|
@@ -63196,12 +63346,12 @@ var textFingerprint = (normalized2) => createHash8("sha256").update(normalized2,
|
|
|
63196
63346
|
function textSimilarity(a, b) {
|
|
63197
63347
|
if (a === b) return 1;
|
|
63198
63348
|
if (!a || !b) return 0;
|
|
63199
|
-
const
|
|
63349
|
+
const size2 = Math.min(a.length, b.length) < 3 ? 2 : 3;
|
|
63200
63350
|
const grams = (s) => {
|
|
63201
63351
|
const out = /* @__PURE__ */ new Map();
|
|
63202
63352
|
const padded = ` ${s} `;
|
|
63203
|
-
for (let i = 0; i +
|
|
63204
|
-
const g = padded.slice(i, i +
|
|
63353
|
+
for (let i = 0; i + size2 <= padded.length; i++) {
|
|
63354
|
+
const g = padded.slice(i, i + size2);
|
|
63205
63355
|
out.set(g, (out.get(g) ?? 0) + 1);
|
|
63206
63356
|
}
|
|
63207
63357
|
return out;
|
|
@@ -64118,7 +64268,7 @@ var scopeGuardInputs = {
|
|
|
64118
64268
|
maxItems: MAX_FORBIDDEN_FOLDERS
|
|
64119
64269
|
}
|
|
64120
64270
|
).optional().describe(
|
|
64121
|
-
"Precondition: the note must not be inside any of these folders or their subfolders (for move-note, neither may the destination). Re-checked immediately before the write."
|
|
64271
|
+
"Precondition: the note must not be inside any of these folders or their subfolders (for move-note and batch-move-notes, neither may the destination). Re-checked immediately before the write."
|
|
64122
64272
|
)
|
|
64123
64273
|
};
|
|
64124
64274
|
function scopeFrom(args) {
|
|
@@ -65041,18 +65191,27 @@ registerTool(
|
|
|
65041
65191
|
registerTool(
|
|
65042
65192
|
"get-note-tables",
|
|
65043
65193
|
{
|
|
65044
|
-
description: "Use when: reading the native tables in one exact note as data or Markdown.\nReturns:
|
|
65045
|
-
inputSchema: {
|
|
65194
|
+
description: "Use when: reading the native tables in one exact note as data or Markdown.\nReturns: one page of whole tables in body order as GitHub-flavored Markdown (first row as header) plus JSON rows with stable row/column ids, tableCellsComplete, and page info. Default limit 500 and maxBytes 4 MiB; while page.hasMore is true, call again with offset set to page.nextOffset. The byte budget includes the full serialized tool response, including repeated Markdown.\nDo not use when: you need the whole note (get-note-markdown / get-note-content) or native object ranges and checklist ids (get-native-objects).\nSafety: read-only; reads the NoteStore database and requires Full Disk Access. A cell that cannot be decoded is null in rows, listed in incompleteCells, and marked [undecoded cell] in Markdown; it is never guessed. A table larger than an empty page returns metadata only with complete: false and contentOmitted: true; increase maxBytes (max 8 MiB) or export the note to a file. Cell text only: links and styling inside cells are not rendered.",
|
|
65195
|
+
inputSchema: {
|
|
65196
|
+
id: noteIdInput,
|
|
65197
|
+
offset: external_exports.number().int().min(0).max(Number.MAX_SAFE_INTEGER).optional().describe("Zero-based position of the first table (default 0); use page.nextOffset"),
|
|
65198
|
+
limit: external_exports.number().int().min(1).max(MAX_TABLES_PER_PAGE).optional().describe(`Maximum tables to return (default and max ${MAX_TABLES_PER_PAGE})`),
|
|
65199
|
+
maxBytes: external_exports.number().int().min(MIN_TABLE_RESPONSE_BYTES).max(MAX_TABLE_RESPONSE_BYTES).optional().describe(
|
|
65200
|
+
`Maximum UTF-8 bytes of the serialized tool response (default ${DEFAULT_TABLE_RESPONSE_BYTES}, max ${MAX_TABLE_RESPONSE_BYTES})`
|
|
65201
|
+
)
|
|
65202
|
+
},
|
|
65046
65203
|
outputSchema: {
|
|
65047
65204
|
id: external_exports.string().optional(),
|
|
65048
65205
|
tables: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
65049
65206
|
tableCount: external_exports.number().optional(),
|
|
65050
65207
|
tableCellsComplete: external_exports.boolean().optional(),
|
|
65051
|
-
markdown: external_exports.string().optional()
|
|
65208
|
+
markdown: external_exports.string().optional(),
|
|
65209
|
+
page: external_exports.record(external_exports.unknown()).optional()
|
|
65052
65210
|
},
|
|
65053
65211
|
annotations: { readOnlyHint: true }
|
|
65054
65212
|
},
|
|
65055
|
-
withErrorHandling(({ id: id2 }) => {
|
|
65213
|
+
withErrorHandling(({ id: id2, offset, limit, maxBytes }) => {
|
|
65214
|
+
const options = validateTablePageOptions({ offset, limit, maxBytes });
|
|
65056
65215
|
const { metadata, message } = getNoteMetadata(id2);
|
|
65057
65216
|
if (!metadata) return errorResponse(message || `Failed to read note "${id2}"`);
|
|
65058
65217
|
if (metadata.passwordProtected) {
|
|
@@ -65061,17 +65220,7 @@ registerTool(
|
|
|
65061
65220
|
);
|
|
65062
65221
|
}
|
|
65063
65222
|
const result = notesManager.getNoteTablesById(id2);
|
|
65064
|
-
|
|
65065
|
-
const summary = count === 0 ? "This note has no native tables." : `${count} table(s)${result.tableCellsComplete ? "" : " (some content could not be decoded; see tables[].reason)"}:
|
|
65066
|
-
|
|
65067
|
-
${result.markdown}`;
|
|
65068
|
-
return successResponse(summary, {
|
|
65069
|
-
id: id2,
|
|
65070
|
-
tables: result.tables,
|
|
65071
|
-
tableCount: count,
|
|
65072
|
-
tableCellsComplete: result.tableCellsComplete,
|
|
65073
|
-
markdown: result.markdown
|
|
65074
|
-
});
|
|
65223
|
+
return tablePageResponse(id2, result, options);
|
|
65075
65224
|
}, "Error reading note tables")
|
|
65076
65225
|
);
|
|
65077
65226
|
registerTool(
|
|
@@ -65850,7 +65999,7 @@ registerTool(
|
|
|
65850
65999
|
readLinks: (noteId3) => readRichNote(noteId3).links,
|
|
65851
66000
|
append: (request) => {
|
|
65852
66001
|
let route = "applescript";
|
|
65853
|
-
const
|
|
66002
|
+
const response2 = guardedAppend(
|
|
65854
66003
|
{
|
|
65855
66004
|
...request,
|
|
65856
66005
|
format: "html",
|
|
@@ -65858,11 +66007,11 @@ registerTool(
|
|
|
65858
66007
|
},
|
|
65859
66008
|
(r) => route = r
|
|
65860
66009
|
);
|
|
65861
|
-
if (
|
|
65862
|
-
const envelope =
|
|
65863
|
-
throw envelope?.code ? new CodedError(
|
|
66010
|
+
if (response2.isError) {
|
|
66011
|
+
const envelope = response2.structuredContent;
|
|
66012
|
+
throw envelope?.code ? new CodedError(response2.content[0].text, envelope) : new Error(response2.content[0].text);
|
|
65864
66013
|
}
|
|
65865
|
-
return { route, contentHash: String(
|
|
66014
|
+
return { route, contentHash: String(response2.structuredContent?.contentHash ?? "") };
|
|
65866
66015
|
}
|
|
65867
66016
|
}
|
|
65868
66017
|
);
|
|
@@ -66678,8 +66827,9 @@ registerTool(
|
|
|
66678
66827
|
registerTool(
|
|
66679
66828
|
"batch-move-notes",
|
|
66680
66829
|
{
|
|
66681
|
-
description: "Use when: moving multiple notes by id into one destination folder.\nReturns: per-id success/failure counts after destination-folder verification.\nDo not use when: moving a single note (move-note).\nSafety:
|
|
66830
|
+
description: "Use when: moving multiple notes by id into one destination folder.\nReturns: per-id success/failure counts after destination-folder verification.\nDo not use when: moving a single note (move-note).\nSafety: optional folder preconditions apply independently to every note, checked live immediately before its move in the same AppleScript; a scope mismatch leaves that note unchanged and other items continue. Each moved note's actual container ID is compared with the destination folder ID before success is reported. The destination folder must already exist (create-folder); a smart folder is refused for the whole call (code unsupported) before any note moves.",
|
|
66682
66831
|
inputSchema: {
|
|
66832
|
+
...scopeGuardInputs,
|
|
66683
66833
|
ids: noteIdArrayInput.describe(
|
|
66684
66834
|
`Array of note IDs to move (max ${MAX.BATCH_IDS} per request)`
|
|
66685
66835
|
),
|
|
@@ -66698,11 +66848,12 @@ registerTool(
|
|
|
66698
66848
|
results: external_exports.array(external_exports.object({}).passthrough()).optional()
|
|
66699
66849
|
}
|
|
66700
66850
|
},
|
|
66701
|
-
withErrorHandling((
|
|
66851
|
+
withErrorHandling((args) => {
|
|
66852
|
+
const { ids, folder, account } = args;
|
|
66702
66853
|
if (ids.length === 0) {
|
|
66703
66854
|
return errorResponse("No note IDs provided");
|
|
66704
66855
|
}
|
|
66705
|
-
const results = notesManager.batchMoveNotes(ids, folder, account);
|
|
66856
|
+
const results = notesManager.batchMoveNotes(ids, folder, account, scopeFrom(args));
|
|
66706
66857
|
const succeeded = results.filter((r) => r.success).length;
|
|
66707
66858
|
const failed = results.filter((r) => !r.success).length;
|
|
66708
66859
|
const lines = [`Batch move to "${folder}": ${succeeded} succeeded, ${failed} failed`];
|
|
@@ -66712,13 +66863,20 @@ registerTool(
|
|
|
66712
66863
|
lines.push(` - ${result.id}: ${result.error}`);
|
|
66713
66864
|
}
|
|
66714
66865
|
}
|
|
66715
|
-
|
|
66716
|
-
|
|
66717
|
-
|
|
66718
|
-
|
|
66719
|
-
|
|
66720
|
-
|
|
66721
|
-
|
|
66866
|
+
if (succeeded > 0)
|
|
66867
|
+
return successResponse(lines.join("\n"), {
|
|
66868
|
+
ok: failed === 0,
|
|
66869
|
+
folder,
|
|
66870
|
+
succeeded,
|
|
66871
|
+
failed,
|
|
66872
|
+
results
|
|
66873
|
+
});
|
|
66874
|
+
const allRefusedBeforeMove = results.length === ids.length && results.every((result) => result.committed === false && result.indeterminate === false);
|
|
66875
|
+
if (!allRefusedBeforeMove)
|
|
66876
|
+
lines.push("\nBatch move outcome is uncertain; read each exact note ID before retrying.");
|
|
66877
|
+
const message = lines.join("\n");
|
|
66878
|
+
const envelope = allRefusedBeforeMove ? { ...classifyError(message), committed: false, indeterminate: false } : { code: "verification_failed", indeterminate: true };
|
|
66879
|
+
return errorResponse(message, new CodedError(message, envelope));
|
|
66722
66880
|
}, "Error performing batch move")
|
|
66723
66881
|
);
|
|
66724
66882
|
registerTool(
|
|
@@ -67003,7 +67161,7 @@ registerTool(
|
|
|
67003
67161
|
`Too large to return whole, so oversized inline images were replaced (strippedImages) or the body was left out (contentOmitted): ${degraded.map((n) => n.id).join(", ")}. Read these with get-note-content, and their files with list-attachments and save-attachment.`
|
|
67004
67162
|
);
|
|
67005
67163
|
}
|
|
67006
|
-
const
|
|
67164
|
+
const response2 = {
|
|
67007
67165
|
content: [
|
|
67008
67166
|
{
|
|
67009
67167
|
type: "text",
|
|
@@ -67018,13 +67176,13 @@ Full JSON export:`
|
|
|
67018
67176
|
],
|
|
67019
67177
|
structuredContent: { ...exportData }
|
|
67020
67178
|
};
|
|
67021
|
-
const responseBytes = Buffer.byteLength(JSON.stringify(
|
|
67179
|
+
const responseBytes = Buffer.byteLength(JSON.stringify(response2));
|
|
67022
67180
|
if (responseBytes > maxResponseBytes) {
|
|
67023
67181
|
return errorResponse(
|
|
67024
67182
|
`Error exporting notes: this page is ${formatMegabytes(responseBytes)}, over the ${formatMegabytes(maxResponseBytes)} response limit, so it was not sent. Call export-notes-json again with offset ${page.offset} and a smaller limit (for example ${Math.max(1, Math.floor(page.returned / 2))}), or raise APPLE_NOTES_MCP_EXPORT_MAX_BYTES if your MCP client accepts larger messages.`
|
|
67025
67183
|
);
|
|
67026
67184
|
}
|
|
67027
|
-
return
|
|
67185
|
+
return response2;
|
|
67028
67186
|
}, "Error exporting notes")
|
|
67029
67187
|
);
|
|
67030
67188
|
registerTool(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apple-notes-mcp",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.7",
|
|
4
4
|
"description": "MCP server for Apple Notes - read, search, create, edit, organize, and export notes on macOS via Claude and other AI assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|