apple-notes-mcp 2.9.1 → 2.9.3
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 +103 -3
- package/build/index.js +1174 -457
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -40618,7 +40618,7 @@ var Decoder = class {
|
|
|
40618
40618
|
* positively and negated by the caller.
|
|
40619
40619
|
*/
|
|
40620
40620
|
leaf(key, value, excluded) {
|
|
40621
|
-
const
|
|
40621
|
+
const flag3 = (description, negative) => typeof value === "boolean" ? {
|
|
40622
40622
|
type: key,
|
|
40623
40623
|
value,
|
|
40624
40624
|
...excluded ? { excluded: true } : {},
|
|
@@ -40626,25 +40626,25 @@ var Decoder = class {
|
|
|
40626
40626
|
} : null;
|
|
40627
40627
|
switch (key) {
|
|
40628
40628
|
case "checklist":
|
|
40629
|
-
return
|
|
40629
|
+
return flag3("has a checklist", "has no checklist");
|
|
40630
40630
|
case "checklistInProgress":
|
|
40631
|
-
return
|
|
40631
|
+
return flag3("has an unfinished checklist", "has no unfinished checklist");
|
|
40632
40632
|
case "checklistCompleted":
|
|
40633
|
-
return
|
|
40633
|
+
return flag3("has a completed checklist", "has no completed checklist");
|
|
40634
40634
|
case "attachment":
|
|
40635
|
-
return
|
|
40635
|
+
return flag3("has attachments", "has no attachments");
|
|
40636
40636
|
case "pinned":
|
|
40637
|
-
return
|
|
40637
|
+
return flag3("is pinned", "is not pinned");
|
|
40638
40638
|
case "systemPaper":
|
|
40639
|
-
return
|
|
40639
|
+
return flag3("is a Quick Note", "is not a Quick Note");
|
|
40640
40640
|
case "passwordProtected":
|
|
40641
|
-
return
|
|
40641
|
+
return flag3("is locked", "is not locked");
|
|
40642
40642
|
case "shared":
|
|
40643
|
-
return
|
|
40643
|
+
return flag3("is shared", "is not shared");
|
|
40644
40644
|
case "mention":
|
|
40645
|
-
return
|
|
40645
|
+
return flag3("mentions anyone", "mentions no one");
|
|
40646
40646
|
case "tagged":
|
|
40647
|
-
return
|
|
40647
|
+
return flag3("has any tag", "has no tags");
|
|
40648
40648
|
case "attachmentSection": {
|
|
40649
40649
|
if (typeof value !== "number" || !ATTACHMENT_SECTIONS[value]) return null;
|
|
40650
40650
|
return {
|
|
@@ -45724,12 +45724,681 @@ function assembleInventory(rows, accounts, scope2) {
|
|
|
45724
45724
|
};
|
|
45725
45725
|
}
|
|
45726
45726
|
|
|
45727
|
+
// src/utils/noteBlocks.ts
|
|
45728
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
45729
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
45730
|
+
import { homedir as homedir12 } from "node:os";
|
|
45731
|
+
import { join as join13 } from "node:path";
|
|
45732
|
+
import { gunzipSync as gunzipSync7 } from "node:zlib";
|
|
45733
|
+
var NoteBlocksError = class extends Error {
|
|
45734
|
+
code;
|
|
45735
|
+
constructor(code, message) {
|
|
45736
|
+
super(message);
|
|
45737
|
+
this.name = "NoteBlocksError";
|
|
45738
|
+
this.code = code;
|
|
45739
|
+
}
|
|
45740
|
+
};
|
|
45741
|
+
var STYLE_NAMES = {
|
|
45742
|
+
0: "title",
|
|
45743
|
+
1: "heading",
|
|
45744
|
+
2: "subheading",
|
|
45745
|
+
4: "monospaced",
|
|
45746
|
+
100: "bulleted",
|
|
45747
|
+
101: "dashed",
|
|
45748
|
+
102: "numbered",
|
|
45749
|
+
103: "checklist"
|
|
45750
|
+
};
|
|
45751
|
+
var ALIGNMENTS = {
|
|
45752
|
+
0: "left",
|
|
45753
|
+
1: "center",
|
|
45754
|
+
2: "right",
|
|
45755
|
+
3: "justify"
|
|
45756
|
+
};
|
|
45757
|
+
var HIGHLIGHTS = {
|
|
45758
|
+
1: "purple",
|
|
45759
|
+
2: "pink",
|
|
45760
|
+
3: "orange",
|
|
45761
|
+
4: "mint",
|
|
45762
|
+
5: "blue"
|
|
45763
|
+
};
|
|
45764
|
+
var KNOWN_RUN_FIELDS = /* @__PURE__ */ new Set([1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 14]);
|
|
45765
|
+
var KNOWN_PARAGRAPH_FIELDS = /* @__PURE__ */ new Set([1, 2, 4, 5, 8, 9]);
|
|
45766
|
+
var isSafeLink = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
|
|
45767
|
+
var utf8 = new TextDecoder();
|
|
45768
|
+
var first = (fields, n) => fields.find((f) => f.fieldNumber === n);
|
|
45769
|
+
var varintOf = (fields, n) => {
|
|
45770
|
+
const f = first(fields, n);
|
|
45771
|
+
return f?.wireType === 0 && f.varint !== void 0 ? signedVarint(f.varint) : void 0;
|
|
45772
|
+
};
|
|
45773
|
+
var bytesOf = (fields, n) => {
|
|
45774
|
+
const f = first(fields, n);
|
|
45775
|
+
return f?.wireType === 2 ? f.bytes : void 0;
|
|
45776
|
+
};
|
|
45777
|
+
var stringOf = (fields, n) => {
|
|
45778
|
+
const b = bytesOf(fields, n);
|
|
45779
|
+
return b ? utf8.decode(b) : void 0;
|
|
45780
|
+
};
|
|
45781
|
+
var sub3 = (fields, n) => {
|
|
45782
|
+
const b = bytesOf(fields, n);
|
|
45783
|
+
return b ? decodeWireFields(b) : void 0;
|
|
45784
|
+
};
|
|
45785
|
+
var hex2 = (bytes) => Buffer.from(bytes).toString("hex");
|
|
45786
|
+
var uuidString = (bytes) => {
|
|
45787
|
+
const h = hex2(bytes).toUpperCase();
|
|
45788
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
45789
|
+
};
|
|
45790
|
+
var channel = (value) => Math.max(0, Math.min(255, Math.round((Number.isFinite(value) ? value : 0) * 255))).toString(16).toUpperCase().padStart(2, "0");
|
|
45791
|
+
function colorOf(fields) {
|
|
45792
|
+
const [r, g, b, a] = [1, 2, 3, 4].map((n) => fixed32Float(first(fields, n)));
|
|
45793
|
+
if (r === void 0 && g === void 0 && b === void 0) return void 0;
|
|
45794
|
+
const rgb = `#${channel(r)}${channel(g)}${channel(b)}`;
|
|
45795
|
+
return a === void 0 || a >= 0.999 ? rgb : `${rgb}${channel(a)}`;
|
|
45796
|
+
}
|
|
45797
|
+
var DEFAULT_PARAGRAPH = {
|
|
45798
|
+
styleType: null,
|
|
45799
|
+
alignmentValue: 0,
|
|
45800
|
+
indent: 0,
|
|
45801
|
+
blockQuote: false
|
|
45802
|
+
};
|
|
45803
|
+
function decodeParagraph(fields, tally) {
|
|
45804
|
+
if (!fields) return DEFAULT_PARAGRAPH;
|
|
45805
|
+
for (const n of new Set(fields.map((f) => f.fieldNumber)))
|
|
45806
|
+
if (!KNOWN_PARAGRAPH_FIELDS.has(n)) tally.set(n, (tally.get(n) || 0) + 1);
|
|
45807
|
+
const attrs = {
|
|
45808
|
+
styleType: varintOf(fields, 1) ?? null,
|
|
45809
|
+
alignmentValue: varintOf(fields, 2) ?? 0,
|
|
45810
|
+
indent: Math.max(0, varintOf(fields, 4) ?? 0),
|
|
45811
|
+
blockQuote: (varintOf(fields, 8) ?? 0) !== 0
|
|
45812
|
+
};
|
|
45813
|
+
if (attrs.styleType === -1) attrs.styleType = null;
|
|
45814
|
+
const checklist = sub3(fields, 5);
|
|
45815
|
+
const checklistId = checklist && bytesOf(checklist, 1);
|
|
45816
|
+
if (attrs.styleType === 103 && checklistId)
|
|
45817
|
+
attrs.checklist = { id: hex2(checklistId), done: varintOf(checklist, 2) === 1 };
|
|
45818
|
+
const uuid2 = bytesOf(fields, 9);
|
|
45819
|
+
if (uuid2?.length === 16) attrs.paragraphUuid = uuidString(uuid2);
|
|
45820
|
+
return attrs;
|
|
45821
|
+
}
|
|
45822
|
+
function decodeInline(fields) {
|
|
45823
|
+
const inline = {};
|
|
45824
|
+
const font = sub3(fields, 3);
|
|
45825
|
+
const hints = font ? varintOf(font, 3) ?? 0 : 0;
|
|
45826
|
+
const weight = varintOf(fields, 5) ?? 0;
|
|
45827
|
+
if (weight === 1 || weight === 3 || hints & 1) inline.bold = true;
|
|
45828
|
+
if (weight === 2 || weight === 3 || hints & 2) inline.italic = true;
|
|
45829
|
+
if (varintOf(fields, 6)) inline.underline = true;
|
|
45830
|
+
if (varintOf(fields, 7)) inline.strikethrough = true;
|
|
45831
|
+
const baseline = varintOf(fields, 8) ?? 0;
|
|
45832
|
+
if (baseline > 0) inline.superscript = true;
|
|
45833
|
+
if (baseline < 0) inline.subscript = true;
|
|
45834
|
+
const color = sub3(fields, 10);
|
|
45835
|
+
const colorValue = color && colorOf(color);
|
|
45836
|
+
if (colorValue) inline.color = colorValue;
|
|
45837
|
+
const emphasis = varintOf(fields, 14);
|
|
45838
|
+
if (emphasis !== void 0 && emphasis !== 0) {
|
|
45839
|
+
inline.highlight = HIGHLIGHTS[emphasis] ?? "unknown";
|
|
45840
|
+
if (inline.highlight === "unknown") inline.highlightValue = emphasis;
|
|
45841
|
+
}
|
|
45842
|
+
const link = stringOf(fields, 9);
|
|
45843
|
+
if (link) {
|
|
45844
|
+
inline.link = link;
|
|
45845
|
+
inline.linkSafe = isSafeLink(link);
|
|
45846
|
+
}
|
|
45847
|
+
if (font) {
|
|
45848
|
+
const name = stringOf(font, 1);
|
|
45849
|
+
const size = fixed32Float(first(font, 2));
|
|
45850
|
+
if (name || size !== void 0)
|
|
45851
|
+
inline.font = {
|
|
45852
|
+
...name ? { name } : {},
|
|
45853
|
+
...size !== void 0 && Number.isFinite(size) ? { size } : {}
|
|
45854
|
+
};
|
|
45855
|
+
}
|
|
45856
|
+
const attachment = sub3(fields, 12);
|
|
45857
|
+
const attachmentId = attachment && stringOf(attachment, 1);
|
|
45858
|
+
if (attachmentId)
|
|
45859
|
+
inline.attachment = { id: attachmentId, uti: stringOf(attachment, 2) || "unknown" };
|
|
45860
|
+
return inline;
|
|
45861
|
+
}
|
|
45862
|
+
function wrap(error2) {
|
|
45863
|
+
if (error2 instanceof NoteBlocksError) throw error2;
|
|
45864
|
+
if (error2 instanceof ProtobufDecodeError)
|
|
45865
|
+
throw new NoteBlocksError("malformed-protobuf", error2.message);
|
|
45866
|
+
throw error2;
|
|
45867
|
+
}
|
|
45868
|
+
function decodeNoteBlocks(data) {
|
|
45869
|
+
let note;
|
|
45870
|
+
try {
|
|
45871
|
+
const document = sub3(decodeWireFields(data), 2);
|
|
45872
|
+
note = document && sub3(document, 3);
|
|
45873
|
+
} catch (error2) {
|
|
45874
|
+
wrap(error2);
|
|
45875
|
+
}
|
|
45876
|
+
const textBytes = note && bytesOf(note, 2);
|
|
45877
|
+
if (!note || !textBytes)
|
|
45878
|
+
throw new NoteBlocksError("unsupported-structure", "Unsupported Notes document structure");
|
|
45879
|
+
const text2 = utf8.decode(textBytes);
|
|
45880
|
+
const runTally = /* @__PURE__ */ new Map();
|
|
45881
|
+
const paragraphTally = /* @__PURE__ */ new Map();
|
|
45882
|
+
const runs = [];
|
|
45883
|
+
let position = 0;
|
|
45884
|
+
try {
|
|
45885
|
+
for (const field of note.filter((f) => f.fieldNumber === 5)) {
|
|
45886
|
+
if (field.wireType !== 2 || !field.bytes)
|
|
45887
|
+
throw new NoteBlocksError("invalid-runs", "Invalid Notes attribute run");
|
|
45888
|
+
const fields = decodeWireFields(field.bytes);
|
|
45889
|
+
const length = varintOf(fields, 1);
|
|
45890
|
+
if (length === void 0 || length < 0 || position + length > text2.length)
|
|
45891
|
+
throw new NoteBlocksError("invalid-runs", "Invalid Notes run length");
|
|
45892
|
+
for (const n of new Set(fields.map((f) => f.fieldNumber)))
|
|
45893
|
+
if (!KNOWN_RUN_FIELDS.has(n)) runTally.set(n, (runTally.get(n) || 0) + 1);
|
|
45894
|
+
runs.push({
|
|
45895
|
+
start: position,
|
|
45896
|
+
length,
|
|
45897
|
+
paragraph: decodeParagraph(sub3(fields, 2), paragraphTally),
|
|
45898
|
+
inline: decodeInline(fields)
|
|
45899
|
+
});
|
|
45900
|
+
position += length;
|
|
45901
|
+
}
|
|
45902
|
+
} catch (error2) {
|
|
45903
|
+
wrap(error2);
|
|
45904
|
+
}
|
|
45905
|
+
if (position !== text2.length)
|
|
45906
|
+
throw new NoteBlocksError("invalid-runs", "Incomplete Notes attribute runs");
|
|
45907
|
+
const blocks = [];
|
|
45908
|
+
const attachments = [];
|
|
45909
|
+
let runIndex = 0;
|
|
45910
|
+
let paragraphStart = 0;
|
|
45911
|
+
while (paragraphStart < text2.length) {
|
|
45912
|
+
const newline = text2.indexOf("\n", paragraphStart);
|
|
45913
|
+
const end = newline === -1 ? text2.length : newline;
|
|
45914
|
+
while (runIndex < runs.length - 1 && runs[runIndex].start + runs[runIndex].length <= paragraphStart)
|
|
45915
|
+
runIndex++;
|
|
45916
|
+
const attrs = runs[runIndex]?.paragraph ?? DEFAULT_PARAGRAPH;
|
|
45917
|
+
const style = attrs.styleType === null ? "body" : STYLE_NAMES[attrs.styleType] ?? "unknown";
|
|
45918
|
+
const alignment = ALIGNMENTS[attrs.alignmentValue] ?? "unknown";
|
|
45919
|
+
const block = {
|
|
45920
|
+
index: blocks.length,
|
|
45921
|
+
start: paragraphStart,
|
|
45922
|
+
length: end - paragraphStart,
|
|
45923
|
+
text: text2.slice(paragraphStart, end),
|
|
45924
|
+
style,
|
|
45925
|
+
styleType: attrs.styleType,
|
|
45926
|
+
indent: attrs.indent,
|
|
45927
|
+
alignment,
|
|
45928
|
+
...alignment === "unknown" ? { alignmentValue: attrs.alignmentValue } : {},
|
|
45929
|
+
blockQuote: attrs.blockQuote,
|
|
45930
|
+
...attrs.checklist ? { checklist: attrs.checklist } : {},
|
|
45931
|
+
...attrs.paragraphUuid ? { paragraphUuid: attrs.paragraphUuid } : {},
|
|
45932
|
+
runs: [],
|
|
45933
|
+
attachments: []
|
|
45934
|
+
};
|
|
45935
|
+
for (let i = runIndex; i < runs.length && runs[i].start < end; i++) {
|
|
45936
|
+
const run = runs[i];
|
|
45937
|
+
const start = Math.max(run.start, paragraphStart);
|
|
45938
|
+
const stop = Math.min(run.start + run.length, end);
|
|
45939
|
+
if (stop <= start) continue;
|
|
45940
|
+
block.runs.push({
|
|
45941
|
+
start,
|
|
45942
|
+
length: stop - start,
|
|
45943
|
+
text: text2.slice(start, stop),
|
|
45944
|
+
...run.inline
|
|
45945
|
+
});
|
|
45946
|
+
if (run.inline.attachment)
|
|
45947
|
+
for (let at = text2.indexOf("\uFFFC", start); at !== -1 && at < stop; at = text2.indexOf("\uFFFC", at + 1)) {
|
|
45948
|
+
const marker = { ...run.inline.attachment, start: at, blockIndex: block.index };
|
|
45949
|
+
block.attachments.push(marker);
|
|
45950
|
+
attachments.push(marker);
|
|
45951
|
+
}
|
|
45952
|
+
}
|
|
45953
|
+
blocks.push(block);
|
|
45954
|
+
paragraphStart = end + 1;
|
|
45955
|
+
}
|
|
45956
|
+
return {
|
|
45957
|
+
text: text2,
|
|
45958
|
+
textLength: text2.length,
|
|
45959
|
+
blocks,
|
|
45960
|
+
attachments,
|
|
45961
|
+
undecodedFields: {
|
|
45962
|
+
attributeRun: Object.fromEntries([...runTally].sort((a, b) => a[0] - b[0])),
|
|
45963
|
+
paragraphStyle: Object.fromEntries([...paragraphTally].sort((a, b) => a[0] - b[0]))
|
|
45964
|
+
},
|
|
45965
|
+
summary: summarize(blocks, attachments)
|
|
45966
|
+
};
|
|
45967
|
+
}
|
|
45968
|
+
function summarize(blocks, attachments) {
|
|
45969
|
+
const summary = {
|
|
45970
|
+
blocks: blocks.length,
|
|
45971
|
+
styles: {},
|
|
45972
|
+
alignments: {},
|
|
45973
|
+
blockQuotes: 0,
|
|
45974
|
+
indented: 0,
|
|
45975
|
+
checklist: { total: 0, done: 0 },
|
|
45976
|
+
inline: {
|
|
45977
|
+
bold: 0,
|
|
45978
|
+
italic: 0,
|
|
45979
|
+
underline: 0,
|
|
45980
|
+
strikethrough: 0,
|
|
45981
|
+
superscript: 0,
|
|
45982
|
+
subscript: 0,
|
|
45983
|
+
color: 0,
|
|
45984
|
+
highlight: 0,
|
|
45985
|
+
link: 0,
|
|
45986
|
+
unsafeLink: 0
|
|
45987
|
+
},
|
|
45988
|
+
attachments: attachments.length
|
|
45989
|
+
};
|
|
45990
|
+
for (const block of blocks) {
|
|
45991
|
+
summary.styles[block.style] = (summary.styles[block.style] || 0) + 1;
|
|
45992
|
+
summary.alignments[block.alignment] = (summary.alignments[block.alignment] || 0) + 1;
|
|
45993
|
+
if (block.blockQuote) summary.blockQuotes++;
|
|
45994
|
+
if (block.indent > 0) summary.indented++;
|
|
45995
|
+
if (block.checklist) {
|
|
45996
|
+
summary.checklist.total++;
|
|
45997
|
+
if (block.checklist.done) summary.checklist.done++;
|
|
45998
|
+
}
|
|
45999
|
+
for (const run of block.runs) {
|
|
46000
|
+
for (const key of [
|
|
46001
|
+
"bold",
|
|
46002
|
+
"italic",
|
|
46003
|
+
"underline",
|
|
46004
|
+
"strikethrough",
|
|
46005
|
+
"superscript",
|
|
46006
|
+
"subscript",
|
|
46007
|
+
"color",
|
|
46008
|
+
"highlight",
|
|
46009
|
+
"link"
|
|
46010
|
+
])
|
|
46011
|
+
if (run[key]) summary.inline[key]++;
|
|
46012
|
+
if (run.link && !run.linkSafe) summary.inline.unsafeLink++;
|
|
46013
|
+
}
|
|
46014
|
+
}
|
|
46015
|
+
return summary;
|
|
46016
|
+
}
|
|
46017
|
+
function decodeCompressedNoteBlocks(compressed) {
|
|
46018
|
+
let data;
|
|
46019
|
+
try {
|
|
46020
|
+
data = gunzipSync7(compressed, { maxOutputLength: 32 * 1024 * 1024 });
|
|
46021
|
+
} catch (error2) {
|
|
46022
|
+
throw new NoteBlocksError(
|
|
46023
|
+
"decompress-failed",
|
|
46024
|
+
`Failed to decompress note data: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
46025
|
+
);
|
|
46026
|
+
}
|
|
46027
|
+
return decodeNoteBlocks(data);
|
|
46028
|
+
}
|
|
46029
|
+
var DEFAULT_BLOCKS_MAX_BYTES = 4 * 1024 * 1024;
|
|
46030
|
+
function blocksMaxResponseBytes(env = process.env) {
|
|
46031
|
+
const raw = env.APPLE_NOTES_MCP_BLOCKS_MAX_BYTES;
|
|
46032
|
+
if (raw !== void 0) {
|
|
46033
|
+
const n = Number(raw);
|
|
46034
|
+
if (Number.isFinite(n) && n > 0) return Math.floor(n);
|
|
46035
|
+
}
|
|
46036
|
+
return DEFAULT_BLOCKS_MAX_BYTES;
|
|
46037
|
+
}
|
|
46038
|
+
function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResponseBytes() } = {}) {
|
|
46039
|
+
const total = doc.blocks.length;
|
|
46040
|
+
const start = Math.min(Math.max(0, offset), total);
|
|
46041
|
+
const blocks = [];
|
|
46042
|
+
let bytes = 0;
|
|
46043
|
+
for (let i = start; i < total && blocks.length < limit; i++) {
|
|
46044
|
+
let block = doc.blocks[i];
|
|
46045
|
+
let size = Buffer.byteLength(JSON.stringify(block));
|
|
46046
|
+
if (bytes + size > maxBytes) {
|
|
46047
|
+
if (blocks.length) break;
|
|
46048
|
+
block = {
|
|
46049
|
+
...block,
|
|
46050
|
+
text: "",
|
|
46051
|
+
textOmitted: true,
|
|
46052
|
+
runs: block.runs.map((run) => ({ ...run, text: "" }))
|
|
46053
|
+
};
|
|
46054
|
+
size = Buffer.byteLength(JSON.stringify(block));
|
|
46055
|
+
}
|
|
46056
|
+
blocks.push(block);
|
|
46057
|
+
bytes += size;
|
|
46058
|
+
}
|
|
46059
|
+
const next = start + blocks.length;
|
|
46060
|
+
return {
|
|
46061
|
+
textLength: doc.textLength,
|
|
46062
|
+
blocks,
|
|
46063
|
+
page: {
|
|
46064
|
+
offset: start,
|
|
46065
|
+
returned: blocks.length,
|
|
46066
|
+
total,
|
|
46067
|
+
hasMore: next < total,
|
|
46068
|
+
...next < total ? { nextOffset: next } : {}
|
|
46069
|
+
},
|
|
46070
|
+
summary: doc.summary,
|
|
46071
|
+
undecodedFields: doc.undecodedFields
|
|
46072
|
+
};
|
|
46073
|
+
}
|
|
46074
|
+
var NOTES_DB_PATH8 = join13(
|
|
46075
|
+
homedir12(),
|
|
46076
|
+
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46077
|
+
);
|
|
46078
|
+
function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
|
|
46079
|
+
const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]{1,18})$/i.exec(id2)?.[1];
|
|
46080
|
+
if (!pk)
|
|
46081
|
+
throw new NoteBlocksError(
|
|
46082
|
+
"invalid-id",
|
|
46083
|
+
`Invalid note ID: expected x-coredata://<store>/ICNote/p<number>`
|
|
46084
|
+
);
|
|
46085
|
+
if (!existsSync9(dbPath2))
|
|
46086
|
+
throw new NoteBlocksError("no-full-disk-access", "The Notes database is not readable");
|
|
46087
|
+
const sql = "SELECT json_object('exists', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = @pk AND Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')), 'data', (SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE = @pk), 'encrypted', (SELECT ZCRYPTOINITIALIZATIONVECTOR IS NOT NULL FROM ZICNOTEDATA WHERE ZNOTE = @pk));";
|
|
46088
|
+
let output;
|
|
46089
|
+
try {
|
|
46090
|
+
output = execFileSync12(
|
|
46091
|
+
"/usr/bin/sqlite3",
|
|
46092
|
+
["-readonly", "-cmd", ".parameter init", "-cmd", `.parameter set @pk ${pk}`, dbPath2, sql],
|
|
46093
|
+
{
|
|
46094
|
+
encoding: "utf8",
|
|
46095
|
+
timeout: 5e3,
|
|
46096
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
46097
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
46098
|
+
}
|
|
46099
|
+
).trim();
|
|
46100
|
+
} catch (error2) {
|
|
46101
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
46102
|
+
if (/authorization denied|unable to open database/i.test(message))
|
|
46103
|
+
throw new NoteBlocksError("no-full-disk-access", "The Notes database is not readable");
|
|
46104
|
+
throw new NoteBlocksError("query-failed", "Failed to query the Notes database");
|
|
46105
|
+
}
|
|
46106
|
+
let row;
|
|
46107
|
+
try {
|
|
46108
|
+
row = JSON.parse(output);
|
|
46109
|
+
} catch {
|
|
46110
|
+
throw new NoteBlocksError("query-failed", "Unexpected Notes database response");
|
|
46111
|
+
}
|
|
46112
|
+
if (!row.exists) throw new NoteBlocksError("not-found", `No note found for ID "${id2}"`);
|
|
46113
|
+
if (row.encrypted)
|
|
46114
|
+
throw new NoteBlocksError(
|
|
46115
|
+
"encrypted",
|
|
46116
|
+
"This note is password-protected; its body is encrypted and cannot be decoded"
|
|
46117
|
+
);
|
|
46118
|
+
if (!row.data || !/^[0-9a-f]+$/i.test(row.data))
|
|
46119
|
+
throw new NoteBlocksError("no-body", "No body data is stored for this note");
|
|
46120
|
+
return decodeCompressedNoteBlocks(Buffer.from(row.data, "hex"));
|
|
46121
|
+
}
|
|
46122
|
+
|
|
46123
|
+
// src/utils/noteRecentList.ts
|
|
46124
|
+
var RECENT_LIMIT = { DEFAULT: 50, MAX: 1e3 };
|
|
46125
|
+
var CHECKPOINT_PREFIX = "cdts1:";
|
|
46126
|
+
var PREVIEW_LENGTH = 180;
|
|
46127
|
+
var OBJECT_REPLACEMENT = new RegExp(String.fromCharCode(65532), "gu");
|
|
46128
|
+
function formatCursor(cursor) {
|
|
46129
|
+
const base = CHECKPOINT_PREFIX + doubleToHex(cursor.modified);
|
|
46130
|
+
return cursor.pk === void 0 ? base : `${base}:${cursor.pk}`;
|
|
46131
|
+
}
|
|
46132
|
+
function checkpointFromBits(bits, pk) {
|
|
46133
|
+
if (!bits) return null;
|
|
46134
|
+
try {
|
|
46135
|
+
return formatCursor({ modified: hexToDouble(bits), pk });
|
|
46136
|
+
} catch {
|
|
46137
|
+
return null;
|
|
46138
|
+
}
|
|
46139
|
+
}
|
|
46140
|
+
var CURSOR = /^([0-9a-f]{16})(?::(0|[1-9]\d{0,15}))?$/i;
|
|
46141
|
+
function parseCursorBody(body, input) {
|
|
46142
|
+
const match = CURSOR.exec(body);
|
|
46143
|
+
const pk = match?.[2] !== void 0 ? Number(match[2]) : void 0;
|
|
46144
|
+
try {
|
|
46145
|
+
if (!match || pk !== void 0 && !Number.isSafeInteger(pk)) throw new Error("bad");
|
|
46146
|
+
return pk === void 0 ? { modified: hexToDouble(match[1]) } : { modified: hexToDouble(match[1]), pk };
|
|
46147
|
+
} catch {
|
|
46148
|
+
throw new NoteStoreError(`Invalid checkpoint "${input}".`, "invalid_input");
|
|
46149
|
+
}
|
|
46150
|
+
}
|
|
46151
|
+
var DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
46152
|
+
var DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?$/i;
|
|
46153
|
+
function parseSince(input) {
|
|
46154
|
+
const text2 = input.trim();
|
|
46155
|
+
if (text2.startsWith(CHECKPOINT_PREFIX)) {
|
|
46156
|
+
return parseCursorBody(text2.slice(CHECKPOINT_PREFIX.length), input);
|
|
46157
|
+
}
|
|
46158
|
+
let ms = Number.NaN;
|
|
46159
|
+
const dateOnly = DATE_ONLY.exec(text2);
|
|
46160
|
+
if (dateOnly) {
|
|
46161
|
+
const [year, month, day] = dateOnly.slice(1).map(Number);
|
|
46162
|
+
const local = new Date(year, month - 1, day);
|
|
46163
|
+
if (local.getMonth() === month - 1 && local.getDate() === day) ms = local.getTime();
|
|
46164
|
+
} else if (DATE_TIME.test(text2)) {
|
|
46165
|
+
ms = Date.parse(text2.replace(/(\.\d{3})\d+/, "$1"));
|
|
46166
|
+
}
|
|
46167
|
+
if (!Number.isFinite(ms)) {
|
|
46168
|
+
throw new NoteStoreError(
|
|
46169
|
+
`Invalid since value "${input}". Use an ISO 8601 date or date-time (e.g. 2026-08-01 or 2026-08-01T09:30:00+02:00) or a modifiedCheckpoint token.`,
|
|
46170
|
+
"invalid_input"
|
|
46171
|
+
);
|
|
46172
|
+
}
|
|
46173
|
+
return { modified: (ms - CORE_DATA_EPOCH_MS) / 1e3 };
|
|
46174
|
+
}
|
|
46175
|
+
function textStats(text2) {
|
|
46176
|
+
const visible2 = text2.replace(OBJECT_REPLACEMENT, "");
|
|
46177
|
+
let wordCount2 = 0;
|
|
46178
|
+
for (const token of visible2.split(/\s+/u)) if (/[\p{L}\p{N}]/u.test(token)) wordCount2++;
|
|
46179
|
+
return { wordCount: wordCount2, charCount: [...visible2].length };
|
|
46180
|
+
}
|
|
46181
|
+
function previewText(text2) {
|
|
46182
|
+
const flat = text2.replace(OBJECT_REPLACEMENT, " ").replace(/\s+/gu, " ").trim();
|
|
46183
|
+
return [...flat].slice(0, PREVIEW_LENGTH).join("");
|
|
46184
|
+
}
|
|
46185
|
+
function decodeBodyText(hex3) {
|
|
46186
|
+
if (!hex3 || !/^[0-9a-f]+$/i.test(hex3)) return null;
|
|
46187
|
+
try {
|
|
46188
|
+
return decodeCompressedNoteBlocks(Buffer.from(hex3, "hex")).text;
|
|
46189
|
+
} catch {
|
|
46190
|
+
return null;
|
|
46191
|
+
}
|
|
46192
|
+
}
|
|
46193
|
+
var flag2 = (columns, alias, name) => columns.has(name) ? `COALESCE(${alias}.${name}, 0)` : "0";
|
|
46194
|
+
var REQUIRED2 = ["Z_PK", "Z_ENT", "ZFOLDER", "ZTITLE1", "ZMODIFICATIONDATE1"];
|
|
46195
|
+
function buildRecentNotesSql(columns, options) {
|
|
46196
|
+
requireColumns(columns, REQUIRED2, "list-recent-notes");
|
|
46197
|
+
const owner = col(columns, "f", "ZOWNER");
|
|
46198
|
+
const account = owner === "NULL" ? accountRef(columns, "n") : `COALESCE(${owner}, ${accountRef(columns, "n")})`;
|
|
46199
|
+
const created = ["ZCREATIONDATE3", "ZCREATIONDATE1", "ZCREATIONDATE"].filter((name) => columns.has(name)).map((name) => `n.${name}`).join(", ") || "NULL";
|
|
46200
|
+
const createdExpr = created.includes(",") ? `COALESCE(${created})` : created;
|
|
46201
|
+
const locked = flag2(columns, "n", "ZISPASSWORDPROTECTED");
|
|
46202
|
+
const cloud = ["ZNEEDSTOBEFETCHEDFROMCLOUD", "ZNEEDSINITIALFETCHFROMCLOUD"].filter((name) => columns.has(name)).map((name) => `COALESCE(n.${name}, 0)`);
|
|
46203
|
+
const where = [`n.Z_ENT = ${entity("ICNote")}`];
|
|
46204
|
+
if (!options.includeDeleted) where.push(activeNoteSql(columns, "n", "f"));
|
|
46205
|
+
if (options.scoped) where.push("a.Z_PK = @account");
|
|
46206
|
+
if (options.inFolder) where.push("n.ZFOLDER = @folder");
|
|
46207
|
+
if (options.since) {
|
|
46208
|
+
where.push(
|
|
46209
|
+
options.sinceKey ? "(n.ZMODIFICATIONDATE1 > @since OR (n.ZMODIFICATIONDATE1 = @since AND n.Z_PK > @sincePk))" : "n.ZMODIFICATIONDATE1 > @since"
|
|
46210
|
+
);
|
|
46211
|
+
}
|
|
46212
|
+
const order = options.since ? "n.ZMODIFICATIONDATE1 ASC, n.Z_PK ASC" : "n.ZMODIFICATIONDATE1 DESC, n.Z_PK DESC";
|
|
46213
|
+
const data = options.withBodies ? `CASE WHEN ${locked} = 1 THEN NULL ELSE (SELECT hex(d.ZDATA) FROM ZICNOTEDATA d WHERE d.ZNOTE = n.Z_PK ORDER BY d.Z_PK DESC LIMIT 1) END` : "NULL";
|
|
46214
|
+
return `SELECT json_object('pk', n.Z_PK, 'identifier', ${col(columns, "n", "ZIDENTIFIER")}, 'title', n.ZTITLE1, 'folder', f.Z_PK, 'account', a.Z_PK, 'created', ${createdExpr}, 'modified', n.ZMODIFICATIONDATE1, 'bits', hex(ieee754_to_blob(n.ZMODIFICATIONDATE1)), 'pinned', ${flag2(columns, "n", "ZISPINNED")}, 'locked', ${locked}, 'trash', f.Z_PK IS NOT NULL AND ${trashFolderSql(columns, "f")}, 'tombstoned', NOT ${notTombstonedSql(columns, "n")}, 'cloud', ${cloud.length ? cloud.join(" + ") : "0"}, 'snippet', CASE WHEN ${locked} = 1 THEN NULL ELSE ${col(columns, "n", "ZSNIPPET")} END, 'data', ${data}) FROM ZICCLOUDSYNCINGOBJECT n LEFT JOIN ZICCLOUDSYNCINGOBJECT f ON f.Z_PK = n.ZFOLDER AND f.Z_ENT = ${entity("ICFolder")} LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = ${account} AND a.Z_ENT = ${entity("ICAccount")} WHERE ${where.join(" AND ")} ORDER BY ${order} LIMIT @limit;`;
|
|
46215
|
+
}
|
|
46216
|
+
function resolveFolderPath(folders, paths, input, accountPk) {
|
|
46217
|
+
const candidates = folders.filter(
|
|
46218
|
+
(folder) => !folder.tombstoned && (accountPk === void 0 || folder.account === accountPk)
|
|
46219
|
+
);
|
|
46220
|
+
const wanted = input.trim().replace(/^\/+|\/+$/g, "").toLocaleLowerCase();
|
|
46221
|
+
const byPath = candidates.filter((f) => (paths.get(f.pk) ?? "").toLocaleLowerCase() === wanted);
|
|
46222
|
+
const byName = candidates.filter(
|
|
46223
|
+
(f) => (f.name ?? "").toLocaleLowerCase() === wanted.replace(/\\\//g, "/")
|
|
46224
|
+
);
|
|
46225
|
+
const matches = byPath.length ? byPath : byName;
|
|
46226
|
+
if (matches.length === 1) return matches[0];
|
|
46227
|
+
if (!matches.length) {
|
|
46228
|
+
throw new NoteStoreError(`No folder "${input}" found.`, "invalid_input");
|
|
46229
|
+
}
|
|
46230
|
+
const options = matches.map((f) => paths.get(f.pk)).join(", ");
|
|
46231
|
+
throw new NoteStoreError(
|
|
46232
|
+
`Folder "${input}" is ambiguous (${options}). Pass the full path, or an account.`,
|
|
46233
|
+
"invalid_input"
|
|
46234
|
+
);
|
|
46235
|
+
}
|
|
46236
|
+
function listRecentNotes(options = {}) {
|
|
46237
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
|
|
46238
|
+
const limit = Math.min(
|
|
46239
|
+
Math.max(1, Math.trunc(options.limit ?? RECENT_LIMIT.DEFAULT)),
|
|
46240
|
+
RECENT_LIMIT.MAX
|
|
46241
|
+
);
|
|
46242
|
+
const since = options.since !== void 0 ? parseSince(options.since) : void 0;
|
|
46243
|
+
const columns = readColumns(dbPath2);
|
|
46244
|
+
const context = readStoreContext(dbPath2, columns);
|
|
46245
|
+
const paths = folderPaths(context.folders);
|
|
46246
|
+
const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
|
|
46247
|
+
const folder = options.folder ? resolveFolderPath(context.folders, paths, options.folder, scope2?.pk) : void 0;
|
|
46248
|
+
const withBodies = Boolean(options.wordCounts);
|
|
46249
|
+
const params = { limit: { int: limit } };
|
|
46250
|
+
if (scope2) params.account = { int: scope2.pk };
|
|
46251
|
+
if (folder) params.folder = { int: folder.pk };
|
|
46252
|
+
if (since) params.since = { double: since.modified };
|
|
46253
|
+
if (since?.pk !== void 0) params.sincePk = { int: since.pk };
|
|
46254
|
+
const sql = buildRecentNotesSql(columns, {
|
|
46255
|
+
includeDeleted: Boolean(options.includeDeleted),
|
|
46256
|
+
scoped: Boolean(scope2),
|
|
46257
|
+
inFolder: Boolean(folder),
|
|
46258
|
+
since: Boolean(since),
|
|
46259
|
+
sinceKey: since?.pk !== void 0,
|
|
46260
|
+
withBodies
|
|
46261
|
+
});
|
|
46262
|
+
const raw = parseJsonLines(runReadOnlySql(dbPath2, sql, params));
|
|
46263
|
+
const accountNames = new Map(context.accounts.map((account) => [account.pk, account.name]));
|
|
46264
|
+
const notes = raw.map((row) => toRecentRow(row, context, paths, accountNames, options));
|
|
46265
|
+
const saturated = notes.length === limit;
|
|
46266
|
+
let nextSince;
|
|
46267
|
+
if (since) {
|
|
46268
|
+
nextSince = notes.length ? notes[notes.length - 1].modifiedCheckpoint : formatCursor(since);
|
|
46269
|
+
} else {
|
|
46270
|
+
nextSince = !saturated && notes.length ? notes[0].modifiedCheckpoint : null;
|
|
46271
|
+
}
|
|
46272
|
+
return {
|
|
46273
|
+
notes,
|
|
46274
|
+
count: notes.length,
|
|
46275
|
+
limit,
|
|
46276
|
+
order: since ? "oldest-first" : "newest-first",
|
|
46277
|
+
saturated,
|
|
46278
|
+
nextSince,
|
|
46279
|
+
...scope2 ? { account: scope2.name } : {},
|
|
46280
|
+
...folder ? { folder: paths.get(folder.pk) } : {}
|
|
46281
|
+
};
|
|
46282
|
+
}
|
|
46283
|
+
function toRecentRow(row, context, paths, accountNames, options) {
|
|
46284
|
+
const note = {
|
|
46285
|
+
id: noteIdFor(context.uuid, row.pk),
|
|
46286
|
+
identifier: row.identifier ?? null,
|
|
46287
|
+
title: row.title ?? null,
|
|
46288
|
+
folder: row.folder !== null ? paths.get(row.folder) ?? null : null,
|
|
46289
|
+
account: row.account !== null ? accountNames.get(row.account) ?? null : null,
|
|
46290
|
+
created: coreDataToIso(row.created),
|
|
46291
|
+
modified: coreDataToIso(row.modified),
|
|
46292
|
+
modifiedCheckpoint: checkpointFromBits(row.bits, row.pk),
|
|
46293
|
+
pinned: Boolean(row.pinned),
|
|
46294
|
+
locked: Boolean(row.locked),
|
|
46295
|
+
inRecentlyDeleted: Boolean(row.trash),
|
|
46296
|
+
markedForDeletion: Boolean(row.tombstoned)
|
|
46297
|
+
};
|
|
46298
|
+
const text2 = row.locked || row.cloud ? null : decodeBodyText(row.data);
|
|
46299
|
+
if (options.wordCounts) {
|
|
46300
|
+
const stats = text2 === null ? null : textStats(text2);
|
|
46301
|
+
note.wordCount = stats?.wordCount ?? null;
|
|
46302
|
+
note.charCount = stats?.charCount ?? null;
|
|
46303
|
+
}
|
|
46304
|
+
if (options.bodyPreview) {
|
|
46305
|
+
note.textDecoded = text2 !== null;
|
|
46306
|
+
note.bodyPreview = text2 !== null ? previewText(text2) : row.snippet ? previewText(row.snippet) : null;
|
|
46307
|
+
}
|
|
46308
|
+
return note;
|
|
46309
|
+
}
|
|
46310
|
+
function buildFolderCountsSql(columns) {
|
|
46311
|
+
requireColumns(columns, ["Z_PK", "Z_ENT", "ZFOLDER"], "list-folder-tree");
|
|
46312
|
+
return `SELECT json_object('folder', n.ZFOLDER, 'n', COUNT(*)) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_ENT = ${entity("ICNote")} AND n.ZFOLDER IS NOT NULL AND ${notTombstonedSql(columns, "n")} GROUP BY n.ZFOLDER;`;
|
|
46313
|
+
}
|
|
46314
|
+
var KIND_ORDER = { folder: 0, smart: 1, trash: 2 };
|
|
46315
|
+
function compareNodes(a, b) {
|
|
46316
|
+
return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
|
|
46317
|
+
}
|
|
46318
|
+
function folderTree(options = {}) {
|
|
46319
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
|
|
46320
|
+
const columns = readColumns(dbPath2);
|
|
46321
|
+
const context = readStoreContext(dbPath2, columns);
|
|
46322
|
+
const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
|
|
46323
|
+
const counts = new Map(
|
|
46324
|
+
parseJsonLines(
|
|
46325
|
+
runReadOnlySql(dbPath2, buildFolderCountsSql(columns))
|
|
46326
|
+
).map((row) => [row.folder, row.n])
|
|
46327
|
+
);
|
|
46328
|
+
return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
|
|
46329
|
+
}
|
|
46330
|
+
function assembleFolderTree(context, counts, includeDeleted, scope2) {
|
|
46331
|
+
const paths = folderPaths(context.folders);
|
|
46332
|
+
const visible2 = context.folders.filter(
|
|
46333
|
+
(folder) => (includeDeleted || !folder.tombstoned) && (!scope2 || folder.account === scope2.pk)
|
|
46334
|
+
);
|
|
46335
|
+
const byPk = new Map(visible2.map((folder) => [folder.pk, folder]));
|
|
46336
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
46337
|
+
for (const folder of visible2) {
|
|
46338
|
+
const node = {
|
|
46339
|
+
id: `x-coredata://${context.uuid}/ICFolder/p${folder.pk}`,
|
|
46340
|
+
identifier: folder.identifier,
|
|
46341
|
+
name: folder.name ?? "",
|
|
46342
|
+
path: paths.get(folder.pk) ?? "",
|
|
46343
|
+
kind: folder.trash ? "trash" : folder.folderType === 2 ? "smart" : "folder",
|
|
46344
|
+
noteCount: counts.get(folder.pk) ?? 0,
|
|
46345
|
+
totalNoteCount: 0,
|
|
46346
|
+
children: []
|
|
46347
|
+
};
|
|
46348
|
+
if (includeDeleted) node.markedForDeletion = Boolean(folder.tombstoned);
|
|
46349
|
+
nodes.set(folder.pk, node);
|
|
46350
|
+
}
|
|
46351
|
+
const parentOf = (folder) => {
|
|
46352
|
+
if (folder.parent === null || !byPk.has(folder.parent)) return void 0;
|
|
46353
|
+
const seen = /* @__PURE__ */ new Set([folder.pk]);
|
|
46354
|
+
for (let at = byPk.get(folder.parent); at; at = at.parent !== null ? byPk.get(at.parent) : void 0) {
|
|
46355
|
+
if (seen.has(at.pk)) return void 0;
|
|
46356
|
+
seen.add(at.pk);
|
|
46357
|
+
}
|
|
46358
|
+
return folder.parent;
|
|
46359
|
+
};
|
|
46360
|
+
const roots = /* @__PURE__ */ new Map();
|
|
46361
|
+
for (const folder of visible2) {
|
|
46362
|
+
const node = nodes.get(folder.pk);
|
|
46363
|
+
const parent = parentOf(folder);
|
|
46364
|
+
if (parent !== void 0) nodes.get(parent).children.push(node);
|
|
46365
|
+
else {
|
|
46366
|
+
const key = folder.account !== null && context.accounts.some((a) => a.pk === folder.account) ? folder.account : null;
|
|
46367
|
+
roots.set(key, [...roots.get(key) ?? [], node]);
|
|
46368
|
+
}
|
|
46369
|
+
}
|
|
46370
|
+
const finish = (node) => {
|
|
46371
|
+
node.children.sort(compareNodes);
|
|
46372
|
+
node.totalNoteCount = node.noteCount + node.children.reduce((sum, c) => sum + finish(c), 0);
|
|
46373
|
+
return node.totalNoteCount;
|
|
46374
|
+
};
|
|
46375
|
+
const accounts = [];
|
|
46376
|
+
const addAccount = (name, identifier, folders) => {
|
|
46377
|
+
folders.sort(compareNodes);
|
|
46378
|
+
let noteCount = 0;
|
|
46379
|
+
for (const node of folders) {
|
|
46380
|
+
const total = finish(node);
|
|
46381
|
+
if (node.kind === "folder") noteCount += total;
|
|
46382
|
+
}
|
|
46383
|
+
accounts.push({ account: name, identifier, noteCount, folders });
|
|
46384
|
+
};
|
|
46385
|
+
for (const account of scope2 ? [scope2] : context.accounts) {
|
|
46386
|
+
addAccount(account.name, account.identifier, roots.get(account.pk) ?? []);
|
|
46387
|
+
}
|
|
46388
|
+
const orphans = roots.get(null);
|
|
46389
|
+
if (orphans && includeDeleted) addAccount("unknown", null, orphans);
|
|
46390
|
+
return { accounts, folderCount: accounts.reduce((sum, a) => sum + countNodes(a.folders), 0) };
|
|
46391
|
+
}
|
|
46392
|
+
function countNodes(nodes) {
|
|
46393
|
+
return nodes.reduce((sum, node) => sum + 1 + countNodes(node.children), 0);
|
|
46394
|
+
}
|
|
46395
|
+
|
|
45727
46396
|
// src/utils/noteIdentifiers.ts
|
|
45728
|
-
import { execFileSync as
|
|
46397
|
+
import { execFileSync as execFileSync13 } from "child_process";
|
|
45729
46398
|
import * as fs7 from "fs";
|
|
45730
46399
|
import * as os7 from "os";
|
|
45731
46400
|
import * as path7 from "path";
|
|
45732
|
-
var
|
|
46401
|
+
var NOTES_DB_PATH9 = path7.join(
|
|
45733
46402
|
os7.homedir(),
|
|
45734
46403
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45735
46404
|
);
|
|
@@ -45807,7 +46476,7 @@ function runJsonQuery(sql, dbPath2) {
|
|
|
45807
46476
|
if (!fs7.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
45808
46477
|
let out;
|
|
45809
46478
|
try {
|
|
45810
|
-
out =
|
|
46479
|
+
out = execFileSync13("sqlite3", ["-readonly", dbPath2, sql], {
|
|
45811
46480
|
encoding: "utf8",
|
|
45812
46481
|
timeout: 5e3,
|
|
45813
46482
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -45827,7 +46496,7 @@ function runJsonQuery(sql, dbPath2) {
|
|
|
45827
46496
|
function coreDataId(store, entity3, pk) {
|
|
45828
46497
|
return `x-coredata://${store}/${entity3}/p${pk}`;
|
|
45829
46498
|
}
|
|
45830
|
-
function resolveIdentifiers(values, entity3, dbPath2 =
|
|
46499
|
+
function resolveIdentifiers(values, entity3, dbPath2 = NOTES_DB_PATH9) {
|
|
45831
46500
|
const resolved = /* @__PURE__ */ new Map();
|
|
45832
46501
|
const keys = [];
|
|
45833
46502
|
const uuids = [];
|
|
@@ -45873,7 +46542,7 @@ function resolveIdentifiers(values, entity3, dbPath2 = NOTES_DB_PATH8) {
|
|
|
45873
46542
|
return resolved;
|
|
45874
46543
|
}
|
|
45875
46544
|
var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
|
|
45876
|
-
function lookupStableIdentifiers(ids, entity3, dbPath2 =
|
|
46545
|
+
function lookupStableIdentifiers(ids, entity3, dbPath2 = NOTES_DB_PATH9) {
|
|
45877
46546
|
const result = /* @__PURE__ */ new Map();
|
|
45878
46547
|
const wanted = /* @__PURE__ */ new Map();
|
|
45879
46548
|
for (const id2 of ids) {
|
|
@@ -45909,7 +46578,7 @@ function lookupStableIdentifiers(ids, entity3, dbPath2 = NOTES_DB_PATH8) {
|
|
|
45909
46578
|
}
|
|
45910
46579
|
return result;
|
|
45911
46580
|
}
|
|
45912
|
-
function withStableIdentifiers(items, entity3, dbPath2 =
|
|
46581
|
+
function withStableIdentifiers(items, entity3, dbPath2 = NOTES_DB_PATH9) {
|
|
45913
46582
|
const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
|
|
45914
46583
|
if (ids.length === 0) return items;
|
|
45915
46584
|
const found = lookupStableIdentifiers(ids, entity3, dbPath2);
|
|
@@ -46052,11 +46721,11 @@ function describeSearchScope(searchContent, resultCount) {
|
|
|
46052
46721
|
}
|
|
46053
46722
|
|
|
46054
46723
|
// src/utils/noteQueryStore.ts
|
|
46055
|
-
import { execFileSync as
|
|
46724
|
+
import { execFileSync as execFileSync14 } from "child_process";
|
|
46056
46725
|
import * as fs8 from "fs";
|
|
46057
46726
|
import * as os8 from "os";
|
|
46058
46727
|
import * as path8 from "path";
|
|
46059
|
-
import { gunzipSync as
|
|
46728
|
+
import { gunzipSync as gunzipSync8 } from "zlib";
|
|
46060
46729
|
|
|
46061
46730
|
// src/utils/noteQuery.ts
|
|
46062
46731
|
var QUERY_LIMITS = {
|
|
@@ -46504,7 +47173,7 @@ function positiveTextTerms(node, negated = false) {
|
|
|
46504
47173
|
}
|
|
46505
47174
|
|
|
46506
47175
|
// src/utils/noteQueryStore.ts
|
|
46507
|
-
var
|
|
47176
|
+
var NOTES_DB_PATH10 = path8.join(
|
|
46508
47177
|
os8.homedir(),
|
|
46509
47178
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46510
47179
|
);
|
|
@@ -46640,7 +47309,7 @@ function buildScanSql(available, options) {
|
|
|
46640
47309
|
].join(" ");
|
|
46641
47310
|
}
|
|
46642
47311
|
function runSqlite6(dbPath2, query2) {
|
|
46643
|
-
return
|
|
47312
|
+
return execFileSync14("sqlite3", ["-readonly", dbPath2, query2], {
|
|
46644
47313
|
encoding: "utf8",
|
|
46645
47314
|
timeout: 3e4,
|
|
46646
47315
|
maxBuffer: 512 * 1024 * 1024,
|
|
@@ -46710,7 +47379,7 @@ function runNoteQuery(ast, options = {}) {
|
|
|
46710
47379
|
const includeDeleted = options.includeDeleted ?? false;
|
|
46711
47380
|
const withBodies = needsContent(ast);
|
|
46712
47381
|
const withTags = needsTags(ast);
|
|
46713
|
-
const dbPath2 = options.dbPath ??
|
|
47382
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH10;
|
|
46714
47383
|
if (!fs8.existsSync(dbPath2)) throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
|
|
46715
47384
|
let output;
|
|
46716
47385
|
try {
|
|
@@ -46771,7 +47440,7 @@ function runNoteQuery(ast, options = {}) {
|
|
|
46771
47440
|
try {
|
|
46772
47441
|
decoded = decodeNoteBody(
|
|
46773
47442
|
new Uint8Array(
|
|
46774
|
-
|
|
47443
|
+
gunzipSync8(Buffer.from(row.data, "hex"), { maxOutputLength: 32 * 1024 * 1024 })
|
|
46775
47444
|
)
|
|
46776
47445
|
);
|
|
46777
47446
|
} catch {
|
|
@@ -46906,10 +47575,10 @@ function contentSearchFailureHint(message, dbUnavailable) {
|
|
|
46906
47575
|
import { spawnSync } from "child_process";
|
|
46907
47576
|
|
|
46908
47577
|
// src/services/nativeTags.ts
|
|
46909
|
-
import { execFileSync as
|
|
47578
|
+
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
46910
47579
|
import { mkdtempSync as mkdtempSync2, writeFileSync, rmSync as rmSync2 } from "node:fs";
|
|
46911
47580
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
46912
|
-
import { join as
|
|
47581
|
+
import { join as join16 } from "node:path";
|
|
46913
47582
|
|
|
46914
47583
|
// src/services/shortcutConsent.ts
|
|
46915
47584
|
function shortcutConsentHint(shortcut) {
|
|
@@ -46985,7 +47654,7 @@ function addNativeTags(request, deps) {
|
|
|
46985
47654
|
};
|
|
46986
47655
|
}
|
|
46987
47656
|
function listInstalledShortcuts() {
|
|
46988
|
-
return
|
|
47657
|
+
return execFileSync15("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
|
|
46989
47658
|
encoding: "utf8",
|
|
46990
47659
|
timeout: 15e3,
|
|
46991
47660
|
maxBuffer: 1024 * 1024,
|
|
@@ -47011,11 +47680,11 @@ function runNativeTagsShortcut(input) {
|
|
|
47011
47680
|
const status = nativeTagsStatus();
|
|
47012
47681
|
if (!status.installed)
|
|
47013
47682
|
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
47014
|
-
const directory = mkdtempSync2(
|
|
47683
|
+
const directory = mkdtempSync2(join16(tmpdir2(), "apple-notes-native-tags-"));
|
|
47015
47684
|
try {
|
|
47016
|
-
const path10 =
|
|
47685
|
+
const path10 = join16(directory, "request.json");
|
|
47017
47686
|
writeFileSync(path10, JSON.stringify(input), { mode: 384 });
|
|
47018
|
-
|
|
47687
|
+
execFileSync15("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path10], {
|
|
47019
47688
|
encoding: "utf8",
|
|
47020
47689
|
timeout: 6e4,
|
|
47021
47690
|
maxBuffer: 1024 * 1024,
|
|
@@ -47034,10 +47703,10 @@ function runNativeTagsShortcut(input) {
|
|
|
47034
47703
|
}
|
|
47035
47704
|
|
|
47036
47705
|
// src/services/backgroundNotes.ts
|
|
47037
|
-
import { execFileSync as
|
|
47706
|
+
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
47038
47707
|
import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
|
|
47039
47708
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
47040
|
-
import { join as
|
|
47709
|
+
import { join as join17 } from "node:path";
|
|
47041
47710
|
|
|
47042
47711
|
// src/utils/appendMarkdown.ts
|
|
47043
47712
|
var escape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -47121,7 +47790,7 @@ function renderMarkdown(markdown, options = {}) {
|
|
|
47121
47790
|
}
|
|
47122
47791
|
return text2;
|
|
47123
47792
|
};
|
|
47124
|
-
const
|
|
47793
|
+
const visible2 = (html2) => withoutTags(html2).replace(/"/g, '"').replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
47125
47794
|
let html = "", list, quote = false, code, previousBlank = true;
|
|
47126
47795
|
const close = () => {
|
|
47127
47796
|
if (list) {
|
|
@@ -47168,7 +47837,7 @@ function renderMarkdown(markdown, options = {}) {
|
|
|
47168
47837
|
}
|
|
47169
47838
|
const rendered = inline(text2);
|
|
47170
47839
|
html += `<div>${rendered}</div>`;
|
|
47171
|
-
expect.quotes[expect.quotes.length - 1] += ` ${
|
|
47840
|
+
expect.quotes[expect.quotes.length - 1] += ` ${visible2(rendered)}`;
|
|
47172
47841
|
previousBlank = false;
|
|
47173
47842
|
continue;
|
|
47174
47843
|
}
|
|
@@ -47191,7 +47860,7 @@ function renderMarkdown(markdown, options = {}) {
|
|
|
47191
47860
|
}
|
|
47192
47861
|
const rendered = inline(task[2]);
|
|
47193
47862
|
html += `<li>${rendered}</li>`;
|
|
47194
|
-
expect.checklist.push({ text:
|
|
47863
|
+
expect.checklist.push({ text: visible2(rendered).trim(), done: task[1] === "x" });
|
|
47195
47864
|
previousBlank = false;
|
|
47196
47865
|
continue;
|
|
47197
47866
|
}
|
|
@@ -47362,9 +48031,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
47362
48031
|
throw new Error(
|
|
47363
48032
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
47364
48033
|
);
|
|
47365
|
-
const directory = mkdtempSync3(
|
|
48034
|
+
const directory = mkdtempSync3(join17(tmpdir3(), "apple-notes-background-"));
|
|
47366
48035
|
try {
|
|
47367
|
-
const file =
|
|
48036
|
+
const file = join17(directory, "request.json");
|
|
47368
48037
|
writeFileSync2(
|
|
47369
48038
|
file,
|
|
47370
48039
|
JSON.stringify({
|
|
@@ -47384,7 +48053,7 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
47384
48053
|
{ mode: 384 }
|
|
47385
48054
|
);
|
|
47386
48055
|
try {
|
|
47387
|
-
|
|
48056
|
+
execFileSync16("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
|
|
47388
48057
|
encoding: "utf8",
|
|
47389
48058
|
timeout: callTimeoutMs() ?? 6e4,
|
|
47390
48059
|
maxBuffer: 1024 * 1024,
|
|
@@ -47709,7 +48378,7 @@ ${request.content}`
|
|
|
47709
48378
|
}
|
|
47710
48379
|
|
|
47711
48380
|
// src/services/capabilityMatrix.ts
|
|
47712
|
-
import { execFileSync as
|
|
48381
|
+
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
47713
48382
|
import { release } from "node:os";
|
|
47714
48383
|
var MACOS_SHORTCUTS_CLI = "12.0";
|
|
47715
48384
|
var MACOS_MARKDOWN_IMPORT = "26.0";
|
|
@@ -47918,7 +48587,7 @@ function evaluateFeatures(env, features = FEATURES) {
|
|
|
47918
48587
|
function readMacOSVersion() {
|
|
47919
48588
|
if (process.platform !== "darwin") return null;
|
|
47920
48589
|
try {
|
|
47921
|
-
const out =
|
|
48590
|
+
const out = execFileSync17("/usr/bin/sw_vers", ["-productVersion"], {
|
|
47922
48591
|
encoding: "utf8",
|
|
47923
48592
|
timeout: 3e3,
|
|
47924
48593
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -48075,18 +48744,18 @@ function formatDoctorReport(r) {
|
|
|
48075
48744
|
}
|
|
48076
48745
|
|
|
48077
48746
|
// src/services/fileConfig.ts
|
|
48078
|
-
import { existsSync as
|
|
48079
|
-
import { join as
|
|
48080
|
-
import { homedir as
|
|
48747
|
+
import { existsSync as existsSync12, readFileSync as readFileSync2 } from "fs";
|
|
48748
|
+
import { join as join18 } from "path";
|
|
48749
|
+
import { homedir as homedir15 } from "os";
|
|
48081
48750
|
function fileConfigPath(env = process.env) {
|
|
48082
48751
|
const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
|
|
48083
48752
|
if (override && override.trim()) return override.trim();
|
|
48084
|
-
return
|
|
48753
|
+
return join18(homedir15(), "Library", "Application Support", "apple-notes-mcp", "config.json");
|
|
48085
48754
|
}
|
|
48086
48755
|
function loadFileConfig(env = process.env, path10 = fileConfigPath(env)) {
|
|
48087
48756
|
const applied = [];
|
|
48088
48757
|
try {
|
|
48089
|
-
if (!
|
|
48758
|
+
if (!existsSync12(path10)) return applied;
|
|
48090
48759
|
const parsed = JSON.parse(readFileSync2(path10, "utf8"));
|
|
48091
48760
|
if (!parsed || typeof parsed !== "object") return applied;
|
|
48092
48761
|
for (const [k, v] of Object.entries(parsed)) {
|
|
@@ -48365,418 +49034,309 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
|
|
|
48365
49034
|
let shuttingDown = false;
|
|
48366
49035
|
let exited = false;
|
|
48367
49036
|
const exitOnce = () => {
|
|
48368
|
-
if (exited) return;
|
|
48369
|
-
exited = true;
|
|
48370
|
-
clearTimeout(timer);
|
|
48371
|
-
exit();
|
|
48372
|
-
};
|
|
48373
|
-
let timer;
|
|
48374
|
-
const exitWhenDrained = () => {
|
|
48375
|
-
if (stream.writableLength === 0) exitOnce();
|
|
48376
|
-
else stream.once("drain", exitWhenDrained);
|
|
48377
|
-
};
|
|
48378
|
-
return () => {
|
|
48379
|
-
if (shuttingDown) return;
|
|
48380
|
-
shuttingDown = true;
|
|
48381
|
-
timer = setTimeout(exitOnce, timeoutMs);
|
|
48382
|
-
setImmediate(exitWhenDrained);
|
|
48383
|
-
};
|
|
48384
|
-
}
|
|
48385
|
-
|
|
48386
|
-
// src/utils/noteBlocks.ts
|
|
48387
|
-
import { execFileSync as execFileSync17 } from "node:child_process";
|
|
48388
|
-
import { existsSync as existsSync12 } from "node:fs";
|
|
48389
|
-
import { homedir as homedir15 } from "node:os";
|
|
48390
|
-
import { join as join18 } from "node:path";
|
|
48391
|
-
import { gunzipSync as gunzipSync8 } from "node:zlib";
|
|
48392
|
-
var NoteBlocksError = class extends Error {
|
|
48393
|
-
code;
|
|
48394
|
-
constructor(code, message) {
|
|
48395
|
-
super(message);
|
|
48396
|
-
this.name = "NoteBlocksError";
|
|
48397
|
-
this.code = code;
|
|
48398
|
-
}
|
|
48399
|
-
};
|
|
48400
|
-
var STYLE_NAMES = {
|
|
48401
|
-
0: "title",
|
|
48402
|
-
1: "heading",
|
|
48403
|
-
2: "subheading",
|
|
48404
|
-
4: "monospaced",
|
|
48405
|
-
100: "bulleted",
|
|
48406
|
-
101: "dashed",
|
|
48407
|
-
102: "numbered",
|
|
48408
|
-
103: "checklist"
|
|
48409
|
-
};
|
|
48410
|
-
var ALIGNMENTS = {
|
|
48411
|
-
0: "left",
|
|
48412
|
-
1: "center",
|
|
48413
|
-
2: "right",
|
|
48414
|
-
3: "justify"
|
|
48415
|
-
};
|
|
48416
|
-
var HIGHLIGHTS = {
|
|
48417
|
-
1: "purple",
|
|
48418
|
-
2: "pink",
|
|
48419
|
-
3: "orange",
|
|
48420
|
-
4: "mint",
|
|
48421
|
-
5: "blue"
|
|
48422
|
-
};
|
|
48423
|
-
var KNOWN_RUN_FIELDS = /* @__PURE__ */ new Set([1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 14]);
|
|
48424
|
-
var KNOWN_PARAGRAPH_FIELDS = /* @__PURE__ */ new Set([1, 2, 4, 5, 8, 9]);
|
|
48425
|
-
var isSafeLink = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
|
|
48426
|
-
var utf8 = new TextDecoder();
|
|
48427
|
-
var first = (fields, n) => fields.find((f) => f.fieldNumber === n);
|
|
48428
|
-
var varintOf = (fields, n) => {
|
|
48429
|
-
const f = first(fields, n);
|
|
48430
|
-
return f?.wireType === 0 && f.varint !== void 0 ? signedVarint(f.varint) : void 0;
|
|
48431
|
-
};
|
|
48432
|
-
var bytesOf = (fields, n) => {
|
|
48433
|
-
const f = first(fields, n);
|
|
48434
|
-
return f?.wireType === 2 ? f.bytes : void 0;
|
|
48435
|
-
};
|
|
48436
|
-
var stringOf = (fields, n) => {
|
|
48437
|
-
const b = bytesOf(fields, n);
|
|
48438
|
-
return b ? utf8.decode(b) : void 0;
|
|
48439
|
-
};
|
|
48440
|
-
var sub3 = (fields, n) => {
|
|
48441
|
-
const b = bytesOf(fields, n);
|
|
48442
|
-
return b ? decodeWireFields(b) : void 0;
|
|
48443
|
-
};
|
|
48444
|
-
var hex2 = (bytes) => Buffer.from(bytes).toString("hex");
|
|
48445
|
-
var uuidString = (bytes) => {
|
|
48446
|
-
const h = hex2(bytes).toUpperCase();
|
|
48447
|
-
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
48448
|
-
};
|
|
48449
|
-
var channel = (value) => Math.max(0, Math.min(255, Math.round((Number.isFinite(value) ? value : 0) * 255))).toString(16).toUpperCase().padStart(2, "0");
|
|
48450
|
-
function colorOf(fields) {
|
|
48451
|
-
const [r, g, b, a] = [1, 2, 3, 4].map((n) => fixed32Float(first(fields, n)));
|
|
48452
|
-
if (r === void 0 && g === void 0 && b === void 0) return void 0;
|
|
48453
|
-
const rgb = `#${channel(r)}${channel(g)}${channel(b)}`;
|
|
48454
|
-
return a === void 0 || a >= 0.999 ? rgb : `${rgb}${channel(a)}`;
|
|
48455
|
-
}
|
|
48456
|
-
var DEFAULT_PARAGRAPH = {
|
|
48457
|
-
styleType: null,
|
|
48458
|
-
alignmentValue: 0,
|
|
48459
|
-
indent: 0,
|
|
48460
|
-
blockQuote: false
|
|
48461
|
-
};
|
|
48462
|
-
function decodeParagraph(fields, tally) {
|
|
48463
|
-
if (!fields) return DEFAULT_PARAGRAPH;
|
|
48464
|
-
for (const n of new Set(fields.map((f) => f.fieldNumber)))
|
|
48465
|
-
if (!KNOWN_PARAGRAPH_FIELDS.has(n)) tally.set(n, (tally.get(n) || 0) + 1);
|
|
48466
|
-
const attrs = {
|
|
48467
|
-
styleType: varintOf(fields, 1) ?? null,
|
|
48468
|
-
alignmentValue: varintOf(fields, 2) ?? 0,
|
|
48469
|
-
indent: Math.max(0, varintOf(fields, 4) ?? 0),
|
|
48470
|
-
blockQuote: (varintOf(fields, 8) ?? 0) !== 0
|
|
48471
|
-
};
|
|
48472
|
-
if (attrs.styleType === -1) attrs.styleType = null;
|
|
48473
|
-
const checklist = sub3(fields, 5);
|
|
48474
|
-
const checklistId = checklist && bytesOf(checklist, 1);
|
|
48475
|
-
if (attrs.styleType === 103 && checklistId)
|
|
48476
|
-
attrs.checklist = { id: hex2(checklistId), done: varintOf(checklist, 2) === 1 };
|
|
48477
|
-
const uuid2 = bytesOf(fields, 9);
|
|
48478
|
-
if (uuid2?.length === 16) attrs.paragraphUuid = uuidString(uuid2);
|
|
48479
|
-
return attrs;
|
|
48480
|
-
}
|
|
48481
|
-
function decodeInline(fields) {
|
|
48482
|
-
const inline = {};
|
|
48483
|
-
const font = sub3(fields, 3);
|
|
48484
|
-
const hints = font ? varintOf(font, 3) ?? 0 : 0;
|
|
48485
|
-
const weight = varintOf(fields, 5) ?? 0;
|
|
48486
|
-
if (weight === 1 || weight === 3 || hints & 1) inline.bold = true;
|
|
48487
|
-
if (weight === 2 || weight === 3 || hints & 2) inline.italic = true;
|
|
48488
|
-
if (varintOf(fields, 6)) inline.underline = true;
|
|
48489
|
-
if (varintOf(fields, 7)) inline.strikethrough = true;
|
|
48490
|
-
const baseline = varintOf(fields, 8) ?? 0;
|
|
48491
|
-
if (baseline > 0) inline.superscript = true;
|
|
48492
|
-
if (baseline < 0) inline.subscript = true;
|
|
48493
|
-
const color = sub3(fields, 10);
|
|
48494
|
-
const colorValue = color && colorOf(color);
|
|
48495
|
-
if (colorValue) inline.color = colorValue;
|
|
48496
|
-
const emphasis = varintOf(fields, 14);
|
|
48497
|
-
if (emphasis !== void 0 && emphasis !== 0) {
|
|
48498
|
-
inline.highlight = HIGHLIGHTS[emphasis] ?? "unknown";
|
|
48499
|
-
if (inline.highlight === "unknown") inline.highlightValue = emphasis;
|
|
48500
|
-
}
|
|
48501
|
-
const link = stringOf(fields, 9);
|
|
48502
|
-
if (link) {
|
|
48503
|
-
inline.link = link;
|
|
48504
|
-
inline.linkSafe = isSafeLink(link);
|
|
48505
|
-
}
|
|
48506
|
-
if (font) {
|
|
48507
|
-
const name = stringOf(font, 1);
|
|
48508
|
-
const size = fixed32Float(first(font, 2));
|
|
48509
|
-
if (name || size !== void 0)
|
|
48510
|
-
inline.font = {
|
|
48511
|
-
...name ? { name } : {},
|
|
48512
|
-
...size !== void 0 && Number.isFinite(size) ? { size } : {}
|
|
48513
|
-
};
|
|
48514
|
-
}
|
|
48515
|
-
const attachment = sub3(fields, 12);
|
|
48516
|
-
const attachmentId = attachment && stringOf(attachment, 1);
|
|
48517
|
-
if (attachmentId)
|
|
48518
|
-
inline.attachment = { id: attachmentId, uti: stringOf(attachment, 2) || "unknown" };
|
|
48519
|
-
return inline;
|
|
48520
|
-
}
|
|
48521
|
-
function wrap(error2) {
|
|
48522
|
-
if (error2 instanceof NoteBlocksError) throw error2;
|
|
48523
|
-
if (error2 instanceof ProtobufDecodeError)
|
|
48524
|
-
throw new NoteBlocksError("malformed-protobuf", error2.message);
|
|
48525
|
-
throw error2;
|
|
48526
|
-
}
|
|
48527
|
-
function decodeNoteBlocks(data) {
|
|
48528
|
-
let note;
|
|
48529
|
-
try {
|
|
48530
|
-
const document = sub3(decodeWireFields(data), 2);
|
|
48531
|
-
note = document && sub3(document, 3);
|
|
48532
|
-
} catch (error2) {
|
|
48533
|
-
wrap(error2);
|
|
48534
|
-
}
|
|
48535
|
-
const textBytes = note && bytesOf(note, 2);
|
|
48536
|
-
if (!note || !textBytes)
|
|
48537
|
-
throw new NoteBlocksError("unsupported-structure", "Unsupported Notes document structure");
|
|
48538
|
-
const text2 = utf8.decode(textBytes);
|
|
48539
|
-
const runTally = /* @__PURE__ */ new Map();
|
|
48540
|
-
const paragraphTally = /* @__PURE__ */ new Map();
|
|
48541
|
-
const runs = [];
|
|
48542
|
-
let position = 0;
|
|
48543
|
-
try {
|
|
48544
|
-
for (const field of note.filter((f) => f.fieldNumber === 5)) {
|
|
48545
|
-
if (field.wireType !== 2 || !field.bytes)
|
|
48546
|
-
throw new NoteBlocksError("invalid-runs", "Invalid Notes attribute run");
|
|
48547
|
-
const fields = decodeWireFields(field.bytes);
|
|
48548
|
-
const length = varintOf(fields, 1);
|
|
48549
|
-
if (length === void 0 || length < 0 || position + length > text2.length)
|
|
48550
|
-
throw new NoteBlocksError("invalid-runs", "Invalid Notes run length");
|
|
48551
|
-
for (const n of new Set(fields.map((f) => f.fieldNumber)))
|
|
48552
|
-
if (!KNOWN_RUN_FIELDS.has(n)) runTally.set(n, (runTally.get(n) || 0) + 1);
|
|
48553
|
-
runs.push({
|
|
48554
|
-
start: position,
|
|
48555
|
-
length,
|
|
48556
|
-
paragraph: decodeParagraph(sub3(fields, 2), paragraphTally),
|
|
48557
|
-
inline: decodeInline(fields)
|
|
48558
|
-
});
|
|
48559
|
-
position += length;
|
|
48560
|
-
}
|
|
48561
|
-
} catch (error2) {
|
|
48562
|
-
wrap(error2);
|
|
48563
|
-
}
|
|
48564
|
-
if (position !== text2.length)
|
|
48565
|
-
throw new NoteBlocksError("invalid-runs", "Incomplete Notes attribute runs");
|
|
48566
|
-
const blocks = [];
|
|
48567
|
-
const attachments = [];
|
|
48568
|
-
let runIndex = 0;
|
|
48569
|
-
let paragraphStart = 0;
|
|
48570
|
-
while (paragraphStart < text2.length) {
|
|
48571
|
-
const newline = text2.indexOf("\n", paragraphStart);
|
|
48572
|
-
const end = newline === -1 ? text2.length : newline;
|
|
48573
|
-
while (runIndex < runs.length - 1 && runs[runIndex].start + runs[runIndex].length <= paragraphStart)
|
|
48574
|
-
runIndex++;
|
|
48575
|
-
const attrs = runs[runIndex]?.paragraph ?? DEFAULT_PARAGRAPH;
|
|
48576
|
-
const style = attrs.styleType === null ? "body" : STYLE_NAMES[attrs.styleType] ?? "unknown";
|
|
48577
|
-
const alignment = ALIGNMENTS[attrs.alignmentValue] ?? "unknown";
|
|
48578
|
-
const block = {
|
|
48579
|
-
index: blocks.length,
|
|
48580
|
-
start: paragraphStart,
|
|
48581
|
-
length: end - paragraphStart,
|
|
48582
|
-
text: text2.slice(paragraphStart, end),
|
|
48583
|
-
style,
|
|
48584
|
-
styleType: attrs.styleType,
|
|
48585
|
-
indent: attrs.indent,
|
|
48586
|
-
alignment,
|
|
48587
|
-
...alignment === "unknown" ? { alignmentValue: attrs.alignmentValue } : {},
|
|
48588
|
-
blockQuote: attrs.blockQuote,
|
|
48589
|
-
...attrs.checklist ? { checklist: attrs.checklist } : {},
|
|
48590
|
-
...attrs.paragraphUuid ? { paragraphUuid: attrs.paragraphUuid } : {},
|
|
48591
|
-
runs: [],
|
|
48592
|
-
attachments: []
|
|
48593
|
-
};
|
|
48594
|
-
for (let i = runIndex; i < runs.length && runs[i].start < end; i++) {
|
|
48595
|
-
const run = runs[i];
|
|
48596
|
-
const start = Math.max(run.start, paragraphStart);
|
|
48597
|
-
const stop = Math.min(run.start + run.length, end);
|
|
48598
|
-
if (stop <= start) continue;
|
|
48599
|
-
block.runs.push({
|
|
48600
|
-
start,
|
|
48601
|
-
length: stop - start,
|
|
48602
|
-
text: text2.slice(start, stop),
|
|
48603
|
-
...run.inline
|
|
48604
|
-
});
|
|
48605
|
-
if (run.inline.attachment)
|
|
48606
|
-
for (let at = text2.indexOf("\uFFFC", start); at !== -1 && at < stop; at = text2.indexOf("\uFFFC", at + 1)) {
|
|
48607
|
-
const marker = { ...run.inline.attachment, start: at, blockIndex: block.index };
|
|
48608
|
-
block.attachments.push(marker);
|
|
48609
|
-
attachments.push(marker);
|
|
48610
|
-
}
|
|
48611
|
-
}
|
|
48612
|
-
blocks.push(block);
|
|
48613
|
-
paragraphStart = end + 1;
|
|
48614
|
-
}
|
|
48615
|
-
return {
|
|
48616
|
-
text: text2,
|
|
48617
|
-
textLength: text2.length,
|
|
48618
|
-
blocks,
|
|
48619
|
-
attachments,
|
|
48620
|
-
undecodedFields: {
|
|
48621
|
-
attributeRun: Object.fromEntries([...runTally].sort((a, b) => a[0] - b[0])),
|
|
48622
|
-
paragraphStyle: Object.fromEntries([...paragraphTally].sort((a, b) => a[0] - b[0]))
|
|
48623
|
-
},
|
|
48624
|
-
summary: summarize(blocks, attachments)
|
|
49037
|
+
if (exited) return;
|
|
49038
|
+
exited = true;
|
|
49039
|
+
clearTimeout(timer);
|
|
49040
|
+
exit();
|
|
48625
49041
|
};
|
|
48626
|
-
|
|
48627
|
-
|
|
48628
|
-
|
|
48629
|
-
|
|
48630
|
-
styles: {},
|
|
48631
|
-
alignments: {},
|
|
48632
|
-
blockQuotes: 0,
|
|
48633
|
-
indented: 0,
|
|
48634
|
-
checklist: { total: 0, done: 0 },
|
|
48635
|
-
inline: {
|
|
48636
|
-
bold: 0,
|
|
48637
|
-
italic: 0,
|
|
48638
|
-
underline: 0,
|
|
48639
|
-
strikethrough: 0,
|
|
48640
|
-
superscript: 0,
|
|
48641
|
-
subscript: 0,
|
|
48642
|
-
color: 0,
|
|
48643
|
-
highlight: 0,
|
|
48644
|
-
link: 0,
|
|
48645
|
-
unsafeLink: 0
|
|
48646
|
-
},
|
|
48647
|
-
attachments: attachments.length
|
|
49042
|
+
let timer;
|
|
49043
|
+
const exitWhenDrained = () => {
|
|
49044
|
+
if (stream.writableLength === 0) exitOnce();
|
|
49045
|
+
else stream.once("drain", exitWhenDrained);
|
|
48648
49046
|
};
|
|
48649
|
-
|
|
48650
|
-
|
|
48651
|
-
|
|
48652
|
-
|
|
48653
|
-
|
|
48654
|
-
|
|
48655
|
-
|
|
48656
|
-
|
|
48657
|
-
|
|
49047
|
+
return () => {
|
|
49048
|
+
if (shuttingDown) return;
|
|
49049
|
+
shuttingDown = true;
|
|
49050
|
+
timer = setTimeout(exitOnce, timeoutMs);
|
|
49051
|
+
setImmediate(exitWhenDrained);
|
|
49052
|
+
};
|
|
49053
|
+
}
|
|
49054
|
+
|
|
49055
|
+
// src/utils/noteStructure.ts
|
|
49056
|
+
import { dirname as dirname4 } from "node:path";
|
|
49057
|
+
|
|
49058
|
+
// src/utils/noteLinks.ts
|
|
49059
|
+
var NOTE_LINK_UTI = "com.apple.notes.inlinetextattachment.link";
|
|
49060
|
+
var HASHTAG_UTI2 = "com.apple.notes.inlinetextattachment.hashtag";
|
|
49061
|
+
var UUID = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
|
|
49062
|
+
function parseNotesShowUrl(url) {
|
|
49063
|
+
const match = /^(?:apple)?notes:\/\/showNote\?(.*)$/i.exec(url);
|
|
49064
|
+
if (!match) return void 0;
|
|
49065
|
+
const query2 = new URLSearchParams(match[1]);
|
|
49066
|
+
const note = query2.get("identifier") ?? "";
|
|
49067
|
+
const paragraph = query2.get("paragraphID") ?? "";
|
|
49068
|
+
const result = {};
|
|
49069
|
+
if (UUID.test(note)) result.targetNote = note.toUpperCase();
|
|
49070
|
+
if (UUID.test(paragraph)) result.paragraphId = paragraph.toUpperCase();
|
|
49071
|
+
return result;
|
|
49072
|
+
}
|
|
49073
|
+
function inlineLinks(doc) {
|
|
49074
|
+
const links = [];
|
|
49075
|
+
let last;
|
|
49076
|
+
for (const block of doc.blocks)
|
|
48658
49077
|
for (const run of block.runs) {
|
|
48659
|
-
|
|
48660
|
-
|
|
48661
|
-
|
|
48662
|
-
|
|
48663
|
-
|
|
48664
|
-
|
|
48665
|
-
|
|
48666
|
-
|
|
48667
|
-
|
|
48668
|
-
|
|
48669
|
-
|
|
48670
|
-
|
|
48671
|
-
|
|
49078
|
+
if (!run.link) {
|
|
49079
|
+
last = void 0;
|
|
49080
|
+
continue;
|
|
49081
|
+
}
|
|
49082
|
+
if (last && last.url === run.link && last.start + last.length === run.start) {
|
|
49083
|
+
last.length += run.length;
|
|
49084
|
+
last.text += run.text;
|
|
49085
|
+
continue;
|
|
49086
|
+
}
|
|
49087
|
+
last = {
|
|
49088
|
+
kind: "inline",
|
|
49089
|
+
url: run.link,
|
|
49090
|
+
linkSafe: run.linkSafe === true,
|
|
49091
|
+
text: run.text,
|
|
49092
|
+
start: run.start,
|
|
49093
|
+
length: run.length,
|
|
49094
|
+
blockIndex: block.index,
|
|
49095
|
+
...parseNotesShowUrl(run.link)
|
|
49096
|
+
};
|
|
49097
|
+
links.push(last);
|
|
48672
49098
|
}
|
|
48673
|
-
|
|
48674
|
-
return summary;
|
|
49099
|
+
return links;
|
|
48675
49100
|
}
|
|
48676
|
-
function
|
|
48677
|
-
|
|
48678
|
-
|
|
48679
|
-
|
|
48680
|
-
}
|
|
48681
|
-
throw new NoteBlocksError(
|
|
48682
|
-
"decompress-failed",
|
|
48683
|
-
`Failed to decompress note data: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
48684
|
-
);
|
|
48685
|
-
}
|
|
48686
|
-
return decodeNoteBlocks(data);
|
|
49101
|
+
function markerPosition(doc, identifier) {
|
|
49102
|
+
if (!doc) return {};
|
|
49103
|
+
const wanted = identifier.toUpperCase();
|
|
49104
|
+
const marker = doc.attachments.find((item) => item.id.toUpperCase() === wanted);
|
|
49105
|
+
return marker ? { start: marker.start, blockIndex: marker.blockIndex, inBody: true } : { inBody: false };
|
|
48687
49106
|
}
|
|
48688
|
-
|
|
48689
|
-
|
|
48690
|
-
const
|
|
48691
|
-
|
|
48692
|
-
|
|
48693
|
-
|
|
48694
|
-
|
|
48695
|
-
|
|
49107
|
+
function nativeLink(row, doc) {
|
|
49108
|
+
if (!row.token) return void 0;
|
|
49109
|
+
const target = parseNotesShowUrl(row.token) ?? {};
|
|
49110
|
+
const kind = target.paragraphId ? "section" : "note";
|
|
49111
|
+
return {
|
|
49112
|
+
kind,
|
|
49113
|
+
url: row.token,
|
|
49114
|
+
linkSafe: isSafeLink(row.token),
|
|
49115
|
+
...row.alt ? { text: row.alt } : {},
|
|
49116
|
+
...markerPosition(doc, row.identifier),
|
|
49117
|
+
...target,
|
|
49118
|
+
...kind === "section" && row.alt ? { section: row.alt } : {},
|
|
49119
|
+
attachmentIdentifier: row.identifier
|
|
49120
|
+
};
|
|
48696
49121
|
}
|
|
48697
|
-
function
|
|
48698
|
-
|
|
48699
|
-
|
|
48700
|
-
|
|
48701
|
-
|
|
48702
|
-
|
|
48703
|
-
let block = doc.blocks[i];
|
|
48704
|
-
let size = Buffer.byteLength(JSON.stringify(block));
|
|
48705
|
-
if (bytes + size > maxBytes) {
|
|
48706
|
-
if (blocks.length) break;
|
|
48707
|
-
block = {
|
|
48708
|
-
...block,
|
|
48709
|
-
text: "",
|
|
48710
|
-
textOmitted: true,
|
|
48711
|
-
runs: block.runs.map((run) => ({ ...run, text: "" }))
|
|
48712
|
-
};
|
|
48713
|
-
size = Buffer.byteLength(JSON.stringify(block));
|
|
48714
|
-
}
|
|
48715
|
-
blocks.push(block);
|
|
48716
|
-
bytes += size;
|
|
48717
|
-
}
|
|
48718
|
-
const next = start + blocks.length;
|
|
49122
|
+
function cardLink(row, {
|
|
49123
|
+
doc,
|
|
49124
|
+
noteId: noteId3,
|
|
49125
|
+
previewPath
|
|
49126
|
+
} = {}) {
|
|
49127
|
+
if (!row.url) return void 0;
|
|
48719
49128
|
return {
|
|
48720
|
-
|
|
48721
|
-
|
|
48722
|
-
|
|
48723
|
-
|
|
48724
|
-
|
|
48725
|
-
|
|
48726
|
-
|
|
48727
|
-
|
|
48728
|
-
}
|
|
48729
|
-
summary: doc.summary,
|
|
48730
|
-
undecodedFields: doc.undecodedFields
|
|
49129
|
+
kind: "card",
|
|
49130
|
+
url: row.url,
|
|
49131
|
+
linkSafe: isSafeLink(row.url),
|
|
49132
|
+
...row.title ? { text: row.title } : {},
|
|
49133
|
+
...markerPosition(doc, row.identifier),
|
|
49134
|
+
...parseNotesShowUrl(row.url),
|
|
49135
|
+
attachmentIdentifier: row.identifier,
|
|
49136
|
+
...noteId3 ? { attachmentId: attachmentCoreDataId(noteId3, row.pk) } : {},
|
|
49137
|
+
...previewPath !== void 0 ? { previewPath } : {}
|
|
48731
49138
|
};
|
|
48732
49139
|
}
|
|
48733
|
-
|
|
48734
|
-
|
|
48735
|
-
|
|
48736
|
-
|
|
48737
|
-
function
|
|
48738
|
-
|
|
48739
|
-
if (
|
|
48740
|
-
|
|
48741
|
-
|
|
48742
|
-
|
|
48743
|
-
|
|
48744
|
-
if (!
|
|
48745
|
-
|
|
48746
|
-
|
|
48747
|
-
|
|
48748
|
-
|
|
48749
|
-
|
|
48750
|
-
|
|
48751
|
-
|
|
49140
|
+
|
|
49141
|
+
// src/utils/noteStructure.ts
|
|
49142
|
+
var APPLE_EPOCH_OFFSET = 978307200;
|
|
49143
|
+
var LAST_VIEWED_NEVER = -541228980;
|
|
49144
|
+
function lastViewedOf(raw, columnPresent = true, now = Date.now()) {
|
|
49145
|
+
if (!columnPresent) return { lastViewed: null, lastViewedStatus: "unsupported" };
|
|
49146
|
+
if (raw === null || raw === void 0)
|
|
49147
|
+
return { lastViewed: null, lastViewedStatus: "not-recorded" };
|
|
49148
|
+
const seconds = typeof raw === "number" ? raw : Number.NaN;
|
|
49149
|
+
if (seconds === LAST_VIEWED_NEVER) return { lastViewed: null, lastViewedStatus: "never-viewed" };
|
|
49150
|
+
const ms = (seconds + APPLE_EPOCH_OFFSET) * 1e3;
|
|
49151
|
+
if (!Number.isFinite(ms) || ms < Date.UTC(2007, 0, 1) || ms > now + 864e5)
|
|
49152
|
+
return { lastViewed: null, lastViewedStatus: "malformed" };
|
|
49153
|
+
return { lastViewed: new Date(ms).toISOString(), lastViewedStatus: "viewed" };
|
|
49154
|
+
}
|
|
49155
|
+
var visible = (text2) => text2.replace(/\ufffc/g, "");
|
|
49156
|
+
var wordCount = (text2) => visible(text2).split(/\s+/u).filter((word) => word !== "").length;
|
|
49157
|
+
var charCount = (text2) => Array.from(visible(text2)).length;
|
|
49158
|
+
function noteStructureSql(columns) {
|
|
49159
|
+
const c = (alias, name) => col(columns, alias, name);
|
|
49160
|
+
const sharedExpr = columns.has("ZSERVERSHAREDATA") ? `(n.ZSERVERSHAREDATA IS NOT NULL OR EXISTS (
|
|
49161
|
+
WITH RECURSIVE up(pk, depth) AS (
|
|
49162
|
+
SELECT n.ZFOLDER, 0 UNION ALL
|
|
49163
|
+
SELECT ${c("p", "ZPARENT")}, depth + 1 FROM up JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = up.pk
|
|
49164
|
+
WHERE depth < 64)
|
|
49165
|
+
SELECT 1 FROM up JOIN ZICCLOUDSYNCINGOBJECT s ON s.Z_PK = up.pk WHERE s.ZSERVERSHAREDATA IS NOT NULL))` : "NULL";
|
|
49166
|
+
const children = columns.has("ZPARENTATTACHMENT") ? ` OR att.ZPARENTATTACHMENT IN (SELECT p.Z_PK FROM ZICCLOUDSYNCINGOBJECT p WHERE ${c("p", "ZNOTE")} = @pk)` : "";
|
|
49167
|
+
return [
|
|
49168
|
+
"BEGIN;",
|
|
49169
|
+
`SELECT json_object('k', 'note',
|
|
49170
|
+
'isNote', n.Z_ENT = ${entity("ICNote")},
|
|
49171
|
+
'identifier', ${c("n", "ZIDENTIFIER")},
|
|
49172
|
+
'title', ${c("n", "ZTITLE1")},
|
|
49173
|
+
'folder', ${c("f", "ZTITLE2")},
|
|
49174
|
+
'inTrash', CASE WHEN f.Z_PK IS NULL THEN 0 ELSE ${trashFolderSql(columns, "f")} END,
|
|
49175
|
+
'account', ${c("a", "ZNAME")},
|
|
49176
|
+
'locked', ${c("n", "ZISPASSWORDPROTECTED")},
|
|
49177
|
+
'pinned', ${c("n", "ZISPINNED")},
|
|
49178
|
+
'shared', ${sharedExpr},
|
|
49179
|
+
'lastViewed', ${c("n", "ZLASTVIEWEDMODIFICATIONDATE")},
|
|
49180
|
+
'data', (SELECT hex(d.ZDATA) FROM ZICNOTEDATA d WHERE d.ZNOTE = n.Z_PK),
|
|
49181
|
+
'encrypted', (SELECT d.ZCRYPTOINITIALIZATIONVECTOR IS NOT NULL FROM ZICNOTEDATA d WHERE d.ZNOTE = n.Z_PK))
|
|
49182
|
+
FROM ZICCLOUDSYNCINGOBJECT n
|
|
49183
|
+
LEFT JOIN ZICCLOUDSYNCINGOBJECT f ON f.Z_PK = ${c("n", "ZFOLDER")}
|
|
49184
|
+
LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = ${accountRef(columns, "n")} AND a.Z_ENT = ${entity("ICAccount")}
|
|
49185
|
+
WHERE n.Z_PK = @pk;`,
|
|
49186
|
+
`SELECT json_object('k', 'attachment',
|
|
49187
|
+
'pk', att.Z_PK,
|
|
49188
|
+
'title', ${c("att", "ZTITLE")},
|
|
49189
|
+
'url', ${c("att", "ZURLSTRING")},
|
|
49190
|
+
'fileSize', ${c("att", "ZFILESIZE")})
|
|
49191
|
+
FROM ZICCLOUDSYNCINGOBJECT att
|
|
49192
|
+
WHERE att.Z_ENT = ${entity("ICAttachment")} AND ${notTombstonedSql(columns, "att")}
|
|
49193
|
+
AND (${c("att", "ZNOTE")} = @pk${children});`,
|
|
49194
|
+
`SELECT json_object('k', 'inline',
|
|
49195
|
+
'identifier', ${c("i", "ZIDENTIFIER")},
|
|
49196
|
+
'uti', ${c("i", "ZTYPEUTI1")},
|
|
49197
|
+
'alt', ${c("i", "ZALTTEXT")},
|
|
49198
|
+
'token', ${c("i", "ZTOKENCONTENTIDENTIFIER")})
|
|
49199
|
+
FROM ZICCLOUDSYNCINGOBJECT i
|
|
49200
|
+
WHERE i.Z_ENT = ${entity("ICInlineAttachment")} AND ${c("i", "ZNOTE1")} = @pk
|
|
49201
|
+
AND ${notTombstonedSql(columns, "i")} AND ${c("i", "ZTYPEUTI1")} IN ('${HASHTAG_UTI2}', '${NOTE_LINK_UTI}');`,
|
|
49202
|
+
"COMMIT;"
|
|
49203
|
+
].join("\n");
|
|
49204
|
+
}
|
|
49205
|
+
function describeNoteStructure(s) {
|
|
49206
|
+
const parts = [
|
|
49207
|
+
s.bodyDecoded ? `${s.blockSummary.blocks} blocks, ${s.wordCount} words` : `body not decoded (${s.bodyError})`,
|
|
49208
|
+
`${s.links.length} links (inline ${s.linkCounts.inline}, card ${s.linkCounts.card}, note ${s.linkCounts.note}, section ${s.linkCounts.section})`,
|
|
49209
|
+
`${s.attachmentCount} attachments`,
|
|
49210
|
+
`${s.tags.length} tags`
|
|
49211
|
+
];
|
|
49212
|
+
if (s.checklistTotal) parts.push(`checklist ${s.checklistDone}/${s.checklistTotal} done`);
|
|
49213
|
+
if (s.hasDrawing) parts.push("has a drawing");
|
|
49214
|
+
if (s.isLocked) parts.push("locked");
|
|
49215
|
+
if (s.isShared) parts.push("shared");
|
|
49216
|
+
return `Note structure: ${parts.join("; ")}.`;
|
|
49217
|
+
}
|
|
49218
|
+
function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH7, includeText = true, maxTextBytes = 4 * 1024 * 1024 } = {}) {
|
|
49219
|
+
const { pk } = parseNoteId2(id2);
|
|
49220
|
+
const columns = readColumns(dbPath2);
|
|
49221
|
+
const rows = parseJsonLines(
|
|
49222
|
+
runReadOnlySql(dbPath2, noteStructureSql(columns), { pk: { int: pk } })
|
|
49223
|
+
);
|
|
49224
|
+
const note = rows.find((row) => row.k === "note");
|
|
49225
|
+
if (!note?.isNote) throw new NoteStoreError(`No note found for ID "${id2}".`, "invalid_input");
|
|
49226
|
+
const details = new Map(
|
|
49227
|
+
rows.filter((row) => row.k === "attachment").map((row) => [row.pk, row])
|
|
49228
|
+
);
|
|
49229
|
+
const inlineRows = rows.filter((row) => row.k === "inline");
|
|
49230
|
+
let doc;
|
|
49231
|
+
let bodyError;
|
|
49232
|
+
if (note.encrypted || note.locked) bodyError = "encrypted";
|
|
49233
|
+
else if (!note.data || !/^[0-9a-f]+$/i.test(note.data)) bodyError = "no-body";
|
|
49234
|
+
else
|
|
49235
|
+
try {
|
|
49236
|
+
doc = decodeCompressedNoteBlocks(Buffer.from(note.data, "hex"));
|
|
49237
|
+
} catch (error2) {
|
|
49238
|
+
if (!(error2 instanceof NoteBlocksError)) throw error2;
|
|
49239
|
+
bodyError = error2.code;
|
|
49240
|
+
}
|
|
49241
|
+
const { rows: attachmentRows, bodyOrder } = readNoteAttachmentRows(id2, dbPath2);
|
|
49242
|
+
const assets = assembleAttachmentAssets(attachmentRows, bodyOrder, dirname4(dbPath2));
|
|
49243
|
+
const roots = /* @__PURE__ */ new Map();
|
|
49244
|
+
const attachments = [];
|
|
49245
|
+
const pks = /* @__PURE__ */ new Map();
|
|
49246
|
+
for (const record2 of assets.attachments) {
|
|
49247
|
+
const detail = details.get(record2.pk);
|
|
49248
|
+
const parent = record2.parentIdentifier !== null ? roots.get(record2.parentIdentifier) : void 0;
|
|
49249
|
+
const item = {
|
|
49250
|
+
id: attachmentCoreDataId(id2, record2.pk),
|
|
49251
|
+
identifier: record2.identifier,
|
|
49252
|
+
uti: record2.uti,
|
|
49253
|
+
kind: record2.kind,
|
|
49254
|
+
...detail?.title ? { title: detail.title } : {},
|
|
49255
|
+
...record2.filename ? { filename: record2.filename } : {},
|
|
49256
|
+
...detail?.url ? { url: detail.url } : {},
|
|
49257
|
+
...typeof detail?.fileSize === "number" ? { fileSize: detail.fileSize } : {},
|
|
49258
|
+
...markerPosition(doc, record2.identifier),
|
|
49259
|
+
previewPath: record2.previewPath,
|
|
49260
|
+
...parent ? { parentId: parent.id } : {}
|
|
49261
|
+
};
|
|
49262
|
+
pks.set(item, record2.pk);
|
|
49263
|
+
if (parent) (parent.children ||= []).push(item);
|
|
49264
|
+
else {
|
|
49265
|
+
roots.set(record2.identifier, item);
|
|
49266
|
+
attachments.push(item);
|
|
49267
|
+
}
|
|
49268
|
+
}
|
|
49269
|
+
const flat = attachments.flatMap((item) => [item, ...item.children ?? []]);
|
|
49270
|
+
const links = doc ? inlineLinks(doc) : [];
|
|
49271
|
+
for (const item of flat) {
|
|
49272
|
+
if (item.kind !== "url") continue;
|
|
49273
|
+
const link = cardLink(
|
|
48752
49274
|
{
|
|
48753
|
-
|
|
48754
|
-
|
|
48755
|
-
|
|
48756
|
-
|
|
48757
|
-
}
|
|
48758
|
-
|
|
48759
|
-
} catch (error2) {
|
|
48760
|
-
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
48761
|
-
if (/authorization denied|unable to open database/i.test(message))
|
|
48762
|
-
throw new NoteBlocksError("no-full-disk-access", "The Notes database is not readable");
|
|
48763
|
-
throw new NoteBlocksError("query-failed", "Failed to query the Notes database");
|
|
48764
|
-
}
|
|
48765
|
-
let row;
|
|
48766
|
-
try {
|
|
48767
|
-
row = JSON.parse(output);
|
|
48768
|
-
} catch {
|
|
48769
|
-
throw new NoteBlocksError("query-failed", "Unexpected Notes database response");
|
|
48770
|
-
}
|
|
48771
|
-
if (!row.exists) throw new NoteBlocksError("not-found", `No note found for ID "${id2}"`);
|
|
48772
|
-
if (row.encrypted)
|
|
48773
|
-
throw new NoteBlocksError(
|
|
48774
|
-
"encrypted",
|
|
48775
|
-
"This note is password-protected; its body is encrypted and cannot be decoded"
|
|
49275
|
+
pk: pks.get(item),
|
|
49276
|
+
identifier: item.identifier,
|
|
49277
|
+
url: item.url ?? null,
|
|
49278
|
+
title: item.title ?? null
|
|
49279
|
+
},
|
|
49280
|
+
{ doc, noteId: id2, previewPath: item.previewPath }
|
|
48776
49281
|
);
|
|
48777
|
-
|
|
48778
|
-
|
|
48779
|
-
|
|
49282
|
+
if (link) links.push(link);
|
|
49283
|
+
}
|
|
49284
|
+
const tagOrder = [];
|
|
49285
|
+
for (const row of inlineRows) {
|
|
49286
|
+
if (!row.identifier) continue;
|
|
49287
|
+
if (row.uti === NOTE_LINK_UTI) {
|
|
49288
|
+
const link = nativeLink({ identifier: row.identifier, token: row.token, alt: row.alt }, doc);
|
|
49289
|
+
if (link) links.push(link);
|
|
49290
|
+
} else if (row.alt) {
|
|
49291
|
+
const position = markerPosition(doc, row.identifier);
|
|
49292
|
+
if (position.inBody !== false)
|
|
49293
|
+
tagOrder.push({ tag: row.alt.replace(/^#/, ""), start: position.start ?? 0 });
|
|
49294
|
+
}
|
|
49295
|
+
}
|
|
49296
|
+
links.sort((a, b) => (a.start ?? Number.MAX_SAFE_INTEGER) - (b.start ?? Number.MAX_SAFE_INTEGER));
|
|
49297
|
+
const tags = [...new Set(tagOrder.sort((a, b) => a.start - b.start).map((entry) => entry.tag))];
|
|
49298
|
+
let text2;
|
|
49299
|
+
let textOmitted;
|
|
49300
|
+
if (doc && includeText) {
|
|
49301
|
+
if (Buffer.byteLength(doc.text) <= maxTextBytes) text2 = doc.text;
|
|
49302
|
+
else textOmitted = true;
|
|
49303
|
+
}
|
|
49304
|
+
const linkCounts = { inline: 0, card: 0, note: 0, section: 0 };
|
|
49305
|
+
for (const link of links) linkCounts[link.kind]++;
|
|
49306
|
+
const lastViewed = lastViewedOf(note.lastViewed, columns.has("ZLASTVIEWEDMODIFICATIONDATE"));
|
|
49307
|
+
const first2 = selectFirstImage(assets);
|
|
49308
|
+
return {
|
|
49309
|
+
id: id2,
|
|
49310
|
+
identifier: note.identifier,
|
|
49311
|
+
deepLink: note.identifier ? `notes://showNote?identifier=${note.identifier}` : null,
|
|
49312
|
+
title: note.title,
|
|
49313
|
+
folder: note.folder,
|
|
49314
|
+
account: note.account,
|
|
49315
|
+
inRecentlyDeleted: note.inTrash === 1,
|
|
49316
|
+
isShared: note.shared === null ? null : note.shared === 1,
|
|
49317
|
+
isLocked: note.locked === 1 || note.encrypted === 1,
|
|
49318
|
+
isPinned: note.pinned === null ? null : note.pinned === 1,
|
|
49319
|
+
...lastViewed,
|
|
49320
|
+
bodyDecoded: doc !== void 0,
|
|
49321
|
+
...bodyError ? { bodyError } : {},
|
|
49322
|
+
...text2 !== void 0 ? { text: text2 } : {},
|
|
49323
|
+
...textOmitted ? { textOmitted } : {},
|
|
49324
|
+
textLength: doc ? doc.textLength : null,
|
|
49325
|
+
wordCount: doc ? wordCount(doc.text) : null,
|
|
49326
|
+
charCount: doc ? charCount(doc.text) : null,
|
|
49327
|
+
blockSummary: doc ? doc.summary : null,
|
|
49328
|
+
links,
|
|
49329
|
+
linkCounts,
|
|
49330
|
+
linksComplete: doc !== void 0,
|
|
49331
|
+
tags,
|
|
49332
|
+
attachments,
|
|
49333
|
+
attachmentCount: attachments.length,
|
|
49334
|
+
checklistTotal: doc ? doc.summary.checklist.total : null,
|
|
49335
|
+
checklistDone: doc ? doc.summary.checklist.done : null,
|
|
49336
|
+
hasDrawing: flat.some((item) => item.kind === "drawing"),
|
|
49337
|
+
firstImage: first2 ? { ...first2, id: attachmentCoreDataId(id2, first2.pk) } : null,
|
|
49338
|
+
...doc ? { undecodedFields: doc.undecodedFields } : {}
|
|
49339
|
+
};
|
|
48780
49340
|
}
|
|
48781
49341
|
|
|
48782
49342
|
// src/utils/linkInsert.ts
|
|
@@ -48896,7 +49456,7 @@ function insertLink(request, deps) {
|
|
|
48896
49456
|
|
|
48897
49457
|
// src/services/notesExport.ts
|
|
48898
49458
|
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
48899
|
-
import { basename as basename4, dirname as
|
|
49459
|
+
import { basename as basename4, dirname as dirname5, extname as extname4, join as join21 } from "node:path";
|
|
48900
49460
|
|
|
48901
49461
|
// src/utils/exportAssets.ts
|
|
48902
49462
|
import {
|
|
@@ -50219,7 +50779,7 @@ function loadNotes(ids, single, read) {
|
|
|
50219
50779
|
return { notes, skipped };
|
|
50220
50780
|
}
|
|
50221
50781
|
function openOutput(output) {
|
|
50222
|
-
mkdirSync5(
|
|
50782
|
+
mkdirSync5(dirname5(output), { recursive: true });
|
|
50223
50783
|
try {
|
|
50224
50784
|
return openCreateOnly(output);
|
|
50225
50785
|
} catch (error2) {
|
|
@@ -50244,7 +50804,7 @@ function exportNotesMarkdown(request, deps) {
|
|
|
50244
50804
|
const ids = selectNotes(request, deps);
|
|
50245
50805
|
const { notes, skipped } = loadNotes(ids, !!request.id, deps.readNote);
|
|
50246
50806
|
const fd = output ? openOutput(output) : void 0;
|
|
50247
|
-
const writer = assetsDir ? new SidecarWriter(assetsDir, output ?
|
|
50807
|
+
const writer = assetsDir ? new SidecarWriter(assetsDir, output ? dirname5(output) : void 0) : void 0;
|
|
50248
50808
|
const ctx = {
|
|
50249
50809
|
stats: emptyStats(),
|
|
50250
50810
|
...writer ? { writer, locator: deps.locator ?? new AssetLocator() } : {}
|
|
@@ -50274,7 +50834,7 @@ function exportNotesMarkdown(request, deps) {
|
|
|
50274
50834
|
return { ...receipt, markdown };
|
|
50275
50835
|
}
|
|
50276
50836
|
function defaultSidecarDir(output) {
|
|
50277
|
-
return join21(
|
|
50837
|
+
return join21(dirname5(output), `${basename4(output, extname4(output))}.assets`);
|
|
50278
50838
|
}
|
|
50279
50839
|
function exportNotesHtml(request, deps) {
|
|
50280
50840
|
if (!request.outputPath)
|
|
@@ -50295,7 +50855,7 @@ function exportNotesHtml(request, deps) {
|
|
|
50295
50855
|
const ids = selectNotes(request, deps);
|
|
50296
50856
|
const { notes, skipped } = loadNotes(ids, !!request.id, deps.readNote);
|
|
50297
50857
|
const fd = openOutput(output);
|
|
50298
|
-
const writer = assetsDir ? new SidecarWriter(assetsDir,
|
|
50858
|
+
const writer = assetsDir ? new SidecarWriter(assetsDir, dirname5(output)) : new DataUrlWriter();
|
|
50299
50859
|
const ctx = {
|
|
50300
50860
|
stats: emptyStats(),
|
|
50301
50861
|
writer,
|
|
@@ -51401,7 +51961,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
51401
51961
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
51402
51962
|
import { existsSync as existsSync13 } from "node:fs";
|
|
51403
51963
|
import { release as release2 } from "node:os";
|
|
51404
|
-
import { dirname as
|
|
51964
|
+
import { dirname as dirname6, resolve as resolve4 } from "node:path";
|
|
51405
51965
|
import { fileURLToPath } from "node:url";
|
|
51406
51966
|
var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
|
|
51407
51967
|
var MARKDOWN_MIN_DARWIN_MAJOR = 25;
|
|
@@ -51424,7 +51984,7 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
51424
51984
|
const result = spawnSync2("/usr/bin/open", [path10], { encoding: "utf8" });
|
|
51425
51985
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
51426
51986
|
});
|
|
51427
|
-
const baseDirectory = dependencies.baseDirectory || resolve4(
|
|
51987
|
+
const baseDirectory = dependencies.baseDirectory || resolve4(dirname6(fileURLToPath(import.meta.url)), "../shortcuts");
|
|
51428
51988
|
const osRelease = (dependencies.osRelease || release2)();
|
|
51429
51989
|
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
51430
51990
|
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
@@ -51505,7 +52065,7 @@ import { spawnSync as spawnSync3 } from "node:child_process";
|
|
|
51505
52065
|
import { createHash as createHash4 } from "node:crypto";
|
|
51506
52066
|
import { existsSync as existsSync14, readFileSync as readFileSync4 } from "node:fs";
|
|
51507
52067
|
import { homedir as homedir19 } from "node:os";
|
|
51508
|
-
import { dirname as
|
|
52068
|
+
import { dirname as dirname7, join as join24, resolve as resolve5 } from "node:path";
|
|
51509
52069
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
51510
52070
|
var PRIVATE_HELPER_PROTOCOL = 1;
|
|
51511
52071
|
var ENABLE_ENV = "APPLE_NOTES_MCP_ENABLE_PRIVATE";
|
|
@@ -51530,7 +52090,7 @@ var manifestSchema = external_exports.object({
|
|
|
51530
52090
|
osVersion: external_exports.string(),
|
|
51531
52091
|
compiler: external_exports.string()
|
|
51532
52092
|
});
|
|
51533
|
-
function packageRoot(fromDir =
|
|
52093
|
+
function packageRoot(fromDir = dirname7(fileURLToPath2(import.meta.url))) {
|
|
51534
52094
|
let dir = fromDir;
|
|
51535
52095
|
for (; ; ) {
|
|
51536
52096
|
const candidate = join24(dir, "package.json");
|
|
@@ -51541,7 +52101,7 @@ function packageRoot(fromDir = dirname6(fileURLToPath2(import.meta.url))) {
|
|
|
51541
52101
|
} catch {
|
|
51542
52102
|
}
|
|
51543
52103
|
}
|
|
51544
|
-
const parent =
|
|
52104
|
+
const parent = dirname7(dir);
|
|
51545
52105
|
if (parent === dir) return resolve5(fromDir, "..");
|
|
51546
52106
|
dir = parent;
|
|
51547
52107
|
}
|
|
@@ -51750,9 +52310,9 @@ function parseOrThrow(schema, value) {
|
|
|
51750
52310
|
);
|
|
51751
52311
|
return parsed.data;
|
|
51752
52312
|
}
|
|
51753
|
-
var
|
|
52313
|
+
var UUID2 = /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i;
|
|
51754
52314
|
function assertNoteIdentifier(identifier) {
|
|
51755
|
-
if (!
|
|
52315
|
+
if (!UUID2.test(identifier))
|
|
51756
52316
|
throw new PrivateHelperError("invalid_request", "identifier must be a Notes UUID");
|
|
51757
52317
|
}
|
|
51758
52318
|
function probePrivateHelper(deps = defaultDeps2()) {
|
|
@@ -53162,6 +53722,59 @@ registerTool(
|
|
|
53162
53722
|
);
|
|
53163
53723
|
}, "Error reading note blocks")
|
|
53164
53724
|
);
|
|
53725
|
+
registerTool(
|
|
53726
|
+
"get-note-structure",
|
|
53727
|
+
{
|
|
53728
|
+
description: "Use when: you want one read-only overview of a note by exact id: decoded text, a block summary, every link with its kind (inline hyperlink, rich link card, native note link, native section link, with target note and paragraph UUIDs when the URL carries them), native tags, attachments with the same kind, body order and preview list-attachments reports (gallery and recording children nested), and metadata: deepLink, isShared, isLocked, isPinned, inRecentlyDeleted, lastViewed, wordCount, charCount, attachmentCount, checklistTotal/checklistDone, hasDrawing, firstImage.\nReturns: the structure object. For a password-protected note, metadata and attachment rows only (bodyDecoded false, body-derived fields null). lastViewed is null with lastViewedStatus never-viewed, not-recorded, malformed or unsupported when Notes holds no real view date.\nDo not use when: you need per-paragraph formatting (get-note-blocks) or the editable HTML body (get-note-content).\nSafety: read-only; reads the NoteStore database and the Notes data folder directly and requires Full Disk Access. Link URLs are returned as stored; check linkSafe before emitting them into HTML.",
|
|
53729
|
+
inputSchema: {
|
|
53730
|
+
id: noteIdInput,
|
|
53731
|
+
includeText: external_exports.boolean().optional().describe(
|
|
53732
|
+
"Include the decoded note text (default true). Text over APPLE_NOTES_MCP_BLOCKS_MAX_BYTES is omitted with textOmitted: true"
|
|
53733
|
+
)
|
|
53734
|
+
},
|
|
53735
|
+
outputSchema: {
|
|
53736
|
+
id: external_exports.string().optional(),
|
|
53737
|
+
identifier: external_exports.string().nullable().optional(),
|
|
53738
|
+
deepLink: external_exports.string().nullable().optional(),
|
|
53739
|
+
title: external_exports.string().nullable().optional(),
|
|
53740
|
+
folder: external_exports.string().nullable().optional(),
|
|
53741
|
+
account: external_exports.string().nullable().optional(),
|
|
53742
|
+
inRecentlyDeleted: external_exports.boolean().optional(),
|
|
53743
|
+
isShared: external_exports.boolean().nullable().optional(),
|
|
53744
|
+
isLocked: external_exports.boolean().optional(),
|
|
53745
|
+
isPinned: external_exports.boolean().nullable().optional(),
|
|
53746
|
+
lastViewed: external_exports.string().nullable().optional(),
|
|
53747
|
+
lastViewedStatus: external_exports.string().optional(),
|
|
53748
|
+
bodyDecoded: external_exports.boolean().optional(),
|
|
53749
|
+
bodyError: external_exports.string().optional(),
|
|
53750
|
+
text: external_exports.string().optional(),
|
|
53751
|
+
textOmitted: external_exports.boolean().optional(),
|
|
53752
|
+
textLength: external_exports.number().nullable().optional(),
|
|
53753
|
+
wordCount: external_exports.number().nullable().optional(),
|
|
53754
|
+
charCount: external_exports.number().nullable().optional(),
|
|
53755
|
+
blockSummary: external_exports.record(external_exports.unknown()).nullable().optional(),
|
|
53756
|
+
links: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
53757
|
+
linkCounts: external_exports.record(external_exports.unknown()).optional(),
|
|
53758
|
+
linksComplete: external_exports.boolean().optional(),
|
|
53759
|
+
tags: external_exports.array(external_exports.string()).optional(),
|
|
53760
|
+
attachments: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
|
|
53761
|
+
attachmentCount: external_exports.number().optional(),
|
|
53762
|
+
checklistTotal: external_exports.number().nullable().optional(),
|
|
53763
|
+
checklistDone: external_exports.number().nullable().optional(),
|
|
53764
|
+
hasDrawing: external_exports.boolean().optional(),
|
|
53765
|
+
firstImage: external_exports.record(external_exports.unknown()).nullable().optional(),
|
|
53766
|
+
undecodedFields: external_exports.record(external_exports.unknown()).optional()
|
|
53767
|
+
},
|
|
53768
|
+
annotations: { readOnlyHint: true }
|
|
53769
|
+
},
|
|
53770
|
+
withErrorHandling(({ id: id2, includeText }) => {
|
|
53771
|
+
const structure = readNoteStructure(id2, {
|
|
53772
|
+
includeText: includeText ?? true,
|
|
53773
|
+
maxTextBytes: blocksMaxResponseBytes()
|
|
53774
|
+
});
|
|
53775
|
+
return successResponse(describeNoteStructure(structure), { ...structure });
|
|
53776
|
+
}, "Error reading note structure")
|
|
53777
|
+
);
|
|
53165
53778
|
registerTool(
|
|
53166
53779
|
"list-native-tags",
|
|
53167
53780
|
{
|
|
@@ -53704,7 +54317,7 @@ registerTool(
|
|
|
53704
54317
|
registerTool(
|
|
53705
54318
|
"list-notes",
|
|
53706
54319
|
{
|
|
53707
|
-
description: "Use when: enumerating notes in an account or folder; supports modifiedSince and limit for large collections.\nReturns: each note's title and id (ids are safe to use for follow-up reads/edits even when titles are duplicated \u2014 see search-notes for keyword-based lookup instead).\nDo not use when: you need content (get-note-content).\nNote: excludes notes in Recently Deleted unless includeRecentlyDeleted is true, which flags them inRecentlyDeleted. Warns if iCloud sync is active and results may be partial.",
|
|
54320
|
+
description: "Use when: enumerating notes in an account or folder; supports modifiedSince and limit for large collections.\nReturns: each note's title and id (ids are safe to use for follow-up reads/edits even when titles are duplicated \u2014 see search-notes for keyword-based lookup instead).\nDo not use when: you need content (get-note-content), or date order, incremental sync cursors, or word counts (list-recent-notes).\nNote: excludes notes in Recently Deleted unless includeRecentlyDeleted is true, which flags them inRecentlyDeleted. Warns if iCloud sync is active and results may be partial.",
|
|
53708
54321
|
inputSchema: {
|
|
53709
54322
|
account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account to list notes from"),
|
|
53710
54323
|
folder: external_exports.string().max(MAX.FOLDER).optional().describe("Filter to specific folder"),
|
|
@@ -54986,6 +55599,110 @@ ${lines.join("\n")}${more}`,
|
|
|
54986
55599
|
);
|
|
54987
55600
|
}, "Error listing notes")
|
|
54988
55601
|
);
|
|
55602
|
+
registerTool(
|
|
55603
|
+
"list-recent-notes",
|
|
55604
|
+
{
|
|
55605
|
+
description: "Use when: syncing notes incrementally from the Notes database, or listing notes by modification date. Pass since (a modifiedCheckpoint cursor or ISO 8601) to page through changes oldest first; omit it for the newest notes first.\nReturns: metadata rows (id, identifier, title, folder path, account, created, modified, modifiedCheckpoint, pinned, locked, inRecentlyDeleted, markedForDeletion), plus optional wordCount/charCount (wordCounts) and bodyPreview/textDecoded (bodyPreview); order, saturated, and nextSince drive sync.\nDo not use when: you need full content (get-note-content) or keyword search (search-notes).\nNote: read-only NoteStore access; requires Full Disk Access. Sync rule: store nextSince and pass it as the next since; every since call advances, and saturated true means more changes may follow, so call again. For a first full sync start from since 1970-01-01. The cursor can miss edits iCloud delivers later with an older timestamp from another device, and deletions are invisible unless includeDeleted is true.",
|
|
55606
|
+
inputSchema: {
|
|
55607
|
+
account: external_exports.string().min(1).max(MAX.ACCOUNT).optional().describe("Only this account (exact or unique-prefix name). Omit for every account."),
|
|
55608
|
+
folder: external_exports.string().min(1).max(MAX.FOLDER).optional().describe(
|
|
55609
|
+
"Only notes directly in this folder: full path (list-folders syntax) or a unique name"
|
|
55610
|
+
),
|
|
55611
|
+
since: external_exports.string().min(1).max(64).optional().describe(
|
|
55612
|
+
"Return notes after this point, oldest first: a modifiedCheckpoint cursor from a row or nextSince, or an ISO 8601 date (local midnight) or date-time (local unless it has an offset), meaning modified strictly after it"
|
|
55613
|
+
),
|
|
55614
|
+
limit: external_exports.number().int().min(1).max(RECENT_LIMIT.MAX).optional().describe(`Maximum notes to return (default ${RECENT_LIMIT.DEFAULT})`),
|
|
55615
|
+
includeDeleted: external_exports.boolean().optional().describe(
|
|
55616
|
+
"Also return notes in Recently Deleted, notes awaiting deletion, and folderless notes (default false)"
|
|
55617
|
+
),
|
|
55618
|
+
wordCounts: external_exports.boolean().optional().describe(
|
|
55619
|
+
"Decode each body for wordCount and charCount; null when the body is locked or not available (default false)"
|
|
55620
|
+
),
|
|
55621
|
+
bodyPreview: external_exports.boolean().optional().describe(
|
|
55622
|
+
"Add bodyPreview (180 characters: decoded body with wordCounts, else the stored snippet) and textDecoded"
|
|
55623
|
+
)
|
|
55624
|
+
},
|
|
55625
|
+
outputSchema: {
|
|
55626
|
+
notes: external_exports.array(external_exports.object({}).passthrough()).optional(),
|
|
55627
|
+
count: external_exports.number().optional(),
|
|
55628
|
+
limit: external_exports.number().optional(),
|
|
55629
|
+
order: external_exports.string().optional(),
|
|
55630
|
+
saturated: external_exports.boolean().optional(),
|
|
55631
|
+
nextSince: external_exports.string().nullable().optional(),
|
|
55632
|
+
account: external_exports.string().optional(),
|
|
55633
|
+
folder: external_exports.string().optional()
|
|
55634
|
+
},
|
|
55635
|
+
annotations: { readOnlyHint: true }
|
|
55636
|
+
},
|
|
55637
|
+
withErrorHandling((params) => {
|
|
55638
|
+
const result = listRecentNotes(params);
|
|
55639
|
+
const syncing = result.order === "oldest-first";
|
|
55640
|
+
const scope2 = [
|
|
55641
|
+
result.folder ? ` in folder "${result.folder}"` : "",
|
|
55642
|
+
result.account ? ` (${result.account})` : "",
|
|
55643
|
+
params.since ? ` modified after ${params.since}` : ""
|
|
55644
|
+
].join("");
|
|
55645
|
+
const lines = result.notes.map(
|
|
55646
|
+
(row) => ` - ${row.title ?? "(untitled)"} \u2014 ${row.modified ?? "no date"} [id: ${row.id}]`
|
|
55647
|
+
);
|
|
55648
|
+
let tail = "";
|
|
55649
|
+
if (syncing && result.nextSince) {
|
|
55650
|
+
tail = result.saturated ? `
|
|
55651
|
+
|
|
55652
|
+
More changes may follow: call again with since ${result.nextSince}.` : `
|
|
55653
|
+
|
|
55654
|
+
Caught up. Next since: ${result.nextSince}`;
|
|
55655
|
+
} else if (result.saturated) {
|
|
55656
|
+
tail = `
|
|
55657
|
+
|
|
55658
|
+
Limit reached: older notes were not listed. Page with since to reach them.`;
|
|
55659
|
+
} else if (result.nextSince) {
|
|
55660
|
+
tail = `
|
|
55661
|
+
|
|
55662
|
+
Next since: ${result.nextSince}`;
|
|
55663
|
+
}
|
|
55664
|
+
return successResponse(
|
|
55665
|
+
(result.count ? `${result.count} notes${scope2}, ${syncing ? "oldest" : "newest"} first:
|
|
55666
|
+
${lines.join("\n")}` : `No notes${scope2}.`) + tail,
|
|
55667
|
+
result
|
|
55668
|
+
);
|
|
55669
|
+
}, "Error listing recent notes")
|
|
55670
|
+
);
|
|
55671
|
+
registerTool(
|
|
55672
|
+
"list-folder-tree",
|
|
55673
|
+
{
|
|
55674
|
+
description: "Use when: you need the folder hierarchy with note counts, per account, in one read.\nReturns: accounts, each with nested folders (id, identifier, name, path, kind folder/smart/trash, noteCount direct, totalNoteCount including subfolders, children) and the account's noteCount.\nDo not use when: you only need folder paths (list-folders) or notes (list-notes, list-recent-notes).\nNote: read-only NoteStore access; requires Full Disk Access. includeDeleted adds folders awaiting deletion (markedForDeletion) and folders whose account is gone.",
|
|
55675
|
+
inputSchema: {
|
|
55676
|
+
account: external_exports.string().min(1).max(MAX.ACCOUNT).optional().describe("Only this account (exact or unique-prefix name). Omit for every account."),
|
|
55677
|
+
includeDeleted: external_exports.boolean().optional().describe("Include folders marked for deletion (default false)")
|
|
55678
|
+
},
|
|
55679
|
+
outputSchema: {
|
|
55680
|
+
accounts: external_exports.array(external_exports.object({}).passthrough()).optional(),
|
|
55681
|
+
folderCount: external_exports.number().optional()
|
|
55682
|
+
},
|
|
55683
|
+
annotations: { readOnlyHint: true }
|
|
55684
|
+
},
|
|
55685
|
+
withErrorHandling(({ account, includeDeleted }) => {
|
|
55686
|
+
const result = folderTree({ account, includeDeleted });
|
|
55687
|
+
const lines = [];
|
|
55688
|
+
const walk = (nodes, depth) => {
|
|
55689
|
+
for (const node of nodes) {
|
|
55690
|
+
const counts = node.totalNoteCount === node.noteCount ? `${node.noteCount}` : `${node.noteCount}, ${node.totalNoteCount} with subfolders`;
|
|
55691
|
+
lines.push(`${" ".repeat(depth + 1)}- ${node.name} (${counts})`);
|
|
55692
|
+
walk(node.children, depth + 1);
|
|
55693
|
+
}
|
|
55694
|
+
};
|
|
55695
|
+
for (const entry of result.accounts) {
|
|
55696
|
+
lines.push(`${entry.account}: ${entry.noteCount} notes`);
|
|
55697
|
+
walk(entry.folders, 0);
|
|
55698
|
+
}
|
|
55699
|
+
return successResponse(
|
|
55700
|
+
`${result.folderCount} folders:
|
|
55701
|
+
${lines.join("\n")}`,
|
|
55702
|
+
result
|
|
55703
|
+
);
|
|
55704
|
+
}, "Error listing folder tree")
|
|
55705
|
+
);
|
|
54989
55706
|
registerResourcesAndPrompts(server, notesManager);
|
|
54990
55707
|
process.on("uncaughtException", (err) => {
|
|
54991
55708
|
if (err?.code === "EPIPE") process.exit(0);
|