apple-notes-mcp 2.8.26 → 2.8.27
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 +41 -0
- package/build/index.js +633 -149
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -930,6 +930,47 @@ Lists all folders in an account with full hierarchical paths.
|
|
|
930
930
|
|
|
931
931
|
---
|
|
932
932
|
|
|
933
|
+
#### `list-smart-folders`
|
|
934
|
+
|
|
935
|
+
Lists every Smart Folder with the rules that define it, read from the NoteStore
|
|
936
|
+
database. Requires Full Disk Access.
|
|
937
|
+
|
|
938
|
+
| Parameter | Type | Required | Description |
|
|
939
|
+
|-----------|------|----------|-------------|
|
|
940
|
+
| `includeMatchingNotes` | boolean | No | Also list the notes each smart folder currently shows (default `false`) |
|
|
941
|
+
| `limit` | number | No | Maximum matching notes per folder, 1–500 (default 50). `matchingNoteCount` is always the total |
|
|
942
|
+
|
|
943
|
+
**Example:**
|
|
944
|
+
```json
|
|
945
|
+
{
|
|
946
|
+
"includeMatchingNotes": true,
|
|
947
|
+
"limit": 20
|
|
948
|
+
}
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
**Returns:** For each smart folder: `name`, `id`, `identifier`, `account`,
|
|
952
|
+
`accountId`, `accountIdentifier`, `parent`, `parentId`, and `parentIdentifier`
|
|
953
|
+
(the parent fields are `null` at the account root). Its rules are decoded as
|
|
954
|
+
`match` (`"all"`, `"any"`, or `"none"`) and `filters`. Each filter has a `type`
|
|
955
|
+
(the stored rule key, such as `folder`, `tag`, `checklist`, or
|
|
956
|
+
`creationDateRelativeRange`), its stored `value`, `excluded` for an Exclude
|
|
957
|
+
rule, and a readable `description`. Folder filters add the folder's `name` and
|
|
958
|
+
`folderId`, and a nested rule group is a filter of type `group` with its own
|
|
959
|
+
`match` and `filters`. Notes stores each query inside an outer
|
|
960
|
+
`{"deleted": false}` wrapper that keeps Recently Deleted out. `query` is the
|
|
961
|
+
stored query with that wrapper removed, `includesRecentlyDeleted` reports the
|
|
962
|
+
wrapper's value, and `rawQuery` is the stored JSON verbatim. A rule this server
|
|
963
|
+
does not recognize is kept as a filter of type `unknown`, and `fullyDecoded` is
|
|
964
|
+
then `false`.
|
|
965
|
+
|
|
966
|
+
With `includeMatchingNotes`, each folder also carries `matchingNoteCount` and
|
|
967
|
+
`matchingNotes` (`title` and `id`). These come from Notes.app itself, which
|
|
968
|
+
evaluates the folder's rules, so they need Automation permission. The server
|
|
969
|
+
does not re-evaluate the rules on its own. A folder whose notes cannot be read
|
|
970
|
+
carries `matchingNotesError` instead.
|
|
971
|
+
|
|
972
|
+
---
|
|
973
|
+
|
|
933
974
|
#### `create-folder`
|
|
934
975
|
|
|
935
976
|
Creates a new folder, including a whole nested hierarchy in one call.
|
package/build/index.js
CHANGED
|
@@ -3268,8 +3268,8 @@ var require_utils = __commonJS({
|
|
|
3268
3268
|
}
|
|
3269
3269
|
return ind;
|
|
3270
3270
|
}
|
|
3271
|
-
function removeDotSegments(
|
|
3272
|
-
let input =
|
|
3271
|
+
function removeDotSegments(path7) {
|
|
3272
|
+
let input = path7;
|
|
3273
3273
|
const output = [];
|
|
3274
3274
|
let nextSlash = -1;
|
|
3275
3275
|
let len = 0;
|
|
@@ -3674,8 +3674,8 @@ var require_schemes = __commonJS({
|
|
|
3674
3674
|
}
|
|
3675
3675
|
if (wsComponent.resourceName) {
|
|
3676
3676
|
const queryIndex = wsComponent.resourceName.indexOf("?");
|
|
3677
|
-
const
|
|
3678
|
-
wsComponent.path =
|
|
3677
|
+
const path7 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
|
|
3678
|
+
wsComponent.path = path7 && path7 !== "/" ? path7 : void 0;
|
|
3679
3679
|
wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
|
|
3680
3680
|
wsComponent.resourceName = void 0;
|
|
3681
3681
|
}
|
|
@@ -7181,12 +7181,12 @@ var require_dist = __commonJS({
|
|
|
7181
7181
|
throw new Error(`Unknown format "${name}"`);
|
|
7182
7182
|
return f;
|
|
7183
7183
|
};
|
|
7184
|
-
function addFormats(ajv, list,
|
|
7184
|
+
function addFormats(ajv, list, fs7, exportName) {
|
|
7185
7185
|
var _a;
|
|
7186
7186
|
var _b;
|
|
7187
7187
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
7188
7188
|
for (const f of list)
|
|
7189
|
-
ajv.addFormat(f,
|
|
7189
|
+
ajv.addFormat(f, fs7[f]);
|
|
7190
7190
|
}
|
|
7191
7191
|
module.exports = exports = formatsPlugin;
|
|
7192
7192
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -12238,32 +12238,32 @@ var require_URL = __commonJS({
|
|
|
12238
12238
|
else
|
|
12239
12239
|
return basepath.substring(0, lastslash + 1) + refpath;
|
|
12240
12240
|
}
|
|
12241
|
-
function remove_dot_segments(
|
|
12242
|
-
if (!
|
|
12241
|
+
function remove_dot_segments(path7) {
|
|
12242
|
+
if (!path7) return path7;
|
|
12243
12243
|
var output = "";
|
|
12244
|
-
while (
|
|
12245
|
-
if (
|
|
12246
|
-
|
|
12244
|
+
while (path7.length > 0) {
|
|
12245
|
+
if (path7 === "." || path7 === "..") {
|
|
12246
|
+
path7 = "";
|
|
12247
12247
|
break;
|
|
12248
12248
|
}
|
|
12249
|
-
var twochars =
|
|
12250
|
-
var threechars =
|
|
12251
|
-
var fourchars =
|
|
12249
|
+
var twochars = path7.substring(0, 2);
|
|
12250
|
+
var threechars = path7.substring(0, 3);
|
|
12251
|
+
var fourchars = path7.substring(0, 4);
|
|
12252
12252
|
if (threechars === "../") {
|
|
12253
|
-
|
|
12253
|
+
path7 = path7.substring(3);
|
|
12254
12254
|
} else if (twochars === "./") {
|
|
12255
|
-
|
|
12255
|
+
path7 = path7.substring(2);
|
|
12256
12256
|
} else if (threechars === "/./") {
|
|
12257
|
-
|
|
12258
|
-
} else if (twochars === "/." &&
|
|
12259
|
-
|
|
12260
|
-
} else if (fourchars === "/../" || threechars === "/.." &&
|
|
12261
|
-
|
|
12257
|
+
path7 = "/" + path7.substring(3);
|
|
12258
|
+
} else if (twochars === "/." && path7.length === 2) {
|
|
12259
|
+
path7 = "/";
|
|
12260
|
+
} else if (fourchars === "/../" || threechars === "/.." && path7.length === 3) {
|
|
12261
|
+
path7 = "/" + path7.substring(4);
|
|
12262
12262
|
output = output.replace(/\/?[^\/]*$/, "");
|
|
12263
12263
|
} else {
|
|
12264
|
-
var segment =
|
|
12264
|
+
var segment = path7.match(/(\/?([^\/]*))/)[0];
|
|
12265
12265
|
output += segment;
|
|
12266
|
-
|
|
12266
|
+
path7 = path7.substring(segment.length);
|
|
12267
12267
|
}
|
|
12268
12268
|
}
|
|
12269
12269
|
return output;
|
|
@@ -24465,14 +24465,14 @@ var require_turndown_cjs = __commonJS({
|
|
|
24465
24465
|
} else if (node.nodeType === 1) {
|
|
24466
24466
|
replacement = replacementForNode.call(self, node);
|
|
24467
24467
|
}
|
|
24468
|
-
return
|
|
24468
|
+
return join14(output, replacement);
|
|
24469
24469
|
}, "");
|
|
24470
24470
|
}
|
|
24471
24471
|
function postProcess(output) {
|
|
24472
24472
|
var self = this;
|
|
24473
24473
|
this.rules.forEach(function(rule) {
|
|
24474
24474
|
if (typeof rule.append === "function") {
|
|
24475
|
-
output =
|
|
24475
|
+
output = join14(output, rule.append(self.options));
|
|
24476
24476
|
}
|
|
24477
24477
|
});
|
|
24478
24478
|
return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
|
|
@@ -24484,7 +24484,7 @@ var require_turndown_cjs = __commonJS({
|
|
|
24484
24484
|
if (whitespace.leading || whitespace.trailing) content = content.trim();
|
|
24485
24485
|
return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
|
|
24486
24486
|
}
|
|
24487
|
-
function
|
|
24487
|
+
function join14(output, replacement) {
|
|
24488
24488
|
var s1 = trimTrailingNewlines(output);
|
|
24489
24489
|
var s2 = trimLeadingNewlines(replacement);
|
|
24490
24490
|
var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
|
|
@@ -24979,8 +24979,8 @@ function getErrorMap() {
|
|
|
24979
24979
|
|
|
24980
24980
|
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
24981
24981
|
var makeIssue = (params) => {
|
|
24982
|
-
const { data, path:
|
|
24983
|
-
const fullPath = [...
|
|
24982
|
+
const { data, path: path7, errorMaps, issueData } = params;
|
|
24983
|
+
const fullPath = [...path7, ...issueData.path || []];
|
|
24984
24984
|
const fullIssue = {
|
|
24985
24985
|
...issueData,
|
|
24986
24986
|
path: fullPath
|
|
@@ -25096,11 +25096,11 @@ var errorUtil;
|
|
|
25096
25096
|
|
|
25097
25097
|
// node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
25098
25098
|
var ParseInputLazyPath = class {
|
|
25099
|
-
constructor(parent, value,
|
|
25099
|
+
constructor(parent, value, path7, key) {
|
|
25100
25100
|
this._cachedPath = [];
|
|
25101
25101
|
this.parent = parent;
|
|
25102
25102
|
this.data = value;
|
|
25103
|
-
this._path =
|
|
25103
|
+
this._path = path7;
|
|
25104
25104
|
this._key = key;
|
|
25105
25105
|
}
|
|
25106
25106
|
get path() {
|
|
@@ -28737,10 +28737,10 @@ function assignProp(target, prop, value) {
|
|
|
28737
28737
|
configurable: true
|
|
28738
28738
|
});
|
|
28739
28739
|
}
|
|
28740
|
-
function getElementAtPath(obj,
|
|
28741
|
-
if (!
|
|
28740
|
+
function getElementAtPath(obj, path7) {
|
|
28741
|
+
if (!path7)
|
|
28742
28742
|
return obj;
|
|
28743
|
-
return
|
|
28743
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
28744
28744
|
}
|
|
28745
28745
|
function promiseAllObject(promisesObj) {
|
|
28746
28746
|
const keys = Object.keys(promisesObj);
|
|
@@ -29060,11 +29060,11 @@ function aborted(x, startIndex = 0) {
|
|
|
29060
29060
|
}
|
|
29061
29061
|
return false;
|
|
29062
29062
|
}
|
|
29063
|
-
function prefixIssues(
|
|
29063
|
+
function prefixIssues(path7, issues) {
|
|
29064
29064
|
return issues.map((iss) => {
|
|
29065
29065
|
var _a;
|
|
29066
29066
|
(_a = iss).path ?? (_a.path = []);
|
|
29067
|
-
iss.path.unshift(
|
|
29067
|
+
iss.path.unshift(path7);
|
|
29068
29068
|
return iss;
|
|
29069
29069
|
});
|
|
29070
29070
|
}
|
|
@@ -32475,11 +32475,11 @@ function normalizeObjectSchema(schema) {
|
|
|
32475
32475
|
}
|
|
32476
32476
|
return void 0;
|
|
32477
32477
|
}
|
|
32478
|
-
function getDotPath(
|
|
32479
|
-
if (
|
|
32478
|
+
function getDotPath(path7) {
|
|
32479
|
+
if (path7.length === 0) {
|
|
32480
32480
|
return "object root";
|
|
32481
32481
|
}
|
|
32482
|
-
return
|
|
32482
|
+
return path7.reduce((acc, seg, index) => {
|
|
32483
32483
|
if (index === 0) {
|
|
32484
32484
|
return String(seg);
|
|
32485
32485
|
}
|
|
@@ -39839,9 +39839,392 @@ function assertLinkedWrite(rich, content, format, allowLinkChanges = false) {
|
|
|
39839
39839
|
}
|
|
39840
39840
|
}
|
|
39841
39841
|
|
|
39842
|
+
// src/utils/smartFolders.ts
|
|
39843
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
39844
|
+
import * as fs2 from "fs";
|
|
39845
|
+
import * as os2 from "os";
|
|
39846
|
+
import * as path2 from "path";
|
|
39847
|
+
var NOTES_DB_PATH2 = path2.join(
|
|
39848
|
+
os2.homedir(),
|
|
39849
|
+
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
39850
|
+
);
|
|
39851
|
+
var FDA_MESSAGE = `Full Disk Access is required to read smart folders. In System Settings > Privacy & Security > Full Disk Access, grant access to the app that launches this server (Claude Desktop / Terminal / iTerm2), then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
|
|
39852
|
+
var COCOA_EPOCH_OFFSET = 978307200;
|
|
39853
|
+
var MAX_DEPTH = 32;
|
|
39854
|
+
var RELATIVE_RANGES = {
|
|
39855
|
+
0: "today",
|
|
39856
|
+
1: "yesterday",
|
|
39857
|
+
2: "in the last 7 days",
|
|
39858
|
+
3: "in the last 30 days",
|
|
39859
|
+
4: "in the last 3 months",
|
|
39860
|
+
5: "in the last 12 months"
|
|
39861
|
+
};
|
|
39862
|
+
var CUSTOM_UNITS = {
|
|
39863
|
+
0: "hours",
|
|
39864
|
+
1: "days",
|
|
39865
|
+
2: "weeks",
|
|
39866
|
+
3: "months",
|
|
39867
|
+
4: "years"
|
|
39868
|
+
};
|
|
39869
|
+
var ATTACHMENT_SECTIONS = {
|
|
39870
|
+
1: "Photos & Videos",
|
|
39871
|
+
2: "Drawings",
|
|
39872
|
+
3: "Maps",
|
|
39873
|
+
4: "Websites",
|
|
39874
|
+
5: "Audio",
|
|
39875
|
+
6: "Documents",
|
|
39876
|
+
7: "Scans"
|
|
39877
|
+
};
|
|
39878
|
+
var isClause = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39879
|
+
var isDeletedWrapper = (value) => isClause(value) && Object.keys(value).length === 1 && typeof value.deleted === "boolean";
|
|
39880
|
+
function cocoaDate(value) {
|
|
39881
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
39882
|
+
const date3 = new Date((value + COCOA_EPOCH_OFFSET) * 1e3);
|
|
39883
|
+
return Number.isNaN(date3.getTime()) ? null : date3.toISOString();
|
|
39884
|
+
}
|
|
39885
|
+
var Decoder = class {
|
|
39886
|
+
constructor(context) {
|
|
39887
|
+
this.context = context;
|
|
39888
|
+
}
|
|
39889
|
+
context;
|
|
39890
|
+
fullyDecoded = true;
|
|
39891
|
+
unknown(key, value, excluded) {
|
|
39892
|
+
this.fullyDecoded = false;
|
|
39893
|
+
return {
|
|
39894
|
+
type: "unknown",
|
|
39895
|
+
...key === null ? {} : { key },
|
|
39896
|
+
value,
|
|
39897
|
+
...excluded ? { excluded: true } : {},
|
|
39898
|
+
description: "Unrecognized clause (see value)"
|
|
39899
|
+
};
|
|
39900
|
+
}
|
|
39901
|
+
/** Decode a list of sibling clauses, dropping inner `deleted` wrappers. */
|
|
39902
|
+
list(clauses, excluded, depth) {
|
|
39903
|
+
if (!Array.isArray(clauses)) return [this.clause(clauses, excluded, depth)];
|
|
39904
|
+
return clauses.filter((clause) => !isDeletedWrapper(clause)).map((clause) => this.clause(clause, excluded, depth));
|
|
39905
|
+
}
|
|
39906
|
+
group(match, clauses, excluded, depth) {
|
|
39907
|
+
const items = Array.isArray(clauses) ? clauses.filter((clause) => !isDeletedWrapper(clause)) : [clauses];
|
|
39908
|
+
if (items.length === 1) return this.clause(items[0], excluded, depth + 1);
|
|
39909
|
+
const filters = this.list(items, false, depth + 1);
|
|
39910
|
+
const joiner = match === "all" ? "all of" : "any of";
|
|
39911
|
+
return {
|
|
39912
|
+
type: "group",
|
|
39913
|
+
match,
|
|
39914
|
+
...excluded ? { excluded: true } : {},
|
|
39915
|
+
filters,
|
|
39916
|
+
description: `${excluded ? "not " : ""}${joiner}: ${filters.map((f) => f.description).join("; ")}`
|
|
39917
|
+
};
|
|
39918
|
+
}
|
|
39919
|
+
/** Mark a positively described leaf as an Exclude rule. */
|
|
39920
|
+
negate(filter) {
|
|
39921
|
+
const text = filter.description;
|
|
39922
|
+
const negated = text.startsWith("has ") ? text.replace(/^has /, "does not have ") : text.startsWith("is ") ? text.replace(/^is /, "is not ") : text.startsWith("mentions ") ? text.replace(/^mentions /, "does not mention ") : `not ${text}`;
|
|
39923
|
+
return { ...filter, excluded: true, description: negated };
|
|
39924
|
+
}
|
|
39925
|
+
clause(clause, excluded, depth) {
|
|
39926
|
+
if (depth > MAX_DEPTH || !isClause(clause)) return this.unknown(null, clause, excluded);
|
|
39927
|
+
const keys = Object.keys(clause);
|
|
39928
|
+
if (keys.length !== 1) {
|
|
39929
|
+
return this.group(
|
|
39930
|
+
"all",
|
|
39931
|
+
keys.map((key2) => ({ [key2]: clause[key2] })),
|
|
39932
|
+
excluded,
|
|
39933
|
+
depth
|
|
39934
|
+
);
|
|
39935
|
+
}
|
|
39936
|
+
const [key] = keys;
|
|
39937
|
+
const value = clause[key];
|
|
39938
|
+
if (key === "and" || key === "or") {
|
|
39939
|
+
if (!Array.isArray(value)) return this.unknown(key, value, excluded);
|
|
39940
|
+
return this.group(key === "and" ? "all" : "any", value, excluded, depth);
|
|
39941
|
+
}
|
|
39942
|
+
if (key === "not") return this.clause(value, !excluded, depth + 1);
|
|
39943
|
+
const leaf = this.leaf(key, value, excluded);
|
|
39944
|
+
if (!leaf) return this.unknown(key, value, excluded);
|
|
39945
|
+
return excluded && !leaf.excluded ? this.negate(leaf) : leaf;
|
|
39946
|
+
}
|
|
39947
|
+
/**
|
|
39948
|
+
* Decode one leaf clause. Boolean flags describe their own negation (an
|
|
39949
|
+
* excluded `pinned: true` reads "is not pinned"); other leaves are described
|
|
39950
|
+
* positively and negated by the caller.
|
|
39951
|
+
*/
|
|
39952
|
+
leaf(key, value, excluded) {
|
|
39953
|
+
const flag = (description, negative) => typeof value === "boolean" ? {
|
|
39954
|
+
type: key,
|
|
39955
|
+
value,
|
|
39956
|
+
...excluded ? { excluded: true } : {},
|
|
39957
|
+
description: value !== excluded ? description : negative
|
|
39958
|
+
} : null;
|
|
39959
|
+
switch (key) {
|
|
39960
|
+
case "checklist":
|
|
39961
|
+
return flag("has a checklist", "has no checklist");
|
|
39962
|
+
case "checklistInProgress":
|
|
39963
|
+
return flag("has an unfinished checklist", "has no unfinished checklist");
|
|
39964
|
+
case "checklistCompleted":
|
|
39965
|
+
return flag("has a completed checklist", "has no completed checklist");
|
|
39966
|
+
case "attachment":
|
|
39967
|
+
return flag("has attachments", "has no attachments");
|
|
39968
|
+
case "pinned":
|
|
39969
|
+
return flag("is pinned", "is not pinned");
|
|
39970
|
+
case "systemPaper":
|
|
39971
|
+
return flag("is a Quick Note", "is not a Quick Note");
|
|
39972
|
+
case "passwordProtected":
|
|
39973
|
+
return flag("is locked", "is not locked");
|
|
39974
|
+
case "shared":
|
|
39975
|
+
return flag("is shared", "is not shared");
|
|
39976
|
+
case "mention":
|
|
39977
|
+
return flag("mentions anyone", "mentions no one");
|
|
39978
|
+
case "tagged":
|
|
39979
|
+
return flag("has any tag", "has no tags");
|
|
39980
|
+
case "attachmentSection": {
|
|
39981
|
+
if (typeof value !== "number" || !ATTACHMENT_SECTIONS[value]) return null;
|
|
39982
|
+
return {
|
|
39983
|
+
type: key,
|
|
39984
|
+
value,
|
|
39985
|
+
name: ATTACHMENT_SECTIONS[value],
|
|
39986
|
+
description: `has ${ATTACHMENT_SECTIONS[value]} attachments`
|
|
39987
|
+
};
|
|
39988
|
+
}
|
|
39989
|
+
case "tag": {
|
|
39990
|
+
if (typeof value !== "string") return null;
|
|
39991
|
+
const names = this.context.tags?.[value] ?? [];
|
|
39992
|
+
const name = names.length === 1 ? names[0] : void 0;
|
|
39993
|
+
return {
|
|
39994
|
+
type: key,
|
|
39995
|
+
value,
|
|
39996
|
+
...name ? { name } : {},
|
|
39997
|
+
description: `has tag #${name ?? value}`
|
|
39998
|
+
};
|
|
39999
|
+
}
|
|
40000
|
+
case "folder": {
|
|
40001
|
+
if (typeof value !== "string") return null;
|
|
40002
|
+
const folder = this.context.folders?.[value];
|
|
40003
|
+
return {
|
|
40004
|
+
type: key,
|
|
40005
|
+
value,
|
|
40006
|
+
...folder ? { folderId: folder.id } : {},
|
|
40007
|
+
...folder?.title ? { name: folder.title } : {},
|
|
40008
|
+
description: folder?.title ? `is in folder "${folder.title}"` : `is in folder ${value}${folder ? "" : " (not found)"}`
|
|
40009
|
+
};
|
|
40010
|
+
}
|
|
40011
|
+
case "sharedParticipant":
|
|
40012
|
+
case "mentionParticipant":
|
|
40013
|
+
return typeof value === "string" ? {
|
|
40014
|
+
type: key,
|
|
40015
|
+
value,
|
|
40016
|
+
description: key === "sharedParticipant" ? `is shared with participant ${value}` : `mentions participant ${value}`
|
|
40017
|
+
} : null;
|
|
40018
|
+
case "creationDateRelativeRange":
|
|
40019
|
+
case "modificationDateRelativeRange": {
|
|
40020
|
+
if (!isClause(value) || typeof value.type !== "number") return null;
|
|
40021
|
+
const verb = key.startsWith("creation") ? "created" : "edited";
|
|
40022
|
+
if (value.type === 6) {
|
|
40023
|
+
const amount = value.customAmount;
|
|
40024
|
+
const unit = typeof value.customUnit === "number" ? CUSTOM_UNITS[value.customUnit] : null;
|
|
40025
|
+
if (typeof amount !== "number" || !unit) return null;
|
|
40026
|
+
return {
|
|
40027
|
+
type: key,
|
|
40028
|
+
value,
|
|
40029
|
+
description: `${verb} in the last ${amount} ${unit}`
|
|
40030
|
+
};
|
|
40031
|
+
}
|
|
40032
|
+
const range = RELATIVE_RANGES[value.type];
|
|
40033
|
+
return range ? { type: key, value, description: `${verb} ${range}` } : null;
|
|
40034
|
+
}
|
|
40035
|
+
case "creationDateRange":
|
|
40036
|
+
case "modificationDateRange": {
|
|
40037
|
+
if (!isClause(value)) return null;
|
|
40038
|
+
const from = cocoaDate(value.fromDate);
|
|
40039
|
+
const to = cocoaDate(value.toDate);
|
|
40040
|
+
if (!from || !to) return null;
|
|
40041
|
+
const verb = key.startsWith("creation") ? "created" : "edited";
|
|
40042
|
+
return {
|
|
40043
|
+
type: key,
|
|
40044
|
+
value,
|
|
40045
|
+
from,
|
|
40046
|
+
to,
|
|
40047
|
+
description: `${verb} between ${from} and ${to}`
|
|
40048
|
+
};
|
|
40049
|
+
}
|
|
40050
|
+
default:
|
|
40051
|
+
return null;
|
|
40052
|
+
}
|
|
40053
|
+
}
|
|
40054
|
+
};
|
|
40055
|
+
function decodeSmartFolderQuery(raw, context = {}) {
|
|
40056
|
+
if (raw === null || raw.trim() === "") {
|
|
40057
|
+
return { match: null, filters: [], query: null, fullyDecoded: false };
|
|
40058
|
+
}
|
|
40059
|
+
let parsed;
|
|
40060
|
+
try {
|
|
40061
|
+
parsed = JSON.parse(raw);
|
|
40062
|
+
} catch {
|
|
40063
|
+
return { match: null, filters: [], query: raw, fullyDecoded: false };
|
|
40064
|
+
}
|
|
40065
|
+
const decoder = new Decoder(context);
|
|
40066
|
+
const type = isClause(parsed) ? parsed.type : void 0;
|
|
40067
|
+
if (!isClause(type)) {
|
|
40068
|
+
return {
|
|
40069
|
+
match: null,
|
|
40070
|
+
filters: [decoder.unknown(null, parsed, false)],
|
|
40071
|
+
query: parsed,
|
|
40072
|
+
fullyDecoded: false
|
|
40073
|
+
};
|
|
40074
|
+
}
|
|
40075
|
+
let includesRecentlyDeleted;
|
|
40076
|
+
let inner = [type];
|
|
40077
|
+
if (Object.keys(type).length === 1 && Array.isArray(type.and)) {
|
|
40078
|
+
const wrapper = type.and.find(isDeletedWrapper);
|
|
40079
|
+
if (wrapper) {
|
|
40080
|
+
includesRecentlyDeleted = wrapper.deleted;
|
|
40081
|
+
inner = type.and.filter((clause) => !isDeletedWrapper(clause));
|
|
40082
|
+
}
|
|
40083
|
+
}
|
|
40084
|
+
const query = inner.length === 1 ? inner[0] : { and: inner };
|
|
40085
|
+
let match = "all";
|
|
40086
|
+
let filters;
|
|
40087
|
+
const only = inner.length === 1 && isClause(inner[0]) ? inner[0] : void 0;
|
|
40088
|
+
const onlyKey = only && Object.keys(only).length === 1 ? Object.keys(only)[0] : void 0;
|
|
40089
|
+
if (only && onlyKey === "and" && Array.isArray(only.and)) {
|
|
40090
|
+
filters = decoder.list(only.and, false, 1);
|
|
40091
|
+
} else if (only && onlyKey === "or" && Array.isArray(only.or)) {
|
|
40092
|
+
match = "any";
|
|
40093
|
+
filters = decoder.list(only.or, false, 1);
|
|
40094
|
+
} else if (only && onlyKey === "not" && isClause(only.not) && Object.keys(only.not).length === 1 && Array.isArray(only.not.or)) {
|
|
40095
|
+
match = "none";
|
|
40096
|
+
filters = decoder.list(only.not.or, false, 1);
|
|
40097
|
+
} else {
|
|
40098
|
+
filters = decoder.list(inner, false, 1);
|
|
40099
|
+
}
|
|
40100
|
+
return {
|
|
40101
|
+
match,
|
|
40102
|
+
filters,
|
|
40103
|
+
query,
|
|
40104
|
+
...includesRecentlyDeleted === void 0 ? {} : { includesRecentlyDeleted },
|
|
40105
|
+
fullyDecoded: decoder.fullyDecoded
|
|
40106
|
+
};
|
|
40107
|
+
}
|
|
40108
|
+
var SMART_FOLDERS_SQL = `BEGIN;
|
|
40109
|
+
SELECT Z_UUID FROM Z_METADATA;
|
|
40110
|
+
SELECT json_group_array(json_object(
|
|
40111
|
+
'pk', f.Z_PK,
|
|
40112
|
+
'identifier', f.ZIDENTIFIER,
|
|
40113
|
+
'title', f.ZTITLE2,
|
|
40114
|
+
'folderType', f.ZFOLDERTYPE,
|
|
40115
|
+
'query', f.ZSMARTFOLDERQUERYJSON,
|
|
40116
|
+
'parentPk', p.Z_PK,
|
|
40117
|
+
'parentIdentifier', p.ZIDENTIFIER,
|
|
40118
|
+
'parentTitle', p.ZTITLE2,
|
|
40119
|
+
'accountPk', a.Z_PK,
|
|
40120
|
+
'accountIdentifier', a.ZIDENTIFIER,
|
|
40121
|
+
'accountName', a.ZNAME))
|
|
40122
|
+
FROM ZICCLOUDSYNCINGOBJECT f
|
|
40123
|
+
LEFT JOIN ZICCLOUDSYNCINGOBJECT p ON p.Z_PK = f.ZPARENT
|
|
40124
|
+
LEFT JOIN ZICCLOUDSYNCINGOBJECT a ON a.Z_PK = f.ZOWNER
|
|
40125
|
+
WHERE f.Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICFolder')
|
|
40126
|
+
AND (f.ZFOLDERTYPE = 2 OR f.ZSMARTFOLDERQUERYJSON IS NOT NULL)
|
|
40127
|
+
AND COALESCE(f.ZMARKEDFORDELETION, 0) = 0;
|
|
40128
|
+
SELECT json_group_object(ZIDENTIFIER, json_object('pk', Z_PK, 'title', ZTITLE2))
|
|
40129
|
+
FROM ZICCLOUDSYNCINGOBJECT
|
|
40130
|
+
WHERE Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICFolder')
|
|
40131
|
+
AND ZIDENTIFIER IS NOT NULL;
|
|
40132
|
+
SELECT json_group_array(json_object('standardized', ZSTANDARDIZEDCONTENT, 'display', ZDISPLAYTEXT))
|
|
40133
|
+
FROM ZICCLOUDSYNCINGOBJECT
|
|
40134
|
+
WHERE Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICHashtag')
|
|
40135
|
+
AND ZSTANDARDIZEDCONTENT IS NOT NULL
|
|
40136
|
+
AND COALESCE(ZMARKEDFORDELETION, 0) = 0;
|
|
40137
|
+
COMMIT;`;
|
|
40138
|
+
var REQUIRED_COLUMNS = [
|
|
40139
|
+
"ZIDENTIFIER",
|
|
40140
|
+
"ZTITLE2",
|
|
40141
|
+
"ZFOLDERTYPE",
|
|
40142
|
+
"ZSMARTFOLDERQUERYJSON",
|
|
40143
|
+
"ZPARENT",
|
|
40144
|
+
"ZOWNER",
|
|
40145
|
+
"ZNAME",
|
|
40146
|
+
"ZMARKEDFORDELETION",
|
|
40147
|
+
"ZSTANDARDIZEDCONTENT",
|
|
40148
|
+
"ZDISPLAYTEXT"
|
|
40149
|
+
];
|
|
40150
|
+
function buildSmartFolders(output) {
|
|
40151
|
+
const lines = output.trim().split("\n");
|
|
40152
|
+
if (lines.length < 4) throw new Error("Unexpected smart folder query output");
|
|
40153
|
+
const [storeUuid, rowsJson, foldersJson, tagsJson] = lines;
|
|
40154
|
+
if (!/^[0-9A-F-]+$/i.test(storeUuid)) throw new Error("Unexpected Notes store identifier");
|
|
40155
|
+
const coreDataId2 = (entity2, pk) => `x-coredata://${storeUuid}/${entity2}/p${pk}`;
|
|
40156
|
+
const rows = JSON.parse(rowsJson);
|
|
40157
|
+
const folderRows = JSON.parse(foldersJson || "{}");
|
|
40158
|
+
const tagRows = JSON.parse(tagsJson || "[]");
|
|
40159
|
+
const folders = {};
|
|
40160
|
+
for (const [identifier, row] of Object.entries(folderRows)) {
|
|
40161
|
+
folders[identifier] = { id: coreDataId2("ICFolder", row.pk), title: row.title };
|
|
40162
|
+
}
|
|
40163
|
+
const tags = {};
|
|
40164
|
+
for (const tag of tagRows) {
|
|
40165
|
+
if (!tag.display) continue;
|
|
40166
|
+
const names = tags[tag.standardized] ||= [];
|
|
40167
|
+
if (!names.includes(tag.display)) names.push(tag.display);
|
|
40168
|
+
}
|
|
40169
|
+
return rows.map((row) => {
|
|
40170
|
+
const decoded = decodeSmartFolderQuery(row.query, { folders, tags });
|
|
40171
|
+
return {
|
|
40172
|
+
id: coreDataId2("ICFolder", row.pk),
|
|
40173
|
+
identifier: row.identifier,
|
|
40174
|
+
name: row.title,
|
|
40175
|
+
account: row.accountName,
|
|
40176
|
+
accountId: row.accountPk === null ? null : coreDataId2("ICAccount", row.accountPk),
|
|
40177
|
+
accountIdentifier: row.accountIdentifier,
|
|
40178
|
+
parent: row.parentTitle,
|
|
40179
|
+
parentId: row.parentPk === null ? null : coreDataId2("ICFolder", row.parentPk),
|
|
40180
|
+
parentIdentifier: row.parentIdentifier,
|
|
40181
|
+
match: decoded.match,
|
|
40182
|
+
filters: decoded.filters,
|
|
40183
|
+
...decoded.includesRecentlyDeleted === void 0 ? {} : { includesRecentlyDeleted: decoded.includesRecentlyDeleted },
|
|
40184
|
+
fullyDecoded: decoded.fullyDecoded,
|
|
40185
|
+
query: decoded.query,
|
|
40186
|
+
rawQuery: row.query
|
|
40187
|
+
};
|
|
40188
|
+
}).sort(
|
|
40189
|
+
(a, b) => (a.account ?? "").localeCompare(b.account ?? "") || (a.name ?? "").localeCompare(b.name ?? "") || a.id.localeCompare(b.id)
|
|
40190
|
+
);
|
|
40191
|
+
}
|
|
40192
|
+
function runSqlite(dbPath2, sql) {
|
|
40193
|
+
return execFileSync4("sqlite3", ["-readonly", dbPath2, sql], {
|
|
40194
|
+
encoding: "utf8",
|
|
40195
|
+
timeout: 5e3,
|
|
40196
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
40197
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
40198
|
+
});
|
|
40199
|
+
}
|
|
40200
|
+
function readSmartFolders(dbPath2 = NOTES_DB_PATH2) {
|
|
40201
|
+
if (!fs2.existsSync(dbPath2)) return { folders: null, error: "no_fda", message: FDA_MESSAGE };
|
|
40202
|
+
try {
|
|
40203
|
+
const columns = new Set(
|
|
40204
|
+
runSqlite(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").trim().split("\n")
|
|
40205
|
+
);
|
|
40206
|
+
const missing = REQUIRED_COLUMNS.filter((column) => !columns.has(column));
|
|
40207
|
+
if (missing.length) {
|
|
40208
|
+
return {
|
|
40209
|
+
folders: null,
|
|
40210
|
+
error: "unsupported_schema",
|
|
40211
|
+
message: `This Notes database does not have the columns smart folders need (${missing.join(", ")}).`
|
|
40212
|
+
};
|
|
40213
|
+
}
|
|
40214
|
+
return { folders: buildSmartFolders(runSqlite(dbPath2, SMART_FOLDERS_SQL)) };
|
|
40215
|
+
} catch (error2) {
|
|
40216
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
40217
|
+
if (message.includes("authorization denied") || message.includes("unable to open database")) {
|
|
40218
|
+
return { folders: null, error: "no_fda", message: FDA_MESSAGE };
|
|
40219
|
+
}
|
|
40220
|
+
console.error(`Failed to read smart folders: ${message}`);
|
|
40221
|
+
return { folders: null, error: "query_error", message: "Failed to read smart folders." };
|
|
40222
|
+
}
|
|
40223
|
+
}
|
|
40224
|
+
|
|
39842
40225
|
// src/utils/attachmentFs.ts
|
|
39843
40226
|
import {
|
|
39844
|
-
existsSync as
|
|
40227
|
+
existsSync as existsSync3,
|
|
39845
40228
|
lstatSync,
|
|
39846
40229
|
mkdirSync,
|
|
39847
40230
|
mkdtempSync,
|
|
@@ -39850,11 +40233,11 @@ import {
|
|
|
39850
40233
|
rmSync,
|
|
39851
40234
|
statSync
|
|
39852
40235
|
} from "fs";
|
|
39853
|
-
import { dirname, isAbsolute, join as
|
|
39854
|
-
import { homedir as
|
|
40236
|
+
import { dirname, isAbsolute, join as join4, relative, resolve, sep } from "path";
|
|
40237
|
+
import { homedir as homedir4, tmpdir } from "os";
|
|
39855
40238
|
function allowedSaveRoots() {
|
|
39856
40239
|
return [
|
|
39857
|
-
resolve(
|
|
40240
|
+
resolve(homedir4()),
|
|
39858
40241
|
resolve(tmpdir()),
|
|
39859
40242
|
"/Volumes",
|
|
39860
40243
|
"/private/var/folders",
|
|
@@ -39862,8 +40245,8 @@ function allowedSaveRoots() {
|
|
|
39862
40245
|
"/private/tmp"
|
|
39863
40246
|
];
|
|
39864
40247
|
}
|
|
39865
|
-
function canonicalize(
|
|
39866
|
-
return realpathSync.native(
|
|
40248
|
+
function canonicalize(path7) {
|
|
40249
|
+
return realpathSync.native(path7);
|
|
39867
40250
|
}
|
|
39868
40251
|
function isWithinRoots(candidate, roots) {
|
|
39869
40252
|
return roots.some((root) => {
|
|
@@ -39885,9 +40268,9 @@ function canonicalRoots(roots) {
|
|
|
39885
40268
|
}
|
|
39886
40269
|
return canonical;
|
|
39887
40270
|
}
|
|
39888
|
-
function entryExists(
|
|
40271
|
+
function entryExists(path7) {
|
|
39889
40272
|
try {
|
|
39890
|
-
lstatSync(
|
|
40273
|
+
lstatSync(path7);
|
|
39891
40274
|
return true;
|
|
39892
40275
|
} catch (e) {
|
|
39893
40276
|
const code = e.code;
|
|
@@ -39928,7 +40311,7 @@ function assertSafeSavePath(p, roots = allowedSaveRoots()) {
|
|
|
39928
40311
|
if (suffix.split(sep).includes("..")) {
|
|
39929
40312
|
throw new Error(`Refusing to write outside allowed locations (home, temp, /Volumes): "${abs}"`);
|
|
39930
40313
|
}
|
|
39931
|
-
const canonicalDest = suffix ?
|
|
40314
|
+
const canonicalDest = suffix ? join4(canonicalAncestor, suffix) : canonicalAncestor;
|
|
39932
40315
|
const allowed = canonicalRoots(roots);
|
|
39933
40316
|
if (!isWithinRoots(canonicalAncestor, allowed) || !isWithinRoots(canonicalDest, allowed)) {
|
|
39934
40317
|
throw new Error(
|
|
@@ -39970,16 +40353,16 @@ function makeTempDir() {
|
|
|
39970
40353
|
}
|
|
39971
40354
|
function cleanupTempDir(dir) {
|
|
39972
40355
|
try {
|
|
39973
|
-
if (
|
|
40356
|
+
if (existsSync3(dir)) rmSync(dir, { recursive: true, force: true });
|
|
39974
40357
|
} catch {
|
|
39975
40358
|
}
|
|
39976
40359
|
}
|
|
39977
40360
|
|
|
39978
40361
|
// src/services/appleNotesManager.ts
|
|
39979
40362
|
var import_turndown = __toESM(require_turndown_cjs(), 1);
|
|
39980
|
-
import { existsSync as
|
|
39981
|
-
import { homedir as
|
|
39982
|
-
import { join as
|
|
40363
|
+
import { existsSync as existsSync4 } from "fs";
|
|
40364
|
+
import { homedir as homedir5 } from "os";
|
|
40365
|
+
import { join as join5 } from "path";
|
|
39983
40366
|
var FIELD_SEP = "";
|
|
39984
40367
|
var RECORD_SEP = "";
|
|
39985
40368
|
var AS_FIELD_SEP = "(character id 31)";
|
|
@@ -40207,8 +40590,8 @@ function getNoteLinkFromDB(coreDataId2) {
|
|
|
40207
40590
|
const match = coreDataId2.match(/\/p(\d+)$/);
|
|
40208
40591
|
if (!match) return null;
|
|
40209
40592
|
const pk = parseInt(match[1], 10);
|
|
40210
|
-
const dbPath2 =
|
|
40211
|
-
if (!
|
|
40593
|
+
const dbPath2 = join5(homedir5(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
|
|
40594
|
+
if (!existsSync4(dbPath2)) return null;
|
|
40212
40595
|
try {
|
|
40213
40596
|
const { DatabaseSync } = __require("node:sqlite");
|
|
40214
40597
|
const db = new DatabaseSync(dbPath2, { readOnly: true });
|
|
@@ -40849,6 +41232,58 @@ var AppleNotesManager = class {
|
|
|
40849
41232
|
return resultList as text
|
|
40850
41233
|
`;
|
|
40851
41234
|
}
|
|
41235
|
+
/**
|
|
41236
|
+
* Lists every smart folder with its decoded query, read-only.
|
|
41237
|
+
*
|
|
41238
|
+
* Folder metadata and queries come from the NoteStore database (Full Disk
|
|
41239
|
+
* Access). With `includeMatchingNotes`, Notes.app itself is asked which notes
|
|
41240
|
+
* each smart folder currently shows, so membership reflects Notes' own
|
|
41241
|
+
* evaluation of the query rather than a reimplementation of it.
|
|
41242
|
+
*
|
|
41243
|
+
* @param options.includeMatchingNotes - Also list the notes each folder shows
|
|
41244
|
+
* @param options.limit - Maximum matching notes per folder (default 50)
|
|
41245
|
+
* @throws Error when the database cannot be read (message says why)
|
|
41246
|
+
*/
|
|
41247
|
+
listSmartFolders(options = {}) {
|
|
41248
|
+
const result = readSmartFolders();
|
|
41249
|
+
if (!result.folders) throw new Error(result.message || "Failed to read smart folders.");
|
|
41250
|
+
if (!options.includeMatchingNotes) return result.folders;
|
|
41251
|
+
const limit = options.limit ?? 50;
|
|
41252
|
+
return result.folders.map((folder) => {
|
|
41253
|
+
try {
|
|
41254
|
+
const { total, notes } = this.listSmartFolderNoteRefs(folder.id, limit);
|
|
41255
|
+
return { ...folder, matchingNoteCount: total, matchingNotes: notes };
|
|
41256
|
+
} catch (error2) {
|
|
41257
|
+
return {
|
|
41258
|
+
...folder,
|
|
41259
|
+
matchingNotesError: error2 instanceof Error ? error2.message : String(error2)
|
|
41260
|
+
};
|
|
41261
|
+
}
|
|
41262
|
+
});
|
|
41263
|
+
}
|
|
41264
|
+
/**
|
|
41265
|
+
* Asks Notes.app for the notes one smart folder currently shows.
|
|
41266
|
+
*
|
|
41267
|
+
* @returns The folder's total note count and up to `limit` (title, id) pairs
|
|
41268
|
+
*/
|
|
41269
|
+
listSmartFolderNoteRefs(folderId2, limit) {
|
|
41270
|
+
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICFolder\/p\d+$/i.test(folderId2))
|
|
41271
|
+
throw new Error("An exact folder ID is required");
|
|
41272
|
+
const safeLimit = Math.max(1, Math.floor(limit));
|
|
41273
|
+
const script = buildAppLevelScript(
|
|
41274
|
+
this.buildBulkListCommand({ folderRef: `folder id "${folderId2}"`, sliceLimit: safeLimit })
|
|
41275
|
+
);
|
|
41276
|
+
const result = executeAppleScript(script);
|
|
41277
|
+
if (!result.success) {
|
|
41278
|
+
throw new Error(`Failed to list smart folder notes: ${result.error ?? "unknown error"}`);
|
|
41279
|
+
}
|
|
41280
|
+
const sepIdx = result.output.indexOf(RECORD_SEP);
|
|
41281
|
+
const header = sepIdx === -1 ? result.output : result.output.slice(0, sepIdx);
|
|
41282
|
+
const total = Number.parseInt(header.trim(), 10);
|
|
41283
|
+
if (Number.isNaN(total)) throw new Error("Unexpected smart folder listing output");
|
|
41284
|
+
const records = sepIdx === -1 ? "" : result.output.slice(sepIdx + 1);
|
|
41285
|
+
return { total, notes: this.parseBulkListOutput(records, safeLimit) };
|
|
41286
|
+
}
|
|
40852
41287
|
/**
|
|
40853
41288
|
* Parses bulk listing output into deduplicated (title, id) pairs.
|
|
40854
41289
|
*
|
|
@@ -42009,7 +42444,7 @@ var AppleNotesManager = class {
|
|
|
42009
42444
|
if (parts[0] !== "OK") {
|
|
42010
42445
|
return { success: false, error: parts[1]?.trim() || "attachment not found" };
|
|
42011
42446
|
}
|
|
42012
|
-
if (!
|
|
42447
|
+
if (!existsSync4(abs) || fileSize(abs) === 0) {
|
|
42013
42448
|
return { success: false, error: `Notes reported success but no file was written to ${abs}` };
|
|
42014
42449
|
}
|
|
42015
42450
|
return {
|
|
@@ -42452,15 +42887,15 @@ var AppleNotesManager = class {
|
|
|
42452
42887
|
};
|
|
42453
42888
|
|
|
42454
42889
|
// src/utils/syncDetection.ts
|
|
42455
|
-
import { execFileSync as
|
|
42456
|
-
import * as
|
|
42457
|
-
import * as
|
|
42458
|
-
import * as
|
|
42459
|
-
var
|
|
42460
|
-
|
|
42890
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
42891
|
+
import * as fs3 from "fs";
|
|
42892
|
+
import * as path3 from "path";
|
|
42893
|
+
import * as os3 from "os";
|
|
42894
|
+
var NOTES_DB_PATH3 = path3.join(
|
|
42895
|
+
os3.homedir(),
|
|
42461
42896
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
42462
42897
|
);
|
|
42463
|
-
var WAL_PATH = `${
|
|
42898
|
+
var WAL_PATH = `${NOTES_DB_PATH3}-wal`;
|
|
42464
42899
|
var RECENT_ACTIVITY_THRESHOLD_SECONDS = 5;
|
|
42465
42900
|
var SYNC_STATUS_CACHE_TTL_MS = 2e3;
|
|
42466
42901
|
var cachedSyncStatus = null;
|
|
@@ -42476,14 +42911,14 @@ function getSyncStatus(useCache = true) {
|
|
|
42476
42911
|
recentActivity: false
|
|
42477
42912
|
};
|
|
42478
42913
|
try {
|
|
42479
|
-
if (!
|
|
42914
|
+
if (!fs3.existsSync(NOTES_DB_PATH3)) {
|
|
42480
42915
|
status.error = "Notes database not found";
|
|
42481
42916
|
cachedSyncStatus = status;
|
|
42482
42917
|
cacheTimestamp = Date.now();
|
|
42483
42918
|
return status;
|
|
42484
42919
|
}
|
|
42485
|
-
if (
|
|
42486
|
-
const walStats =
|
|
42920
|
+
if (fs3.existsSync(WAL_PATH)) {
|
|
42921
|
+
const walStats = fs3.statSync(WAL_PATH);
|
|
42487
42922
|
const secondsAgo = (Date.now() - walStats.mtimeMs) / 1e3;
|
|
42488
42923
|
status.secondsSinceLastChange = Math.round(secondsAgo);
|
|
42489
42924
|
status.recentActivity = secondsAgo < RECENT_ACTIVITY_THRESHOLD_SECONDS;
|
|
@@ -42497,9 +42932,9 @@ function getSyncStatus(useCache = true) {
|
|
|
42497
42932
|
WHERE object.ZCLOUDSTATE = state.Z_PK
|
|
42498
42933
|
);
|
|
42499
42934
|
`;
|
|
42500
|
-
const result =
|
|
42935
|
+
const result = execFileSync5(
|
|
42501
42936
|
"sqlite3",
|
|
42502
|
-
["-readonly",
|
|
42937
|
+
["-readonly", NOTES_DB_PATH3, query.replace(/\n/g, " ")],
|
|
42503
42938
|
{
|
|
42504
42939
|
encoding: "utf8",
|
|
42505
42940
|
timeout: 5e3,
|
|
@@ -42556,15 +42991,15 @@ function withSyncAwarenessSync(operation, fn) {
|
|
|
42556
42991
|
}
|
|
42557
42992
|
|
|
42558
42993
|
// src/utils/noteMetadata.ts
|
|
42559
|
-
import { execFileSync as
|
|
42560
|
-
import * as
|
|
42561
|
-
import * as
|
|
42562
|
-
import * as
|
|
42563
|
-
var
|
|
42564
|
-
|
|
42994
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
42995
|
+
import * as fs4 from "fs";
|
|
42996
|
+
import * as path4 from "path";
|
|
42997
|
+
import * as os4 from "os";
|
|
42998
|
+
var NOTES_DB_PATH4 = path4.join(
|
|
42999
|
+
os4.homedir(),
|
|
42565
43000
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
42566
43001
|
);
|
|
42567
|
-
var
|
|
43002
|
+
var FDA_MESSAGE2 = `Full Disk Access is required to read note metadata. In System Settings > Privacy & Security > Full Disk Access, grant access to the app that launches this server (Claude Desktop / Terminal / iTerm2), then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL} \u2014 run the doctor tool to verify.`;
|
|
42568
43003
|
var COLUMN_MAP = [
|
|
42569
43004
|
{ key: "pinned", column: "ZISPINNED", type: "bool" },
|
|
42570
43005
|
{ key: "hasChecklist", column: "ZHASCHECKLIST", type: "bool" },
|
|
@@ -42576,15 +43011,15 @@ var COLUMN_MAP = [
|
|
|
42576
43011
|
{ key: "widgetSnippet", column: "ZWIDGETSNIPPET", type: "text" },
|
|
42577
43012
|
{ key: "smartFolderQuery", column: "ZSMARTFOLDERQUERYJSON", type: "text" }
|
|
42578
43013
|
];
|
|
42579
|
-
function
|
|
42580
|
-
return
|
|
43014
|
+
function runSqlite2(query) {
|
|
43015
|
+
return execFileSync6("sqlite3", ["-readonly", NOTES_DB_PATH4, query], {
|
|
42581
43016
|
encoding: "utf8",
|
|
42582
43017
|
timeout: 5e3,
|
|
42583
43018
|
stdio: ["pipe", "pipe", "pipe"]
|
|
42584
43019
|
}).trim();
|
|
42585
43020
|
}
|
|
42586
43021
|
function presentColumns() {
|
|
42587
|
-
const out =
|
|
43022
|
+
const out = runSqlite2("PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);");
|
|
42588
43023
|
const cols = /* @__PURE__ */ new Set();
|
|
42589
43024
|
for (const line of out.split("\n")) {
|
|
42590
43025
|
const name = line.split("|")[1];
|
|
@@ -42602,8 +43037,8 @@ function getNoteMetadata(noteId3) {
|
|
|
42602
43037
|
};
|
|
42603
43038
|
}
|
|
42604
43039
|
const pk = pkMatch[1];
|
|
42605
|
-
if (!
|
|
42606
|
-
return { metadata: null, error: "no_fda", message:
|
|
43040
|
+
if (!fs4.existsSync(NOTES_DB_PATH4)) {
|
|
43041
|
+
return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
|
|
42607
43042
|
}
|
|
42608
43043
|
try {
|
|
42609
43044
|
const available = presentColumns();
|
|
@@ -42612,7 +43047,7 @@ function getNoteMetadata(noteId3) {
|
|
|
42612
43047
|
return { metadata: {} };
|
|
42613
43048
|
}
|
|
42614
43049
|
const pairs = selected.map((c) => `'${c.key}', ${c.column}`).join(", ");
|
|
42615
|
-
const row =
|
|
43050
|
+
const row = runSqlite2(
|
|
42616
43051
|
`SELECT json_object(${pairs}) FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = ${pk};`
|
|
42617
43052
|
);
|
|
42618
43053
|
if (!row) {
|
|
@@ -42637,7 +43072,7 @@ function getNoteMetadata(noteId3) {
|
|
|
42637
43072
|
} catch (error2) {
|
|
42638
43073
|
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
42639
43074
|
if (message.includes("authorization denied") || message.includes("unable to open database")) {
|
|
42640
|
-
return { metadata: null, error: "no_fda", message:
|
|
43075
|
+
return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
|
|
42641
43076
|
}
|
|
42642
43077
|
console.error(`Failed to read note metadata: ${message}`);
|
|
42643
43078
|
return { metadata: null, error: "query_error", message: "Failed to read note metadata." };
|
|
@@ -42645,12 +43080,12 @@ function getNoteMetadata(noteId3) {
|
|
|
42645
43080
|
}
|
|
42646
43081
|
|
|
42647
43082
|
// src/utils/noteIdentifiers.ts
|
|
42648
|
-
import { execFileSync as
|
|
42649
|
-
import * as
|
|
42650
|
-
import * as
|
|
42651
|
-
import * as
|
|
42652
|
-
var
|
|
42653
|
-
|
|
43083
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
43084
|
+
import * as fs5 from "fs";
|
|
43085
|
+
import * as os5 from "os";
|
|
43086
|
+
import * as path5 from "path";
|
|
43087
|
+
var NOTES_DB_PATH5 = path5.join(
|
|
43088
|
+
os5.homedir(),
|
|
42654
43089
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
42655
43090
|
);
|
|
42656
43091
|
var UUID_PATTERN = /^[0-9A-Fa-f]{8}(?:-[0-9A-Fa-f]{4}){3}-[0-9A-Fa-f]{12}$/;
|
|
@@ -42724,10 +43159,10 @@ function buildLookupSql(entity2, keys) {
|
|
|
42724
43159
|
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(entity2)} AND o.Z_PK IN (${inList})));`;
|
|
42725
43160
|
}
|
|
42726
43161
|
function runJsonQuery(sql, dbPath2) {
|
|
42727
|
-
if (!
|
|
43162
|
+
if (!fs5.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
|
|
42728
43163
|
let out;
|
|
42729
43164
|
try {
|
|
42730
|
-
out =
|
|
43165
|
+
out = execFileSync7("sqlite3", ["-readonly", dbPath2, sql], {
|
|
42731
43166
|
encoding: "utf8",
|
|
42732
43167
|
timeout: 5e3,
|
|
42733
43168
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -42747,7 +43182,7 @@ function runJsonQuery(sql, dbPath2) {
|
|
|
42747
43182
|
function coreDataId(store, entity2, pk) {
|
|
42748
43183
|
return `x-coredata://${store}/${entity2}/p${pk}`;
|
|
42749
43184
|
}
|
|
42750
|
-
function resolveIdentifiers(values, entity2, dbPath2 =
|
|
43185
|
+
function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH5) {
|
|
42751
43186
|
const resolved = /* @__PURE__ */ new Map();
|
|
42752
43187
|
const keys = [];
|
|
42753
43188
|
const uuids = [];
|
|
@@ -42793,7 +43228,7 @@ function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH4) {
|
|
|
42793
43228
|
return resolved;
|
|
42794
43229
|
}
|
|
42795
43230
|
var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
|
|
42796
|
-
function lookupStableIdentifiers(ids, entity2, dbPath2 =
|
|
43231
|
+
function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH5) {
|
|
42797
43232
|
const result = /* @__PURE__ */ new Map();
|
|
42798
43233
|
const wanted = /* @__PURE__ */ new Map();
|
|
42799
43234
|
for (const id2 of ids) {
|
|
@@ -42829,7 +43264,7 @@ function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH4) {
|
|
|
42829
43264
|
}
|
|
42830
43265
|
return result;
|
|
42831
43266
|
}
|
|
42832
|
-
function withStableIdentifiers(items, entity2, dbPath2 =
|
|
43267
|
+
function withStableIdentifiers(items, entity2, dbPath2 = NOTES_DB_PATH5) {
|
|
42833
43268
|
const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
|
|
42834
43269
|
if (ids.length === 0) return items;
|
|
42835
43270
|
const found = lookupStableIdentifiers(ids, entity2, dbPath2);
|
|
@@ -43417,13 +43852,13 @@ function positiveTextTerms(node, negated = false) {
|
|
|
43417
43852
|
}
|
|
43418
43853
|
|
|
43419
43854
|
// src/utils/noteQueryStore.ts
|
|
43420
|
-
import { execFileSync as
|
|
43421
|
-
import * as
|
|
43422
|
-
import * as
|
|
43423
|
-
import * as
|
|
43855
|
+
import { execFileSync as execFileSync8 } from "child_process";
|
|
43856
|
+
import * as fs6 from "fs";
|
|
43857
|
+
import * as os6 from "os";
|
|
43858
|
+
import * as path6 from "path";
|
|
43424
43859
|
import { gunzipSync as gunzipSync3 } from "zlib";
|
|
43425
|
-
var
|
|
43426
|
-
|
|
43860
|
+
var NOTES_DB_PATH6 = path6.join(
|
|
43861
|
+
os6.homedir(),
|
|
43427
43862
|
"Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
|
|
43428
43863
|
);
|
|
43429
43864
|
var QUERY_SCAN = { DEFAULT: 500, MAX: 5e3 };
|
|
@@ -43510,13 +43945,13 @@ function countWords(text) {
|
|
|
43510
43945
|
}
|
|
43511
43946
|
return count;
|
|
43512
43947
|
}
|
|
43513
|
-
var
|
|
43948
|
+
var REQUIRED_COLUMNS2 = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
|
|
43514
43949
|
function col(available, alias, name) {
|
|
43515
43950
|
return available.has(name) ? `${alias}.${name}` : "NULL";
|
|
43516
43951
|
}
|
|
43517
43952
|
var entity = (name) => `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME='${name}')`;
|
|
43518
43953
|
function buildScanSql(available, options) {
|
|
43519
|
-
const missing =
|
|
43954
|
+
const missing = REQUIRED_COLUMNS2.filter((c) => !available.has(c));
|
|
43520
43955
|
if (missing.length) {
|
|
43521
43956
|
throw new NoteQueryStoreError(
|
|
43522
43957
|
`This macOS version's Notes database lacks columns query-notes needs (${missing.join(", ")}).`,
|
|
@@ -43557,8 +43992,8 @@ function buildScanSql(available, options) {
|
|
|
43557
43992
|
"COMMIT;"
|
|
43558
43993
|
].join(" ");
|
|
43559
43994
|
}
|
|
43560
|
-
function
|
|
43561
|
-
return
|
|
43995
|
+
function runSqlite3(dbPath2, query) {
|
|
43996
|
+
return execFileSync8("sqlite3", ["-readonly", dbPath2, query], {
|
|
43562
43997
|
encoding: "utf8",
|
|
43563
43998
|
timeout: 3e4,
|
|
43564
43999
|
maxBuffer: 512 * 1024 * 1024,
|
|
@@ -43567,7 +44002,7 @@ function runSqlite2(dbPath2, query) {
|
|
|
43567
44002
|
}
|
|
43568
44003
|
function presentColumns2(dbPath2) {
|
|
43569
44004
|
const cols = /* @__PURE__ */ new Set();
|
|
43570
|
-
for (const line of
|
|
44005
|
+
for (const line of runSqlite3(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n")) {
|
|
43571
44006
|
const name = line.split("|")[1];
|
|
43572
44007
|
if (name) cols.add(name);
|
|
43573
44008
|
}
|
|
@@ -43585,12 +44020,12 @@ function resolveFolders(rows) {
|
|
|
43585
44020
|
seen.add(pk);
|
|
43586
44021
|
const parent = row.parent !== null ? resolve3(row.parent, seen) : void 0;
|
|
43587
44022
|
const name = row.name ?? "";
|
|
43588
|
-
const
|
|
44023
|
+
const path7 = parent ? `${parent.path}/${escapeSegment(name)}` : escapeSegment(name);
|
|
43589
44024
|
const plainPath = parent ? `${parent.plainPath}/${name}` : name;
|
|
43590
44025
|
const info = {
|
|
43591
|
-
path:
|
|
44026
|
+
path: path7,
|
|
43592
44027
|
plainPath,
|
|
43593
|
-
keys: [...new Set([name, plainPath,
|
|
44028
|
+
keys: [...new Set([name, plainPath, path7].map(normalizeForMatch))],
|
|
43594
44029
|
accountPk: row.owner ?? parent?.accountPk ?? null,
|
|
43595
44030
|
shared: Boolean(row.shared) || Boolean(parent?.shared)
|
|
43596
44031
|
};
|
|
@@ -43622,12 +44057,12 @@ function queryNotes(expression, options = {}) {
|
|
|
43622
44057
|
const includeDeleted = options.includeDeleted ?? false;
|
|
43623
44058
|
const withBodies = needsContent(ast);
|
|
43624
44059
|
const withTags = needsTags(ast);
|
|
43625
|
-
const dbPath2 = options.dbPath ??
|
|
43626
|
-
if (!
|
|
44060
|
+
const dbPath2 = options.dbPath ?? NOTES_DB_PATH6;
|
|
44061
|
+
if (!fs6.existsSync(dbPath2)) throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
|
|
43627
44062
|
let output;
|
|
43628
44063
|
try {
|
|
43629
44064
|
const available = presentColumns2(dbPath2);
|
|
43630
|
-
output =
|
|
44065
|
+
output = runSqlite3(
|
|
43631
44066
|
dbPath2,
|
|
43632
44067
|
buildScanSql(available, { scanLimit, includeDeleted, withBodies, withTags })
|
|
43633
44068
|
);
|
|
@@ -43758,10 +44193,10 @@ function queryNotes(expression, options = {}) {
|
|
|
43758
44193
|
import { spawnSync } from "child_process";
|
|
43759
44194
|
|
|
43760
44195
|
// src/services/nativeTags.ts
|
|
43761
|
-
import { execFileSync as
|
|
44196
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
43762
44197
|
import { mkdtempSync as mkdtempSync2, writeFileSync, rmSync as rmSync2 } from "node:fs";
|
|
43763
44198
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
43764
|
-
import { join as
|
|
44199
|
+
import { join as join10 } from "node:path";
|
|
43765
44200
|
|
|
43766
44201
|
// src/services/shortcutConsent.ts
|
|
43767
44202
|
function shortcutConsentHint(shortcut) {
|
|
@@ -43837,7 +44272,7 @@ function addNativeTags(request, deps) {
|
|
|
43837
44272
|
};
|
|
43838
44273
|
}
|
|
43839
44274
|
function listInstalledShortcuts() {
|
|
43840
|
-
return
|
|
44275
|
+
return execFileSync9("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
|
|
43841
44276
|
encoding: "utf8",
|
|
43842
44277
|
timeout: 15e3,
|
|
43843
44278
|
maxBuffer: 1024 * 1024,
|
|
@@ -43863,11 +44298,11 @@ function runNativeTagsShortcut(input) {
|
|
|
43863
44298
|
const status = nativeTagsStatus();
|
|
43864
44299
|
if (!status.installed)
|
|
43865
44300
|
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
43866
|
-
const directory = mkdtempSync2(
|
|
44301
|
+
const directory = mkdtempSync2(join10(tmpdir2(), "apple-notes-native-tags-"));
|
|
43867
44302
|
try {
|
|
43868
|
-
const
|
|
43869
|
-
writeFileSync(
|
|
43870
|
-
|
|
44303
|
+
const path7 = join10(directory, "request.json");
|
|
44304
|
+
writeFileSync(path7, JSON.stringify(input), { mode: 384 });
|
|
44305
|
+
execFileSync9("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path7], {
|
|
43871
44306
|
encoding: "utf8",
|
|
43872
44307
|
timeout: 6e4,
|
|
43873
44308
|
maxBuffer: 1024 * 1024,
|
|
@@ -43886,10 +44321,10 @@ function runNativeTagsShortcut(input) {
|
|
|
43886
44321
|
}
|
|
43887
44322
|
|
|
43888
44323
|
// src/services/backgroundNotes.ts
|
|
43889
|
-
import { execFileSync as
|
|
44324
|
+
import { execFileSync as execFileSync10 } from "node:child_process";
|
|
43890
44325
|
import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
|
|
43891
44326
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
43892
|
-
import { join as
|
|
44327
|
+
import { join as join11 } from "node:path";
|
|
43893
44328
|
|
|
43894
44329
|
// src/utils/appendMarkdown.ts
|
|
43895
44330
|
var escape2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -44078,9 +44513,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
44078
44513
|
throw new Error(
|
|
44079
44514
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
44080
44515
|
);
|
|
44081
|
-
const directory = mkdtempSync3(
|
|
44516
|
+
const directory = mkdtempSync3(join11(tmpdir3(), "apple-notes-background-"));
|
|
44082
44517
|
try {
|
|
44083
|
-
const file =
|
|
44518
|
+
const file = join11(directory, "request.json");
|
|
44084
44519
|
writeFileSync2(
|
|
44085
44520
|
file,
|
|
44086
44521
|
JSON.stringify({
|
|
@@ -44100,7 +44535,7 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
44100
44535
|
{ mode: 384 }
|
|
44101
44536
|
);
|
|
44102
44537
|
try {
|
|
44103
|
-
|
|
44538
|
+
execFileSync10("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
|
|
44104
44539
|
encoding: "utf8",
|
|
44105
44540
|
timeout: 6e4,
|
|
44106
44541
|
maxBuffer: 1024 * 1024,
|
|
@@ -44303,7 +44738,7 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
|
|
|
44303
44738
|
throw new Error(
|
|
44304
44739
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
44305
44740
|
);
|
|
44306
|
-
const segments = (
|
|
44741
|
+
const segments = (path7) => JSON.stringify(splitFolderPath(path7).map((part) => part.toLocaleLowerCase()));
|
|
44307
44742
|
if (request.folder) {
|
|
44308
44743
|
const wanted = segments(request.folder);
|
|
44309
44744
|
if (!manager.listAccounts().some(
|
|
@@ -44392,7 +44827,7 @@ ${request.content}`
|
|
|
44392
44827
|
}
|
|
44393
44828
|
|
|
44394
44829
|
// src/services/capabilityMatrix.ts
|
|
44395
|
-
import { execFileSync as
|
|
44830
|
+
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
44396
44831
|
import { release } from "node:os";
|
|
44397
44832
|
var MACOS_SHORTCUTS_CLI = "12.0";
|
|
44398
44833
|
var MACOS_MARKDOWN_IMPORT = "26.0";
|
|
@@ -44599,7 +45034,7 @@ function evaluateFeatures(env, features = FEATURES) {
|
|
|
44599
45034
|
function readMacOSVersion() {
|
|
44600
45035
|
if (process.platform !== "darwin") return null;
|
|
44601
45036
|
try {
|
|
44602
|
-
const out =
|
|
45037
|
+
const out = execFileSync11("/usr/bin/sw_vers", ["-productVersion"], {
|
|
44603
45038
|
encoding: "utf8",
|
|
44604
45039
|
timeout: 3e3,
|
|
44605
45040
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -44632,9 +45067,9 @@ function getCapabilityMatrix(probe = probeCapabilityEnvironment) {
|
|
|
44632
45067
|
return evaluateFeatures(probe());
|
|
44633
45068
|
}
|
|
44634
45069
|
function formatCapabilityMatrix(matrix) {
|
|
44635
|
-
const
|
|
45070
|
+
const os7 = matrix.runtimeOS;
|
|
44636
45071
|
const lines = [
|
|
44637
|
-
`Feature matrix (macOS ${
|
|
45072
|
+
`Feature matrix (macOS ${os7.macOSVersion ?? "unknown"}, Darwin ${os7.darwinRelease}):`
|
|
44638
45073
|
];
|
|
44639
45074
|
for (const [name, status] of Object.entries(matrix.features)) {
|
|
44640
45075
|
lines.push(
|
|
@@ -44752,19 +45187,19 @@ function formatDoctorReport(r) {
|
|
|
44752
45187
|
}
|
|
44753
45188
|
|
|
44754
45189
|
// src/services/fileConfig.ts
|
|
44755
|
-
import { existsSync as
|
|
44756
|
-
import { join as
|
|
44757
|
-
import { homedir as
|
|
45190
|
+
import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
|
|
45191
|
+
import { join as join12 } from "path";
|
|
45192
|
+
import { homedir as homedir10 } from "os";
|
|
44758
45193
|
function fileConfigPath(env = process.env) {
|
|
44759
45194
|
const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
|
|
44760
45195
|
if (override && override.trim()) return override.trim();
|
|
44761
|
-
return
|
|
45196
|
+
return join12(homedir10(), "Library", "Application Support", "apple-notes-mcp", "config.json");
|
|
44762
45197
|
}
|
|
44763
|
-
function loadFileConfig(env = process.env,
|
|
45198
|
+
function loadFileConfig(env = process.env, path7 = fileConfigPath(env)) {
|
|
44764
45199
|
const applied = [];
|
|
44765
45200
|
try {
|
|
44766
|
-
if (!
|
|
44767
|
-
const parsed = JSON.parse(readFileSync2(
|
|
45201
|
+
if (!existsSync9(path7)) return applied;
|
|
45202
|
+
const parsed = JSON.parse(readFileSync2(path7, "utf8"));
|
|
44768
45203
|
if (!parsed || typeof parsed !== "object") return applied;
|
|
44769
45204
|
for (const [k, v] of Object.entries(parsed)) {
|
|
44770
45205
|
if (typeof v !== "string") continue;
|
|
@@ -44774,7 +45209,7 @@ function loadFileConfig(env = process.env, path6 = fileConfigPath(env)) {
|
|
|
44774
45209
|
}
|
|
44775
45210
|
}
|
|
44776
45211
|
} catch (e) {
|
|
44777
|
-
console.error(`Failed to load apple-notes-mcp config file ${
|
|
45212
|
+
console.error(`Failed to load apple-notes-mcp config file ${path7}: ${String(e)}`);
|
|
44778
45213
|
}
|
|
44779
45214
|
return applied;
|
|
44780
45215
|
}
|
|
@@ -45285,7 +45720,7 @@ import {
|
|
|
45285
45720
|
writeFileSync as writeFileSync3
|
|
45286
45721
|
} from "node:fs";
|
|
45287
45722
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
45288
|
-
import { basename, isAbsolute as isAbsolute2, join as
|
|
45723
|
+
import { basename, isAbsolute as isAbsolute2, join as join13 } from "node:path";
|
|
45289
45724
|
var noteId = exactIdInput(
|
|
45290
45725
|
"ICNote",
|
|
45291
45726
|
/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i,
|
|
@@ -45328,9 +45763,9 @@ function assertExistingContentPreserved(before, after) {
|
|
|
45328
45763
|
throw new Error("Existing native object content or presentation changed");
|
|
45329
45764
|
}
|
|
45330
45765
|
}
|
|
45331
|
-
function localAttachment(
|
|
45332
|
-
if (!isAbsolute2(
|
|
45333
|
-
const descriptor = openSync(
|
|
45766
|
+
function localAttachment(path7) {
|
|
45767
|
+
if (!isAbsolute2(path7)) throw new Error("An absolute local file path is required");
|
|
45768
|
+
const descriptor = openSync(path7, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
45334
45769
|
try {
|
|
45335
45770
|
const stat = fstatSync(descriptor);
|
|
45336
45771
|
if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
|
|
@@ -45394,13 +45829,13 @@ function registerDirectOperations(server2, manager) {
|
|
|
45394
45829
|
"add-attachment",
|
|
45395
45830
|
"Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes.",
|
|
45396
45831
|
{ id: noteId, expectedContentHash: revision, path: external_exports.string().min(1).max(4096) },
|
|
45397
|
-
({ id: id2, expectedContentHash, path:
|
|
45832
|
+
({ id: id2, expectedContentHash, path: path7 }) => {
|
|
45398
45833
|
const before = readSnapshot(manager, id2);
|
|
45399
45834
|
if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
|
|
45400
|
-
const bytes = localAttachment(
|
|
45835
|
+
const bytes = localAttachment(path7);
|
|
45401
45836
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
45402
|
-
const directory = mkdtempSync4(
|
|
45403
|
-
const temporaryFile =
|
|
45837
|
+
const directory = mkdtempSync4(join13(tmpdir4(), "notes-attachment-add-"));
|
|
45838
|
+
const temporaryFile = join13(directory, basename(path7));
|
|
45404
45839
|
try {
|
|
45405
45840
|
writeFileSync3(temporaryFile, bytes, { mode: 384 });
|
|
45406
45841
|
if (readSnapshot(manager, id2).hash !== before.hash)
|
|
@@ -45946,7 +46381,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
45946
46381
|
|
|
45947
46382
|
// src/setupShortcuts.ts
|
|
45948
46383
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
45949
|
-
import { existsSync as
|
|
46384
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
45950
46385
|
import { release as release2 } from "node:os";
|
|
45951
46386
|
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
45952
46387
|
import { fileURLToPath } from "node:url";
|
|
@@ -45966,16 +46401,16 @@ var shortcutFiles = [
|
|
|
45966
46401
|
];
|
|
45967
46402
|
function setupShortcuts(checkOnly, dependencies = {}) {
|
|
45968
46403
|
const status = dependencies.status || nativeTagsStatus;
|
|
45969
|
-
const exists = dependencies.exists ||
|
|
45970
|
-
const open = dependencies.open || ((
|
|
45971
|
-
const result = spawnSync2("/usr/bin/open", [
|
|
46404
|
+
const exists = dependencies.exists || existsSync10;
|
|
46405
|
+
const open = dependencies.open || ((path7) => {
|
|
46406
|
+
const result = spawnSync2("/usr/bin/open", [path7], { encoding: "utf8" });
|
|
45972
46407
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
45973
46408
|
});
|
|
45974
46409
|
const baseDirectory = dependencies.baseDirectory || resolve2(dirname2(fileURLToPath(import.meta.url)), "../shortcuts");
|
|
45975
46410
|
const osRelease = (dependencies.osRelease || release2)();
|
|
45976
46411
|
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
45977
46412
|
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
45978
|
-
const
|
|
46413
|
+
const path7 = resolve2(baseDirectory, file);
|
|
45979
46414
|
let installed = false;
|
|
45980
46415
|
let identifier;
|
|
45981
46416
|
let error2;
|
|
@@ -45991,9 +46426,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
45991
46426
|
if (!installed && !checkOnly) {
|
|
45992
46427
|
if (optional2 && !(darwinMajor >= MARKDOWN_MIN_DARWIN_MAJOR))
|
|
45993
46428
|
skipped = `requires macOS 26 or later (this Mac reports Darwin ${osRelease})`;
|
|
45994
|
-
else if (!exists(
|
|
46429
|
+
else if (!exists(path7)) error2 = `Packaged Shortcut is missing: ${path7}`;
|
|
45995
46430
|
else {
|
|
45996
|
-
const result = open(
|
|
46431
|
+
const result = open(path7);
|
|
45997
46432
|
opened = result.ok;
|
|
45998
46433
|
if (!result.ok) error2 = result.error || `Could not open ${file}`;
|
|
45999
46434
|
}
|
|
@@ -46002,7 +46437,7 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
46002
46437
|
name,
|
|
46003
46438
|
installed,
|
|
46004
46439
|
identifier,
|
|
46005
|
-
file:
|
|
46440
|
+
file: path7,
|
|
46006
46441
|
opened,
|
|
46007
46442
|
...optional2 ? { optional: optional2 } : {},
|
|
46008
46443
|
...skipped ? { skipped } : {},
|
|
@@ -47346,6 +47781,55 @@ ${folderList}${syncNote}`,
|
|
|
47346
47781
|
);
|
|
47347
47782
|
}, "Error listing folders")
|
|
47348
47783
|
);
|
|
47784
|
+
registerTool(
|
|
47785
|
+
"list-smart-folders",
|
|
47786
|
+
{
|
|
47787
|
+
description: `Use when: listing Smart Folders and the rules that define them.
|
|
47788
|
+
Returns: each smart folder's name, ids, account, and parent, its rules decoded as match ("all"/"any"/"none") plus filters (each with a readable description), the stored query with the outer deleted wrapper removed, and the raw stored query JSON. With includeMatchingNotes, also the notes Notes.app currently shows in each folder.
|
|
47789
|
+
Do not use when: listing ordinary folders (list-folders) or searching notes (search-notes).
|
|
47790
|
+
Safety: read-only; reads the NoteStore database and requires Full Disk Access. includeMatchingNotes asks Notes.app (Automation permission) for each folder's current contents rather than re-evaluating the rules. Unrecognized rules are kept as "unknown" filters and set fullyDecoded false.`,
|
|
47791
|
+
inputSchema: {
|
|
47792
|
+
includeMatchingNotes: external_exports.boolean().optional().describe(
|
|
47793
|
+
"Also list the notes Notes.app currently shows in each smart folder (default false)"
|
|
47794
|
+
),
|
|
47795
|
+
limit: external_exports.number().int().min(1).max(500).optional().describe("Maximum matching notes returned per folder (default 50; count is always total)")
|
|
47796
|
+
},
|
|
47797
|
+
outputSchema: {
|
|
47798
|
+
smartFolders: external_exports.array(external_exports.object({}).passthrough()).optional(),
|
|
47799
|
+
count: external_exports.number().optional(),
|
|
47800
|
+
fullyDecoded: external_exports.boolean().optional()
|
|
47801
|
+
},
|
|
47802
|
+
annotations: { readOnlyHint: true }
|
|
47803
|
+
},
|
|
47804
|
+
withErrorHandling(({ includeMatchingNotes = false, limit }) => {
|
|
47805
|
+
const folders = notesManager.listSmartFolders({ includeMatchingNotes, limit });
|
|
47806
|
+
const fullyDecoded = folders.every((folder) => folder.fullyDecoded);
|
|
47807
|
+
if (folders.length === 0) {
|
|
47808
|
+
return successResponse("No smart folders found.", {
|
|
47809
|
+
smartFolders: [],
|
|
47810
|
+
count: 0,
|
|
47811
|
+
fullyDecoded
|
|
47812
|
+
});
|
|
47813
|
+
}
|
|
47814
|
+
const lines = folders.map((folder) => {
|
|
47815
|
+
const joiner = folder.match === "any" ? " OR " : " AND ";
|
|
47816
|
+
const rules = folder.filters.map((filter) => filter.description).join(joiner);
|
|
47817
|
+
const prefix = folder.match === "none" ? "none of: " : "";
|
|
47818
|
+
const where = [folder.account, folder.parent].filter(Boolean).join(" / ");
|
|
47819
|
+
const matches = folder.matchingNoteCount !== void 0 ? ` [${folder.matchingNoteCount} note(s)]` : folder.matchingNotesError ? ` [notes unavailable: ${folder.matchingNotesError}]` : "";
|
|
47820
|
+
return ` - ${folder.name ?? "(untitled)"}${where ? ` (${where})` : ""}: ${prefix}${rules || "(no rules)"}${matches}`;
|
|
47821
|
+
});
|
|
47822
|
+
return successResponse(
|
|
47823
|
+
`Found ${folders.length} smart folder(s):
|
|
47824
|
+
${lines.join("\n")}${fullyDecoded ? "" : '\n\nSome rules were not recognized; see filters of type "unknown".'}`,
|
|
47825
|
+
{
|
|
47826
|
+
smartFolders: folders,
|
|
47827
|
+
count: folders.length,
|
|
47828
|
+
fullyDecoded
|
|
47829
|
+
}
|
|
47830
|
+
);
|
|
47831
|
+
}, "Error listing smart folders")
|
|
47832
|
+
);
|
|
47349
47833
|
registerTool(
|
|
47350
47834
|
"create-folder",
|
|
47351
47835
|
{
|
package/package.json
CHANGED