apple-notes-mcp 2.8.29 → 2.8.30
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 +35 -2
- package/build/index.js +547 -124
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ Install as a Claude Code plugin for automatic configuration and enhanced AI beha
|
|
|
56
56
|
|
|
57
57
|
This method also installs a **skill** that teaches Claude when and how to use Apple Notes effectively.
|
|
58
58
|
|
|
59
|
-
On the first tool call, macOS shows an Automation permission prompt ("Claude" wants access to control "Notes") — click **OK**. Optionally, grant **Full Disk Access** to the app that launches the server to enable the database-backed tools (`get-checklist-state`, `get-note-metadata`, `get-note-link`, checklist annotations in `get-note-markdown`, and full `get-sync-status` detail); see the [Full Disk Access Setup Guide](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md). The rest of the server is pure AppleScript and works without it.
|
|
59
|
+
On the first tool call, macOS shows an Automation permission prompt ("Claude" wants access to control "Notes") — click **OK**. Optionally, grant **Full Disk Access** to the app that launches the server to enable the database-backed tools (`get-checklist-state`, `get-note-metadata`, `get-audio-transcripts`, `get-note-link`, checklist annotations in `get-note-markdown`, and full `get-sync-status` detail); see the [Full Disk Access Setup Guide](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md). The rest of the server is pure AppleScript and works without it.
|
|
60
60
|
|
|
61
61
|
Native tag, checklist, table, pin, and rich append operations use two packaged
|
|
62
62
|
Apple Shortcuts. A third, `Apple Notes MCP - Create Markdown Note`, is optional:
|
|
@@ -169,6 +169,7 @@ On first use, macOS will ask for permission to automate Notes.app. Click "OK" to
|
|
|
169
169
|
| **Multi-Account** | Work with iCloud, Gmail, Exchange, or any configured account, including account IDs and default folders |
|
|
170
170
|
| **Batch Operations** | Delete or move multiple notes at once |
|
|
171
171
|
| **Checklist State** | Read checklist done/undone state directly from the Notes database (requires Full Disk Access) |
|
|
172
|
+
| **Audio Transcripts** | Read the transcripts and summaries Notes stored for audio recordings (requires Full Disk Access) |
|
|
172
173
|
| **Export** | Export all notes as JSON or get individual notes as Markdown |
|
|
173
174
|
| **Attachments** | List attachments, save them to disk, or fetch their bytes as base64 |
|
|
174
175
|
| **Notes.app UI State** | Reveal a note in Notes.app or read the current Notes.app selection |
|
|
@@ -1274,6 +1275,38 @@ Reads note metadata that AppleScript cannot expose, by querying the NoteStore SQ
|
|
|
1274
1275
|
|
|
1275
1276
|
---
|
|
1276
1277
|
|
|
1278
|
+
#### `get-audio-transcripts`
|
|
1279
|
+
|
|
1280
|
+
Reads the transcript, and the summary when one exists, that Notes already computed for the audio recordings in a note. It does not transcribe anything itself. Notes keeps each recording's transcript as word-level mergeable data on the audio attachment's database row, and this tool decodes it read-only.
|
|
1281
|
+
|
|
1282
|
+
**Requires:** Full Disk Access for the MCP host process (see [Full Disk Access Setup](#full-disk-access)). Password-protected notes are refused.
|
|
1283
|
+
|
|
1284
|
+
| Parameter | Type | Required | Description |
|
|
1285
|
+
|-----------|------|----------|-------------|
|
|
1286
|
+
| `id` | string | Yes | Note ID (use `search-notes` to find it first) |
|
|
1287
|
+
| `includeSegments` | boolean | No | Also return word-level segments: `text`, `start` and `duration` in seconds, and `speaker`. Default `false` |
|
|
1288
|
+
| `maxSegments` | number | No | Cap on segments per attachment when `includeSegments` is set (default 2000, at most 20000) |
|
|
1289
|
+
|
|
1290
|
+
**Example:**
|
|
1291
|
+
```json
|
|
1292
|
+
{
|
|
1293
|
+
"id": "x-coredata://ABC123/ICNote/p456",
|
|
1294
|
+
"includeSegments": true,
|
|
1295
|
+
"maxSegments": 500
|
|
1296
|
+
}
|
|
1297
|
+
```
|
|
1298
|
+
|
|
1299
|
+
**Returns:** `structuredContent.attachments` holds one entry per top-level audio attachment, in the order the recordings appear in the note. Each entry has:
|
|
1300
|
+
|
|
1301
|
+
- `attachmentId` (usable with `save-attachment`), `identifier`, `typeUti`, and `durationSeconds` when known
|
|
1302
|
+
- `status`: `ok` (a transcript is stored), `none` (no transcript is stored, for example a recording Notes has not transcribed, or an audio file attached from elsewhere), or `undecodable` with a `reason`
|
|
1303
|
+
- `text` (the words joined into readable text), `wordCount`, `fragmentCount`, `speakers`, `summary` and `topLineSummary` when stored, and `needsTranscription` when the database records it
|
|
1304
|
+
- `segments`, only when `includeSegments` is set, with `segmentsTruncated` when the cap cut them short
|
|
1305
|
+
|
|
1306
|
+
A recording extended with more takes has several fragments. Their transcripts are joined in stored order, separated by a blank line, and each segment carries a `fragment` index. Recordings with more than one fragment have been verified against synthetic fixtures only. A response that would exceed `APPLE_NOTES_MCP_EXPORT_MAX_BYTES` (default 8 MB) drops segments first and then shortens `text`, marking `truncated`, `segmentsTruncated` and `textTruncated`. When the note body cannot be parsed, attachments come back in database order with `bodyOrder: false`.
|
|
1307
|
+
|
|
1308
|
+
---
|
|
1309
|
+
|
|
1277
1310
|
#### `add-attachment`
|
|
1278
1311
|
|
|
1279
1312
|
Adds one nonempty local file of at most 64 MiB to an exact note using `id`, the
|
|
@@ -1691,7 +1724,7 @@ MCP stores no secrets, but as a general rule keep only non-secret config here.
|
|
|
1691
1724
|
|
|
1692
1725
|
## Full Disk Access
|
|
1693
1726
|
|
|
1694
|
-
Several tools read directly from the Apple Notes SQLite database, which lives in a macOS-protected directory. Those tools require **Full Disk Access** for the process running the MCP server: `get-checklist-state`, `get-note-metadata`, `get-note-blocks`, `get-note-link`, the checklist annotations in `get-note-markdown`, and the database half of `get-sync-status`.
|
|
1727
|
+
Several tools read directly from the Apple Notes SQLite database, which lives in a macOS-protected directory. Those tools require **Full Disk Access** for the process running the MCP server: `get-checklist-state`, `get-note-metadata`, `get-note-blocks`, `get-audio-transcripts`, `get-note-link`, the checklist annotations in `get-note-markdown`, and the database half of `get-sync-status`.
|
|
1695
1728
|
|
|
1696
1729
|
> 📘 **For the full why-and-how walkthrough (which app to grant, verifying with `doctor`, graceful degradation), see the [Full Disk Access Setup Guide](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md).** The summary below is the quick version.
|
|
1697
1730
|
|
package/build/index.js
CHANGED
|
@@ -24465,14 +24465,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
24465
24465
|
} else if (node.nodeType === 1) {
|
|
24466
24466
|
replacement = replacementForNode.call(self, node);
|
|
24467
24467
|
}
|
|
24468
|
-
return
|
|
24468
|
+
return join16(output, replacement);
|
|
24469
24469
|
}, "");
|
|
24470
24470
|
}
|
|
24471
24471
|
function postProcess(output) {
|
|
24472
24472
|
var self = this;
|
|
24473
24473
|
this.rules.forEach(function(rule) {
|
|
24474
24474
|
if (typeof rule.append === "function") {
|
|
24475
|
-
output =
|
|
24475
|
+
output = join16(output, rule.append(self.options));
|
|
24476
24476
|
}
|
|
24477
24477
|
});
|
|
24478
24478
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -24484,7 +24484,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
24484
24484
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
24485
24485
|
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
|
|
24486
24486
|
}
|
|
24487
|
-
function
|
|
24487
|
+
function join16(output, replacement) {
|
|
24488
24488
|
var s1 = trimTrailingNewlines(output);
|
|
24489
24489
|
var s2 = trimLeadingNewlines(replacement);
|
|
24490
24490
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -24777,8 +24777,8 @@ var ZodError = class _ZodError extends Error {
|
|
|
24777
24777
|
constructor(issues) {
|
|
24778
24778
|
super();
|
|
24779
24779
|
this.issues = [];
|
|
24780
|
-
this.addIssue = (
|
|
24781
|
-
this.issues = [...this.issues,
|
|
24780
|
+
this.addIssue = (sub4) => {
|
|
24781
|
+
this.issues = [...this.issues, sub4];
|
|
24782
24782
|
};
|
|
24783
24783
|
this.addIssues = (subs = []) => {
|
|
24784
24784
|
this.issues = [...this.issues, ...subs];
|
|
@@ -24845,13 +24845,13 @@ var ZodError = class _ZodError extends Error {
|
|
|
24845
24845
|
flatten(mapper = (issue2) => issue2.message) {
|
|
24846
24846
|
const fieldErrors = {};
|
|
24847
24847
|
const formErrors = [];
|
|
24848
|
-
for (const
|
|
24849
|
-
if (
|
|
24850
|
-
const firstEl =
|
|
24848
|
+
for (const sub4 of this.issues) {
|
|
24849
|
+
if (sub4.path.length > 0) {
|
|
24850
|
+
const firstEl = sub4.path[0];
|
|
24851
24851
|
fieldErrors[firstEl] = fieldErrors[firstEl] || [];
|
|
24852
|
-
fieldErrors[firstEl].push(mapper(
|
|
24852
|
+
fieldErrors[firstEl].push(mapper(sub4));
|
|
24853
24853
|
} else {
|
|
24854
|
-
formErrors.push(mapper(
|
|
24854
|
+
formErrors.push(mapper(sub4));
|
|
24855
24855
|
}
|
|
24856
24856
|
}
|
|
24857
24857
|
return { formErrors, fieldErrors };
|
|
@@ -29150,12 +29150,12 @@ var $ZodRealError = $constructor("$ZodError", initializer, { Parent: Error });
|
|
|
29150
29150
|
function flattenError(error2, mapper = (issue2) => issue2.message) {
|
|
29151
29151
|
const fieldErrors = {};
|
|
29152
29152
|
const formErrors = [];
|
|
29153
|
-
for (const
|
|
29154
|
-
if (
|
|
29155
|
-
fieldErrors[
|
|
29156
|
-
fieldErrors[
|
|
29153
|
+
for (const sub4 of error2.issues) {
|
|
29154
|
+
if (sub4.path.length > 0) {
|
|
29155
|
+
fieldErrors[sub4.path[0]] = fieldErrors[sub4.path[0]] || [];
|
|
29156
|
+
fieldErrors[sub4.path[0]].push(mapper(sub4));
|
|
29157
29157
|
} else {
|
|
29158
|
-
formErrors.push(mapper(
|
|
29158
|
+
formErrors.push(mapper(sub4));
|
|
29159
29159
|
}
|
|
29160
29160
|
}
|
|
29161
29161
|
return { formErrors, fieldErrors };
|
|
@@ -39275,7 +39275,7 @@ function decodeVarint(buf, offset) {
|
|
|
39275
39275
|
}
|
|
39276
39276
|
throw new Error(`Unexpected end of buffer reading varint at offset ${offset}`);
|
|
39277
39277
|
}
|
|
39278
|
-
function decodeMessage(buf) {
|
|
39278
|
+
function decodeMessage(buf, options = {}) {
|
|
39279
39279
|
const fields = [];
|
|
39280
39280
|
let offset = 0;
|
|
39281
39281
|
while (offset < buf.length) {
|
|
@@ -39296,10 +39296,13 @@ function decodeMessage(buf) {
|
|
|
39296
39296
|
const value = buf.slice(offset, offset + length);
|
|
39297
39297
|
fields.push({ fieldNumber, wireType, value });
|
|
39298
39298
|
offset += length;
|
|
39299
|
-
} else if (wireType === 5) {
|
|
39300
|
-
|
|
39301
|
-
|
|
39302
|
-
|
|
39299
|
+
} else if (wireType === 5 || wireType === 1) {
|
|
39300
|
+
const width = wireType === 5 ? 4 : 8;
|
|
39301
|
+
if (offset + width > buf.length) break;
|
|
39302
|
+
if (options.keepFixed) {
|
|
39303
|
+
fields.push({ fieldNumber, wireType, value: buf.slice(offset, offset + width) });
|
|
39304
|
+
}
|
|
39305
|
+
offset += width;
|
|
39303
39306
|
} else {
|
|
39304
39307
|
break;
|
|
39305
39308
|
}
|
|
@@ -39330,6 +39333,11 @@ function embeddedMessage(field) {
|
|
|
39330
39333
|
if (!bytes) return void 0;
|
|
39331
39334
|
return decodeMessage(bytes);
|
|
39332
39335
|
}
|
|
39336
|
+
function fixed64Double(field) {
|
|
39337
|
+
if (!field || field.wireType !== 1 || !(field.value instanceof Uint8Array)) return void 0;
|
|
39338
|
+
if (field.value.length !== 8) return void 0;
|
|
39339
|
+
return new DataView(field.value.buffer, field.value.byteOffset, 8).getFloat64(0, true);
|
|
39340
|
+
}
|
|
39333
39341
|
var ProtobufDecodeError = class extends Error {
|
|
39334
39342
|
constructor(message) {
|
|
39335
39343
|
super(message);
|
|
@@ -39901,9 +39909,373 @@ function assertLinkedWrite(rich, content, format, allowLinkChanges = false) {
|
|
|
39901
39909
|
}
|
|
39902
39910
|
}
|
|
39903
39911
|
|
|
39904
|
-
// src/utils/
|
|
39912
|
+
// src/utils/audioTranscripts.ts
|
|
39913
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
39914
|
+
import { homedir as homedir3 } from "node:os";
|
|
39915
|
+
import { join as join3 } from "node:path";
|
|
39905
39916
|
import { gunzipSync as gunzipSync3 } from "node:zlib";
|
|
39917
|
+
var NOTES_DB_PATH2 = join3(
|
|
39918
|
+
homedir3(),
|
|
39919
|
+
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
39920
|
+
);
|
|
39921
|
+
var DEFAULT_MAX_SEGMENTS = 2e3;
|
|
39922
|
+
var MAX_SEGMENTS_LIMIT = 2e4;
|
|
39923
|
+
var AudioTranscriptError = class extends Error {
|
|
39924
|
+
constructor(kind, message) {
|
|
39925
|
+
super(message);
|
|
39926
|
+
this.kind = kind;
|
|
39927
|
+
this.name = "AudioTranscriptError";
|
|
39928
|
+
}
|
|
39929
|
+
kind;
|
|
39930
|
+
};
|
|
39931
|
+
var decode = (bytes) => decodeMessage(bytes, { keepFixed: true });
|
|
39906
39932
|
var sub = (f, n) => {
|
|
39933
|
+
const bytes = getField(f, n)?.value;
|
|
39934
|
+
return bytes instanceof Uint8Array ? decode(bytes) : void 0;
|
|
39935
|
+
};
|
|
39936
|
+
var need = (value, what) => {
|
|
39937
|
+
if (value === void 0) throw new Error(`Missing ${what}`);
|
|
39938
|
+
return value;
|
|
39939
|
+
};
|
|
39940
|
+
var many = (f, n) => getFields(f, n).map((field) => {
|
|
39941
|
+
if (!(field.value instanceof Uint8Array)) throw new Error("Invalid nested entry");
|
|
39942
|
+
return decode(field.value);
|
|
39943
|
+
});
|
|
39944
|
+
function parseAudioRecording(data) {
|
|
39945
|
+
const root = decode(data);
|
|
39946
|
+
const entries = many(root, 3);
|
|
39947
|
+
if (entries.length === 0) throw new Error("No mergeable objects");
|
|
39948
|
+
if (entries.length > 2e6) throw new Error("Recording too large");
|
|
39949
|
+
const keys = getFields(root, 4).map((f) => stringValue(f) ?? "");
|
|
39950
|
+
const types = getFields(root, 5).map((f) => stringValue(f) ?? "");
|
|
39951
|
+
const uuids = getFields(root, 6).map(
|
|
39952
|
+
(f) => f.value instanceof Uint8Array ? Buffer.from(f.value).toString("hex") : ""
|
|
39953
|
+
);
|
|
39954
|
+
const uuidSlot = /* @__PURE__ */ new Map();
|
|
39955
|
+
uuids.forEach((uuid2, i) => {
|
|
39956
|
+
if (uuid2 && !uuidSlot.has(uuid2)) uuidSlot.set(uuid2, i);
|
|
39957
|
+
});
|
|
39958
|
+
const entry = (index) => {
|
|
39959
|
+
const value = entries[index];
|
|
39960
|
+
if (!value) throw new Error("Invalid object reference");
|
|
39961
|
+
return value;
|
|
39962
|
+
};
|
|
39963
|
+
const customMap = (e) => {
|
|
39964
|
+
const map = sub(e, 13);
|
|
39965
|
+
if (!map) return void 0;
|
|
39966
|
+
const values = /* @__PURE__ */ new Map();
|
|
39967
|
+
for (const item of many(map, 3)) {
|
|
39968
|
+
const key = keys[need(varintValue(getField(item, 1)), "map key")];
|
|
39969
|
+
const value = sub(item, 2);
|
|
39970
|
+
if (key !== void 0 && value) values.set(key, value);
|
|
39971
|
+
}
|
|
39972
|
+
return { type: types[need(varintValue(getField(map, 1)), "map type")] ?? "", values };
|
|
39973
|
+
};
|
|
39974
|
+
const objectIndex = (id2) => id2 ? varintValue(getField(id2, 6)) : void 0;
|
|
39975
|
+
const registerTarget = (id2) => {
|
|
39976
|
+
const index = objectIndex(id2);
|
|
39977
|
+
if (index === void 0) return void 0;
|
|
39978
|
+
const register = sub(entry(index), 1);
|
|
39979
|
+
if (!register) return void 0;
|
|
39980
|
+
const target = objectIndex(sub(register, 2));
|
|
39981
|
+
return target === void 0 ? void 0 : entry(target);
|
|
39982
|
+
};
|
|
39983
|
+
const registerPrimitive = (id2) => {
|
|
39984
|
+
const target = registerTarget(id2);
|
|
39985
|
+
const map = target && customMap(target);
|
|
39986
|
+
if (!map) return void 0;
|
|
39987
|
+
const self = map.values.get("self");
|
|
39988
|
+
if (self) return stringValue(getField(self, 4));
|
|
39989
|
+
const double = map.values.get("doubleValue");
|
|
39990
|
+
if (double) return fixed64Double(getField(double, 3));
|
|
39991
|
+
const integer2 = map.values.get("integerValue");
|
|
39992
|
+
if (integer2) return varintValue(getField(integer2, 2));
|
|
39993
|
+
return void 0;
|
|
39994
|
+
};
|
|
39995
|
+
const registerNoteText = (id2) => {
|
|
39996
|
+
const target = registerTarget(id2);
|
|
39997
|
+
const note = target && sub(target, 10);
|
|
39998
|
+
const text = note && stringValue(getField(note, 2));
|
|
39999
|
+
return text?.replace(/\n+$/u, "") || void 0;
|
|
40000
|
+
};
|
|
40001
|
+
const recordings = entries.map((e) => customMap(e)).filter((m) => m?.type === "com.apple.notes.ICTTAudioRecording");
|
|
40002
|
+
if (recordings.length !== 1) throw new Error("Expected exactly one audio recording object");
|
|
40003
|
+
const recording = recordings[0];
|
|
40004
|
+
const fragments = [];
|
|
40005
|
+
const fragmentList = objectIndex(recording.values.get("fragments"));
|
|
40006
|
+
if (fragmentList !== void 0) {
|
|
40007
|
+
const list = need(sub(entry(fragmentList), 5), "fragment list");
|
|
40008
|
+
for (const item of many(list, 1)) {
|
|
40009
|
+
const fragment = customMap(entry(need(objectIndex(sub(item, 2)), "fragment reference")));
|
|
40010
|
+
if (fragment?.type !== "com.apple.notes.ICTTAudioRecording.Fragment")
|
|
40011
|
+
throw new Error("Unexpected fragment type");
|
|
40012
|
+
const identity = stringValue(getField(fragment.values.get("identity") ?? [], 4));
|
|
40013
|
+
const segments = [];
|
|
40014
|
+
const transcriptRef = objectIndex(fragment.values.get("transcript"));
|
|
40015
|
+
if (transcriptRef !== void 0) {
|
|
40016
|
+
const set = sub(entry(transcriptRef), 15);
|
|
40017
|
+
if (!set) throw new Error("Unsupported transcript container");
|
|
40018
|
+
const bySlot = /* @__PURE__ */ new Map();
|
|
40019
|
+
for (const element of many(sub(set, 2) ?? [], 1)) {
|
|
40020
|
+
const keyMap = customMap(entry(need(objectIndex(sub(element, 1)), "segment key")));
|
|
40021
|
+
const slot = varintValue(getField(keyMap?.values.get("UUIDIndex") ?? [], 2));
|
|
40022
|
+
const value = objectIndex(sub(element, 2));
|
|
40023
|
+
if (slot === void 0 || value === void 0) throw new Error("Invalid segment key");
|
|
40024
|
+
bySlot.set(slot, value);
|
|
40025
|
+
}
|
|
40026
|
+
const ordering = many(need(sub(set, 1), "transcript ordering"), 2).map((pair) => ({
|
|
40027
|
+
index: need(varintValue(getField(pair, 1)), "segment index"),
|
|
40028
|
+
uuid: getField(pair, 2)?.value
|
|
40029
|
+
})).sort((a, b) => a.index - b.index);
|
|
40030
|
+
for (const { uuid: uuid2 } of ordering) {
|
|
40031
|
+
if (!(uuid2 instanceof Uint8Array)) throw new Error("Invalid segment UUID");
|
|
40032
|
+
const slot = uuidSlot.get(Buffer.from(uuid2).toString("hex"));
|
|
40033
|
+
const segmentIndex = slot === void 0 ? void 0 : bySlot.get(slot);
|
|
40034
|
+
if (segmentIndex === void 0) throw new Error("Unresolved transcript segment");
|
|
40035
|
+
const segment = customMap(entry(segmentIndex));
|
|
40036
|
+
if (segment?.type !== "com.apple.notes.ICTTTranscriptSegment")
|
|
40037
|
+
throw new Error("Unexpected segment type");
|
|
40038
|
+
const text = registerPrimitive(segment.values.get("text"));
|
|
40039
|
+
if (typeof text !== "string") throw new Error("Segment without text");
|
|
40040
|
+
const start = registerPrimitive(segment.values.get("timestamp"));
|
|
40041
|
+
const duration3 = registerPrimitive(segment.values.get("duration"));
|
|
40042
|
+
const speaker = registerPrimitive(segment.values.get("speaker"));
|
|
40043
|
+
segments.push({
|
|
40044
|
+
text,
|
|
40045
|
+
...typeof start === "number" ? { start } : {},
|
|
40046
|
+
...typeof duration3 === "number" ? { duration: duration3 } : {},
|
|
40047
|
+
...typeof speaker === "string" && speaker ? { speaker } : {}
|
|
40048
|
+
});
|
|
40049
|
+
}
|
|
40050
|
+
}
|
|
40051
|
+
fragments.push({ ...identity ? { identity } : {}, segments });
|
|
40052
|
+
}
|
|
40053
|
+
}
|
|
40054
|
+
const summary = registerNoteText(recording.values.get("summary"));
|
|
40055
|
+
const topLineSummary = registerNoteText(recording.values.get("topLineSummary"));
|
|
40056
|
+
return {
|
|
40057
|
+
fragments,
|
|
40058
|
+
...summary ? { summary } : {},
|
|
40059
|
+
...topLineSummary ? { topLineSummary } : {}
|
|
40060
|
+
};
|
|
40061
|
+
}
|
|
40062
|
+
function joinSegments(segments) {
|
|
40063
|
+
let out = "";
|
|
40064
|
+
for (const { text } of segments) {
|
|
40065
|
+
if (!text) continue;
|
|
40066
|
+
const glue = !out || /\s$/u.test(out) || /^\s/u.test(text) || /^[.,!?;:%)\]}…'’]/u.test(text) ? "" : " ";
|
|
40067
|
+
out += glue + text;
|
|
40068
|
+
}
|
|
40069
|
+
return out.trim();
|
|
40070
|
+
}
|
|
40071
|
+
function parseNoteId(noteId3) {
|
|
40072
|
+
const match = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/ICNote\/p(\d+)$/.exec(noteId3);
|
|
40073
|
+
if (!match) {
|
|
40074
|
+
throw new AudioTranscriptError(
|
|
40075
|
+
"invalid_id",
|
|
40076
|
+
`Invalid note ID format: "${noteId3}". Expected format: x-coredata://UUID/ICNote/pNNN`
|
|
40077
|
+
);
|
|
40078
|
+
}
|
|
40079
|
+
return { store: match[1], pk: match[2] };
|
|
40080
|
+
}
|
|
40081
|
+
var AUDIO_UTI_SQL = "(a.ZTYPEUTI LIKE '%audio%' OR a.ZTYPEUTI IN ('public.mp3'))";
|
|
40082
|
+
function buildTranscriptSql(pk, available) {
|
|
40083
|
+
if (!/^\d+$/.test(pk)) throw new Error("Invalid primary key");
|
|
40084
|
+
const optional2 = (column, key, alias = "a") => available.has(column) ? `, '${key}', ${alias}.${column}` : "";
|
|
40085
|
+
const locked = available.has("ZISPASSWORDPROTECTED") ? "COALESCE(n.ZISPASSWORDPROTECTED, 0)" : "0";
|
|
40086
|
+
return [
|
|
40087
|
+
"BEGIN;",
|
|
40088
|
+
// Every statement yields exactly one row, so output lines stay positional.
|
|
40089
|
+
`SELECT json_object('found', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = ${pk}), 'locked', (SELECT ${locked} FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = ${pk}));`,
|
|
40090
|
+
`SELECT COALESCE((SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE = ${pk} LIMIT 1), '');`,
|
|
40091
|
+
"SELECT json_group_array(json_object('pk', a.Z_PK, 'identifier', a.ZIDENTIFIER, 'uti', a.ZTYPEUTI, 'data', hex(a.ZMERGEABLEDATA1)" + optional2("ZDURATION", "duration") + optional2("ZNEEDSTRANSCRIPTION", "needsTranscription") + ", 'fragments', json((SELECT json_group_array(json_object('identifier', c.ZIDENTIFIER" + optional2("ZDURATION", "duration", "c") + `)) FROM ZICCLOUDSYNCINGOBJECT c WHERE c.ZPARENTATTACHMENT = a.Z_PK)))) FROM ZICCLOUDSYNCINGOBJECT a WHERE a.ZNOTE = ${pk} AND a.ZPARENTATTACHMENT IS NULL AND ${AUDIO_UTI_SQL};`,
|
|
40092
|
+
"COMMIT;"
|
|
40093
|
+
].join("\n");
|
|
40094
|
+
}
|
|
40095
|
+
var REQUIRED_COLUMNS = [
|
|
40096
|
+
"ZMERGEABLEDATA1",
|
|
40097
|
+
"ZPARENTATTACHMENT",
|
|
40098
|
+
"ZTYPEUTI",
|
|
40099
|
+
"ZNOTE",
|
|
40100
|
+
"ZIDENTIFIER"
|
|
40101
|
+
];
|
|
40102
|
+
function runSqlite(dbPath2, sql) {
|
|
40103
|
+
return execFileSync4("sqlite3", ["-readonly", dbPath2, sql], {
|
|
40104
|
+
encoding: "utf8",
|
|
40105
|
+
timeout: 1e4,
|
|
40106
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
40107
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
40108
|
+
});
|
|
40109
|
+
}
|
|
40110
|
+
function bodyAttachmentOrder(gzipped) {
|
|
40111
|
+
const doc = decodeMessage(gunzipSync3(gzipped, { maxOutputLength: 64 * 1024 * 1024 }));
|
|
40112
|
+
const body = embeddedMessage(getField(embeddedMessage(getField(doc, 2)) ?? [], 3));
|
|
40113
|
+
if (!body) throw new Error("Unsupported Notes document structure");
|
|
40114
|
+
const ids = [];
|
|
40115
|
+
for (const run of getFields(body, 5)) {
|
|
40116
|
+
const attachment = embeddedMessage(getField(embeddedMessage(run) ?? [], 12));
|
|
40117
|
+
const id2 = attachment && stringValue(getField(attachment, 1));
|
|
40118
|
+
if (id2 && !ids.includes(id2)) ids.push(id2);
|
|
40119
|
+
}
|
|
40120
|
+
return ids;
|
|
40121
|
+
}
|
|
40122
|
+
function readAudioTranscripts(noteId3, options = {}) {
|
|
40123
|
+
const { store, pk } = parseNoteId(noteId3);
|
|
40124
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH2;
|
|
40125
|
+
const maxSegments = Math.min(
|
|
40126
|
+
Math.max(1, Math.floor(options.maxSegments ?? DEFAULT_MAX_SEGMENTS)),
|
|
40127
|
+
MAX_SEGMENTS_LIMIT
|
|
40128
|
+
);
|
|
40129
|
+
let lines;
|
|
40130
|
+
try {
|
|
40131
|
+
const available = new Set(
|
|
40132
|
+
runSqlite(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").split("\n").map((line) => line.trim()).filter(Boolean)
|
|
40133
|
+
);
|
|
40134
|
+
const missing = REQUIRED_COLUMNS.filter((c) => !available.has(c));
|
|
40135
|
+
if (missing.length)
|
|
40136
|
+
throw new AudioTranscriptError(
|
|
40137
|
+
"query_error",
|
|
40138
|
+
`This macOS version's Notes database lacks ${missing.join(", ")}; stored transcripts cannot be read.`
|
|
40139
|
+
);
|
|
40140
|
+
lines = runSqlite(dbPath2, buildTranscriptSql(pk, available)).split("\n");
|
|
40141
|
+
} catch (error2) {
|
|
40142
|
+
if (error2 instanceof AudioTranscriptError) throw error2;
|
|
40143
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
40144
|
+
if (/authorization denied|unable to open database/i.test(message))
|
|
40145
|
+
throw new AudioTranscriptError(
|
|
40146
|
+
"no_fda",
|
|
40147
|
+
`Full Disk Access is required to read stored transcripts. Grant it to the app that launches this server, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`
|
|
40148
|
+
);
|
|
40149
|
+
throw new AudioTranscriptError("query_error", "Failed to read the Notes database.");
|
|
40150
|
+
}
|
|
40151
|
+
const [noteLine = "{}", bodyLine = "", rowsLine = "[]"] = lines;
|
|
40152
|
+
const note = JSON.parse(noteLine || "{}");
|
|
40153
|
+
if (!note.found)
|
|
40154
|
+
throw new AudioTranscriptError(
|
|
40155
|
+
"not_found",
|
|
40156
|
+
`No note found in the database for ID "${noteId3}".`
|
|
40157
|
+
);
|
|
40158
|
+
if (note.locked)
|
|
40159
|
+
throw new AudioTranscriptError(
|
|
40160
|
+
"locked",
|
|
40161
|
+
"This note is password-protected; its transcripts are encrypted and cannot be read."
|
|
40162
|
+
);
|
|
40163
|
+
const rows = JSON.parse(rowsLine || "[]");
|
|
40164
|
+
let order;
|
|
40165
|
+
try {
|
|
40166
|
+
if (/^[0-9A-F]+$/i.test(bodyLine.trim()))
|
|
40167
|
+
order = bodyAttachmentOrder(Buffer.from(bodyLine.trim(), "hex"));
|
|
40168
|
+
} catch {
|
|
40169
|
+
order = void 0;
|
|
40170
|
+
}
|
|
40171
|
+
const ordered = order ? order.flatMap((id2) => rows.filter((row) => row.identifier === id2)) : [...rows].sort((a, b) => a.pk - b.pk);
|
|
40172
|
+
const attachments = ordered.map((row) => describeRow(row, store, options, maxSegments));
|
|
40173
|
+
return { id: noteId3, attachments, bodyOrder: Boolean(order), truncated: false };
|
|
40174
|
+
}
|
|
40175
|
+
function describeRow(row, store, options, maxSegments) {
|
|
40176
|
+
const childDurations = (row.fragments ?? []).map((f) => f.duration).filter((d) => typeof d === "number" && d > 0);
|
|
40177
|
+
const durationSeconds = typeof row.duration === "number" && row.duration > 0 ? row.duration : childDurations.length ? childDurations.reduce((a, b) => a + b, 0) : void 0;
|
|
40178
|
+
const base = {
|
|
40179
|
+
attachmentId: `x-coredata://${store}/ICAttachment/p${row.pk}`,
|
|
40180
|
+
identifier: row.identifier,
|
|
40181
|
+
typeUti: row.uti,
|
|
40182
|
+
status: "none",
|
|
40183
|
+
...durationSeconds !== void 0 ? { durationSeconds } : {},
|
|
40184
|
+
...typeof row.needsTranscription === "number" ? { needsTranscription: row.needsTranscription === 1 } : {}
|
|
40185
|
+
};
|
|
40186
|
+
if (!row.data) return base;
|
|
40187
|
+
let recording;
|
|
40188
|
+
try {
|
|
40189
|
+
recording = parseAudioRecording(Buffer.from(row.data, "hex"));
|
|
40190
|
+
} catch (error2) {
|
|
40191
|
+
return {
|
|
40192
|
+
...base,
|
|
40193
|
+
status: "undecodable",
|
|
40194
|
+
reason: error2 instanceof Error ? error2.message : String(error2)
|
|
40195
|
+
};
|
|
40196
|
+
}
|
|
40197
|
+
const multi = recording.fragments.length > 1;
|
|
40198
|
+
const all = recording.fragments.flatMap(
|
|
40199
|
+
(fragment, i) => fragment.segments.map((s) => multi ? { ...s, fragment: i } : s)
|
|
40200
|
+
);
|
|
40201
|
+
const text = recording.fragments.map((f) => joinSegments(f.segments)).filter(Boolean).join("\n\n");
|
|
40202
|
+
const speakers = [...new Set(all.flatMap((s) => s.speaker ? [s.speaker] : []))];
|
|
40203
|
+
return {
|
|
40204
|
+
...base,
|
|
40205
|
+
status: all.length ? "ok" : "none",
|
|
40206
|
+
fragmentCount: recording.fragments.length,
|
|
40207
|
+
wordCount: all.length,
|
|
40208
|
+
...text ? { text } : {},
|
|
40209
|
+
...speakers.length ? { speakers } : {},
|
|
40210
|
+
...recording.summary ? { summary: recording.summary } : {},
|
|
40211
|
+
...recording.topLineSummary ? { topLineSummary: recording.topLineSummary } : {},
|
|
40212
|
+
...options.includeSegments && all.length ? {
|
|
40213
|
+
segments: all.slice(0, maxSegments),
|
|
40214
|
+
...all.length > maxSegments ? { segmentsTruncated: true } : {}
|
|
40215
|
+
} : {}
|
|
40216
|
+
};
|
|
40217
|
+
}
|
|
40218
|
+
function fitTranscriptsToBudget(result, maxBytes, measure) {
|
|
40219
|
+
if (measure(result) <= maxBytes) return result;
|
|
40220
|
+
let next = {
|
|
40221
|
+
...result,
|
|
40222
|
+
truncated: true,
|
|
40223
|
+
attachments: result.attachments.map((a) => {
|
|
40224
|
+
if (!a.segments) return a;
|
|
40225
|
+
const rest = { ...a, segmentsTruncated: true };
|
|
40226
|
+
delete rest.segments;
|
|
40227
|
+
return rest;
|
|
40228
|
+
})
|
|
40229
|
+
};
|
|
40230
|
+
for (let share = 0.5; measure(next) > maxBytes && share > 1e-4; share /= 2) {
|
|
40231
|
+
next = {
|
|
40232
|
+
...next,
|
|
40233
|
+
attachments: next.attachments.map((a) => {
|
|
40234
|
+
const original = result.attachments.find((o) => o.attachmentId === a.attachmentId);
|
|
40235
|
+
if (!original?.text) return a;
|
|
40236
|
+
const keep = Math.floor(original.text.length * share);
|
|
40237
|
+
return { ...a, text: original.text.slice(0, keep), textTruncated: true };
|
|
40238
|
+
})
|
|
40239
|
+
};
|
|
40240
|
+
}
|
|
40241
|
+
return next;
|
|
40242
|
+
}
|
|
40243
|
+
var clock = (seconds) => {
|
|
40244
|
+
const total = Math.round(seconds);
|
|
40245
|
+
const h = Math.floor(total / 3600);
|
|
40246
|
+
const m = Math.floor(total % 3600 / 60);
|
|
40247
|
+
const s = total % 60;
|
|
40248
|
+
const mm = h ? String(m).padStart(2, "0") : String(m);
|
|
40249
|
+
return `${h ? `${h}:` : ""}${mm}:${String(s).padStart(2, "0")}`;
|
|
40250
|
+
};
|
|
40251
|
+
function formatTranscriptsText(result) {
|
|
40252
|
+
const count = result.attachments.length;
|
|
40253
|
+
if (count === 0) return `No audio attachments found in note ${result.id}.`;
|
|
40254
|
+
const lines = [`${count} audio attachment${count === 1 ? "" : "s"} in note ${result.id}:`];
|
|
40255
|
+
result.attachments.forEach((a, i) => {
|
|
40256
|
+
const facts = [
|
|
40257
|
+
`status ${a.status}`,
|
|
40258
|
+
...a.durationSeconds !== void 0 ? [clock(a.durationSeconds)] : [],
|
|
40259
|
+
...a.wordCount !== void 0 ? [`${a.wordCount} words`] : [],
|
|
40260
|
+
...a.fragmentCount && a.fragmentCount > 1 ? [`${a.fragmentCount} fragments`] : [],
|
|
40261
|
+
...a.speakers?.length ? [`${a.speakers.length} speakers`] : [],
|
|
40262
|
+
...a.reason ? [`reason: ${a.reason}`] : []
|
|
40263
|
+
];
|
|
40264
|
+
lines.push("", `[${i + 1}] ${a.attachmentId} (${facts.join(", ")})`);
|
|
40265
|
+
if (a.summary) lines.push(`Summary: ${a.summary}`);
|
|
40266
|
+
if (a.text) lines.push(a.textTruncated ? `${a.text} [truncated]` : a.text);
|
|
40267
|
+
});
|
|
40268
|
+
if (result.truncated)
|
|
40269
|
+
lines.push(
|
|
40270
|
+
"",
|
|
40271
|
+
"Some segments or text were left out to stay under the response size limit (APPLE_NOTES_MCP_EXPORT_MAX_BYTES)."
|
|
40272
|
+
);
|
|
40273
|
+
return lines.join("\n");
|
|
40274
|
+
}
|
|
40275
|
+
|
|
40276
|
+
// src/utils/noteTables.ts
|
|
40277
|
+
import { gunzipSync as gunzipSync4 } from "node:zlib";
|
|
40278
|
+
var sub2 = (f, n) => {
|
|
39907
40279
|
const value = embeddedMessage(getField(f, n));
|
|
39908
40280
|
if (!value) throw new Error(`Missing table field ${n}`);
|
|
39909
40281
|
return value;
|
|
@@ -39913,7 +40285,7 @@ var num = (f, n) => {
|
|
|
39913
40285
|
if (v === void 0) throw new Error(`Missing table index ${n}`);
|
|
39914
40286
|
return v;
|
|
39915
40287
|
};
|
|
39916
|
-
var
|
|
40288
|
+
var many2 = (f, n) => getFields(f, n).map((v) => {
|
|
39917
40289
|
const m = embeddedMessage(v);
|
|
39918
40290
|
if (!m) throw new Error("Invalid table entry");
|
|
39919
40291
|
return m;
|
|
@@ -39930,37 +40302,37 @@ function parseNoteTableCells(compressed) {
|
|
|
39930
40302
|
return decodeTable(compressed, false);
|
|
39931
40303
|
}
|
|
39932
40304
|
function decodeTable(compressed, strict) {
|
|
39933
|
-
const root = decodeMessage(
|
|
39934
|
-
const data =
|
|
40305
|
+
const root = decodeMessage(gunzipSync4(compressed, { maxOutputLength: 16 * 1024 * 1024 }));
|
|
40306
|
+
const data = sub2(sub2(root, 2), 3), entries = many2(data, 3);
|
|
39935
40307
|
if (entries.length > 1e5) throw new Error("Table too large");
|
|
39936
40308
|
const keys = getFields(data, 4).map(stringValue), types = getFields(data, 5).map(stringValue), uuids = getFields(data, 6).map(hex);
|
|
39937
40309
|
const entry = (index) => {
|
|
39938
40310
|
if (!entries[index]) throw new Error("Invalid table reference");
|
|
39939
40311
|
return entries[index];
|
|
39940
40312
|
};
|
|
39941
|
-
const uuidIndex = (index) => num(
|
|
40313
|
+
const uuidIndex = (index) => num(sub2(many2(sub2(entry(index), 13), 3)[0], 2), 2);
|
|
39942
40314
|
const roots = entries.filter((e) => {
|
|
39943
40315
|
const map = embeddedMessage(getField(e, 13));
|
|
39944
40316
|
return map && types[num(map, 1)] === "com.apple.notes.ICTable";
|
|
39945
40317
|
});
|
|
39946
40318
|
if (roots.length !== 1) throw new Error("Ambiguous native table root");
|
|
39947
40319
|
const refs = new Map(
|
|
39948
|
-
|
|
40320
|
+
many2(sub2(roots[0], 13), 3).filter((m) => ["crRows", "crColumns", "cellColumns"].includes(keys[num(m, 1)] || "")).map((m) => [keys[num(m, 1)], num(sub2(m, 2), 6)])
|
|
39949
40321
|
);
|
|
39950
40322
|
const ordered = (key) => {
|
|
39951
40323
|
const ref = refs.get(key);
|
|
39952
40324
|
if (ref === void 0) throw new Error("Missing table dimension");
|
|
39953
|
-
const ordering =
|
|
39954
|
-
const ids =
|
|
40325
|
+
const ordering = sub2(sub2(entry(ref), 16), 1), array2 = sub2(ordering, 1);
|
|
40326
|
+
const ids = many2(array2, 2).map((a) => hex(getField(a, 2)));
|
|
39955
40327
|
const map = /* @__PURE__ */ new Map();
|
|
39956
40328
|
ids.forEach((id2, i) => {
|
|
39957
40329
|
const index = uuids.indexOf(id2);
|
|
39958
40330
|
if (index < 0) throw new Error("Missing dimension UUID");
|
|
39959
40331
|
map.set(index, i);
|
|
39960
40332
|
});
|
|
39961
|
-
const aliases =
|
|
39962
|
-
uuidIndex(num(
|
|
39963
|
-
uuidIndex(num(
|
|
40333
|
+
const aliases = many2(sub2(ordering, 2), 1).map((pair) => [
|
|
40334
|
+
uuidIndex(num(sub2(pair, 1), 6)),
|
|
40335
|
+
uuidIndex(num(sub2(pair, 2), 6))
|
|
39964
40336
|
]);
|
|
39965
40337
|
for (let pass = 0; pass < aliases.length + 1; pass++) {
|
|
39966
40338
|
let changed = false;
|
|
@@ -39980,13 +40352,13 @@ function decodeTable(compressed, strict) {
|
|
|
39980
40352
|
const incomplete = [];
|
|
39981
40353
|
const cellRef = refs.get("cellColumns");
|
|
39982
40354
|
if (cellRef === void 0) throw new Error("Missing table cells");
|
|
39983
|
-
for (const column of
|
|
39984
|
-
const ci = columns.map.get(uuidIndex(num(
|
|
39985
|
-
const cells = entry(num(
|
|
39986
|
-
for (const row of
|
|
39987
|
-
const ri = rows.map.get(uuidIndex(num(
|
|
40355
|
+
for (const column of many2(sub2(entry(cellRef), 6), 1)) {
|
|
40356
|
+
const ci = columns.map.get(uuidIndex(num(sub2(column, 1), 6)));
|
|
40357
|
+
const cells = entry(num(sub2(column, 2), 6));
|
|
40358
|
+
for (const row of many2(sub2(cells, 6), 1)) {
|
|
40359
|
+
const ri = rows.map.get(uuidIndex(num(sub2(row, 1), 6)));
|
|
39988
40360
|
if (ri === void 0 || ci === void 0) continue;
|
|
39989
|
-
const note =
|
|
40361
|
+
const note = sub2(entry(num(sub2(row, 2), 6)), 10);
|
|
39990
40362
|
const text = stringValue(getField(note, 2));
|
|
39991
40363
|
if (text === void 0 || text.includes("\uFFFC")) {
|
|
39992
40364
|
if (strict) throw new Error("Embedded or unsupported table cell");
|
|
@@ -40003,8 +40375,8 @@ function decodeTable(compressed, strict) {
|
|
|
40003
40375
|
}
|
|
40004
40376
|
const rtl = entries.some((e) => {
|
|
40005
40377
|
const map = embeddedMessage(getField(e, 13));
|
|
40006
|
-
return map &&
|
|
40007
|
-
(m) => stringValue(getField(
|
|
40378
|
+
return map && many2(map, 3).some(
|
|
40379
|
+
(m) => stringValue(getField(sub2(m, 2), 4)) === "CRTableColumnDirectionRightToLeft"
|
|
40008
40380
|
);
|
|
40009
40381
|
});
|
|
40010
40382
|
const width = columns.ids.length;
|
|
@@ -40081,11 +40453,11 @@ function collectNoteTables(rich, noteId3) {
|
|
|
40081
40453
|
}
|
|
40082
40454
|
|
|
40083
40455
|
// src/utils/smartFolders.ts
|
|
40084
|
-
import { execFileSync as
|
|
40456
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
40085
40457
|
import * as fs2 from "fs";
|
|
40086
40458
|
import * as os2 from "os";
|
|
40087
40459
|
import * as path2 from "path";
|
|
40088
|
-
var
|
|
40460
|
+
var NOTES_DB_PATH3 = path2.join(
|
|
40089
40461
|
os2.homedir(),
|
|
40090
40462
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
40091
40463
|
);
|
|
@@ -40376,7 +40748,7 @@ WHERE Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICHashtag')
|
|
|
40376
40748
|
AND ZSTANDARDIZEDCONTENT IS NOT NULL
|
|
40377
40749
|
AND COALESCE(ZMARKEDFORDELETION, 0) = 0;
|
|
40378
40750
|
COMMIT;`;
|
|
40379
|
-
var
|
|
40751
|
+
var REQUIRED_COLUMNS2 = [
|
|
40380
40752
|
"ZIDENTIFIER",
|
|
40381
40753
|
"ZTITLE2",
|
|
40382
40754
|
"ZFOLDERTYPE",
|
|
@@ -40430,21 +40802,21 @@ function buildSmartFolders(output) {
|
|
|
40430
40802
|
(a, b) => (a.account ?? "").localeCompare(b.account ?? "") || (a.name ?? "").localeCompare(b.name ?? "") || a.id.localeCompare(b.id)
|
|
40431
40803
|
);
|
|
40432
40804
|
}
|
|
40433
|
-
function
|
|
40434
|
-
return
|
|
40805
|
+
function runSqlite2(dbPath2, sql) {
|
|
40806
|
+
return execFileSync5("sqlite3", ["-readonly", dbPath2, sql], {
|
|
40435
40807
|
encoding: "utf8",
|
|
40436
40808
|
timeout: 5e3,
|
|
40437
40809
|
maxBuffer: 16 * 1024 * 1024,
|
|
40438
40810
|
stdio: ["pipe", "pipe", "pipe"]
|
|
40439
40811
|
});
|
|
40440
40812
|
}
|
|
40441
|
-
function readSmartFolders(dbPath2 =
|
|
40813
|
+
function readSmartFolders(dbPath2 = NOTES_DB_PATH3) {
|
|
40442
40814
|
if (!fs2.existsSync(dbPath2)) return { folders: null, error: "no_fda", message: FDA_MESSAGE };
|
|
40443
40815
|
try {
|
|
40444
40816
|
const columns = new Set(
|
|
40445
|
-
|
|
40817
|
+
runSqlite2(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").trim().split("\n")
|
|
40446
40818
|
);
|
|
40447
|
-
const missing =
|
|
40819
|
+
const missing = REQUIRED_COLUMNS2.filter((column) => !columns.has(column));
|
|
40448
40820
|
if (missing.length) {
|
|
40449
40821
|
return {
|
|
40450
40822
|
folders: null,
|
|
@@ -40452,7 +40824,7 @@ function readSmartFolders(dbPath2 = NOTES_DB_PATH2) {
|
|
|
40452
40824
|
message: `This Notes database does not have the columns smart folders need (${missing.join(", ")}).`
|
|
40453
40825
|
};
|
|
40454
40826
|
}
|
|
40455
|
-
return { folders: buildSmartFolders(
|
|
40827
|
+
return { folders: buildSmartFolders(runSqlite2(dbPath2, SMART_FOLDERS_SQL)) };
|
|
40456
40828
|
} catch (error2) {
|
|
40457
40829
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
40458
40830
|
if (message.includes("authorization denied") || message.includes("unable to open database")) {
|
|
@@ -40474,11 +40846,11 @@ import {
|
|
|
40474
40846
|
rmSync,
|
|
40475
40847
|
statSync
|
|
40476
40848
|
} from "fs";
|
|
40477
|
-
import { dirname, isAbsolute, join as
|
|
40478
|
-
import { homedir as
|
|
40849
|
+
import { dirname, isAbsolute, join as join5, relative, resolve, sep } from "path";
|
|
40850
|
+
import { homedir as homedir5, tmpdir } from "os";
|
|
40479
40851
|
function allowedSaveRoots() {
|
|
40480
40852
|
return [
|
|
40481
|
-
resolve(
|
|
40853
|
+
resolve(homedir5()),
|
|
40482
40854
|
resolve(tmpdir()),
|
|
40483
40855
|
"/Volumes",
|
|
40484
40856
|
"/private/var/folders",
|
|
@@ -40552,7 +40924,7 @@ function assertSafeSavePath(p, roots = allowedSaveRoots()) {
|
|
|
40552
40924
|
if (suffix.split(sep).includes("..")) {
|
|
40553
40925
|
throw new Error(`Refusing to write outside allowed locations (home, temp, /Volumes): "${abs}"`);
|
|
40554
40926
|
}
|
|
40555
|
-
const canonicalDest = suffix ?
|
|
40927
|
+
const canonicalDest = suffix ? join5(canonicalAncestor, suffix) : canonicalAncestor;
|
|
40556
40928
|
const allowed = canonicalRoots(roots);
|
|
40557
40929
|
if (!isWithinRoots(canonicalAncestor, allowed) || !isWithinRoots(canonicalDest, allowed)) {
|
|
40558
40930
|
throw new Error(
|
|
@@ -40602,8 +40974,8 @@ function cleanupTempDir(dir) {
|
|
|
40602
40974
|
// src/services/appleNotesManager.ts
|
|
40603
40975
|
var import_turndown = __toESM(require_turndown_cjs(), 1);
|
|
40604
40976
|
import { existsSync as existsSync4 } from "fs";
|
|
40605
|
-
import { homedir as
|
|
40606
|
-
import { join as
|
|
40977
|
+
import { homedir as homedir6 } from "os";
|
|
40978
|
+
import { join as join6 } from "path";
|
|
40607
40979
|
var FIELD_SEP = "";
|
|
40608
40980
|
var RECORD_SEP = "";
|
|
40609
40981
|
var AS_FIELD_SEP = "(character id 31)";
|
|
@@ -40831,7 +41203,7 @@ function getNoteLinkFromDB(coreDataId2) {
|
|
|
40831
41203
|
const match = coreDataId2.match(/\/p(\d+)$/);
|
|
40832
41204
|
if (!match) return null;
|
|
40833
41205
|
const pk = parseInt(match[1], 10);
|
|
40834
|
-
const dbPath2 =
|
|
41206
|
+
const dbPath2 = join6(homedir6(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
|
|
40835
41207
|
if (!existsSync4(dbPath2)) return null;
|
|
40836
41208
|
try {
|
|
40837
41209
|
const { DatabaseSync } = __require("node:sqlite");
|
|
@@ -43140,18 +43512,31 @@ var AppleNotesManager = class {
|
|
|
43140
43512
|
}
|
|
43141
43513
|
return markdown;
|
|
43142
43514
|
}
|
|
43515
|
+
/**
|
|
43516
|
+
* Reads the transcripts Notes has stored for a note's top-level audio
|
|
43517
|
+
* recordings, one entry per attachment in body order. Read-only: queries the
|
|
43518
|
+
* NoteStore database with `sqlite3 -readonly` and needs Full Disk Access.
|
|
43519
|
+
*
|
|
43520
|
+
* @param id - CoreData URL identifier for the note
|
|
43521
|
+
* @param options - word-level segment inclusion and cap
|
|
43522
|
+
* @throws AudioTranscriptError for an invalid id, a missing or locked note,
|
|
43523
|
+
* missing Full Disk Access, or a database read failure
|
|
43524
|
+
*/
|
|
43525
|
+
getAudioTranscripts(id2, options = {}) {
|
|
43526
|
+
return readAudioTranscripts(id2, options);
|
|
43527
|
+
}
|
|
43143
43528
|
};
|
|
43144
43529
|
|
|
43145
43530
|
// src/utils/syncDetection.ts
|
|
43146
|
-
import { execFileSync as
|
|
43531
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
43147
43532
|
import * as fs3 from "fs";
|
|
43148
43533
|
import * as path3 from "path";
|
|
43149
43534
|
import * as os3 from "os";
|
|
43150
|
-
var
|
|
43535
|
+
var NOTES_DB_PATH4 = path3.join(
|
|
43151
43536
|
os3.homedir(),
|
|
43152
43537
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
43153
43538
|
);
|
|
43154
|
-
var WAL_PATH = `${
|
|
43539
|
+
var WAL_PATH = `${NOTES_DB_PATH4}-wal`;
|
|
43155
43540
|
var RECENT_ACTIVITY_THRESHOLD_SECONDS = 5;
|
|
43156
43541
|
var SYNC_STATUS_CACHE_TTL_MS = 2e3;
|
|
43157
43542
|
var cachedSyncStatus = null;
|
|
@@ -43167,7 +43552,7 @@ function getSyncStatus(useCache = true) {
|
|
|
43167
43552
|
recentActivity: false
|
|
43168
43553
|
};
|
|
43169
43554
|
try {
|
|
43170
|
-
if (!fs3.existsSync(
|
|
43555
|
+
if (!fs3.existsSync(NOTES_DB_PATH4)) {
|
|
43171
43556
|
status.error = "Notes database not found";
|
|
43172
43557
|
cachedSyncStatus = status;
|
|
43173
43558
|
cacheTimestamp = Date.now();
|
|
@@ -43188,9 +43573,9 @@ function getSyncStatus(useCache = true) {
|
|
|
43188
43573
|
WHERE object.ZCLOUDSTATE = state.Z_PK
|
|
43189
43574
|
);
|
|
43190
43575
|
`;
|
|
43191
|
-
const result =
|
|
43576
|
+
const result = execFileSync6(
|
|
43192
43577
|
"sqlite3",
|
|
43193
|
-
["-readonly",
|
|
43578
|
+
["-readonly", NOTES_DB_PATH4, query.replace(/\n/g, " ")],
|
|
43194
43579
|
{
|
|
43195
43580
|
encoding: "utf8",
|
|
43196
43581
|
timeout: 5e3,
|
|
@@ -43247,11 +43632,11 @@ function withSyncAwarenessSync(operation, fn) {
|
|
|
43247
43632
|
}
|
|
43248
43633
|
|
|
43249
43634
|
// src/utils/noteMetadata.ts
|
|
43250
|
-
import { execFileSync as
|
|
43635
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
43251
43636
|
import * as fs4 from "fs";
|
|
43252
43637
|
import * as path4 from "path";
|
|
43253
43638
|
import * as os4 from "os";
|
|
43254
|
-
var
|
|
43639
|
+
var NOTES_DB_PATH5 = path4.join(
|
|
43255
43640
|
os4.homedir(),
|
|
43256
43641
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
43257
43642
|
);
|
|
@@ -43267,15 +43652,15 @@ var COLUMN_MAP = [
|
|
|
43267
43652
|
{ key: "widgetSnippet", column: "ZWIDGETSNIPPET", type: "text" },
|
|
43268
43653
|
{ key: "smartFolderQuery", column: "ZSMARTFOLDERQUERYJSON", type: "text" }
|
|
43269
43654
|
];
|
|
43270
|
-
function
|
|
43271
|
-
return
|
|
43655
|
+
function runSqlite3(query) {
|
|
43656
|
+
return execFileSync7("sqlite3", ["-readonly", NOTES_DB_PATH5, query], {
|
|
43272
43657
|
encoding: "utf8",
|
|
43273
43658
|
timeout: 5e3,
|
|
43274
43659
|
stdio: ["pipe", "pipe", "pipe"]
|
|
43275
43660
|
}).trim();
|
|
43276
43661
|
}
|
|
43277
43662
|
function presentColumns() {
|
|
43278
|
-
const out =
|
|
43663
|
+
const out = runSqlite3("PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);");
|
|
43279
43664
|
const cols = /* @__PURE__ */ new Set();
|
|
43280
43665
|
for (const line of out.split("\n")) {
|
|
43281
43666
|
const name = line.split("|")[1];
|
|
@@ -43293,7 +43678,7 @@ function getNoteMetadata(noteId3) {
|
|
|
43293
43678
|
};
|
|
43294
43679
|
}
|
|
43295
43680
|
const pk = pkMatch[1];
|
|
43296
|
-
if (!fs4.existsSync(
|
|
43681
|
+
if (!fs4.existsSync(NOTES_DB_PATH5)) {
|
|
43297
43682
|
return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
|
|
43298
43683
|
}
|
|
43299
43684
|
try {
|
|
@@ -43303,7 +43688,7 @@ function getNoteMetadata(noteId3) {
|
|
|
43303
43688
|
return { metadata: {} };
|
|
43304
43689
|
}
|
|
43305
43690
|
const pairs = selected.map((c) => `'${c.key}', ${c.column}`).join(", ");
|
|
43306
|
-
const row =
|
|
43691
|
+
const row = runSqlite3(
|
|
43307
43692
|
`SELECT json_object(${pairs}) FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = ${pk};`
|
|
43308
43693
|
);
|
|
43309
43694
|
if (!row) {
|
|
@@ -43336,11 +43721,11 @@ function getNoteMetadata(noteId3) {
|
|
|
43336
43721
|
}
|
|
43337
43722
|
|
|
43338
43723
|
// src/utils/noteIdentifiers.ts
|
|
43339
|
-
import { execFileSync as
|
|
43724
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
43340
43725
|
import * as fs5 from "fs";
|
|
43341
43726
|
import * as os5 from "os";
|
|
43342
43727
|
import * as path5 from "path";
|
|
43343
|
-
var
|
|
43728
|
+
var NOTES_DB_PATH6 = path5.join(
|
|
43344
43729
|
os5.homedir(),
|
|
43345
43730
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
43346
43731
|
);
|
|
@@ -43418,7 +43803,7 @@ function runJsonQuery(sql, dbPath2) {
|
|
|
43418
43803
|
if (!fs5.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
43419
43804
|
let out;
|
|
43420
43805
|
try {
|
|
43421
|
-
out =
|
|
43806
|
+
out = execFileSync8("sqlite3", ["-readonly", dbPath2, sql], {
|
|
43422
43807
|
encoding: "utf8",
|
|
43423
43808
|
timeout: 5e3,
|
|
43424
43809
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -43438,7 +43823,7 @@ function runJsonQuery(sql, dbPath2) {
|
|
|
43438
43823
|
function coreDataId(store, entity2, pk) {
|
|
43439
43824
|
return `x-coredata://${store}/${entity2}/p${pk}`;
|
|
43440
43825
|
}
|
|
43441
|
-
function resolveIdentifiers(values, entity2, dbPath2 =
|
|
43826
|
+
function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH6) {
|
|
43442
43827
|
const resolved = /* @__PURE__ */ new Map();
|
|
43443
43828
|
const keys = [];
|
|
43444
43829
|
const uuids = [];
|
|
@@ -43484,7 +43869,7 @@ function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH5) {
|
|
|
43484
43869
|
return resolved;
|
|
43485
43870
|
}
|
|
43486
43871
|
var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
|
|
43487
|
-
function lookupStableIdentifiers(ids, entity2, dbPath2 =
|
|
43872
|
+
function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH6) {
|
|
43488
43873
|
const result = /* @__PURE__ */ new Map();
|
|
43489
43874
|
const wanted = /* @__PURE__ */ new Map();
|
|
43490
43875
|
for (const id2 of ids) {
|
|
@@ -43520,7 +43905,7 @@ function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH5) {
|
|
|
43520
43905
|
}
|
|
43521
43906
|
return result;
|
|
43522
43907
|
}
|
|
43523
|
-
function withStableIdentifiers(items, entity2, dbPath2 =
|
|
43908
|
+
function withStableIdentifiers(items, entity2, dbPath2 = NOTES_DB_PATH6) {
|
|
43524
43909
|
const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
|
|
43525
43910
|
if (ids.length === 0) return items;
|
|
43526
43911
|
const found = lookupStableIdentifiers(ids, entity2, dbPath2);
|
|
@@ -44108,12 +44493,12 @@ function positiveTextTerms(node, negated = false) {
|
|
|
44108
44493
|
}
|
|
44109
44494
|
|
|
44110
44495
|
// src/utils/noteQueryStore.ts
|
|
44111
|
-
import { execFileSync as
|
|
44496
|
+
import { execFileSync as execFileSync9 } from "child_process";
|
|
44112
44497
|
import * as fs6 from "fs";
|
|
44113
44498
|
import * as os6 from "os";
|
|
44114
44499
|
import * as path6 from "path";
|
|
44115
|
-
import { gunzipSync as
|
|
44116
|
-
var
|
|
44500
|
+
import { gunzipSync as gunzipSync5 } from "zlib";
|
|
44501
|
+
var NOTES_DB_PATH7 = path6.join(
|
|
44117
44502
|
os6.homedir(),
|
|
44118
44503
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
44119
44504
|
);
|
|
@@ -44201,13 +44586,13 @@ function countWords(text) {
|
|
|
44201
44586
|
}
|
|
44202
44587
|
return count;
|
|
44203
44588
|
}
|
|
44204
|
-
var
|
|
44589
|
+
var REQUIRED_COLUMNS3 = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
|
|
44205
44590
|
function col(available, alias, name) {
|
|
44206
44591
|
return available.has(name) ? `${alias}.${name}` : "NULL";
|
|
44207
44592
|
}
|
|
44208
44593
|
var entity = (name) => `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME='${name}')`;
|
|
44209
44594
|
function buildScanSql(available, options) {
|
|
44210
|
-
const missing =
|
|
44595
|
+
const missing = REQUIRED_COLUMNS3.filter((c) => !available.has(c));
|
|
44211
44596
|
if (missing.length) {
|
|
44212
44597
|
throw new NoteQueryStoreError(
|
|
44213
44598
|
`This macOS version's Notes database lacks columns query-notes needs (${missing.join(", ")}).`,
|
|
@@ -44248,8 +44633,8 @@ function buildScanSql(available, options) {
|
|
|
44248
44633
|
"COMMIT;"
|
|
44249
44634
|
].join(" ");
|
|
44250
44635
|
}
|
|
44251
|
-
function
|
|
44252
|
-
return
|
|
44636
|
+
function runSqlite4(dbPath2, query) {
|
|
44637
|
+
return execFileSync9("sqlite3", ["-readonly", dbPath2, query], {
|
|
44253
44638
|
encoding: "utf8",
|
|
44254
44639
|
timeout: 3e4,
|
|
44255
44640
|
maxBuffer: 512 * 1024 * 1024,
|
|
@@ -44258,7 +44643,7 @@ function runSqlite3(dbPath2, query) {
|
|
|
44258
44643
|
}
|
|
44259
44644
|
function presentColumns2(dbPath2) {
|
|
44260
44645
|
const cols = /* @__PURE__ */ new Set();
|
|
44261
|
-
for (const line of
|
|
44646
|
+
for (const line of runSqlite4(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n")) {
|
|
44262
44647
|
const name = line.split("|")[1];
|
|
44263
44648
|
if (name) cols.add(name);
|
|
44264
44649
|
}
|
|
@@ -44313,12 +44698,12 @@ function queryNotes(expression, options = {}) {
|
|
|
44313
44698
|
const includeDeleted = options.includeDeleted ?? false;
|
|
44314
44699
|
const withBodies = needsContent(ast);
|
|
44315
44700
|
const withTags = needsTags(ast);
|
|
44316
|
-
const dbPath2 = options.dbPath ??
|
|
44701
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
|
|
44317
44702
|
if (!fs6.existsSync(dbPath2)) throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
|
|
44318
44703
|
let output;
|
|
44319
44704
|
try {
|
|
44320
44705
|
const available = presentColumns2(dbPath2);
|
|
44321
|
-
output =
|
|
44706
|
+
output = runSqlite4(
|
|
44322
44707
|
dbPath2,
|
|
44323
44708
|
buildScanSql(available, { scanLimit, includeDeleted, withBodies, withTags })
|
|
44324
44709
|
);
|
|
@@ -44363,7 +44748,7 @@ function queryNotes(expression, options = {}) {
|
|
|
44363
44748
|
const locked = Boolean(row.locked);
|
|
44364
44749
|
let decoded;
|
|
44365
44750
|
let content;
|
|
44366
|
-
const
|
|
44751
|
+
const decode2 = () => {
|
|
44367
44752
|
if (decoded !== void 0) return decoded;
|
|
44368
44753
|
decoded = null;
|
|
44369
44754
|
if (locked) return decoded;
|
|
@@ -44374,7 +44759,7 @@ function queryNotes(expression, options = {}) {
|
|
|
44374
44759
|
try {
|
|
44375
44760
|
decoded = decodeNoteBody(
|
|
44376
44761
|
new Uint8Array(
|
|
44377
|
-
|
|
44762
|
+
gunzipSync5(Buffer.from(row.data, "hex"), { maxOutputLength: 32 * 1024 * 1024 })
|
|
44378
44763
|
)
|
|
44379
44764
|
);
|
|
44380
44765
|
} catch {
|
|
@@ -44394,7 +44779,7 @@ function queryNotes(expression, options = {}) {
|
|
|
44394
44779
|
modified: coreDataMs(row.modified),
|
|
44395
44780
|
content: () => {
|
|
44396
44781
|
if (content !== void 0) return content;
|
|
44397
|
-
const body2 =
|
|
44782
|
+
const body2 = decode2();
|
|
44398
44783
|
if (!body2) return content = null;
|
|
44399
44784
|
const text = body2.text;
|
|
44400
44785
|
const firstBreak = text.indexOf("\n");
|
|
@@ -44419,7 +44804,7 @@ function queryNotes(expression, options = {}) {
|
|
|
44419
44804
|
if (!evaluateNoteQuery(ast, note)) continue;
|
|
44420
44805
|
matched++;
|
|
44421
44806
|
if (hits.length >= limit) continue;
|
|
44422
|
-
const body = locked ? null :
|
|
44807
|
+
const body = locked ? null : decode2();
|
|
44423
44808
|
hits.push({
|
|
44424
44809
|
id: `x-coredata://${uuid2}/ICNote/p${row.pk}`,
|
|
44425
44810
|
title: row.title ?? "",
|
|
@@ -44449,10 +44834,10 @@ function queryNotes(expression, options = {}) {
|
|
|
44449
44834
|
import { spawnSync } from "child_process";
|
|
44450
44835
|
|
|
44451
44836
|
// src/services/nativeTags.ts
|
|
44452
|
-
import { execFileSync as
|
|
44837
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
44453
44838
|
import { mkdtempSync as mkdtempSync2, writeFileSync, rmSync as rmSync2 } from "node:fs";
|
|
44454
44839
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
44455
|
-
import { join as
|
|
44840
|
+
import { join as join11 } from "node:path";
|
|
44456
44841
|
|
|
44457
44842
|
// src/services/shortcutConsent.ts
|
|
44458
44843
|
function shortcutConsentHint(shortcut) {
|
|
@@ -44528,7 +44913,7 @@ function addNativeTags(request, deps) {
|
|
|
44528
44913
|
};
|
|
44529
44914
|
}
|
|
44530
44915
|
function listInstalledShortcuts() {
|
|
44531
|
-
return
|
|
44916
|
+
return execFileSync10("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
|
|
44532
44917
|
encoding: "utf8",
|
|
44533
44918
|
timeout: 15e3,
|
|
44534
44919
|
maxBuffer: 1024 * 1024,
|
|
@@ -44554,11 +44939,11 @@ function runNativeTagsShortcut(input) {
|
|
|
44554
44939
|
const status = nativeTagsStatus();
|
|
44555
44940
|
if (!status.installed)
|
|
44556
44941
|
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
44557
|
-
const directory = mkdtempSync2(
|
|
44942
|
+
const directory = mkdtempSync2(join11(tmpdir2(), "apple-notes-native-tags-"));
|
|
44558
44943
|
try {
|
|
44559
|
-
const path7 =
|
|
44944
|
+
const path7 = join11(directory, "request.json");
|
|
44560
44945
|
writeFileSync(path7, JSON.stringify(input), { mode: 384 });
|
|
44561
|
-
|
|
44946
|
+
execFileSync10("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path7], {
|
|
44562
44947
|
encoding: "utf8",
|
|
44563
44948
|
timeout: 6e4,
|
|
44564
44949
|
maxBuffer: 1024 * 1024,
|
|
@@ -44577,10 +44962,10 @@ function runNativeTagsShortcut(input) {
|
|
|
44577
44962
|
}
|
|
44578
44963
|
|
|
44579
44964
|
// src/services/backgroundNotes.ts
|
|
44580
|
-
import { execFileSync as
|
|
44965
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
44581
44966
|
import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
|
|
44582
44967
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
44583
|
-
import { join as
|
|
44968
|
+
import { join as join12 } from "node:path";
|
|
44584
44969
|
|
|
44585
44970
|
// src/utils/appendMarkdown.ts
|
|
44586
44971
|
var escape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -44769,9 +45154,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
44769
45154
|
throw new Error(
|
|
44770
45155
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
44771
45156
|
);
|
|
44772
|
-
const directory = mkdtempSync3(
|
|
45157
|
+
const directory = mkdtempSync3(join12(tmpdir3(), "apple-notes-background-"));
|
|
44773
45158
|
try {
|
|
44774
|
-
const file =
|
|
45159
|
+
const file = join12(directory, "request.json");
|
|
44775
45160
|
writeFileSync2(
|
|
44776
45161
|
file,
|
|
44777
45162
|
JSON.stringify({
|
|
@@ -44791,7 +45176,7 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
44791
45176
|
{ mode: 384 }
|
|
44792
45177
|
);
|
|
44793
45178
|
try {
|
|
44794
|
-
|
|
45179
|
+
execFileSync11("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
|
|
44795
45180
|
encoding: "utf8",
|
|
44796
45181
|
timeout: 6e4,
|
|
44797
45182
|
maxBuffer: 1024 * 1024,
|
|
@@ -45083,7 +45468,7 @@ ${request.content}`
|
|
|
45083
45468
|
}
|
|
45084
45469
|
|
|
45085
45470
|
// src/services/capabilityMatrix.ts
|
|
45086
|
-
import { execFileSync as
|
|
45471
|
+
import { execFileSync as execFileSync12 } from "node:child_process";
|
|
45087
45472
|
import { release } from "node:os";
|
|
45088
45473
|
var MACOS_SHORTCUTS_CLI = "12.0";
|
|
45089
45474
|
var MACOS_MARKDOWN_IMPORT = "26.0";
|
|
@@ -45290,7 +45675,7 @@ function evaluateFeatures(env, features = FEATURES) {
|
|
|
45290
45675
|
function readMacOSVersion() {
|
|
45291
45676
|
if (process.platform !== "darwin") return null;
|
|
45292
45677
|
try {
|
|
45293
|
-
const out =
|
|
45678
|
+
const out = execFileSync12("/usr/bin/sw_vers", ["-productVersion"], {
|
|
45294
45679
|
encoding: "utf8",
|
|
45295
45680
|
timeout: 3e3,
|
|
45296
45681
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -45444,12 +45829,12 @@ function formatDoctorReport(r) {
|
|
|
45444
45829
|
|
|
45445
45830
|
// src/services/fileConfig.ts
|
|
45446
45831
|
import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
|
|
45447
|
-
import { join as
|
|
45448
|
-
import { homedir as
|
|
45832
|
+
import { join as join13 } from "path";
|
|
45833
|
+
import { homedir as homedir11 } from "os";
|
|
45449
45834
|
function fileConfigPath(env = process.env) {
|
|
45450
45835
|
const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
|
|
45451
45836
|
if (override && override.trim()) return override.trim();
|
|
45452
|
-
return
|
|
45837
|
+
return join13(homedir11(), "Library", "Application Support", "apple-notes-mcp", "config.json");
|
|
45453
45838
|
}
|
|
45454
45839
|
function loadFileConfig(env = process.env, path7 = fileConfigPath(env)) {
|
|
45455
45840
|
const applied = [];
|
|
@@ -45752,11 +46137,11 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
|
|
|
45752
46137
|
}
|
|
45753
46138
|
|
|
45754
46139
|
// src/utils/noteBlocks.ts
|
|
45755
|
-
import { execFileSync as
|
|
46140
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
45756
46141
|
import { existsSync as existsSync10 } from "node:fs";
|
|
45757
|
-
import { homedir as
|
|
45758
|
-
import { join as
|
|
45759
|
-
import { gunzipSync as
|
|
46142
|
+
import { homedir as homedir12 } from "node:os";
|
|
46143
|
+
import { join as join14 } from "node:path";
|
|
46144
|
+
import { gunzipSync as gunzipSync6 } from "node:zlib";
|
|
45760
46145
|
var NoteBlocksError = class extends Error {
|
|
45761
46146
|
code;
|
|
45762
46147
|
constructor(code, message) {
|
|
@@ -45805,7 +46190,7 @@ var stringOf = (fields, n) => {
|
|
|
45805
46190
|
const b = bytesOf(fields, n);
|
|
45806
46191
|
return b ? utf8.decode(b) : void 0;
|
|
45807
46192
|
};
|
|
45808
|
-
var
|
|
46193
|
+
var sub3 = (fields, n) => {
|
|
45809
46194
|
const b = bytesOf(fields, n);
|
|
45810
46195
|
return b ? decodeWireFields(b) : void 0;
|
|
45811
46196
|
};
|
|
@@ -45838,7 +46223,7 @@ function decodeParagraph(fields, tally) {
|
|
|
45838
46223
|
blockQuote: (varintOf(fields, 8) ?? 0) !== 0
|
|
45839
46224
|
};
|
|
45840
46225
|
if (attrs.styleType === -1) attrs.styleType = null;
|
|
45841
|
-
const checklist =
|
|
46226
|
+
const checklist = sub3(fields, 5);
|
|
45842
46227
|
const checklistId = checklist && bytesOf(checklist, 1);
|
|
45843
46228
|
if (attrs.styleType === 103 && checklistId)
|
|
45844
46229
|
attrs.checklist = { id: hex2(checklistId), done: varintOf(checklist, 2) === 1 };
|
|
@@ -45848,7 +46233,7 @@ function decodeParagraph(fields, tally) {
|
|
|
45848
46233
|
}
|
|
45849
46234
|
function decodeInline(fields) {
|
|
45850
46235
|
const inline = {};
|
|
45851
|
-
const font =
|
|
46236
|
+
const font = sub3(fields, 3);
|
|
45852
46237
|
const hints = font ? varintOf(font, 3) ?? 0 : 0;
|
|
45853
46238
|
const weight = varintOf(fields, 5) ?? 0;
|
|
45854
46239
|
if (weight === 1 || weight === 3 || hints & 1) inline.bold = true;
|
|
@@ -45858,7 +46243,7 @@ function decodeInline(fields) {
|
|
|
45858
46243
|
const baseline = varintOf(fields, 8) ?? 0;
|
|
45859
46244
|
if (baseline > 0) inline.superscript = true;
|
|
45860
46245
|
if (baseline < 0) inline.subscript = true;
|
|
45861
|
-
const color =
|
|
46246
|
+
const color = sub3(fields, 10);
|
|
45862
46247
|
const colorValue = color && colorOf(color);
|
|
45863
46248
|
if (colorValue) inline.color = colorValue;
|
|
45864
46249
|
const emphasis = varintOf(fields, 14);
|
|
@@ -45880,7 +46265,7 @@ function decodeInline(fields) {
|
|
|
45880
46265
|
...size !== void 0 && Number.isFinite(size) ? { size } : {}
|
|
45881
46266
|
};
|
|
45882
46267
|
}
|
|
45883
|
-
const attachment =
|
|
46268
|
+
const attachment = sub3(fields, 12);
|
|
45884
46269
|
const attachmentId = attachment && stringOf(attachment, 1);
|
|
45885
46270
|
if (attachmentId)
|
|
45886
46271
|
inline.attachment = { id: attachmentId, uti: stringOf(attachment, 2) || "unknown" };
|
|
@@ -45895,8 +46280,8 @@ function wrap(error2) {
|
|
|
45895
46280
|
function decodeNoteBlocks(data) {
|
|
45896
46281
|
let note;
|
|
45897
46282
|
try {
|
|
45898
|
-
const document =
|
|
45899
|
-
note = document &&
|
|
46283
|
+
const document = sub3(decodeWireFields(data), 2);
|
|
46284
|
+
note = document && sub3(document, 3);
|
|
45900
46285
|
} catch (error2) {
|
|
45901
46286
|
wrap(error2);
|
|
45902
46287
|
}
|
|
@@ -45921,7 +46306,7 @@ function decodeNoteBlocks(data) {
|
|
|
45921
46306
|
runs.push({
|
|
45922
46307
|
start: position,
|
|
45923
46308
|
length,
|
|
45924
|
-
paragraph: decodeParagraph(
|
|
46309
|
+
paragraph: decodeParagraph(sub3(fields, 2), paragraphTally),
|
|
45925
46310
|
inline: decodeInline(fields)
|
|
45926
46311
|
});
|
|
45927
46312
|
position += length;
|
|
@@ -46044,7 +46429,7 @@ function summarize(blocks, attachments) {
|
|
|
46044
46429
|
function decodeCompressedNoteBlocks(compressed) {
|
|
46045
46430
|
let data;
|
|
46046
46431
|
try {
|
|
46047
|
-
data =
|
|
46432
|
+
data = gunzipSync6(compressed, { maxOutputLength: 32 * 1024 * 1024 });
|
|
46048
46433
|
} catch (error2) {
|
|
46049
46434
|
throw new NoteBlocksError(
|
|
46050
46435
|
"decompress-failed",
|
|
@@ -46098,11 +46483,11 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
|
|
|
46098
46483
|
undecodedFields: doc.undecodedFields
|
|
46099
46484
|
};
|
|
46100
46485
|
}
|
|
46101
|
-
var
|
|
46102
|
-
|
|
46486
|
+
var NOTES_DB_PATH8 = join14(
|
|
46487
|
+
homedir12(),
|
|
46103
46488
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46104
46489
|
);
|
|
46105
|
-
function readNoteBlocks(id2, { dbPath: dbPath2 =
|
|
46490
|
+
function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
|
|
46106
46491
|
const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]{1,18})$/i.exec(id2)?.[1];
|
|
46107
46492
|
if (!pk)
|
|
46108
46493
|
throw new NoteBlocksError(
|
|
@@ -46114,7 +46499,7 @@ function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH7 } = {}) {
|
|
|
46114
46499
|
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));";
|
|
46115
46500
|
let output;
|
|
46116
46501
|
try {
|
|
46117
|
-
output =
|
|
46502
|
+
output = execFileSync13(
|
|
46118
46503
|
"/usr/bin/sqlite3",
|
|
46119
46504
|
["-readonly", "-cmd", ".parameter init", "-cmd", `.parameter set @pk ${pk}`, dbPath2, sql],
|
|
46120
46505
|
{
|
|
@@ -46275,7 +46660,7 @@ import {
|
|
|
46275
46660
|
writeFileSync as writeFileSync3
|
|
46276
46661
|
} from "node:fs";
|
|
46277
46662
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
46278
|
-
import { basename, isAbsolute as isAbsolute2, join as
|
|
46663
|
+
import { basename, isAbsolute as isAbsolute2, join as join15 } from "node:path";
|
|
46279
46664
|
var noteId = exactIdInput(
|
|
46280
46665
|
"ICNote",
|
|
46281
46666
|
/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i,
|
|
@@ -46389,8 +46774,8 @@ function registerDirectOperations(server2, manager) {
|
|
|
46389
46774
|
if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
|
|
46390
46775
|
const bytes = localAttachment(path7);
|
|
46391
46776
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
46392
|
-
const directory = mkdtempSync4(
|
|
46393
|
-
const temporaryFile =
|
|
46777
|
+
const directory = mkdtempSync4(join15(tmpdir4(), "notes-attachment-add-"));
|
|
46778
|
+
const temporaryFile = join15(directory, basename(path7));
|
|
46394
46779
|
try {
|
|
46395
46780
|
writeFileSync3(temporaryFile, bytes, { mode: 384 });
|
|
46396
46781
|
if (readSnapshot(manager, id2).hash !== before.hash)
|
|
@@ -49098,6 +49483,44 @@ ${summary}`,
|
|
|
49098
49483
|
);
|
|
49099
49484
|
}, "Error reading checklist state")
|
|
49100
49485
|
);
|
|
49486
|
+
registerTool(
|
|
49487
|
+
"get-audio-transcripts",
|
|
49488
|
+
{
|
|
49489
|
+
description: "Use when: reading the transcript (and summary, if any) that Notes already computed for the audio recordings in one note, by id.\nReturns: one entry per top-level audio attachment in body order, with attachmentId, durationSeconds, status (ok, none when no transcript is stored, or undecodable with a reason), joined transcript text, wordCount, speakers and summary when stored, and optional word-level segments.\nDo not use when: you need the audio file itself (save-attachment) or the note body (get-note-content). This tool does not transcribe; it only reads what Notes stored.\nSafety: read-only; requires Full Disk Access and reads the NoteStore database. Password-protected notes are refused. Large responses drop segments first, then shorten text, to stay under APPLE_NOTES_MCP_EXPORT_MAX_BYTES.",
|
|
49490
|
+
inputSchema: {
|
|
49491
|
+
id: noteIdInput,
|
|
49492
|
+
includeSegments: external_exports.boolean().optional().describe(
|
|
49493
|
+
"Also return word-level segments (text, start and duration in seconds, speaker). Default false."
|
|
49494
|
+
),
|
|
49495
|
+
maxSegments: external_exports.number().int().min(1).max(MAX_SEGMENTS_LIMIT).optional().describe(
|
|
49496
|
+
`Cap on segments returned per attachment when includeSegments is true (default ${DEFAULT_MAX_SEGMENTS}).`
|
|
49497
|
+
)
|
|
49498
|
+
},
|
|
49499
|
+
outputSchema: {
|
|
49500
|
+
id: external_exports.string().optional(),
|
|
49501
|
+
attachments: external_exports.array(external_exports.object({}).passthrough()).optional(),
|
|
49502
|
+
bodyOrder: external_exports.boolean().optional(),
|
|
49503
|
+
truncated: external_exports.boolean().optional()
|
|
49504
|
+
},
|
|
49505
|
+
annotations: { readOnlyHint: true }
|
|
49506
|
+
},
|
|
49507
|
+
withErrorHandling(({ id: id2, includeSegments, maxSegments }) => {
|
|
49508
|
+
let result;
|
|
49509
|
+
try {
|
|
49510
|
+
result = notesManager.getAudioTranscripts(id2, { includeSegments, maxSegments });
|
|
49511
|
+
} catch (error2) {
|
|
49512
|
+
if (error2 instanceof AudioTranscriptError) return errorResponse(error2.message);
|
|
49513
|
+
throw error2;
|
|
49514
|
+
}
|
|
49515
|
+
const toResponse = (r) => successResponse(formatTranscriptsText(r), { ...r });
|
|
49516
|
+
const fitted = fitTranscriptsToBudget(
|
|
49517
|
+
result,
|
|
49518
|
+
exportMaxResponseBytes(),
|
|
49519
|
+
(r) => Buffer.byteLength(JSON.stringify(toResponse(r)))
|
|
49520
|
+
);
|
|
49521
|
+
return toResponse(fitted);
|
|
49522
|
+
}, "Error reading audio transcripts")
|
|
49523
|
+
);
|
|
49101
49524
|
registerTool(
|
|
49102
49525
|
"get-note-metadata",
|
|
49103
49526
|
{
|
package/package.json
CHANGED