apple-notes-mcp 2.9.24 → 2.9.25
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 +26 -16
- package/build/index.js +749 -549
- 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,13 +41464,13 @@ 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"];
|
|
@@ -41253,13 +41528,13 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPriva
|
|
|
41253
41528
|
}
|
|
41254
41529
|
function cleanupTempDir(dir) {
|
|
41255
41530
|
try {
|
|
41256
|
-
if (
|
|
41531
|
+
if (existsSync5(dir)) rmSync(dir, { recursive: true, force: true });
|
|
41257
41532
|
} catch {
|
|
41258
41533
|
}
|
|
41259
41534
|
}
|
|
41260
41535
|
|
|
41261
41536
|
// src/utils/paperAttachments.ts
|
|
41262
|
-
import { execFileSync as
|
|
41537
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
41263
41538
|
import {
|
|
41264
41539
|
closeSync as closeSync3,
|
|
41265
41540
|
constants as constants3,
|
|
@@ -41272,10 +41547,10 @@ import {
|
|
|
41272
41547
|
unlinkSync as unlinkSync2,
|
|
41273
41548
|
writeSync as writeSync2
|
|
41274
41549
|
} from "node:fs";
|
|
41275
|
-
import { basename as basename2, dirname as dirname3, extname as extname2, join as
|
|
41550
|
+
import { basename as basename2, dirname as dirname3, extname as extname2, join as join9 } from "node:path";
|
|
41276
41551
|
|
|
41277
41552
|
// src/utils/attachmentAssets.ts
|
|
41278
|
-
import { execFileSync as
|
|
41553
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
41279
41554
|
import {
|
|
41280
41555
|
closeSync as closeSync2,
|
|
41281
41556
|
constants as constants2,
|
|
@@ -41289,11 +41564,11 @@ import {
|
|
|
41289
41564
|
unlinkSync,
|
|
41290
41565
|
writeSync
|
|
41291
41566
|
} from "node:fs";
|
|
41292
|
-
import { homedir as
|
|
41293
|
-
import { basename, dirname as dirname2, extname, isAbsolute as isAbsolute2, join as
|
|
41567
|
+
import { homedir as homedir8 } from "node:os";
|
|
41568
|
+
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
41569
|
import { gunzipSync as gunzipSync5 } from "node:zlib";
|
|
41295
|
-
var NOTES_CONTAINER_DIR =
|
|
41296
|
-
|
|
41570
|
+
var NOTES_CONTAINER_DIR = join8(
|
|
41571
|
+
homedir8(),
|
|
41297
41572
|
"Library/Group Containers/group.com.apple.notes"
|
|
41298
41573
|
);
|
|
41299
41574
|
var AttachmentStoreError = class extends Error {
|
|
@@ -41398,7 +41673,7 @@ function buildAttachmentRowsSql(notePk, columns) {
|
|
|
41398
41673
|
}
|
|
41399
41674
|
function runSqlite3(dbPath2, sql) {
|
|
41400
41675
|
try {
|
|
41401
|
-
return
|
|
41676
|
+
return execFileSync8("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
|
|
41402
41677
|
encoding: "utf8",
|
|
41403
41678
|
timeout: 1e4,
|
|
41404
41679
|
maxBuffer: 128 * 1024 * 1024,
|
|
@@ -41466,7 +41741,7 @@ function attachmentOrderFromNoteData(hex3) {
|
|
|
41466
41741
|
return null;
|
|
41467
41742
|
}
|
|
41468
41743
|
}
|
|
41469
|
-
function readNoteAttachmentRows(noteId3, dbPath2 =
|
|
41744
|
+
function readNoteAttachmentRows(noteId3, dbPath2 = join8(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
|
|
41470
41745
|
const { pk } = parseNoteId2(noteId3);
|
|
41471
41746
|
const columnList = runSqlite3(
|
|
41472
41747
|
dbPath2,
|
|
@@ -41527,7 +41802,7 @@ function boundedEntries(dir, limit) {
|
|
|
41527
41802
|
}
|
|
41528
41803
|
}
|
|
41529
41804
|
function resolveAccountDir(containerDir, accountIdentifier) {
|
|
41530
|
-
const accountsDir =
|
|
41805
|
+
const accountsDir = join8(containerDir, "Accounts");
|
|
41531
41806
|
let accountsReal;
|
|
41532
41807
|
try {
|
|
41533
41808
|
accountsReal = realpathSync2.native(accountsDir);
|
|
@@ -41536,14 +41811,14 @@ function resolveAccountDir(containerDir, accountIdentifier) {
|
|
|
41536
41811
|
}
|
|
41537
41812
|
const account = safeComponent(accountIdentifier);
|
|
41538
41813
|
if (account) {
|
|
41539
|
-
const dir = realInside(
|
|
41814
|
+
const dir = realInside(join8(accountsReal, account), accountsReal);
|
|
41540
41815
|
if (dir && isDirectory(dir)) return dir;
|
|
41541
41816
|
}
|
|
41542
41817
|
const entries2 = (boundedEntries(accountsReal, 64) ?? []).filter(
|
|
41543
|
-
(e) => isDirectory(
|
|
41818
|
+
(e) => isDirectory(join8(accountsReal, e))
|
|
41544
41819
|
);
|
|
41545
41820
|
if (entries2.length !== 1) return null;
|
|
41546
|
-
return realInside(
|
|
41821
|
+
return realInside(join8(accountsReal, entries2[0]), accountsReal);
|
|
41547
41822
|
}
|
|
41548
41823
|
function generationRank(name) {
|
|
41549
41824
|
const m = /^(\d+)_/.exec(name);
|
|
@@ -41551,23 +41826,23 @@ function generationRank(name) {
|
|
|
41551
41826
|
}
|
|
41552
41827
|
function generationDirs(base, accountDir) {
|
|
41553
41828
|
const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
|
|
41554
|
-
return entries2.map((e) => realInside(
|
|
41829
|
+
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
41830
|
}
|
|
41556
41831
|
function fallbackFiles(accountDir, rootName, identifier, generation, names) {
|
|
41557
|
-
const base =
|
|
41832
|
+
const base = join8(accountDir, rootName, identifier);
|
|
41558
41833
|
const found = [];
|
|
41559
41834
|
const add = (candidate) => {
|
|
41560
41835
|
const real = realInside(candidate, accountDir);
|
|
41561
41836
|
if (real && isRegularFile(real) && !found.includes(real)) found.push(real);
|
|
41562
41837
|
};
|
|
41563
41838
|
const gen = safeComponent(generation);
|
|
41564
|
-
if (gen) for (const name of names) add(
|
|
41839
|
+
if (gen) for (const name of names) add(join8(base, gen, name));
|
|
41565
41840
|
if (isDirectory(base)) {
|
|
41566
41841
|
for (const dir of generationDirs(base, accountDir))
|
|
41567
|
-
for (const name of names) add(
|
|
41568
|
-
for (const name of names) add(
|
|
41842
|
+
for (const name of names) add(join8(dir, name));
|
|
41843
|
+
for (const name of names) add(join8(base, name));
|
|
41569
41844
|
}
|
|
41570
|
-
for (const name of names) add(
|
|
41845
|
+
for (const name of names) add(join8(accountDir, rootName, `${identifier}${extname(name)}`));
|
|
41571
41846
|
return found;
|
|
41572
41847
|
}
|
|
41573
41848
|
function previewPixelArea(name) {
|
|
@@ -41579,7 +41854,7 @@ function previewFileInBundle(bundle, accountDir) {
|
|
|
41579
41854
|
const direct = [];
|
|
41580
41855
|
const nested = [];
|
|
41581
41856
|
for (const entry of boundedEntries(bundle, MAX_BUNDLE_ENTRIES) ?? []) {
|
|
41582
|
-
const child2 = realInside(
|
|
41857
|
+
const child2 = realInside(join8(bundle, entry), accountDir);
|
|
41583
41858
|
if (!child2) continue;
|
|
41584
41859
|
if (isRegularFile(child2)) direct.push(child2);
|
|
41585
41860
|
else if (isDirectory(child2)) nested.push(child2);
|
|
@@ -41587,14 +41862,14 @@ function previewFileInBundle(bundle, accountDir) {
|
|
|
41587
41862
|
nested.sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
|
|
41588
41863
|
const pick2 = (files) => files.find((f) => basename(f) === "Preview.png") ?? files.find((f) => PREVIEW_IMAGE_SUFFIXES.has(extname(f).toLowerCase())) ?? null;
|
|
41589
41864
|
for (const dir of nested) {
|
|
41590
|
-
const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(
|
|
41865
|
+
const files = (boundedEntries(dir, MAX_BUNDLE_ENTRIES) ?? []).map((e) => realInside(join8(dir, e), accountDir)).filter((p) => p !== null && isRegularFile(p));
|
|
41591
41866
|
const chosen = pick2(files);
|
|
41592
41867
|
if (chosen) return chosen;
|
|
41593
41868
|
}
|
|
41594
41869
|
return pick2(direct);
|
|
41595
41870
|
}
|
|
41596
41871
|
function listPreviewEntries(accountDir) {
|
|
41597
|
-
return boundedEntries(
|
|
41872
|
+
return boundedEntries(join8(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
|
|
41598
41873
|
}
|
|
41599
41874
|
function previewPaths(accountDir, identifier, entries2) {
|
|
41600
41875
|
const id2 = safeComponent(identifier);
|
|
@@ -41606,7 +41881,7 @@ function previewPaths(accountDir, identifier, entries2) {
|
|
|
41606
41881
|
}).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
|
|
41607
41882
|
const files = [];
|
|
41608
41883
|
for (const name of candidates) {
|
|
41609
|
-
const real = realInside(
|
|
41884
|
+
const real = realInside(join8(accountDir, "Previews", name), accountDir);
|
|
41610
41885
|
if (!real) continue;
|
|
41611
41886
|
const file = isRegularFile(real) ? real : isDirectory(real) ? previewFileInBundle(real, accountDir) : null;
|
|
41612
41887
|
if (file && !files.includes(file)) files.push(file);
|
|
@@ -41623,13 +41898,13 @@ function assetPathsFor(accountDir, row) {
|
|
|
41623
41898
|
const mediaName = safeComponent(row.mediaFilename);
|
|
41624
41899
|
const mediaGen = safeComponent(row.mediaGeneration);
|
|
41625
41900
|
if (mediaId && mediaName) {
|
|
41626
|
-
if (mediaGen) add(
|
|
41627
|
-
add(
|
|
41901
|
+
if (mediaGen) add(join8(accountDir, "Media", mediaId, mediaGen, mediaName));
|
|
41902
|
+
add(join8(accountDir, "Media", mediaId, mediaName));
|
|
41628
41903
|
}
|
|
41629
41904
|
const id2 = safeComponent(row.identifier);
|
|
41630
41905
|
if (!id2) return found;
|
|
41631
41906
|
const ownName = safeComponent(row.filename);
|
|
41632
|
-
if (ownName) add(
|
|
41907
|
+
if (ownName) add(join8(accountDir, "Media", id2, ownName));
|
|
41633
41908
|
for (const file of fallbackFiles(accountDir, "FallbackImages", id2, row.fallbackImageGeneration, [
|
|
41634
41909
|
"FallbackImage.png",
|
|
41635
41910
|
"FallbackImage.jpg"
|
|
@@ -41720,10 +41995,10 @@ function canonicalTail(p) {
|
|
|
41720
41995
|
const tail = [];
|
|
41721
41996
|
for (; ; ) {
|
|
41722
41997
|
try {
|
|
41723
|
-
return
|
|
41998
|
+
return join8(realpathSync2.native(current), ...tail);
|
|
41724
41999
|
} catch {
|
|
41725
42000
|
const parent = dirname2(current);
|
|
41726
|
-
if (parent === current) return
|
|
42001
|
+
if (parent === current) return join8(current, ...tail);
|
|
41727
42002
|
tail.unshift(basename(current));
|
|
41728
42003
|
current = parent;
|
|
41729
42004
|
}
|
|
@@ -41806,7 +42081,7 @@ function exportOneAttachment(record2, dir, source) {
|
|
|
41806
42081
|
if (!source) return base;
|
|
41807
42082
|
const name = exportFileName(record2, source.path, source.kind);
|
|
41808
42083
|
for (let attempt = 1; attempt <= MAX_COLLISION_SUFFIX; attempt++) {
|
|
41809
|
-
const dest =
|
|
42084
|
+
const dest = join8(dir, collisionName(name, attempt));
|
|
41810
42085
|
try {
|
|
41811
42086
|
if (dirname2(dest) !== resolve2(dir))
|
|
41812
42087
|
throw new Error(`Refusing to write outside the export directory: "${dest}"`);
|
|
@@ -41935,7 +42210,7 @@ function buildDrawingRowsSql(notePk, columns) {
|
|
|
41935
42210
|
}
|
|
41936
42211
|
function runSqlite4(dbPath2, sql) {
|
|
41937
42212
|
try {
|
|
41938
|
-
return
|
|
42213
|
+
return execFileSync9("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
|
|
41939
42214
|
encoding: "utf8",
|
|
41940
42215
|
timeout: 1e4,
|
|
41941
42216
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -41975,7 +42250,7 @@ function parseDrawingRows(json2) {
|
|
|
41975
42250
|
}
|
|
41976
42251
|
return rows.sort((a, b) => a.pk - b.pk);
|
|
41977
42252
|
}
|
|
41978
|
-
function readDrawingRows(noteId3, dbPath2 =
|
|
42253
|
+
function readDrawingRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
|
|
41979
42254
|
const { pk } = parseNoteId2(noteId3);
|
|
41980
42255
|
const columns = new Set(
|
|
41981
42256
|
runSqlite4(
|
|
@@ -42019,16 +42294,16 @@ var byGenerationDesc = (a, b) => generationRank(basename2(b)) - generationRank(b
|
|
|
42019
42294
|
function findFallbackImage(accountDir, identifier, generation) {
|
|
42020
42295
|
const id2 = safeComponent(identifier);
|
|
42021
42296
|
if (!id2) return null;
|
|
42022
|
-
const base =
|
|
42297
|
+
const base = join9(accountDir, "FallbackImages", id2);
|
|
42023
42298
|
const names = ["FallbackImage.png", "FallbackImage.jpg"];
|
|
42024
42299
|
const candidates = [];
|
|
42025
42300
|
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(
|
|
42301
|
+
if (gen) for (const n of names) candidates.push(join9(base, gen, n));
|
|
42302
|
+
const gens = entries(base, MAX_DIR_ENTRIES).map((e) => join9(base, e)).sort(byGenerationDesc);
|
|
42303
|
+
for (const g of gens) for (const n of names) candidates.push(join9(g, n));
|
|
42304
|
+
for (const n of names) candidates.push(join9(base, n));
|
|
42305
|
+
candidates.push(join9(accountDir, "FallbackImages", `${id2}.png`));
|
|
42306
|
+
candidates.push(join9(accountDir, "FallbackImages", `${id2}.jpg`));
|
|
42032
42307
|
for (const c of candidates) {
|
|
42033
42308
|
const real = realInside(c, accountDir);
|
|
42034
42309
|
if (real && kindOf(real) === "file") return real;
|
|
@@ -42038,21 +42313,21 @@ function findFallbackImage(accountDir, identifier, generation) {
|
|
|
42038
42313
|
function findLargestPreview(accountDir, identifier) {
|
|
42039
42314
|
const id2 = safeComponent(identifier);
|
|
42040
42315
|
if (!id2) return null;
|
|
42041
|
-
const dir =
|
|
42316
|
+
const dir = join9(accountDir, "Previews");
|
|
42042
42317
|
const prefix = `${id2}-`.toLowerCase();
|
|
42043
42318
|
const names = entries(dir, MAX_PREVIEW_DIR_ENTRIES2).filter((n) => n.toLowerCase().startsWith(prefix)).filter((n) => {
|
|
42044
42319
|
const ext = extname2(n).toLowerCase();
|
|
42045
42320
|
return !ext || IMAGE_SUFFIXES.has(ext) || /^\.\d+$/.test(ext);
|
|
42046
42321
|
}).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
|
|
42047
42322
|
for (const name of names) {
|
|
42048
|
-
const real = realInside(
|
|
42323
|
+
const real = realInside(join9(dir, name), accountDir);
|
|
42049
42324
|
if (!real) continue;
|
|
42050
42325
|
const kind = kindOf(real);
|
|
42051
42326
|
if (kind === "file") return real;
|
|
42052
42327
|
if (kind !== "dir") continue;
|
|
42053
|
-
const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(
|
|
42328
|
+
const gens = entries(real, MAX_DIR_ENTRIES).map((e) => realInside(join9(real, e), accountDir)).filter((p) => p !== null && kindOf(p) === "dir").sort(byGenerationDesc);
|
|
42054
42329
|
for (const g of [...gens, real]) {
|
|
42055
|
-
const file = realInside(
|
|
42330
|
+
const file = realInside(join9(g, "Preview.png"), accountDir);
|
|
42056
42331
|
if (file && kindOf(file) === "file") return file;
|
|
42057
42332
|
}
|
|
42058
42333
|
}
|
|
@@ -42065,7 +42340,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
|
|
|
42065
42340
|
const id2 = safeComponent(row.identifier);
|
|
42066
42341
|
const fallbackImagePath = accountDir ? findFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : null;
|
|
42067
42342
|
const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
|
|
42068
|
-
const bundle = accountDir && id2 && kind === "paper" ? realInside(
|
|
42343
|
+
const bundle = accountDir && id2 && kind === "paper" ? realInside(join9(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
|
|
42069
42344
|
let raster = null;
|
|
42070
42345
|
for (const [path10, source] of [
|
|
42071
42346
|
[fallbackImagePath, "fallback"],
|
|
@@ -42262,9 +42537,9 @@ function scopeConflictMessage(reason) {
|
|
|
42262
42537
|
|
|
42263
42538
|
// src/services/appleNotesManager.ts
|
|
42264
42539
|
var import_turndown = __toESM(require_turndown_cjs(), 1);
|
|
42265
|
-
import { existsSync as
|
|
42266
|
-
import { homedir as
|
|
42267
|
-
import { join as
|
|
42540
|
+
import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
|
|
42541
|
+
import { homedir as homedir9, tmpdir as tmpdir2 } from "os";
|
|
42542
|
+
import { join as join10 } from "path";
|
|
42268
42543
|
var FIELD_SEP = "";
|
|
42269
42544
|
var RECORD_SEP = "";
|
|
42270
42545
|
var AS_FIELD_SEP = "(character id 31)";
|
|
@@ -42486,6 +42761,11 @@ function smartFolderDestinationError(folderPath) {
|
|
|
42486
42761
|
}
|
|
42487
42762
|
);
|
|
42488
42763
|
}
|
|
42764
|
+
function smartFoldersReadError(kind, message) {
|
|
42765
|
+
return new CodedError(message, {
|
|
42766
|
+
code: kind === "unsupported_schema" ? "unsupported" : kind === "no_fda" ? "full_disk_access_missing" : "operation_failed"
|
|
42767
|
+
});
|
|
42768
|
+
}
|
|
42489
42769
|
function throwIfSmartFolderDestination(error2, folderPath) {
|
|
42490
42770
|
if (error2?.includes(SMART_FOLDER_DESTINATION)) throw smartFolderDestinationError(folderPath);
|
|
42491
42771
|
}
|
|
@@ -42621,8 +42901,8 @@ var ExpectedBodies = class {
|
|
|
42621
42901
|
if (body.length <= INLINE_EXPECTED_BODY_LIMIT) {
|
|
42622
42902
|
return { setup: "", operand: `"${escapeHtmlForAppleScript(body)}"` };
|
|
42623
42903
|
}
|
|
42624
|
-
this.directory ??= mkdtempSync2(
|
|
42625
|
-
const file =
|
|
42904
|
+
this.directory ??= mkdtempSync2(join10(tmpdir2(), "apple-notes-expected-body-"));
|
|
42905
|
+
const file = join10(this.directory, `body-${this.files++}.html`);
|
|
42626
42906
|
writeFileSync(file, body, { encoding: "utf8", mode: 384 });
|
|
42627
42907
|
return {
|
|
42628
42908
|
setup: `
|
|
@@ -42668,8 +42948,8 @@ function getNoteLinkFromDB(coreDataId3) {
|
|
|
42668
42948
|
const match = coreDataId3.match(/\/p(\d+)$/);
|
|
42669
42949
|
if (!match) return null;
|
|
42670
42950
|
const pk = parseInt(match[1], 10);
|
|
42671
|
-
const dbPath2 =
|
|
42672
|
-
if (!
|
|
42951
|
+
const dbPath2 = join10(homedir9(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
|
|
42952
|
+
if (!existsSync6(dbPath2)) return null;
|
|
42673
42953
|
try {
|
|
42674
42954
|
const { DatabaseSync } = __require("node:sqlite");
|
|
42675
42955
|
const db = new DatabaseSync(dbPath2, { readOnly: true });
|
|
@@ -43303,9 +43583,12 @@ var AppleNotesManager = class {
|
|
|
43303
43583
|
if currentBody is not ${expected.operand} and currentBody is not ${expected.operand} & linefeed then return "SAFETY_CONFLICT"
|
|
43304
43584
|
delete noteRef
|
|
43305
43585
|
end considering
|
|
43586
|
+
set __stillThere to missing value
|
|
43306
43587
|
try
|
|
43307
|
-
|
|
43588
|
+
set __stillThere to (id of notes of originalFolder) contains "${safeId}"
|
|
43308
43589
|
end try
|
|
43590
|
+
if __stillThere is missing value then return "SAFETY_DELETE_UNVERIFIED"
|
|
43591
|
+
if __stillThere then return "SAFETY_NOT_DELETED"
|
|
43309
43592
|
return "SAFETY_DELETED"
|
|
43310
43593
|
`);
|
|
43311
43594
|
result = executeMutationAppleScript(script);
|
|
@@ -43328,6 +43611,7 @@ var AppleNotesManager = class {
|
|
|
43328
43611
|
if (status === "SAFETY_IN_RECENTLY_DELETED") return { status: "in-recently-deleted" };
|
|
43329
43612
|
if (status === "SAFETY_CONTAINER_UNKNOWN") return { status: "container-unknown" };
|
|
43330
43613
|
if (status === "SAFETY_NOT_DELETED") return { status: "not-deleted" };
|
|
43614
|
+
if (status === "SAFETY_DELETE_UNVERIFIED") return { status: "unverified" };
|
|
43331
43615
|
return status === "SAFETY_DELETED" ? { status: "deleted" } : { status: "failed" };
|
|
43332
43616
|
}
|
|
43333
43617
|
/**
|
|
@@ -43475,7 +43759,8 @@ var AppleNotesManager = class {
|
|
|
43475
43759
|
*/
|
|
43476
43760
|
listSmartFolders(options = {}) {
|
|
43477
43761
|
const result = readSmartFolders();
|
|
43478
|
-
if (!result.folders)
|
|
43762
|
+
if (!result.folders)
|
|
43763
|
+
throw smartFoldersReadError(result.error, result.message || "Failed to read smart folders.");
|
|
43479
43764
|
if (!options.includeMatchingNotes) return result.folders;
|
|
43480
43765
|
const limit = options.limit ?? 50;
|
|
43481
43766
|
return result.folders.map((folder) => {
|
|
@@ -44863,7 +45148,7 @@ var AppleNotesManager = class {
|
|
|
44863
45148
|
if (parts[0] !== "OK") {
|
|
44864
45149
|
return { success: false, error: parts[1]?.trim() || "attachment not found" };
|
|
44865
45150
|
}
|
|
44866
|
-
if (!
|
|
45151
|
+
if (!existsSync6(abs) || fileSize(abs) === 0) {
|
|
44867
45152
|
return { success: false, error: `Notes reported success but no file was written to ${abs}` };
|
|
44868
45153
|
}
|
|
44869
45154
|
return {
|
|
@@ -45371,15 +45656,15 @@ var AppleNotesManager = class {
|
|
|
45371
45656
|
};
|
|
45372
45657
|
|
|
45373
45658
|
// src/utils/syncDetection.ts
|
|
45374
|
-
import { execFileSync as
|
|
45375
|
-
import * as
|
|
45376
|
-
import * as
|
|
45377
|
-
import * as
|
|
45378
|
-
var
|
|
45379
|
-
|
|
45659
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
45660
|
+
import * as fs5 from "fs";
|
|
45661
|
+
import * as path5 from "path";
|
|
45662
|
+
import * as os5 from "os";
|
|
45663
|
+
var NOTES_DB_PATH6 = path5.join(
|
|
45664
|
+
os5.homedir(),
|
|
45380
45665
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45381
45666
|
);
|
|
45382
|
-
var WAL_PATH = `${
|
|
45667
|
+
var WAL_PATH = `${NOTES_DB_PATH6}-wal`;
|
|
45383
45668
|
var RECENT_ACTIVITY_THRESHOLD_SECONDS = 5;
|
|
45384
45669
|
var SYNC_STATUS_CACHE_TTL_MS = 2e3;
|
|
45385
45670
|
var cachedSyncStatus = null;
|
|
@@ -45395,14 +45680,14 @@ function getSyncStatus(useCache = true) {
|
|
|
45395
45680
|
recentActivity: false
|
|
45396
45681
|
};
|
|
45397
45682
|
try {
|
|
45398
|
-
if (!
|
|
45683
|
+
if (!fs5.existsSync(NOTES_DB_PATH6)) {
|
|
45399
45684
|
status.error = "Notes database not found";
|
|
45400
45685
|
cachedSyncStatus = status;
|
|
45401
45686
|
cacheTimestamp = Date.now();
|
|
45402
45687
|
return status;
|
|
45403
45688
|
}
|
|
45404
|
-
if (
|
|
45405
|
-
const walStats =
|
|
45689
|
+
if (fs5.existsSync(WAL_PATH)) {
|
|
45690
|
+
const walStats = fs5.statSync(WAL_PATH);
|
|
45406
45691
|
const secondsAgo = (Date.now() - walStats.mtimeMs) / 1e3;
|
|
45407
45692
|
status.secondsSinceLastChange = Math.round(secondsAgo);
|
|
45408
45693
|
status.recentActivity = secondsAgo < RECENT_ACTIVITY_THRESHOLD_SECONDS;
|
|
@@ -45416,9 +45701,9 @@ function getSyncStatus(useCache = true) {
|
|
|
45416
45701
|
WHERE object.ZCLOUDSTATE = state.Z_PK
|
|
45417
45702
|
);
|
|
45418
45703
|
`;
|
|
45419
|
-
const result =
|
|
45704
|
+
const result = execFileSync10(
|
|
45420
45705
|
"sqlite3",
|
|
45421
|
-
["-readonly",
|
|
45706
|
+
["-readonly", NOTES_DB_PATH6, query2.replace(/\n/g, " ")],
|
|
45422
45707
|
{
|
|
45423
45708
|
encoding: "utf8",
|
|
45424
45709
|
timeout: 5e3,
|
|
@@ -45475,12 +45760,12 @@ function withSyncAwarenessSync(operation, fn) {
|
|
|
45475
45760
|
}
|
|
45476
45761
|
|
|
45477
45762
|
// src/utils/noteMetadata.ts
|
|
45478
|
-
import { execFileSync as
|
|
45479
|
-
import * as
|
|
45480
|
-
import * as
|
|
45481
|
-
import * as
|
|
45482
|
-
var
|
|
45483
|
-
|
|
45763
|
+
import { execFileSync as execFileSync11 } from "child_process";
|
|
45764
|
+
import * as fs6 from "fs";
|
|
45765
|
+
import * as path6 from "path";
|
|
45766
|
+
import * as os6 from "os";
|
|
45767
|
+
var NOTES_DB_PATH7 = path6.join(
|
|
45768
|
+
os6.homedir(),
|
|
45484
45769
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45485
45770
|
);
|
|
45486
45771
|
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 +45781,7 @@ var COLUMN_MAP = [
|
|
|
45496
45781
|
{ key: "smartFolderQuery", column: "ZSMARTFOLDERQUERYJSON", type: "text" }
|
|
45497
45782
|
];
|
|
45498
45783
|
function runSqlite5(query2) {
|
|
45499
|
-
return
|
|
45784
|
+
return execFileSync11("sqlite3", ["-readonly", NOTES_DB_PATH7, query2], {
|
|
45500
45785
|
encoding: "utf8",
|
|
45501
45786
|
timeout: 5e3,
|
|
45502
45787
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -45521,7 +45806,7 @@ function getNoteMetadata(noteId3) {
|
|
|
45521
45806
|
};
|
|
45522
45807
|
}
|
|
45523
45808
|
const pk = pkMatch[1];
|
|
45524
|
-
if (!
|
|
45809
|
+
if (!fs6.existsSync(NOTES_DB_PATH7)) {
|
|
45525
45810
|
return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
|
|
45526
45811
|
}
|
|
45527
45812
|
try {
|
|
@@ -45567,12 +45852,12 @@ function getNoteMetadata(noteId3) {
|
|
|
45567
45852
|
import { gunzipSync as gunzipSync6 } from "zlib";
|
|
45568
45853
|
|
|
45569
45854
|
// src/utils/noteStoreSql.ts
|
|
45570
|
-
import { execFileSync as
|
|
45571
|
-
import * as
|
|
45572
|
-
import * as
|
|
45573
|
-
import * as
|
|
45574
|
-
var
|
|
45575
|
-
|
|
45855
|
+
import { execFileSync as execFileSync12 } from "child_process";
|
|
45856
|
+
import * as fs7 from "fs";
|
|
45857
|
+
import * as os7 from "os";
|
|
45858
|
+
import * as path7 from "path";
|
|
45859
|
+
var NOTES_DB_PATH8 = path7.join(
|
|
45860
|
+
os7.homedir(),
|
|
45576
45861
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45577
45862
|
);
|
|
45578
45863
|
var CORE_DATA_EPOCH_MS = Date.UTC(2001, 0, 1);
|
|
@@ -45610,7 +45895,7 @@ function renderValue(value) {
|
|
|
45610
45895
|
return `ieee754_from_blob(x'${doubleToHex(double)}')`;
|
|
45611
45896
|
}
|
|
45612
45897
|
function runReadOnlySql(dbPath2, sql, params = {}) {
|
|
45613
|
-
if (!
|
|
45898
|
+
if (!fs7.existsSync(dbPath2)) throw new NoteStoreError(STORE_FDA_MESSAGE, "no_fda");
|
|
45614
45899
|
const args = ["-readonly"];
|
|
45615
45900
|
const names = Object.keys(params);
|
|
45616
45901
|
if (names.length) args.push("-cmd", ".parameter init");
|
|
@@ -45622,7 +45907,7 @@ function runReadOnlySql(dbPath2, sql, params = {}) {
|
|
|
45622
45907
|
}
|
|
45623
45908
|
args.push(dbPath2, sql);
|
|
45624
45909
|
try {
|
|
45625
|
-
return
|
|
45910
|
+
return execFileSync12("sqlite3", args, {
|
|
45626
45911
|
encoding: "utf8",
|
|
45627
45912
|
timeout: 3e4,
|
|
45628
45913
|
maxBuffer: 512 * 1024 * 1024,
|
|
@@ -45815,7 +46100,7 @@ function buildSpecialNotesSql(columns, kind, scoped) {
|
|
|
45815
46100
|
].join(" ");
|
|
45816
46101
|
}
|
|
45817
46102
|
function listSpecialNotes(options) {
|
|
45818
|
-
const dbPath2 = options.dbPath ??
|
|
46103
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
45819
46104
|
const limit = Math.min(
|
|
45820
46105
|
Math.max(1, Math.trunc(options.limit ?? SPECIAL_LIMIT.DEFAULT)),
|
|
45821
46106
|
SPECIAL_LIMIT.MAX
|
|
@@ -45871,7 +46156,7 @@ function toSpecialRow(row, uuid2, paths, accountNames, kind) {
|
|
|
45871
46156
|
return note;
|
|
45872
46157
|
}
|
|
45873
46158
|
var EXACT_NOTE_ID = /^x-coredata:\/\/([0-9A-F-]+)\/ICNote\/p(\d+)$/i;
|
|
45874
|
-
function quickNoteFlag(noteId3, dbPath2 =
|
|
46159
|
+
function quickNoteFlag(noteId3, dbPath2 = NOTES_DB_PATH8) {
|
|
45875
46160
|
const match = EXACT_NOTE_ID.exec(noteId3);
|
|
45876
46161
|
if (!match) throw new NoteStoreError(`Not an exact note id: ${noteId3}`, "invalid_input");
|
|
45877
46162
|
const columns = readColumns(dbPath2);
|
|
@@ -45923,7 +46208,7 @@ function referencedObjects(hex3) {
|
|
|
45923
46208
|
}
|
|
45924
46209
|
}
|
|
45925
46210
|
function nativeTagInventory(options = {}) {
|
|
45926
|
-
const dbPath2 = options.dbPath ??
|
|
46211
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
45927
46212
|
const columns = readColumns(dbPath2);
|
|
45928
46213
|
const context = readStoreContext(dbPath2, columns);
|
|
45929
46214
|
const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
|
|
@@ -45985,15 +46270,27 @@ function assembleInventory(rows, accounts, scope2) {
|
|
|
45985
46270
|
}
|
|
45986
46271
|
|
|
45987
46272
|
// src/utils/noteBlocks.ts
|
|
45988
|
-
import { execFileSync as
|
|
45989
|
-
import { existsSync as
|
|
45990
|
-
import { homedir as
|
|
45991
|
-
import { join as
|
|
46273
|
+
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
46274
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
46275
|
+
import { homedir as homedir13 } from "node:os";
|
|
46276
|
+
import { join as join14 } from "node:path";
|
|
45992
46277
|
import { gunzipSync as gunzipSync7 } from "node:zlib";
|
|
45993
|
-
var
|
|
46278
|
+
var NOTE_BLOCKS_ERROR_CODES = {
|
|
46279
|
+
"invalid-id": "validation_error",
|
|
46280
|
+
"no-full-disk-access": "full_disk_access_missing",
|
|
46281
|
+
"not-found": "not_found",
|
|
46282
|
+
"no-body": "operation_failed",
|
|
46283
|
+
encrypted: "unsupported",
|
|
46284
|
+
"decompress-failed": "unsupported",
|
|
46285
|
+
"malformed-protobuf": "unsupported",
|
|
46286
|
+
"unsupported-structure": "unsupported",
|
|
46287
|
+
"invalid-runs": "unsupported",
|
|
46288
|
+
"query-failed": "operation_failed"
|
|
46289
|
+
};
|
|
46290
|
+
var NoteBlocksError = class extends CodedError {
|
|
45994
46291
|
code;
|
|
45995
46292
|
constructor(code, message) {
|
|
45996
|
-
super(message);
|
|
46293
|
+
super(message, { code: NOTE_BLOCKS_ERROR_CODES[code] });
|
|
45997
46294
|
this.name = "NoteBlocksError";
|
|
45998
46295
|
this.code = code;
|
|
45999
46296
|
}
|
|
@@ -46331,23 +46628,23 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
|
|
|
46331
46628
|
undecodedFields: doc.undecodedFields
|
|
46332
46629
|
};
|
|
46333
46630
|
}
|
|
46334
|
-
var
|
|
46335
|
-
|
|
46631
|
+
var NOTES_DB_PATH9 = join14(
|
|
46632
|
+
homedir13(),
|
|
46336
46633
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46337
46634
|
);
|
|
46338
|
-
function readNoteBlocks(id2, { dbPath: dbPath2 =
|
|
46635
|
+
function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
|
|
46339
46636
|
const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]{1,18})$/i.exec(id2)?.[1];
|
|
46340
46637
|
if (!pk)
|
|
46341
46638
|
throw new NoteBlocksError(
|
|
46342
46639
|
"invalid-id",
|
|
46343
46640
|
`Invalid note ID: expected x-coredata://<store>/ICNote/p<number>`
|
|
46344
46641
|
);
|
|
46345
|
-
if (!
|
|
46642
|
+
if (!existsSync10(dbPath2))
|
|
46346
46643
|
throw new NoteBlocksError("no-full-disk-access", "The Notes database is not readable");
|
|
46347
46644
|
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
46645
|
let output;
|
|
46349
46646
|
try {
|
|
46350
|
-
output =
|
|
46647
|
+
output = execFileSync13(
|
|
46351
46648
|
"/usr/bin/sqlite3",
|
|
46352
46649
|
["-readonly", "-cmd", ".parameter init", "-cmd", `.parameter set @pk ${pk}`, dbPath2, sql],
|
|
46353
46650
|
{
|
|
@@ -46494,7 +46791,7 @@ function resolveFolderPath(folders, paths, input, accountPk) {
|
|
|
46494
46791
|
);
|
|
46495
46792
|
}
|
|
46496
46793
|
function listRecentNotes(options = {}) {
|
|
46497
|
-
const dbPath2 = options.dbPath ??
|
|
46794
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
46498
46795
|
const limit = Math.min(
|
|
46499
46796
|
Math.max(1, Math.trunc(options.limit ?? RECENT_LIMIT.DEFAULT)),
|
|
46500
46797
|
RECENT_LIMIT.MAX
|
|
@@ -46549,346 +46846,108 @@ function toRecentRow(row, context, paths, accountNames, options) {
|
|
|
46549
46846
|
account: row.account !== null ? accountNames.get(row.account) ?? null : null,
|
|
46550
46847
|
created: coreDataToIso(row.created),
|
|
46551
46848
|
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}`);
|
|
46849
|
+
modifiedCheckpoint: checkpointFromBits(row.bits, row.pk),
|
|
46850
|
+
pinned: Boolean(row.pinned),
|
|
46851
|
+
locked: Boolean(row.locked),
|
|
46852
|
+
inRecentlyDeleted: Boolean(row.trash),
|
|
46853
|
+
markedForDeletion: Boolean(row.tombstoned)
|
|
46854
|
+
};
|
|
46855
|
+
const text2 = row.locked || row.cloud ? null : decodeBodyText(row.data);
|
|
46856
|
+
if (options.wordCounts) {
|
|
46857
|
+
const stats = text2 === null ? null : textStats(text2);
|
|
46858
|
+
note.wordCount = stats?.wordCount ?? null;
|
|
46859
|
+
note.charCount = stats?.charCount ?? null;
|
|
46795
46860
|
}
|
|
46796
|
-
if (
|
|
46797
|
-
|
|
46798
|
-
|
|
46799
|
-
`No ${label} found for ${missing.join(", ")}. A numeric key must belong to a ${label}, not another object type.`
|
|
46800
|
-
);
|
|
46861
|
+
if (options.bodyPreview) {
|
|
46862
|
+
note.textDecoded = text2 !== null;
|
|
46863
|
+
note.bodyPreview = text2 !== null ? previewText(text2) : row.snippet ? previewText(row.snippet) : null;
|
|
46801
46864
|
}
|
|
46802
|
-
return
|
|
46865
|
+
return note;
|
|
46803
46866
|
}
|
|
46804
|
-
|
|
46805
|
-
|
|
46806
|
-
|
|
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;
|
|
46822
|
-
}
|
|
46823
|
-
const store = query2.store?.toUpperCase();
|
|
46824
|
-
for (const row of query2.rows) {
|
|
46825
|
-
const fields = {};
|
|
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
|
-
}
|
|
46838
|
-
}
|
|
46839
|
-
return result;
|
|
46867
|
+
function buildFolderCountsSql(columns) {
|
|
46868
|
+
requireColumns(columns, ["Z_PK", "Z_ENT", "ZFOLDER"], "list-folder-tree");
|
|
46869
|
+
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;`;
|
|
46840
46870
|
}
|
|
46841
|
-
|
|
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
|
-
);
|
|
46871
|
+
var KIND_ORDER = { folder: 0, smart: 1, trash: 2 };
|
|
46872
|
+
function compareNodes(a, b) {
|
|
46873
|
+
return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
|
|
46849
46874
|
}
|
|
46850
|
-
|
|
46851
|
-
|
|
46852
|
-
|
|
46853
|
-
|
|
46854
|
-
|
|
46855
|
-
|
|
46875
|
+
function folderTree(options = {}) {
|
|
46876
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
46877
|
+
const columns = readColumns(dbPath2);
|
|
46878
|
+
const context = readStoreContext(dbPath2, columns);
|
|
46879
|
+
const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
|
|
46880
|
+
const counts = new Map(
|
|
46881
|
+
parseJsonLines(
|
|
46882
|
+
runReadOnlySql(dbPath2, buildFolderCountsSql(columns))
|
|
46883
|
+
).map((row) => [row.folder, row.n])
|
|
46856
46884
|
);
|
|
46885
|
+
return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
|
|
46857
46886
|
}
|
|
46858
|
-
function
|
|
46859
|
-
|
|
46860
|
-
|
|
46861
|
-
|
|
46862
|
-
|
|
46863
|
-
|
|
46864
|
-
|
|
46865
|
-
|
|
46866
|
-
|
|
46867
|
-
|
|
46887
|
+
function assembleFolderTree(context, counts, includeDeleted, scope2) {
|
|
46888
|
+
const paths = folderPaths(context.folders);
|
|
46889
|
+
const visible2 = context.folders.filter(
|
|
46890
|
+
(folder) => (includeDeleted || !folder.tombstoned) && (!scope2 || folder.account === scope2.pk)
|
|
46891
|
+
);
|
|
46892
|
+
const byPk = new Map(visible2.map((folder) => [folder.pk, folder]));
|
|
46893
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
46894
|
+
for (const folder of visible2) {
|
|
46895
|
+
const node = {
|
|
46896
|
+
id: `x-coredata://${context.uuid}/ICFolder/p${folder.pk}`,
|
|
46897
|
+
identifier: folder.identifier,
|
|
46898
|
+
name: folder.name ?? "",
|
|
46899
|
+
path: paths.get(folder.pk) ?? "",
|
|
46900
|
+
kind: folder.trash ? "trash" : folder.folderType === 2 ? "smart" : "folder",
|
|
46901
|
+
noteCount: counts.get(folder.pk) ?? 0,
|
|
46902
|
+
totalNoteCount: 0,
|
|
46903
|
+
children: []
|
|
46904
|
+
};
|
|
46905
|
+
if (includeDeleted) node.markedForDeletion = Boolean(folder.tombstoned);
|
|
46906
|
+
nodes.set(folder.pk, node);
|
|
46868
46907
|
}
|
|
46869
|
-
|
|
46870
|
-
|
|
46871
|
-
|
|
46872
|
-
|
|
46873
|
-
|
|
46874
|
-
|
|
46875
|
-
|
|
46876
|
-
|
|
46877
|
-
|
|
46878
|
-
const
|
|
46879
|
-
const
|
|
46880
|
-
|
|
46881
|
-
|
|
46882
|
-
|
|
46883
|
-
|
|
46884
|
-
|
|
46885
|
-
|
|
46886
|
-
|
|
46887
|
-
|
|
46888
|
-
|
|
46889
|
-
|
|
46890
|
-
|
|
46908
|
+
const parentOf = (folder) => {
|
|
46909
|
+
if (folder.parent === null || !byPk.has(folder.parent)) return void 0;
|
|
46910
|
+
const seen = /* @__PURE__ */ new Set([folder.pk]);
|
|
46911
|
+
for (let at = byPk.get(folder.parent); at; at = at.parent !== null ? byPk.get(at.parent) : void 0) {
|
|
46912
|
+
if (seen.has(at.pk)) return void 0;
|
|
46913
|
+
seen.add(at.pk);
|
|
46914
|
+
}
|
|
46915
|
+
return folder.parent;
|
|
46916
|
+
};
|
|
46917
|
+
const roots = /* @__PURE__ */ new Map();
|
|
46918
|
+
for (const folder of visible2) {
|
|
46919
|
+
const node = nodes.get(folder.pk);
|
|
46920
|
+
const parent = parentOf(folder);
|
|
46921
|
+
if (parent !== void 0) nodes.get(parent).children.push(node);
|
|
46922
|
+
else {
|
|
46923
|
+
const key = folder.account !== null && context.accounts.some((a) => a.pk === folder.account) ? folder.account : null;
|
|
46924
|
+
roots.set(key, [...roots.get(key) ?? [], node]);
|
|
46925
|
+
}
|
|
46926
|
+
}
|
|
46927
|
+
const finish = (node) => {
|
|
46928
|
+
node.children.sort(compareNodes);
|
|
46929
|
+
node.totalNoteCount = node.noteCount + node.children.reduce((sum, c) => sum + finish(c), 0);
|
|
46930
|
+
return node.totalNoteCount;
|
|
46931
|
+
};
|
|
46932
|
+
const accounts = [];
|
|
46933
|
+
const addAccount = (name, identifier, folders) => {
|
|
46934
|
+
folders.sort(compareNodes);
|
|
46935
|
+
let noteCount = 0;
|
|
46936
|
+
for (const node of folders) {
|
|
46937
|
+
const total = finish(node);
|
|
46938
|
+
if (node.kind === "folder") noteCount += total;
|
|
46939
|
+
}
|
|
46940
|
+
accounts.push({ account: name, identifier, noteCount, folders });
|
|
46891
46941
|
};
|
|
46942
|
+
for (const account of scope2 ? [scope2] : context.accounts) {
|
|
46943
|
+
addAccount(account.name, account.identifier, roots.get(account.pk) ?? []);
|
|
46944
|
+
}
|
|
46945
|
+
const orphans = roots.get(null);
|
|
46946
|
+
if (orphans && includeDeleted) addAccount("unknown", null, orphans);
|
|
46947
|
+
return { accounts, folderCount: accounts.reduce((sum, a) => sum + countNodes(a.folders), 0) };
|
|
46948
|
+
}
|
|
46949
|
+
function countNodes(nodes) {
|
|
46950
|
+
return nodes.reduce((sum, node) => sum + 1 + countNodes(node.children), 0);
|
|
46892
46951
|
}
|
|
46893
46952
|
|
|
46894
46953
|
// src/utils/contentWarnings.ts
|
|
@@ -48573,7 +48632,15 @@ function mutateBackground(request, operation, data, verify, deps) {
|
|
|
48573
48632
|
transportError = error2 ?? {};
|
|
48574
48633
|
transportMessage = describeTransportFailure(error2);
|
|
48575
48634
|
}
|
|
48576
|
-
|
|
48635
|
+
let after;
|
|
48636
|
+
try {
|
|
48637
|
+
after = deps.read(request.id);
|
|
48638
|
+
} catch (error2) {
|
|
48639
|
+
throw new CodedError(
|
|
48640
|
+
`Operation outcome uncertain; read exact note before any retry: readback failed: ${error2 instanceof Error ? error2.message : String(error2)}${transportMessage ? "; " + transportMessage : ""}`,
|
|
48641
|
+
{ code: "verification_failed", indeterminate: true }
|
|
48642
|
+
);
|
|
48643
|
+
}
|
|
48577
48644
|
try {
|
|
48578
48645
|
verify(before, after);
|
|
48579
48646
|
} catch (error2) {
|
|
@@ -48839,14 +48906,27 @@ var FEATURES = [
|
|
|
48839
48906
|
},
|
|
48840
48907
|
{
|
|
48841
48908
|
name: "fullDiskAccessReads",
|
|
48842
|
-
description: "Read-only reads of the Notes database:
|
|
48909
|
+
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
48910
|
tools: [
|
|
48844
48911
|
"query-notes",
|
|
48845
48912
|
"get-checklist-state",
|
|
48846
48913
|
"get-note-metadata",
|
|
48847
48914
|
"get-note-link",
|
|
48848
48915
|
"get-native-objects",
|
|
48916
|
+
"get-note-blocks",
|
|
48917
|
+
"get-note-structure",
|
|
48918
|
+
"get-note-tables",
|
|
48919
|
+
"list-note-links",
|
|
48849
48920
|
"list-native-tags",
|
|
48921
|
+
"list-smart-folders",
|
|
48922
|
+
"list-special-notes",
|
|
48923
|
+
"list-recent-notes",
|
|
48924
|
+
"list-folder-tree",
|
|
48925
|
+
"list-paper-attachments",
|
|
48926
|
+
"export-paper-image",
|
|
48927
|
+
"export-attachments",
|
|
48928
|
+
"export-notes-markdown",
|
|
48929
|
+
"export-notes-html",
|
|
48850
48930
|
"get-note-markdown (checklist annotations)",
|
|
48851
48931
|
"get-sync-status (pending uploads)"
|
|
48852
48932
|
],
|
|
@@ -48907,7 +48987,8 @@ var FEATURES = [
|
|
|
48907
48987
|
// Placeholders for features that need a native WRITE helper, which this
|
|
48908
48988
|
// server does not ship. The opt-in private helper is read-only (write
|
|
48909
48989
|
// support was deliberately deferred), so enabling or installing it changes
|
|
48910
|
-
// nothing here: these always report `not_implemented`.
|
|
48990
|
+
// nothing here: these always report `not_implemented`. Reading smart folders
|
|
48991
|
+
// is available through list-smart-folders (fullDiskAccessReads).
|
|
48911
48992
|
{
|
|
48912
48993
|
name: "checklistToggle",
|
|
48913
48994
|
description: "Check or uncheck an existing checklist item in place",
|
|
@@ -48931,17 +49012,17 @@ var FEATURES = [
|
|
|
48931
49012
|
},
|
|
48932
49013
|
{
|
|
48933
49014
|
name: "paragraphLinks",
|
|
48934
|
-
description: "
|
|
48935
|
-
tools: [],
|
|
49015
|
+
description: "List a note's paragraphs and return a link that opens Notes at one paragraph or heading, read from the Notes database",
|
|
49016
|
+
tools: ["list-note-paragraphs", "get-paragraph-link"],
|
|
48936
49017
|
minimumMacOSVersion: null,
|
|
48937
|
-
requirements: [{ kind: "
|
|
49018
|
+
requirements: [{ kind: "full_disk_access" }]
|
|
48938
49019
|
},
|
|
48939
49020
|
{
|
|
48940
49021
|
name: "audioTranscription",
|
|
48941
|
-
description: "
|
|
48942
|
-
tools: [],
|
|
49022
|
+
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)",
|
|
49023
|
+
tools: ["get-audio-transcripts", "transcribe-note-audio"],
|
|
48943
49024
|
minimumMacOSVersion: null,
|
|
48944
|
-
requirements: [{ kind: "
|
|
49025
|
+
requirements: [{ kind: "full_disk_access" }]
|
|
48945
49026
|
},
|
|
48946
49027
|
{
|
|
48947
49028
|
name: "markdownTemplateLibrary",
|
|
@@ -49612,7 +49693,7 @@ function resolveNote(dbPath2, columns, selector) {
|
|
|
49612
49693
|
function noteBodySql(columns) {
|
|
49613
49694
|
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
49695
|
}
|
|
49615
|
-
function readNoteParagraphs(selector, { dbPath: dbPath2 =
|
|
49696
|
+
function readNoteParagraphs(selector, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
|
|
49616
49697
|
checkNoteSelector(selector);
|
|
49617
49698
|
const columns = readColumns(dbPath2);
|
|
49618
49699
|
const { pk, id: id2 } = resolveNote(dbPath2, columns, selector);
|
|
@@ -49841,7 +49922,7 @@ function describeNoteStructure(s) {
|
|
|
49841
49922
|
if (s.isShared) parts.push("shared");
|
|
49842
49923
|
return `Note structure: ${parts.join("; ")}.`;
|
|
49843
49924
|
}
|
|
49844
|
-
function readNoteStructure(id2, { dbPath: dbPath2 =
|
|
49925
|
+
function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH8, includeText = true, maxTextBytes = 4 * 1024 * 1024 } = {}) {
|
|
49845
49926
|
const { pk } = parseNoteId2(id2);
|
|
49846
49927
|
const columns = readColumns(dbPath2);
|
|
49847
49928
|
const rows = parseJsonLines(
|
|
@@ -50087,7 +50168,7 @@ function decodeBodies(dbPath2, sql, params) {
|
|
|
50087
50168
|
}
|
|
50088
50169
|
var KIND_ORDER2 = { inline: 0, card: 1, note: 2, section: 3 };
|
|
50089
50170
|
function listNoteLinks(options = {}) {
|
|
50090
|
-
const { dbPath: dbPath2 =
|
|
50171
|
+
const { dbPath: dbPath2 = NOTES_DB_PATH8, offset = 0, limit = 200, maxBytes = 4 * 1024 * 1024 } = options;
|
|
50091
50172
|
if (options.id && (options.account || options.folder))
|
|
50092
50173
|
throw new NoteStoreError("Pass either id or account/folder, not both.", "invalid_input");
|
|
50093
50174
|
const notePk = options.id ? parseNoteId2(options.id).pk : 0;
|
|
@@ -50309,8 +50390,9 @@ function insertLink(request, deps) {
|
|
|
50309
50390
|
try {
|
|
50310
50391
|
readback = verifyLinkReadback(before, deps.readLinks(request.id), insertion, request.url);
|
|
50311
50392
|
} 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
|
|
50393
|
+
throw new CodedError(
|
|
50394
|
+
`The text was written, but link verification failed: ${error2 instanceof Error ? error2.message : String(error2)}. Do not retry automatically.`,
|
|
50395
|
+
{ code: "verification_failed", committed: true, indeterminate: true }
|
|
50314
50396
|
);
|
|
50315
50397
|
}
|
|
50316
50398
|
return {
|
|
@@ -53554,7 +53636,7 @@ function registerDirectOperations(server2, manager) {
|
|
|
53554
53636
|
);
|
|
53555
53637
|
tool(
|
|
53556
53638
|
"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
|
|
53639
|
+
"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 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
53640
|
{
|
|
53559
53641
|
title: external_exports.string().min(1).max(2e3).refine((s) => !/[\r\n\0]/u.test(s), "One-line title"),
|
|
53560
53642
|
content: external_exports.string().min(1).max(1024 * 1024).optional().describe("Optional plain-text body placed above the attachment"),
|
|
@@ -53580,26 +53662,45 @@ function registerDirectOperations(server2, manager) {
|
|
|
53580
53662
|
`Failed to create note "${args.title}". Check that the folder and account exist (list-folders, list-accounts); nothing was attached`
|
|
53581
53663
|
);
|
|
53582
53664
|
const handOff = `Note ${note.id} was created; attach to it with add-attachment instead of creating another note`;
|
|
53665
|
+
const createdError = (message, cause) => new CodedError(message, {
|
|
53666
|
+
code: classifyError(message, cause).code,
|
|
53667
|
+
committed: true,
|
|
53668
|
+
indeterminate: false
|
|
53669
|
+
});
|
|
53583
53670
|
let snapshot;
|
|
53584
53671
|
try {
|
|
53585
53672
|
snapshot = readSnapshot(manager, note.id);
|
|
53586
53673
|
} catch (error2) {
|
|
53587
|
-
|
|
53674
|
+
const message = `${handOff}. The new note could not be read back: ${String(error2)}`;
|
|
53675
|
+
throw createdError(message, error2);
|
|
53588
53676
|
}
|
|
53677
|
+
const progress = { insertionStarted: false };
|
|
53589
53678
|
try {
|
|
53590
53679
|
return {
|
|
53591
|
-
...attachFile(
|
|
53592
|
-
|
|
53593
|
-
|
|
53594
|
-
|
|
53595
|
-
|
|
53596
|
-
|
|
53680
|
+
...attachFile(
|
|
53681
|
+
manager,
|
|
53682
|
+
{
|
|
53683
|
+
id: note.id,
|
|
53684
|
+
expectedContentHash: snapshot.hash,
|
|
53685
|
+
path: args.path,
|
|
53686
|
+
filename: name
|
|
53687
|
+
},
|
|
53688
|
+
void 0,
|
|
53689
|
+
progress
|
|
53690
|
+
),
|
|
53597
53691
|
title: args.title,
|
|
53598
53692
|
folder: args.folder,
|
|
53599
53693
|
noteCreated: true
|
|
53600
53694
|
};
|
|
53601
53695
|
} catch (error2) {
|
|
53602
|
-
|
|
53696
|
+
const detail = error2 instanceof Error ? error2.message : String(error2);
|
|
53697
|
+
if (!progress.insertionStarted) throw createdError(`${handOff}. ${detail}`, error2);
|
|
53698
|
+
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`;
|
|
53699
|
+
throw new CodedError(message, {
|
|
53700
|
+
code: "verification_failed",
|
|
53701
|
+
committed: true,
|
|
53702
|
+
indeterminate: true
|
|
53703
|
+
});
|
|
53603
53704
|
}
|
|
53604
53705
|
}
|
|
53605
53706
|
);
|
|
@@ -53706,7 +53807,7 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
|
|
|
53706
53807
|
);
|
|
53707
53808
|
return { attachmentId, name: row.filename };
|
|
53708
53809
|
}
|
|
53709
|
-
function attachFile(manager, args, checked) {
|
|
53810
|
+
function attachFile(manager, args, checked, progress = { insertionStarted: false }) {
|
|
53710
53811
|
const { id: id2, expectedContentHash, path: path10 } = args;
|
|
53711
53812
|
const name = attachmentName(path10, args.filename);
|
|
53712
53813
|
const before = checked ?? readSnapshot(manager, id2);
|
|
@@ -53721,6 +53822,7 @@ function attachFile(manager, args, checked) {
|
|
|
53721
53822
|
if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
|
|
53722
53823
|
let returnedId;
|
|
53723
53824
|
let transportUncertain = false;
|
|
53825
|
+
progress.insertionStarted = true;
|
|
53724
53826
|
try {
|
|
53725
53827
|
returnedId = manager.addAttachmentById(id2, before.body, temporaryFile);
|
|
53726
53828
|
} catch {
|
|
@@ -54048,15 +54150,27 @@ function runFolderDelete(manager, args, deps = defaultDeps) {
|
|
|
54048
54150
|
throw uncertain(
|
|
54049
54151
|
`The delete outcome is uncertain (${outcome.reason}); read folder ${args.id} before retrying`
|
|
54050
54152
|
);
|
|
54051
|
-
|
|
54153
|
+
let stillExists;
|
|
54154
|
+
try {
|
|
54155
|
+
stillExists = manager.folderExistsById(args.id);
|
|
54156
|
+
} catch (error2) {
|
|
54157
|
+
throw uncertain(
|
|
54158
|
+
`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`
|
|
54159
|
+
);
|
|
54160
|
+
}
|
|
54161
|
+
if (stillExists)
|
|
54052
54162
|
throw uncertain(
|
|
54053
54163
|
`The delete outcome is uncertain: Notes.app still resolves folder ${args.id}; read it before retrying`
|
|
54054
54164
|
);
|
|
54055
54165
|
let storeTombstoned = false;
|
|
54056
54166
|
for (let attempt = 0; attempt < 5 && !storeTombstoned; attempt++) {
|
|
54057
54167
|
if (attempt > 0) deps.sleep(200);
|
|
54058
|
-
|
|
54059
|
-
|
|
54168
|
+
try {
|
|
54169
|
+
const after = deps.readStore(coreDataPk(args.id));
|
|
54170
|
+
storeTombstoned = !after || after.markedForDeletion;
|
|
54171
|
+
} catch {
|
|
54172
|
+
break;
|
|
54173
|
+
}
|
|
54060
54174
|
}
|
|
54061
54175
|
return {
|
|
54062
54176
|
...base,
|
|
@@ -56808,20 +56922,26 @@ function appendChecklistItems(args, deps, readRich) {
|
|
|
56808
56922
|
const landed = [];
|
|
56809
56923
|
let contentHash = args.expectedContentHash;
|
|
56810
56924
|
for (const [index, text2] of args.items.entries()) {
|
|
56811
|
-
let
|
|
56925
|
+
let attempted = false;
|
|
56926
|
+
const tracked = {
|
|
56927
|
+
...deps,
|
|
56928
|
+
run: (input) => {
|
|
56929
|
+
attempted = true;
|
|
56930
|
+
deps.run(input);
|
|
56931
|
+
}
|
|
56932
|
+
};
|
|
56812
56933
|
try {
|
|
56813
56934
|
const result = mutateBackground(
|
|
56814
56935
|
{ ...args, expectedContentHash: contentHash },
|
|
56815
56936
|
"create-checklist-item",
|
|
56816
56937
|
{ text: text2 },
|
|
56817
56938
|
(before, after) => {
|
|
56818
|
-
wrote = true;
|
|
56819
56939
|
assertPreserved(before, after, { append: true });
|
|
56820
56940
|
const added = after.checklist.slice(before.checklist.length);
|
|
56821
56941
|
if (added.length !== 1 || added[0].text !== text2 || added[0].done)
|
|
56822
56942
|
throw new Error("Native checklist item not verified");
|
|
56823
56943
|
},
|
|
56824
|
-
|
|
56944
|
+
tracked
|
|
56825
56945
|
);
|
|
56826
56946
|
const items = byPosition(readRich(args.id).checklistItems);
|
|
56827
56947
|
const fresh = items.filter(
|
|
@@ -56837,20 +56957,33 @@ function appendChecklistItems(args, deps, readRich) {
|
|
|
56837
56957
|
landed.push({ index, id: fresh[0].id, text: text2 });
|
|
56838
56958
|
contentHash = result.contentHash;
|
|
56839
56959
|
} catch (error2) {
|
|
56960
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
56961
|
+
const uncertain2 = attempted && !(error2 instanceof CodedError && error2.envelope.committed === false);
|
|
56962
|
+
const classified = classifyError(message, error2).code;
|
|
56963
|
+
const envelope = uncertain2 ? {
|
|
56964
|
+
code: classified === "timeout_indeterminate" ? classified : "verification_failed",
|
|
56965
|
+
indeterminate: true
|
|
56966
|
+
} : {
|
|
56967
|
+
code: classified,
|
|
56968
|
+
indeterminate: false,
|
|
56969
|
+
// Earlier items did land, so only an empty batch wrote nothing.
|
|
56970
|
+
...landed.length === 0 ? { committed: false } : {}
|
|
56971
|
+
};
|
|
56840
56972
|
return {
|
|
56841
56973
|
ok: false,
|
|
56974
|
+
...envelope,
|
|
56842
56975
|
id: args.id,
|
|
56843
56976
|
landed,
|
|
56844
56977
|
stoppedAt: {
|
|
56845
56978
|
index,
|
|
56846
56979
|
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)
|
|
56980
|
+
outcome: uncertain2 ? "uncertain" : "not-written",
|
|
56981
|
+
error: message
|
|
56851
56982
|
},
|
|
56852
56983
|
notAttempted: args.items.slice(index + 1),
|
|
56853
|
-
|
|
56984
|
+
// After an uncertain run the note may have changed, so the last
|
|
56985
|
+
// verified revision would be stale; read the note for a fresh one.
|
|
56986
|
+
...uncertain2 ? {} : { contentHash },
|
|
56854
56987
|
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
56988
|
};
|
|
56856
56989
|
}
|
|
@@ -56860,15 +56993,24 @@ function appendChecklistItems(args, deps, readRich) {
|
|
|
56860
56993
|
const orderVerified = tail.length === landed.length && tail.every((item, i) => item.id === landed[i].id && item.text === landed[i].text);
|
|
56861
56994
|
return {
|
|
56862
56995
|
ok: orderVerified,
|
|
56996
|
+
...orderVerified ? {} : { code: "verification_failed", committed: true },
|
|
56863
56997
|
id: args.id,
|
|
56864
56998
|
contentHash,
|
|
56865
56999
|
items: landed,
|
|
57000
|
+
landed,
|
|
56866
57001
|
orderVerified,
|
|
56867
57002
|
...orderVerified ? {} : {
|
|
56868
57003
|
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
57004
|
}
|
|
56870
57005
|
};
|
|
56871
57006
|
}
|
|
57007
|
+
function nativeToolResult(result) {
|
|
57008
|
+
return {
|
|
57009
|
+
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
57010
|
+
structuredContent: result,
|
|
57011
|
+
...result.ok === false && typeof result.code === "string" ? { isError: true } : {}
|
|
57012
|
+
};
|
|
57013
|
+
}
|
|
56872
57014
|
function registerNativeOperations(server2, manager) {
|
|
56873
57015
|
function tool(name, description, input, handler, readOnly = false) {
|
|
56874
57016
|
server2.registerTool(
|
|
@@ -56881,11 +57023,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
56881
57023
|
},
|
|
56882
57024
|
(async (args) => {
|
|
56883
57025
|
try {
|
|
56884
|
-
|
|
56885
|
-
return {
|
|
56886
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
56887
|
-
structuredContent: result
|
|
56888
|
-
};
|
|
57026
|
+
return nativeToolResult(handler(args));
|
|
56889
57027
|
} catch (error2) {
|
|
56890
57028
|
return errorResult(error2 instanceof Error ? error2.message : "Operation failed", error2);
|
|
56891
57029
|
}
|
|
@@ -57733,7 +57871,7 @@ function parseNoteObjectId(noteId3) {
|
|
|
57733
57871
|
);
|
|
57734
57872
|
return { store: match[1], pk: Number(match[2]) };
|
|
57735
57873
|
}
|
|
57736
|
-
function queryNoteScoped(sql, pk, dbPath2 =
|
|
57874
|
+
function queryNoteScoped(sql, pk, dbPath2 = NOTES_DB_PATH8) {
|
|
57737
57875
|
return runReadOnlySql(dbPath2, sql, { pk: { int: pk } }).split("\n");
|
|
57738
57876
|
}
|
|
57739
57877
|
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 +57900,7 @@ function drawingRowsSql(columns, dataColumn) {
|
|
|
57762
57900
|
`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
57901
|
].join("\n");
|
|
57764
57902
|
}
|
|
57765
|
-
function readDrawingRows2(noteId3, dbPath2 =
|
|
57903
|
+
function readDrawingRows2(noteId3, dbPath2 = NOTES_DB_PATH8) {
|
|
57766
57904
|
const { store, pk } = parseNoteObjectId(noteId3);
|
|
57767
57905
|
const columns = readColumns(dbPath2);
|
|
57768
57906
|
const dataColumn = columns.has("ZMERGEABLEDATA1") ? "ZMERGEABLEDATA1" : columns.has("ZMERGEABLEDATA") ? "ZMERGEABLEDATA" : null;
|
|
@@ -57997,7 +58135,7 @@ function isFile2(path10) {
|
|
|
57997
58135
|
var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
57998
58136
|
function readAudioAssets(noteId3, options = {}) {
|
|
57999
58137
|
const { store, pk } = parseNoteId2(noteId3);
|
|
58000
|
-
const dbPath2 = options.dbPath ??
|
|
58138
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
58001
58139
|
const columns = readColumns(dbPath2);
|
|
58002
58140
|
const missing = ["ZMEDIA", "ZPARENTATTACHMENT", "ZFILENAME"].filter((c) => !columns.has(c));
|
|
58003
58141
|
if (missing.length)
|
|
@@ -58376,6 +58514,10 @@ function inspectInstallation(deps = defaultDeps2()) {
|
|
|
58376
58514
|
);
|
|
58377
58515
|
return { ...base, ready: true, reason: null, detail: null };
|
|
58378
58516
|
}
|
|
58517
|
+
function helperTimeoutMs(env) {
|
|
58518
|
+
const value = Number.parseInt(env[TIMEOUT_ENV] || "", 10);
|
|
58519
|
+
return Number.isFinite(value) && value > 0 ? value : DEFAULT_TIMEOUT_MS3;
|
|
58520
|
+
}
|
|
58379
58521
|
var PrivateHelperError = class extends Error {
|
|
58380
58522
|
constructor(code, message, details = {}) {
|
|
58381
58523
|
super(message);
|
|
@@ -58458,7 +58600,7 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
|
|
|
58458
58600
|
throw new PrivateHelperError(install.reason || "helper_not_installed", install.detail || "");
|
|
58459
58601
|
binaryPath = install.binaryPath;
|
|
58460
58602
|
}
|
|
58461
|
-
const timeout =
|
|
58603
|
+
const timeout = helperTimeoutMs(deps.env);
|
|
58462
58604
|
const result = deps.spawn(binaryPath, [], {
|
|
58463
58605
|
input: JSON.stringify({ protocol: PRIVATE_HELPER_PROTOCOL, action, ...fields }),
|
|
58464
58606
|
encoding: "utf8",
|
|
@@ -58468,13 +58610,23 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
|
|
|
58468
58610
|
env: deps.env
|
|
58469
58611
|
});
|
|
58470
58612
|
const errno = result.error?.code;
|
|
58471
|
-
if (errno === "ETIMEDOUT"
|
|
58613
|
+
if (errno === "ETIMEDOUT")
|
|
58472
58614
|
throw new PrivateHelperError("timeout", `The helper did not answer within ${timeout} ms.`);
|
|
58615
|
+
if (errno === "ENOBUFS")
|
|
58616
|
+
throw new PrivateHelperError(
|
|
58617
|
+
"invalid_response",
|
|
58618
|
+
`The helper's output exceeded ${MAX_OUTPUT_BYTES3} bytes and was cut off.`
|
|
58619
|
+
);
|
|
58473
58620
|
if (result.error)
|
|
58474
58621
|
throw new PrivateHelperError(
|
|
58475
58622
|
"helper_unreachable",
|
|
58476
58623
|
`Could not run the helper: ${result.error.message}`
|
|
58477
58624
|
);
|
|
58625
|
+
if (result.signal)
|
|
58626
|
+
throw new PrivateHelperError(
|
|
58627
|
+
"helper_crashed",
|
|
58628
|
+
`The helper was terminated by ${result.signal} before it answered.`
|
|
58629
|
+
);
|
|
58478
58630
|
const stdout = String(result.stdout ?? "").trim();
|
|
58479
58631
|
let parsed;
|
|
58480
58632
|
try {
|
|
@@ -58798,7 +58950,8 @@ function envelopeCode(helperCode, message) {
|
|
|
58798
58950
|
case "not_found":
|
|
58799
58951
|
return "not_found";
|
|
58800
58952
|
case "timeout":
|
|
58801
|
-
|
|
58953
|
+
case "helper_crashed":
|
|
58954
|
+
return "operation_failed";
|
|
58802
58955
|
case "invalid_request":
|
|
58803
58956
|
case "invalid_json":
|
|
58804
58957
|
case "input_too_large":
|
|
@@ -58908,6 +59061,7 @@ var server = new McpServer({
|
|
|
58908
59061
|
version: version2,
|
|
58909
59062
|
description: "MCP server for managing Apple Notes - create, search, update, and organize notes"
|
|
58910
59063
|
});
|
|
59064
|
+
installSdkErrorCodes(server);
|
|
58911
59065
|
var notesManager = new AppleNotesManager();
|
|
58912
59066
|
registerDirectOperations(server, notesManager);
|
|
58913
59067
|
registerFolderDelete(server, notesManager);
|
|
@@ -58924,6 +59078,9 @@ function successResponse(message, structured) {
|
|
|
58924
59078
|
function errorResponse(message, cause) {
|
|
58925
59079
|
return errorResult(message, cause);
|
|
58926
59080
|
}
|
|
59081
|
+
function notFoundResponse(message) {
|
|
59082
|
+
return errorResult(message, new CodedError(message, { code: "not_found" }));
|
|
59083
|
+
}
|
|
58927
59084
|
function withErrorHandling(handler, errorPrefix) {
|
|
58928
59085
|
return async (params) => {
|
|
58929
59086
|
try {
|
|
@@ -59051,6 +59208,9 @@ function bodyReadFailureMessage(id2, title, error2) {
|
|
|
59051
59208
|
return describeBodyReadFailure(title, error2, attachments);
|
|
59052
59209
|
}
|
|
59053
59210
|
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.";
|
|
59211
|
+
function deleteUnverifiedMessage(title, id2) {
|
|
59212
|
+
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.`;
|
|
59213
|
+
}
|
|
59054
59214
|
function containerUnknownMessage(title) {
|
|
59055
59215
|
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
59216
|
}
|
|
@@ -59121,12 +59281,32 @@ var folderNameSchema = {
|
|
|
59121
59281
|
};
|
|
59122
59282
|
function registerTool(name, config2, cb) {
|
|
59123
59283
|
const { outputSchema, ...rest } = config2;
|
|
59284
|
+
const callback = NOTES_WRITE_TOOLS.has(name) ? (async (...args) => writeToolResult(
|
|
59285
|
+
await cb(...args)
|
|
59286
|
+
)) : cb;
|
|
59124
59287
|
return server.registerTool(
|
|
59125
59288
|
name,
|
|
59126
59289
|
outputSchema ? { ...rest, outputSchema: external_exports.object(outputSchema).passthrough() } : rest,
|
|
59127
|
-
|
|
59290
|
+
callback
|
|
59128
59291
|
);
|
|
59129
59292
|
}
|
|
59293
|
+
var NOTES_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
59294
|
+
"create-note",
|
|
59295
|
+
"update-note",
|
|
59296
|
+
"append-to-note",
|
|
59297
|
+
"insert-link",
|
|
59298
|
+
"delete-note",
|
|
59299
|
+
"move-note",
|
|
59300
|
+
"create-folder",
|
|
59301
|
+
"delete-folder",
|
|
59302
|
+
"batch-delete-notes",
|
|
59303
|
+
"batch-move-notes"
|
|
59304
|
+
]);
|
|
59305
|
+
function writeToolResult(result) {
|
|
59306
|
+
const envelope = result.structuredContent;
|
|
59307
|
+
if (!result.isError || !envelope) return result;
|
|
59308
|
+
return { ...result, structuredContent: unavailableWriteEnvelope(envelope) };
|
|
59309
|
+
}
|
|
59130
59310
|
registerTool(
|
|
59131
59311
|
"create-note",
|
|
59132
59312
|
{
|
|
@@ -59519,7 +59699,7 @@ registerTool(
|
|
|
59519
59699
|
if (id2) {
|
|
59520
59700
|
const note2 = notesManager.getNoteById(id2);
|
|
59521
59701
|
if (!note2) {
|
|
59522
|
-
return
|
|
59702
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59523
59703
|
}
|
|
59524
59704
|
if (note2.passwordProtected) {
|
|
59525
59705
|
return errorResponse(
|
|
@@ -59561,7 +59741,7 @@ registerTool(
|
|
|
59561
59741
|
}
|
|
59562
59742
|
const note = notesManager.getNoteDetails(title, account);
|
|
59563
59743
|
if (!note) {
|
|
59564
|
-
return
|
|
59744
|
+
return notFoundResponse(`Note "${title}" not found`);
|
|
59565
59745
|
}
|
|
59566
59746
|
if (note.passwordProtected) {
|
|
59567
59747
|
return errorResponse(
|
|
@@ -59619,7 +59799,7 @@ registerTool(
|
|
|
59619
59799
|
if (id2) {
|
|
59620
59800
|
const note2 = notesManager.getNoteById(id2);
|
|
59621
59801
|
if (!note2) {
|
|
59622
|
-
return
|
|
59802
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59623
59803
|
}
|
|
59624
59804
|
if (note2.passwordProtected) {
|
|
59625
59805
|
return errorResponse(
|
|
@@ -59637,7 +59817,7 @@ registerTool(
|
|
|
59637
59817
|
}
|
|
59638
59818
|
const note = notesManager.getNoteDetails(title, account);
|
|
59639
59819
|
if (!note) {
|
|
59640
|
-
return
|
|
59820
|
+
return notFoundResponse(`Note "${title}" not found`);
|
|
59641
59821
|
}
|
|
59642
59822
|
if (note.passwordProtected) {
|
|
59643
59823
|
return errorResponse(
|
|
@@ -59673,7 +59853,7 @@ registerTool(
|
|
|
59673
59853
|
withErrorHandling(({ id: id2 }) => {
|
|
59674
59854
|
const note = notesManager.getNoteById(id2);
|
|
59675
59855
|
if (!note) {
|
|
59676
|
-
return
|
|
59856
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59677
59857
|
}
|
|
59678
59858
|
const metadata = {
|
|
59679
59859
|
id: note.id,
|
|
@@ -59706,7 +59886,7 @@ registerTool(
|
|
|
59706
59886
|
withErrorHandling(({ title, account }) => {
|
|
59707
59887
|
const note = notesManager.getNoteDetails(title, account);
|
|
59708
59888
|
if (!note) {
|
|
59709
|
-
return
|
|
59889
|
+
return notFoundResponse(`Note "${title}" not found`);
|
|
59710
59890
|
}
|
|
59711
59891
|
const metadata = {
|
|
59712
59892
|
id: note.id,
|
|
@@ -59763,7 +59943,7 @@ registerTool(
|
|
|
59763
59943
|
if (id2) {
|
|
59764
59944
|
const note2 = notesManager.getNoteById(id2);
|
|
59765
59945
|
if (!note2) {
|
|
59766
|
-
return
|
|
59946
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59767
59947
|
}
|
|
59768
59948
|
if (note2.passwordProtected) {
|
|
59769
59949
|
return errorResponse(
|
|
@@ -59783,7 +59963,7 @@ registerTool(
|
|
|
59783
59963
|
}
|
|
59784
59964
|
const note = notesManager.getNoteDetails(title, account);
|
|
59785
59965
|
if (!note) {
|
|
59786
|
-
return
|
|
59966
|
+
return notFoundResponse(
|
|
59787
59967
|
`Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
|
|
59788
59968
|
);
|
|
59789
59969
|
}
|
|
@@ -59861,7 +60041,7 @@ registerTool(
|
|
|
59861
60041
|
},
|
|
59862
60042
|
withErrorHandling(({ id: id2 }) => {
|
|
59863
60043
|
const note = notesManager.getNoteById(id2);
|
|
59864
|
-
if (!note) return
|
|
60044
|
+
if (!note) return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59865
60045
|
const body = notesManager.getNoteContentById(id2);
|
|
59866
60046
|
if (!body) return errorResponse(`Failed to read content of note "${note.title}"`);
|
|
59867
60047
|
const rich = readRichNote(id2);
|
|
@@ -59967,7 +60147,10 @@ registerTool(
|
|
|
59967
60147
|
} catch (error2) {
|
|
59968
60148
|
if (!(error2 instanceof NoteBlocksError)) throw error2;
|
|
59969
60149
|
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(
|
|
60150
|
+
return errorResponse(
|
|
60151
|
+
`Error reading note blocks [${error2.code}]: ${error2.message}${hint}`,
|
|
60152
|
+
error2
|
|
60153
|
+
);
|
|
59971
60154
|
}
|
|
59972
60155
|
const { summary } = page;
|
|
59973
60156
|
const styles = Object.entries(summary.styles).map(([style, count]) => `${style} ${count}`).join(", ");
|
|
@@ -60528,7 +60711,10 @@ registerTool(
|
|
|
60528
60711
|
},
|
|
60529
60712
|
(r) => route = r
|
|
60530
60713
|
);
|
|
60531
|
-
if (response.isError)
|
|
60714
|
+
if (response.isError) {
|
|
60715
|
+
const envelope = response.structuredContent;
|
|
60716
|
+
throw envelope?.code ? new CodedError(response.content[0].text, envelope) : new Error(response.content[0].text);
|
|
60717
|
+
}
|
|
60532
60718
|
return { route, contentHash: String(response.structuredContent?.contentHash ?? "") };
|
|
60533
60719
|
}
|
|
60534
60720
|
}
|
|
@@ -60620,6 +60806,13 @@ registerTool(
|
|
|
60620
60806
|
if (result.status === "not-deleted") {
|
|
60621
60807
|
return errorResponse(NOT_DELETED_MESSAGE);
|
|
60622
60808
|
}
|
|
60809
|
+
if (result.status === "unverified") {
|
|
60810
|
+
const message = deleteUnverifiedMessage(snapshot.note.title, id2);
|
|
60811
|
+
return errorResponse(
|
|
60812
|
+
message,
|
|
60813
|
+
new CodedError(message, { code: "verification_failed", indeterminate: true })
|
|
60814
|
+
);
|
|
60815
|
+
}
|
|
60623
60816
|
if (result.status !== "deleted") {
|
|
60624
60817
|
return errorResponse(
|
|
60625
60818
|
`The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
|
|
@@ -60664,7 +60857,7 @@ registerTool(
|
|
|
60664
60857
|
withErrorHandling(({ id: id2, folder, account, ...scopeArgs }) => {
|
|
60665
60858
|
const note = notesManager.getNoteById(id2);
|
|
60666
60859
|
if (!note) {
|
|
60667
|
-
return
|
|
60860
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
60668
60861
|
}
|
|
60669
60862
|
const success = notesManager.moveNoteById(id2, folder, account, scopeFrom(scopeArgs));
|
|
60670
60863
|
if (!success) {
|
|
@@ -61195,7 +61388,7 @@ registerTool(
|
|
|
61195
61388
|
if (id2) {
|
|
61196
61389
|
const note2 = notesManager.getNoteById(id2);
|
|
61197
61390
|
if (!note2) {
|
|
61198
|
-
return
|
|
61391
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
61199
61392
|
}
|
|
61200
61393
|
const attachments2 = notesManager.listAttachmentsById(id2);
|
|
61201
61394
|
if (attachments2.length === 0) {
|
|
@@ -61248,7 +61441,7 @@ ${attachmentList2}`,
|
|
|
61248
61441
|
}
|
|
61249
61442
|
const note = notesManager.getNoteDetails(title, account);
|
|
61250
61443
|
if (!note) {
|
|
61251
|
-
return
|
|
61444
|
+
return notFoundResponse(
|
|
61252
61445
|
`Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
|
|
61253
61446
|
);
|
|
61254
61447
|
}
|
|
@@ -61306,6 +61499,8 @@ registerTool(
|
|
|
61306
61499
|
}
|
|
61307
61500
|
if (result.status === "not-deleted")
|
|
61308
61501
|
return { id: id2, success: false, error: NOT_DELETED_MESSAGE };
|
|
61502
|
+
if (result.status === "unverified")
|
|
61503
|
+
return { id: id2, success: false, error: deleteUnverifiedMessage(snapshot.note.title, id2) };
|
|
61309
61504
|
return { id: id2, success: false, error: "Delete result uncertain; inspect this exact ID" };
|
|
61310
61505
|
});
|
|
61311
61506
|
const succeeded = results.filter((r) => r.success).length;
|
|
@@ -61533,10 +61728,12 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
|
|
|
61533
61728
|
);
|
|
61534
61729
|
}
|
|
61535
61730
|
}
|
|
61536
|
-
|
|
61537
|
-
|
|
61538
|
-
|
|
61539
|
-
|
|
61731
|
+
const summary = `Exported ${exported} file(s) to ${r.exportDir} (${previews} preview-only, ${skipped} with nothing on disk, ${failed} failed).`;
|
|
61732
|
+
if (exported === 0 && failed > 0) {
|
|
61733
|
+
const error2 = errorResponse(summary, new CodedError(summary, { code: "operation_failed" }));
|
|
61734
|
+
return { ...error2, structuredContent: { ...structured, ...error2.structuredContent } };
|
|
61735
|
+
}
|
|
61736
|
+
return successResponse(summary, structured);
|
|
61540
61737
|
}, "Error exporting attachments")
|
|
61541
61738
|
);
|
|
61542
61739
|
registerTool(
|
|
@@ -61686,7 +61883,7 @@ registerTool(
|
|
|
61686
61883
|
if (id2) {
|
|
61687
61884
|
const markdown2 = notesManager.getNoteMarkdownById(id2);
|
|
61688
61885
|
if (!markdown2) {
|
|
61689
|
-
return
|
|
61886
|
+
return notFoundResponse(`Note with ID "${id2}" not found or has no content`);
|
|
61690
61887
|
}
|
|
61691
61888
|
return successResponse(markdown2, { markdown: markdown2 });
|
|
61692
61889
|
}
|
|
@@ -61695,7 +61892,7 @@ registerTool(
|
|
|
61695
61892
|
}
|
|
61696
61893
|
const markdown = notesManager.getNoteMarkdown(title, account);
|
|
61697
61894
|
if (!markdown) {
|
|
61698
|
-
return
|
|
61895
|
+
return notFoundResponse(
|
|
61699
61896
|
`Note "${title}" not found or has no content. Use search-notes to find notes, then use the note's ID for reliable operations.`
|
|
61700
61897
|
);
|
|
61701
61898
|
}
|
|
@@ -61770,7 +61967,10 @@ registerTool(
|
|
|
61770
61967
|
`Error exporting Markdown [${error2.code}]: the template is invalid:
|
|
61771
61968
|
` + error2.details.map((detail) => `${detail.path}: ${detail.message}`).join("\n")
|
|
61772
61969
|
);
|
|
61773
|
-
return errorResponse(
|
|
61970
|
+
return errorResponse(
|
|
61971
|
+
`Error exporting Markdown [${error2.code}]: ${error2.message}${hint}`,
|
|
61972
|
+
error2
|
|
61973
|
+
);
|
|
61774
61974
|
}
|
|
61775
61975
|
const warned = receipt.warnings?.length ? `; ${receipt.warnings.length + (receipt.warningsOmitted ?? 0)} warning(s)` : "";
|
|
61776
61976
|
const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + warned;
|
|
@@ -61823,7 +62023,7 @@ registerTool(
|
|
|
61823
62023
|
} catch (error2) {
|
|
61824
62024
|
if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
|
|
61825
62025
|
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}
|
|
62026
|
+
return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
|
|
61827
62027
|
}
|
|
61828
62028
|
const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;
|
|
61829
62029
|
const skipped = receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "";
|
|
@@ -61851,7 +62051,7 @@ registerTool(
|
|
|
61851
62051
|
withErrorHandling(({ id: id2 }) => {
|
|
61852
62052
|
const note = notesManager.getNoteById(id2);
|
|
61853
62053
|
if (!note) {
|
|
61854
|
-
return
|
|
62054
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
61855
62055
|
}
|
|
61856
62056
|
if (note.passwordProtected) {
|
|
61857
62057
|
return errorResponse(
|