apple-notes-mcp 2.9.24 → 2.9.26
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 +29 -19
- package/build/index.js +797 -592
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -39088,6 +39088,10 @@ function isPermissionDenied(error2) {
|
|
|
39088
39088
|
if (!error2) return false;
|
|
39089
39089
|
return PERMISSION_DENIED_PATTERN.test(error2) || error2.includes(PERMISSION_DENIED_MESSAGE);
|
|
39090
39090
|
}
|
|
39091
|
+
var QUOTED = /"[^"\n]*"|“[^”\n]*”/g;
|
|
39092
|
+
function stripQuoted(text2) {
|
|
39093
|
+
return text2.replace(QUOTED, '""');
|
|
39094
|
+
}
|
|
39091
39095
|
var ERROR_MAPPINGS = [
|
|
39092
39096
|
// Permission errors
|
|
39093
39097
|
{
|
|
@@ -39158,12 +39162,13 @@ function parseErrorMessage(errorOutput) {
|
|
|
39158
39162
|
if (executionError) {
|
|
39159
39163
|
coreError = executionError[1].trim();
|
|
39160
39164
|
}
|
|
39161
|
-
if (isPermissionDenied(errorOutput)) {
|
|
39165
|
+
if (isPermissionDenied(stripQuoted(errorOutput))) {
|
|
39162
39166
|
return PERMISSION_DENIED_MESSAGE;
|
|
39163
39167
|
}
|
|
39168
|
+
const unquoted = stripQuoted(coreError);
|
|
39164
39169
|
for (const { pattern: pattern2, message } of ERROR_MAPPINGS) {
|
|
39165
39170
|
const match = coreError.match(pattern2);
|
|
39166
|
-
if (match) {
|
|
39171
|
+
if (match && (match.length > 1 || pattern2.test(unquoted))) {
|
|
39167
39172
|
let result = message;
|
|
39168
39173
|
for (let i = 1; i < match.length; i++) {
|
|
39169
39174
|
result = result.replace(`$${i}`, match[i] || "");
|
|
@@ -39179,7 +39184,10 @@ function parseErrorMessage(errorOutput) {
|
|
|
39179
39184
|
}
|
|
39180
39185
|
function executeAppleScript(script, options = {}) {
|
|
39181
39186
|
const timeoutMs = options.timeoutMs ?? callTimeoutMs() ?? envPositiveNumber("APPLE_NOTES_MCP_TIMEOUT_MS") ?? DEFAULT_TIMEOUT_MS;
|
|
39182
|
-
const maxRetries =
|
|
39187
|
+
const maxRetries = Math.max(
|
|
39188
|
+
1,
|
|
39189
|
+
options.maxRetries ?? envPositiveNumber("APPLE_NOTES_MCP_MAX_RETRIES") ?? DEFAULT_MAX_RETRIES
|
|
39190
|
+
);
|
|
39183
39191
|
const retryDelayMs = options.retryDelayMs ?? envPositiveNumber("APPLE_NOTES_MCP_RETRY_DELAY_MS") ?? DEFAULT_RETRY_DELAY_MS;
|
|
39184
39192
|
const maxBufferBytes = Math.min(options.maxBufferBytes ?? getMaxBuffer(), MAX_OUTPUT_BYTES);
|
|
39185
39193
|
if (!script || !script.trim()) {
|
|
@@ -39588,6 +39596,252 @@ function getChecklistItems(noteId3) {
|
|
|
39588
39596
|
return { items };
|
|
39589
39597
|
}
|
|
39590
39598
|
|
|
39599
|
+
// src/utils/noteIdentifiers.ts
|
|
39600
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
39601
|
+
import * as fs2 from "fs";
|
|
39602
|
+
import * as os2 from "os";
|
|
39603
|
+
import * as path2 from "path";
|
|
39604
|
+
var NOTES_DB_PATH2 = path2.join(
|
|
39605
|
+
os2.homedir(),
|
|
39606
|
+
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
39607
|
+
);
|
|
39608
|
+
var UUID_PATTERN = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;
|
|
39609
|
+
var NUMERIC_KEY_PATTERN = /^\d{1,18}$/;
|
|
39610
|
+
var NOTE_ID_MESSAGE = "A canonical Apple Note ID is required (x-coredata://.../ICNote/p...), or the note's Notes UUID or numeric key";
|
|
39611
|
+
function identifierForm(value) {
|
|
39612
|
+
if (UUID_PATTERN.test(value)) return "uuid";
|
|
39613
|
+
if (NUMERIC_KEY_PATTERN.test(value)) return "key";
|
|
39614
|
+
return "other";
|
|
39615
|
+
}
|
|
39616
|
+
function isAlternateIdentifier(value) {
|
|
39617
|
+
return identifierForm(value) !== "other";
|
|
39618
|
+
}
|
|
39619
|
+
var IdentifierResolutionError = class extends Error {
|
|
39620
|
+
constructor(code, message) {
|
|
39621
|
+
super(message);
|
|
39622
|
+
this.code = code;
|
|
39623
|
+
this.name = "IdentifierResolutionError";
|
|
39624
|
+
}
|
|
39625
|
+
code;
|
|
39626
|
+
};
|
|
39627
|
+
var ENTITY_LABEL = {
|
|
39628
|
+
ICNote: "note",
|
|
39629
|
+
ICFolder: "folder",
|
|
39630
|
+
ICAccount: "account"
|
|
39631
|
+
};
|
|
39632
|
+
var NO_FDA_MESSAGE = `Resolving a Notes UUID or numeric key reads the Notes database, which needs Full Disk Access for the Node binary running this server, or the terminal that launches it (System Settings > Privacy & Security > Full Disk Access, then fully quit and relaunch it). x-coredata ids from search-notes, list-notes, or list-folders work without it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
|
|
39633
|
+
function identifierFailureIn(message) {
|
|
39634
|
+
if (message.includes(NO_FDA_MESSAGE)) return "no_fda";
|
|
39635
|
+
if (/\bNo (?:note|folder|account) found for (?:numeric key|identifier) /.test(message))
|
|
39636
|
+
return "not_found";
|
|
39637
|
+
if (message.includes("Failed to read the Notes database.") || message.includes("The Notes database has no store UUID"))
|
|
39638
|
+
return "query_error";
|
|
39639
|
+
return null;
|
|
39640
|
+
}
|
|
39641
|
+
function canonicalKey(value) {
|
|
39642
|
+
return BigInt(value).toString();
|
|
39643
|
+
}
|
|
39644
|
+
function assertAll(values, pattern2, label) {
|
|
39645
|
+
for (const value of values) {
|
|
39646
|
+
if (!pattern2.test(value)) throw new Error(`Refusing to query with an invalid ${label}`);
|
|
39647
|
+
}
|
|
39648
|
+
}
|
|
39649
|
+
function entityClause(entity3) {
|
|
39650
|
+
return `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = '${entity3}')`;
|
|
39651
|
+
}
|
|
39652
|
+
function buildResolveSql(entity3, keys, uuids) {
|
|
39653
|
+
assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
|
|
39654
|
+
assertAll(uuids, UUID_PATTERN, "UUID");
|
|
39655
|
+
const matches = [];
|
|
39656
|
+
if (keys.length > 0) {
|
|
39657
|
+
matches.push(`o.Z_PK IN (${[...new Set(keys.map(canonicalKey))].join(", ")})`);
|
|
39658
|
+
}
|
|
39659
|
+
if (uuids.length > 0) {
|
|
39660
|
+
const variants = /* @__PURE__ */ new Set();
|
|
39661
|
+
for (const uuid2 of uuids) {
|
|
39662
|
+
variants.add(uuid2);
|
|
39663
|
+
variants.add(uuid2.toUpperCase());
|
|
39664
|
+
variants.add(uuid2.toLowerCase());
|
|
39665
|
+
}
|
|
39666
|
+
matches.push(`o.ZIDENTIFIER IN (${[...variants].map((v) => `'${v}'`).join(", ")})`);
|
|
39667
|
+
}
|
|
39668
|
+
const where = matches.length > 0 ? matches.join(" OR ") : "0";
|
|
39669
|
+
return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object('pk', o.Z_PK, 'identifier', o.ZIDENTIFIER)) FROM ZICCLOUDSYNCINGOBJECT o WHERE o.Z_ENT = ${entityClause(entity3)} AND (${where})));`;
|
|
39670
|
+
}
|
|
39671
|
+
function buildLookupSql(entity3, keys) {
|
|
39672
|
+
assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
|
|
39673
|
+
const pks = [...new Set(keys.map(canonicalKey))];
|
|
39674
|
+
const inList = pks.length > 0 ? pks.join(", ") : "NULL";
|
|
39675
|
+
let fields = "'pk', o.Z_PK, 'identifier', o.ZIDENTIFIER";
|
|
39676
|
+
let joins = "";
|
|
39677
|
+
if (entity3 === "ICNote") {
|
|
39678
|
+
fields += ", 'folderIdentifier', f.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
|
|
39679
|
+
joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT f ON f.Z_PK = o.ZFOLDER LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = f.ZOWNER ";
|
|
39680
|
+
} else if (entity3 === "ICFolder") {
|
|
39681
|
+
fields += ", 'parentIdentifier', p.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
|
|
39682
|
+
joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = o.ZPARENT LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = o.ZOWNER ";
|
|
39683
|
+
}
|
|
39684
|
+
return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object(${fields})) FROM ZICCLOUDSYNCINGOBJECT o ${joins}WHERE o.Z_ENT = ${entityClause(entity3)} AND o.Z_PK IN (${inList})));`;
|
|
39685
|
+
}
|
|
39686
|
+
function runJsonQuery(sql, dbPath2) {
|
|
39687
|
+
if (!fs2.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
39688
|
+
let out;
|
|
39689
|
+
try {
|
|
39690
|
+
out = execFileSync3("sqlite3", ["-readonly", dbPath2, sql], {
|
|
39691
|
+
encoding: "utf8",
|
|
39692
|
+
timeout: 5e3,
|
|
39693
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
39694
|
+
}).trim();
|
|
39695
|
+
} catch (error2) {
|
|
39696
|
+
const detail = error2 instanceof Error ? `${error2.message} ${String(error2.stderr ?? "")}` : String(error2);
|
|
39697
|
+
if (detail.includes("authorization denied") || detail.includes("unable to open database")) {
|
|
39698
|
+
throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
39699
|
+
}
|
|
39700
|
+
console.error(`Identifier query failed: ${detail}`);
|
|
39701
|
+
throw new IdentifierResolutionError("query_error", "Failed to read the Notes database.");
|
|
39702
|
+
}
|
|
39703
|
+
const parsed = JSON.parse(out || "{}");
|
|
39704
|
+
const rows = typeof parsed.rows === "string" ? JSON.parse(parsed.rows) : parsed.rows;
|
|
39705
|
+
return { store: parsed.store ?? null, rows: rows ?? [] };
|
|
39706
|
+
}
|
|
39707
|
+
function coreDataId(store, entity3, pk) {
|
|
39708
|
+
return `x-coredata://${store}/${entity3}/p${pk}`;
|
|
39709
|
+
}
|
|
39710
|
+
function resolveIdentifiers(values, entity3, dbPath2 = NOTES_DB_PATH2) {
|
|
39711
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
39712
|
+
const keys = [];
|
|
39713
|
+
const uuids = [];
|
|
39714
|
+
for (const value of values) {
|
|
39715
|
+
const form = identifierForm(value);
|
|
39716
|
+
if (form === "key") keys.push(value);
|
|
39717
|
+
else if (form === "uuid") uuids.push(value);
|
|
39718
|
+
else resolved.set(value, value);
|
|
39719
|
+
}
|
|
39720
|
+
if (keys.length === 0 && uuids.length === 0) return resolved;
|
|
39721
|
+
const { store, rows } = runJsonQuery(
|
|
39722
|
+
buildResolveSql(entity3, keys, uuids),
|
|
39723
|
+
dbPath2
|
|
39724
|
+
);
|
|
39725
|
+
if (!store) {
|
|
39726
|
+
throw new IdentifierResolutionError(
|
|
39727
|
+
"query_error",
|
|
39728
|
+
"The Notes database has no store UUID, so an x-coredata id cannot be built."
|
|
39729
|
+
);
|
|
39730
|
+
}
|
|
39731
|
+
const byKey = new Map(rows.map((row) => [String(row.pk), row]));
|
|
39732
|
+
const byUuid = new Map(
|
|
39733
|
+
rows.filter((row) => row.identifier).map((row) => [String(row.identifier).toUpperCase(), row])
|
|
39734
|
+
);
|
|
39735
|
+
const label = ENTITY_LABEL[entity3];
|
|
39736
|
+
const missing = [];
|
|
39737
|
+
for (const key of keys) {
|
|
39738
|
+
const row = byKey.get(canonicalKey(key));
|
|
39739
|
+
if (row) resolved.set(key, coreDataId(store, entity3, row.pk));
|
|
39740
|
+
else missing.push(`numeric key ${key}`);
|
|
39741
|
+
}
|
|
39742
|
+
for (const uuid2 of uuids) {
|
|
39743
|
+
const row = byUuid.get(uuid2.toUpperCase());
|
|
39744
|
+
if (row) resolved.set(uuid2, coreDataId(store, entity3, row.pk));
|
|
39745
|
+
else missing.push(`identifier ${uuid2}`);
|
|
39746
|
+
}
|
|
39747
|
+
if (missing.length > 0) {
|
|
39748
|
+
throw new IdentifierResolutionError(
|
|
39749
|
+
"not_found",
|
|
39750
|
+
`No ${label} found for ${missing.join(", ")}. A numeric key must belong to a ${label}, not another object type.`
|
|
39751
|
+
);
|
|
39752
|
+
}
|
|
39753
|
+
return resolved;
|
|
39754
|
+
}
|
|
39755
|
+
var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
|
|
39756
|
+
function lookupStableIdentifiers(ids, entity3, dbPath2 = NOTES_DB_PATH2) {
|
|
39757
|
+
const result = /* @__PURE__ */ new Map();
|
|
39758
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
39759
|
+
for (const id2 of ids) {
|
|
39760
|
+
const match = COREDATA_PARTS.exec(id2);
|
|
39761
|
+
if (!match || match[2] !== entity3) continue;
|
|
39762
|
+
const pk = canonicalKey(match[3]);
|
|
39763
|
+
const list = wanted.get(pk) ?? [];
|
|
39764
|
+
list.push({ id: id2, store: match[1] });
|
|
39765
|
+
wanted.set(pk, list);
|
|
39766
|
+
}
|
|
39767
|
+
if (wanted.size === 0) return result;
|
|
39768
|
+
let query2;
|
|
39769
|
+
try {
|
|
39770
|
+
query2 = runJsonQuery(buildLookupSql(entity3, [...wanted.keys()]), dbPath2);
|
|
39771
|
+
} catch {
|
|
39772
|
+
return result;
|
|
39773
|
+
}
|
|
39774
|
+
const store = query2.store?.toUpperCase();
|
|
39775
|
+
for (const row of query2.rows) {
|
|
39776
|
+
const fields = {};
|
|
39777
|
+
for (const key of [
|
|
39778
|
+
"identifier",
|
|
39779
|
+
"folderIdentifier",
|
|
39780
|
+
"parentIdentifier",
|
|
39781
|
+
"accountIdentifier"
|
|
39782
|
+
]) {
|
|
39783
|
+
const value = row[key];
|
|
39784
|
+
if (typeof value === "string" && value) fields[key] = value;
|
|
39785
|
+
}
|
|
39786
|
+
for (const target of wanted.get(String(row.pk)) ?? []) {
|
|
39787
|
+
if (target.store.toUpperCase() === store) result.set(target.id, fields);
|
|
39788
|
+
}
|
|
39789
|
+
}
|
|
39790
|
+
return result;
|
|
39791
|
+
}
|
|
39792
|
+
function withStableIdentifiers(items, entity3, dbPath2 = NOTES_DB_PATH2) {
|
|
39793
|
+
const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
|
|
39794
|
+
if (ids.length === 0) return items;
|
|
39795
|
+
const found = lookupStableIdentifiers(ids, entity3, dbPath2);
|
|
39796
|
+
if (found.size === 0) return items;
|
|
39797
|
+
return items.map(
|
|
39798
|
+
(item) => item.id && found.has(item.id) ? { ...item, ...found.get(item.id) } : item
|
|
39799
|
+
);
|
|
39800
|
+
}
|
|
39801
|
+
var defaultResolver = (values, entity3) => resolveIdentifiers(values, entity3);
|
|
39802
|
+
var DEFAULT_MAX_ID_LENGTH = 2e3;
|
|
39803
|
+
function acceptAlternateForms(pattern2) {
|
|
39804
|
+
return new RegExp(
|
|
39805
|
+
`${pattern2.source}|${UUID_PATTERN.source}|${NUMERIC_KEY_PATTERN.source}`,
|
|
39806
|
+
pattern2.flags
|
|
39807
|
+
);
|
|
39808
|
+
}
|
|
39809
|
+
function resolveInSchema(values, entity3, ctx, resolver) {
|
|
39810
|
+
if (!values.some(isAlternateIdentifier)) return new Map(values.map((v) => [v, v]));
|
|
39811
|
+
try {
|
|
39812
|
+
return resolver(values, entity3);
|
|
39813
|
+
} catch (error2) {
|
|
39814
|
+
ctx.addIssue({
|
|
39815
|
+
code: external_exports.ZodIssueCode.custom,
|
|
39816
|
+
message: error2 instanceof Error ? error2.message : String(error2)
|
|
39817
|
+
});
|
|
39818
|
+
return null;
|
|
39819
|
+
}
|
|
39820
|
+
}
|
|
39821
|
+
function exactIdInput(entity3, pattern2, message, options = {}) {
|
|
39822
|
+
const resolver = options.resolver ?? defaultResolver;
|
|
39823
|
+
return external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern2), message).transform((value, ctx) => {
|
|
39824
|
+
const map = resolveInSchema([value], entity3, ctx, resolver);
|
|
39825
|
+
return map ? map.get(value) ?? value : external_exports.NEVER;
|
|
39826
|
+
});
|
|
39827
|
+
}
|
|
39828
|
+
function exactIdArrayInput(entity3, pattern2, message, options = {}) {
|
|
39829
|
+
const resolver = options.resolver ?? defaultResolver;
|
|
39830
|
+
const item = external_exports.string().max(options.maxLength ?? DEFAULT_MAX_ID_LENGTH).regex(acceptAlternateForms(pattern2), message);
|
|
39831
|
+
const array2 = options.maxItems === void 0 ? external_exports.array(item) : external_exports.array(item).max(options.maxItems);
|
|
39832
|
+
return array2.transform((values, ctx) => {
|
|
39833
|
+
const map = resolveInSchema(values, entity3, ctx, resolver);
|
|
39834
|
+
return map ? values.map((value) => map.get(value) ?? value) : external_exports.NEVER;
|
|
39835
|
+
});
|
|
39836
|
+
}
|
|
39837
|
+
function looseIdTransform(entity3, resolver = defaultResolver) {
|
|
39838
|
+
return (value, ctx) => {
|
|
39839
|
+
if (!isAlternateIdentifier(value)) return value;
|
|
39840
|
+
const map = resolveInSchema([value], entity3, ctx, resolver);
|
|
39841
|
+
return map ? map.get(value) ?? value : external_exports.NEVER;
|
|
39842
|
+
};
|
|
39843
|
+
}
|
|
39844
|
+
|
|
39591
39845
|
// src/utils/errorCodes.ts
|
|
39592
39846
|
var CodedError = class extends Error {
|
|
39593
39847
|
envelope;
|
|
@@ -39634,8 +39888,10 @@ var NOTHING_WRITTEN = /nothing (?:was )?(?:changed|created|written)|no replaceme
|
|
|
39634
39888
|
var WRITE_ACCEPTED = /\baccepted (?:the|an) \w+, but/i;
|
|
39635
39889
|
function classifyError(message, cause) {
|
|
39636
39890
|
if (cause instanceof CodedError) return { ...cause.envelope };
|
|
39637
|
-
const text2 =
|
|
39638
|
-
|
|
39891
|
+
const text2 = stripQuoted(
|
|
39892
|
+
cause instanceof Error && cause.message && !message.includes(cause.message) ? `${message}
|
|
39893
|
+
${cause.message}` : message
|
|
39894
|
+
);
|
|
39639
39895
|
const timeoutCause = typeof cause === "object" && cause !== null && cause.code === "ETIMEDOUT";
|
|
39640
39896
|
let code = "operation_failed";
|
|
39641
39897
|
if (timeoutCause) code = "timeout_indeterminate";
|
|
@@ -39658,6 +39914,24 @@ function errorResult(message, cause) {
|
|
|
39658
39914
|
isError: true
|
|
39659
39915
|
};
|
|
39660
39916
|
}
|
|
39917
|
+
function unavailableWriteEnvelope(envelope) {
|
|
39918
|
+
if (envelope.code !== "notes_unavailable" || envelope.indeterminate !== void 0 || envelope.committed !== void 0)
|
|
39919
|
+
return envelope;
|
|
39920
|
+
return { ...envelope, indeterminate: true };
|
|
39921
|
+
}
|
|
39922
|
+
var INPUT_VALIDATION = /^(?:MCP error -?\d+: )?Input validation error:/;
|
|
39923
|
+
function inputValidationEnvelope(message) {
|
|
39924
|
+
const failure2 = identifierFailureIn(message);
|
|
39925
|
+
const code = failure2 === "no_fda" ? "full_disk_access_missing" : failure2 === "not_found" ? "not_found" : failure2 === "query_error" ? "operation_failed" : "validation_error";
|
|
39926
|
+
return { code, committed: false, indeterminate: false };
|
|
39927
|
+
}
|
|
39928
|
+
function sdkToolError(message) {
|
|
39929
|
+
return INPUT_VALIDATION.test(message) ? errorResult(message, new CodedError(message, inputValidationEnvelope(message))) : errorResult(message);
|
|
39930
|
+
}
|
|
39931
|
+
function installSdkErrorCodes(server2) {
|
|
39932
|
+
const target = server2;
|
|
39933
|
+
if (typeof target.createToolError === "function") target.createToolError = sdkToolError;
|
|
39934
|
+
}
|
|
39661
39935
|
|
|
39662
39936
|
// src/utils/inlineImages.ts
|
|
39663
39937
|
var DEFAULT_MAX_INLINE_IMAGE_BYTES = 256 * 1024;
|
|
@@ -39700,10 +39974,10 @@ function strippedImagesWarning(stripped) {
|
|
|
39700
39974
|
}
|
|
39701
39975
|
|
|
39702
39976
|
// src/utils/noteRichText.ts
|
|
39703
|
-
import { execFileSync as
|
|
39977
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
39704
39978
|
import { createHash } from "node:crypto";
|
|
39705
|
-
import { homedir as
|
|
39706
|
-
import { join as
|
|
39979
|
+
import { homedir as homedir3 } from "node:os";
|
|
39980
|
+
import { join as join3 } from "node:path";
|
|
39707
39981
|
import { gunzipSync as gunzipSync2 } from "node:zlib";
|
|
39708
39982
|
|
|
39709
39983
|
// src/utils/uniqueById.ts
|
|
@@ -39727,7 +40001,7 @@ var HTML_LOSSY_ORDER = [
|
|
|
39727
40001
|
"alignment",
|
|
39728
40002
|
"highlight"
|
|
39729
40003
|
];
|
|
39730
|
-
var dbPath =
|
|
40004
|
+
var dbPath = join3(homedir3(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
|
|
39731
40005
|
var safeUrl = (url) => /^(?:https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url) && !Array.from(url).some((char) => char.charCodeAt(0) < 32);
|
|
39732
40006
|
var escapeAttribute = (text2) => text2.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
39733
40007
|
var normalized = (text2) => text2.replace(/[\s\ufffc]/gu, "");
|
|
@@ -39859,7 +40133,7 @@ function readRichNote(id2) {
|
|
|
39859
40133
|
const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]+)$/i.exec(id2)?.[1];
|
|
39860
40134
|
if (!pk) throw new Error("Invalid exact note ID");
|
|
39861
40135
|
const sql = `BEGIN; SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE=${pk}; SELECT json_group_object(ZIDENTIFIER,ZALTTEXT) FROM ZICCLOUDSYNCINGOBJECT WHERE ZNOTE1=${pk} AND ZTYPEUTI1='com.apple.notes.inlinetextattachment.hashtag'; SELECT json_group_array(json_object('id',ZIDENTIFIER,'pk',Z_PK,'type',COALESCE(ZTYPEUTI1,ZTYPEUTI),'mergeable',hex(COALESCE(ZMERGEABLEDATA1,ZMERGEABLEDATA)),'view',ZATTACHMENTVIEWTYPE)) FROM ZICCLOUDSYNCINGOBJECT WHERE ZNOTE1=${pk} OR ZNOTE=${pk}; COMMIT;`;
|
|
39862
|
-
const rows =
|
|
40136
|
+
const rows = execFileSync4("/usr/bin/sqlite3", ["-readonly", dbPath, sql], {
|
|
39863
40137
|
encoding: "utf8",
|
|
39864
40138
|
timeout: 5e3,
|
|
39865
40139
|
maxBuffer: 64 * 1024 * 1024,
|
|
@@ -40055,12 +40329,12 @@ function assertLinkedWrite(rich, content, format, allowLinkChanges = false) {
|
|
|
40055
40329
|
}
|
|
40056
40330
|
|
|
40057
40331
|
// src/utils/audioTranscripts.ts
|
|
40058
|
-
import { execFileSync as
|
|
40059
|
-
import { homedir as
|
|
40060
|
-
import { join as
|
|
40332
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
40333
|
+
import { homedir as homedir4 } from "node:os";
|
|
40334
|
+
import { join as join4 } from "node:path";
|
|
40061
40335
|
import { gunzipSync as gunzipSync3 } from "node:zlib";
|
|
40062
|
-
var
|
|
40063
|
-
|
|
40336
|
+
var NOTES_DB_PATH3 = join4(
|
|
40337
|
+
homedir4(),
|
|
40064
40338
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
40065
40339
|
);
|
|
40066
40340
|
var DEFAULT_MAX_SEGMENTS = 2e3;
|
|
@@ -40228,10 +40502,11 @@ function buildTranscriptSql(pk, available) {
|
|
|
40228
40502
|
if (!/^\d+$/.test(pk)) throw new Error("Invalid primary key");
|
|
40229
40503
|
const optional2 = (column, key, alias = "a") => available.has(column) ? `, '${key}', ${alias}.${column}` : "";
|
|
40230
40504
|
const locked = available.has("ZISPASSWORDPROTECTED") ? "COALESCE(n.ZISPASSWORDPROTECTED, 0)" : "0";
|
|
40505
|
+
const isNote = `n.Z_PK = ${pk} AND n.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')`;
|
|
40231
40506
|
return [
|
|
40232
40507
|
"BEGIN;",
|
|
40233
40508
|
// Every statement yields exactly one row, so output lines stay positional.
|
|
40234
|
-
`SELECT json_object('found', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n WHERE
|
|
40509
|
+
`SELECT json_object('found', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n WHERE ${isNote}), 'locked', (SELECT ${locked} FROM ZICCLOUDSYNCINGOBJECT n WHERE ${isNote}));`,
|
|
40235
40510
|
`SELECT COALESCE((SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE = ${pk} LIMIT 1), '');`,
|
|
40236
40511
|
"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};`,
|
|
40237
40512
|
"COMMIT;"
|
|
@@ -40245,7 +40520,7 @@ var REQUIRED_COLUMNS = [
|
|
|
40245
40520
|
"ZIDENTIFIER"
|
|
40246
40521
|
];
|
|
40247
40522
|
function runSqlite(dbPath2, sql) {
|
|
40248
|
-
return
|
|
40523
|
+
return execFileSync5("sqlite3", ["-readonly", dbPath2, sql], {
|
|
40249
40524
|
encoding: "utf8",
|
|
40250
40525
|
timeout: 1e4,
|
|
40251
40526
|
maxBuffer: 256 * 1024 * 1024,
|
|
@@ -40266,7 +40541,7 @@ function bodyAttachmentOrder(gzipped) {
|
|
|
40266
40541
|
}
|
|
40267
40542
|
function readAudioTranscripts(noteId3, options = {}) {
|
|
40268
40543
|
const { store, pk } = parseNoteId(noteId3);
|
|
40269
|
-
const dbPath2 = options.dbPath ??
|
|
40544
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH3;
|
|
40270
40545
|
const maxSegments = Math.min(
|
|
40271
40546
|
Math.max(1, Math.floor(options.maxSegments ?? DEFAULT_MAX_SEGMENTS)),
|
|
40272
40547
|
MAX_SEGMENTS_LIMIT
|
|
@@ -40598,17 +40873,17 @@ function collectNoteTables(rich, noteId3) {
|
|
|
40598
40873
|
}
|
|
40599
40874
|
|
|
40600
40875
|
// src/utils/smartFolders.ts
|
|
40601
|
-
import { execFileSync as
|
|
40602
|
-
import * as
|
|
40603
|
-
import * as
|
|
40604
|
-
import * as
|
|
40605
|
-
var
|
|
40606
|
-
|
|
40607
|
-
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
40608
|
-
);
|
|
40609
|
-
var FDA_MESSAGE = `Full Disk Access is required to read smart folders. In System Settings > Privacy & Security > Full Disk Access, grant access to the Node binary running this server (required under Claude Desktop) or the terminal that launches it, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
|
|
40610
|
-
var COCOA_EPOCH_OFFSET = 978307200;
|
|
40611
|
-
var MAX_DEPTH = 32;
|
|
40876
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
40877
|
+
import * as fs3 from "fs";
|
|
40878
|
+
import * as os3 from "os";
|
|
40879
|
+
import * as path3 from "path";
|
|
40880
|
+
var NOTES_DB_PATH4 = path3.join(
|
|
40881
|
+
os3.homedir(),
|
|
40882
|
+
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
40883
|
+
);
|
|
40884
|
+
var FDA_MESSAGE = `Full Disk Access is required to read smart folders. In System Settings > Privacy & Security > Full Disk Access, grant access to the Node binary running this server (required under Claude Desktop) or the terminal that launches it, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
|
|
40885
|
+
var COCOA_EPOCH_OFFSET = 978307200;
|
|
40886
|
+
var MAX_DEPTH = 32;
|
|
40612
40887
|
var RELATIVE_RANGES = {
|
|
40613
40888
|
0: "today",
|
|
40614
40889
|
1: "yesterday",
|
|
@@ -40948,15 +41223,15 @@ function buildSmartFolders(output) {
|
|
|
40948
41223
|
);
|
|
40949
41224
|
}
|
|
40950
41225
|
function runSqlite2(dbPath2, sql) {
|
|
40951
|
-
return
|
|
41226
|
+
return execFileSync6("sqlite3", ["-readonly", dbPath2, sql], {
|
|
40952
41227
|
encoding: "utf8",
|
|
40953
41228
|
timeout: 5e3,
|
|
40954
41229
|
maxBuffer: 16 * 1024 * 1024,
|
|
40955
41230
|
stdio: ["pipe", "pipe", "pipe"]
|
|
40956
41231
|
});
|
|
40957
41232
|
}
|
|
40958
|
-
function readSmartFolders(dbPath2 =
|
|
40959
|
-
if (!
|
|
41233
|
+
function readSmartFolders(dbPath2 = NOTES_DB_PATH4) {
|
|
41234
|
+
if (!fs3.existsSync(dbPath2)) return { folders: null, error: "no_fda", message: FDA_MESSAGE };
|
|
40960
41235
|
try {
|
|
40961
41236
|
const columns = new Set(
|
|
40962
41237
|
runSqlite2(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").trim().split("\n")
|
|
@@ -40981,30 +41256,30 @@ function readSmartFolders(dbPath2 = NOTES_DB_PATH3) {
|
|
|
40981
41256
|
}
|
|
40982
41257
|
|
|
40983
41258
|
// src/utils/trashFolders.ts
|
|
40984
|
-
import { execFileSync as
|
|
40985
|
-
import * as
|
|
40986
|
-
import * as
|
|
40987
|
-
import * as
|
|
40988
|
-
var
|
|
40989
|
-
|
|
41259
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
41260
|
+
import * as fs4 from "fs";
|
|
41261
|
+
import * as os4 from "os";
|
|
41262
|
+
import * as path4 from "path";
|
|
41263
|
+
var NOTES_DB_PATH5 = path4.join(
|
|
41264
|
+
os4.homedir(),
|
|
40990
41265
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
40991
41266
|
);
|
|
40992
41267
|
var RECENTLY_DELETED_FOLDER_NAME = "Recently Deleted";
|
|
40993
41268
|
var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
40994
41269
|
var cache = null;
|
|
40995
41270
|
function query(dbPath2, sql) {
|
|
40996
|
-
return
|
|
41271
|
+
return execFileSync7("sqlite3", ["-readonly", dbPath2, sql], {
|
|
40997
41272
|
encoding: "utf8",
|
|
40998
41273
|
timeout: 5e3,
|
|
40999
41274
|
stdio: ["pipe", "pipe", "pipe"]
|
|
41000
41275
|
});
|
|
41001
41276
|
}
|
|
41002
|
-
function readTrashFolderIds(dbPath2 =
|
|
41277
|
+
function readTrashFolderIds(dbPath2 = NOTES_DB_PATH5) {
|
|
41003
41278
|
if (cache && cache.dbPath === dbPath2 && Date.now() - cache.at < CACHE_TTL_MS) {
|
|
41004
41279
|
return cache.ids;
|
|
41005
41280
|
}
|
|
41006
41281
|
try {
|
|
41007
|
-
if (!
|
|
41282
|
+
if (!fs4.existsSync(dbPath2)) return [];
|
|
41008
41283
|
const columns = new Set(
|
|
41009
41284
|
query(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").split("\n").map((line) => line.trim()).filter(Boolean)
|
|
41010
41285
|
);
|
|
@@ -41028,7 +41303,7 @@ function readTrashFolderIds(dbPath2 = NOTES_DB_PATH4) {
|
|
|
41028
41303
|
import {
|
|
41029
41304
|
closeSync,
|
|
41030
41305
|
constants,
|
|
41031
|
-
existsSync as
|
|
41306
|
+
existsSync as existsSync5,
|
|
41032
41307
|
fstatSync,
|
|
41033
41308
|
lstatSync,
|
|
41034
41309
|
mkdirSync,
|
|
@@ -41039,11 +41314,11 @@ import {
|
|
|
41039
41314
|
rmSync,
|
|
41040
41315
|
statSync
|
|
41041
41316
|
} from "fs";
|
|
41042
|
-
import { dirname, isAbsolute, join as
|
|
41043
|
-
import { homedir as
|
|
41317
|
+
import { dirname, isAbsolute, join as join7, relative, resolve, sep } from "path";
|
|
41318
|
+
import { homedir as homedir7, tmpdir } from "os";
|
|
41044
41319
|
function allowedSaveRoots() {
|
|
41045
41320
|
return [
|
|
41046
|
-
resolve(
|
|
41321
|
+
resolve(homedir7()),
|
|
41047
41322
|
resolve(tmpdir()),
|
|
41048
41323
|
"/Volumes",
|
|
41049
41324
|
"/private/var/folders",
|
|
@@ -41052,7 +41327,7 @@ function allowedSaveRoots() {
|
|
|
41052
41327
|
];
|
|
41053
41328
|
}
|
|
41054
41329
|
function deniedSaveRoots() {
|
|
41055
|
-
return [
|
|
41330
|
+
return [join7(homedir7(), "Library/Group Containers/group.com.apple.notes")];
|
|
41056
41331
|
}
|
|
41057
41332
|
function canonicalize(path10) {
|
|
41058
41333
|
return realpathSync.native(path10);
|
|
@@ -41120,7 +41395,7 @@ function assertSafeSavePath(p, roots = allowedSaveRoots(), denied = deniedSaveRo
|
|
|
41120
41395
|
if (suffix.split(sep).includes("..")) {
|
|
41121
41396
|
throw new Error(`Refusing to write outside allowed locations (home, temp, /Volumes): "${abs}"`);
|
|
41122
41397
|
}
|
|
41123
|
-
const canonicalDest = suffix ?
|
|
41398
|
+
const canonicalDest = suffix ? join7(canonicalAncestor, suffix) : canonicalAncestor;
|
|
41124
41399
|
const allowed = canonicalRoots(roots);
|
|
41125
41400
|
if (!isWithinRoots(canonicalAncestor, allowed) || !isWithinRoots(canonicalDest, allowed)) {
|
|
41126
41401
|
throw new Error(
|
|
@@ -41189,18 +41464,21 @@ function privateContentReason(p, roots) {
|
|
|
41189
41464
|
const containing = roots.map((r) => r.endsWith(sep) ? r.slice(0, -1) : r).filter((r) => isWithinRoots(p, [r])).sort((a, b) => b.length - a.length)[0];
|
|
41190
41465
|
const below = containing === void 0 ? p : relative(containing, p);
|
|
41191
41466
|
if (below.split(sep).some((part) => part.startsWith("."))) return "a hidden file or directory";
|
|
41192
|
-
const home = resolve(
|
|
41467
|
+
const home = resolve(homedir7());
|
|
41193
41468
|
const libraries = [
|
|
41194
|
-
|
|
41195
|
-
...canonicalRoots([home]).map((h) =>
|
|
41469
|
+
join7(home, "Library"),
|
|
41470
|
+
...canonicalRoots([home]).map((h) => join7(h, "Library"))
|
|
41196
41471
|
];
|
|
41197
41472
|
if (!isWithinRoots(p, libraries)) return null;
|
|
41198
|
-
const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) =>
|
|
41473
|
+
const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) => join7(l, d)));
|
|
41199
41474
|
return isWithinRoots(p, cloudDocuments) ? null : "~/Library";
|
|
41200
41475
|
}
|
|
41201
41476
|
var CLOUD_DOCUMENT_DIRS = ["Mobile Documents", "CloudStorage"];
|
|
41202
|
-
function
|
|
41203
|
-
const
|
|
41477
|
+
function readAllowedFile(p, maxBytes, options = {}) {
|
|
41478
|
+
const roots = options.roots ?? allowedSaveRoots();
|
|
41479
|
+
const allowPrivate = options.allowPrivate ?? process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1";
|
|
41480
|
+
const label = options.label ?? "Content file";
|
|
41481
|
+
const abs = assertReadableInRoots(p, roots, label);
|
|
41204
41482
|
const assertNotPrivate = (candidate, candidateRoots) => {
|
|
41205
41483
|
if (allowPrivate) return;
|
|
41206
41484
|
const reason = privateContentReason(candidate, candidateRoots);
|
|
@@ -41221,12 +41499,12 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPriva
|
|
|
41221
41499
|
}
|
|
41222
41500
|
try {
|
|
41223
41501
|
const stat = fstatSync(descriptor);
|
|
41224
|
-
if (!stat.isFile()) throw new Error(
|
|
41502
|
+
if (!stat.isFile()) throw new Error(`${label} is not a regular file: "${abs}"`);
|
|
41225
41503
|
let after;
|
|
41226
41504
|
try {
|
|
41227
41505
|
after = canonicalize(abs);
|
|
41228
41506
|
} catch {
|
|
41229
|
-
throw new Error(
|
|
41507
|
+
throw new Error(`${label} changed while it was being opened; try again: "${abs}"`);
|
|
41230
41508
|
}
|
|
41231
41509
|
if (!isWithinRoots(after, canonicalRoots(roots)))
|
|
41232
41510
|
throw new Error(
|
|
@@ -41235,31 +41513,35 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPriva
|
|
|
41235
41513
|
assertNotPrivate(after, canonicalRoots(roots));
|
|
41236
41514
|
const named = statSync(after);
|
|
41237
41515
|
if (named.dev !== stat.dev || named.ino !== stat.ino)
|
|
41238
|
-
throw new Error(
|
|
41239
|
-
if (stat.size === 0) throw new Error(
|
|
41516
|
+
throw new Error(`${label} changed while it was being opened; try again: "${abs}"`);
|
|
41517
|
+
if (stat.size === 0) throw new Error(`${label} is empty: "${abs}"`);
|
|
41240
41518
|
if (stat.size > maxBytes)
|
|
41241
|
-
throw new Error(
|
|
41519
|
+
throw new Error(`${label} is ${stat.size} bytes, over the ${maxBytes}-byte limit.`);
|
|
41242
41520
|
const bytes = readFileSync(descriptor);
|
|
41243
41521
|
if (bytes.length !== stat.size)
|
|
41244
|
-
throw new Error(
|
|
41245
|
-
|
|
41246
|
-
return new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
|
|
41247
|
-
} catch {
|
|
41248
|
-
throw new Error(`Content file is not valid UTF-8 text: "${abs}"`);
|
|
41249
|
-
}
|
|
41522
|
+
throw new Error(`${label} changed while it was being read; try again`);
|
|
41523
|
+
return bytes;
|
|
41250
41524
|
} finally {
|
|
41251
41525
|
closeSync(descriptor);
|
|
41252
41526
|
}
|
|
41253
41527
|
}
|
|
41528
|
+
function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPrivate = process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1") {
|
|
41529
|
+
const bytes = readAllowedFile(p, maxBytes, { roots, allowPrivate });
|
|
41530
|
+
try {
|
|
41531
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
|
|
41532
|
+
} catch {
|
|
41533
|
+
throw new Error(`Content file is not valid UTF-8 text: "${resolve(p)}"`);
|
|
41534
|
+
}
|
|
41535
|
+
}
|
|
41254
41536
|
function cleanupTempDir(dir) {
|
|
41255
41537
|
try {
|
|
41256
|
-
if (
|
|
41538
|
+
if (existsSync5(dir)) rmSync(dir, { recursive: true, force: true });
|
|
41257
41539
|
} catch {
|
|
41258
41540
|
}
|
|
41259
41541
|
}
|
|
41260
41542
|
|
|
41261
41543
|
// src/utils/paperAttachments.ts
|
|
41262
|
-
import { execFileSync as
|
|
41544
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
41263
41545
|
import {
|
|
41264
41546
|
closeSync as closeSync3,
|
|
41265
41547
|
constants as constants3,
|
|
@@ -41272,10 +41554,10 @@ import {
|
|
|
41272
41554
|
unlinkSync as unlinkSync2,
|
|
41273
41555
|
writeSync as writeSync2
|
|
41274
41556
|
} from "node:fs";
|
|
41275
|
-
import { basename as basename2, dirname as dirname3, extname as extname2, join as
|
|
41557
|
+
import { basename as basename2, dirname as dirname3, extname as extname2, join as join9 } from "node:path";
|
|
41276
41558
|
|
|
41277
41559
|
// src/utils/attachmentAssets.ts
|
|
41278
|
-
import { execFileSync as
|
|
41560
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
41279
41561
|
import {
|
|
41280
41562
|
closeSync as closeSync2,
|
|
41281
41563
|
constants as constants2,
|
|
@@ -41289,11 +41571,11 @@ import {
|
|
|
41289
41571
|
unlinkSync,
|
|
41290
41572
|
writeSync
|
|
41291
41573
|
} from "node:fs";
|
|
41292
|
-
import { homedir as
|
|
41293
|
-
import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as
|
|
41574
|
+
import { homedir as homedir8 } from "node:os";
|
|
41575
|
+
import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as join8, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
41294
41576
|
import { gunzipSync as gunzipSync5 } from "node:zlib";
|
|
41295
|
-
var NOTES_CONTAINER_DIR =
|
|
41296
|
-
|
|
41577
|
+
var NOTES_CONTAINER_DIR = join8(
|
|
41578
|
+
homedir8(),
|
|
41297
41579
|
"Library/Group Containers/group.com.apple.notes"
|
|
41298
41580
|
);
|
|
41299
41581
|
var AttachmentStoreError = class extends Error {
|
|
@@ -41398,7 +41680,7 @@ function buildAttachmentRowsSql(notePk, columns) {
|
|
|
41398
41680
|
}
|
|
41399
41681
|
function runSqlite3(dbPath2, sql) {
|
|
41400
41682
|
try {
|
|
41401
|
-
return
|
|
41683
|
+
return execFileSync8("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
|
|
41402
41684
|
encoding: "utf8",
|
|
41403
41685
|
timeout: 1e4,
|
|
41404
41686
|
maxBuffer: 128 * 1024 * 1024,
|
|
@@ -41466,7 +41748,7 @@ function attachmentOrderFromNoteData(hex3) {
|
|
|
41466
41748
|
return null;
|
|
41467
41749
|
}
|
|
41468
41750
|
}
|
|
41469
|
-
function readNoteAttachmentRows(noteId3, dbPath2 =
|
|
41751
|
+
function readNoteAttachmentRows(noteId3, dbPath2 = join8(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
|
|
41470
41752
|
const { pk } = parseNoteId2(noteId3);
|
|
41471
41753
|
const columnList = runSqlite3(
|
|
41472
41754
|
dbPath2,
|
|
@@ -41527,7 +41809,7 @@ function boundedEntries(dir, limit) {
|
|
|
41527
41809
|
}
|
|
41528
41810
|
}
|
|
41529
41811
|
function resolveAccountDir(containerDir, accountIdentifier) {
|
|
41530
|
-
const accountsDir =
|
|
41812
|
+
const accountsDir = join8(containerDir, "Accounts");
|
|
41531
41813
|
let accountsReal;
|
|
41532
41814
|
try {
|
|
41533
41815
|
accountsReal = realpathSync2.native(accountsDir);
|
|
@@ -41536,14 +41818,14 @@ function resolveAccountDir(containerDir, accountIdentifier) {
|
|
|
41536
41818
|
}
|
|
41537
41819
|
const account = safeComponent(accountIdentifier);
|
|
41538
41820
|
if (account) {
|
|
41539
|
-
const dir = realInside(
|
|
41821
|
+
const dir = realInside(join8(accountsReal, account), accountsReal);
|
|
41540
41822
|
if (dir && isDirectory(dir)) return dir;
|
|
41541
41823
|
}
|
|
41542
41824
|
const entries2 = (boundedEntries(accountsReal, 64) ?? []).filter(
|
|
41543
|
-
(e) => isDirectory(
|
|
41825
|
+
(e) => isDirectory(join8(accountsReal, e))
|
|
41544
41826
|
);
|
|
41545
41827
|
if (entries2.length !== 1) return null;
|
|
41546
|
-
return realInside(
|
|
41828
|
+
return realInside(join8(accountsReal, entries2[0]), accountsReal);
|
|
41547
41829
|
}
|
|
41548
41830
|
function generationRank(name) {
|
|
41549
41831
|
const m = /^(\d+)_/.exec(name);
|
|
@@ -41551,23 +41833,23 @@ function generationRank(name) {
|
|
|
41551
41833
|
}
|
|
41552
41834
|
function generationDirs(base, accountDir) {
|
|
41553
41835
|
const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
|
|
41554
|
-
return entries2.map((e) => realInside(
|
|
41836
|
+
return entries2.map((e) => realInside(join8(base, e), accountDir)).filter((p) => p !== null && isDirectory(p)).sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
|
|
41555
41837
|
}
|
|
41556
41838
|
function fallbackFiles(accountDir, rootName, identifier, generation, names) {
|
|
41557
|
-
const base =
|
|
41839
|
+
const base = join8(accountDir, rootName, identifier);
|
|
41558
41840
|
const found = [];
|
|
41559
41841
|
const add = (candidate) => {
|
|
41560
41842
|
const real = realInside(candidate, accountDir);
|
|
41561
41843
|
if (real && isRegularFile(real) && !found.includes(real)) found.push(real);
|
|
41562
41844
|
};
|
|
41563
41845
|
const gen = safeComponent(generation);
|
|
41564
|
-
if (gen) for (const name of names) add(
|
|
41846
|
+
if (gen) for (const name of names) add(join8(base, gen, name));
|
|
41565
41847
|
if (isDirectory(base)) {
|
|
41566
41848
|
for (const dir of generationDirs(base, accountDir))
|
|
41567
|
-
for (const name of names) add(
|
|
41568
|
-
for (const name of names) add(
|
|
41849
|
+
for (const name of names) add(join8(dir, name));
|
|
41850
|
+
for (const name of names) add(join8(base, name));
|
|
41569
41851
|
}
|
|
41570
|
-
for (const name of names) add(
|
|
41852
|
+
for (const name of names) add(join8(accountDir, rootName, `${identifier}${extname(name)}`));
|
|
41571
41853
|
return found;
|
|
41572
41854
|
}
|
|
41573
41855
|
function previewPixelArea(name) {
|
|
@@ -41579,7 +41861,7 @@ function previewFileInBundle(bundle, accountDir) {
|
|
|
41579
41861
|
const direct = [];
|
|
41580
41862
|
const nested = [];
|
|
41581
41863
|
for (const entry of boundedEntries(bundle, MAX_BUNDLE_ENTRIES) ?? []) {
|
|
41582
|
-
const child2 = realInside(
|
|
41864
|
+
const child2 = realInside(join8(bundle, entry), accountDir);
|
|
41583
41865
|
if (!child2) continue;
|
|
41584
41866
|
if (isRegularFile(child2)) direct.push(child2);
|
|
41585
41867
|
else if (isDirectory(child2)) nested.push(child2);
|
|
@@ -41587,14 +41869,14 @@ function previewFileInBundle(bundle, accountDir) {
|
|
|
41587
41869
|
nested.sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
|
|
41588
41870
|
const pick2 = (files) => files.find((f) => basename(f) === "Preview.png") ?? files.find((f) => PREVIEW_IMAGE_SUFFIXES.has(extname(f).toLowerCase())) ?? null;
|
|
41589
41871
|
for (const dir of nested) {
|
|
41590
|
-
const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(
|
|
41872
|
+
const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(join8(dir, e), accountDir)).filter((p) => p !== null && isRegularFile(p));
|
|
41591
41873
|
const chosen = pick2(files);
|
|
41592
41874
|
if (chosen) return chosen;
|
|
41593
41875
|
}
|
|
41594
41876
|
return pick2(direct);
|
|
41595
41877
|
}
|
|
41596
41878
|
function listPreviewEntries(accountDir) {
|
|
41597
|
-
return boundedEntries(
|
|
41879
|
+
return boundedEntries(join8(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
|
|
41598
41880
|
}
|
|
41599
41881
|
function previewPaths(accountDir, identifier, entries2) {
|
|
41600
41882
|
const id2 = safeComponent(identifier);
|
|
@@ -41606,7 +41888,7 @@ function previewPaths(accountDir, identifier, entries2) {
|
|
|
41606
41888
|
}).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
|
|
41607
41889
|
const files = [];
|
|
41608
41890
|
for (const name of candidates) {
|
|
41609
|
-
const real = realInside(
|
|
41891
|
+
const real = realInside(join8(accountDir, "Previews", name), accountDir);
|
|
41610
41892
|
if (!real) continue;
|
|
41611
41893
|
const file = isRegularFile(real) ? real : isDirectory(real) ? previewFileInBundle(real, accountDir) : null;
|
|
41612
41894
|
if (file && !files.includes(file)) files.push(file);
|
|
@@ -41623,13 +41905,13 @@ function assetPathsFor(accountDir, row) {
|
|
|
41623
41905
|
const mediaName = safeComponent(row.mediaFilename);
|
|
41624
41906
|
const mediaGen = safeComponent(row.mediaGeneration);
|
|
41625
41907
|
if (mediaId && mediaName) {
|
|
41626
|
-
if (mediaGen) add(
|
|
41627
|
-
add(
|
|
41908
|
+
if (mediaGen) add(join8(accountDir, "Media", mediaId, mediaGen, mediaName));
|
|
41909
|
+
add(join8(accountDir, "Media", mediaId, mediaName));
|
|
41628
41910
|
}
|
|
41629
41911
|
const id2 = safeComponent(row.identifier);
|
|
41630
41912
|
if (!id2) return found;
|
|
41631
41913
|
const ownName = safeComponent(row.filename);
|
|
41632
|
-
if (ownName) add(
|
|
41914
|
+
if (ownName) add(join8(accountDir, "Media", id2, ownName));
|
|
41633
41915
|
for (const file of fallbackFiles(accountDir, "FallbackImages", id2, row.fallbackImageGeneration, [
|
|
41634
41916
|
"FallbackImage.png",
|
|
41635
41917
|
"FallbackImage.jpg"
|
|
@@ -41720,10 +42002,10 @@ function canonicalTail(p) {
|
|
|
41720
42002
|
const tail = [];
|
|
41721
42003
|
for (; ; ) {
|
|
41722
42004
|
try {
|
|
41723
|
-
return
|
|
42005
|
+
return join8(realpathSync2.native(current), ...tail);
|
|
41724
42006
|
} catch {
|
|
41725
42007
|
const parent = dirname2(current);
|
|
41726
|
-
if (parent === current) return
|
|
42008
|
+
if (parent === current) return join8(current, ...tail);
|
|
41727
42009
|
tail.unshift(basename(current));
|
|
41728
42010
|
current = parent;
|
|
41729
42011
|
}
|
|
@@ -41806,7 +42088,7 @@ function exportOneAttachment(record2, dir, source) {
|
|
|
41806
42088
|
if (!source) return base;
|
|
41807
42089
|
const name = exportFileName(record2, source.path, source.kind);
|
|
41808
42090
|
for (let attempt = 1; attempt <= MAX_COLLISION_SUFFIX; attempt++) {
|
|
41809
|
-
const dest =
|
|
42091
|
+
const dest = join8(dir, collisionName(name, attempt));
|
|
41810
42092
|
try {
|
|
41811
42093
|
if (dirname2(dest) !== resolve2(dir))
|
|
41812
42094
|
throw new Error(`Refusing to write outside the export directory: "${dest}"`);
|
|
@@ -41935,7 +42217,7 @@ function buildDrawingRowsSql(notePk, columns) {
|
|
|
41935
42217
|
}
|
|
41936
42218
|
function runSqlite4(dbPath2, sql) {
|
|
41937
42219
|
try {
|
|
41938
|
-
return
|
|
42220
|
+
return execFileSync9("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
|
|
41939
42221
|
encoding: "utf8",
|
|
41940
42222
|
timeout: 1e4,
|
|
41941
42223
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -41975,7 +42257,7 @@ function parseDrawingRows(json2) {
|
|
|
41975
42257
|
}
|
|
41976
42258
|
return rows.sort((a, b) => a.pk - b.pk);
|
|
41977
42259
|
}
|
|
41978
|
-
function readDrawingRows(noteId3, dbPath2 =
|
|
42260
|
+
function readDrawingRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
|
|
41979
42261
|
const { pk } = parseNoteId2(noteId3);
|
|
41980
42262
|
const columns = new Set(
|
|
41981
42263
|
runSqlite4(
|
|
@@ -42019,16 +42301,16 @@ var byGenerationDesc = (a, b) => generationRank(basename2(b)) - generationRank(b
|
|
|
42019
42301
|
function findFallbackImage(accountDir, identifier, generation) {
|
|
42020
42302
|
const id2 = safeComponent(identifier);
|
|
42021
42303
|
if (!id2) return null;
|
|
42022
|
-
const base =
|
|
42304
|
+
const base = join9(accountDir, "FallbackImages", id2);
|
|
42023
42305
|
const names = ["FallbackImage.png", "FallbackImage.jpg"];
|
|
42024
42306
|
const candidates = [];
|
|
42025
42307
|
const gen = safeComponent(generation);
|
|
42026
|
-
if (gen) for (const n of names) candidates.push(
|
|
42027
|
-
const gens = entries(base, MAX_DIR_ENTRIES).map((e) =>
|
|
42028
|
-
for (const g of gens) for (const n of names) candidates.push(
|
|
42029
|
-
for (const n of names) candidates.push(
|
|
42030
|
-
candidates.push(
|
|
42031
|
-
candidates.push(
|
|
42308
|
+
if (gen) for (const n of names) candidates.push(join9(base, gen, n));
|
|
42309
|
+
const gens = entries(base, MAX_DIR_ENTRIES).map((e) => join9(base, e)).sort(byGenerationDesc);
|
|
42310
|
+
for (const g of gens) for (const n of names) candidates.push(join9(g, n));
|
|
42311
|
+
for (const n of names) candidates.push(join9(base, n));
|
|
42312
|
+
candidates.push(join9(accountDir, "FallbackImages", `${id2}.png`));
|
|
42313
|
+
candidates.push(join9(accountDir, "FallbackImages", `${id2}.jpg`));
|
|
42032
42314
|
for (const c of candidates) {
|
|
42033
42315
|
const real = realInside(c, accountDir);
|
|
42034
42316
|
if (real && kindOf(real) === "file") return real;
|
|
@@ -42038,21 +42320,21 @@ function findFallbackImage(accountDir, identifier, generation) {
|
|
|
42038
42320
|
function findLargestPreview(accountDir, identifier) {
|
|
42039
42321
|
const id2 = safeComponent(identifier);
|
|
42040
42322
|
if (!id2) return null;
|
|
42041
|
-
const dir =
|
|
42323
|
+
const dir = join9(accountDir, "Previews");
|
|
42042
42324
|
const prefix = `${id2}-`.toLowerCase();
|
|
42043
42325
|
const names = entries(dir, MAX_PREVIEW_DIR_ENTRIES2).filter((n) => n.toLowerCase().startsWith(prefix)).filter((n) => {
|
|
42044
42326
|
const ext = extname2(n).toLowerCase();
|
|
42045
42327
|
return !ext || IMAGE_SUFFIXES.has(ext) || /^\.\d+$/.test(ext);
|
|
42046
42328
|
}).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
|
|
42047
42329
|
for (const name of names) {
|
|
42048
|
-
const real = realInside(
|
|
42330
|
+
const real = realInside(join9(dir, name), accountDir);
|
|
42049
42331
|
if (!real) continue;
|
|
42050
42332
|
const kind = kindOf(real);
|
|
42051
42333
|
if (kind === "file") return real;
|
|
42052
42334
|
if (kind !== "dir") continue;
|
|
42053
|
-
const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(
|
|
42335
|
+
const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(join9(real, e), accountDir)).filter((p) => p !== null && kindOf(p) === "dir").sort(byGenerationDesc);
|
|
42054
42336
|
for (const g of [...gens, real]) {
|
|
42055
|
-
const file = realInside(
|
|
42337
|
+
const file = realInside(join9(g, "Preview.png"), accountDir);
|
|
42056
42338
|
if (file && kindOf(file) === "file") return file;
|
|
42057
42339
|
}
|
|
42058
42340
|
}
|
|
@@ -42065,7 +42347,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
|
|
|
42065
42347
|
const id2 = safeComponent(row.identifier);
|
|
42066
42348
|
const fallbackImagePath = accountDir ? findFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : null;
|
|
42067
42349
|
const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
|
|
42068
|
-
const bundle = accountDir && id2 && kind === "paper" ? realInside(
|
|
42350
|
+
const bundle = accountDir && id2 && kind === "paper" ? realInside(join9(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
|
|
42069
42351
|
let raster = null;
|
|
42070
42352
|
for (const [path10, source] of [
|
|
42071
42353
|
[fallbackImagePath, "fallback"],
|
|
@@ -42262,9 +42544,9 @@ function scopeConflictMessage(reason) {
|
|
|
42262
42544
|
|
|
42263
42545
|
// src/services/appleNotesManager.ts
|
|
42264
42546
|
var import_turndown = __toESM(require_turndown_cjs(), 1);
|
|
42265
|
-
import { existsSync as
|
|
42266
|
-
import { homedir as
|
|
42267
|
-
import { join as
|
|
42547
|
+
import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
|
|
42548
|
+
import { homedir as homedir9, tmpdir as tmpdir2 } from "os";
|
|
42549
|
+
import { join as join10 } from "path";
|
|
42268
42550
|
var FIELD_SEP = "";
|
|
42269
42551
|
var RECORD_SEP = "";
|
|
42270
42552
|
var AS_FIELD_SEP = "(character id 31)";
|
|
@@ -42486,6 +42768,11 @@ function smartFolderDestinationError(folderPath) {
|
|
|
42486
42768
|
}
|
|
42487
42769
|
);
|
|
42488
42770
|
}
|
|
42771
|
+
function smartFoldersReadError(kind, message) {
|
|
42772
|
+
return new CodedError(message, {
|
|
42773
|
+
code: kind === "unsupported_schema" ? "unsupported" : kind === "no_fda" ? "full_disk_access_missing" : "operation_failed"
|
|
42774
|
+
});
|
|
42775
|
+
}
|
|
42489
42776
|
function throwIfSmartFolderDestination(error2, folderPath) {
|
|
42490
42777
|
if (error2?.includes(SMART_FOLDER_DESTINATION)) throw smartFolderDestinationError(folderPath);
|
|
42491
42778
|
}
|
|
@@ -42621,8 +42908,8 @@ var ExpectedBodies = class {
|
|
|
42621
42908
|
if (body.length <= INLINE_EXPECTED_BODY_LIMIT) {
|
|
42622
42909
|
return { setup: "", operand: `"${escapeHtmlForAppleScript(body)}"` };
|
|
42623
42910
|
}
|
|
42624
|
-
this.directory ??= mkdtempSync2(
|
|
42625
|
-
const file =
|
|
42911
|
+
this.directory ??= mkdtempSync2(join10(tmpdir2(), "apple-notes-expected-body-"));
|
|
42912
|
+
const file = join10(this.directory, `body-${this.files++}.html`);
|
|
42626
42913
|
writeFileSync(file, body, { encoding: "utf8", mode: 384 });
|
|
42627
42914
|
return {
|
|
42628
42915
|
setup: `
|
|
@@ -42668,8 +42955,8 @@ function getNoteLinkFromDB(coreDataId3) {
|
|
|
42668
42955
|
const match = coreDataId3.match(/\/p(\d+)$/);
|
|
42669
42956
|
if (!match) return null;
|
|
42670
42957
|
const pk = parseInt(match[1], 10);
|
|
42671
|
-
const dbPath2 =
|
|
42672
|
-
if (!
|
|
42958
|
+
const dbPath2 = join10(homedir9(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
|
|
42959
|
+
if (!existsSync6(dbPath2)) return null;
|
|
42673
42960
|
try {
|
|
42674
42961
|
const { DatabaseSync } = __require("node:sqlite");
|
|
42675
42962
|
const db = new DatabaseSync(dbPath2, { readOnly: true });
|
|
@@ -43303,9 +43590,12 @@ var AppleNotesManager = class {
|
|
|
43303
43590
|
if currentBody is not ${expected.operand} and currentBody is not ${expected.operand} & linefeed then return "SAFETY_CONFLICT"
|
|
43304
43591
|
delete noteRef
|
|
43305
43592
|
end considering
|
|
43593
|
+
set __stillThere to missing value
|
|
43306
43594
|
try
|
|
43307
|
-
|
|
43595
|
+
set __stillThere to (id of notes of originalFolder) contains "${safeId}"
|
|
43308
43596
|
end try
|
|
43597
|
+
if __stillThere is missing value then return "SAFETY_DELETE_UNVERIFIED"
|
|
43598
|
+
if __stillThere then return "SAFETY_NOT_DELETED"
|
|
43309
43599
|
return "SAFETY_DELETED"
|
|
43310
43600
|
`);
|
|
43311
43601
|
result = executeMutationAppleScript(script);
|
|
@@ -43328,6 +43618,7 @@ var AppleNotesManager = class {
|
|
|
43328
43618
|
if (status === "SAFETY_IN_RECENTLY_DELETED") return { status: "in-recently-deleted" };
|
|
43329
43619
|
if (status === "SAFETY_CONTAINER_UNKNOWN") return { status: "container-unknown" };
|
|
43330
43620
|
if (status === "SAFETY_NOT_DELETED") return { status: "not-deleted" };
|
|
43621
|
+
if (status === "SAFETY_DELETE_UNVERIFIED") return { status: "unverified" };
|
|
43331
43622
|
return status === "SAFETY_DELETED" ? { status: "deleted" } : { status: "failed" };
|
|
43332
43623
|
}
|
|
43333
43624
|
/**
|
|
@@ -43475,7 +43766,8 @@ var AppleNotesManager = class {
|
|
|
43475
43766
|
*/
|
|
43476
43767
|
listSmartFolders(options = {}) {
|
|
43477
43768
|
const result = readSmartFolders();
|
|
43478
|
-
if (!result.folders)
|
|
43769
|
+
if (!result.folders)
|
|
43770
|
+
throw smartFoldersReadError(result.error, result.message || "Failed to read smart folders.");
|
|
43479
43771
|
if (!options.includeMatchingNotes) return result.folders;
|
|
43480
43772
|
const limit = options.limit ?? 50;
|
|
43481
43773
|
return result.folders.map((folder) => {
|
|
@@ -44863,7 +45155,7 @@ var AppleNotesManager = class {
|
|
|
44863
45155
|
if (parts[0] !== "OK") {
|
|
44864
45156
|
return { success: false, error: parts[1]?.trim() || "attachment not found" };
|
|
44865
45157
|
}
|
|
44866
|
-
if (!
|
|
45158
|
+
if (!existsSync6(abs) || fileSize(abs) === 0) {
|
|
44867
45159
|
return { success: false, error: `Notes reported success but no file was written to ${abs}` };
|
|
44868
45160
|
}
|
|
44869
45161
|
return {
|
|
@@ -45371,15 +45663,15 @@ var AppleNotesManager = class {
|
|
|
45371
45663
|
};
|
|
45372
45664
|
|
|
45373
45665
|
// src/utils/syncDetection.ts
|
|
45374
|
-
import { execFileSync as
|
|
45375
|
-
import * as
|
|
45376
|
-
import * as
|
|
45377
|
-
import * as
|
|
45378
|
-
var
|
|
45379
|
-
|
|
45666
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
45667
|
+
import * as fs5 from "fs";
|
|
45668
|
+
import * as path5 from "path";
|
|
45669
|
+
import * as os5 from "os";
|
|
45670
|
+
var NOTES_DB_PATH6 = path5.join(
|
|
45671
|
+
os5.homedir(),
|
|
45380
45672
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45381
45673
|
);
|
|
45382
|
-
var WAL_PATH = `${
|
|
45674
|
+
var WAL_PATH = `${NOTES_DB_PATH6}-wal`;
|
|
45383
45675
|
var RECENT_ACTIVITY_THRESHOLD_SECONDS = 5;
|
|
45384
45676
|
var SYNC_STATUS_CACHE_TTL_MS = 2e3;
|
|
45385
45677
|
var cachedSyncStatus = null;
|
|
@@ -45395,14 +45687,14 @@ function getSyncStatus(useCache = true) {
|
|
|
45395
45687
|
recentActivity: false
|
|
45396
45688
|
};
|
|
45397
45689
|
try {
|
|
45398
|
-
if (!
|
|
45690
|
+
if (!fs5.existsSync(NOTES_DB_PATH6)) {
|
|
45399
45691
|
status.error = "Notes database not found";
|
|
45400
45692
|
cachedSyncStatus = status;
|
|
45401
45693
|
cacheTimestamp = Date.now();
|
|
45402
45694
|
return status;
|
|
45403
45695
|
}
|
|
45404
|
-
if (
|
|
45405
|
-
const walStats =
|
|
45696
|
+
if (fs5.existsSync(WAL_PATH)) {
|
|
45697
|
+
const walStats = fs5.statSync(WAL_PATH);
|
|
45406
45698
|
const secondsAgo = (Date.now() - walStats.mtimeMs) / 1e3;
|
|
45407
45699
|
status.secondsSinceLastChange = Math.round(secondsAgo);
|
|
45408
45700
|
status.recentActivity = secondsAgo < RECENT_ACTIVITY_THRESHOLD_SECONDS;
|
|
@@ -45416,9 +45708,9 @@ function getSyncStatus(useCache = true) {
|
|
|
45416
45708
|
WHERE object.ZCLOUDSTATE = state.Z_PK
|
|
45417
45709
|
);
|
|
45418
45710
|
`;
|
|
45419
|
-
const result =
|
|
45711
|
+
const result = execFileSync10(
|
|
45420
45712
|
"sqlite3",
|
|
45421
|
-
["-readonly",
|
|
45713
|
+
["-readonly", NOTES_DB_PATH6, query2.replace(/\n/g, " ")],
|
|
45422
45714
|
{
|
|
45423
45715
|
encoding: "utf8",
|
|
45424
45716
|
timeout: 5e3,
|
|
@@ -45475,12 +45767,12 @@ function withSyncAwarenessSync(operation, fn) {
|
|
|
45475
45767
|
}
|
|
45476
45768
|
|
|
45477
45769
|
// src/utils/noteMetadata.ts
|
|
45478
|
-
import { execFileSync as
|
|
45479
|
-
import * as
|
|
45480
|
-
import * as
|
|
45481
|
-
import * as
|
|
45482
|
-
var
|
|
45483
|
-
|
|
45770
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
45771
|
+
import * as fs6 from "fs";
|
|
45772
|
+
import * as path6 from "path";
|
|
45773
|
+
import * as os6 from "os";
|
|
45774
|
+
var NOTES_DB_PATH7 = path6.join(
|
|
45775
|
+
os6.homedir(),
|
|
45484
45776
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45485
45777
|
);
|
|
45486
45778
|
var FDA_MESSAGE2 = `Full Disk Access is required to read note metadata. In System Settings > Privacy & Security > Full Disk Access, grant access to the Node binary running this server (required under Claude Desktop) or the terminal that launches it, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
|
|
@@ -45496,7 +45788,7 @@ var COLUMN_MAP = [
|
|
|
45496
45788
|
{ key: "smartFolderQuery", column: "ZSMARTFOLDERQUERYJSON", type: "text" }
|
|
45497
45789
|
];
|
|
45498
45790
|
function runSqlite5(query2) {
|
|
45499
|
-
return
|
|
45791
|
+
return execFileSync11("sqlite3", ["-readonly", NOTES_DB_PATH7, query2], {
|
|
45500
45792
|
encoding: "utf8",
|
|
45501
45793
|
timeout: 5e3,
|
|
45502
45794
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -45521,7 +45813,7 @@ function getNoteMetadata(noteId3) {
|
|
|
45521
45813
|
};
|
|
45522
45814
|
}
|
|
45523
45815
|
const pk = pkMatch[1];
|
|
45524
|
-
if (!
|
|
45816
|
+
if (!fs6.existsSync(NOTES_DB_PATH7)) {
|
|
45525
45817
|
return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
|
|
45526
45818
|
}
|
|
45527
45819
|
try {
|
|
@@ -45567,12 +45859,12 @@ function getNoteMetadata(noteId3) {
|
|
|
45567
45859
|
import { gunzipSync as gunzipSync6 } from "zlib";
|
|
45568
45860
|
|
|
45569
45861
|
// src/utils/noteStoreSql.ts
|
|
45570
|
-
import { execFileSync as
|
|
45571
|
-
import * as
|
|
45572
|
-
import * as
|
|
45573
|
-
import * as
|
|
45574
|
-
var
|
|
45575
|
-
|
|
45862
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
45863
|
+
import * as fs7 from "fs";
|
|
45864
|
+
import * as os7 from "os";
|
|
45865
|
+
import * as path7 from "path";
|
|
45866
|
+
var NOTES_DB_PATH8 = path7.join(
|
|
45867
|
+
os7.homedir(),
|
|
45576
45868
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45577
45869
|
);
|
|
45578
45870
|
var CORE_DATA_EPOCH_MS = Date.UTC(2001, 0, 1);
|
|
@@ -45610,7 +45902,7 @@ function renderValue(value) {
|
|
|
45610
45902
|
return `ieee754_from_blob(x'${doubleToHex(double)}')`;
|
|
45611
45903
|
}
|
|
45612
45904
|
function runReadOnlySql(dbPath2, sql, params = {}) {
|
|
45613
|
-
if (!
|
|
45905
|
+
if (!fs7.existsSync(dbPath2)) throw new NoteStoreError(STORE_FDA_MESSAGE, "no_fda");
|
|
45614
45906
|
const args = ["-readonly"];
|
|
45615
45907
|
const names = Object.keys(params);
|
|
45616
45908
|
if (names.length) args.push("-cmd", ".parameter init");
|
|
@@ -45622,7 +45914,7 @@ function runReadOnlySql(dbPath2, sql, params = {}) {
|
|
|
45622
45914
|
}
|
|
45623
45915
|
args.push(dbPath2, sql);
|
|
45624
45916
|
try {
|
|
45625
|
-
return
|
|
45917
|
+
return execFileSync12("sqlite3", args, {
|
|
45626
45918
|
encoding: "utf8",
|
|
45627
45919
|
timeout: 3e4,
|
|
45628
45920
|
maxBuffer: 512 * 1024 * 1024,
|
|
@@ -45815,7 +46107,7 @@ function buildSpecialNotesSql(columns, kind, scoped) {
|
|
|
45815
46107
|
].join(" ");
|
|
45816
46108
|
}
|
|
45817
46109
|
function listSpecialNotes(options) {
|
|
45818
|
-
const dbPath2 = options.dbPath ??
|
|
46110
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
45819
46111
|
const limit = Math.min(
|
|
45820
46112
|
Math.max(1, Math.trunc(options.limit ?? SPECIAL_LIMIT.DEFAULT)),
|
|
45821
46113
|
SPECIAL_LIMIT.MAX
|
|
@@ -45871,7 +46163,7 @@ function toSpecialRow(row, uuid2, paths, accountNames, kind) {
|
|
|
45871
46163
|
return note;
|
|
45872
46164
|
}
|
|
45873
46165
|
var EXACT_NOTE_ID = /^x-coredata:\/\/([0-9A-F-]+)\/ICNote\/p(\d+)$/i;
|
|
45874
|
-
function quickNoteFlag(noteId3, dbPath2 =
|
|
46166
|
+
function quickNoteFlag(noteId3, dbPath2 = NOTES_DB_PATH8) {
|
|
45875
46167
|
const match = EXACT_NOTE_ID.exec(noteId3);
|
|
45876
46168
|
if (!match) throw new NoteStoreError(`Not an exact note id: ${noteId3}`, "invalid_input");
|
|
45877
46169
|
const columns = readColumns(dbPath2);
|
|
@@ -45923,7 +46215,7 @@ function referencedObjects(hex3) {
|
|
|
45923
46215
|
}
|
|
45924
46216
|
}
|
|
45925
46217
|
function nativeTagInventory(options = {}) {
|
|
45926
|
-
const dbPath2 = options.dbPath ??
|
|
46218
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
45927
46219
|
const columns = readColumns(dbPath2);
|
|
45928
46220
|
const context = readStoreContext(dbPath2, columns);
|
|
45929
46221
|
const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
|
|
@@ -45985,15 +46277,27 @@ function assembleInventory(rows, accounts, scope2) {
|
|
|
45985
46277
|
}
|
|
45986
46278
|
|
|
45987
46279
|
// src/utils/noteBlocks.ts
|
|
45988
|
-
import { execFileSync as
|
|
45989
|
-
import { existsSync as
|
|
45990
|
-
import { homedir as
|
|
45991
|
-
import { join as
|
|
46280
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
46281
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
46282
|
+
import { homedir as homedir13 } from "node:os";
|
|
46283
|
+
import { join as join14 } from "node:path";
|
|
45992
46284
|
import { gunzipSync as gunzipSync7 } from "node:zlib";
|
|
45993
|
-
var
|
|
46285
|
+
var NOTE_BLOCKS_ERROR_CODES = {
|
|
46286
|
+
"invalid-id": "validation_error",
|
|
46287
|
+
"no-full-disk-access": "full_disk_access_missing",
|
|
46288
|
+
"not-found": "not_found",
|
|
46289
|
+
"no-body": "operation_failed",
|
|
46290
|
+
encrypted: "unsupported",
|
|
46291
|
+
"decompress-failed": "unsupported",
|
|
46292
|
+
"malformed-protobuf": "unsupported",
|
|
46293
|
+
"unsupported-structure": "unsupported",
|
|
46294
|
+
"invalid-runs": "unsupported",
|
|
46295
|
+
"query-failed": "operation_failed"
|
|
46296
|
+
};
|
|
46297
|
+
var NoteBlocksError = class extends CodedError {
|
|
45994
46298
|
code;
|
|
45995
46299
|
constructor(code, message) {
|
|
45996
|
-
super(message);
|
|
46300
|
+
super(message, { code: NOTE_BLOCKS_ERROR_CODES[code] });
|
|
45997
46301
|
this.name = "NoteBlocksError";
|
|
45998
46302
|
this.code = code;
|
|
45999
46303
|
}
|
|
@@ -46331,23 +46635,23 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
|
|
|
46331
46635
|
undecodedFields: doc.undecodedFields
|
|
46332
46636
|
};
|
|
46333
46637
|
}
|
|
46334
|
-
var
|
|
46335
|
-
|
|
46638
|
+
var NOTES_DB_PATH9 = join14(
|
|
46639
|
+
homedir13(),
|
|
46336
46640
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46337
46641
|
);
|
|
46338
|
-
function readNoteBlocks(id2, { dbPath: dbPath2 =
|
|
46642
|
+
function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
|
|
46339
46643
|
const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]{1,18})$/i.exec(id2)?.[1];
|
|
46340
46644
|
if (!pk)
|
|
46341
46645
|
throw new NoteBlocksError(
|
|
46342
46646
|
"invalid-id",
|
|
46343
46647
|
`Invalid note ID: expected x-coredata://<store>/ICNote/p<number>`
|
|
46344
46648
|
);
|
|
46345
|
-
if (!
|
|
46649
|
+
if (!existsSync10(dbPath2))
|
|
46346
46650
|
throw new NoteBlocksError("no-full-disk-access", "The Notes database is not readable");
|
|
46347
46651
|
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));";
|
|
46348
46652
|
let output;
|
|
46349
46653
|
try {
|
|
46350
|
-
output =
|
|
46654
|
+
output = execFileSync13(
|
|
46351
46655
|
"/usr/bin/sqlite3",
|
|
46352
46656
|
["-readonly", "-cmd", ".parameter init", "-cmd", `.parameter set @pk ${pk}`, dbPath2, sql],
|
|
46353
46657
|
{
|
|
@@ -46494,7 +46798,7 @@ function resolveFolderPath(folders, paths, input, accountPk) {
|
|
|
46494
46798
|
);
|
|
46495
46799
|
}
|
|
46496
46800
|
function listRecentNotes(options = {}) {
|
|
46497
|
-
const dbPath2 = options.dbPath ??
|
|
46801
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
46498
46802
|
const limit = Math.min(
|
|
46499
46803
|
Math.max(1, Math.trunc(options.limit ?? RECENT_LIMIT.DEFAULT)),
|
|
46500
46804
|
RECENT_LIMIT.MAX
|
|
@@ -46539,356 +46843,118 @@ function listRecentNotes(options = {}) {
|
|
|
46539
46843
|
...scope2 ? { account: scope2.name } : {},
|
|
46540
46844
|
...folder ? { folder: paths.get(folder.pk) } : {}
|
|
46541
46845
|
};
|
|
46542
|
-
}
|
|
46543
|
-
function toRecentRow(row, context, paths, accountNames, options) {
|
|
46544
|
-
const note = {
|
|
46545
|
-
id: noteIdFor(context.uuid, row.pk),
|
|
46546
|
-
identifier: row.identifier ?? null,
|
|
46547
|
-
title: row.title ?? null,
|
|
46548
|
-
folder: row.folder !== null ? paths.get(row.folder) ?? null : null,
|
|
46549
|
-
account: row.account !== null ? accountNames.get(row.account) ?? null : null,
|
|
46550
|
-
created: coreDataToIso(row.created),
|
|
46551
|
-
modified: coreDataToIso(row.modified),
|
|
46552
|
-
modifiedCheckpoint: checkpointFromBits(row.bits, row.pk),
|
|
46553
|
-
pinned: Boolean(row.pinned),
|
|
46554
|
-
locked: Boolean(row.locked),
|
|
46555
|
-
inRecentlyDeleted: Boolean(row.trash),
|
|
46556
|
-
markedForDeletion: Boolean(row.tombstoned)
|
|
46557
|
-
};
|
|
46558
|
-
const text2 = row.locked || row.cloud ? null : decodeBodyText(row.data);
|
|
46559
|
-
if (options.wordCounts) {
|
|
46560
|
-
const stats = text2 === null ? null : textStats(text2);
|
|
46561
|
-
note.wordCount = stats?.wordCount ?? null;
|
|
46562
|
-
note.charCount = stats?.charCount ?? null;
|
|
46563
|
-
}
|
|
46564
|
-
if (options.bodyPreview) {
|
|
46565
|
-
note.textDecoded = text2 !== null;
|
|
46566
|
-
note.bodyPreview = text2 !== null ? previewText(text2) : row.snippet ? previewText(row.snippet) : null;
|
|
46567
|
-
}
|
|
46568
|
-
return note;
|
|
46569
|
-
}
|
|
46570
|
-
function buildFolderCountsSql(columns) {
|
|
46571
|
-
requireColumns(columns, ["Z_PK", "Z_ENT", "ZFOLDER"], "list-folder-tree");
|
|
46572
|
-
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;`;
|
|
46573
|
-
}
|
|
46574
|
-
var KIND_ORDER = { folder: 0, smart: 1, trash: 2 };
|
|
46575
|
-
function compareNodes(a, b) {
|
|
46576
|
-
return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
|
|
46577
|
-
}
|
|
46578
|
-
function folderTree(options = {}) {
|
|
46579
|
-
const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
|
|
46580
|
-
const columns = readColumns(dbPath2);
|
|
46581
|
-
const context = readStoreContext(dbPath2, columns);
|
|
46582
|
-
const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
|
|
46583
|
-
const counts = new Map(
|
|
46584
|
-
parseJsonLines(
|
|
46585
|
-
runReadOnlySql(dbPath2, buildFolderCountsSql(columns))
|
|
46586
|
-
).map((row) => [row.folder, row.n])
|
|
46587
|
-
);
|
|
46588
|
-
return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
|
|
46589
|
-
}
|
|
46590
|
-
function assembleFolderTree(context, counts, includeDeleted, scope2) {
|
|
46591
|
-
const paths = folderPaths(context.folders);
|
|
46592
|
-
const visible2 = context.folders.filter(
|
|
46593
|
-
(folder) => (includeDeleted || !folder.tombstoned) && (!scope2 || folder.account === scope2.pk)
|
|
46594
|
-
);
|
|
46595
|
-
const byPk = new Map(visible2.map((folder) => [folder.pk, folder]));
|
|
46596
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
46597
|
-
for (const folder of visible2) {
|
|
46598
|
-
const node = {
|
|
46599
|
-
id: `x-coredata://${context.uuid}/ICFolder/p${folder.pk}`,
|
|
46600
|
-
identifier: folder.identifier,
|
|
46601
|
-
name: folder.name ?? "",
|
|
46602
|
-
path: paths.get(folder.pk) ?? "",
|
|
46603
|
-
kind: folder.trash ? "trash" : folder.folderType === 2 ? "smart" : "folder",
|
|
46604
|
-
noteCount: counts.get(folder.pk) ?? 0,
|
|
46605
|
-
totalNoteCount: 0,
|
|
46606
|
-
children: []
|
|
46607
|
-
};
|
|
46608
|
-
if (includeDeleted) node.markedForDeletion = Boolean(folder.tombstoned);
|
|
46609
|
-
nodes.set(folder.pk, node);
|
|
46610
|
-
}
|
|
46611
|
-
const parentOf = (folder) => {
|
|
46612
|
-
if (folder.parent === null || !byPk.has(folder.parent)) return void 0;
|
|
46613
|
-
const seen = /* @__PURE__ */ new Set([folder.pk]);
|
|
46614
|
-
for (let at = byPk.get(folder.parent); at; at = at.parent !== null ? byPk.get(at.parent) : void 0) {
|
|
46615
|
-
if (seen.has(at.pk)) return void 0;
|
|
46616
|
-
seen.add(at.pk);
|
|
46617
|
-
}
|
|
46618
|
-
return folder.parent;
|
|
46619
|
-
};
|
|
46620
|
-
const roots = /* @__PURE__ */ new Map();
|
|
46621
|
-
for (const folder of visible2) {
|
|
46622
|
-
const node = nodes.get(folder.pk);
|
|
46623
|
-
const parent = parentOf(folder);
|
|
46624
|
-
if (parent !== void 0) nodes.get(parent).children.push(node);
|
|
46625
|
-
else {
|
|
46626
|
-
const key = folder.account !== null && context.accounts.some((a) => a.pk === folder.account) ? folder.account : null;
|
|
46627
|
-
roots.set(key, [...roots.get(key) ?? [], node]);
|
|
46628
|
-
}
|
|
46629
|
-
}
|
|
46630
|
-
const finish = (node) => {
|
|
46631
|
-
node.children.sort(compareNodes);
|
|
46632
|
-
node.totalNoteCount = node.noteCount + node.children.reduce((sum, c) => sum + finish(c), 0);
|
|
46633
|
-
return node.totalNoteCount;
|
|
46634
|
-
};
|
|
46635
|
-
const accounts = [];
|
|
46636
|
-
const addAccount = (name, identifier, folders) => {
|
|
46637
|
-
folders.sort(compareNodes);
|
|
46638
|
-
let noteCount = 0;
|
|
46639
|
-
for (const node of folders) {
|
|
46640
|
-
const total = finish(node);
|
|
46641
|
-
if (node.kind === "folder") noteCount += total;
|
|
46642
|
-
}
|
|
46643
|
-
accounts.push({ account: name, identifier, noteCount, folders });
|
|
46644
|
-
};
|
|
46645
|
-
for (const account of scope2 ? [scope2] : context.accounts) {
|
|
46646
|
-
addAccount(account.name, account.identifier, roots.get(account.pk) ?? []);
|
|
46647
|
-
}
|
|
46648
|
-
const orphans = roots.get(null);
|
|
46649
|
-
if (orphans && includeDeleted) addAccount("unknown", null, orphans);
|
|
46650
|
-
return { accounts, folderCount: accounts.reduce((sum, a) => sum + countNodes(a.folders), 0) };
|
|
46651
|
-
}
|
|
46652
|
-
function countNodes(nodes) {
|
|
46653
|
-
return nodes.reduce((sum, node) => sum + 1 + countNodes(node.children), 0);
|
|
46654
|
-
}
|
|
46655
|
-
|
|
46656
|
-
// src/utils/noteIdentifiers.ts
|
|
46657
|
-
import { execFileSync as execFileSync13 } from "child_process";
|
|
46658
|
-
import * as fs7 from "fs";
|
|
46659
|
-
import * as os7 from "os";
|
|
46660
|
-
import * as path7 from "path";
|
|
46661
|
-
var NOTES_DB_PATH9 = path7.join(
|
|
46662
|
-
os7.homedir(),
|
|
46663
|
-
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46664
|
-
);
|
|
46665
|
-
var UUID_PATTERN = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;
|
|
46666
|
-
var NUMERIC_KEY_PATTERN = /^\d{1,18}$/;
|
|
46667
|
-
var NOTE_ID_MESSAGE = "A canonical Apple Note ID is required (x-coredata://.../ICNote/p...), or the note's Notes UUID or numeric key";
|
|
46668
|
-
function identifierForm(value) {
|
|
46669
|
-
if (UUID_PATTERN.test(value)) return "uuid";
|
|
46670
|
-
if (NUMERIC_KEY_PATTERN.test(value)) return "key";
|
|
46671
|
-
return "other";
|
|
46672
|
-
}
|
|
46673
|
-
function isAlternateIdentifier(value) {
|
|
46674
|
-
return identifierForm(value) !== "other";
|
|
46675
|
-
}
|
|
46676
|
-
var IdentifierResolutionError = class extends Error {
|
|
46677
|
-
constructor(code, message) {
|
|
46678
|
-
super(message);
|
|
46679
|
-
this.code = code;
|
|
46680
|
-
this.name = "IdentifierResolutionError";
|
|
46681
|
-
}
|
|
46682
|
-
code;
|
|
46683
|
-
};
|
|
46684
|
-
var ENTITY_LABEL = {
|
|
46685
|
-
ICNote: "note",
|
|
46686
|
-
ICFolder: "folder",
|
|
46687
|
-
ICAccount: "account"
|
|
46688
|
-
};
|
|
46689
|
-
var NO_FDA_MESSAGE = `Resolving a Notes UUID or numeric key reads the Notes database, which needs Full Disk Access for the Node binary running this server, or the terminal that launches it (System Settings > Privacy & Security > Full Disk Access, then fully quit and relaunch it). x-coredata ids from search-notes, list-notes, or list-folders work without it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`;
|
|
46690
|
-
function canonicalKey(value) {
|
|
46691
|
-
return BigInt(value).toString();
|
|
46692
|
-
}
|
|
46693
|
-
function assertAll(values, pattern2, label) {
|
|
46694
|
-
for (const value of values) {
|
|
46695
|
-
if (!pattern2.test(value)) throw new Error(`Refusing to query with an invalid ${label}`);
|
|
46696
|
-
}
|
|
46697
|
-
}
|
|
46698
|
-
function entityClause(entity3) {
|
|
46699
|
-
return `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = '${entity3}')`;
|
|
46700
|
-
}
|
|
46701
|
-
function buildResolveSql(entity3, keys, uuids) {
|
|
46702
|
-
assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
|
|
46703
|
-
assertAll(uuids, UUID_PATTERN, "UUID");
|
|
46704
|
-
const matches = [];
|
|
46705
|
-
if (keys.length > 0) {
|
|
46706
|
-
matches.push(`o.Z_PK IN (${[...new Set(keys.map(canonicalKey))].join(", ")})`);
|
|
46707
|
-
}
|
|
46708
|
-
if (uuids.length > 0) {
|
|
46709
|
-
const variants = /* @__PURE__ */ new Set();
|
|
46710
|
-
for (const uuid2 of uuids) {
|
|
46711
|
-
variants.add(uuid2);
|
|
46712
|
-
variants.add(uuid2.toUpperCase());
|
|
46713
|
-
variants.add(uuid2.toLowerCase());
|
|
46714
|
-
}
|
|
46715
|
-
matches.push(`o.ZIDENTIFIER IN (${[...variants].map((v) => `'${v}'`).join(", ")})`);
|
|
46716
|
-
}
|
|
46717
|
-
const where = matches.length > 0 ? matches.join(" OR ") : "0";
|
|
46718
|
-
return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object('pk', o.Z_PK, 'identifier', o.ZIDENTIFIER)) FROM ZICCLOUDSYNCINGOBJECT o WHERE o.Z_ENT = ${entityClause(entity3)} AND (${where})));`;
|
|
46719
|
-
}
|
|
46720
|
-
function buildLookupSql(entity3, keys) {
|
|
46721
|
-
assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
|
|
46722
|
-
const pks = [...new Set(keys.map(canonicalKey))];
|
|
46723
|
-
const inList = pks.length > 0 ? pks.join(", ") : "NULL";
|
|
46724
|
-
let fields = "'pk', o.Z_PK, 'identifier', o.ZIDENTIFIER";
|
|
46725
|
-
let joins = "";
|
|
46726
|
-
if (entity3 === "ICNote") {
|
|
46727
|
-
fields += ", 'folderIdentifier', f.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
|
|
46728
|
-
joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT f ON f.Z_PK = o.ZFOLDER LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = f.ZOWNER ";
|
|
46729
|
-
} else if (entity3 === "ICFolder") {
|
|
46730
|
-
fields += ", 'parentIdentifier', p.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
|
|
46731
|
-
joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = o.ZPARENT LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = o.ZOWNER ";
|
|
46732
|
-
}
|
|
46733
|
-
return `SELECT json_object('store', (SELECT Z_UUID FROM Z_METADATA LIMIT 1), 'rows', (SELECT json_group_array(json_object(${fields})) FROM ZICCLOUDSYNCINGOBJECT o ${joins}WHERE o.Z_ENT = ${entityClause(entity3)} AND o.Z_PK IN (${inList})));`;
|
|
46734
|
-
}
|
|
46735
|
-
function runJsonQuery(sql, dbPath2) {
|
|
46736
|
-
if (!fs7.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
46737
|
-
let out;
|
|
46738
|
-
try {
|
|
46739
|
-
out = execFileSync13("sqlite3", ["-readonly", dbPath2, sql], {
|
|
46740
|
-
encoding: "utf8",
|
|
46741
|
-
timeout: 5e3,
|
|
46742
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
46743
|
-
}).trim();
|
|
46744
|
-
} catch (error2) {
|
|
46745
|
-
const detail = error2 instanceof Error ? `${error2.message} ${String(error2.stderr ?? "")}` : String(error2);
|
|
46746
|
-
if (detail.includes("authorization denied") || detail.includes("unable to open database")) {
|
|
46747
|
-
throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
46748
|
-
}
|
|
46749
|
-
console.error(`Identifier query failed: ${detail}`);
|
|
46750
|
-
throw new IdentifierResolutionError("query_error", "Failed to read the Notes database.");
|
|
46751
|
-
}
|
|
46752
|
-
const parsed = JSON.parse(out || "{}");
|
|
46753
|
-
const rows = typeof parsed.rows === "string" ? JSON.parse(parsed.rows) : parsed.rows;
|
|
46754
|
-
return { store: parsed.store ?? null, rows: rows ?? [] };
|
|
46755
|
-
}
|
|
46756
|
-
function coreDataId(store, entity3, pk) {
|
|
46757
|
-
return `x-coredata://${store}/${entity3}/p${pk}`;
|
|
46758
|
-
}
|
|
46759
|
-
function resolveIdentifiers(values, entity3, dbPath2 = NOTES_DB_PATH9) {
|
|
46760
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
46761
|
-
const keys = [];
|
|
46762
|
-
const uuids = [];
|
|
46763
|
-
for (const value of values) {
|
|
46764
|
-
const form = identifierForm(value);
|
|
46765
|
-
if (form === "key") keys.push(value);
|
|
46766
|
-
else if (form === "uuid") uuids.push(value);
|
|
46767
|
-
else resolved.set(value, value);
|
|
46768
|
-
}
|
|
46769
|
-
if (keys.length === 0 && uuids.length === 0) return resolved;
|
|
46770
|
-
const { store, rows } = runJsonQuery(
|
|
46771
|
-
buildResolveSql(entity3, keys, uuids),
|
|
46772
|
-
dbPath2
|
|
46773
|
-
);
|
|
46774
|
-
if (!store) {
|
|
46775
|
-
throw new IdentifierResolutionError(
|
|
46776
|
-
"query_error",
|
|
46777
|
-
"The Notes database has no store UUID, so an x-coredata id cannot be built."
|
|
46778
|
-
);
|
|
46779
|
-
}
|
|
46780
|
-
const byKey = new Map(rows.map((row) => [String(row.pk), row]));
|
|
46781
|
-
const byUuid = new Map(
|
|
46782
|
-
rows.filter((row) => row.identifier).map((row) => [String(row.identifier).toUpperCase(), row])
|
|
46783
|
-
);
|
|
46784
|
-
const label = ENTITY_LABEL[entity3];
|
|
46785
|
-
const missing = [];
|
|
46786
|
-
for (const key of keys) {
|
|
46787
|
-
const row = byKey.get(canonicalKey(key));
|
|
46788
|
-
if (row) resolved.set(key, coreDataId(store, entity3, row.pk));
|
|
46789
|
-
else missing.push(`numeric key ${key}`);
|
|
46790
|
-
}
|
|
46791
|
-
for (const uuid2 of uuids) {
|
|
46792
|
-
const row = byUuid.get(uuid2.toUpperCase());
|
|
46793
|
-
if (row) resolved.set(uuid2, coreDataId(store, entity3, row.pk));
|
|
46794
|
-
else missing.push(`identifier ${uuid2}`);
|
|
46795
|
-
}
|
|
46796
|
-
if (missing.length > 0) {
|
|
46797
|
-
throw new IdentifierResolutionError(
|
|
46798
|
-
"not_found",
|
|
46799
|
-
`No ${label} found for ${missing.join(", ")}. A numeric key must belong to a ${label}, not another object type.`
|
|
46800
|
-
);
|
|
46801
|
-
}
|
|
46802
|
-
return resolved;
|
|
46803
|
-
}
|
|
46804
|
-
var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
|
|
46805
|
-
function lookupStableIdentifiers(ids, entity3, dbPath2 = NOTES_DB_PATH9) {
|
|
46806
|
-
const result = /* @__PURE__ */ new Map();
|
|
46807
|
-
const wanted = /* @__PURE__ */ new Map();
|
|
46808
|
-
for (const id2 of ids) {
|
|
46809
|
-
const match = COREDATA_PARTS.exec(id2);
|
|
46810
|
-
if (!match || match[2] !== entity3) continue;
|
|
46811
|
-
const pk = canonicalKey(match[3]);
|
|
46812
|
-
const list = wanted.get(pk) ?? [];
|
|
46813
|
-
list.push({ id: id2, store: match[1] });
|
|
46814
|
-
wanted.set(pk, list);
|
|
46815
|
-
}
|
|
46816
|
-
if (wanted.size === 0) return result;
|
|
46817
|
-
let query2;
|
|
46818
|
-
try {
|
|
46819
|
-
query2 = runJsonQuery(buildLookupSql(entity3, [...wanted.keys()]), dbPath2);
|
|
46820
|
-
} catch {
|
|
46821
|
-
return result;
|
|
46846
|
+
}
|
|
46847
|
+
function toRecentRow(row, context, paths, accountNames, options) {
|
|
46848
|
+
const note = {
|
|
46849
|
+
id: noteIdFor(context.uuid, row.pk),
|
|
46850
|
+
identifier: row.identifier ?? null,
|
|
46851
|
+
title: row.title ?? null,
|
|
46852
|
+
folder: row.folder !== null ? paths.get(row.folder) ?? null : null,
|
|
46853
|
+
account: row.account !== null ? accountNames.get(row.account) ?? null : null,
|
|
46854
|
+
created: coreDataToIso(row.created),
|
|
46855
|
+
modified: coreDataToIso(row.modified),
|
|
46856
|
+
modifiedCheckpoint: checkpointFromBits(row.bits, row.pk),
|
|
46857
|
+
pinned: Boolean(row.pinned),
|
|
46858
|
+
locked: Boolean(row.locked),
|
|
46859
|
+
inRecentlyDeleted: Boolean(row.trash),
|
|
46860
|
+
markedForDeletion: Boolean(row.tombstoned)
|
|
46861
|
+
};
|
|
46862
|
+
const text2 = row.locked || row.cloud ? null : decodeBodyText(row.data);
|
|
46863
|
+
if (options.wordCounts) {
|
|
46864
|
+
const stats = text2 === null ? null : textStats(text2);
|
|
46865
|
+
note.wordCount = stats?.wordCount ?? null;
|
|
46866
|
+
note.charCount = stats?.charCount ?? null;
|
|
46822
46867
|
}
|
|
46823
|
-
|
|
46824
|
-
|
|
46825
|
-
|
|
46826
|
-
for (const key of [
|
|
46827
|
-
"identifier",
|
|
46828
|
-
"folderIdentifier",
|
|
46829
|
-
"parentIdentifier",
|
|
46830
|
-
"accountIdentifier"
|
|
46831
|
-
]) {
|
|
46832
|
-
const value = row[key];
|
|
46833
|
-
if (typeof value === "string" && value) fields[key] = value;
|
|
46834
|
-
}
|
|
46835
|
-
for (const target of wanted.get(String(row.pk)) ?? []) {
|
|
46836
|
-
if (target.store.toUpperCase() === store) result.set(target.id, fields);
|
|
46837
|
-
}
|
|
46868
|
+
if (options.bodyPreview) {
|
|
46869
|
+
note.textDecoded = text2 !== null;
|
|
46870
|
+
note.bodyPreview = text2 !== null ? previewText(text2) : row.snippet ? previewText(row.snippet) : null;
|
|
46838
46871
|
}
|
|
46839
|
-
return
|
|
46872
|
+
return note;
|
|
46840
46873
|
}
|
|
46841
|
-
function
|
|
46842
|
-
|
|
46843
|
-
|
|
46844
|
-
const found = lookupStableIdentifiers(ids, entity3, dbPath2);
|
|
46845
|
-
if (found.size === 0) return items;
|
|
46846
|
-
return items.map(
|
|
46847
|
-
(item) => item.id && found.has(item.id) ? { ...item, ...found.get(item.id) } : item
|
|
46848
|
-
);
|
|
46874
|
+
function buildFolderCountsSql(columns) {
|
|
46875
|
+
requireColumns(columns, ["Z_PK", "Z_ENT", "ZFOLDER"], "list-folder-tree");
|
|
46876
|
+
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;`;
|
|
46849
46877
|
}
|
|
46850
|
-
var
|
|
46851
|
-
|
|
46852
|
-
|
|
46853
|
-
|
|
46854
|
-
|
|
46855
|
-
|
|
46878
|
+
var KIND_ORDER = { folder: 0, smart: 1, trash: 2 };
|
|
46879
|
+
function compareNodes(a, b) {
|
|
46880
|
+
return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
|
|
46881
|
+
}
|
|
46882
|
+
function folderTree(options = {}) {
|
|
46883
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
46884
|
+
const columns = readColumns(dbPath2);
|
|
46885
|
+
const context = readStoreContext(dbPath2, columns);
|
|
46886
|
+
const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
|
|
46887
|
+
const counts = new Map(
|
|
46888
|
+
parseJsonLines(
|
|
46889
|
+
runReadOnlySql(dbPath2, buildFolderCountsSql(columns))
|
|
46890
|
+
).map((row) => [row.folder, row.n])
|
|
46856
46891
|
);
|
|
46892
|
+
return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
|
|
46857
46893
|
}
|
|
46858
|
-
function
|
|
46859
|
-
|
|
46860
|
-
|
|
46861
|
-
|
|
46862
|
-
|
|
46863
|
-
|
|
46864
|
-
|
|
46865
|
-
|
|
46866
|
-
|
|
46867
|
-
|
|
46894
|
+
function assembleFolderTree(context, counts, includeDeleted, scope2) {
|
|
46895
|
+
const paths = folderPaths(context.folders);
|
|
46896
|
+
const visible2 = context.folders.filter(
|
|
46897
|
+
(folder) => (includeDeleted || !folder.tombstoned) && (!scope2 || folder.account === scope2.pk)
|
|
46898
|
+
);
|
|
46899
|
+
const byPk = new Map(visible2.map((folder) => [folder.pk, folder]));
|
|
46900
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
46901
|
+
for (const folder of visible2) {
|
|
46902
|
+
const node = {
|
|
46903
|
+
id: `x-coredata://${context.uuid}/ICFolder/p${folder.pk}`,
|
|
46904
|
+
identifier: folder.identifier,
|
|
46905
|
+
name: folder.name ?? "",
|
|
46906
|
+
path: paths.get(folder.pk) ?? "",
|
|
46907
|
+
kind: folder.trash ? "trash" : folder.folderType === 2 ? "smart" : "folder",
|
|
46908
|
+
noteCount: counts.get(folder.pk) ?? 0,
|
|
46909
|
+
totalNoteCount: 0,
|
|
46910
|
+
children: []
|
|
46911
|
+
};
|
|
46912
|
+
if (includeDeleted) node.markedForDeletion = Boolean(folder.tombstoned);
|
|
46913
|
+
nodes.set(folder.pk, node);
|
|
46868
46914
|
}
|
|
46869
|
-
|
|
46870
|
-
|
|
46871
|
-
|
|
46872
|
-
|
|
46873
|
-
|
|
46874
|
-
|
|
46875
|
-
|
|
46876
|
-
|
|
46877
|
-
|
|
46878
|
-
const
|
|
46879
|
-
const
|
|
46880
|
-
|
|
46881
|
-
|
|
46882
|
-
|
|
46883
|
-
|
|
46884
|
-
|
|
46885
|
-
|
|
46886
|
-
|
|
46887
|
-
|
|
46888
|
-
|
|
46889
|
-
|
|
46890
|
-
|
|
46915
|
+
const parentOf = (folder) => {
|
|
46916
|
+
if (folder.parent === null || !byPk.has(folder.parent)) return void 0;
|
|
46917
|
+
const seen = /* @__PURE__ */ new Set([folder.pk]);
|
|
46918
|
+
for (let at = byPk.get(folder.parent); at; at = at.parent !== null ? byPk.get(at.parent) : void 0) {
|
|
46919
|
+
if (seen.has(at.pk)) return void 0;
|
|
46920
|
+
seen.add(at.pk);
|
|
46921
|
+
}
|
|
46922
|
+
return folder.parent;
|
|
46923
|
+
};
|
|
46924
|
+
const roots = /* @__PURE__ */ new Map();
|
|
46925
|
+
for (const folder of visible2) {
|
|
46926
|
+
const node = nodes.get(folder.pk);
|
|
46927
|
+
const parent = parentOf(folder);
|
|
46928
|
+
if (parent !== void 0) nodes.get(parent).children.push(node);
|
|
46929
|
+
else {
|
|
46930
|
+
const key = folder.account !== null && context.accounts.some((a) => a.pk === folder.account) ? folder.account : null;
|
|
46931
|
+
roots.set(key, [...roots.get(key) ?? [], node]);
|
|
46932
|
+
}
|
|
46933
|
+
}
|
|
46934
|
+
const finish = (node) => {
|
|
46935
|
+
node.children.sort(compareNodes);
|
|
46936
|
+
node.totalNoteCount = node.noteCount + node.children.reduce((sum, c) => sum + finish(c), 0);
|
|
46937
|
+
return node.totalNoteCount;
|
|
46938
|
+
};
|
|
46939
|
+
const accounts = [];
|
|
46940
|
+
const addAccount = (name, identifier, folders) => {
|
|
46941
|
+
folders.sort(compareNodes);
|
|
46942
|
+
let noteCount = 0;
|
|
46943
|
+
for (const node of folders) {
|
|
46944
|
+
const total = finish(node);
|
|
46945
|
+
if (node.kind === "folder") noteCount += total;
|
|
46946
|
+
}
|
|
46947
|
+
accounts.push({ account: name, identifier, noteCount, folders });
|
|
46891
46948
|
};
|
|
46949
|
+
for (const account of scope2 ? [scope2] : context.accounts) {
|
|
46950
|
+
addAccount(account.name, account.identifier, roots.get(account.pk) ?? []);
|
|
46951
|
+
}
|
|
46952
|
+
const orphans = roots.get(null);
|
|
46953
|
+
if (orphans && includeDeleted) addAccount("unknown", null, orphans);
|
|
46954
|
+
return { accounts, folderCount: accounts.reduce((sum, a) => sum + countNodes(a.folders), 0) };
|
|
46955
|
+
}
|
|
46956
|
+
function countNodes(nodes) {
|
|
46957
|
+
return nodes.reduce((sum, node) => sum + 1 + countNodes(node.children), 0);
|
|
46892
46958
|
}
|
|
46893
46959
|
|
|
46894
46960
|
// src/utils/contentWarnings.ts
|
|
@@ -48573,7 +48639,15 @@ function mutateBackground(request, operation, data, verify, deps) {
|
|
|
48573
48639
|
transportError = error2 ?? {};
|
|
48574
48640
|
transportMessage = describeTransportFailure(error2);
|
|
48575
48641
|
}
|
|
48576
|
-
|
|
48642
|
+
let after;
|
|
48643
|
+
try {
|
|
48644
|
+
after = deps.read(request.id);
|
|
48645
|
+
} catch (error2) {
|
|
48646
|
+
throw new CodedError(
|
|
48647
|
+
`Operation outcome uncertain; read exact note before any retry: readback failed: ${error2 instanceof Error ? error2.message : String(error2)}${transportMessage ? "; " + transportMessage : ""}`,
|
|
48648
|
+
{ code: "verification_failed", indeterminate: true }
|
|
48649
|
+
);
|
|
48650
|
+
}
|
|
48577
48651
|
try {
|
|
48578
48652
|
verify(before, after);
|
|
48579
48653
|
} catch (error2) {
|
|
@@ -48839,14 +48913,27 @@ var FEATURES = [
|
|
|
48839
48913
|
},
|
|
48840
48914
|
{
|
|
48841
48915
|
name: "fullDiskAccessReads",
|
|
48842
|
-
description: "Read-only reads of the Notes database:
|
|
48916
|
+
description: "Read-only reads of the Notes database: queries, checklist state, metadata, links, note structure, tables, native objects, tags, smart folders, special and recent notes, folder tree, Paper and attachment exports, Markdown and HTML exports, and sync detail",
|
|
48843
48917
|
tools: [
|
|
48844
48918
|
"query-notes",
|
|
48845
48919
|
"get-checklist-state",
|
|
48846
48920
|
"get-note-metadata",
|
|
48847
48921
|
"get-note-link",
|
|
48848
48922
|
"get-native-objects",
|
|
48923
|
+
"get-note-blocks",
|
|
48924
|
+
"get-note-structure",
|
|
48925
|
+
"get-note-tables",
|
|
48926
|
+
"list-note-links",
|
|
48849
48927
|
"list-native-tags",
|
|
48928
|
+
"list-smart-folders",
|
|
48929
|
+
"list-special-notes",
|
|
48930
|
+
"list-recent-notes",
|
|
48931
|
+
"list-folder-tree",
|
|
48932
|
+
"list-paper-attachments",
|
|
48933
|
+
"export-paper-image",
|
|
48934
|
+
"export-attachments",
|
|
48935
|
+
"export-notes-markdown",
|
|
48936
|
+
"export-notes-html",
|
|
48850
48937
|
"get-note-markdown (checklist annotations)",
|
|
48851
48938
|
"get-sync-status (pending uploads)"
|
|
48852
48939
|
],
|
|
@@ -48907,7 +48994,8 @@ var FEATURES = [
|
|
|
48907
48994
|
// Placeholders for features that need a native WRITE helper, which this
|
|
48908
48995
|
// server does not ship. The opt-in private helper is read-only (write
|
|
48909
48996
|
// support was deliberately deferred), so enabling or installing it changes
|
|
48910
|
-
// nothing here: these always report `not_implemented`.
|
|
48997
|
+
// nothing here: these always report `not_implemented`. Reading smart folders
|
|
48998
|
+
// is available through list-smart-folders (fullDiskAccessReads).
|
|
48911
48999
|
{
|
|
48912
49000
|
name: "checklistToggle",
|
|
48913
49001
|
description: "Check or uncheck an existing checklist item in place",
|
|
@@ -48931,17 +49019,17 @@ var FEATURES = [
|
|
|
48931
49019
|
},
|
|
48932
49020
|
{
|
|
48933
49021
|
name: "paragraphLinks",
|
|
48934
|
-
description: "
|
|
48935
|
-
tools: [],
|
|
49022
|
+
description: "List a note's paragraphs and return a link that opens Notes at one paragraph or heading, read from the Notes database",
|
|
49023
|
+
tools: ["list-note-paragraphs", "get-paragraph-link"],
|
|
48936
49024
|
minimumMacOSVersion: null,
|
|
48937
|
-
requirements: [{ kind: "
|
|
49025
|
+
requirements: [{ kind: "full_disk_access" }]
|
|
48938
49026
|
},
|
|
48939
49027
|
{
|
|
48940
49028
|
name: "audioTranscription",
|
|
48941
|
-
description: "
|
|
48942
|
-
tools: [],
|
|
49029
|
+
description: "Read the transcripts Notes stored for a note's audio recordings, or transcribe them on this Mac (transcribe-note-audio also needs the public helper built with setup --public-helper)",
|
|
49030
|
+
tools: ["get-audio-transcripts", "transcribe-note-audio"],
|
|
48943
49031
|
minimumMacOSVersion: null,
|
|
48944
|
-
requirements: [{ kind: "
|
|
49032
|
+
requirements: [{ kind: "full_disk_access" }]
|
|
48945
49033
|
},
|
|
48946
49034
|
{
|
|
48947
49035
|
name: "markdownTemplateLibrary",
|
|
@@ -49612,7 +49700,7 @@ function resolveNote(dbPath2, columns, selector) {
|
|
|
49612
49700
|
function noteBodySql(columns) {
|
|
49613
49701
|
return `SELECT json_object('isNote', n.Z_ENT = ${entity("ICNote")}, 'identifier', ${col(columns, "n", "ZIDENTIFIER")}, 'data', (SELECT hex(d.ZDATA) FROM ZICNOTEDATA d WHERE d.ZNOTE = n.Z_PK), 'encrypted', (SELECT d.ZCRYPTOINITIALIZATIONVECTOR IS NOT NULL FROM ZICNOTEDATA d WHERE d.ZNOTE = n.Z_PK), 'locked', ${col(columns, "n", "ZISPASSWORDPROTECTED")}) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk;`;
|
|
49614
49702
|
}
|
|
49615
|
-
function readNoteParagraphs(selector, { dbPath: dbPath2 =
|
|
49703
|
+
function readNoteParagraphs(selector, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
|
|
49616
49704
|
checkNoteSelector(selector);
|
|
49617
49705
|
const columns = readColumns(dbPath2);
|
|
49618
49706
|
const { pk, id: id2 } = resolveNote(dbPath2, columns, selector);
|
|
@@ -49841,7 +49929,7 @@ function describeNoteStructure(s) {
|
|
|
49841
49929
|
if (s.isShared) parts.push("shared");
|
|
49842
49930
|
return `Note structure: ${parts.join("; ")}.`;
|
|
49843
49931
|
}
|
|
49844
|
-
function readNoteStructure(id2, { dbPath: dbPath2 =
|
|
49932
|
+
function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH8, includeText = true, maxTextBytes = 4 * 1024 * 1024 } = {}) {
|
|
49845
49933
|
const { pk } = parseNoteId2(id2);
|
|
49846
49934
|
const columns = readColumns(dbPath2);
|
|
49847
49935
|
const rows = parseJsonLines(
|
|
@@ -50087,7 +50175,7 @@ function decodeBodies(dbPath2, sql, params) {
|
|
|
50087
50175
|
}
|
|
50088
50176
|
var KIND_ORDER2 = { inline: 0, card: 1, note: 2, section: 3 };
|
|
50089
50177
|
function listNoteLinks(options = {}) {
|
|
50090
|
-
const { dbPath: dbPath2 =
|
|
50178
|
+
const { dbPath: dbPath2 = NOTES_DB_PATH8, offset = 0, limit = 200, maxBytes = 4 * 1024 * 1024 } = options;
|
|
50091
50179
|
if (options.id && (options.account || options.folder))
|
|
50092
50180
|
throw new NoteStoreError("Pass either id or account/folder, not both.", "invalid_input");
|
|
50093
50181
|
const notePk = options.id ? parseNoteId2(options.id).pk : 0;
|
|
@@ -50309,8 +50397,9 @@ function insertLink(request, deps) {
|
|
|
50309
50397
|
try {
|
|
50310
50398
|
readback = verifyLinkReadback(before, deps.readLinks(request.id), insertion, request.url);
|
|
50311
50399
|
} catch (error2) {
|
|
50312
|
-
throw new
|
|
50313
|
-
`The text was written, but link verification failed: ${error2 instanceof Error ? error2.message : String(error2)}. Do not retry automatically
|
|
50400
|
+
throw new CodedError(
|
|
50401
|
+
`The text was written, but link verification failed: ${error2 instanceof Error ? error2.message : String(error2)}. Do not retry automatically.`,
|
|
50402
|
+
{ code: "verification_failed", committed: true, indeterminate: true }
|
|
50314
50403
|
);
|
|
50315
50404
|
}
|
|
50316
50405
|
return {
|
|
@@ -53176,7 +53265,6 @@ import {
|
|
|
53176
53265
|
fstatSync as fstatSync7,
|
|
53177
53266
|
mkdtempSync as mkdtempSync6,
|
|
53178
53267
|
openSync as openSync7,
|
|
53179
|
-
readFileSync as readFileSync4,
|
|
53180
53268
|
readSync as readSync5,
|
|
53181
53269
|
rmSync as rmSync6,
|
|
53182
53270
|
writeFileSync as writeFileSync5
|
|
@@ -53456,17 +53544,14 @@ function assertExistingContentPreserved(before, after) {
|
|
|
53456
53544
|
}
|
|
53457
53545
|
function localAttachment(path10) {
|
|
53458
53546
|
if (!isAbsolute4(path10)) throw new Error("An absolute local file path is required");
|
|
53459
|
-
const descriptor = openSync7(path10, constants7.O_RDONLY | constants7.O_NOFOLLOW);
|
|
53460
53547
|
try {
|
|
53461
|
-
|
|
53462
|
-
|
|
53463
|
-
|
|
53464
|
-
|
|
53465
|
-
|
|
53466
|
-
|
|
53467
|
-
|
|
53468
|
-
} finally {
|
|
53469
|
-
closeSync7(descriptor);
|
|
53548
|
+
return readAllowedFile(path10, MAX_ADD_ATTACHMENT_BYTES, { label: "Attachment" });
|
|
53549
|
+
} catch (error2) {
|
|
53550
|
+
throw new CodedError(error2 instanceof Error ? error2.message : String(error2), {
|
|
53551
|
+
code: "validation_error",
|
|
53552
|
+
committed: false,
|
|
53553
|
+
indeterminate: false
|
|
53554
|
+
});
|
|
53470
53555
|
}
|
|
53471
53556
|
}
|
|
53472
53557
|
var MAX_ADD_ATTACHMENT_BYTES = 64 * 1024 * 1024;
|
|
@@ -53541,20 +53626,22 @@ function registerDirectOperations(server2, manager) {
|
|
|
53541
53626
|
})
|
|
53542
53627
|
);
|
|
53543
53628
|
const attachmentInput = {
|
|
53544
|
-
path: external_exports.string().min(1).max(4096).describe(
|
|
53629
|
+
path: external_exports.string().min(1).max(4096).describe(
|
|
53630
|
+
"Absolute path of the local regular file to attach, in home, temp or /Volumes; hidden paths and ~/Library (except iCloud Drive and CloudStorage) are refused"
|
|
53631
|
+
),
|
|
53545
53632
|
filename: external_exports.string().min(1).max(255).optional().describe(
|
|
53546
53633
|
"Name the attachment gets in Notes instead of the source file's name. One path component with the source file's extension; no slash, colon, or control characters, and no leading dot."
|
|
53547
53634
|
)
|
|
53548
53635
|
};
|
|
53549
53636
|
tool(
|
|
53550
53637
|
"add-attachment",
|
|
53551
|
-
"Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, attachment name, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes. On macOS 27 Notes' AppleScript does not list PDF attachments; with Full Disk Access the new attachment is verified through the read-only NoteStore database instead (verifiedBy: database), otherwise the outcome is reported as uncertain.",
|
|
53638
|
+
"Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, attachment name, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: reads only a regular file in home, temp or /Volumes, refusing hidden paths (~/.ssh, .env) and ~/Library outside iCloud Drive and CloudStorage unless the server sets APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1; requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes. On macOS 27 Notes' AppleScript does not list PDF attachments; with Full Disk Access the new attachment is verified through the read-only NoteStore database instead (verifiedBy: database), otherwise the outcome is reported as uncertain.",
|
|
53552
53639
|
{ id: noteId, expectedContentHash: revision, ...attachmentInput },
|
|
53553
53640
|
(args) => attachFile(manager, args)
|
|
53554
53641
|
);
|
|
53555
53642
|
tool(
|
|
53556
53643
|
"create-note-with-attachment",
|
|
53557
|
-
"Use when: creating a new note that holds one local file, in one call.\nReturns: the new note id plus the add-attachment result (attachment id, bytes, name, content hash).\nDo not use when: the note already exists (add-attachment).\nSafety: checks the file and filename before creating anything, creates the note through Notes.app like create-note, then attaches with the same byte verification as add-attachment. If the attachment step fails
|
|
53644
|
+
"Use when: creating a new note that holds one local file, in one call.\nReturns: the new note id plus the add-attachment result (attachment id, bytes, name, content hash).\nDo not use when: the note already exists (add-attachment).\nSafety: checks the file and filename before creating anything, with the same read scope as add-attachment (home, temp or /Volumes; no hidden paths or ~/Library outside iCloud Drive and CloudStorage), creates the note through Notes.app like create-note, then attaches with the same byte verification as add-attachment. If the attachment step fails before the file is inserted, the error names the new note's id; attach to it with add-attachment instead of creating another note. If insertion started but could not be verified, the outcome is uncertain: read the note with list-attachments before attaching again.",
|
|
53558
53645
|
{
|
|
53559
53646
|
title: external_exports.string().min(1).max(2e3).refine((s) => !/[\r\n\0]/u.test(s), "One-line title"),
|
|
53560
53647
|
content: external_exports.string().min(1).max(1024 * 1024).optional().describe("Optional plain-text body placed above the attachment"),
|
|
@@ -53580,26 +53667,45 @@ function registerDirectOperations(server2, manager) {
|
|
|
53580
53667
|
`Failed to create note "${args.title}". Check that the folder and account exist (list-folders, list-accounts); nothing was attached`
|
|
53581
53668
|
);
|
|
53582
53669
|
const handOff = `Note ${note.id} was created; attach to it with add-attachment instead of creating another note`;
|
|
53670
|
+
const createdError = (message, cause) => new CodedError(message, {
|
|
53671
|
+
code: classifyError(message, cause).code,
|
|
53672
|
+
committed: true,
|
|
53673
|
+
indeterminate: false
|
|
53674
|
+
});
|
|
53583
53675
|
let snapshot;
|
|
53584
53676
|
try {
|
|
53585
53677
|
snapshot = readSnapshot(manager, note.id);
|
|
53586
53678
|
} catch (error2) {
|
|
53587
|
-
|
|
53679
|
+
const message = `${handOff}. The new note could not be read back: ${String(error2)}`;
|
|
53680
|
+
throw createdError(message, error2);
|
|
53588
53681
|
}
|
|
53682
|
+
const progress = { insertionStarted: false };
|
|
53589
53683
|
try {
|
|
53590
53684
|
return {
|
|
53591
|
-
...attachFile(
|
|
53592
|
-
|
|
53593
|
-
|
|
53594
|
-
|
|
53595
|
-
|
|
53596
|
-
|
|
53685
|
+
...attachFile(
|
|
53686
|
+
manager,
|
|
53687
|
+
{
|
|
53688
|
+
id: note.id,
|
|
53689
|
+
expectedContentHash: snapshot.hash,
|
|
53690
|
+
path: args.path,
|
|
53691
|
+
filename: name
|
|
53692
|
+
},
|
|
53693
|
+
void 0,
|
|
53694
|
+
progress
|
|
53695
|
+
),
|
|
53597
53696
|
title: args.title,
|
|
53598
53697
|
folder: args.folder,
|
|
53599
53698
|
noteCreated: true
|
|
53600
53699
|
};
|
|
53601
53700
|
} catch (error2) {
|
|
53602
|
-
|
|
53701
|
+
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
53702
|
+
if (!progress.insertionStarted) throw createdError(`${handOff}. ${detail}`, error2);
|
|
53703
|
+
const message = `Note ${note.id} was created, but the attachment outcome is uncertain: ${detail}. Read the note (list-attachments) before attaching again, and do not create another note`;
|
|
53704
|
+
throw new CodedError(message, {
|
|
53705
|
+
code: "verification_failed",
|
|
53706
|
+
committed: true,
|
|
53707
|
+
indeterminate: true
|
|
53708
|
+
});
|
|
53603
53709
|
}
|
|
53604
53710
|
}
|
|
53605
53711
|
);
|
|
@@ -53706,7 +53812,7 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
|
|
|
53706
53812
|
);
|
|
53707
53813
|
return { attachmentId, name: row.filename };
|
|
53708
53814
|
}
|
|
53709
|
-
function attachFile(manager, args, checked) {
|
|
53815
|
+
function attachFile(manager, args, checked, progress = { insertionStarted: false }) {
|
|
53710
53816
|
const { id: id2, expectedContentHash, path: path10 } = args;
|
|
53711
53817
|
const name = attachmentName(path10, args.filename);
|
|
53712
53818
|
const before = checked ?? readSnapshot(manager, id2);
|
|
@@ -53721,6 +53827,7 @@ function attachFile(manager, args, checked) {
|
|
|
53721
53827
|
if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
|
|
53722
53828
|
let returnedId;
|
|
53723
53829
|
let transportUncertain = false;
|
|
53830
|
+
progress.insertionStarted = true;
|
|
53724
53831
|
try {
|
|
53725
53832
|
returnedId = manager.addAttachmentById(id2, before.body, temporaryFile);
|
|
53726
53833
|
} catch {
|
|
@@ -54048,15 +54155,27 @@ function runFolderDelete(manager, args, deps = defaultDeps) {
|
|
|
54048
54155
|
throw uncertain(
|
|
54049
54156
|
`The delete outcome is uncertain (${outcome.reason}); read folder ${args.id} before retrying`
|
|
54050
54157
|
);
|
|
54051
|
-
|
|
54158
|
+
let stillExists;
|
|
54159
|
+
try {
|
|
54160
|
+
stillExists = manager.folderExistsById(args.id);
|
|
54161
|
+
} catch (error2) {
|
|
54162
|
+
throw uncertain(
|
|
54163
|
+
`The delete outcome is uncertain: Notes.app accepted the delete, but the readback failed (${error2 instanceof Error ? error2.message : String(error2)}); read folder ${args.id} before retrying`
|
|
54164
|
+
);
|
|
54165
|
+
}
|
|
54166
|
+
if (stillExists)
|
|
54052
54167
|
throw uncertain(
|
|
54053
54168
|
`The delete outcome is uncertain: Notes.app still resolves folder ${args.id}; read it before retrying`
|
|
54054
54169
|
);
|
|
54055
54170
|
let storeTombstoned = false;
|
|
54056
54171
|
for (let attempt = 0; attempt < 5 && !storeTombstoned; attempt++) {
|
|
54057
54172
|
if (attempt > 0) deps.sleep(200);
|
|
54058
|
-
|
|
54059
|
-
|
|
54173
|
+
try {
|
|
54174
|
+
const after = deps.readStore(coreDataPk(args.id));
|
|
54175
|
+
storeTombstoned = !after || after.markedForDeletion;
|
|
54176
|
+
} catch {
|
|
54177
|
+
break;
|
|
54178
|
+
}
|
|
54060
54179
|
}
|
|
54061
54180
|
return {
|
|
54062
54181
|
...base,
|
|
@@ -56808,20 +56927,26 @@ function appendChecklistItems(args, deps, readRich) {
|
|
|
56808
56927
|
const landed = [];
|
|
56809
56928
|
let contentHash = args.expectedContentHash;
|
|
56810
56929
|
for (const [index, text2] of args.items.entries()) {
|
|
56811
|
-
let
|
|
56930
|
+
let attempted = false;
|
|
56931
|
+
const tracked = {
|
|
56932
|
+
...deps,
|
|
56933
|
+
run: (input) => {
|
|
56934
|
+
attempted = true;
|
|
56935
|
+
deps.run(input);
|
|
56936
|
+
}
|
|
56937
|
+
};
|
|
56812
56938
|
try {
|
|
56813
56939
|
const result = mutateBackground(
|
|
56814
56940
|
{ ...args, expectedContentHash: contentHash },
|
|
56815
56941
|
"create-checklist-item",
|
|
56816
56942
|
{ text: text2 },
|
|
56817
56943
|
(before, after) => {
|
|
56818
|
-
wrote = true;
|
|
56819
56944
|
assertPreserved(before, after, { append: true });
|
|
56820
56945
|
const added = after.checklist.slice(before.checklist.length);
|
|
56821
56946
|
if (added.length !== 1 || added[0].text !== text2 || added[0].done)
|
|
56822
56947
|
throw new Error("Native checklist item not verified");
|
|
56823
56948
|
},
|
|
56824
|
-
|
|
56949
|
+
tracked
|
|
56825
56950
|
);
|
|
56826
56951
|
const items = byPosition(readRich(args.id).checklistItems);
|
|
56827
56952
|
const fresh = items.filter(
|
|
@@ -56837,20 +56962,33 @@ function appendChecklistItems(args, deps, readRich) {
|
|
|
56837
56962
|
landed.push({ index, id: fresh[0].id, text: text2 });
|
|
56838
56963
|
contentHash = result.contentHash;
|
|
56839
56964
|
} catch (error2) {
|
|
56965
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
56966
|
+
const uncertain2 = attempted && !(error2 instanceof CodedError && error2.envelope.committed === false);
|
|
56967
|
+
const classified = classifyError(message, error2).code;
|
|
56968
|
+
const envelope = uncertain2 ? {
|
|
56969
|
+
code: classified === "timeout_indeterminate" ? classified : "verification_failed",
|
|
56970
|
+
indeterminate: true
|
|
56971
|
+
} : {
|
|
56972
|
+
code: classified,
|
|
56973
|
+
indeterminate: false,
|
|
56974
|
+
// Earlier items did land, so only an empty batch wrote nothing.
|
|
56975
|
+
...landed.length === 0 ? { committed: false } : {}
|
|
56976
|
+
};
|
|
56840
56977
|
return {
|
|
56841
56978
|
ok: false,
|
|
56979
|
+
...envelope,
|
|
56842
56980
|
id: args.id,
|
|
56843
56981
|
landed,
|
|
56844
56982
|
stoppedAt: {
|
|
56845
56983
|
index,
|
|
56846
56984
|
text: text2,
|
|
56847
|
-
|
|
56848
|
-
|
|
56849
|
-
outcome: wrote && !(error2 instanceof CodedError && error2.envelope.committed === false) ? "uncertain" : "not-written",
|
|
56850
|
-
error: error2 instanceof Error ? error2.message : String(error2)
|
|
56985
|
+
outcome: uncertain2 ? "uncertain" : "not-written",
|
|
56986
|
+
error: message
|
|
56851
56987
|
},
|
|
56852
56988
|
notAttempted: args.items.slice(index + 1),
|
|
56853
|
-
|
|
56989
|
+
// After an uncertain run the note may have changed, so the last
|
|
56990
|
+
// verified revision would be stale; read the note for a fresh one.
|
|
56991
|
+
...uncertain2 ? {} : { contentHash },
|
|
56854
56992
|
message: `Stopped at item ${index + 1} of ${args.items.length}; ${landed.length} item(s) landed and were verified. Read the exact note before retrying, and retry only items that are not present.`
|
|
56855
56993
|
};
|
|
56856
56994
|
}
|
|
@@ -56860,15 +56998,24 @@ function appendChecklistItems(args, deps, readRich) {
|
|
|
56860
56998
|
const orderVerified = tail.length === landed.length && tail.every((item, i) => item.id === landed[i].id && item.text === landed[i].text);
|
|
56861
56999
|
return {
|
|
56862
57000
|
ok: orderVerified,
|
|
57001
|
+
...orderVerified ? {} : { code: "verification_failed", committed: true },
|
|
56863
57002
|
id: args.id,
|
|
56864
57003
|
contentHash,
|
|
56865
57004
|
items: landed,
|
|
57005
|
+
landed,
|
|
56866
57006
|
orderVerified,
|
|
56867
57007
|
...orderVerified ? {} : {
|
|
56868
57008
|
message: "Every item landed and was verified, but they are not the note's last checklist items in the requested order. Read the note before editing it further."
|
|
56869
57009
|
}
|
|
56870
57010
|
};
|
|
56871
57011
|
}
|
|
57012
|
+
function nativeToolResult(result) {
|
|
57013
|
+
return {
|
|
57014
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
57015
|
+
structuredContent: result,
|
|
57016
|
+
...result.ok === false && typeof result.code === "string" ? { isError: true } : {}
|
|
57017
|
+
};
|
|
57018
|
+
}
|
|
56872
57019
|
function registerNativeOperations(server2, manager) {
|
|
56873
57020
|
function tool(name, description, input, handler, readOnly = false) {
|
|
56874
57021
|
server2.registerTool(
|
|
@@ -56881,11 +57028,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
56881
57028
|
},
|
|
56882
57029
|
(async (args) => {
|
|
56883
57030
|
try {
|
|
56884
|
-
|
|
56885
|
-
return {
|
|
56886
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
56887
|
-
structuredContent: result
|
|
56888
|
-
};
|
|
57031
|
+
return nativeToolResult(handler(args));
|
|
56889
57032
|
} catch (error2) {
|
|
56890
57033
|
return errorResult(error2 instanceof Error ? error2.message : "Operation failed", error2);
|
|
56891
57034
|
}
|
|
@@ -57270,7 +57413,7 @@ import {
|
|
|
57270
57413
|
existsSync as existsSync14,
|
|
57271
57414
|
mkdirSync as mkdirSync8,
|
|
57272
57415
|
mkdtempSync as mkdtempSync7,
|
|
57273
|
-
readFileSync as
|
|
57416
|
+
readFileSync as readFileSync4,
|
|
57274
57417
|
renameSync as renameSync2,
|
|
57275
57418
|
rmSync as rmSync7,
|
|
57276
57419
|
writeFileSync as writeFileSync6
|
|
@@ -57307,7 +57450,7 @@ function packageRoot(fromDir = dirname8(fileURLToPath2(import.meta.url))) {
|
|
|
57307
57450
|
const candidate = join27(dir, "package.json");
|
|
57308
57451
|
if (existsSync14(candidate)) {
|
|
57309
57452
|
try {
|
|
57310
|
-
if (JSON.parse(
|
|
57453
|
+
if (JSON.parse(readFileSync4(candidate, "utf8")).name === "apple-notes-mcp")
|
|
57311
57454
|
return dir;
|
|
57312
57455
|
} catch {
|
|
57313
57456
|
}
|
|
@@ -57323,7 +57466,7 @@ function defaultPublicHelperDeps(overrides = {}) {
|
|
|
57323
57466
|
platform: process.platform,
|
|
57324
57467
|
sourcePath: join27(packageRoot(), PUBLIC_HELPER_SOURCE),
|
|
57325
57468
|
exists: existsSync14,
|
|
57326
|
-
readFile: (path10) =>
|
|
57469
|
+
readFile: (path10) => readFileSync4(path10),
|
|
57327
57470
|
spawn: spawnSync3,
|
|
57328
57471
|
spawnAsync: spawn,
|
|
57329
57472
|
...overrides
|
|
@@ -57733,7 +57876,7 @@ function parseNoteObjectId(noteId3) {
|
|
|
57733
57876
|
);
|
|
57734
57877
|
return { store: match[1], pk: Number(match[2]) };
|
|
57735
57878
|
}
|
|
57736
|
-
function queryNoteScoped(sql, pk, dbPath2 =
|
|
57879
|
+
function queryNoteScoped(sql, pk, dbPath2 = NOTES_DB_PATH8) {
|
|
57737
57880
|
return runReadOnlySql(dbPath2, sql, { pk: { int: pk } }).split("\n");
|
|
57738
57881
|
}
|
|
57739
57882
|
var NOTE_STATE_SQL = `SELECT json_object('found', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk AND n.Z_ENT = ${entity("ICNote")}), 'locked', (SELECT COALESCE(n.ZISPASSWORDPROTECTED, 0) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = @pk));`;
|
|
@@ -57762,7 +57905,7 @@ function drawingRowsSql(columns, dataColumn) {
|
|
|
57762
57905
|
`SELECT json_group_array(json_object('pk', a.Z_PK, 'identifier', a.ZIDENTIFIER, 'uti', a.ZTYPEUTI, 'data', hex(a.${dataColumn}))) FROM (SELECT * FROM ZICCLOUDSYNCINGOBJECT a WHERE a.ZNOTE = @pk AND a.ZTYPEUTI IN ('com.apple.drawing.2', 'com.apple.drawing') AND ${notTombstonedSql(columns, "a")} ORDER BY a.Z_PK) a;`
|
|
57763
57906
|
].join("\n");
|
|
57764
57907
|
}
|
|
57765
|
-
function readDrawingRows2(noteId3, dbPath2 =
|
|
57908
|
+
function readDrawingRows2(noteId3, dbPath2 = NOTES_DB_PATH8) {
|
|
57766
57909
|
const { store, pk } = parseNoteObjectId(noteId3);
|
|
57767
57910
|
const columns = readColumns(dbPath2);
|
|
57768
57911
|
const dataColumn = columns.has("ZMERGEABLEDATA1") ? "ZMERGEABLEDATA1" : columns.has("ZMERGEABLEDATA") ? "ZMERGEABLEDATA" : null;
|
|
@@ -57997,7 +58140,7 @@ function isFile2(path10) {
|
|
|
57997
58140
|
var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
57998
58141
|
function readAudioAssets(noteId3, options = {}) {
|
|
57999
58142
|
const { store, pk } = parseNoteId2(noteId3);
|
|
58000
|
-
const dbPath2 = options.dbPath ??
|
|
58143
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
58001
58144
|
const columns = readColumns(dbPath2);
|
|
58002
58145
|
const missing = ["ZMEDIA", "ZPARENTATTACHMENT", "ZFILENAME"].filter((c) => !columns.has(c));
|
|
58003
58146
|
if (missing.length)
|
|
@@ -58263,7 +58406,7 @@ import { join as join30 } from "node:path";
|
|
|
58263
58406
|
// src/services/privateHelper.ts
|
|
58264
58407
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
58265
58408
|
import { createHash as createHash7 } from "node:crypto";
|
|
58266
|
-
import { existsSync as existsSync15, readFileSync as
|
|
58409
|
+
import { existsSync as existsSync15, readFileSync as readFileSync5 } from "node:fs";
|
|
58267
58410
|
import { homedir as homedir21 } from "node:os";
|
|
58268
58411
|
import { dirname as dirname9, join as join29, resolve as resolve8 } from "node:path";
|
|
58269
58412
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
@@ -58296,7 +58439,7 @@ function packageRoot2(fromDir = dirname9(fileURLToPath3(import.meta.url))) {
|
|
|
58296
58439
|
const candidate = join29(dir, "package.json");
|
|
58297
58440
|
if (existsSync15(candidate)) {
|
|
58298
58441
|
try {
|
|
58299
|
-
const pkg = JSON.parse(
|
|
58442
|
+
const pkg = JSON.parse(readFileSync5(candidate, "utf8"));
|
|
58300
58443
|
if (pkg.name === "apple-notes-mcp") return dir;
|
|
58301
58444
|
} catch {
|
|
58302
58445
|
}
|
|
@@ -58312,7 +58455,7 @@ function defaultDeps2(overrides = {}) {
|
|
|
58312
58455
|
platform: process.platform,
|
|
58313
58456
|
sourcePath: join29(packageRoot2(), HELPER_SOURCE_RELATIVE),
|
|
58314
58457
|
exists: existsSync15,
|
|
58315
|
-
readFile: (path10) =>
|
|
58458
|
+
readFile: (path10) => readFileSync5(path10),
|
|
58316
58459
|
spawn: spawnSync4,
|
|
58317
58460
|
...overrides
|
|
58318
58461
|
};
|
|
@@ -58376,6 +58519,10 @@ function inspectInstallation(deps = defaultDeps2()) {
|
|
|
58376
58519
|
);
|
|
58377
58520
|
return { ...base, ready: true, reason: null, detail: null };
|
|
58378
58521
|
}
|
|
58522
|
+
function helperTimeoutMs(env) {
|
|
58523
|
+
const value = Number.parseInt(env[TIMEOUT_ENV] || "", 10);
|
|
58524
|
+
return Number.isFinite(value) && value > 0 ? value : DEFAULT_TIMEOUT_MS3;
|
|
58525
|
+
}
|
|
58379
58526
|
var PrivateHelperError = class extends Error {
|
|
58380
58527
|
constructor(code, message, details = {}) {
|
|
58381
58528
|
super(message);
|
|
@@ -58458,7 +58605,7 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
|
|
|
58458
58605
|
throw new PrivateHelperError(install.reason || "helper_not_installed", install.detail || "");
|
|
58459
58606
|
binaryPath = install.binaryPath;
|
|
58460
58607
|
}
|
|
58461
|
-
const timeout =
|
|
58608
|
+
const timeout = helperTimeoutMs(deps.env);
|
|
58462
58609
|
const result = deps.spawn(binaryPath, [], {
|
|
58463
58610
|
input: JSON.stringify({ protocol: PRIVATE_HELPER_PROTOCOL, action, ...fields }),
|
|
58464
58611
|
encoding: "utf8",
|
|
@@ -58468,13 +58615,23 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
|
|
|
58468
58615
|
env: deps.env
|
|
58469
58616
|
});
|
|
58470
58617
|
const errno = result.error?.code;
|
|
58471
|
-
if (errno === "ETIMEDOUT"
|
|
58618
|
+
if (errno === "ETIMEDOUT")
|
|
58472
58619
|
throw new PrivateHelperError("timeout", `The helper did not answer within ${timeout} ms.`);
|
|
58620
|
+
if (errno === "ENOBUFS")
|
|
58621
|
+
throw new PrivateHelperError(
|
|
58622
|
+
"invalid_response",
|
|
58623
|
+
`The helper's output exceeded ${MAX_OUTPUT_BYTES3} bytes and was cut off.`
|
|
58624
|
+
);
|
|
58473
58625
|
if (result.error)
|
|
58474
58626
|
throw new PrivateHelperError(
|
|
58475
58627
|
"helper_unreachable",
|
|
58476
58628
|
`Could not run the helper: ${result.error.message}`
|
|
58477
58629
|
);
|
|
58630
|
+
if (result.signal)
|
|
58631
|
+
throw new PrivateHelperError(
|
|
58632
|
+
"helper_crashed",
|
|
58633
|
+
`The helper was terminated by ${result.signal} before it answered.`
|
|
58634
|
+
);
|
|
58478
58635
|
const stdout = String(result.stdout ?? "").trim();
|
|
58479
58636
|
let parsed;
|
|
58480
58637
|
try {
|
|
@@ -58798,7 +58955,8 @@ function envelopeCode(helperCode, message) {
|
|
|
58798
58955
|
case "not_found":
|
|
58799
58956
|
return "not_found";
|
|
58800
58957
|
case "timeout":
|
|
58801
|
-
|
|
58958
|
+
case "helper_crashed":
|
|
58959
|
+
return "operation_failed";
|
|
58802
58960
|
case "invalid_request":
|
|
58803
58961
|
case "invalid_json":
|
|
58804
58962
|
case "input_too_large":
|
|
@@ -58908,6 +59066,7 @@ var server = new McpServer({
|
|
|
58908
59066
|
version: version2,
|
|
58909
59067
|
description: "MCP server for managing Apple Notes - create, search, update, and organize notes"
|
|
58910
59068
|
});
|
|
59069
|
+
installSdkErrorCodes(server);
|
|
58911
59070
|
var notesManager = new AppleNotesManager();
|
|
58912
59071
|
registerDirectOperations(server, notesManager);
|
|
58913
59072
|
registerFolderDelete(server, notesManager);
|
|
@@ -58924,6 +59083,9 @@ function successResponse(message, structured) {
|
|
|
58924
59083
|
function errorResponse(message, cause) {
|
|
58925
59084
|
return errorResult(message, cause);
|
|
58926
59085
|
}
|
|
59086
|
+
function notFoundResponse(message) {
|
|
59087
|
+
return errorResult(message, new CodedError(message, { code: "not_found" }));
|
|
59088
|
+
}
|
|
58927
59089
|
function withErrorHandling(handler, errorPrefix) {
|
|
58928
59090
|
return async (params) => {
|
|
58929
59091
|
try {
|
|
@@ -59051,6 +59213,9 @@ function bodyReadFailureMessage(id2, title, error2) {
|
|
|
59051
59213
|
return describeBodyReadFailure(title, error2, attachments);
|
|
59052
59214
|
}
|
|
59053
59215
|
var NOT_DELETED_MESSAGE = "Notes.app accepted the delete, but the note is still in its original folder, so it was not moved to Recently Deleted. Nothing was deleted; read the note again before retrying.";
|
|
59216
|
+
function deleteUnverifiedMessage(title, id2) {
|
|
59217
|
+
return `Notes.app accepted the delete for note "${title}", but its original folder could not be re-read to confirm the note left it. The outcome is uncertain; read exact ID ${id2} before retrying.`;
|
|
59218
|
+
}
|
|
59054
59219
|
function containerUnknownMessage(title) {
|
|
59055
59220
|
return `Could not read which folder note "${title}" is in, so it may already be in Recently Deleted, where deleting it would remove it permanently. Nothing was deleted. Retry in a moment; if it keeps failing, quit and reopen Notes.app, then retry.`;
|
|
59056
59221
|
}
|
|
@@ -59121,12 +59286,32 @@ var folderNameSchema = {
|
|
|
59121
59286
|
};
|
|
59122
59287
|
function registerTool(name, config2, cb) {
|
|
59123
59288
|
const { outputSchema, ...rest } = config2;
|
|
59289
|
+
const callback = NOTES_WRITE_TOOLS.has(name) ? (async (...args) => writeToolResult(
|
|
59290
|
+
await cb(...args)
|
|
59291
|
+
)) : cb;
|
|
59124
59292
|
return server.registerTool(
|
|
59125
59293
|
name,
|
|
59126
59294
|
outputSchema ? { ...rest, outputSchema: external_exports.object(outputSchema).passthrough() } : rest,
|
|
59127
|
-
|
|
59295
|
+
callback
|
|
59128
59296
|
);
|
|
59129
59297
|
}
|
|
59298
|
+
var NOTES_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
59299
|
+
"create-note",
|
|
59300
|
+
"update-note",
|
|
59301
|
+
"append-to-note",
|
|
59302
|
+
"insert-link",
|
|
59303
|
+
"delete-note",
|
|
59304
|
+
"move-note",
|
|
59305
|
+
"create-folder",
|
|
59306
|
+
"delete-folder",
|
|
59307
|
+
"batch-delete-notes",
|
|
59308
|
+
"batch-move-notes"
|
|
59309
|
+
]);
|
|
59310
|
+
function writeToolResult(result) {
|
|
59311
|
+
const envelope = result.structuredContent;
|
|
59312
|
+
if (!result.isError || !envelope) return result;
|
|
59313
|
+
return { ...result, structuredContent: unavailableWriteEnvelope(envelope) };
|
|
59314
|
+
}
|
|
59130
59315
|
registerTool(
|
|
59131
59316
|
"create-note",
|
|
59132
59317
|
{
|
|
@@ -59519,7 +59704,7 @@ registerTool(
|
|
|
59519
59704
|
if (id2) {
|
|
59520
59705
|
const note2 = notesManager.getNoteById(id2);
|
|
59521
59706
|
if (!note2) {
|
|
59522
|
-
return
|
|
59707
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59523
59708
|
}
|
|
59524
59709
|
if (note2.passwordProtected) {
|
|
59525
59710
|
return errorResponse(
|
|
@@ -59561,7 +59746,7 @@ registerTool(
|
|
|
59561
59746
|
}
|
|
59562
59747
|
const note = notesManager.getNoteDetails(title, account);
|
|
59563
59748
|
if (!note) {
|
|
59564
|
-
return
|
|
59749
|
+
return notFoundResponse(`Note "${title}" not found`);
|
|
59565
59750
|
}
|
|
59566
59751
|
if (note.passwordProtected) {
|
|
59567
59752
|
return errorResponse(
|
|
@@ -59619,7 +59804,7 @@ registerTool(
|
|
|
59619
59804
|
if (id2) {
|
|
59620
59805
|
const note2 = notesManager.getNoteById(id2);
|
|
59621
59806
|
if (!note2) {
|
|
59622
|
-
return
|
|
59807
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59623
59808
|
}
|
|
59624
59809
|
if (note2.passwordProtected) {
|
|
59625
59810
|
return errorResponse(
|
|
@@ -59637,7 +59822,7 @@ registerTool(
|
|
|
59637
59822
|
}
|
|
59638
59823
|
const note = notesManager.getNoteDetails(title, account);
|
|
59639
59824
|
if (!note) {
|
|
59640
|
-
return
|
|
59825
|
+
return notFoundResponse(`Note "${title}" not found`);
|
|
59641
59826
|
}
|
|
59642
59827
|
if (note.passwordProtected) {
|
|
59643
59828
|
return errorResponse(
|
|
@@ -59673,7 +59858,7 @@ registerTool(
|
|
|
59673
59858
|
withErrorHandling(({ id: id2 }) => {
|
|
59674
59859
|
const note = notesManager.getNoteById(id2);
|
|
59675
59860
|
if (!note) {
|
|
59676
|
-
return
|
|
59861
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59677
59862
|
}
|
|
59678
59863
|
const metadata = {
|
|
59679
59864
|
id: note.id,
|
|
@@ -59706,7 +59891,7 @@ registerTool(
|
|
|
59706
59891
|
withErrorHandling(({ title, account }) => {
|
|
59707
59892
|
const note = notesManager.getNoteDetails(title, account);
|
|
59708
59893
|
if (!note) {
|
|
59709
|
-
return
|
|
59894
|
+
return notFoundResponse(`Note "${title}" not found`);
|
|
59710
59895
|
}
|
|
59711
59896
|
const metadata = {
|
|
59712
59897
|
id: note.id,
|
|
@@ -59763,7 +59948,7 @@ registerTool(
|
|
|
59763
59948
|
if (id2) {
|
|
59764
59949
|
const note2 = notesManager.getNoteById(id2);
|
|
59765
59950
|
if (!note2) {
|
|
59766
|
-
return
|
|
59951
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59767
59952
|
}
|
|
59768
59953
|
if (note2.passwordProtected) {
|
|
59769
59954
|
return errorResponse(
|
|
@@ -59783,7 +59968,7 @@ registerTool(
|
|
|
59783
59968
|
}
|
|
59784
59969
|
const note = notesManager.getNoteDetails(title, account);
|
|
59785
59970
|
if (!note) {
|
|
59786
|
-
return
|
|
59971
|
+
return notFoundResponse(
|
|
59787
59972
|
`Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
|
|
59788
59973
|
);
|
|
59789
59974
|
}
|
|
@@ -59861,7 +60046,7 @@ registerTool(
|
|
|
59861
60046
|
},
|
|
59862
60047
|
withErrorHandling(({ id: id2 }) => {
|
|
59863
60048
|
const note = notesManager.getNoteById(id2);
|
|
59864
|
-
if (!note) return
|
|
60049
|
+
if (!note) return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59865
60050
|
const body = notesManager.getNoteContentById(id2);
|
|
59866
60051
|
if (!body) return errorResponse(`Failed to read content of note "${note.title}"`);
|
|
59867
60052
|
const rich = readRichNote(id2);
|
|
@@ -59967,7 +60152,10 @@ registerTool(
|
|
|
59967
60152
|
} catch (error2) {
|
|
59968
60153
|
if (!(error2 instanceof NoteBlocksError)) throw error2;
|
|
59969
60154
|
const hint = error2.code === "no-full-disk-access" ? ` Grant Full Disk Access to the Node binary running this server (run the doctor tool for its path): ${FULL_DISK_ACCESS_GUIDE_URL}` : "";
|
|
59970
|
-
return errorResponse(
|
|
60155
|
+
return errorResponse(
|
|
60156
|
+
`Error reading note blocks [${error2.code}]: ${error2.message}${hint}`,
|
|
60157
|
+
error2
|
|
60158
|
+
);
|
|
59971
60159
|
}
|
|
59972
60160
|
const { summary } = page;
|
|
59973
60161
|
const styles = Object.entries(summary.styles).map(([style, count]) => `${style} ${count}`).join(", ");
|
|
@@ -60528,7 +60716,10 @@ registerTool(
|
|
|
60528
60716
|
},
|
|
60529
60717
|
(r) => route = r
|
|
60530
60718
|
);
|
|
60531
|
-
if (response.isError)
|
|
60719
|
+
if (response.isError) {
|
|
60720
|
+
const envelope = response.structuredContent;
|
|
60721
|
+
throw envelope?.code ? new CodedError(response.content[0].text, envelope) : new Error(response.content[0].text);
|
|
60722
|
+
}
|
|
60532
60723
|
return { route, contentHash: String(response.structuredContent?.contentHash ?? "") };
|
|
60533
60724
|
}
|
|
60534
60725
|
}
|
|
@@ -60620,6 +60811,13 @@ registerTool(
|
|
|
60620
60811
|
if (result.status === "not-deleted") {
|
|
60621
60812
|
return errorResponse(NOT_DELETED_MESSAGE);
|
|
60622
60813
|
}
|
|
60814
|
+
if (result.status === "unverified") {
|
|
60815
|
+
const message = deleteUnverifiedMessage(snapshot.note.title, id2);
|
|
60816
|
+
return errorResponse(
|
|
60817
|
+
message,
|
|
60818
|
+
new CodedError(message, { code: "verification_failed", indeterminate: true })
|
|
60819
|
+
);
|
|
60820
|
+
}
|
|
60623
60821
|
if (result.status !== "deleted") {
|
|
60624
60822
|
return errorResponse(
|
|
60625
60823
|
`The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
|
|
@@ -60664,7 +60862,7 @@ registerTool(
|
|
|
60664
60862
|
withErrorHandling(({ id: id2, folder, account, ...scopeArgs }) => {
|
|
60665
60863
|
const note = notesManager.getNoteById(id2);
|
|
60666
60864
|
if (!note) {
|
|
60667
|
-
return
|
|
60865
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
60668
60866
|
}
|
|
60669
60867
|
const success = notesManager.moveNoteById(id2, folder, account, scopeFrom(scopeArgs));
|
|
60670
60868
|
if (!success) {
|
|
@@ -61195,7 +61393,7 @@ registerTool(
|
|
|
61195
61393
|
if (id2) {
|
|
61196
61394
|
const note2 = notesManager.getNoteById(id2);
|
|
61197
61395
|
if (!note2) {
|
|
61198
|
-
return
|
|
61396
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
61199
61397
|
}
|
|
61200
61398
|
const attachments2 = notesManager.listAttachmentsById(id2);
|
|
61201
61399
|
if (attachments2.length === 0) {
|
|
@@ -61248,7 +61446,7 @@ ${attachmentList2}`,
|
|
|
61248
61446
|
}
|
|
61249
61447
|
const note = notesManager.getNoteDetails(title, account);
|
|
61250
61448
|
if (!note) {
|
|
61251
|
-
return
|
|
61449
|
+
return notFoundResponse(
|
|
61252
61450
|
`Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
|
|
61253
61451
|
);
|
|
61254
61452
|
}
|
|
@@ -61306,6 +61504,8 @@ registerTool(
|
|
|
61306
61504
|
}
|
|
61307
61505
|
if (result.status === "not-deleted")
|
|
61308
61506
|
return { id: id2, success: false, error: NOT_DELETED_MESSAGE };
|
|
61507
|
+
if (result.status === "unverified")
|
|
61508
|
+
return { id: id2, success: false, error: deleteUnverifiedMessage(snapshot.note.title, id2) };
|
|
61309
61509
|
return { id: id2, success: false, error: "Delete result uncertain; inspect this exact ID" };
|
|
61310
61510
|
});
|
|
61311
61511
|
const succeeded = results.filter((r) => r.success).length;
|
|
@@ -61533,10 +61733,12 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
|
|
|
61533
61733
|
);
|
|
61534
61734
|
}
|
|
61535
61735
|
}
|
|
61536
|
-
|
|
61537
|
-
|
|
61538
|
-
|
|
61539
|
-
|
|
61736
|
+
const summary = `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${skipped} with nothing on disk, ${failed} failed).`;
|
|
61737
|
+
if (exported === 0 && failed > 0) {
|
|
61738
|
+
const error2 = errorResponse(summary, new CodedError(summary, { code: "operation_failed" }));
|
|
61739
|
+
return { ...error2, structuredContent: { ...structured, ...error2.structuredContent } };
|
|
61740
|
+
}
|
|
61741
|
+
return successResponse(summary, structured);
|
|
61540
61742
|
}, "Error exporting attachments")
|
|
61541
61743
|
);
|
|
61542
61744
|
registerTool(
|
|
@@ -61686,7 +61888,7 @@ registerTool(
|
|
|
61686
61888
|
if (id2) {
|
|
61687
61889
|
const markdown2 = notesManager.getNoteMarkdownById(id2);
|
|
61688
61890
|
if (!markdown2) {
|
|
61689
|
-
return
|
|
61891
|
+
return notFoundResponse(`Note with ID "${id2}" not found or has no content`);
|
|
61690
61892
|
}
|
|
61691
61893
|
return successResponse(markdown2, { markdown: markdown2 });
|
|
61692
61894
|
}
|
|
@@ -61695,7 +61897,7 @@ registerTool(
|
|
|
61695
61897
|
}
|
|
61696
61898
|
const markdown = notesManager.getNoteMarkdown(title, account);
|
|
61697
61899
|
if (!markdown) {
|
|
61698
|
-
return
|
|
61900
|
+
return notFoundResponse(
|
|
61699
61901
|
`Note "${title}" not found or has no content. Use search-notes to find notes, then use the note's ID for reliable operations.`
|
|
61700
61902
|
);
|
|
61701
61903
|
}
|
|
@@ -61770,7 +61972,10 @@ registerTool(
|
|
|
61770
61972
|
`Error exporting Markdown [${error2.code}]: the template is invalid:
|
|
61771
61973
|
` + error2.details.map((detail) => `${detail.path}: ${detail.message}`).join("\n")
|
|
61772
61974
|
);
|
|
61773
|
-
return errorResponse(
|
|
61975
|
+
return errorResponse(
|
|
61976
|
+
`Error exporting Markdown [${error2.code}]: ${error2.message}${hint}`,
|
|
61977
|
+
error2
|
|
61978
|
+
);
|
|
61774
61979
|
}
|
|
61775
61980
|
const warned = receipt.warnings?.length ? `; ${receipt.warnings.length + (receipt.warningsOmitted ?? 0)} warning(s)` : "";
|
|
61776
61981
|
const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + warned;
|
|
@@ -61823,7 +62028,7 @@ registerTool(
|
|
|
61823
62028
|
} catch (error2) {
|
|
61824
62029
|
if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
|
|
61825
62030
|
const hint = error2.code === "no-full-disk-access" ? ` Grant Full Disk Access to the Node binary running this server (run the doctor tool for its path): ${FULL_DISK_ACCESS_GUIDE_URL}` : "";
|
|
61826
|
-
return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}
|
|
62031
|
+
return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
|
|
61827
62032
|
}
|
|
61828
62033
|
const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;
|
|
61829
62034
|
const skipped = receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "";
|
|
@@ -61851,7 +62056,7 @@ registerTool(
|
|
|
61851
62056
|
withErrorHandling(({ id: id2 }) => {
|
|
61852
62057
|
const note = notesManager.getNoteById(id2);
|
|
61853
62058
|
if (!note) {
|
|
61854
|
-
return
|
|
62059
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
61855
62060
|
}
|
|
61856
62061
|
if (note.passwordProtected) {
|
|
61857
62062
|
return errorResponse(
|