apple-notes-mcp 2.9.23 → 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 +28 -17
- package/build/index.js +958 -601
- 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(
|
|
@@ -41184,11 +41459,36 @@ function assertReadableInRoots(p, roots = allowedSaveRoots(), label = "Content f
|
|
|
41184
41459
|
);
|
|
41185
41460
|
return abs;
|
|
41186
41461
|
}
|
|
41187
|
-
|
|
41462
|
+
var ALLOW_PRIVATE_CONTENT_ENV = "APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS";
|
|
41463
|
+
function privateContentReason(p, roots) {
|
|
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];
|
|
41465
|
+
const below = containing === void 0 ? p : relative(containing, p);
|
|
41466
|
+
if (below.split(sep).some((part) => part.startsWith("."))) return "a hidden file or directory";
|
|
41467
|
+
const home = resolve(homedir7());
|
|
41468
|
+
const libraries = [
|
|
41469
|
+
join7(home, "Library"),
|
|
41470
|
+
...canonicalRoots([home]).map((h) => join7(h, "Library"))
|
|
41471
|
+
];
|
|
41472
|
+
if (!isWithinRoots(p, libraries)) return null;
|
|
41473
|
+
const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) => join7(l, d)));
|
|
41474
|
+
return isWithinRoots(p, cloudDocuments) ? null : "~/Library";
|
|
41475
|
+
}
|
|
41476
|
+
var CLOUD_DOCUMENT_DIRS = ["Mobile Documents", "CloudStorage"];
|
|
41477
|
+
function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPrivate = process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1") {
|
|
41188
41478
|
const abs = assertReadableInRoots(p, roots);
|
|
41479
|
+
const assertNotPrivate = (candidate, candidateRoots) => {
|
|
41480
|
+
if (allowPrivate) return;
|
|
41481
|
+
const reason = privateContentReason(candidate, candidateRoots);
|
|
41482
|
+
if (reason)
|
|
41483
|
+
throw new Error(
|
|
41484
|
+
`Refusing to read "${abs}": it is in ${reason}, which can hold credentials or app data. Move the file to a regular folder, or set ${ALLOW_PRIVATE_CONTENT_ENV}=1 for the server to allow it.`
|
|
41485
|
+
);
|
|
41486
|
+
};
|
|
41487
|
+
assertNotPrivate(abs, roots);
|
|
41488
|
+
assertNotPrivate(canonicalize(abs), canonicalRoots(roots));
|
|
41189
41489
|
let descriptor;
|
|
41190
41490
|
try {
|
|
41191
|
-
descriptor = openSync(abs, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
41491
|
+
descriptor = openSync(abs, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
41192
41492
|
} catch (error2) {
|
|
41193
41493
|
if (error2.code === "ELOOP")
|
|
41194
41494
|
throw new Error(`Refusing to read the symbolic link "${abs}".`);
|
|
@@ -41197,6 +41497,20 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots()) {
|
|
|
41197
41497
|
try {
|
|
41198
41498
|
const stat = fstatSync(descriptor);
|
|
41199
41499
|
if (!stat.isFile()) throw new Error(`Content file is not a regular file: "${abs}"`);
|
|
41500
|
+
let after;
|
|
41501
|
+
try {
|
|
41502
|
+
after = canonicalize(abs);
|
|
41503
|
+
} catch {
|
|
41504
|
+
throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
|
|
41505
|
+
}
|
|
41506
|
+
if (!isWithinRoots(after, canonicalRoots(roots)))
|
|
41507
|
+
throw new Error(
|
|
41508
|
+
`Refusing to read outside allowed locations (home, temp, /Volumes): "${abs}" resolves to "${after}".`
|
|
41509
|
+
);
|
|
41510
|
+
assertNotPrivate(after, canonicalRoots(roots));
|
|
41511
|
+
const named = statSync(after);
|
|
41512
|
+
if (named.dev !== stat.dev || named.ino !== stat.ino)
|
|
41513
|
+
throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
|
|
41200
41514
|
if (stat.size === 0) throw new Error(`Content file is empty: "${abs}"`);
|
|
41201
41515
|
if (stat.size > maxBytes)
|
|
41202
41516
|
throw new Error(`Content file is ${stat.size} bytes, over the ${maxBytes}-byte limit.`);
|
|
@@ -41214,13 +41528,13 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots()) {
|
|
|
41214
41528
|
}
|
|
41215
41529
|
function cleanupTempDir(dir) {
|
|
41216
41530
|
try {
|
|
41217
|
-
if (
|
|
41531
|
+
if (existsSync5(dir)) rmSync(dir, { recursive: true, force: true });
|
|
41218
41532
|
} catch {
|
|
41219
41533
|
}
|
|
41220
41534
|
}
|
|
41221
41535
|
|
|
41222
41536
|
// src/utils/paperAttachments.ts
|
|
41223
|
-
import { execFileSync as
|
|
41537
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
41224
41538
|
import {
|
|
41225
41539
|
closeSync as closeSync3,
|
|
41226
41540
|
constants as constants3,
|
|
@@ -41233,10 +41547,10 @@ import {
|
|
|
41233
41547
|
unlinkSync as unlinkSync2,
|
|
41234
41548
|
writeSync as writeSync2
|
|
41235
41549
|
} from "node:fs";
|
|
41236
|
-
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";
|
|
41237
41551
|
|
|
41238
41552
|
// src/utils/attachmentAssets.ts
|
|
41239
|
-
import { execFileSync as
|
|
41553
|
+
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
41240
41554
|
import {
|
|
41241
41555
|
closeSync as closeSync2,
|
|
41242
41556
|
constants as constants2,
|
|
@@ -41250,11 +41564,11 @@ import {
|
|
|
41250
41564
|
unlinkSync,
|
|
41251
41565
|
writeSync
|
|
41252
41566
|
} from "node:fs";
|
|
41253
|
-
import { homedir as
|
|
41254
|
-
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";
|
|
41255
41569
|
import { gunzipSync as gunzipSync5 } from "node:zlib";
|
|
41256
|
-
var NOTES_CONTAINER_DIR =
|
|
41257
|
-
|
|
41570
|
+
var NOTES_CONTAINER_DIR = join8(
|
|
41571
|
+
homedir8(),
|
|
41258
41572
|
"Library/Group Containers/group.com.apple.notes"
|
|
41259
41573
|
);
|
|
41260
41574
|
var AttachmentStoreError = class extends Error {
|
|
@@ -41359,7 +41673,7 @@ function buildAttachmentRowsSql(notePk, columns) {
|
|
|
41359
41673
|
}
|
|
41360
41674
|
function runSqlite3(dbPath2, sql) {
|
|
41361
41675
|
try {
|
|
41362
|
-
return
|
|
41676
|
+
return execFileSync8("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
|
|
41363
41677
|
encoding: "utf8",
|
|
41364
41678
|
timeout: 1e4,
|
|
41365
41679
|
maxBuffer: 128 * 1024 * 1024,
|
|
@@ -41427,7 +41741,7 @@ function attachmentOrderFromNoteData(hex3) {
|
|
|
41427
41741
|
return null;
|
|
41428
41742
|
}
|
|
41429
41743
|
}
|
|
41430
|
-
function readNoteAttachmentRows(noteId3, dbPath2 =
|
|
41744
|
+
function readNoteAttachmentRows(noteId3, dbPath2 = join8(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
|
|
41431
41745
|
const { pk } = parseNoteId2(noteId3);
|
|
41432
41746
|
const columnList = runSqlite3(
|
|
41433
41747
|
dbPath2,
|
|
@@ -41488,7 +41802,7 @@ function boundedEntries(dir, limit) {
|
|
|
41488
41802
|
}
|
|
41489
41803
|
}
|
|
41490
41804
|
function resolveAccountDir(containerDir, accountIdentifier) {
|
|
41491
|
-
const accountsDir =
|
|
41805
|
+
const accountsDir = join8(containerDir, "Accounts");
|
|
41492
41806
|
let accountsReal;
|
|
41493
41807
|
try {
|
|
41494
41808
|
accountsReal = realpathSync2.native(accountsDir);
|
|
@@ -41497,14 +41811,14 @@ function resolveAccountDir(containerDir, accountIdentifier) {
|
|
|
41497
41811
|
}
|
|
41498
41812
|
const account = safeComponent(accountIdentifier);
|
|
41499
41813
|
if (account) {
|
|
41500
|
-
const dir = realInside(
|
|
41814
|
+
const dir = realInside(join8(accountsReal, account), accountsReal);
|
|
41501
41815
|
if (dir && isDirectory(dir)) return dir;
|
|
41502
41816
|
}
|
|
41503
41817
|
const entries2 = (boundedEntries(accountsReal, 64) ?? []).filter(
|
|
41504
|
-
(e) => isDirectory(
|
|
41818
|
+
(e) => isDirectory(join8(accountsReal, e))
|
|
41505
41819
|
);
|
|
41506
41820
|
if (entries2.length !== 1) return null;
|
|
41507
|
-
return realInside(
|
|
41821
|
+
return realInside(join8(accountsReal, entries2[0]), accountsReal);
|
|
41508
41822
|
}
|
|
41509
41823
|
function generationRank(name) {
|
|
41510
41824
|
const m = /^(\d+)_/.exec(name);
|
|
@@ -41512,23 +41826,23 @@ function generationRank(name) {
|
|
|
41512
41826
|
}
|
|
41513
41827
|
function generationDirs(base, accountDir) {
|
|
41514
41828
|
const entries2 = boundedEntries(base, MAX_GENERATION_DIRS) ?? [];
|
|
41515
|
-
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)));
|
|
41516
41830
|
}
|
|
41517
41831
|
function fallbackFiles(accountDir, rootName, identifier, generation, names) {
|
|
41518
|
-
const base =
|
|
41832
|
+
const base = join8(accountDir, rootName, identifier);
|
|
41519
41833
|
const found = [];
|
|
41520
41834
|
const add = (candidate) => {
|
|
41521
41835
|
const real = realInside(candidate, accountDir);
|
|
41522
41836
|
if (real && isRegularFile(real) && !found.includes(real)) found.push(real);
|
|
41523
41837
|
};
|
|
41524
41838
|
const gen = safeComponent(generation);
|
|
41525
|
-
if (gen) for (const name of names) add(
|
|
41839
|
+
if (gen) for (const name of names) add(join8(base, gen, name));
|
|
41526
41840
|
if (isDirectory(base)) {
|
|
41527
41841
|
for (const dir of generationDirs(base, accountDir))
|
|
41528
|
-
for (const name of names) add(
|
|
41529
|
-
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));
|
|
41530
41844
|
}
|
|
41531
|
-
for (const name of names) add(
|
|
41845
|
+
for (const name of names) add(join8(accountDir, rootName, `${identifier}${extname(name)}`));
|
|
41532
41846
|
return found;
|
|
41533
41847
|
}
|
|
41534
41848
|
function previewPixelArea(name) {
|
|
@@ -41540,7 +41854,7 @@ function previewFileInBundle(bundle, accountDir) {
|
|
|
41540
41854
|
const direct = [];
|
|
41541
41855
|
const nested = [];
|
|
41542
41856
|
for (const entry of boundedEntries(bundle, MAX_BUNDLE_ENTRIES) ?? []) {
|
|
41543
|
-
const child2 = realInside(
|
|
41857
|
+
const child2 = realInside(join8(bundle, entry), accountDir);
|
|
41544
41858
|
if (!child2) continue;
|
|
41545
41859
|
if (isRegularFile(child2)) direct.push(child2);
|
|
41546
41860
|
else if (isDirectory(child2)) nested.push(child2);
|
|
@@ -41548,14 +41862,14 @@ function previewFileInBundle(bundle, accountDir) {
|
|
|
41548
41862
|
nested.sort((a, b) => generationRank(basename(b)) - generationRank(basename(a)));
|
|
41549
41863
|
const pick2 = (files) => files.find((f) => basename(f) === "Preview.png") ?? files.find((f) => PREVIEW_IMAGE_SUFFIXES.has(extname(f).toLowerCase())) ?? null;
|
|
41550
41864
|
for (const dir of nested) {
|
|
41551
|
-
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));
|
|
41552
41866
|
const chosen = pick2(files);
|
|
41553
41867
|
if (chosen) return chosen;
|
|
41554
41868
|
}
|
|
41555
41869
|
return pick2(direct);
|
|
41556
41870
|
}
|
|
41557
41871
|
function listPreviewEntries(accountDir) {
|
|
41558
|
-
return boundedEntries(
|
|
41872
|
+
return boundedEntries(join8(accountDir, "Previews"), MAX_PREVIEW_DIR_ENTRIES) ?? [];
|
|
41559
41873
|
}
|
|
41560
41874
|
function previewPaths(accountDir, identifier, entries2) {
|
|
41561
41875
|
const id2 = safeComponent(identifier);
|
|
@@ -41567,7 +41881,7 @@ function previewPaths(accountDir, identifier, entries2) {
|
|
|
41567
41881
|
}).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
|
|
41568
41882
|
const files = [];
|
|
41569
41883
|
for (const name of candidates) {
|
|
41570
|
-
const real = realInside(
|
|
41884
|
+
const real = realInside(join8(accountDir, "Previews", name), accountDir);
|
|
41571
41885
|
if (!real) continue;
|
|
41572
41886
|
const file = isRegularFile(real) ? real : isDirectory(real) ? previewFileInBundle(real, accountDir) : null;
|
|
41573
41887
|
if (file && !files.includes(file)) files.push(file);
|
|
@@ -41584,13 +41898,13 @@ function assetPathsFor(accountDir, row) {
|
|
|
41584
41898
|
const mediaName = safeComponent(row.mediaFilename);
|
|
41585
41899
|
const mediaGen = safeComponent(row.mediaGeneration);
|
|
41586
41900
|
if (mediaId && mediaName) {
|
|
41587
|
-
if (mediaGen) add(
|
|
41588
|
-
add(
|
|
41901
|
+
if (mediaGen) add(join8(accountDir, "Media", mediaId, mediaGen, mediaName));
|
|
41902
|
+
add(join8(accountDir, "Media", mediaId, mediaName));
|
|
41589
41903
|
}
|
|
41590
41904
|
const id2 = safeComponent(row.identifier);
|
|
41591
41905
|
if (!id2) return found;
|
|
41592
41906
|
const ownName = safeComponent(row.filename);
|
|
41593
|
-
if (ownName) add(
|
|
41907
|
+
if (ownName) add(join8(accountDir, "Media", id2, ownName));
|
|
41594
41908
|
for (const file of fallbackFiles(accountDir, "FallbackImages", id2, row.fallbackImageGeneration, [
|
|
41595
41909
|
"FallbackImage.png",
|
|
41596
41910
|
"FallbackImage.jpg"
|
|
@@ -41681,10 +41995,10 @@ function canonicalTail(p) {
|
|
|
41681
41995
|
const tail = [];
|
|
41682
41996
|
for (; ; ) {
|
|
41683
41997
|
try {
|
|
41684
|
-
return
|
|
41998
|
+
return join8(realpathSync2.native(current), ...tail);
|
|
41685
41999
|
} catch {
|
|
41686
42000
|
const parent = dirname2(current);
|
|
41687
|
-
if (parent === current) return
|
|
42001
|
+
if (parent === current) return join8(current, ...tail);
|
|
41688
42002
|
tail.unshift(basename(current));
|
|
41689
42003
|
current = parent;
|
|
41690
42004
|
}
|
|
@@ -41725,16 +42039,17 @@ function copyFileExclusive(src, dest) {
|
|
|
41725
42039
|
}
|
|
41726
42040
|
function exportFileName(record2, source, kind) {
|
|
41727
42041
|
const ext = extname(source);
|
|
42042
|
+
const id2 = safeComponent(record2.identifier) ?? "attachment";
|
|
41728
42043
|
if (kind === "preview") {
|
|
41729
42044
|
const stored2 = safeComponent(record2.filename ? basename(record2.filename) : null);
|
|
41730
|
-
const stem = stored2 ? basename(stored2, extname(stored2)) :
|
|
41731
|
-
return `${safeComponent(stem) ??
|
|
42045
|
+
const stem = stored2 ? basename(stored2, extname(stored2)) : id2;
|
|
42046
|
+
return `${safeComponent(stem) ?? id2}-preview${ext}`;
|
|
41732
42047
|
}
|
|
41733
42048
|
const stored = safeComponent(record2.filename ? basename(record2.filename) : null);
|
|
41734
42049
|
if (stored && !GENERIC_FILE_NAMES.has(stored.toLowerCase())) return stored;
|
|
41735
|
-
const own = basename(source);
|
|
41736
|
-
if (!GENERIC_FILE_NAMES.has(own.toLowerCase())) return own;
|
|
41737
|
-
return `${
|
|
42050
|
+
const own = safeComponent(basename(source));
|
|
42051
|
+
if (own && !GENERIC_FILE_NAMES.has(own.toLowerCase())) return own;
|
|
42052
|
+
return `${id2}${ext}`;
|
|
41738
42053
|
}
|
|
41739
42054
|
function collisionName(name, attempt) {
|
|
41740
42055
|
if (attempt <= 1) return name;
|
|
@@ -41766,8 +42081,10 @@ function exportOneAttachment(record2, dir, source) {
|
|
|
41766
42081
|
if (!source) return base;
|
|
41767
42082
|
const name = exportFileName(record2, source.path, source.kind);
|
|
41768
42083
|
for (let attempt = 1; attempt <= MAX_COLLISION_SUFFIX; attempt++) {
|
|
41769
|
-
const dest =
|
|
42084
|
+
const dest = join8(dir, collisionName(name, attempt));
|
|
41770
42085
|
try {
|
|
42086
|
+
if (dirname2(dest) !== resolve2(dir))
|
|
42087
|
+
throw new Error(`Refusing to write outside the export directory: "${dest}"`);
|
|
41771
42088
|
assertSafeSavePath(dest);
|
|
41772
42089
|
copyFileExclusive(source.path, dest);
|
|
41773
42090
|
return { ...base, exportedTo: dest, exportedKind: source.kind };
|
|
@@ -41893,7 +42210,7 @@ function buildDrawingRowsSql(notePk, columns) {
|
|
|
41893
42210
|
}
|
|
41894
42211
|
function runSqlite4(dbPath2, sql) {
|
|
41895
42212
|
try {
|
|
41896
|
-
return
|
|
42213
|
+
return execFileSync9("/usr/bin/sqlite3", ["-readonly", dbPath2, sql], {
|
|
41897
42214
|
encoding: "utf8",
|
|
41898
42215
|
timeout: 1e4,
|
|
41899
42216
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -41933,7 +42250,7 @@ function parseDrawingRows(json2) {
|
|
|
41933
42250
|
}
|
|
41934
42251
|
return rows.sort((a, b) => a.pk - b.pk);
|
|
41935
42252
|
}
|
|
41936
|
-
function readDrawingRows(noteId3, dbPath2 =
|
|
42253
|
+
function readDrawingRows(noteId3, dbPath2 = join9(NOTES_CONTAINER_DIR, "NoteStore.sqlite")) {
|
|
41937
42254
|
const { pk } = parseNoteId2(noteId3);
|
|
41938
42255
|
const columns = new Set(
|
|
41939
42256
|
runSqlite4(
|
|
@@ -41977,16 +42294,16 @@ var byGenerationDesc = (a, b) => generationRank(basename2(b)) - generationRank(b
|
|
|
41977
42294
|
function findFallbackImage(accountDir, identifier, generation) {
|
|
41978
42295
|
const id2 = safeComponent(identifier);
|
|
41979
42296
|
if (!id2) return null;
|
|
41980
|
-
const base =
|
|
42297
|
+
const base = join9(accountDir, "FallbackImages", id2);
|
|
41981
42298
|
const names = ["FallbackImage.png", "FallbackImage.jpg"];
|
|
41982
42299
|
const candidates = [];
|
|
41983
42300
|
const gen = safeComponent(generation);
|
|
41984
|
-
if (gen) for (const n of names) candidates.push(
|
|
41985
|
-
const gens = entries(base, MAX_DIR_ENTRIES).map((e) =>
|
|
41986
|
-
for (const g of gens) for (const n of names) candidates.push(
|
|
41987
|
-
for (const n of names) candidates.push(
|
|
41988
|
-
candidates.push(
|
|
41989
|
-
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`));
|
|
41990
42307
|
for (const c of candidates) {
|
|
41991
42308
|
const real = realInside(c, accountDir);
|
|
41992
42309
|
if (real && kindOf(real) === "file") return real;
|
|
@@ -41996,21 +42313,21 @@ function findFallbackImage(accountDir, identifier, generation) {
|
|
|
41996
42313
|
function findLargestPreview(accountDir, identifier) {
|
|
41997
42314
|
const id2 = safeComponent(identifier);
|
|
41998
42315
|
if (!id2) return null;
|
|
41999
|
-
const dir =
|
|
42316
|
+
const dir = join9(accountDir, "Previews");
|
|
42000
42317
|
const prefix = `${id2}-`.toLowerCase();
|
|
42001
42318
|
const names = entries(dir, MAX_PREVIEW_DIR_ENTRIES2).filter((n) => n.toLowerCase().startsWith(prefix)).filter((n) => {
|
|
42002
42319
|
const ext = extname2(n).toLowerCase();
|
|
42003
42320
|
return !ext || IMAGE_SUFFIXES.has(ext) || /^\.\d+$/.test(ext);
|
|
42004
42321
|
}).sort((a, b) => previewPixelArea(b) - previewPixelArea(a) || a.localeCompare(b));
|
|
42005
42322
|
for (const name of names) {
|
|
42006
|
-
const real = realInside(
|
|
42323
|
+
const real = realInside(join9(dir, name), accountDir);
|
|
42007
42324
|
if (!real) continue;
|
|
42008
42325
|
const kind = kindOf(real);
|
|
42009
42326
|
if (kind === "file") return real;
|
|
42010
42327
|
if (kind !== "dir") continue;
|
|
42011
|
-
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);
|
|
42012
42329
|
for (const g of [...gens, real]) {
|
|
42013
|
-
const file = realInside(
|
|
42330
|
+
const file = realInside(join9(g, "Preview.png"), accountDir);
|
|
42014
42331
|
if (file && kindOf(file) === "file") return file;
|
|
42015
42332
|
}
|
|
42016
42333
|
}
|
|
@@ -42023,7 +42340,7 @@ function describeDrawings(rows, containerDir = NOTES_CONTAINER_DIR) {
|
|
|
42023
42340
|
const id2 = safeComponent(row.identifier);
|
|
42024
42341
|
const fallbackImagePath = accountDir ? findFallbackImage(accountDir, row.identifier, row.fallbackImageGeneration) : null;
|
|
42025
42342
|
const previewPath = accountDir ? findLargestPreview(accountDir, row.identifier) : null;
|
|
42026
|
-
const bundle = accountDir && id2 && kind === "paper" ? realInside(
|
|
42343
|
+
const bundle = accountDir && id2 && kind === "paper" ? realInside(join9(accountDir, "Paper", "Bundles", `${id2}.bundle`), accountDir) : null;
|
|
42027
42344
|
let raster = null;
|
|
42028
42345
|
for (const [path10, source] of [
|
|
42029
42346
|
[fallbackImagePath, "fallback"],
|
|
@@ -42220,9 +42537,9 @@ function scopeConflictMessage(reason) {
|
|
|
42220
42537
|
|
|
42221
42538
|
// src/services/appleNotesManager.ts
|
|
42222
42539
|
var import_turndown = __toESM(require_turndown_cjs(), 1);
|
|
42223
|
-
import { existsSync as
|
|
42224
|
-
import { homedir as
|
|
42225
|
-
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";
|
|
42226
42543
|
var FIELD_SEP = "";
|
|
42227
42544
|
var RECORD_SEP = "";
|
|
42228
42545
|
var AS_FIELD_SEP = "(character id 31)";
|
|
@@ -42444,6 +42761,11 @@ function smartFolderDestinationError(folderPath) {
|
|
|
42444
42761
|
}
|
|
42445
42762
|
);
|
|
42446
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
|
+
}
|
|
42447
42769
|
function throwIfSmartFolderDestination(error2, folderPath) {
|
|
42448
42770
|
if (error2?.includes(SMART_FOLDER_DESTINATION)) throw smartFolderDestinationError(folderPath);
|
|
42449
42771
|
}
|
|
@@ -42579,8 +42901,8 @@ var ExpectedBodies = class {
|
|
|
42579
42901
|
if (body.length <= INLINE_EXPECTED_BODY_LIMIT) {
|
|
42580
42902
|
return { setup: "", operand: `"${escapeHtmlForAppleScript(body)}"` };
|
|
42581
42903
|
}
|
|
42582
|
-
this.directory ??= mkdtempSync2(
|
|
42583
|
-
const file =
|
|
42904
|
+
this.directory ??= mkdtempSync2(join10(tmpdir2(), "apple-notes-expected-body-"));
|
|
42905
|
+
const file = join10(this.directory, `body-${this.files++}.html`);
|
|
42584
42906
|
writeFileSync(file, body, { encoding: "utf8", mode: 384 });
|
|
42585
42907
|
return {
|
|
42586
42908
|
setup: `
|
|
@@ -42626,8 +42948,8 @@ function getNoteLinkFromDB(coreDataId3) {
|
|
|
42626
42948
|
const match = coreDataId3.match(/\/p(\d+)$/);
|
|
42627
42949
|
if (!match) return null;
|
|
42628
42950
|
const pk = parseInt(match[1], 10);
|
|
42629
|
-
const dbPath2 =
|
|
42630
|
-
if (!
|
|
42951
|
+
const dbPath2 = join10(homedir9(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
|
|
42952
|
+
if (!existsSync6(dbPath2)) return null;
|
|
42631
42953
|
try {
|
|
42632
42954
|
const { DatabaseSync } = __require("node:sqlite");
|
|
42633
42955
|
const db = new DatabaseSync(dbPath2, { readOnly: true });
|
|
@@ -43261,9 +43583,12 @@ var AppleNotesManager = class {
|
|
|
43261
43583
|
if currentBody is not ${expected.operand} and currentBody is not ${expected.operand} & linefeed then return "SAFETY_CONFLICT"
|
|
43262
43584
|
delete noteRef
|
|
43263
43585
|
end considering
|
|
43586
|
+
set __stillThere to missing value
|
|
43264
43587
|
try
|
|
43265
|
-
|
|
43588
|
+
set __stillThere to (id of notes of originalFolder) contains "${safeId}"
|
|
43266
43589
|
end try
|
|
43590
|
+
if __stillThere is missing value then return "SAFETY_DELETE_UNVERIFIED"
|
|
43591
|
+
if __stillThere then return "SAFETY_NOT_DELETED"
|
|
43267
43592
|
return "SAFETY_DELETED"
|
|
43268
43593
|
`);
|
|
43269
43594
|
result = executeMutationAppleScript(script);
|
|
@@ -43286,6 +43611,7 @@ var AppleNotesManager = class {
|
|
|
43286
43611
|
if (status === "SAFETY_IN_RECENTLY_DELETED") return { status: "in-recently-deleted" };
|
|
43287
43612
|
if (status === "SAFETY_CONTAINER_UNKNOWN") return { status: "container-unknown" };
|
|
43288
43613
|
if (status === "SAFETY_NOT_DELETED") return { status: "not-deleted" };
|
|
43614
|
+
if (status === "SAFETY_DELETE_UNVERIFIED") return { status: "unverified" };
|
|
43289
43615
|
return status === "SAFETY_DELETED" ? { status: "deleted" } : { status: "failed" };
|
|
43290
43616
|
}
|
|
43291
43617
|
/**
|
|
@@ -43433,7 +43759,8 @@ var AppleNotesManager = class {
|
|
|
43433
43759
|
*/
|
|
43434
43760
|
listSmartFolders(options = {}) {
|
|
43435
43761
|
const result = readSmartFolders();
|
|
43436
|
-
if (!result.folders)
|
|
43762
|
+
if (!result.folders)
|
|
43763
|
+
throw smartFoldersReadError(result.error, result.message || "Failed to read smart folders.");
|
|
43437
43764
|
if (!options.includeMatchingNotes) return result.folders;
|
|
43438
43765
|
const limit = options.limit ?? 50;
|
|
43439
43766
|
return result.folders.map((folder) => {
|
|
@@ -44821,7 +45148,7 @@ var AppleNotesManager = class {
|
|
|
44821
45148
|
if (parts[0] !== "OK") {
|
|
44822
45149
|
return { success: false, error: parts[1]?.trim() || "attachment not found" };
|
|
44823
45150
|
}
|
|
44824
|
-
if (!
|
|
45151
|
+
if (!existsSync6(abs) || fileSize(abs) === 0) {
|
|
44825
45152
|
return { success: false, error: `Notes reported success but no file was written to ${abs}` };
|
|
44826
45153
|
}
|
|
44827
45154
|
return {
|
|
@@ -45329,15 +45656,15 @@ var AppleNotesManager = class {
|
|
|
45329
45656
|
};
|
|
45330
45657
|
|
|
45331
45658
|
// src/utils/syncDetection.ts
|
|
45332
|
-
import { execFileSync as
|
|
45333
|
-
import * as
|
|
45334
|
-
import * as
|
|
45335
|
-
import * as
|
|
45336
|
-
var
|
|
45337
|
-
|
|
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(),
|
|
45338
45665
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45339
45666
|
);
|
|
45340
|
-
var WAL_PATH = `${
|
|
45667
|
+
var WAL_PATH = `${NOTES_DB_PATH6}-wal`;
|
|
45341
45668
|
var RECENT_ACTIVITY_THRESHOLD_SECONDS = 5;
|
|
45342
45669
|
var SYNC_STATUS_CACHE_TTL_MS = 2e3;
|
|
45343
45670
|
var cachedSyncStatus = null;
|
|
@@ -45353,14 +45680,14 @@ function getSyncStatus(useCache = true) {
|
|
|
45353
45680
|
recentActivity: false
|
|
45354
45681
|
};
|
|
45355
45682
|
try {
|
|
45356
|
-
if (!
|
|
45683
|
+
if (!fs5.existsSync(NOTES_DB_PATH6)) {
|
|
45357
45684
|
status.error = "Notes database not found";
|
|
45358
45685
|
cachedSyncStatus = status;
|
|
45359
45686
|
cacheTimestamp = Date.now();
|
|
45360
45687
|
return status;
|
|
45361
45688
|
}
|
|
45362
|
-
if (
|
|
45363
|
-
const walStats =
|
|
45689
|
+
if (fs5.existsSync(WAL_PATH)) {
|
|
45690
|
+
const walStats = fs5.statSync(WAL_PATH);
|
|
45364
45691
|
const secondsAgo = (Date.now() - walStats.mtimeMs) / 1e3;
|
|
45365
45692
|
status.secondsSinceLastChange = Math.round(secondsAgo);
|
|
45366
45693
|
status.recentActivity = secondsAgo < RECENT_ACTIVITY_THRESHOLD_SECONDS;
|
|
@@ -45374,9 +45701,9 @@ function getSyncStatus(useCache = true) {
|
|
|
45374
45701
|
WHERE object.ZCLOUDSTATE = state.Z_PK
|
|
45375
45702
|
);
|
|
45376
45703
|
`;
|
|
45377
|
-
const result =
|
|
45704
|
+
const result = execFileSync10(
|
|
45378
45705
|
"sqlite3",
|
|
45379
|
-
["-readonly",
|
|
45706
|
+
["-readonly", NOTES_DB_PATH6, query2.replace(/\n/g, " ")],
|
|
45380
45707
|
{
|
|
45381
45708
|
encoding: "utf8",
|
|
45382
45709
|
timeout: 5e3,
|
|
@@ -45433,12 +45760,12 @@ function withSyncAwarenessSync(operation, fn) {
|
|
|
45433
45760
|
}
|
|
45434
45761
|
|
|
45435
45762
|
// src/utils/noteMetadata.ts
|
|
45436
|
-
import { execFileSync as
|
|
45437
|
-
import * as
|
|
45438
|
-
import * as
|
|
45439
|
-
import * as
|
|
45440
|
-
var
|
|
45441
|
-
|
|
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(),
|
|
45442
45769
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45443
45770
|
);
|
|
45444
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.`;
|
|
@@ -45454,7 +45781,7 @@ var COLUMN_MAP = [
|
|
|
45454
45781
|
{ key: "smartFolderQuery", column: "ZSMARTFOLDERQUERYJSON", type: "text" }
|
|
45455
45782
|
];
|
|
45456
45783
|
function runSqlite5(query2) {
|
|
45457
|
-
return
|
|
45784
|
+
return execFileSync11("sqlite3", ["-readonly", NOTES_DB_PATH7, query2], {
|
|
45458
45785
|
encoding: "utf8",
|
|
45459
45786
|
timeout: 5e3,
|
|
45460
45787
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -45479,7 +45806,7 @@ function getNoteMetadata(noteId3) {
|
|
|
45479
45806
|
};
|
|
45480
45807
|
}
|
|
45481
45808
|
const pk = pkMatch[1];
|
|
45482
|
-
if (!
|
|
45809
|
+
if (!fs6.existsSync(NOTES_DB_PATH7)) {
|
|
45483
45810
|
return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
|
|
45484
45811
|
}
|
|
45485
45812
|
try {
|
|
@@ -45525,12 +45852,12 @@ function getNoteMetadata(noteId3) {
|
|
|
45525
45852
|
import { gunzipSync as gunzipSync6 } from "zlib";
|
|
45526
45853
|
|
|
45527
45854
|
// src/utils/noteStoreSql.ts
|
|
45528
|
-
import { execFileSync as
|
|
45529
|
-
import * as
|
|
45530
|
-
import * as
|
|
45531
|
-
import * as
|
|
45532
|
-
var
|
|
45533
|
-
|
|
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(),
|
|
45534
45861
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
45535
45862
|
);
|
|
45536
45863
|
var CORE_DATA_EPOCH_MS = Date.UTC(2001, 0, 1);
|
|
@@ -45568,7 +45895,7 @@ function renderValue(value) {
|
|
|
45568
45895
|
return `ieee754_from_blob(x'${doubleToHex(double)}')`;
|
|
45569
45896
|
}
|
|
45570
45897
|
function runReadOnlySql(dbPath2, sql, params = {}) {
|
|
45571
|
-
if (!
|
|
45898
|
+
if (!fs7.existsSync(dbPath2)) throw new NoteStoreError(STORE_FDA_MESSAGE, "no_fda");
|
|
45572
45899
|
const args = ["-readonly"];
|
|
45573
45900
|
const names = Object.keys(params);
|
|
45574
45901
|
if (names.length) args.push("-cmd", ".parameter init");
|
|
@@ -45580,7 +45907,7 @@ function runReadOnlySql(dbPath2, sql, params = {}) {
|
|
|
45580
45907
|
}
|
|
45581
45908
|
args.push(dbPath2, sql);
|
|
45582
45909
|
try {
|
|
45583
|
-
return
|
|
45910
|
+
return execFileSync12("sqlite3", args, {
|
|
45584
45911
|
encoding: "utf8",
|
|
45585
45912
|
timeout: 3e4,
|
|
45586
45913
|
maxBuffer: 512 * 1024 * 1024,
|
|
@@ -45773,7 +46100,7 @@ function buildSpecialNotesSql(columns, kind, scoped) {
|
|
|
45773
46100
|
].join(" ");
|
|
45774
46101
|
}
|
|
45775
46102
|
function listSpecialNotes(options) {
|
|
45776
|
-
const dbPath2 = options.dbPath ??
|
|
46103
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
45777
46104
|
const limit = Math.min(
|
|
45778
46105
|
Math.max(1, Math.trunc(options.limit ?? SPECIAL_LIMIT.DEFAULT)),
|
|
45779
46106
|
SPECIAL_LIMIT.MAX
|
|
@@ -45829,7 +46156,7 @@ function toSpecialRow(row, uuid2, paths, accountNames, kind) {
|
|
|
45829
46156
|
return note;
|
|
45830
46157
|
}
|
|
45831
46158
|
var EXACT_NOTE_ID = /^x-coredata:\/\/([0-9A-F-]+)\/ICNote\/p(\d+)$/i;
|
|
45832
|
-
function quickNoteFlag(noteId3, dbPath2 =
|
|
46159
|
+
function quickNoteFlag(noteId3, dbPath2 = NOTES_DB_PATH8) {
|
|
45833
46160
|
const match = EXACT_NOTE_ID.exec(noteId3);
|
|
45834
46161
|
if (!match) throw new NoteStoreError(`Not an exact note id: ${noteId3}`, "invalid_input");
|
|
45835
46162
|
const columns = readColumns(dbPath2);
|
|
@@ -45881,7 +46208,7 @@ function referencedObjects(hex3) {
|
|
|
45881
46208
|
}
|
|
45882
46209
|
}
|
|
45883
46210
|
function nativeTagInventory(options = {}) {
|
|
45884
|
-
const dbPath2 = options.dbPath ??
|
|
46211
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
45885
46212
|
const columns = readColumns(dbPath2);
|
|
45886
46213
|
const context = readStoreContext(dbPath2, columns);
|
|
45887
46214
|
const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
|
|
@@ -45943,15 +46270,27 @@ function assembleInventory(rows, accounts, scope2) {
|
|
|
45943
46270
|
}
|
|
45944
46271
|
|
|
45945
46272
|
// src/utils/noteBlocks.ts
|
|
45946
|
-
import { execFileSync as
|
|
45947
|
-
import { existsSync as
|
|
45948
|
-
import { homedir as
|
|
45949
|
-
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";
|
|
45950
46277
|
import { gunzipSync as gunzipSync7 } from "node:zlib";
|
|
45951
|
-
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 {
|
|
45952
46291
|
code;
|
|
45953
46292
|
constructor(code, message) {
|
|
45954
|
-
super(message);
|
|
46293
|
+
super(message, { code: NOTE_BLOCKS_ERROR_CODES[code] });
|
|
45955
46294
|
this.name = "NoteBlocksError";
|
|
45956
46295
|
this.code = code;
|
|
45957
46296
|
}
|
|
@@ -46289,23 +46628,23 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
|
|
|
46289
46628
|
undecodedFields: doc.undecodedFields
|
|
46290
46629
|
};
|
|
46291
46630
|
}
|
|
46292
|
-
var
|
|
46293
|
-
|
|
46631
|
+
var NOTES_DB_PATH9 = join14(
|
|
46632
|
+
homedir13(),
|
|
46294
46633
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46295
46634
|
);
|
|
46296
|
-
function readNoteBlocks(id2, { dbPath: dbPath2 =
|
|
46635
|
+
function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH9 } = {}) {
|
|
46297
46636
|
const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]{1,18})$/i.exec(id2)?.[1];
|
|
46298
46637
|
if (!pk)
|
|
46299
46638
|
throw new NoteBlocksError(
|
|
46300
46639
|
"invalid-id",
|
|
46301
46640
|
`Invalid note ID: expected x-coredata://<store>/ICNote/p<number>`
|
|
46302
46641
|
);
|
|
46303
|
-
if (!
|
|
46642
|
+
if (!existsSync10(dbPath2))
|
|
46304
46643
|
throw new NoteBlocksError("no-full-disk-access", "The Notes database is not readable");
|
|
46305
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));";
|
|
46306
46645
|
let output;
|
|
46307
46646
|
try {
|
|
46308
|
-
output =
|
|
46647
|
+
output = execFileSync13(
|
|
46309
46648
|
"/usr/bin/sqlite3",
|
|
46310
46649
|
["-readonly", "-cmd", ".parameter init", "-cmd", `.parameter set @pk ${pk}`, dbPath2, sql],
|
|
46311
46650
|
{
|
|
@@ -46452,7 +46791,7 @@ function resolveFolderPath(folders, paths, input, accountPk) {
|
|
|
46452
46791
|
);
|
|
46453
46792
|
}
|
|
46454
46793
|
function listRecentNotes(options = {}) {
|
|
46455
|
-
const dbPath2 = options.dbPath ??
|
|
46794
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
46456
46795
|
const limit = Math.min(
|
|
46457
46796
|
Math.max(1, Math.trunc(options.limit ?? RECENT_LIMIT.DEFAULT)),
|
|
46458
46797
|
RECENT_LIMIT.MAX
|
|
@@ -46530,323 +46869,85 @@ function buildFolderCountsSql(columns) {
|
|
|
46530
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;`;
|
|
46531
46870
|
}
|
|
46532
46871
|
var KIND_ORDER = { folder: 0, smart: 1, trash: 2 };
|
|
46533
|
-
function compareNodes(a, b) {
|
|
46534
|
-
return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
|
|
46535
|
-
}
|
|
46536
|
-
function folderTree(options = {}) {
|
|
46537
|
-
const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
|
|
46538
|
-
const columns = readColumns(dbPath2);
|
|
46539
|
-
const context = readStoreContext(dbPath2, columns);
|
|
46540
|
-
const scope2 = options.account ? resolveAccountName(context.accounts, options.account) : void 0;
|
|
46541
|
-
const counts = new Map(
|
|
46542
|
-
parseJsonLines(
|
|
46543
|
-
runReadOnlySql(dbPath2, buildFolderCountsSql(columns))
|
|
46544
|
-
).map((row) => [row.folder, row.n])
|
|
46545
|
-
);
|
|
46546
|
-
return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
|
|
46547
|
-
}
|
|
46548
|
-
function assembleFolderTree(context, counts, includeDeleted, scope2) {
|
|
46549
|
-
const paths = folderPaths(context.folders);
|
|
46550
|
-
const visible2 = context.folders.filter(
|
|
46551
|
-
(folder) => (includeDeleted || !folder.tombstoned) && (!scope2 || folder.account === scope2.pk)
|
|
46552
|
-
);
|
|
46553
|
-
const byPk = new Map(visible2.map((folder) => [folder.pk, folder]));
|
|
46554
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
46555
|
-
for (const folder of visible2) {
|
|
46556
|
-
const node = {
|
|
46557
|
-
id: `x-coredata://${context.uuid}/ICFolder/p${folder.pk}`,
|
|
46558
|
-
identifier: folder.identifier,
|
|
46559
|
-
name: folder.name ?? "",
|
|
46560
|
-
path: paths.get(folder.pk) ?? "",
|
|
46561
|
-
kind: folder.trash ? "trash" : folder.folderType === 2 ? "smart" : "folder",
|
|
46562
|
-
noteCount: counts.get(folder.pk) ?? 0,
|
|
46563
|
-
totalNoteCount: 0,
|
|
46564
|
-
children: []
|
|
46565
|
-
};
|
|
46566
|
-
if (includeDeleted) node.markedForDeletion = Boolean(folder.tombstoned);
|
|
46567
|
-
nodes.set(folder.pk, node);
|
|
46568
|
-
}
|
|
46569
|
-
const parentOf = (folder) => {
|
|
46570
|
-
if (folder.parent === null || !byPk.has(folder.parent)) return void 0;
|
|
46571
|
-
const seen = /* @__PURE__ */ new Set([folder.pk]);
|
|
46572
|
-
for (let at = byPk.get(folder.parent); at; at = at.parent !== null ? byPk.get(at.parent) : void 0) {
|
|
46573
|
-
if (seen.has(at.pk)) return void 0;
|
|
46574
|
-
seen.add(at.pk);
|
|
46575
|
-
}
|
|
46576
|
-
return folder.parent;
|
|
46577
|
-
};
|
|
46578
|
-
const roots = /* @__PURE__ */ new Map();
|
|
46579
|
-
for (const folder of visible2) {
|
|
46580
|
-
const node = nodes.get(folder.pk);
|
|
46581
|
-
const parent = parentOf(folder);
|
|
46582
|
-
if (parent !== void 0) nodes.get(parent).children.push(node);
|
|
46583
|
-
else {
|
|
46584
|
-
const key = folder.account !== null && context.accounts.some((a) => a.pk === folder.account) ? folder.account : null;
|
|
46585
|
-
roots.set(key, [...roots.get(key) ?? [], node]);
|
|
46586
|
-
}
|
|
46587
|
-
}
|
|
46588
|
-
const finish = (node) => {
|
|
46589
|
-
node.children.sort(compareNodes);
|
|
46590
|
-
node.totalNoteCount = node.noteCount + node.children.reduce((sum, c) => sum + finish(c), 0);
|
|
46591
|
-
return node.totalNoteCount;
|
|
46592
|
-
};
|
|
46593
|
-
const accounts = [];
|
|
46594
|
-
const addAccount = (name, identifier, folders) => {
|
|
46595
|
-
folders.sort(compareNodes);
|
|
46596
|
-
let noteCount = 0;
|
|
46597
|
-
for (const node of folders) {
|
|
46598
|
-
const total = finish(node);
|
|
46599
|
-
if (node.kind === "folder") noteCount += total;
|
|
46600
|
-
}
|
|
46601
|
-
accounts.push({ account: name, identifier, noteCount, folders });
|
|
46602
|
-
};
|
|
46603
|
-
for (const account of scope2 ? [scope2] : context.accounts) {
|
|
46604
|
-
addAccount(account.name, account.identifier, roots.get(account.pk) ?? []);
|
|
46605
|
-
}
|
|
46606
|
-
const orphans = roots.get(null);
|
|
46607
|
-
if (orphans && includeDeleted) addAccount("unknown", null, orphans);
|
|
46608
|
-
return { accounts, folderCount: accounts.reduce((sum, a) => sum + countNodes(a.folders), 0) };
|
|
46609
|
-
}
|
|
46610
|
-
function countNodes(nodes) {
|
|
46611
|
-
return nodes.reduce((sum, node) => sum + 1 + countNodes(node.children), 0);
|
|
46612
|
-
}
|
|
46613
|
-
|
|
46614
|
-
// src/utils/noteIdentifiers.ts
|
|
46615
|
-
import { execFileSync as execFileSync13 } from "child_process";
|
|
46616
|
-
import * as fs7 from "fs";
|
|
46617
|
-
import * as os7 from "os";
|
|
46618
|
-
import * as path7 from "path";
|
|
46619
|
-
var NOTES_DB_PATH9 = path7.join(
|
|
46620
|
-
os7.homedir(),
|
|
46621
|
-
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
46622
|
-
);
|
|
46623
|
-
var UUID_PATTERN = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;
|
|
46624
|
-
var NUMERIC_KEY_PATTERN = /^\d{1,18}$/;
|
|
46625
|
-
var NOTE_ID_MESSAGE = "A canonical Apple Note ID is required (x-coredata://.../ICNote/p...), or the note's Notes UUID or numeric key";
|
|
46626
|
-
function identifierForm(value) {
|
|
46627
|
-
if (UUID_PATTERN.test(value)) return "uuid";
|
|
46628
|
-
if (NUMERIC_KEY_PATTERN.test(value)) return "key";
|
|
46629
|
-
return "other";
|
|
46630
|
-
}
|
|
46631
|
-
function isAlternateIdentifier(value) {
|
|
46632
|
-
return identifierForm(value) !== "other";
|
|
46633
|
-
}
|
|
46634
|
-
var IdentifierResolutionError = class extends Error {
|
|
46635
|
-
constructor(code, message) {
|
|
46636
|
-
super(message);
|
|
46637
|
-
this.code = code;
|
|
46638
|
-
this.name = "IdentifierResolutionError";
|
|
46639
|
-
}
|
|
46640
|
-
code;
|
|
46641
|
-
};
|
|
46642
|
-
var ENTITY_LABEL = {
|
|
46643
|
-
ICNote: "note",
|
|
46644
|
-
ICFolder: "folder",
|
|
46645
|
-
ICAccount: "account"
|
|
46646
|
-
};
|
|
46647
|
-
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}`;
|
|
46648
|
-
function canonicalKey(value) {
|
|
46649
|
-
return BigInt(value).toString();
|
|
46650
|
-
}
|
|
46651
|
-
function assertAll(values, pattern2, label) {
|
|
46652
|
-
for (const value of values) {
|
|
46653
|
-
if (!pattern2.test(value)) throw new Error(`Refusing to query with an invalid ${label}`);
|
|
46654
|
-
}
|
|
46655
|
-
}
|
|
46656
|
-
function entityClause(entity3) {
|
|
46657
|
-
return `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = '${entity3}')`;
|
|
46658
|
-
}
|
|
46659
|
-
function buildResolveSql(entity3, keys, uuids) {
|
|
46660
|
-
assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
|
|
46661
|
-
assertAll(uuids, UUID_PATTERN, "UUID");
|
|
46662
|
-
const matches = [];
|
|
46663
|
-
if (keys.length > 0) {
|
|
46664
|
-
matches.push(`o.Z_PK IN (${[...new Set(keys.map(canonicalKey))].join(", ")})`);
|
|
46665
|
-
}
|
|
46666
|
-
if (uuids.length > 0) {
|
|
46667
|
-
const variants = /* @__PURE__ */ new Set();
|
|
46668
|
-
for (const uuid2 of uuids) {
|
|
46669
|
-
variants.add(uuid2);
|
|
46670
|
-
variants.add(uuid2.toUpperCase());
|
|
46671
|
-
variants.add(uuid2.toLowerCase());
|
|
46672
|
-
}
|
|
46673
|
-
matches.push(`o.ZIDENTIFIER IN (${[...variants].map((v) => `'${v}'`).join(", ")})`);
|
|
46674
|
-
}
|
|
46675
|
-
const where = matches.length > 0 ? matches.join(" OR ") : "0";
|
|
46676
|
-
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})));`;
|
|
46677
|
-
}
|
|
46678
|
-
function buildLookupSql(entity3, keys) {
|
|
46679
|
-
assertAll(keys, NUMERIC_KEY_PATTERN, "numeric key");
|
|
46680
|
-
const pks = [...new Set(keys.map(canonicalKey))];
|
|
46681
|
-
const inList = pks.length > 0 ? pks.join(", ") : "NULL";
|
|
46682
|
-
let fields = "'pk', o.Z_PK, 'identifier', o.ZIDENTIFIER";
|
|
46683
|
-
let joins = "";
|
|
46684
|
-
if (entity3 === "ICNote") {
|
|
46685
|
-
fields += ", 'folderIdentifier', f.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
|
|
46686
|
-
joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT f ON f.Z_PK = o.ZFOLDER LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = f.ZOWNER ";
|
|
46687
|
-
} else if (entity3 === "ICFolder") {
|
|
46688
|
-
fields += ", 'parentIdentifier', p.ZIDENTIFIER, 'accountIdentifier', a.ZIDENTIFIER";
|
|
46689
|
-
joins = "LEFT JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = o.ZPARENT LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = o.ZOWNER ";
|
|
46690
|
-
}
|
|
46691
|
-
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})));`;
|
|
46692
|
-
}
|
|
46693
|
-
function runJsonQuery(sql, dbPath2) {
|
|
46694
|
-
if (!fs7.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
46695
|
-
let out;
|
|
46696
|
-
try {
|
|
46697
|
-
out = execFileSync13("sqlite3", ["-readonly", dbPath2, sql], {
|
|
46698
|
-
encoding: "utf8",
|
|
46699
|
-
timeout: 5e3,
|
|
46700
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
46701
|
-
}).trim();
|
|
46702
|
-
} catch (error2) {
|
|
46703
|
-
const detail = error2 instanceof Error ? `${error2.message} ${String(error2.stderr ?? "")}` : String(error2);
|
|
46704
|
-
if (detail.includes("authorization denied") || detail.includes("unable to open database")) {
|
|
46705
|
-
throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
46706
|
-
}
|
|
46707
|
-
console.error(`Identifier query failed: ${detail}`);
|
|
46708
|
-
throw new IdentifierResolutionError("query_error", "Failed to read the Notes database.");
|
|
46709
|
-
}
|
|
46710
|
-
const parsed = JSON.parse(out || "{}");
|
|
46711
|
-
const rows = typeof parsed.rows === "string" ? JSON.parse(parsed.rows) : parsed.rows;
|
|
46712
|
-
return { store: parsed.store ?? null, rows: rows ?? [] };
|
|
46713
|
-
}
|
|
46714
|
-
function coreDataId(store, entity3, pk) {
|
|
46715
|
-
return `x-coredata://${store}/${entity3}/p${pk}`;
|
|
46716
|
-
}
|
|
46717
|
-
function resolveIdentifiers(values, entity3, dbPath2 = NOTES_DB_PATH9) {
|
|
46718
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
46719
|
-
const keys = [];
|
|
46720
|
-
const uuids = [];
|
|
46721
|
-
for (const value of values) {
|
|
46722
|
-
const form = identifierForm(value);
|
|
46723
|
-
if (form === "key") keys.push(value);
|
|
46724
|
-
else if (form === "uuid") uuids.push(value);
|
|
46725
|
-
else resolved.set(value, value);
|
|
46726
|
-
}
|
|
46727
|
-
if (keys.length === 0 && uuids.length === 0) return resolved;
|
|
46728
|
-
const { store, rows } = runJsonQuery(
|
|
46729
|
-
buildResolveSql(entity3, keys, uuids),
|
|
46730
|
-
dbPath2
|
|
46731
|
-
);
|
|
46732
|
-
if (!store) {
|
|
46733
|
-
throw new IdentifierResolutionError(
|
|
46734
|
-
"query_error",
|
|
46735
|
-
"The Notes database has no store UUID, so an x-coredata id cannot be built."
|
|
46736
|
-
);
|
|
46737
|
-
}
|
|
46738
|
-
const byKey = new Map(rows.map((row) => [String(row.pk), row]));
|
|
46739
|
-
const byUuid = new Map(
|
|
46740
|
-
rows.filter((row) => row.identifier).map((row) => [String(row.identifier).toUpperCase(), row])
|
|
46741
|
-
);
|
|
46742
|
-
const label = ENTITY_LABEL[entity3];
|
|
46743
|
-
const missing = [];
|
|
46744
|
-
for (const key of keys) {
|
|
46745
|
-
const row = byKey.get(canonicalKey(key));
|
|
46746
|
-
if (row) resolved.set(key, coreDataId(store, entity3, row.pk));
|
|
46747
|
-
else missing.push(`numeric key ${key}`);
|
|
46748
|
-
}
|
|
46749
|
-
for (const uuid2 of uuids) {
|
|
46750
|
-
const row = byUuid.get(uuid2.toUpperCase());
|
|
46751
|
-
if (row) resolved.set(uuid2, coreDataId(store, entity3, row.pk));
|
|
46752
|
-
else missing.push(`identifier ${uuid2}`);
|
|
46753
|
-
}
|
|
46754
|
-
if (missing.length > 0) {
|
|
46755
|
-
throw new IdentifierResolutionError(
|
|
46756
|
-
"not_found",
|
|
46757
|
-
`No ${label} found for ${missing.join(", ")}. A numeric key must belong to a ${label}, not another object type.`
|
|
46758
|
-
);
|
|
46759
|
-
}
|
|
46760
|
-
return resolved;
|
|
46761
|
-
}
|
|
46762
|
-
var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
|
|
46763
|
-
function lookupStableIdentifiers(ids, entity3, dbPath2 = NOTES_DB_PATH9) {
|
|
46764
|
-
const result = /* @__PURE__ */ new Map();
|
|
46765
|
-
const wanted = /* @__PURE__ */ new Map();
|
|
46766
|
-
for (const id2 of ids) {
|
|
46767
|
-
const match = COREDATA_PARTS.exec(id2);
|
|
46768
|
-
if (!match || match[2] !== entity3) continue;
|
|
46769
|
-
const pk = canonicalKey(match[3]);
|
|
46770
|
-
const list = wanted.get(pk) ?? [];
|
|
46771
|
-
list.push({ id: id2, store: match[1] });
|
|
46772
|
-
wanted.set(pk, list);
|
|
46773
|
-
}
|
|
46774
|
-
if (wanted.size === 0) return result;
|
|
46775
|
-
let query2;
|
|
46776
|
-
try {
|
|
46777
|
-
query2 = runJsonQuery(buildLookupSql(entity3, [...wanted.keys()]), dbPath2);
|
|
46778
|
-
} catch {
|
|
46779
|
-
return result;
|
|
46780
|
-
}
|
|
46781
|
-
const store = query2.store?.toUpperCase();
|
|
46782
|
-
for (const row of query2.rows) {
|
|
46783
|
-
const fields = {};
|
|
46784
|
-
for (const key of [
|
|
46785
|
-
"identifier",
|
|
46786
|
-
"folderIdentifier",
|
|
46787
|
-
"parentIdentifier",
|
|
46788
|
-
"accountIdentifier"
|
|
46789
|
-
]) {
|
|
46790
|
-
const value = row[key];
|
|
46791
|
-
if (typeof value === "string" && value) fields[key] = value;
|
|
46792
|
-
}
|
|
46793
|
-
for (const target of wanted.get(String(row.pk)) ?? []) {
|
|
46794
|
-
if (target.store.toUpperCase() === store) result.set(target.id, fields);
|
|
46795
|
-
}
|
|
46796
|
-
}
|
|
46797
|
-
return result;
|
|
46872
|
+
function compareNodes(a, b) {
|
|
46873
|
+
return KIND_ORDER[a.kind] - KIND_ORDER[b.kind] || a.name.localeCompare(b.name);
|
|
46798
46874
|
}
|
|
46799
|
-
function
|
|
46800
|
-
const
|
|
46801
|
-
|
|
46802
|
-
const
|
|
46803
|
-
|
|
46804
|
-
|
|
46805
|
-
(
|
|
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])
|
|
46806
46884
|
);
|
|
46885
|
+
return assembleFolderTree(context, counts, Boolean(options.includeDeleted), scope2);
|
|
46807
46886
|
}
|
|
46808
|
-
|
|
46809
|
-
|
|
46810
|
-
|
|
46811
|
-
|
|
46812
|
-
`${pattern2.source}|${UUID_PATTERN.source}|${NUMERIC_KEY_PATTERN.source}`,
|
|
46813
|
-
pattern2.flags
|
|
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)
|
|
46814
46891
|
);
|
|
46815
|
-
|
|
46816
|
-
|
|
46817
|
-
|
|
46818
|
-
|
|
46819
|
-
|
|
46820
|
-
|
|
46821
|
-
|
|
46822
|
-
|
|
46823
|
-
|
|
46824
|
-
|
|
46825
|
-
|
|
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);
|
|
46826
46907
|
}
|
|
46827
|
-
|
|
46828
|
-
|
|
46829
|
-
|
|
46830
|
-
|
|
46831
|
-
|
|
46832
|
-
|
|
46833
|
-
|
|
46834
|
-
|
|
46835
|
-
|
|
46836
|
-
const
|
|
46837
|
-
const
|
|
46838
|
-
|
|
46839
|
-
|
|
46840
|
-
|
|
46841
|
-
|
|
46842
|
-
|
|
46843
|
-
|
|
46844
|
-
|
|
46845
|
-
|
|
46846
|
-
|
|
46847
|
-
|
|
46848
|
-
|
|
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 });
|
|
46849
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);
|
|
46850
46951
|
}
|
|
46851
46952
|
|
|
46852
46953
|
// src/utils/contentWarnings.ts
|
|
@@ -48531,7 +48632,15 @@ function mutateBackground(request, operation, data, verify, deps) {
|
|
|
48531
48632
|
transportError = error2 ?? {};
|
|
48532
48633
|
transportMessage = describeTransportFailure(error2);
|
|
48533
48634
|
}
|
|
48534
|
-
|
|
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
|
+
}
|
|
48535
48644
|
try {
|
|
48536
48645
|
verify(before, after);
|
|
48537
48646
|
} catch (error2) {
|
|
@@ -48797,14 +48906,27 @@ var FEATURES = [
|
|
|
48797
48906
|
},
|
|
48798
48907
|
{
|
|
48799
48908
|
name: "fullDiskAccessReads",
|
|
48800
|
-
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",
|
|
48801
48910
|
tools: [
|
|
48802
48911
|
"query-notes",
|
|
48803
48912
|
"get-checklist-state",
|
|
48804
48913
|
"get-note-metadata",
|
|
48805
48914
|
"get-note-link",
|
|
48806
48915
|
"get-native-objects",
|
|
48916
|
+
"get-note-blocks",
|
|
48917
|
+
"get-note-structure",
|
|
48918
|
+
"get-note-tables",
|
|
48919
|
+
"list-note-links",
|
|
48807
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",
|
|
48808
48930
|
"get-note-markdown (checklist annotations)",
|
|
48809
48931
|
"get-sync-status (pending uploads)"
|
|
48810
48932
|
],
|
|
@@ -48865,7 +48987,8 @@ var FEATURES = [
|
|
|
48865
48987
|
// Placeholders for features that need a native WRITE helper, which this
|
|
48866
48988
|
// server does not ship. The opt-in private helper is read-only (write
|
|
48867
48989
|
// support was deliberately deferred), so enabling or installing it changes
|
|
48868
|
-
// 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).
|
|
48869
48992
|
{
|
|
48870
48993
|
name: "checklistToggle",
|
|
48871
48994
|
description: "Check or uncheck an existing checklist item in place",
|
|
@@ -48889,17 +49012,17 @@ var FEATURES = [
|
|
|
48889
49012
|
},
|
|
48890
49013
|
{
|
|
48891
49014
|
name: "paragraphLinks",
|
|
48892
|
-
description: "
|
|
48893
|
-
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"],
|
|
48894
49017
|
minimumMacOSVersion: null,
|
|
48895
|
-
requirements: [{ kind: "
|
|
49018
|
+
requirements: [{ kind: "full_disk_access" }]
|
|
48896
49019
|
},
|
|
48897
49020
|
{
|
|
48898
49021
|
name: "audioTranscription",
|
|
48899
|
-
description: "
|
|
48900
|
-
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"],
|
|
48901
49024
|
minimumMacOSVersion: null,
|
|
48902
|
-
requirements: [{ kind: "
|
|
49025
|
+
requirements: [{ kind: "full_disk_access" }]
|
|
48903
49026
|
},
|
|
48904
49027
|
{
|
|
48905
49028
|
name: "markdownTemplateLibrary",
|
|
@@ -49570,7 +49693,7 @@ function resolveNote(dbPath2, columns, selector) {
|
|
|
49570
49693
|
function noteBodySql(columns) {
|
|
49571
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;`;
|
|
49572
49695
|
}
|
|
49573
|
-
function readNoteParagraphs(selector, { dbPath: dbPath2 =
|
|
49696
|
+
function readNoteParagraphs(selector, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
|
|
49574
49697
|
checkNoteSelector(selector);
|
|
49575
49698
|
const columns = readColumns(dbPath2);
|
|
49576
49699
|
const { pk, id: id2 } = resolveNote(dbPath2, columns, selector);
|
|
@@ -49799,7 +49922,7 @@ function describeNoteStructure(s) {
|
|
|
49799
49922
|
if (s.isShared) parts.push("shared");
|
|
49800
49923
|
return `Note structure: ${parts.join("; ")}.`;
|
|
49801
49924
|
}
|
|
49802
|
-
function readNoteStructure(id2, { dbPath: dbPath2 =
|
|
49925
|
+
function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH8, includeText = true, maxTextBytes = 4 * 1024 * 1024 } = {}) {
|
|
49803
49926
|
const { pk } = parseNoteId2(id2);
|
|
49804
49927
|
const columns = readColumns(dbPath2);
|
|
49805
49928
|
const rows = parseJsonLines(
|
|
@@ -50045,7 +50168,7 @@ function decodeBodies(dbPath2, sql, params) {
|
|
|
50045
50168
|
}
|
|
50046
50169
|
var KIND_ORDER2 = { inline: 0, card: 1, note: 2, section: 3 };
|
|
50047
50170
|
function listNoteLinks(options = {}) {
|
|
50048
|
-
const { dbPath: dbPath2 =
|
|
50171
|
+
const { dbPath: dbPath2 = NOTES_DB_PATH8, offset = 0, limit = 200, maxBytes = 4 * 1024 * 1024 } = options;
|
|
50049
50172
|
if (options.id && (options.account || options.folder))
|
|
50050
50173
|
throw new NoteStoreError("Pass either id or account/folder, not both.", "invalid_input");
|
|
50051
50174
|
const notePk = options.id ? parseNoteId2(options.id).pk : 0;
|
|
@@ -50267,8 +50390,9 @@ function insertLink(request, deps) {
|
|
|
50267
50390
|
try {
|
|
50268
50391
|
readback = verifyLinkReadback(before, deps.readLinks(request.id), insertion, request.url);
|
|
50269
50392
|
} catch (error2) {
|
|
50270
|
-
throw new
|
|
50271
|
-
`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 }
|
|
50272
50396
|
);
|
|
50273
50397
|
}
|
|
50274
50398
|
return {
|
|
@@ -52590,21 +52714,23 @@ function renderNotesWithTemplate(notes, ctx, options) {
|
|
|
52590
52714
|
}
|
|
52591
52715
|
|
|
52592
52716
|
// src/utils/templateAssets.ts
|
|
52593
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
52717
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
52594
52718
|
import {
|
|
52595
52719
|
closeSync as closeSync5,
|
|
52596
52720
|
constants as constants5,
|
|
52597
52721
|
fstatSync as fstatSync5,
|
|
52722
|
+
linkSync,
|
|
52598
52723
|
lstatSync as lstatSync4,
|
|
52599
52724
|
mkdirSync as mkdirSync5,
|
|
52600
52725
|
openSync as openSync5,
|
|
52601
52726
|
readSync as readSync4,
|
|
52727
|
+
unlinkSync as unlinkSync3,
|
|
52602
52728
|
writeSync as writeSync4
|
|
52603
52729
|
} from "node:fs";
|
|
52604
52730
|
import { extname as extname4, isAbsolute as isAbsolute3, join as join21, relative as relative4, resolve as resolve4, sep as sep4 } from "node:path";
|
|
52605
52731
|
var CHUNK = 1024 * 1024;
|
|
52606
52732
|
function openRegular(path10) {
|
|
52607
|
-
const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW);
|
|
52733
|
+
const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
52608
52734
|
if (!fstatSync5(fd).isFile()) {
|
|
52609
52735
|
closeSync5(fd);
|
|
52610
52736
|
throw new Error("not a regular file");
|
|
@@ -52690,26 +52816,56 @@ var HashedSidecarWriter = class {
|
|
|
52690
52816
|
closeSync5(source);
|
|
52691
52817
|
}
|
|
52692
52818
|
}
|
|
52819
|
+
/**
|
|
52820
|
+
* Copy to a private temporary name, then hard-link it into place, so the
|
|
52821
|
+
* hashed name only ever holds complete content: an interrupted copy can't
|
|
52822
|
+
* leave a truncated file that later exports would report as name-taken.
|
|
52823
|
+
* Where the volume has no hard links, copy straight to the hashed name and
|
|
52824
|
+
* remove it if the copy fails.
|
|
52825
|
+
*/
|
|
52693
52826
|
copy(source, target) {
|
|
52694
|
-
const
|
|
52695
|
-
|
|
52696
|
-
constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW,
|
|
52697
|
-
420
|
|
52698
|
-
);
|
|
52827
|
+
const temp = join21(this.dir, `.asset-${randomBytes(6).toString("hex")}.tmp`);
|
|
52828
|
+
writeNew(source, temp);
|
|
52699
52829
|
try {
|
|
52700
|
-
|
|
52701
|
-
|
|
52702
|
-
|
|
52703
|
-
|
|
52704
|
-
|
|
52705
|
-
while (written < n) written += writeSync4(out, chunk, written, n - written);
|
|
52706
|
-
position += n;
|
|
52707
|
-
}
|
|
52830
|
+
linkSync(temp, target);
|
|
52831
|
+
return;
|
|
52832
|
+
} catch (error2) {
|
|
52833
|
+
const code = error2.code;
|
|
52834
|
+
if (code !== "ENOTSUP" && code !== "EPERM" && code !== "EXDEV") throw error2;
|
|
52708
52835
|
} finally {
|
|
52709
|
-
|
|
52836
|
+
try {
|
|
52837
|
+
unlinkSync3(temp);
|
|
52838
|
+
} catch {
|
|
52839
|
+
}
|
|
52710
52840
|
}
|
|
52841
|
+
writeNew(source, target);
|
|
52711
52842
|
}
|
|
52712
52843
|
};
|
|
52844
|
+
function writeNew(source, target) {
|
|
52845
|
+
const out = openSync5(
|
|
52846
|
+
target,
|
|
52847
|
+
constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW,
|
|
52848
|
+
420
|
|
52849
|
+
);
|
|
52850
|
+
try {
|
|
52851
|
+
const chunk = Buffer.alloc(CHUNK);
|
|
52852
|
+
for (let position = 0; ; ) {
|
|
52853
|
+
const n = readSync4(source, chunk, 0, chunk.length, position);
|
|
52854
|
+
if (n <= 0) break;
|
|
52855
|
+
let written = 0;
|
|
52856
|
+
while (written < n) written += writeSync4(out, chunk, written, n - written);
|
|
52857
|
+
position += n;
|
|
52858
|
+
}
|
|
52859
|
+
} catch (error2) {
|
|
52860
|
+
closeSync5(out);
|
|
52861
|
+
try {
|
|
52862
|
+
unlinkSync3(target);
|
|
52863
|
+
} catch {
|
|
52864
|
+
}
|
|
52865
|
+
throw error2;
|
|
52866
|
+
}
|
|
52867
|
+
closeSync5(out);
|
|
52868
|
+
}
|
|
52713
52869
|
var ReferenceWriter = class {
|
|
52714
52870
|
constructor(linkBase) {
|
|
52715
52871
|
this.linkBase = linkBase;
|
|
@@ -53276,7 +53432,7 @@ function pasteboardFilename(requested, frozenFilename) {
|
|
|
53276
53432
|
function readRegularFile(path10) {
|
|
53277
53433
|
let descriptor;
|
|
53278
53434
|
try {
|
|
53279
|
-
descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
|
|
53435
|
+
descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
|
|
53280
53436
|
} catch {
|
|
53281
53437
|
throw new PasteboardError("file_unreadable", MESSAGES.file_unreadable);
|
|
53282
53438
|
}
|
|
@@ -53480,7 +53636,7 @@ function registerDirectOperations(server2, manager) {
|
|
|
53480
53636
|
);
|
|
53481
53637
|
tool(
|
|
53482
53638
|
"create-note-with-attachment",
|
|
53483
|
-
"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.",
|
|
53484
53640
|
{
|
|
53485
53641
|
title: external_exports.string().min(1).max(2e3).refine((s) => !/[\r\n\0]/u.test(s), "One-line title"),
|
|
53486
53642
|
content: external_exports.string().min(1).max(1024 * 1024).optional().describe("Optional plain-text body placed above the attachment"),
|
|
@@ -53506,26 +53662,45 @@ function registerDirectOperations(server2, manager) {
|
|
|
53506
53662
|
`Failed to create note "${args.title}". Check that the folder and account exist (list-folders, list-accounts); nothing was attached`
|
|
53507
53663
|
);
|
|
53508
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
|
+
});
|
|
53509
53670
|
let snapshot;
|
|
53510
53671
|
try {
|
|
53511
53672
|
snapshot = readSnapshot(manager, note.id);
|
|
53512
53673
|
} catch (error2) {
|
|
53513
|
-
|
|
53674
|
+
const message = `${handOff}. The new note could not be read back: ${String(error2)}`;
|
|
53675
|
+
throw createdError(message, error2);
|
|
53514
53676
|
}
|
|
53677
|
+
const progress = { insertionStarted: false };
|
|
53515
53678
|
try {
|
|
53516
53679
|
return {
|
|
53517
|
-
...attachFile(
|
|
53518
|
-
|
|
53519
|
-
|
|
53520
|
-
|
|
53521
|
-
|
|
53522
|
-
|
|
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
|
+
),
|
|
53523
53691
|
title: args.title,
|
|
53524
53692
|
folder: args.folder,
|
|
53525
53693
|
noteCreated: true
|
|
53526
53694
|
};
|
|
53527
53695
|
} catch (error2) {
|
|
53528
|
-
|
|
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
|
+
});
|
|
53529
53704
|
}
|
|
53530
53705
|
}
|
|
53531
53706
|
);
|
|
@@ -53632,7 +53807,7 @@ function storedInsertion(manager, id2, before, bytes, returnedId) {
|
|
|
53632
53807
|
);
|
|
53633
53808
|
return { attachmentId, name: row.filename };
|
|
53634
53809
|
}
|
|
53635
|
-
function attachFile(manager, args, checked) {
|
|
53810
|
+
function attachFile(manager, args, checked, progress = { insertionStarted: false }) {
|
|
53636
53811
|
const { id: id2, expectedContentHash, path: path10 } = args;
|
|
53637
53812
|
const name = attachmentName(path10, args.filename);
|
|
53638
53813
|
const before = checked ?? readSnapshot(manager, id2);
|
|
@@ -53647,6 +53822,7 @@ function attachFile(manager, args, checked) {
|
|
|
53647
53822
|
if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
|
|
53648
53823
|
let returnedId;
|
|
53649
53824
|
let transportUncertain = false;
|
|
53825
|
+
progress.insertionStarted = true;
|
|
53650
53826
|
try {
|
|
53651
53827
|
returnedId = manager.addAttachmentById(id2, before.body, temporaryFile);
|
|
53652
53828
|
} catch {
|
|
@@ -53974,15 +54150,27 @@ function runFolderDelete(manager, args, deps = defaultDeps) {
|
|
|
53974
54150
|
throw uncertain(
|
|
53975
54151
|
`The delete outcome is uncertain (${outcome.reason}); read folder ${args.id} before retrying`
|
|
53976
54152
|
);
|
|
53977
|
-
|
|
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)
|
|
53978
54162
|
throw uncertain(
|
|
53979
54163
|
`The delete outcome is uncertain: Notes.app still resolves folder ${args.id}; read it before retrying`
|
|
53980
54164
|
);
|
|
53981
54165
|
let storeTombstoned = false;
|
|
53982
54166
|
for (let attempt = 0; attempt < 5 && !storeTombstoned; attempt++) {
|
|
53983
54167
|
if (attempt > 0) deps.sleep(200);
|
|
53984
|
-
|
|
53985
|
-
|
|
54168
|
+
try {
|
|
54169
|
+
const after = deps.readStore(coreDataPk(args.id));
|
|
54170
|
+
storeTombstoned = !after || after.markedForDeletion;
|
|
54171
|
+
} catch {
|
|
54172
|
+
break;
|
|
54173
|
+
}
|
|
53986
54174
|
}
|
|
53987
54175
|
return {
|
|
53988
54176
|
...base,
|
|
@@ -54031,19 +54219,19 @@ function registerFolderDelete(server2, manager, deps = defaultDeps) {
|
|
|
54031
54219
|
}
|
|
54032
54220
|
|
|
54033
54221
|
// src/services/templateStore.ts
|
|
54034
|
-
import { randomBytes } from "node:crypto";
|
|
54222
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
54035
54223
|
import {
|
|
54036
54224
|
closeSync as closeSync8,
|
|
54037
54225
|
constants as constants8,
|
|
54038
54226
|
fstatSync as fstatSync8,
|
|
54039
|
-
linkSync,
|
|
54227
|
+
linkSync as linkSync2,
|
|
54040
54228
|
lstatSync as lstatSync5,
|
|
54041
54229
|
mkdirSync as mkdirSync7,
|
|
54042
54230
|
openSync as openSync8,
|
|
54043
54231
|
readdirSync as readdirSync4,
|
|
54044
54232
|
readSync as readSync6,
|
|
54045
54233
|
renameSync,
|
|
54046
|
-
unlinkSync as
|
|
54234
|
+
unlinkSync as unlinkSync4,
|
|
54047
54235
|
writeSync as writeSync5
|
|
54048
54236
|
} from "node:fs";
|
|
54049
54237
|
import { homedir as homedir19 } from "node:os";
|
|
@@ -54106,14 +54294,26 @@ var TemplateStore = class {
|
|
|
54106
54294
|
file(name) {
|
|
54107
54295
|
return join26(this.dir, `${name}.json`);
|
|
54108
54296
|
}
|
|
54109
|
-
/**
|
|
54297
|
+
/**
|
|
54298
|
+
* Read a saved template's text, refusing symlinks and non-regular files.
|
|
54299
|
+
* O_NONBLOCK keeps a FIFO from blocking the event loop (it is then refused as
|
|
54300
|
+
* not a regular file); it does not affect reading a regular file. Only a
|
|
54301
|
+
* symlink (ELOOP) is reported as unsafe-path; other open failures, such as
|
|
54302
|
+
* EACCES, are rethrown as they are.
|
|
54303
|
+
*/
|
|
54110
54304
|
readText(path10) {
|
|
54111
54305
|
let fd;
|
|
54112
54306
|
try {
|
|
54113
|
-
fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW);
|
|
54307
|
+
fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
|
|
54114
54308
|
} catch (error2) {
|
|
54115
|
-
|
|
54116
|
-
|
|
54309
|
+
const errno = error2.code;
|
|
54310
|
+
if (errno === "ENOENT") return void 0;
|
|
54311
|
+
if (errno === "ELOOP")
|
|
54312
|
+
throw new TemplateStoreError(
|
|
54313
|
+
"unsafe-path",
|
|
54314
|
+
`Refusing to read ${path10}: it is a symbolic link.`
|
|
54315
|
+
);
|
|
54316
|
+
throw error2;
|
|
54117
54317
|
}
|
|
54118
54318
|
try {
|
|
54119
54319
|
const stat = fstatSync8(fd);
|
|
@@ -54218,7 +54418,7 @@ var TemplateStore = class {
|
|
|
54218
54418
|
"template-exists",
|
|
54219
54419
|
`A template named "${name}" already exists. Pass force: true to replace it.`
|
|
54220
54420
|
);
|
|
54221
|
-
const temp = join26(this.dir, `.${name}.${
|
|
54421
|
+
const temp = join26(this.dir, `.${name}.${randomBytes2(6).toString("hex")}.tmp`);
|
|
54222
54422
|
const fd = openSync8(
|
|
54223
54423
|
temp,
|
|
54224
54424
|
constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
|
|
@@ -54228,14 +54428,20 @@ var TemplateStore = class {
|
|
|
54228
54428
|
const data = Buffer.from(body, "utf8");
|
|
54229
54429
|
let written = 0;
|
|
54230
54430
|
while (written < data.length) written += writeSync5(fd, data, written);
|
|
54231
|
-
}
|
|
54431
|
+
} catch (error2) {
|
|
54232
54432
|
closeSync8(fd);
|
|
54433
|
+
try {
|
|
54434
|
+
unlinkSync4(temp);
|
|
54435
|
+
} catch {
|
|
54436
|
+
}
|
|
54437
|
+
throw error2;
|
|
54233
54438
|
}
|
|
54439
|
+
closeSync8(fd);
|
|
54234
54440
|
try {
|
|
54235
54441
|
if (force) renameSync(temp, path10);
|
|
54236
54442
|
else {
|
|
54237
54443
|
try {
|
|
54238
|
-
|
|
54444
|
+
linkSync2(temp, path10);
|
|
54239
54445
|
} catch (error2) {
|
|
54240
54446
|
if (error2.code === "EEXIST")
|
|
54241
54447
|
throw new TemplateStoreError(
|
|
@@ -54247,7 +54453,7 @@ var TemplateStore = class {
|
|
|
54247
54453
|
}
|
|
54248
54454
|
} finally {
|
|
54249
54455
|
try {
|
|
54250
|
-
|
|
54456
|
+
unlinkSync4(temp);
|
|
54251
54457
|
} catch {
|
|
54252
54458
|
}
|
|
54253
54459
|
}
|
|
@@ -54270,7 +54476,7 @@ var TemplateStore = class {
|
|
|
54270
54476
|
"unsafe-path",
|
|
54271
54477
|
`Refusing to delete ${path10}: not a regular file.`
|
|
54272
54478
|
);
|
|
54273
|
-
|
|
54479
|
+
unlinkSync4(path10);
|
|
54274
54480
|
return { path: path10 };
|
|
54275
54481
|
}
|
|
54276
54482
|
};
|
|
@@ -54656,7 +54862,7 @@ function parseColor(value) {
|
|
|
54656
54862
|
}
|
|
54657
54863
|
function parsePaint(value) {
|
|
54658
54864
|
const v = value.trim();
|
|
54659
|
-
if (v
|
|
54865
|
+
if (/^none$/i.test(v)) return { kind: "none" };
|
|
54660
54866
|
if (/^currentcolor$/i.test(v)) return { kind: "current" };
|
|
54661
54867
|
const url = /^url\(\s*['"]?([^'")]*)['"]?\s*\)/i.exec(v);
|
|
54662
54868
|
if (url) return { kind: "url", target: url[1].trim() };
|
|
@@ -55031,8 +55237,9 @@ function distanceToChord(p, a, b) {
|
|
|
55031
55237
|
if (len === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
|
|
55032
55238
|
return Math.abs((p[0] - a[0]) * dy - (p[1] - a[1]) * dx) / len;
|
|
55033
55239
|
}
|
|
55034
|
-
function flattenCubic(p0, c1, c2, p3, tolerance, out, depth = 0) {
|
|
55240
|
+
function flattenCubic(p0, c1, c2, p3, tolerance, out, depth = 0, charge) {
|
|
55035
55241
|
if (depth >= 16 || Math.max(distanceToChord(c1, p0, p3), distanceToChord(c2, p0, p3)) <= tolerance) {
|
|
55242
|
+
charge?.();
|
|
55036
55243
|
out.push(p3);
|
|
55037
55244
|
return;
|
|
55038
55245
|
}
|
|
@@ -55043,32 +55250,48 @@ function flattenCubic(p0, c1, c2, p3, tolerance, out, depth = 0) {
|
|
|
55043
55250
|
const d = mid(a, b);
|
|
55044
55251
|
const e = mid(b, c);
|
|
55045
55252
|
const m = mid(d, e);
|
|
55046
|
-
flattenCubic(p0, a, d, m, tolerance, out, depth + 1);
|
|
55047
|
-
flattenCubic(m, e, c, p3, tolerance, out, depth + 1);
|
|
55253
|
+
flattenCubic(p0, a, d, m, tolerance, out, depth + 1, charge);
|
|
55254
|
+
flattenCubic(m, e, c, p3, tolerance, out, depth + 1, charge);
|
|
55048
55255
|
}
|
|
55049
|
-
function flattenSubpath(sub4, m, tolerance) {
|
|
55256
|
+
function flattenSubpath(sub4, m, tolerance, charge) {
|
|
55257
|
+
charge?.();
|
|
55050
55258
|
const points = [apply(m, sub4.start)];
|
|
55051
55259
|
let pen = sub4.start;
|
|
55052
55260
|
for (const seg of sub4.segments) {
|
|
55053
|
-
if (seg.kind === "L")
|
|
55054
|
-
|
|
55261
|
+
if (seg.kind === "L") {
|
|
55262
|
+
charge?.();
|
|
55263
|
+
points.push(apply(m, seg.to));
|
|
55264
|
+
} else
|
|
55055
55265
|
flattenCubic(
|
|
55056
55266
|
apply(m, pen),
|
|
55057
55267
|
apply(m, seg.c1),
|
|
55058
55268
|
apply(m, seg.c2),
|
|
55059
55269
|
apply(m, seg.to),
|
|
55060
55270
|
tolerance,
|
|
55061
|
-
points
|
|
55271
|
+
points,
|
|
55272
|
+
0,
|
|
55273
|
+
charge
|
|
55062
55274
|
);
|
|
55063
55275
|
pen = seg.to;
|
|
55064
55276
|
}
|
|
55065
55277
|
if (sub4.closed) {
|
|
55066
55278
|
const first2 = points[0];
|
|
55067
55279
|
const last = points[points.length - 1];
|
|
55068
|
-
if (last[0] !== first2[0] || last[1] !== first2[1])
|
|
55280
|
+
if (last[0] !== first2[0] || last[1] !== first2[1]) {
|
|
55281
|
+
charge?.();
|
|
55282
|
+
points.push(first2);
|
|
55283
|
+
}
|
|
55069
55284
|
}
|
|
55070
55285
|
return points;
|
|
55071
55286
|
}
|
|
55287
|
+
function subpathControlPoints(sub4, m) {
|
|
55288
|
+
const out = [apply(m, sub4.start)];
|
|
55289
|
+
for (const seg of sub4.segments) {
|
|
55290
|
+
if (seg.kind === "C") out.push(apply(m, seg.c1), apply(m, seg.c2));
|
|
55291
|
+
out.push(apply(m, seg.to));
|
|
55292
|
+
}
|
|
55293
|
+
return out;
|
|
55294
|
+
}
|
|
55072
55295
|
function clipSegment(a, b, r) {
|
|
55073
55296
|
const dx = b[0] - a[0];
|
|
55074
55297
|
const dy = b[1] - a[1];
|
|
@@ -55248,6 +55471,13 @@ var SvgError = class extends Error {
|
|
|
55248
55471
|
code;
|
|
55249
55472
|
location;
|
|
55250
55473
|
};
|
|
55474
|
+
function lookupNamespace(scope2, prefix) {
|
|
55475
|
+
for (let s = scope2; s; s = s.parent) {
|
|
55476
|
+
const ns = s.own?.get(prefix);
|
|
55477
|
+
if (ns !== void 0) return ns;
|
|
55478
|
+
}
|
|
55479
|
+
return void 0;
|
|
55480
|
+
}
|
|
55251
55481
|
var NAME_START = /[A-Za-z_À-]/;
|
|
55252
55482
|
var NAME_CHAR = /[A-Za-z0-9_.\-:·À-]/;
|
|
55253
55483
|
var PREDEFINED = { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'" };
|
|
@@ -55276,6 +55506,9 @@ function parseXml(source, limits) {
|
|
|
55276
55506
|
let pos = 0;
|
|
55277
55507
|
let line = 1;
|
|
55278
55508
|
let elementCount = 0;
|
|
55509
|
+
let namespaceDeclarations = 0;
|
|
55510
|
+
const maxAttributes = limits.maxAttributes ?? 1024;
|
|
55511
|
+
const maxNamespaceDeclarations = limits.maxNamespaceDeclarations ?? 1024;
|
|
55279
55512
|
const stack = [];
|
|
55280
55513
|
let root = null;
|
|
55281
55514
|
const fail = (message) => {
|
|
@@ -55337,6 +55570,7 @@ function parseXml(source, limits) {
|
|
|
55337
55570
|
while (i < source.length && NAME_CHAR.test(source[i])) i++;
|
|
55338
55571
|
const name = source.slice(pos + 1, i);
|
|
55339
55572
|
const rawAttributes = [];
|
|
55573
|
+
const attributeNames = /* @__PURE__ */ new Set();
|
|
55340
55574
|
let selfClosing = false;
|
|
55341
55575
|
for (; ; ) {
|
|
55342
55576
|
const ws = i;
|
|
@@ -55366,8 +55600,13 @@ function parseXml(source, limits) {
|
|
|
55366
55600
|
if (close < 0) fail("Unterminated attribute value");
|
|
55367
55601
|
const rawValue = source.slice(i + 1, close);
|
|
55368
55602
|
if (rawValue.includes("<")) fail("'<' in an attribute value");
|
|
55369
|
-
if (
|
|
55370
|
-
|
|
55603
|
+
if (attributeNames.has(attrName)) fail(`Duplicate attribute ${attrName.slice(0, 40)}`);
|
|
55604
|
+
attributeNames.add(attrName);
|
|
55605
|
+
if (attributeNames.size > maxAttributes)
|
|
55606
|
+
throw new SvgError(
|
|
55607
|
+
"svg_complexity_limit",
|
|
55608
|
+
`An element has more than ${maxAttributes} attributes (line ${line})`
|
|
55609
|
+
);
|
|
55371
55610
|
rawAttributes.push({
|
|
55372
55611
|
name: attrName,
|
|
55373
55612
|
value: decodeEntities(rawValue.replace(/[\t\n\r]/g, " "), line)
|
|
@@ -55384,19 +55623,30 @@ function parseXml(source, limits) {
|
|
|
55384
55623
|
);
|
|
55385
55624
|
if (stack.length + 1 > limits.maxDepth)
|
|
55386
55625
|
throw new SvgError("svg_complexity_limit", `Elements nest deeper than ${limits.maxDepth}`);
|
|
55387
|
-
const parentScope = stack.length ? stack[stack.length - 1].scope :
|
|
55388
|
-
|
|
55626
|
+
const parentScope = stack.length ? stack[stack.length - 1].scope : null;
|
|
55627
|
+
let own = null;
|
|
55389
55628
|
for (const a of rawAttributes) {
|
|
55390
|
-
|
|
55391
|
-
|
|
55629
|
+
const prefix = a.name === "xmlns" ? "" : a.name.startsWith("xmlns:") ? a.name.slice(6) : null;
|
|
55630
|
+
if (prefix === null) continue;
|
|
55631
|
+
if (++namespaceDeclarations > maxNamespaceDeclarations)
|
|
55632
|
+
throw new SvgError(
|
|
55633
|
+
"svg_complexity_limit",
|
|
55634
|
+
`The document declares more than ${maxNamespaceDeclarations} namespaces`
|
|
55635
|
+
);
|
|
55636
|
+
(own ??= /* @__PURE__ */ new Map()).set(prefix, a.value);
|
|
55392
55637
|
}
|
|
55638
|
+
const scope2 = own ? { own, parent: parentScope } : parentScope ?? { own: null, parent: null };
|
|
55393
55639
|
const resolve9 = (qualified, isAttribute) => {
|
|
55394
55640
|
const colon = qualified.indexOf(":");
|
|
55395
|
-
if (colon < 0)
|
|
55641
|
+
if (colon < 0)
|
|
55642
|
+
return {
|
|
55643
|
+
local: qualified,
|
|
55644
|
+
ns: isAttribute ? null : lookupNamespace(scope2, "") ?? null
|
|
55645
|
+
};
|
|
55396
55646
|
const prefix = qualified.slice(0, colon);
|
|
55397
55647
|
const local = qualified.slice(colon + 1);
|
|
55398
55648
|
if (prefix === "xml") return { local, ns: XML_NS };
|
|
55399
|
-
const ns = scope2
|
|
55649
|
+
const ns = lookupNamespace(scope2, prefix);
|
|
55400
55650
|
if (ns === void 0) fail(`Undeclared namespace prefix "${prefix.slice(0, 40)}"`);
|
|
55401
55651
|
return { local, ns };
|
|
55402
55652
|
};
|
|
@@ -55424,6 +55674,8 @@ var SVG_ANALYZER_VERSION = "apple-notes-mcp/svg-analyzer@1";
|
|
|
55424
55674
|
var SVG_LIMITS = {
|
|
55425
55675
|
maxSourceBytes: 1048576,
|
|
55426
55676
|
maxSourceElements: 16384,
|
|
55677
|
+
maxAttributesPerElement: 1024,
|
|
55678
|
+
maxNamespaceDeclarations: 1024,
|
|
55427
55679
|
maxExpandedElements: 16384,
|
|
55428
55680
|
maxReferenceExpansions: 8192,
|
|
55429
55681
|
maxDepth: 64,
|
|
@@ -55595,7 +55847,7 @@ function attr(el, local) {
|
|
|
55595
55847
|
return el.attributes.find((a) => a.local === local && a.ns === null)?.value;
|
|
55596
55848
|
}
|
|
55597
55849
|
function href(el) {
|
|
55598
|
-
return el.attributes.find((a) => a.local === "href" &&
|
|
55850
|
+
return el.attributes.find((a) => a.local === "href" && a.ns === null)?.value ?? el.attributes.find((a) => a.local === "href" && a.ns === XLINK_NS)?.value;
|
|
55599
55851
|
}
|
|
55600
55852
|
function parseStyleAttribute(value) {
|
|
55601
55853
|
const out = [];
|
|
@@ -55611,7 +55863,24 @@ function parseStyleAttribute(value) {
|
|
|
55611
55863
|
function hasText(el) {
|
|
55612
55864
|
return el.text.trim().length > 0 || el.children.some(hasText);
|
|
55613
55865
|
}
|
|
55614
|
-
var ACTIVE_SCHEME =
|
|
55866
|
+
var ACTIVE_SCHEME = /^(?:javascript|vbscript|data)\s*:/i;
|
|
55867
|
+
function urlAsParsed(value) {
|
|
55868
|
+
return value.replace(/[\t\n\r]/g, "").replace(/^[\x00-\x20]+|[\x00-\x20]+$/g, "");
|
|
55869
|
+
}
|
|
55870
|
+
function unescapeCss(value) {
|
|
55871
|
+
if (!value.includes("\\")) return value;
|
|
55872
|
+
return value.replace(
|
|
55873
|
+
/\\(?:([0-9A-Fa-f]{1,6})(?:\r\n|[ \t\n\r\f])?|(\r\n|[\n\r\f])|([\s\S]))/g,
|
|
55874
|
+
(_m, hex3, newline, other) => {
|
|
55875
|
+
if (hex3 !== void 0) {
|
|
55876
|
+
const code = Number.parseInt(hex3, 16);
|
|
55877
|
+
return code === 0 || code > 1114111 || code >= 55296 && code <= 57343 ? "\uFFFD" : String.fromCodePoint(code);
|
|
55878
|
+
}
|
|
55879
|
+
if (newline !== void 0) return "";
|
|
55880
|
+
return other;
|
|
55881
|
+
}
|
|
55882
|
+
);
|
|
55883
|
+
}
|
|
55615
55884
|
function assertInert(el, location) {
|
|
55616
55885
|
if (UNSAFE_ELEMENTS.has(el.local))
|
|
55617
55886
|
throw new SvgError(
|
|
@@ -55628,7 +55897,7 @@ function assertInert(el, location) {
|
|
|
55628
55897
|
);
|
|
55629
55898
|
const value = a.value;
|
|
55630
55899
|
if (a.local === "href" && (a.ns === null || a.ns === XLINK_NS)) {
|
|
55631
|
-
const target = value
|
|
55900
|
+
const target = urlAsParsed(value);
|
|
55632
55901
|
if (el.local === "a") {
|
|
55633
55902
|
if (ACTIVE_SCHEME.test(target))
|
|
55634
55903
|
throw new SvgError("svg_unsafe", "Link uses an active URL scheme", location);
|
|
@@ -55641,10 +55910,11 @@ function assertInert(el, location) {
|
|
|
55641
55910
|
);
|
|
55642
55911
|
}
|
|
55643
55912
|
}
|
|
55644
|
-
|
|
55645
|
-
|
|
55913
|
+
const css = unescapeCss(value);
|
|
55914
|
+
for (const m of css.matchAll(/url\(\s*['"]?([^'")]*)/gi))
|
|
55915
|
+
if (!urlAsParsed(m[1]).startsWith("#"))
|
|
55646
55916
|
throw new SvgError("svg_unsafe", "url() references must be local (#id)", location);
|
|
55647
|
-
if (a.local === "style" && /@import|expression\s*\(|javascript\s*:/i.test(
|
|
55917
|
+
if (a.local === "style" && /@import|expression\s*\(|javascript\s*:/i.test(urlAsParsed(css)))
|
|
55648
55918
|
throw new SvgError("svg_unsafe", "Active content in a style attribute", location);
|
|
55649
55919
|
}
|
|
55650
55920
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -55726,7 +55996,8 @@ var Analyzer = class {
|
|
|
55726
55996
|
if (styleAttr) declarations.push(...parseStyleAttribute(styleAttr));
|
|
55727
55997
|
const diag = Math.hypot(ctx.viewport[0], ctx.viewport[1]) / Math.SQRT2;
|
|
55728
55998
|
for (const [name, value] of declarations) {
|
|
55729
|
-
|
|
55999
|
+
const keyword = value.toLowerCase();
|
|
56000
|
+
if (keyword === "inherit") continue;
|
|
55730
56001
|
const invalid2 = () => this.issue(
|
|
55731
56002
|
"invalid_value",
|
|
55732
56003
|
null,
|
|
@@ -55765,23 +56036,23 @@ var Analyzer = class {
|
|
|
55765
56036
|
break;
|
|
55766
56037
|
}
|
|
55767
56038
|
case "fill-rule":
|
|
55768
|
-
if (
|
|
56039
|
+
if (keyword === "evenodd" || keyword === "nonzero") style.evenOdd = keyword === "evenodd";
|
|
55769
56040
|
else invalid2();
|
|
55770
56041
|
break;
|
|
55771
56042
|
case "visibility":
|
|
55772
|
-
style.visible =
|
|
56043
|
+
style.visible = keyword === "visible";
|
|
55773
56044
|
break;
|
|
55774
56045
|
case "display":
|
|
55775
|
-
|
|
56046
|
+
display = keyword !== "none";
|
|
55776
56047
|
break;
|
|
55777
56048
|
case "stroke-linecap":
|
|
55778
|
-
style.linecap =
|
|
56049
|
+
style.linecap = keyword;
|
|
55779
56050
|
break;
|
|
55780
56051
|
case "stroke-linejoin":
|
|
55781
|
-
style.linejoin =
|
|
56052
|
+
style.linejoin = keyword;
|
|
55782
56053
|
break;
|
|
55783
56054
|
case "stroke-dasharray": {
|
|
55784
|
-
if (
|
|
56055
|
+
if (keyword === "none") {
|
|
55785
56056
|
style.dasharray = null;
|
|
55786
56057
|
break;
|
|
55787
56058
|
}
|
|
@@ -55802,7 +56073,7 @@ var Analyzer = class {
|
|
|
55802
56073
|
case "overflow":
|
|
55803
56074
|
break;
|
|
55804
56075
|
case "mix-blend-mode":
|
|
55805
|
-
if (
|
|
56076
|
+
if (keyword !== "normal")
|
|
55806
56077
|
this.issue(
|
|
55807
56078
|
"blend_mode_ignored",
|
|
55808
56079
|
"paint-approximation",
|
|
@@ -55811,7 +56082,7 @@ var Analyzer = class {
|
|
|
55811
56082
|
);
|
|
55812
56083
|
break;
|
|
55813
56084
|
case "paint-order":
|
|
55814
|
-
if (
|
|
56085
|
+
if (keyword !== "normal" && !/^fill(\s+stroke)?(\s+markers)?$/.test(keyword))
|
|
55815
56086
|
this.issue(
|
|
55816
56087
|
"paint_order_ignored",
|
|
55817
56088
|
"paint-approximation",
|
|
@@ -55821,7 +56092,7 @@ var Analyzer = class {
|
|
|
55821
56092
|
break;
|
|
55822
56093
|
default:
|
|
55823
56094
|
if (DROPPED_MODIFIERS.has(name)) {
|
|
55824
|
-
if (
|
|
56095
|
+
if (keyword !== "none")
|
|
55825
56096
|
this.issue(
|
|
55826
56097
|
"modifier_dropped",
|
|
55827
56098
|
"drop-content",
|
|
@@ -55843,7 +56114,7 @@ var Analyzer = class {
|
|
|
55843
56114
|
propertyValue(el, name) {
|
|
55844
56115
|
const styleAttr = attr(el, "style");
|
|
55845
56116
|
const fromStyle = styleAttr ? parseStyleAttribute(styleAttr).filter(([n]) => n === name).pop() : void 0;
|
|
55846
|
-
return fromStyle ? fromStyle[1] : attr(el, name);
|
|
56117
|
+
return (fromStyle ? fromStyle[1] : attr(el, name))?.trim().toLowerCase();
|
|
55847
56118
|
}
|
|
55848
56119
|
run() {
|
|
55849
56120
|
const root = this.root;
|
|
@@ -56243,9 +56514,7 @@ var Analyzer = class {
|
|
|
56243
56514
|
if (!geometry || !ctx.style.visible) return;
|
|
56244
56515
|
const { style } = ctx;
|
|
56245
56516
|
const polylines = geometry.subpaths.filter((s) => s.segments.length > 0).map((s) => {
|
|
56246
|
-
|
|
56247
|
-
this.charge("geometryWork", pts.length);
|
|
56248
|
-
if (pts.some(
|
|
56517
|
+
if (subpathControlPoints(s, ctx.matrix).some(
|
|
56249
56518
|
(p) => !(Math.abs(p[0]) <= SVG_LIMITS.maxCoordinate && Math.abs(p[1]) <= SVG_LIMITS.maxCoordinate)
|
|
56250
56519
|
))
|
|
56251
56520
|
throw new SvgError(
|
|
@@ -56253,7 +56522,7 @@ var Analyzer = class {
|
|
|
56253
56522
|
"Geometry is not finite or exceeds the coordinate limit",
|
|
56254
56523
|
ctx.location
|
|
56255
56524
|
);
|
|
56256
|
-
return
|
|
56525
|
+
return flattenSubpath(s, ctx.matrix, TOLERANCE, () => this.charge("geometryWork", 1));
|
|
56257
56526
|
});
|
|
56258
56527
|
if (!polylines.length) return;
|
|
56259
56528
|
const fill = this.paintColor(style.fill, style);
|
|
@@ -56409,7 +56678,9 @@ function analyzeSvgBuffer(source) {
|
|
|
56409
56678
|
if (text2.charCodeAt(0) === 65279) text2 = text2.slice(1);
|
|
56410
56679
|
const root = parseXml(text2, {
|
|
56411
56680
|
maxElements: SVG_LIMITS.maxSourceElements,
|
|
56412
|
-
maxDepth: SVG_LIMITS.maxDepth
|
|
56681
|
+
maxDepth: SVG_LIMITS.maxDepth,
|
|
56682
|
+
maxAttributes: SVG_LIMITS.maxAttributesPerElement,
|
|
56683
|
+
maxNamespaceDeclarations: SVG_LIMITS.maxNamespaceDeclarations
|
|
56413
56684
|
});
|
|
56414
56685
|
const analyzer = new Analyzer(root);
|
|
56415
56686
|
const viewport = analyzer.run();
|
|
@@ -56651,20 +56922,26 @@ function appendChecklistItems(args, deps, readRich) {
|
|
|
56651
56922
|
const landed = [];
|
|
56652
56923
|
let contentHash = args.expectedContentHash;
|
|
56653
56924
|
for (const [index, text2] of args.items.entries()) {
|
|
56654
|
-
let
|
|
56925
|
+
let attempted = false;
|
|
56926
|
+
const tracked = {
|
|
56927
|
+
...deps,
|
|
56928
|
+
run: (input) => {
|
|
56929
|
+
attempted = true;
|
|
56930
|
+
deps.run(input);
|
|
56931
|
+
}
|
|
56932
|
+
};
|
|
56655
56933
|
try {
|
|
56656
56934
|
const result = mutateBackground(
|
|
56657
56935
|
{ ...args, expectedContentHash: contentHash },
|
|
56658
56936
|
"create-checklist-item",
|
|
56659
56937
|
{ text: text2 },
|
|
56660
56938
|
(before, after) => {
|
|
56661
|
-
wrote = true;
|
|
56662
56939
|
assertPreserved(before, after, { append: true });
|
|
56663
56940
|
const added = after.checklist.slice(before.checklist.length);
|
|
56664
56941
|
if (added.length !== 1 || added[0].text !== text2 || added[0].done)
|
|
56665
56942
|
throw new Error("Native checklist item not verified");
|
|
56666
56943
|
},
|
|
56667
|
-
|
|
56944
|
+
tracked
|
|
56668
56945
|
);
|
|
56669
56946
|
const items = byPosition(readRich(args.id).checklistItems);
|
|
56670
56947
|
const fresh = items.filter(
|
|
@@ -56680,20 +56957,33 @@ function appendChecklistItems(args, deps, readRich) {
|
|
|
56680
56957
|
landed.push({ index, id: fresh[0].id, text: text2 });
|
|
56681
56958
|
contentHash = result.contentHash;
|
|
56682
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
|
+
};
|
|
56683
56972
|
return {
|
|
56684
56973
|
ok: false,
|
|
56974
|
+
...envelope,
|
|
56685
56975
|
id: args.id,
|
|
56686
56976
|
landed,
|
|
56687
56977
|
stoppedAt: {
|
|
56688
56978
|
index,
|
|
56689
56979
|
text: text2,
|
|
56690
|
-
|
|
56691
|
-
|
|
56692
|
-
outcome: wrote && !(error2 instanceof CodedError && error2.envelope.committed === false) ? "uncertain" : "not-written",
|
|
56693
|
-
error: error2 instanceof Error ? error2.message : String(error2)
|
|
56980
|
+
outcome: uncertain2 ? "uncertain" : "not-written",
|
|
56981
|
+
error: message
|
|
56694
56982
|
},
|
|
56695
56983
|
notAttempted: args.items.slice(index + 1),
|
|
56696
|
-
|
|
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 },
|
|
56697
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.`
|
|
56698
56988
|
};
|
|
56699
56989
|
}
|
|
@@ -56703,15 +56993,24 @@ function appendChecklistItems(args, deps, readRich) {
|
|
|
56703
56993
|
const orderVerified = tail.length === landed.length && tail.every((item, i) => item.id === landed[i].id && item.text === landed[i].text);
|
|
56704
56994
|
return {
|
|
56705
56995
|
ok: orderVerified,
|
|
56996
|
+
...orderVerified ? {} : { code: "verification_failed", committed: true },
|
|
56706
56997
|
id: args.id,
|
|
56707
56998
|
contentHash,
|
|
56708
56999
|
items: landed,
|
|
57000
|
+
landed,
|
|
56709
57001
|
orderVerified,
|
|
56710
57002
|
...orderVerified ? {} : {
|
|
56711
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."
|
|
56712
57004
|
}
|
|
56713
57005
|
};
|
|
56714
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
|
+
}
|
|
56715
57014
|
function registerNativeOperations(server2, manager) {
|
|
56716
57015
|
function tool(name, description, input, handler, readOnly = false) {
|
|
56717
57016
|
server2.registerTool(
|
|
@@ -56724,11 +57023,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
56724
57023
|
},
|
|
56725
57024
|
(async (args) => {
|
|
56726
57025
|
try {
|
|
56727
|
-
|
|
56728
|
-
return {
|
|
56729
|
-
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
56730
|
-
structuredContent: result
|
|
56731
|
-
};
|
|
57026
|
+
return nativeToolResult(handler(args));
|
|
56732
57027
|
} catch (error2) {
|
|
56733
57028
|
return errorResult(error2 instanceof Error ? error2.message : "Operation failed", error2);
|
|
56734
57029
|
}
|
|
@@ -57576,7 +57871,7 @@ function parseNoteObjectId(noteId3) {
|
|
|
57576
57871
|
);
|
|
57577
57872
|
return { store: match[1], pk: Number(match[2]) };
|
|
57578
57873
|
}
|
|
57579
|
-
function queryNoteScoped(sql, pk, dbPath2 =
|
|
57874
|
+
function queryNoteScoped(sql, pk, dbPath2 = NOTES_DB_PATH8) {
|
|
57580
57875
|
return runReadOnlySql(dbPath2, sql, { pk: { int: pk } }).split("\n");
|
|
57581
57876
|
}
|
|
57582
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));`;
|
|
@@ -57605,7 +57900,7 @@ function drawingRowsSql(columns, dataColumn) {
|
|
|
57605
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;`
|
|
57606
57901
|
].join("\n");
|
|
57607
57902
|
}
|
|
57608
|
-
function readDrawingRows2(noteId3, dbPath2 =
|
|
57903
|
+
function readDrawingRows2(noteId3, dbPath2 = NOTES_DB_PATH8) {
|
|
57609
57904
|
const { store, pk } = parseNoteObjectId(noteId3);
|
|
57610
57905
|
const columns = readColumns(dbPath2);
|
|
57611
57906
|
const dataColumn = columns.has("ZMERGEABLEDATA1") ? "ZMERGEABLEDATA1" : columns.has("ZMERGEABLEDATA") ? "ZMERGEABLEDATA" : null;
|
|
@@ -57840,7 +58135,7 @@ function isFile2(path10) {
|
|
|
57840
58135
|
var positive = (value) => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
|
|
57841
58136
|
function readAudioAssets(noteId3, options = {}) {
|
|
57842
58137
|
const { store, pk } = parseNoteId2(noteId3);
|
|
57843
|
-
const dbPath2 = options.dbPath ??
|
|
58138
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH8;
|
|
57844
58139
|
const columns = readColumns(dbPath2);
|
|
57845
58140
|
const missing = ["ZMEDIA", "ZPARENTATTACHMENT", "ZFILENAME"].filter((c) => !columns.has(c));
|
|
57846
58141
|
if (missing.length)
|
|
@@ -58219,6 +58514,10 @@ function inspectInstallation(deps = defaultDeps2()) {
|
|
|
58219
58514
|
);
|
|
58220
58515
|
return { ...base, ready: true, reason: null, detail: null };
|
|
58221
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
|
+
}
|
|
58222
58521
|
var PrivateHelperError = class extends Error {
|
|
58223
58522
|
constructor(code, message, details = {}) {
|
|
58224
58523
|
super(message);
|
|
@@ -58301,7 +58600,7 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
|
|
|
58301
58600
|
throw new PrivateHelperError(install.reason || "helper_not_installed", install.detail || "");
|
|
58302
58601
|
binaryPath = install.binaryPath;
|
|
58303
58602
|
}
|
|
58304
|
-
const timeout =
|
|
58603
|
+
const timeout = helperTimeoutMs(deps.env);
|
|
58305
58604
|
const result = deps.spawn(binaryPath, [], {
|
|
58306
58605
|
input: JSON.stringify({ protocol: PRIVATE_HELPER_PROTOCOL, action, ...fields }),
|
|
58307
58606
|
encoding: "utf8",
|
|
@@ -58311,13 +58610,23 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
|
|
|
58311
58610
|
env: deps.env
|
|
58312
58611
|
});
|
|
58313
58612
|
const errno = result.error?.code;
|
|
58314
|
-
if (errno === "ETIMEDOUT"
|
|
58613
|
+
if (errno === "ETIMEDOUT")
|
|
58315
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
|
+
);
|
|
58316
58620
|
if (result.error)
|
|
58317
58621
|
throw new PrivateHelperError(
|
|
58318
58622
|
"helper_unreachable",
|
|
58319
58623
|
`Could not run the helper: ${result.error.message}`
|
|
58320
58624
|
);
|
|
58625
|
+
if (result.signal)
|
|
58626
|
+
throw new PrivateHelperError(
|
|
58627
|
+
"helper_crashed",
|
|
58628
|
+
`The helper was terminated by ${result.signal} before it answered.`
|
|
58629
|
+
);
|
|
58321
58630
|
const stdout = String(result.stdout ?? "").trim();
|
|
58322
58631
|
let parsed;
|
|
58323
58632
|
try {
|
|
@@ -58641,7 +58950,8 @@ function envelopeCode(helperCode, message) {
|
|
|
58641
58950
|
case "not_found":
|
|
58642
58951
|
return "not_found";
|
|
58643
58952
|
case "timeout":
|
|
58644
|
-
|
|
58953
|
+
case "helper_crashed":
|
|
58954
|
+
return "operation_failed";
|
|
58645
58955
|
case "invalid_request":
|
|
58646
58956
|
case "invalid_json":
|
|
58647
58957
|
case "input_too_large":
|
|
@@ -58751,6 +59061,7 @@ var server = new McpServer({
|
|
|
58751
59061
|
version: version2,
|
|
58752
59062
|
description: "MCP server for managing Apple Notes - create, search, update, and organize notes"
|
|
58753
59063
|
});
|
|
59064
|
+
installSdkErrorCodes(server);
|
|
58754
59065
|
var notesManager = new AppleNotesManager();
|
|
58755
59066
|
registerDirectOperations(server, notesManager);
|
|
58756
59067
|
registerFolderDelete(server, notesManager);
|
|
@@ -58767,6 +59078,9 @@ function successResponse(message, structured) {
|
|
|
58767
59078
|
function errorResponse(message, cause) {
|
|
58768
59079
|
return errorResult(message, cause);
|
|
58769
59080
|
}
|
|
59081
|
+
function notFoundResponse(message) {
|
|
59082
|
+
return errorResult(message, new CodedError(message, { code: "not_found" }));
|
|
59083
|
+
}
|
|
58770
59084
|
function withErrorHandling(handler, errorPrefix) {
|
|
58771
59085
|
return async (params) => {
|
|
58772
59086
|
try {
|
|
@@ -58894,6 +59208,9 @@ function bodyReadFailureMessage(id2, title, error2) {
|
|
|
58894
59208
|
return describeBodyReadFailure(title, error2, attachments);
|
|
58895
59209
|
}
|
|
58896
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
|
+
}
|
|
58897
59214
|
function containerUnknownMessage(title) {
|
|
58898
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.`;
|
|
58899
59216
|
}
|
|
@@ -58964,12 +59281,32 @@ var folderNameSchema = {
|
|
|
58964
59281
|
};
|
|
58965
59282
|
function registerTool(name, config2, cb) {
|
|
58966
59283
|
const { outputSchema, ...rest } = config2;
|
|
59284
|
+
const callback = NOTES_WRITE_TOOLS.has(name) ? (async (...args) => writeToolResult(
|
|
59285
|
+
await cb(...args)
|
|
59286
|
+
)) : cb;
|
|
58967
59287
|
return server.registerTool(
|
|
58968
59288
|
name,
|
|
58969
59289
|
outputSchema ? { ...rest, outputSchema: external_exports.object(outputSchema).passthrough() } : rest,
|
|
58970
|
-
|
|
59290
|
+
callback
|
|
58971
59291
|
);
|
|
58972
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
|
+
}
|
|
58973
59310
|
registerTool(
|
|
58974
59311
|
"create-note",
|
|
58975
59312
|
{
|
|
@@ -58980,7 +59317,7 @@ registerTool(
|
|
|
58980
59317
|
'Note body; required unless contentPath is given (pass exactly one). In plaintext and HTML, AppleScript cannot create true Apple Notes checklists \u2014 `<input type="checkbox">`, checklist CSS classes, and markdown `- [ ]` lines do not render as checkable items; create a plain `<ul>` or `- ` list and convert it in Notes.app with \u21E7\u2318L. With format "markdown" (markdownRoute "shortcut"), `- [ ]`/`- [x]` lines, `>` block quotes, ``` fenced code, `---` dividers and `inline code` (which Notes renders as a highlight, not monospace) become native styles (see create-note-markdown-blocks in get-capabilities).'
|
|
58981
59318
|
),
|
|
58982
59319
|
contentPath: external_exports.string().min(1).max(MAX.SAVE_PATH).optional().describe(
|
|
58983
|
-
`Absolute path of a local UTF-8 file to use as the body instead of content (pass exactly one). The same locations save-attachment may write to are allowed (home, temp, /Volumes); symbolic links and non-regular files are refused. Limit ${MAX_CONTENT_FILE_BYTES} bytes.`
|
|
59320
|
+
`Absolute path of a local UTF-8 file to use as the body instead of content (pass exactly one). The same locations save-attachment may write to are allowed (home, temp, /Volumes), except hidden paths (such as ~/.ssh or ~/.config) and ~/Library (iCloud Drive and ~/Library/CloudStorage are allowed); symbolic links and non-regular files are refused. Limit ${MAX_CONTENT_FILE_BYTES} bytes.`
|
|
58984
59321
|
),
|
|
58985
59322
|
format: external_exports.enum(["plaintext", "html", "markdown"]).optional().default("plaintext").describe(
|
|
58986
59323
|
"Content format: 'plaintext' (default), 'html' for rich formatting, or 'markdown'. Markdown whose first line is exactly `# <title>` (same case and spacing) has that line and one blank line after it removed, since the title is supplied separately."
|
|
@@ -59362,7 +59699,7 @@ registerTool(
|
|
|
59362
59699
|
if (id2) {
|
|
59363
59700
|
const note2 = notesManager.getNoteById(id2);
|
|
59364
59701
|
if (!note2) {
|
|
59365
|
-
return
|
|
59702
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59366
59703
|
}
|
|
59367
59704
|
if (note2.passwordProtected) {
|
|
59368
59705
|
return errorResponse(
|
|
@@ -59404,7 +59741,7 @@ registerTool(
|
|
|
59404
59741
|
}
|
|
59405
59742
|
const note = notesManager.getNoteDetails(title, account);
|
|
59406
59743
|
if (!note) {
|
|
59407
|
-
return
|
|
59744
|
+
return notFoundResponse(`Note "${title}" not found`);
|
|
59408
59745
|
}
|
|
59409
59746
|
if (note.passwordProtected) {
|
|
59410
59747
|
return errorResponse(
|
|
@@ -59462,7 +59799,7 @@ registerTool(
|
|
|
59462
59799
|
if (id2) {
|
|
59463
59800
|
const note2 = notesManager.getNoteById(id2);
|
|
59464
59801
|
if (!note2) {
|
|
59465
|
-
return
|
|
59802
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59466
59803
|
}
|
|
59467
59804
|
if (note2.passwordProtected) {
|
|
59468
59805
|
return errorResponse(
|
|
@@ -59480,7 +59817,7 @@ registerTool(
|
|
|
59480
59817
|
}
|
|
59481
59818
|
const note = notesManager.getNoteDetails(title, account);
|
|
59482
59819
|
if (!note) {
|
|
59483
|
-
return
|
|
59820
|
+
return notFoundResponse(`Note "${title}" not found`);
|
|
59484
59821
|
}
|
|
59485
59822
|
if (note.passwordProtected) {
|
|
59486
59823
|
return errorResponse(
|
|
@@ -59516,7 +59853,7 @@ registerTool(
|
|
|
59516
59853
|
withErrorHandling(({ id: id2 }) => {
|
|
59517
59854
|
const note = notesManager.getNoteById(id2);
|
|
59518
59855
|
if (!note) {
|
|
59519
|
-
return
|
|
59856
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59520
59857
|
}
|
|
59521
59858
|
const metadata = {
|
|
59522
59859
|
id: note.id,
|
|
@@ -59549,7 +59886,7 @@ registerTool(
|
|
|
59549
59886
|
withErrorHandling(({ title, account }) => {
|
|
59550
59887
|
const note = notesManager.getNoteDetails(title, account);
|
|
59551
59888
|
if (!note) {
|
|
59552
|
-
return
|
|
59889
|
+
return notFoundResponse(`Note "${title}" not found`);
|
|
59553
59890
|
}
|
|
59554
59891
|
const metadata = {
|
|
59555
59892
|
id: note.id,
|
|
@@ -59606,7 +59943,7 @@ registerTool(
|
|
|
59606
59943
|
if (id2) {
|
|
59607
59944
|
const note2 = notesManager.getNoteById(id2);
|
|
59608
59945
|
if (!note2) {
|
|
59609
|
-
return
|
|
59946
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59610
59947
|
}
|
|
59611
59948
|
if (note2.passwordProtected) {
|
|
59612
59949
|
return errorResponse(
|
|
@@ -59626,7 +59963,7 @@ registerTool(
|
|
|
59626
59963
|
}
|
|
59627
59964
|
const note = notesManager.getNoteDetails(title, account);
|
|
59628
59965
|
if (!note) {
|
|
59629
|
-
return
|
|
59966
|
+
return notFoundResponse(
|
|
59630
59967
|
`Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
|
|
59631
59968
|
);
|
|
59632
59969
|
}
|
|
@@ -59704,7 +60041,7 @@ registerTool(
|
|
|
59704
60041
|
},
|
|
59705
60042
|
withErrorHandling(({ id: id2 }) => {
|
|
59706
60043
|
const note = notesManager.getNoteById(id2);
|
|
59707
|
-
if (!note) return
|
|
60044
|
+
if (!note) return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
59708
60045
|
const body = notesManager.getNoteContentById(id2);
|
|
59709
60046
|
if (!body) return errorResponse(`Failed to read content of note "${note.title}"`);
|
|
59710
60047
|
const rich = readRichNote(id2);
|
|
@@ -59810,7 +60147,10 @@ registerTool(
|
|
|
59810
60147
|
} catch (error2) {
|
|
59811
60148
|
if (!(error2 instanceof NoteBlocksError)) throw error2;
|
|
59812
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}` : "";
|
|
59813
|
-
return errorResponse(
|
|
60150
|
+
return errorResponse(
|
|
60151
|
+
`Error reading note blocks [${error2.code}]: ${error2.message}${hint}`,
|
|
60152
|
+
error2
|
|
60153
|
+
);
|
|
59814
60154
|
}
|
|
59815
60155
|
const { summary } = page;
|
|
59816
60156
|
const styles = Object.entries(summary.styles).map(([style, count]) => `${style} ${count}`).join(", ");
|
|
@@ -60371,7 +60711,10 @@ registerTool(
|
|
|
60371
60711
|
},
|
|
60372
60712
|
(r) => route = r
|
|
60373
60713
|
);
|
|
60374
|
-
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
|
+
}
|
|
60375
60718
|
return { route, contentHash: String(response.structuredContent?.contentHash ?? "") };
|
|
60376
60719
|
}
|
|
60377
60720
|
}
|
|
@@ -60463,6 +60806,13 @@ registerTool(
|
|
|
60463
60806
|
if (result.status === "not-deleted") {
|
|
60464
60807
|
return errorResponse(NOT_DELETED_MESSAGE);
|
|
60465
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
|
+
}
|
|
60466
60816
|
if (result.status !== "deleted") {
|
|
60467
60817
|
return errorResponse(
|
|
60468
60818
|
`The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
|
|
@@ -60507,7 +60857,7 @@ registerTool(
|
|
|
60507
60857
|
withErrorHandling(({ id: id2, folder, account, ...scopeArgs }) => {
|
|
60508
60858
|
const note = notesManager.getNoteById(id2);
|
|
60509
60859
|
if (!note) {
|
|
60510
|
-
return
|
|
60860
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
60511
60861
|
}
|
|
60512
60862
|
const success = notesManager.moveNoteById(id2, folder, account, scopeFrom(scopeArgs));
|
|
60513
60863
|
if (!success) {
|
|
@@ -61038,7 +61388,7 @@ registerTool(
|
|
|
61038
61388
|
if (id2) {
|
|
61039
61389
|
const note2 = notesManager.getNoteById(id2);
|
|
61040
61390
|
if (!note2) {
|
|
61041
|
-
return
|
|
61391
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
61042
61392
|
}
|
|
61043
61393
|
const attachments2 = notesManager.listAttachmentsById(id2);
|
|
61044
61394
|
if (attachments2.length === 0) {
|
|
@@ -61091,7 +61441,7 @@ ${attachmentList2}`,
|
|
|
61091
61441
|
}
|
|
61092
61442
|
const note = notesManager.getNoteDetails(title, account);
|
|
61093
61443
|
if (!note) {
|
|
61094
|
-
return
|
|
61444
|
+
return notFoundResponse(
|
|
61095
61445
|
`Note "${title}" not found. Use search-notes to find notes, then use the note's ID for reliable operations.`
|
|
61096
61446
|
);
|
|
61097
61447
|
}
|
|
@@ -61149,6 +61499,8 @@ registerTool(
|
|
|
61149
61499
|
}
|
|
61150
61500
|
if (result.status === "not-deleted")
|
|
61151
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) };
|
|
61152
61504
|
return { id: id2, success: false, error: "Delete result uncertain; inspect this exact ID" };
|
|
61153
61505
|
});
|
|
61154
61506
|
const succeeded = results.filter((r) => r.success).length;
|
|
@@ -61376,10 +61728,12 @@ Safety: writes files; exportDir must be absolute and under the home directory, a
|
|
|
61376
61728
|
);
|
|
61377
61729
|
}
|
|
61378
61730
|
}
|
|
61379
|
-
|
|
61380
|
-
|
|
61381
|
-
|
|
61382
|
-
|
|
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);
|
|
61383
61737
|
}, "Error exporting attachments")
|
|
61384
61738
|
);
|
|
61385
61739
|
registerTool(
|
|
@@ -61529,7 +61883,7 @@ registerTool(
|
|
|
61529
61883
|
if (id2) {
|
|
61530
61884
|
const markdown2 = notesManager.getNoteMarkdownById(id2);
|
|
61531
61885
|
if (!markdown2) {
|
|
61532
|
-
return
|
|
61886
|
+
return notFoundResponse(`Note with ID "${id2}" not found or has no content`);
|
|
61533
61887
|
}
|
|
61534
61888
|
return successResponse(markdown2, { markdown: markdown2 });
|
|
61535
61889
|
}
|
|
@@ -61538,7 +61892,7 @@ registerTool(
|
|
|
61538
61892
|
}
|
|
61539
61893
|
const markdown = notesManager.getNoteMarkdown(title, account);
|
|
61540
61894
|
if (!markdown) {
|
|
61541
|
-
return
|
|
61895
|
+
return notFoundResponse(
|
|
61542
61896
|
`Note "${title}" not found or has no content. Use search-notes to find notes, then use the note's ID for reliable operations.`
|
|
61543
61897
|
);
|
|
61544
61898
|
}
|
|
@@ -61613,7 +61967,10 @@ registerTool(
|
|
|
61613
61967
|
`Error exporting Markdown [${error2.code}]: the template is invalid:
|
|
61614
61968
|
` + error2.details.map((detail) => `${detail.path}: ${detail.message}`).join("\n")
|
|
61615
61969
|
);
|
|
61616
|
-
return errorResponse(
|
|
61970
|
+
return errorResponse(
|
|
61971
|
+
`Error exporting Markdown [${error2.code}]: ${error2.message}${hint}`,
|
|
61972
|
+
error2
|
|
61973
|
+
);
|
|
61617
61974
|
}
|
|
61618
61975
|
const warned = receipt.warnings?.length ? `; ${receipt.warnings.length + (receipt.warningsOmitted ?? 0)} warning(s)` : "";
|
|
61619
61976
|
const skipped = (receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "") + warned;
|
|
@@ -61666,7 +62023,7 @@ registerTool(
|
|
|
61666
62023
|
} catch (error2) {
|
|
61667
62024
|
if (!(error2 instanceof NotesExportError || error2 instanceof NoteBlocksError)) throw error2;
|
|
61668
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}` : "";
|
|
61669
|
-
return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}
|
|
62026
|
+
return errorResponse(`Error exporting HTML [${error2.code}]: ${error2.message}${hint}`, error2);
|
|
61670
62027
|
}
|
|
61671
62028
|
const assets = receipt.assets ? `; copied ${receipt.assets.files} asset file(s) to ${receipt.assets.dir}` : `; embedded ${receipt.embedded ?? 0} asset(s)`;
|
|
61672
62029
|
const skipped = receipt.skipped.length ? `; skipped ${receipt.skipped.length}` : "";
|
|
@@ -61694,7 +62051,7 @@ registerTool(
|
|
|
61694
62051
|
withErrorHandling(({ id: id2 }) => {
|
|
61695
62052
|
const note = notesManager.getNoteById(id2);
|
|
61696
62053
|
if (!note) {
|
|
61697
|
-
return
|
|
62054
|
+
return notFoundResponse(`Note with ID "${id2}" not found`);
|
|
61698
62055
|
}
|
|
61699
62056
|
if (note.passwordProtected) {
|
|
61700
62057
|
return errorResponse(
|