apple-notes-mcp 2.15.4 → 2.15.6
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 +21 -3
- package/build/index.js +509 -255
- 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.
|
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);
|
|
@@ -47039,11 +47039,11 @@ function decodeInline(fields) {
|
|
|
47039
47039
|
}
|
|
47040
47040
|
if (font) {
|
|
47041
47041
|
const name = stringOf(font, 1);
|
|
47042
|
-
const
|
|
47043
|
-
if (name ||
|
|
47042
|
+
const size2 = fixed32Float(first(font, 2));
|
|
47043
|
+
if (name || size2 !== void 0)
|
|
47044
47044
|
inline.font = {
|
|
47045
47045
|
...name ? { name } : {},
|
|
47046
|
-
...
|
|
47046
|
+
...size2 !== void 0 && Number.isFinite(size2) ? { size: size2 } : {}
|
|
47047
47047
|
};
|
|
47048
47048
|
}
|
|
47049
47049
|
const attachment = sub3(fields, 12);
|
|
@@ -47236,8 +47236,8 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
|
|
|
47236
47236
|
let bytes = 0;
|
|
47237
47237
|
for (let i = start; i < total && blocks.length < limit; i++) {
|
|
47238
47238
|
let block = doc.blocks[i];
|
|
47239
|
-
let
|
|
47240
|
-
if (bytes +
|
|
47239
|
+
let size2 = Buffer.byteLength(JSON.stringify(block));
|
|
47240
|
+
if (bytes + size2 > maxBytes) {
|
|
47241
47241
|
if (blocks.length) break;
|
|
47242
47242
|
block = {
|
|
47243
47243
|
...block,
|
|
@@ -47245,10 +47245,10 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
|
|
|
47245
47245
|
textOmitted: true,
|
|
47246
47246
|
runs: block.runs.map((run) => ({ ...run, text: "" }))
|
|
47247
47247
|
};
|
|
47248
|
-
|
|
47248
|
+
size2 = Buffer.byteLength(JSON.stringify(block));
|
|
47249
47249
|
}
|
|
47250
47250
|
blocks.push(block);
|
|
47251
|
-
bytes +=
|
|
47251
|
+
bytes += size2;
|
|
47252
47252
|
}
|
|
47253
47253
|
const next = start + blocks.length;
|
|
47254
47254
|
return {
|
|
@@ -48713,33 +48713,11 @@ import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as
|
|
|
48713
48713
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
48714
48714
|
import { join as join17 } from "node:path";
|
|
48715
48715
|
|
|
48716
|
-
// src/
|
|
48717
|
-
function shortcutConsentHint(shortcut) {
|
|
48718
|
-
const target = shortcut ? `"${shortcut}"` : "the bridge Shortcut";
|
|
48719
|
-
return `This may be an unanswered first-run Shortcuts consent prompt, which a background run cannot display: run ${target} once in the foreground in Shortcuts.app and choose Always Allow.`;
|
|
48720
|
-
}
|
|
48721
|
-
|
|
48722
|
-
// src/services/nativeTags.ts
|
|
48723
|
-
var NATIVE_TAGS_SHORTCUT = "Apple Notes MCP - Native Tags";
|
|
48724
|
-
function normalizeNativeTags(tags) {
|
|
48725
|
-
if (!tags.length || tags.length > 100) throw new Error("Provide between 1 and 100 tags");
|
|
48726
|
-
return [
|
|
48727
|
-
...new Set(
|
|
48728
|
-
tags.map((value) => {
|
|
48729
|
-
const tag = value.normalize("NFC").replace(/^#/, "");
|
|
48730
|
-
if (tag.length > 100 || !new RegExp("^(?=.*\\p{L})[\\p{L}\\p{N}_-]+$", "u").test(tag))
|
|
48731
|
-
throw new Error(
|
|
48732
|
-
"Tags must contain a letter and only letters, digits, hyphens or underscores"
|
|
48733
|
-
);
|
|
48734
|
-
return tag;
|
|
48735
|
-
})
|
|
48736
|
-
)
|
|
48737
|
-
];
|
|
48738
|
-
}
|
|
48716
|
+
// src/utils/richContentPreservation.ts
|
|
48739
48717
|
var TAG_OBJECT_TYPE = "com.apple.notes.inlinetextattachment.hashtag";
|
|
48740
48718
|
function requirePreservationMetadata(rich) {
|
|
48741
|
-
const unavailable = () => {
|
|
48742
|
-
throw new Error(
|
|
48719
|
+
const unavailable = (message = "Native object, checklist or formatting preservation metadata is unavailable") => {
|
|
48720
|
+
throw new Error(message);
|
|
48743
48721
|
};
|
|
48744
48722
|
const { objects, objectData, checklistItems, styleRuns, nativeTagObjectIds } = rich;
|
|
48745
48723
|
if (!objects || !objectData || !checklistItems || !styleRuns || !nativeTagObjectIds || rich.nativeObjectDataComplete !== true)
|
|
@@ -48774,16 +48752,173 @@ function requirePreservationMetadata(rich) {
|
|
|
48774
48752
|
return unavailable();
|
|
48775
48753
|
}
|
|
48776
48754
|
let position = 0;
|
|
48755
|
+
const firstReferences = /* @__PURE__ */ new Map();
|
|
48777
48756
|
for (const run of styleRuns) {
|
|
48778
48757
|
if (run.start !== position || !Number.isInteger(run.length) || run.length < 0 || typeof run.signature !== "string" || run.nativeSemantics?.complete !== true || run.length === 0 && (run.nativeSemantics.unknown || run.nativeSemantics.structuredParagraph || run.nativeSemantics.links || run.nativeSemantics.objects.length > 0))
|
|
48779
48758
|
return unavailable();
|
|
48759
|
+
const references = run.nativeSemantics.objects;
|
|
48760
|
+
if (references.length > 1 || rich.text.slice(run.start, run.start + run.length).includes("\uFFFC") && references.length !== 1)
|
|
48761
|
+
return unavailable("Native object placement preservation metadata is unavailable");
|
|
48762
|
+
for (const reference of references) {
|
|
48763
|
+
if (!objectData.some((data) => data.id === reference.id && data.type === reference.type))
|
|
48764
|
+
return unavailable("Native object placement preservation metadata is unavailable");
|
|
48765
|
+
if (!firstReferences.has(reference.id))
|
|
48766
|
+
firstReferences.set(reference.id, {
|
|
48767
|
+
start: run.start,
|
|
48768
|
+
length: run.length,
|
|
48769
|
+
type: reference.type
|
|
48770
|
+
});
|
|
48771
|
+
}
|
|
48780
48772
|
position += run.length;
|
|
48781
48773
|
}
|
|
48782
48774
|
if (position !== rich.text.length) return unavailable();
|
|
48775
|
+
if (objects.some((object3) => {
|
|
48776
|
+
const reference = firstReferences.get(object3.id);
|
|
48777
|
+
return !reference || reference.start !== object3.start || reference.length !== object3.length || reference.type !== object3.type;
|
|
48778
|
+
}))
|
|
48779
|
+
return unavailable("Native object placement preservation metadata is unavailable");
|
|
48783
48780
|
for (const link of rich.links)
|
|
48784
48781
|
if (!Number.isInteger(link.start) || !Number.isInteger(link.length) || link.start < 0 || link.length < 1 || link.start + link.length > rich.text.length || rich.text.slice(link.start, link.start + link.length) !== link.text)
|
|
48785
48782
|
return unavailable();
|
|
48786
48783
|
}
|
|
48784
|
+
function assertZeroLengthFormatting(before, after, spans) {
|
|
48785
|
+
const oldEmpty = before.styleRuns.filter((run) => run.length === 0).map((run) => {
|
|
48786
|
+
const span = spans.find(
|
|
48787
|
+
(span2) => run.start >= span2.before && run.start <= span2.before + span2.length
|
|
48788
|
+
);
|
|
48789
|
+
if (!span) throw new Error("Zero-length formatting falls inside the requested removal");
|
|
48790
|
+
return { ...run, start: span.after + run.start - span.before };
|
|
48791
|
+
});
|
|
48792
|
+
const newEmpty = after.styleRuns.filter(
|
|
48793
|
+
(run) => run.length === 0 && spans.some((span) => run.start >= span.after && run.start <= span.after + span.length)
|
|
48794
|
+
);
|
|
48795
|
+
if (JSON.stringify(oldEmpty) !== JSON.stringify(newEmpty))
|
|
48796
|
+
throw new Error("Existing zero-length formatting metadata changed");
|
|
48797
|
+
}
|
|
48798
|
+
function retainedSpans(before, after, change) {
|
|
48799
|
+
if (change.kind !== "remove-tag") {
|
|
48800
|
+
if (change.kind === "append" ? !after.text.startsWith(before.text) : after.text !== before.text)
|
|
48801
|
+
throw new Error("Existing note text or whitespace was not preserved");
|
|
48802
|
+
return {
|
|
48803
|
+
spans: [{ before: 0, after: 0, length: before.text.length }],
|
|
48804
|
+
removed: /* @__PURE__ */ new Set()
|
|
48805
|
+
};
|
|
48806
|
+
}
|
|
48807
|
+
const removed = new Set(before.nativeTagObjectIds[change.tag]);
|
|
48808
|
+
if (!removed.size || after.nativeTags.includes(change.tag))
|
|
48809
|
+
throw new Error("Native tag removal was not verified");
|
|
48810
|
+
const spans = [];
|
|
48811
|
+
let position = 0, output = "";
|
|
48812
|
+
for (const object3 of before.objects.filter((object4) => removed.has(object4.id))) {
|
|
48813
|
+
const text2 = before.text.slice(object3.start, object3.start + object3.length);
|
|
48814
|
+
if (text2 !== "\uFFFC" && text2 !== `#${change.tag}`)
|
|
48815
|
+
throw new Error("Native tag removal range is unsupported");
|
|
48816
|
+
const prefix = before.text.slice(position, object3.start);
|
|
48817
|
+
spans.push({ before: position, after: output.length, length: prefix.length });
|
|
48818
|
+
output += prefix;
|
|
48819
|
+
position = object3.start + object3.length;
|
|
48820
|
+
}
|
|
48821
|
+
spans.push({ before: position, after: output.length, length: before.text.length - position });
|
|
48822
|
+
output += before.text.slice(position);
|
|
48823
|
+
if (after.text !== output)
|
|
48824
|
+
throw new Error("Existing note text or whitespace was not preserved around the removed tag");
|
|
48825
|
+
return { spans, removed };
|
|
48826
|
+
}
|
|
48827
|
+
function assertRetainedRichContent(before, after, change = { kind: "unchanged" }) {
|
|
48828
|
+
requirePreservationMetadata(before);
|
|
48829
|
+
requirePreservationMetadata(after);
|
|
48830
|
+
const { spans, removed } = retainedSpans(before, after, change);
|
|
48831
|
+
const rangeStart = (start, length) => {
|
|
48832
|
+
const span = spans.find(
|
|
48833
|
+
(span2) => start >= span2.before && start + length <= span2.before + span2.length
|
|
48834
|
+
);
|
|
48835
|
+
return span && span.after + start - span.before;
|
|
48836
|
+
};
|
|
48837
|
+
const retainedIds = before.nativeObjectIds.filter((id2) => !removed.has(id2));
|
|
48838
|
+
if (retainedIds.some((id2) => !after.nativeObjectIds.includes(id2)) || [...removed].some((id2) => after.nativeObjectIds.includes(id2)) || change.kind !== "append" && after.nativeObjectIds.length !== retainedIds.length || change.kind === "append" && after.objects.some(
|
|
48839
|
+
(object3) => !retainedIds.includes(object3.id) && object3.start < before.text.length
|
|
48840
|
+
))
|
|
48841
|
+
throw new Error("Existing native object identity or placement changed");
|
|
48842
|
+
for (const object3 of before.objects.filter((object4) => !removed.has(object4.id))) {
|
|
48843
|
+
const actual = after.objects.find((current) => current.id === object3.id);
|
|
48844
|
+
if (actual.type !== object3.type || actual.length !== object3.length || actual.start !== rangeStart(object3.start, object3.length))
|
|
48845
|
+
throw new Error("Existing native object range changed");
|
|
48846
|
+
const oldData = before.objectData.find((row) => row.id === object3.id), newData = after.objectData.find((row) => row.id === object3.id);
|
|
48847
|
+
if (oldData.pk !== newData.pk || oldData.type !== newData.type || oldData.mergeable !== newData.mergeable || oldData.view !== newData.view || oldData.altText !== newData.altText)
|
|
48848
|
+
throw new Error("Existing native object payload changed");
|
|
48849
|
+
}
|
|
48850
|
+
const tags = before.nativeTags.filter(
|
|
48851
|
+
(tag) => change.kind !== "remove-tag" || tag !== change.tag
|
|
48852
|
+
);
|
|
48853
|
+
if (tags.some(
|
|
48854
|
+
(tag) => JSON.stringify(before.nativeTagObjectIds[tag]) !== JSON.stringify(after.nativeTagObjectIds[tag])
|
|
48855
|
+
) || change.kind !== "append" && after.nativeTags.length !== tags.length)
|
|
48856
|
+
throw new Error("Existing native tag identities changed");
|
|
48857
|
+
const links = change.kind === "append" ? after.links.filter((link) => link.start < before.text.length) : after.links;
|
|
48858
|
+
if (links.length !== before.links.length || before.links.some((link, index) => {
|
|
48859
|
+
const actual = links[index];
|
|
48860
|
+
return actual.text !== link.text || actual.url !== link.url || actual.length !== link.length || actual.start !== rangeStart(link.start, link.length);
|
|
48861
|
+
}))
|
|
48862
|
+
throw new Error("Existing links or their ranges were not preserved");
|
|
48863
|
+
const items = change.kind === "append" ? after.checklistItems.filter((item) => item.start < before.text.length) : after.checklistItems;
|
|
48864
|
+
if (items.length !== before.checklistItems.length || before.checklistItems.some((item, index) => {
|
|
48865
|
+
const actual = items[index];
|
|
48866
|
+
const retained = spans.flatMap((span) => {
|
|
48867
|
+
const start2 = Math.max(item.start, span.before), end = Math.min(item.start + item.text.length, span.before + span.length);
|
|
48868
|
+
return start2 < end ? [before.text.slice(start2, end)] : [];
|
|
48869
|
+
}).join("");
|
|
48870
|
+
const startSpan = spans.find(
|
|
48871
|
+
(span) => item.start >= span.before && item.start <= span.before + span.length
|
|
48872
|
+
);
|
|
48873
|
+
const start = startSpan && startSpan.after + item.start - startSpan.before;
|
|
48874
|
+
return actual.id !== item.id || actual.text !== retained || actual.done !== item.done || actual.start !== start;
|
|
48875
|
+
}))
|
|
48876
|
+
throw new Error("Existing checklist item identity, state or range changed");
|
|
48877
|
+
for (const span of spans) {
|
|
48878
|
+
let offset = 0, oldIndex = 0, newIndex = 0;
|
|
48879
|
+
while (offset < span.length) {
|
|
48880
|
+
const oldPosition = span.before + offset, newPosition = span.after + offset;
|
|
48881
|
+
while (before.styleRuns[oldIndex].start + before.styleRuns[oldIndex].length <= oldPosition)
|
|
48882
|
+
oldIndex++;
|
|
48883
|
+
while (after.styleRuns[newIndex].start + after.styleRuns[newIndex].length <= newPosition)
|
|
48884
|
+
newIndex++;
|
|
48885
|
+
const oldRun = before.styleRuns[oldIndex], newRun = after.styleRuns[newIndex];
|
|
48886
|
+
if (oldRun.signature !== newRun.signature || oldRun.paragraphStyle !== newRun.paragraphStyle || oldRun.blockQuote !== newRun.blockQuote || oldRun.highlight !== newRun.highlight)
|
|
48887
|
+
throw new Error("Existing rich formatting changed");
|
|
48888
|
+
offset += Math.min(
|
|
48889
|
+
span.length - offset,
|
|
48890
|
+
oldRun.start + oldRun.length - oldPosition,
|
|
48891
|
+
newRun.start + newRun.length - newPosition
|
|
48892
|
+
);
|
|
48893
|
+
}
|
|
48894
|
+
}
|
|
48895
|
+
assertZeroLengthFormatting(before, after, spans);
|
|
48896
|
+
}
|
|
48897
|
+
|
|
48898
|
+
// src/services/shortcutConsent.ts
|
|
48899
|
+
function shortcutConsentHint(shortcut) {
|
|
48900
|
+
const target = shortcut ? `"${shortcut}"` : "the bridge Shortcut";
|
|
48901
|
+
return `This may be an unanswered first-run Shortcuts consent prompt, which a background run cannot display: run ${target} once in the foreground in Shortcuts.app and choose Always Allow.`;
|
|
48902
|
+
}
|
|
48903
|
+
|
|
48904
|
+
// src/services/nativeTags.ts
|
|
48905
|
+
var NATIVE_TAGS_SHORTCUT = "Apple Notes MCP - Native Tags";
|
|
48906
|
+
function normalizeNativeTags(tags) {
|
|
48907
|
+
if (!tags.length || tags.length > 100) throw new Error("Provide between 1 and 100 tags");
|
|
48908
|
+
return [
|
|
48909
|
+
...new Set(
|
|
48910
|
+
tags.map((value) => {
|
|
48911
|
+
const tag = value.normalize("NFC").replace(/^#/, "");
|
|
48912
|
+
if (tag.length > 100 || !new RegExp("^(?=.*\\p{L})[\\p{L}\\p{N}_-]+$", "u").test(tag))
|
|
48913
|
+
throw new Error(
|
|
48914
|
+
"Tags must contain a letter and only letters, digits, hyphens or underscores"
|
|
48915
|
+
);
|
|
48916
|
+
return tag;
|
|
48917
|
+
})
|
|
48918
|
+
)
|
|
48919
|
+
];
|
|
48920
|
+
}
|
|
48921
|
+
var TAG_OBJECT_TYPE2 = "com.apple.notes.inlinetextattachment.hashtag";
|
|
48787
48922
|
function plainNativeRange(rich, start, length) {
|
|
48788
48923
|
return rich.styleRuns.every((run) => {
|
|
48789
48924
|
if (run.start >= start + length || run.start + run.length <= start) return true;
|
|
@@ -48799,7 +48934,7 @@ function preservedTextSpans(before, after, missing) {
|
|
|
48799
48934
|
([tag, ids]) => ids.map((id2) => [id2, tag])
|
|
48800
48935
|
)
|
|
48801
48936
|
);
|
|
48802
|
-
if (!added.length || added.some((o) => o.type !==
|
|
48937
|
+
if (!added.length || added.some((o) => o.type !== TAG_OBJECT_TYPE2 || !missing.includes(tagsById.get(o.id))) || missing.some((tag) => !added.some((o) => tagsById.get(o.id) === tag)))
|
|
48803
48938
|
return void 0;
|
|
48804
48939
|
const spans = [];
|
|
48805
48940
|
let oldPosition = 0, newPosition = 0, appended = false;
|
|
@@ -48856,6 +48991,11 @@ function preservesAddedSemantics(before, after, spans) {
|
|
|
48856
48991
|
return true;
|
|
48857
48992
|
}
|
|
48858
48993
|
function preservesNativeContent(before, after, spans) {
|
|
48994
|
+
try {
|
|
48995
|
+
assertZeroLengthFormatting(before, after, spans);
|
|
48996
|
+
} catch {
|
|
48997
|
+
return false;
|
|
48998
|
+
}
|
|
48859
48999
|
const rangeStart = (start, length) => {
|
|
48860
49000
|
const span = spans.find((s) => start >= s.before && start + length <= s.before + s.length);
|
|
48861
49001
|
return span && span.after + start - span.before;
|
|
@@ -48906,6 +49046,16 @@ function preservesNativeContent(before, after, spans) {
|
|
|
48906
49046
|
}
|
|
48907
49047
|
return true;
|
|
48908
49048
|
}
|
|
49049
|
+
function nativeTagAdditionPreserved(before, after, tags) {
|
|
49050
|
+
requirePreservationMetadata(before);
|
|
49051
|
+
requirePreservationMetadata(after);
|
|
49052
|
+
const missing = tags.filter((tag) => !before.nativeTags.includes(tag));
|
|
49053
|
+
const allTags = [.../* @__PURE__ */ new Set([...before.nativeTags, ...tags])];
|
|
49054
|
+
const spans = preservedTextSpans(before, after, missing);
|
|
49055
|
+
return after.nativeTags.length === allTags.length && allTags.every((tag) => after.nativeTags.includes(tag)) && before.nativeObjectIds.every((id2) => after.nativeObjectIds.includes(id2)) && before.hasChecklist === after.hasChecklist && Boolean(
|
|
49056
|
+
spans && preservesAddedSemantics(before, after, spans) && preservesNativeContent(before, after, spans)
|
|
49057
|
+
);
|
|
49058
|
+
}
|
|
48909
49059
|
function addNativeTags(request, deps) {
|
|
48910
49060
|
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(request.id))
|
|
48911
49061
|
throw new Error("An exact CoreData note ID is required");
|
|
@@ -48947,9 +49097,7 @@ function addNativeTags(request, deps) {
|
|
|
48947
49097
|
} catch (error2) {
|
|
48948
49098
|
throw verificationFailure(error2 instanceof Error ? error2.message : String(error2));
|
|
48949
49099
|
}
|
|
48950
|
-
|
|
48951
|
-
const spans = preservedTextSpans(before.rich, after.rich, missing);
|
|
48952
|
-
if (after.title !== before.title || after.rich.nativeTags.length !== allTags.length || allTags.some((tag) => !after.rich.nativeTags.includes(tag)) || before.rich.nativeObjectIds.some((id2) => !after.rich.nativeObjectIds.includes(id2)) || before.rich.hasChecklist !== after.rich.hasChecklist || !spans || !preservesAddedSemantics(before.rich, after.rich, spans) || !preservesNativeContent(before.rich, after.rich, spans)) {
|
|
49100
|
+
if (after.title !== before.title || !nativeTagAdditionPreserved(before.rich, after.rich, tags)) {
|
|
48953
49101
|
throw verificationFailure();
|
|
48954
49102
|
}
|
|
48955
49103
|
return {
|
|
@@ -49389,53 +49537,25 @@ function backgroundDependencies(manager) {
|
|
|
49389
49537
|
function assertPreserved(before, after, options = {}) {
|
|
49390
49538
|
if (before.id !== after.id || before.title !== after.title)
|
|
49391
49539
|
throw new Error("Note identity changed");
|
|
49392
|
-
|
|
49393
|
-
|
|
49394
|
-
|
|
49395
|
-
|
|
49396
|
-
|
|
49397
|
-
|
|
49398
|
-
|
|
49399
|
-
|
|
49400
|
-
|
|
49401
|
-
|
|
49402
|
-
|
|
49403
|
-
|
|
49404
|
-
|
|
49405
|
-
|
|
49406
|
-
|
|
49407
|
-
if (JSON.stringify(before.checklist) !== JSON.stringify(
|
|
49408
|
-
options.append ? after.checklist.slice(0, before.checklist.length) : after.checklist
|
|
49409
|
-
))
|
|
49410
|
-
throw new Error("Existing checklist items changed");
|
|
49411
|
-
for (const item of before.rich.checklistItems || []) {
|
|
49412
|
-
const actual = after.rich.checklistItems?.find((current) => current.id === item.id);
|
|
49413
|
-
if (!actual || actual.text !== item.text || actual.done !== item.done)
|
|
49414
|
-
throw new Error("Existing checklist item identity or state changed");
|
|
49415
|
-
}
|
|
49416
|
-
if (before.rich.nativeTags.filter((t) => t !== options.tagChange).some((t) => !after.rich.nativeTags.includes(t)))
|
|
49417
|
-
throw new Error("Existing native tag was lost");
|
|
49418
|
-
const allowedRemoved = options.tagChange ? before.rich.nativeTagObjectIds?.[options.tagChange] || [] : [];
|
|
49419
|
-
for (const object3 of before.rich.objectData || []) {
|
|
49420
|
-
if (allowedRemoved.includes(object3.id)) continue;
|
|
49421
|
-
const actual = after.rich.objectData?.find((o) => o.id === object3.id);
|
|
49422
|
-
if (!actual || actual.mergeable !== object3.mergeable || actual.view !== object3.view)
|
|
49423
|
-
throw new Error("Existing native object content or presentation changed");
|
|
49424
|
-
}
|
|
49425
|
-
if (!options.tagChange && before.rich.styleRuns && after.rich.styleRuns) {
|
|
49426
|
-
const end = before.rich.text.trimEnd().length;
|
|
49427
|
-
for (const oldRun of before.rich.styleRuns)
|
|
49428
|
-
for (const newRun of after.rich.styleRuns) {
|
|
49429
|
-
const overlapStart = Math.max(oldRun.start, newRun.start);
|
|
49430
|
-
const overlapEnd = Math.min(
|
|
49431
|
-
end,
|
|
49432
|
-
oldRun.start + oldRun.length,
|
|
49433
|
-
newRun.start + newRun.length
|
|
49434
|
-
);
|
|
49435
|
-
if (overlapStart < overlapEnd && /[^\s\ufffc]/u.test(before.rich.text.slice(overlapStart, overlapEnd)) && oldRun.signature !== newRun.signature)
|
|
49436
|
-
throw new Error("Existing rich formatting changed");
|
|
49437
|
-
}
|
|
49540
|
+
if (options.tagChange && !options.removeTag) {
|
|
49541
|
+
if (!nativeTagAdditionPreserved(before.rich, after.rich, [options.tagChange]))
|
|
49542
|
+
throw new Error("Native tag addition or retained content was not verified");
|
|
49543
|
+
} else {
|
|
49544
|
+
try {
|
|
49545
|
+
assertRetainedRichContent(
|
|
49546
|
+
before.rich,
|
|
49547
|
+
after.rich,
|
|
49548
|
+
options.tagChange ? { kind: "remove-tag", tag: options.tagChange } : options.append ? { kind: "append" } : { kind: "unchanged" }
|
|
49549
|
+
);
|
|
49550
|
+
} catch (error2) {
|
|
49551
|
+
if (error2 instanceof Error && error2.message === "Existing note text or whitespace was not preserved")
|
|
49552
|
+
throw new Error("Existing note text was not preserved", { cause: error2 });
|
|
49553
|
+
throw error2;
|
|
49554
|
+
}
|
|
49438
49555
|
}
|
|
49556
|
+
for (const snapshot of [before, after])
|
|
49557
|
+
if (JSON.stringify(snapshot.checklist) !== JSON.stringify(snapshot.rich.checklistItems.map(({ text: text2, done }) => ({ text: text2, done }))))
|
|
49558
|
+
throw new Error("Native checklist readers disagree about item text or state");
|
|
49439
49559
|
}
|
|
49440
49560
|
function describeTransportFailure(error2) {
|
|
49441
49561
|
const detail = error2;
|
|
@@ -49449,12 +49569,37 @@ function textBelowTitle(text2, title) {
|
|
|
49449
49569
|
if (firstLine.trim() !== title.trim()) return text2;
|
|
49450
49570
|
return lineEnd < 0 ? "" : text2.slice(lineEnd + 1);
|
|
49451
49571
|
}
|
|
49572
|
+
function assertChecklistEndAppendAvailable(rich) {
|
|
49573
|
+
requirePreservationMetadata(rich);
|
|
49574
|
+
const items = [...rich.checklistItems].sort((a, b) => a.start - b.start);
|
|
49575
|
+
if (!items.length) return;
|
|
49576
|
+
const last = items.at(-1);
|
|
49577
|
+
if (items.some(
|
|
49578
|
+
(item, index) => item.start > 0 && rich.text[item.start - 1] !== "\n" || index > 0 && item.start !== items[index - 1].start + items[index - 1].text.length + 1
|
|
49579
|
+
) || !/^\n?$/u.test(rich.text.slice(last.start + last.text.length)))
|
|
49580
|
+
throw new CodedError(
|
|
49581
|
+
"Checklist end-append requires one existing checklist group at the end of the note. Notes may insert into an earlier group when text or native objects follow it, or when groups are separate. Nothing changed",
|
|
49582
|
+
{ code: "validation_error", committed: false, indeterminate: false }
|
|
49583
|
+
);
|
|
49584
|
+
}
|
|
49585
|
+
function requireBackgroundPreflightMetadata(rich) {
|
|
49586
|
+
try {
|
|
49587
|
+
requirePreservationMetadata(rich);
|
|
49588
|
+
} catch (error2) {
|
|
49589
|
+
throw new CodedError(error2 instanceof Error ? error2.message : String(error2), {
|
|
49590
|
+
code: "validation_error",
|
|
49591
|
+
committed: false,
|
|
49592
|
+
indeterminate: false
|
|
49593
|
+
});
|
|
49594
|
+
}
|
|
49595
|
+
}
|
|
49452
49596
|
function mutateBackground(request, operation, data, verify, deps) {
|
|
49453
49597
|
if (request.scopeText.length < 12 || request.scopeText.length > 500 || /[\r\n\0]/u.test(request.scopeText))
|
|
49454
49598
|
throw new Error("Use a distinctive existing single-line scope of 12\u2013500 characters");
|
|
49455
49599
|
const before = deps.read(request.id);
|
|
49456
49600
|
if (before.hash !== request.expectedContentHash)
|
|
49457
49601
|
throw new Error("Note revision changed; read it again");
|
|
49602
|
+
requireBackgroundPreflightMetadata(before.rich);
|
|
49458
49603
|
if (!before.rich.text.includes(request.scopeText))
|
|
49459
49604
|
throw new Error("Scope is absent from exact note");
|
|
49460
49605
|
if (!textBelowTitle(before.rich.text, before.title).includes(request.scopeText))
|
|
@@ -49465,8 +49610,10 @@ function mutateBackground(request, operation, data, verify, deps) {
|
|
|
49465
49610
|
const candidates = deps.candidates(before.title, request.scopeText);
|
|
49466
49611
|
if (candidates.length !== 1 || candidates[0] !== request.id)
|
|
49467
49612
|
throw new Error("Ambiguous note selection; nothing changed");
|
|
49468
|
-
|
|
49469
|
-
|
|
49613
|
+
const current = deps.read(request.id);
|
|
49614
|
+
if (current.hash !== before.hash) throw new Error("Note revision changed during preflight");
|
|
49615
|
+
requireBackgroundPreflightMetadata(current.rich);
|
|
49616
|
+
if (operation === "create-checklist-item") assertChecklistEndAppendAvailable(current.rich);
|
|
49470
49617
|
let transportUncertain = false;
|
|
49471
49618
|
let transportMessage = "";
|
|
49472
49619
|
let transportError;
|
|
@@ -49570,7 +49717,7 @@ function setNativeTag(manager, request) {
|
|
|
49570
49717
|
request.present ? "add-tag" : "remove-tag",
|
|
49571
49718
|
{ tag },
|
|
49572
49719
|
(before, after) => {
|
|
49573
|
-
assertPreserved(before, after, { tagChange: tag });
|
|
49720
|
+
assertPreserved(before, after, { tagChange: tag, removeTag: !request.present });
|
|
49574
49721
|
if (after.rich.nativeTags.includes(tag) !== request.present)
|
|
49575
49722
|
throw new Error("Native tag state not verified");
|
|
49576
49723
|
const removed = before.rich.nativeObjectIds.filter(
|
|
@@ -49918,7 +50065,7 @@ async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHe
|
|
|
49918
50065
|
stdio: ["pipe", "pipe", "ignore"]
|
|
49919
50066
|
});
|
|
49920
50067
|
const chunks = [];
|
|
49921
|
-
let
|
|
50068
|
+
let size2 = 0;
|
|
49922
50069
|
let failure2 = null;
|
|
49923
50070
|
const stop = (error2) => {
|
|
49924
50071
|
failure2 ??= error2;
|
|
@@ -49935,8 +50082,8 @@ async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHe
|
|
|
49935
50082
|
signal?.removeEventListener("abort", onAbort);
|
|
49936
50083
|
};
|
|
49937
50084
|
child2.stdout?.on("data", (chunk) => {
|
|
49938
|
-
|
|
49939
|
-
if (
|
|
50085
|
+
size2 += chunk.length;
|
|
50086
|
+
if (size2 > MAX_OUTPUT_BYTES2)
|
|
49940
50087
|
stop(new PublicHelperError("invalid_response", "The helper response is too large."));
|
|
49941
50088
|
else chunks.push(chunk);
|
|
49942
50089
|
});
|
|
@@ -51034,6 +51181,199 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS, pen
|
|
|
51034
51181
|
};
|
|
51035
51182
|
}
|
|
51036
51183
|
|
|
51184
|
+
// src/utils/structuredText.ts
|
|
51185
|
+
import { isDeepStrictEqual } from "node:util";
|
|
51186
|
+
var STRUCTURED_TEXT_PREFIX = "structuredContent: ";
|
|
51187
|
+
var SHOWN_ABOVE = "[shown in full above]";
|
|
51188
|
+
var MIN_ELIDED_CHARS = 64;
|
|
51189
|
+
var MAX_MIRROR_CHARS = 16384;
|
|
51190
|
+
var MAX_FIELD_CHARS = 1024;
|
|
51191
|
+
function textOf(content) {
|
|
51192
|
+
return content.map(
|
|
51193
|
+
(item) => item && typeof item === "object" && item.type === "text" ? String(item.text ?? "") : ""
|
|
51194
|
+
).join("\n");
|
|
51195
|
+
}
|
|
51196
|
+
function elide(value, text2) {
|
|
51197
|
+
if (typeof value === "string") {
|
|
51198
|
+
return value.length >= MIN_ELIDED_CHARS && text2.includes(value) ? SHOWN_ABOVE : value;
|
|
51199
|
+
}
|
|
51200
|
+
if (Array.isArray(value)) return value.map((item) => elide(item, text2));
|
|
51201
|
+
if (value && typeof value === "object") {
|
|
51202
|
+
const out = {};
|
|
51203
|
+
for (const [key, item] of Object.entries(value)) {
|
|
51204
|
+
if (item !== void 0) out[key] = elide(item, text2);
|
|
51205
|
+
}
|
|
51206
|
+
return out;
|
|
51207
|
+
}
|
|
51208
|
+
return value;
|
|
51209
|
+
}
|
|
51210
|
+
function textIsTheJson(content, structured) {
|
|
51211
|
+
const plain = JSON.parse(JSON.stringify(structured));
|
|
51212
|
+
return content.some((item) => {
|
|
51213
|
+
const text2 = item?.text;
|
|
51214
|
+
if (item?.type !== "text" || typeof text2 !== "string") return false;
|
|
51215
|
+
const trimmed = text2.trim();
|
|
51216
|
+
if (!trimmed.startsWith("{")) return false;
|
|
51217
|
+
try {
|
|
51218
|
+
return isDeepStrictEqual(JSON.parse(trimmed), plain);
|
|
51219
|
+
} catch {
|
|
51220
|
+
return false;
|
|
51221
|
+
}
|
|
51222
|
+
});
|
|
51223
|
+
}
|
|
51224
|
+
function structuredTextLine(structured, text2) {
|
|
51225
|
+
const elided = elide(structured, text2);
|
|
51226
|
+
let json2 = JSON.stringify(elided);
|
|
51227
|
+
if (json2.length > MAX_MIRROR_CHARS) {
|
|
51228
|
+
const kept = {};
|
|
51229
|
+
const omitted = [];
|
|
51230
|
+
for (const [key, value] of Object.entries(elided)) {
|
|
51231
|
+
if (JSON.stringify(value).length > MAX_FIELD_CHARS) omitted.push(key);
|
|
51232
|
+
else kept[key] = value;
|
|
51233
|
+
}
|
|
51234
|
+
json2 = JSON.stringify({ ...kept, _omitted: omitted });
|
|
51235
|
+
}
|
|
51236
|
+
return STRUCTURED_TEXT_PREFIX + json2;
|
|
51237
|
+
}
|
|
51238
|
+
function withStructuredText(result) {
|
|
51239
|
+
const r = result;
|
|
51240
|
+
if (!r || typeof r !== "object") return result;
|
|
51241
|
+
const structured = r.structuredContent;
|
|
51242
|
+
if (!structured || typeof structured !== "object" || Array.isArray(structured)) return result;
|
|
51243
|
+
const content = Array.isArray(r.content) ? r.content : [];
|
|
51244
|
+
if (textIsTheJson(content, structured)) return result;
|
|
51245
|
+
const line = structuredTextLine(structured, textOf(content));
|
|
51246
|
+
return { ...r, content: [...content, { type: "text", text: line }] };
|
|
51247
|
+
}
|
|
51248
|
+
function installStructuredText(server2) {
|
|
51249
|
+
const target = server2;
|
|
51250
|
+
const register = target.registerTool;
|
|
51251
|
+
if (typeof register === "function") {
|
|
51252
|
+
target.registerTool = function(name, config2, cb) {
|
|
51253
|
+
const wrapped = typeof cb === "function" ? async (...args) => withStructuredText(await cb(...args)) : cb;
|
|
51254
|
+
return register.call(this, name, config2, wrapped);
|
|
51255
|
+
};
|
|
51256
|
+
}
|
|
51257
|
+
const createError = target.createToolError;
|
|
51258
|
+
if (typeof createError === "function") {
|
|
51259
|
+
target.createToolError = (message) => withStructuredText(createError.call(target, message));
|
|
51260
|
+
}
|
|
51261
|
+
}
|
|
51262
|
+
|
|
51263
|
+
// src/services/tableResponse.ts
|
|
51264
|
+
var DEFAULT_TABLE_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
51265
|
+
var MAX_TABLE_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
51266
|
+
var MAX_TABLES_PER_PAGE = 500;
|
|
51267
|
+
var MIN_TABLE_RESPONSE_BYTES = 1024;
|
|
51268
|
+
function validateTablePageOptions(options) {
|
|
51269
|
+
const {
|
|
51270
|
+
offset = 0,
|
|
51271
|
+
limit = MAX_TABLES_PER_PAGE,
|
|
51272
|
+
maxBytes = DEFAULT_TABLE_RESPONSE_BYTES
|
|
51273
|
+
} = options;
|
|
51274
|
+
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) {
|
|
51275
|
+
throw new CodedError("Invalid table page offset, limit or maxBytes.", {
|
|
51276
|
+
code: "validation_error"
|
|
51277
|
+
});
|
|
51278
|
+
}
|
|
51279
|
+
return { offset, limit, maxBytes };
|
|
51280
|
+
}
|
|
51281
|
+
function response(id2, result, tables, options, stoppedAtSizeLimit) {
|
|
51282
|
+
const tableCount = result.tables.length;
|
|
51283
|
+
const next = options.offset + tables.length;
|
|
51284
|
+
const hasMore = next < tableCount;
|
|
51285
|
+
const complete = result.tableCellsComplete && tables.every((table) => table.complete);
|
|
51286
|
+
const markdown = tables.map(
|
|
51287
|
+
(table) => table.markdown ?? `[table ${table.index} could not be ${table.contentOmitted ? "returned" : "decoded"}: ${table.reason}]`
|
|
51288
|
+
).join("\n\n");
|
|
51289
|
+
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)"}:
|
|
51290
|
+
|
|
51291
|
+
${markdown}`;
|
|
51292
|
+
if (hasMore)
|
|
51293
|
+
summary += `
|
|
51294
|
+
|
|
51295
|
+
More tables remain: call get-note-tables with offset ${next} and the same limit and maxBytes.`;
|
|
51296
|
+
return {
|
|
51297
|
+
content: [{ type: "text", text: summary }],
|
|
51298
|
+
structuredContent: {
|
|
51299
|
+
id: id2,
|
|
51300
|
+
tables,
|
|
51301
|
+
tableCount,
|
|
51302
|
+
tableCellsComplete: complete,
|
|
51303
|
+
markdown,
|
|
51304
|
+
page: {
|
|
51305
|
+
...options,
|
|
51306
|
+
totalAvailable: tableCount,
|
|
51307
|
+
returned: tables.length,
|
|
51308
|
+
...hasMore ? { nextOffset: next } : {},
|
|
51309
|
+
hasMore,
|
|
51310
|
+
stoppedAtSizeLimit
|
|
51311
|
+
}
|
|
51312
|
+
}
|
|
51313
|
+
};
|
|
51314
|
+
}
|
|
51315
|
+
var size = (value) => Buffer.byteLength(JSON.stringify(withStructuredText(value)));
|
|
51316
|
+
function tablePageResponse(id2, result, request = {}) {
|
|
51317
|
+
const options = validateTablePageOptions(request);
|
|
51318
|
+
const available = Math.max(0, Math.min(options.limit, result.tables.length - options.offset));
|
|
51319
|
+
const fits = (count) => {
|
|
51320
|
+
const candidate = result.tables.slice(options.offset, options.offset + count);
|
|
51321
|
+
const couldStopLater = count < available;
|
|
51322
|
+
const candidateBytes = Math.max(
|
|
51323
|
+
size(response(id2, result, candidate, options, false)),
|
|
51324
|
+
couldStopLater ? size(response(id2, result, candidate, options, true)) : 0
|
|
51325
|
+
);
|
|
51326
|
+
return candidateBytes <= options.maxBytes;
|
|
51327
|
+
};
|
|
51328
|
+
let accepted = 0;
|
|
51329
|
+
let trial = 1;
|
|
51330
|
+
while (trial <= available) {
|
|
51331
|
+
if (fits(trial)) {
|
|
51332
|
+
accepted = trial;
|
|
51333
|
+
if (trial === available) break;
|
|
51334
|
+
trial = Math.min(available, trial * 2);
|
|
51335
|
+
continue;
|
|
51336
|
+
}
|
|
51337
|
+
let low = accepted + 1;
|
|
51338
|
+
let high = trial - 1;
|
|
51339
|
+
while (low <= high) {
|
|
51340
|
+
const middle = Math.floor((low + high) / 2);
|
|
51341
|
+
if (fits(middle)) {
|
|
51342
|
+
accepted = middle;
|
|
51343
|
+
low = middle + 1;
|
|
51344
|
+
} else {
|
|
51345
|
+
high = middle - 1;
|
|
51346
|
+
}
|
|
51347
|
+
}
|
|
51348
|
+
break;
|
|
51349
|
+
}
|
|
51350
|
+
let tables = result.tables.slice(options.offset, options.offset + accepted);
|
|
51351
|
+
const stoppedAtSizeLimit = accepted < available;
|
|
51352
|
+
if (stoppedAtSizeLimit && accepted === 0) {
|
|
51353
|
+
const table = result.tables[options.offset];
|
|
51354
|
+
tables = [
|
|
51355
|
+
{
|
|
51356
|
+
index: table.index,
|
|
51357
|
+
id: table.id,
|
|
51358
|
+
...table.attachmentId !== void 0 ? { attachmentId: table.attachmentId } : {},
|
|
51359
|
+
...table.rowCount !== void 0 ? { rowCount: table.rowCount } : {},
|
|
51360
|
+
...table.columnCount !== void 0 ? { columnCount: table.columnCount } : {},
|
|
51361
|
+
complete: false,
|
|
51362
|
+
contentOmitted: true,
|
|
51363
|
+
reason: "Table content exceeds this response size limit. Increase maxBytes or export the note to a file."
|
|
51364
|
+
}
|
|
51365
|
+
];
|
|
51366
|
+
}
|
|
51367
|
+
const output = response(id2, result, tables, options, stoppedAtSizeLimit);
|
|
51368
|
+
if (size(output) > options.maxBytes) {
|
|
51369
|
+
throw new CodedError(
|
|
51370
|
+
"Table metadata is too large for this response limit; increase maxBytes.",
|
|
51371
|
+
{ code: "validation_error" }
|
|
51372
|
+
);
|
|
51373
|
+
}
|
|
51374
|
+
return output;
|
|
51375
|
+
}
|
|
51376
|
+
|
|
51037
51377
|
// src/utils/noteParagraphs.ts
|
|
51038
51378
|
import { gunzipSync as gunzipSync9 } from "node:zlib";
|
|
51039
51379
|
var ENVELOPE_CODE = {
|
|
@@ -51245,10 +51585,10 @@ function pageParagraphs(paragraphs, { offset = 0, limit = 500, maxBytes = 4 * 10
|
|
|
51245
51585
|
const out = [];
|
|
51246
51586
|
let bytes = 0;
|
|
51247
51587
|
for (let i = start; i < list.length && out.length < limit; i++) {
|
|
51248
|
-
const
|
|
51249
|
-
if (out.length && bytes +
|
|
51588
|
+
const size2 = Buffer.byteLength(JSON.stringify(list[i]));
|
|
51589
|
+
if (out.length && bytes + size2 > maxBytes) break;
|
|
51250
51590
|
out.push(list[i]);
|
|
51251
|
-
bytes +=
|
|
51591
|
+
bytes += size2;
|
|
51252
51592
|
}
|
|
51253
51593
|
const next = start + out.length;
|
|
51254
51594
|
return {
|
|
@@ -51784,10 +52124,10 @@ function listNoteLinks(options = {}) {
|
|
|
51784
52124
|
const page = [];
|
|
51785
52125
|
let bytes = 0;
|
|
51786
52126
|
for (let i = start; i < links.length && page.length < limit; i++) {
|
|
51787
|
-
const
|
|
51788
|
-
if (page.length && bytes +
|
|
52127
|
+
const size2 = Buffer.byteLength(JSON.stringify(links[i]));
|
|
52128
|
+
if (page.length && bytes + size2 > maxBytes) break;
|
|
51789
52129
|
page.push(links[i]);
|
|
51790
|
-
bytes +=
|
|
52130
|
+
bytes += size2;
|
|
51791
52131
|
}
|
|
51792
52132
|
const next = start + page.length;
|
|
51793
52133
|
return {
|
|
@@ -52398,8 +52738,8 @@ var DataUrlWriter = class {
|
|
|
52398
52738
|
if (!this.fits(data.length)) return { error: "too-large" };
|
|
52399
52739
|
return this.embed(`generated:${key}`, data, mime);
|
|
52400
52740
|
}
|
|
52401
|
-
fits(
|
|
52402
|
-
return
|
|
52741
|
+
fits(size2) {
|
|
52742
|
+
return size2 <= this.maxBytes && this.embeddedBytes + size2 <= this.totalBytes;
|
|
52403
52743
|
}
|
|
52404
52744
|
embed(key, data, mime) {
|
|
52405
52745
|
const result = { url: `data:${mime};base64,${data.toString("base64")}`, mime };
|
|
@@ -55160,85 +55500,6 @@ function exportWithTemplate(request, deps, chosen, notes, skipped, { output, ass
|
|
|
55160
55500
|
return { ...receipt, markdown };
|
|
55161
55501
|
}
|
|
55162
55502
|
|
|
55163
|
-
// src/utils/structuredText.ts
|
|
55164
|
-
import { isDeepStrictEqual } from "node:util";
|
|
55165
|
-
var STRUCTURED_TEXT_PREFIX = "structuredContent: ";
|
|
55166
|
-
var SHOWN_ABOVE = "[shown in full above]";
|
|
55167
|
-
var MIN_ELIDED_CHARS = 64;
|
|
55168
|
-
var MAX_MIRROR_CHARS = 16384;
|
|
55169
|
-
var MAX_FIELD_CHARS = 1024;
|
|
55170
|
-
function textOf(content) {
|
|
55171
|
-
return content.map(
|
|
55172
|
-
(item) => item && typeof item === "object" && item.type === "text" ? String(item.text ?? "") : ""
|
|
55173
|
-
).join("\n");
|
|
55174
|
-
}
|
|
55175
|
-
function elide(value, text2) {
|
|
55176
|
-
if (typeof value === "string") {
|
|
55177
|
-
return value.length >= MIN_ELIDED_CHARS && text2.includes(value) ? SHOWN_ABOVE : value;
|
|
55178
|
-
}
|
|
55179
|
-
if (Array.isArray(value)) return value.map((item) => elide(item, text2));
|
|
55180
|
-
if (value && typeof value === "object") {
|
|
55181
|
-
const out = {};
|
|
55182
|
-
for (const [key, item] of Object.entries(value)) {
|
|
55183
|
-
if (item !== void 0) out[key] = elide(item, text2);
|
|
55184
|
-
}
|
|
55185
|
-
return out;
|
|
55186
|
-
}
|
|
55187
|
-
return value;
|
|
55188
|
-
}
|
|
55189
|
-
function textIsTheJson(content, structured) {
|
|
55190
|
-
const plain = JSON.parse(JSON.stringify(structured));
|
|
55191
|
-
return content.some((item) => {
|
|
55192
|
-
const text2 = item?.text;
|
|
55193
|
-
if (item?.type !== "text" || typeof text2 !== "string") return false;
|
|
55194
|
-
const trimmed = text2.trim();
|
|
55195
|
-
if (!trimmed.startsWith("{")) return false;
|
|
55196
|
-
try {
|
|
55197
|
-
return isDeepStrictEqual(JSON.parse(trimmed), plain);
|
|
55198
|
-
} catch {
|
|
55199
|
-
return false;
|
|
55200
|
-
}
|
|
55201
|
-
});
|
|
55202
|
-
}
|
|
55203
|
-
function structuredTextLine(structured, text2) {
|
|
55204
|
-
const elided = elide(structured, text2);
|
|
55205
|
-
let json2 = JSON.stringify(elided);
|
|
55206
|
-
if (json2.length > MAX_MIRROR_CHARS) {
|
|
55207
|
-
const kept = {};
|
|
55208
|
-
const omitted = [];
|
|
55209
|
-
for (const [key, value] of Object.entries(elided)) {
|
|
55210
|
-
if (JSON.stringify(value).length > MAX_FIELD_CHARS) omitted.push(key);
|
|
55211
|
-
else kept[key] = value;
|
|
55212
|
-
}
|
|
55213
|
-
json2 = JSON.stringify({ ...kept, _omitted: omitted });
|
|
55214
|
-
}
|
|
55215
|
-
return STRUCTURED_TEXT_PREFIX + json2;
|
|
55216
|
-
}
|
|
55217
|
-
function withStructuredText(result) {
|
|
55218
|
-
const r = result;
|
|
55219
|
-
if (!r || typeof r !== "object") return result;
|
|
55220
|
-
const structured = r.structuredContent;
|
|
55221
|
-
if (!structured || typeof structured !== "object" || Array.isArray(structured)) return result;
|
|
55222
|
-
const content = Array.isArray(r.content) ? r.content : [];
|
|
55223
|
-
if (textIsTheJson(content, structured)) return result;
|
|
55224
|
-
const line = structuredTextLine(structured, textOf(content));
|
|
55225
|
-
return { ...r, content: [...content, { type: "text", text: line }] };
|
|
55226
|
-
}
|
|
55227
|
-
function installStructuredText(server2) {
|
|
55228
|
-
const target = server2;
|
|
55229
|
-
const register = target.registerTool;
|
|
55230
|
-
if (typeof register === "function") {
|
|
55231
|
-
target.registerTool = function(name, config2, cb) {
|
|
55232
|
-
const wrapped = typeof cb === "function" ? async (...args) => withStructuredText(await cb(...args)) : cb;
|
|
55233
|
-
return register.call(this, name, config2, wrapped);
|
|
55234
|
-
};
|
|
55235
|
-
}
|
|
55236
|
-
const createError = target.createToolError;
|
|
55237
|
-
if (typeof createError === "function") {
|
|
55238
|
-
target.createToolError = (message) => withStructuredText(createError.call(target, message));
|
|
55239
|
-
}
|
|
55240
|
-
}
|
|
55241
|
-
|
|
55242
55503
|
// src/tools/directOperations.ts
|
|
55243
55504
|
import { createHash as createHash4 } from "node:crypto";
|
|
55244
55505
|
import {
|
|
@@ -55509,30 +55770,13 @@ function readSnapshot(manager, id2) {
|
|
|
55509
55770
|
const rich = readRichNote(id2);
|
|
55510
55771
|
if (enriched.revision !== rich.revision)
|
|
55511
55772
|
throw new Error("Native metadata changed during read; read the note again");
|
|
55773
|
+
requirePreservationMetadata(rich);
|
|
55512
55774
|
return { id: id2, title: note.title, body, rich, hash: richContentHash(body, enriched) };
|
|
55513
55775
|
}
|
|
55514
55776
|
function assertExistingContentPreserved(before, after) {
|
|
55515
55777
|
if (before.id !== after.id || before.title !== after.title)
|
|
55516
55778
|
throw new Error("Note identity changed");
|
|
55517
|
-
|
|
55518
|
-
if (!tidy(after.rich.text).startsWith(tidy(before.rich.text)))
|
|
55519
|
-
throw new Error("Existing note text was not preserved");
|
|
55520
|
-
if (linkSignature(before.rich.links) !== linkSignature(after.rich.links.slice(0, before.rich.links.length)))
|
|
55521
|
-
throw new Error("Existing links were not preserved");
|
|
55522
|
-
if (before.rich.nativeObjectIds.some((id2) => !after.rich.nativeObjectIds.includes(id2)))
|
|
55523
|
-
throw new Error("Existing native object was lost");
|
|
55524
|
-
if (before.rich.nativeTags.some((tag) => !after.rich.nativeTags.includes(tag)))
|
|
55525
|
-
throw new Error("Existing native tag was lost");
|
|
55526
|
-
for (const item of before.rich.checklistItems || []) {
|
|
55527
|
-
const actual = after.rich.checklistItems?.find((current) => current.id === item.id);
|
|
55528
|
-
if (!actual || actual.text !== item.text || actual.done !== item.done)
|
|
55529
|
-
throw new Error("Existing checklist item identity or state changed");
|
|
55530
|
-
}
|
|
55531
|
-
for (const object3 of before.rich.objectData || []) {
|
|
55532
|
-
const actual = after.rich.objectData?.find((current) => current.id === object3.id);
|
|
55533
|
-
if (!actual || actual.mergeable !== object3.mergeable || actual.view !== object3.view)
|
|
55534
|
-
throw new Error("Existing native object content or presentation changed");
|
|
55535
|
-
}
|
|
55779
|
+
assertRetainedRichContent(before.rich, after.rich, { kind: "append" });
|
|
55536
55780
|
}
|
|
55537
55781
|
function localAttachment(path10) {
|
|
55538
55782
|
if (!isAbsolute4(path10)) throw new Error("An absolute local file path is required");
|
|
@@ -55547,12 +55791,12 @@ function localAttachment(path10) {
|
|
|
55547
55791
|
}
|
|
55548
55792
|
}
|
|
55549
55793
|
var MAX_ADD_ATTACHMENT_BYTES = 64 * 1024 * 1024;
|
|
55550
|
-
function fileMatches(path10,
|
|
55794
|
+
function fileMatches(path10, size2, expected) {
|
|
55551
55795
|
let descriptor;
|
|
55552
55796
|
try {
|
|
55553
55797
|
descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
|
|
55554
55798
|
const stat = fstatSync7(descriptor);
|
|
55555
|
-
if (!stat.isFile() || stat.size !==
|
|
55799
|
+
if (!stat.isFile() || stat.size !== size2) return false;
|
|
55556
55800
|
const hash = createHash4("sha256");
|
|
55557
55801
|
const chunk = Buffer.allocUnsafe(1024 * 1024);
|
|
55558
55802
|
let total = 0;
|
|
@@ -55560,10 +55804,10 @@ function fileMatches(path10, size, expected) {
|
|
|
55560
55804
|
const read = readSync5(descriptor, chunk, 0, chunk.length, null);
|
|
55561
55805
|
if (read === 0) break;
|
|
55562
55806
|
total += read;
|
|
55563
|
-
if (total >
|
|
55807
|
+
if (total > size2) return false;
|
|
55564
55808
|
hash.update(chunk.subarray(0, read));
|
|
55565
55809
|
}
|
|
55566
|
-
return total ===
|
|
55810
|
+
return total === size2 && hash.digest("hex") === expected;
|
|
55567
55811
|
} catch {
|
|
55568
55812
|
return false;
|
|
55569
55813
|
} finally {
|
|
@@ -55909,8 +56153,19 @@ function attachFile(manager, args, checked, progress = { insertionStarted: false
|
|
|
55909
56153
|
transportWarning: "Transport was uncertain; exact bytes and prior content were verified"
|
|
55910
56154
|
} : {}
|
|
55911
56155
|
};
|
|
56156
|
+
} catch (error2) {
|
|
56157
|
+
if (progress.insertionStarted)
|
|
56158
|
+
throw new CodedError(
|
|
56159
|
+
`Attachment outcome uncertain; read the exact note before any retry: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
56160
|
+
{ code: "verification_failed", indeterminate: true }
|
|
56161
|
+
);
|
|
56162
|
+
throw error2;
|
|
55912
56163
|
} finally {
|
|
55913
|
-
|
|
56164
|
+
try {
|
|
56165
|
+
rmSync7(directory, { recursive: true, force: true });
|
|
56166
|
+
} catch {
|
|
56167
|
+
console.error("Could not remove private temporary attachment files");
|
|
56168
|
+
}
|
|
55914
56169
|
}
|
|
55915
56170
|
}
|
|
55916
56171
|
|
|
@@ -59994,7 +60249,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
59994
60249
|
);
|
|
59995
60250
|
tool(
|
|
59996
60251
|
"create-checklist-item",
|
|
59997
|
-
"Use when: appending one real unchecked Notes checklist item.\nReturns: the created checklist identity after exact-note readback.\nDo not use when: plain text is sufficient or more than one item is requested.\nSafety: preserves existing text, links, native objects, checklist identities, and formatting.",
|
|
60252
|
+
"Use when: appending one real unchecked Notes checklist item.\nReturns: the created checklist identity after exact-note readback.\nDo not use when: plain text is sufficient or more than one item is requested.\nSafety: preserves existing text, links, native objects, checklist identities, and formatting. If a checklist already exists, requires one contiguous group at the end of the note; otherwise refuses before running the bridge.",
|
|
59998
60253
|
{
|
|
59999
60254
|
...common,
|
|
60000
60255
|
text: external_exports.string().min(1).max(1e4).refine((s) => !/[\r\n\0]/u.test(s), "One line per checklist item")
|
|
@@ -60018,7 +60273,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
60018
60273
|
);
|
|
60019
60274
|
tool(
|
|
60020
60275
|
"create-checklist-items",
|
|
60021
|
-
"Use when: appending several real unchecked Notes checklist items to one note, in order.\nReturns: each landed item's native identity and text, the final order check, and the new revision; on a stop, which items landed, the item whose outcome is uncertain, and the items not attempted.\nDo not use when: one item is enough (create-checklist-item) or plain text is sufficient.\nSafety: runs the verified single-item bridge once per item, chaining each verified revision into the next;
|
|
60276
|
+
"Use when: appending several real unchecked Notes checklist items to one note, in order.\nReturns: each landed item's native identity and text, the final order check, and the new revision; on a stop, which items landed, the item whose outcome is uncertain, and the items not attempted.\nDo not use when: one item is enough (create-checklist-item) or plain text is sufficient.\nSafety: runs the verified single-item bridge once per item, chaining each verified revision into the next; requires any existing checklist to be one contiguous group at the end of the note before dispatch. Checks after every item that exactly one new unchecked item with that text and a new identity appeared and that earlier items kept theirs, and stops at the first uncertain result without retrying. Each item takes a few seconds, so a full batch can run about a minute; if the call times out on the client side, read the note before retrying and send only items that are not present.",
|
|
60022
60277
|
{
|
|
60023
60278
|
...common,
|
|
60024
60279
|
items: external_exports.array(
|
|
@@ -62701,10 +62956,10 @@ async function readEditorJson(req) {
|
|
|
62701
62956
|
if (!/^application\/json(?:\s*;|$)/i.test(type))
|
|
62702
62957
|
throw new EditorHttpError(415, "unsupported-media-type", "Send JSON (application/json).");
|
|
62703
62958
|
const chunks = [];
|
|
62704
|
-
let
|
|
62959
|
+
let size2 = 0;
|
|
62705
62960
|
for await (const chunk of req) {
|
|
62706
|
-
|
|
62707
|
-
if (
|
|
62961
|
+
size2 += chunk.length;
|
|
62962
|
+
if (size2 > MAX_EDITOR_BODY_BYTES)
|
|
62708
62963
|
throw new EditorHttpError(413, "too-large", "Request body too large.");
|
|
62709
62964
|
chunks.push(chunk);
|
|
62710
62965
|
}
|
|
@@ -63055,12 +63310,12 @@ var textFingerprint = (normalized2) => createHash8("sha256").update(normalized2,
|
|
|
63055
63310
|
function textSimilarity(a, b) {
|
|
63056
63311
|
if (a === b) return 1;
|
|
63057
63312
|
if (!a || !b) return 0;
|
|
63058
|
-
const
|
|
63313
|
+
const size2 = Math.min(a.length, b.length) < 3 ? 2 : 3;
|
|
63059
63314
|
const grams = (s) => {
|
|
63060
63315
|
const out = /* @__PURE__ */ new Map();
|
|
63061
63316
|
const padded = ` ${s} `;
|
|
63062
|
-
for (let i = 0; i +
|
|
63063
|
-
const g = padded.slice(i, i +
|
|
63317
|
+
for (let i = 0; i + size2 <= padded.length; i++) {
|
|
63318
|
+
const g = padded.slice(i, i + size2);
|
|
63064
63319
|
out.set(g, (out.get(g) ?? 0) + 1);
|
|
63065
63320
|
}
|
|
63066
63321
|
return out;
|
|
@@ -64900,18 +65155,27 @@ registerTool(
|
|
|
64900
65155
|
registerTool(
|
|
64901
65156
|
"get-note-tables",
|
|
64902
65157
|
{
|
|
64903
|
-
description: "Use when: reading the native tables in one exact note as data or Markdown.\nReturns:
|
|
64904
|
-
inputSchema: {
|
|
65158
|
+
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.",
|
|
65159
|
+
inputSchema: {
|
|
65160
|
+
id: noteIdInput,
|
|
65161
|
+
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"),
|
|
65162
|
+
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})`),
|
|
65163
|
+
maxBytes: external_exports.number().int().min(MIN_TABLE_RESPONSE_BYTES).max(MAX_TABLE_RESPONSE_BYTES).optional().describe(
|
|
65164
|
+
`Maximum UTF-8 bytes of the serialized tool response (default ${DEFAULT_TABLE_RESPONSE_BYTES}, max ${MAX_TABLE_RESPONSE_BYTES})`
|
|
65165
|
+
)
|
|
65166
|
+
},
|
|
64905
65167
|
outputSchema: {
|
|
64906
65168
|
id: external_exports.string().optional(),
|
|
64907
65169
|
tables: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
64908
65170
|
tableCount: external_exports.number().optional(),
|
|
64909
65171
|
tableCellsComplete: external_exports.boolean().optional(),
|
|
64910
|
-
markdown: external_exports.string().optional()
|
|
65172
|
+
markdown: external_exports.string().optional(),
|
|
65173
|
+
page: external_exports.record(external_exports.unknown()).optional()
|
|
64911
65174
|
},
|
|
64912
65175
|
annotations: { readOnlyHint: true }
|
|
64913
65176
|
},
|
|
64914
|
-
withErrorHandling(({ id: id2 }) => {
|
|
65177
|
+
withErrorHandling(({ id: id2, offset, limit, maxBytes }) => {
|
|
65178
|
+
const options = validateTablePageOptions({ offset, limit, maxBytes });
|
|
64915
65179
|
const { metadata, message } = getNoteMetadata(id2);
|
|
64916
65180
|
if (!metadata) return errorResponse(message || `Failed to read note "${id2}"`);
|
|
64917
65181
|
if (metadata.passwordProtected) {
|
|
@@ -64920,17 +65184,7 @@ registerTool(
|
|
|
64920
65184
|
);
|
|
64921
65185
|
}
|
|
64922
65186
|
const result = notesManager.getNoteTablesById(id2);
|
|
64923
|
-
|
|
64924
|
-
const summary = count === 0 ? "This note has no native tables." : `${count} table(s)${result.tableCellsComplete ? "" : " (some content could not be decoded; see tables[].reason)"}:
|
|
64925
|
-
|
|
64926
|
-
${result.markdown}`;
|
|
64927
|
-
return successResponse(summary, {
|
|
64928
|
-
id: id2,
|
|
64929
|
-
tables: result.tables,
|
|
64930
|
-
tableCount: count,
|
|
64931
|
-
tableCellsComplete: result.tableCellsComplete,
|
|
64932
|
-
markdown: result.markdown
|
|
64933
|
-
});
|
|
65187
|
+
return tablePageResponse(id2, result, options);
|
|
64934
65188
|
}, "Error reading note tables")
|
|
64935
65189
|
);
|
|
64936
65190
|
registerTool(
|
|
@@ -65709,7 +65963,7 @@ registerTool(
|
|
|
65709
65963
|
readLinks: (noteId3) => readRichNote(noteId3).links,
|
|
65710
65964
|
append: (request) => {
|
|
65711
65965
|
let route = "applescript";
|
|
65712
|
-
const
|
|
65966
|
+
const response2 = guardedAppend(
|
|
65713
65967
|
{
|
|
65714
65968
|
...request,
|
|
65715
65969
|
format: "html",
|
|
@@ -65717,11 +65971,11 @@ registerTool(
|
|
|
65717
65971
|
},
|
|
65718
65972
|
(r) => route = r
|
|
65719
65973
|
);
|
|
65720
|
-
if (
|
|
65721
|
-
const envelope =
|
|
65722
|
-
throw envelope?.code ? new CodedError(
|
|
65974
|
+
if (response2.isError) {
|
|
65975
|
+
const envelope = response2.structuredContent;
|
|
65976
|
+
throw envelope?.code ? new CodedError(response2.content[0].text, envelope) : new Error(response2.content[0].text);
|
|
65723
65977
|
}
|
|
65724
|
-
return { route, contentHash: String(
|
|
65978
|
+
return { route, contentHash: String(response2.structuredContent?.contentHash ?? "") };
|
|
65725
65979
|
}
|
|
65726
65980
|
}
|
|
65727
65981
|
);
|
|
@@ -66862,7 +67116,7 @@ registerTool(
|
|
|
66862
67116
|
`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.`
|
|
66863
67117
|
);
|
|
66864
67118
|
}
|
|
66865
|
-
const
|
|
67119
|
+
const response2 = {
|
|
66866
67120
|
content: [
|
|
66867
67121
|
{
|
|
66868
67122
|
type: "text",
|
|
@@ -66877,13 +67131,13 @@ Full JSON export:`
|
|
|
66877
67131
|
],
|
|
66878
67132
|
structuredContent: { ...exportData }
|
|
66879
67133
|
};
|
|
66880
|
-
const responseBytes = Buffer.byteLength(JSON.stringify(
|
|
67134
|
+
const responseBytes = Buffer.byteLength(JSON.stringify(response2));
|
|
66881
67135
|
if (responseBytes > maxResponseBytes) {
|
|
66882
67136
|
return errorResponse(
|
|
66883
67137
|
`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.`
|
|
66884
67138
|
);
|
|
66885
67139
|
}
|
|
66886
|
-
return
|
|
67140
|
+
return response2;
|
|
66887
67141
|
}, "Error exporting notes")
|
|
66888
67142
|
);
|
|
66889
67143
|
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.6",
|
|
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",
|