apple-notes-mcp 2.9.22 → 2.9.24
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 +8 -1
- package/build/index.js +233 -76
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -274,7 +274,7 @@ Creates a new note in Apple Notes.
|
|
|
274
274
|
|-----------|------|----------|-------------|
|
|
275
275
|
| `title` | string | Yes | The title of the note. Automatically prepended as `<h1>` — do NOT include the title in `content` |
|
|
276
276
|
| `content` | string | One of `content`/`contentPath` | The body content of the note (do not repeat the title here) |
|
|
277
|
-
| `contentPath` | string | One of `content`/`contentPath` | Absolute path of a local UTF-8 file to use as the body instead of `content`. Allowed in the same places [`save-attachment`](#save-attachment) may write (home, temp, `/Volumes`);
|
|
277
|
+
| `contentPath` | string | One of `content`/`contentPath` | Absolute path of a local UTF-8 file to use as the body instead of `content`. Allowed in the same places [`save-attachment`](#save-attachment) may write (home, temp, `/Volumes`), except hidden paths (any component starting with `.`, such as `~/.ssh` or a project `.env`) and `~/Library` other than iCloud Drive (`~/Library/Mobile Documents`) and cloud storage folders (`~/Library/CloudStorage`), which can hold credentials; set `APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS=1` to allow those. Symbolic links, non-regular files, invalid UTF-8 and files over 1 MiB are refused before anything is written. A leading byte-order mark is dropped |
|
|
278
278
|
| `tags` | string[] | No | Returned-only metadata — **NOT written to Notes.app**. Apple Notes tags can't be set via AppleScript, so values passed here are echoed back in the response but do not appear on the created note. Inline `#hashtags` in `content` stay searchable text and are returned as `hashtags`, but they do not become native Notes tags; add real tags afterwards with [`add-native-tags`](#add-native-tags). Refused with `format: "markdown"` |
|
|
279
279
|
| `folder` | string | No | Folder to create the note in. Supports nested paths like `"Work/Clients"`. **The folder must already exist** — create it first with [`create-folder`](#create-folder). A smart folder is refused (see [`move-note`](#move-note)). Defaults to account root |
|
|
280
280
|
| `account` | string | No | Account name (defaults to Notes.app's default account; matched exactly or by a *unique* prefix — an ambiguous prefix is refused). Must be an account Notes.app already has configured — see [`list-accounts`](#list-accounts) |
|
|
@@ -2765,6 +2765,7 @@ All configuration is optional — the server works out of the box. Override beha
|
|
|
2765
2765
|
| `APPLE_NOTES_MCP_BACKGROUND_SHORTCUT` | `Apple Notes MCP - Background Operations v5` | Name or UUID of the installed Background Operations bridge to run. `setup` still installs and checks the default name. |
|
|
2766
2766
|
| `APPLE_NOTES_MCP_MARKDOWN_SHORTCUT` | `Apple Notes MCP - Create Markdown Note` | Name or UUID of the installed Create Markdown Note bridge to run. `setup` still installs and checks the default name. |
|
|
2767
2767
|
| `APPLE_NOTES_MCP_PASTEBOARD_NAME` | unset | Testing only: makes [`add-attachment-from-pasteboard`](#add-attachment-from-pasteboard) read a private named pasteboard instead of the general clipboard. |
|
|
2768
|
+
| `APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS` | unset | Set to `1` to let `create-note` read a [`contentPath`](#create-note) inside a hidden directory or file (such as `~/.config`) or `~/Library` outside iCloud Drive and `~/Library/CloudStorage`. Off by default because those places hold keys, tokens and app data. |
|
|
2768
2769
|
| `DEBUG` / `VERBOSE` | unset | Set either to enable verbose diagnostic logging to stderr. |
|
|
2769
2770
|
|
|
2770
2771
|
### Configuration file (when the host strips `env`)
|
|
@@ -3008,6 +3009,12 @@ A software consulting, contracting, and development company.
|
|
|
3008
3009
|
- Email: rob@superiortech.io
|
|
3009
3010
|
- GitHub: [@sweetrb](https://github.com/sweetrb)
|
|
3010
3011
|
|
|
3012
|
+
### Contributors
|
|
3013
|
+
|
|
3014
|
+
- **Oliver Ames** ([@oliverames](https://github.com/oliverames)) — the project's most prolific
|
|
3015
|
+
contributor, with dozens of merged pull requests spanning features, fixes, documentation, and
|
|
3016
|
+
sharp, well-reproduced bug reports.
|
|
3017
|
+
|
|
3011
3018
|
## License
|
|
3012
3019
|
|
|
3013
3020
|
MIT License - see [LICENSE](https://github.com/sweetrb/apple-notes-mcp/blob/main/LICENSE) for details.
|
package/build/index.js
CHANGED
|
@@ -41184,11 +41184,36 @@ function assertReadableInRoots(p, roots = allowedSaveRoots(), label = "Content f
|
|
|
41184
41184
|
);
|
|
41185
41185
|
return abs;
|
|
41186
41186
|
}
|
|
41187
|
-
|
|
41187
|
+
var ALLOW_PRIVATE_CONTENT_ENV = "APPLE_NOTES_MCP_ALLOW_PRIVATE_CONTENT_PATHS";
|
|
41188
|
+
function privateContentReason(p, roots) {
|
|
41189
|
+
const containing = roots.map((r) => r.endsWith(sep) ? r.slice(0, -1) : r).filter((r) => isWithinRoots(p, [r])).sort((a, b) => b.length - a.length)[0];
|
|
41190
|
+
const below = containing === void 0 ? p : relative(containing, p);
|
|
41191
|
+
if (below.split(sep).some((part) => part.startsWith("."))) return "a hidden file or directory";
|
|
41192
|
+
const home = resolve(homedir6());
|
|
41193
|
+
const libraries = [
|
|
41194
|
+
join6(home, "Library"),
|
|
41195
|
+
...canonicalRoots([home]).map((h) => join6(h, "Library"))
|
|
41196
|
+
];
|
|
41197
|
+
if (!isWithinRoots(p, libraries)) return null;
|
|
41198
|
+
const cloudDocuments = libraries.flatMap((l) => CLOUD_DOCUMENT_DIRS.map((d) => join6(l, d)));
|
|
41199
|
+
return isWithinRoots(p, cloudDocuments) ? null : "~/Library";
|
|
41200
|
+
}
|
|
41201
|
+
var CLOUD_DOCUMENT_DIRS = ["Mobile Documents", "CloudStorage"];
|
|
41202
|
+
function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots(), allowPrivate = process.env[ALLOW_PRIVATE_CONTENT_ENV] === "1") {
|
|
41188
41203
|
const abs = assertReadableInRoots(p, roots);
|
|
41204
|
+
const assertNotPrivate = (candidate, candidateRoots) => {
|
|
41205
|
+
if (allowPrivate) return;
|
|
41206
|
+
const reason = privateContentReason(candidate, candidateRoots);
|
|
41207
|
+
if (reason)
|
|
41208
|
+
throw new Error(
|
|
41209
|
+
`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.`
|
|
41210
|
+
);
|
|
41211
|
+
};
|
|
41212
|
+
assertNotPrivate(abs, roots);
|
|
41213
|
+
assertNotPrivate(canonicalize(abs), canonicalRoots(roots));
|
|
41189
41214
|
let descriptor;
|
|
41190
41215
|
try {
|
|
41191
|
-
descriptor = openSync(abs, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
41216
|
+
descriptor = openSync(abs, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
41192
41217
|
} catch (error2) {
|
|
41193
41218
|
if (error2.code === "ELOOP")
|
|
41194
41219
|
throw new Error(`Refusing to read the symbolic link "${abs}".`);
|
|
@@ -41197,6 +41222,20 @@ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots()) {
|
|
|
41197
41222
|
try {
|
|
41198
41223
|
const stat = fstatSync(descriptor);
|
|
41199
41224
|
if (!stat.isFile()) throw new Error(`Content file is not a regular file: "${abs}"`);
|
|
41225
|
+
let after;
|
|
41226
|
+
try {
|
|
41227
|
+
after = canonicalize(abs);
|
|
41228
|
+
} catch {
|
|
41229
|
+
throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
|
|
41230
|
+
}
|
|
41231
|
+
if (!isWithinRoots(after, canonicalRoots(roots)))
|
|
41232
|
+
throw new Error(
|
|
41233
|
+
`Refusing to read outside allowed locations (home, temp, /Volumes): "${abs}" resolves to "${after}".`
|
|
41234
|
+
);
|
|
41235
|
+
assertNotPrivate(after, canonicalRoots(roots));
|
|
41236
|
+
const named = statSync(after);
|
|
41237
|
+
if (named.dev !== stat.dev || named.ino !== stat.ino)
|
|
41238
|
+
throw new Error(`Content file changed while it was being opened; try again: "${abs}"`);
|
|
41200
41239
|
if (stat.size === 0) throw new Error(`Content file is empty: "${abs}"`);
|
|
41201
41240
|
if (stat.size > maxBytes)
|
|
41202
41241
|
throw new Error(`Content file is ${stat.size} bytes, over the ${maxBytes}-byte limit.`);
|
|
@@ -41725,16 +41764,17 @@ function copyFileExclusive(src, dest) {
|
|
|
41725
41764
|
}
|
|
41726
41765
|
function exportFileName(record2, source, kind) {
|
|
41727
41766
|
const ext = extname(source);
|
|
41767
|
+
const id2 = safeComponent(record2.identifier) ?? "attachment";
|
|
41728
41768
|
if (kind === "preview") {
|
|
41729
41769
|
const stored2 = safeComponent(record2.filename ? basename(record2.filename) : null);
|
|
41730
|
-
const stem = stored2 ? basename(stored2, extname(stored2)) :
|
|
41731
|
-
return `${safeComponent(stem) ??
|
|
41770
|
+
const stem = stored2 ? basename(stored2, extname(stored2)) : id2;
|
|
41771
|
+
return `${safeComponent(stem) ?? id2}-preview${ext}`;
|
|
41732
41772
|
}
|
|
41733
41773
|
const stored = safeComponent(record2.filename ? basename(record2.filename) : null);
|
|
41734
41774
|
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 `${
|
|
41775
|
+
const own = safeComponent(basename(source));
|
|
41776
|
+
if (own && !GENERIC_FILE_NAMES.has(own.toLowerCase())) return own;
|
|
41777
|
+
return `${id2}${ext}`;
|
|
41738
41778
|
}
|
|
41739
41779
|
function collisionName(name, attempt) {
|
|
41740
41780
|
if (attempt <= 1) return name;
|
|
@@ -41768,6 +41808,8 @@ function exportOneAttachment(record2, dir, source) {
|
|
|
41768
41808
|
for (let attempt = 1; attempt <= MAX_COLLISION_SUFFIX; attempt++) {
|
|
41769
41809
|
const dest = join7(dir, collisionName(name, attempt));
|
|
41770
41810
|
try {
|
|
41811
|
+
if (dirname2(dest) !== resolve2(dir))
|
|
41812
|
+
throw new Error(`Refusing to write outside the export directory: "${dest}"`);
|
|
41771
41813
|
assertSafeSavePath(dest);
|
|
41772
41814
|
copyFileExclusive(source.path, dest);
|
|
41773
41815
|
return { ...base, exportedTo: dest, exportedKind: source.kind };
|
|
@@ -52590,21 +52632,23 @@ function renderNotesWithTemplate(notes, ctx, options) {
|
|
|
52590
52632
|
}
|
|
52591
52633
|
|
|
52592
52634
|
// src/utils/templateAssets.ts
|
|
52593
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
52635
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
52594
52636
|
import {
|
|
52595
52637
|
closeSync as closeSync5,
|
|
52596
52638
|
constants as constants5,
|
|
52597
52639
|
fstatSync as fstatSync5,
|
|
52640
|
+
linkSync,
|
|
52598
52641
|
lstatSync as lstatSync4,
|
|
52599
52642
|
mkdirSync as mkdirSync5,
|
|
52600
52643
|
openSync as openSync5,
|
|
52601
52644
|
readSync as readSync4,
|
|
52645
|
+
unlinkSync as unlinkSync3,
|
|
52602
52646
|
writeSync as writeSync4
|
|
52603
52647
|
} from "node:fs";
|
|
52604
52648
|
import { extname as extname4, isAbsolute as isAbsolute3, join as join21, relative as relative4, resolve as resolve4, sep as sep4 } from "node:path";
|
|
52605
52649
|
var CHUNK = 1024 * 1024;
|
|
52606
52650
|
function openRegular(path10) {
|
|
52607
|
-
const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW);
|
|
52651
|
+
const fd = openSync5(path10, constants5.O_RDONLY | constants5.O_NOFOLLOW | constants5.O_NONBLOCK);
|
|
52608
52652
|
if (!fstatSync5(fd).isFile()) {
|
|
52609
52653
|
closeSync5(fd);
|
|
52610
52654
|
throw new Error("not a regular file");
|
|
@@ -52690,26 +52734,56 @@ var HashedSidecarWriter = class {
|
|
|
52690
52734
|
closeSync5(source);
|
|
52691
52735
|
}
|
|
52692
52736
|
}
|
|
52737
|
+
/**
|
|
52738
|
+
* Copy to a private temporary name, then hard-link it into place, so the
|
|
52739
|
+
* hashed name only ever holds complete content: an interrupted copy can't
|
|
52740
|
+
* leave a truncated file that later exports would report as name-taken.
|
|
52741
|
+
* Where the volume has no hard links, copy straight to the hashed name and
|
|
52742
|
+
* remove it if the copy fails.
|
|
52743
|
+
*/
|
|
52693
52744
|
copy(source, target) {
|
|
52694
|
-
const
|
|
52695
|
-
|
|
52696
|
-
constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW,
|
|
52697
|
-
420
|
|
52698
|
-
);
|
|
52745
|
+
const temp = join21(this.dir, `.asset-${randomBytes(6).toString("hex")}.tmp`);
|
|
52746
|
+
writeNew(source, temp);
|
|
52699
52747
|
try {
|
|
52700
|
-
|
|
52701
|
-
|
|
52702
|
-
|
|
52703
|
-
|
|
52704
|
-
|
|
52705
|
-
while (written < n) written += writeSync4(out, chunk, written, n - written);
|
|
52706
|
-
position += n;
|
|
52707
|
-
}
|
|
52748
|
+
linkSync(temp, target);
|
|
52749
|
+
return;
|
|
52750
|
+
} catch (error2) {
|
|
52751
|
+
const code = error2.code;
|
|
52752
|
+
if (code !== "ENOTSUP" && code !== "EPERM" && code !== "EXDEV") throw error2;
|
|
52708
52753
|
} finally {
|
|
52709
|
-
|
|
52754
|
+
try {
|
|
52755
|
+
unlinkSync3(temp);
|
|
52756
|
+
} catch {
|
|
52757
|
+
}
|
|
52710
52758
|
}
|
|
52759
|
+
writeNew(source, target);
|
|
52711
52760
|
}
|
|
52712
52761
|
};
|
|
52762
|
+
function writeNew(source, target) {
|
|
52763
|
+
const out = openSync5(
|
|
52764
|
+
target,
|
|
52765
|
+
constants5.O_WRONLY | constants5.O_CREAT | constants5.O_EXCL | constants5.O_NOFOLLOW,
|
|
52766
|
+
420
|
|
52767
|
+
);
|
|
52768
|
+
try {
|
|
52769
|
+
const chunk = Buffer.alloc(CHUNK);
|
|
52770
|
+
for (let position = 0; ; ) {
|
|
52771
|
+
const n = readSync4(source, chunk, 0, chunk.length, position);
|
|
52772
|
+
if (n <= 0) break;
|
|
52773
|
+
let written = 0;
|
|
52774
|
+
while (written < n) written += writeSync4(out, chunk, written, n - written);
|
|
52775
|
+
position += n;
|
|
52776
|
+
}
|
|
52777
|
+
} catch (error2) {
|
|
52778
|
+
closeSync5(out);
|
|
52779
|
+
try {
|
|
52780
|
+
unlinkSync3(target);
|
|
52781
|
+
} catch {
|
|
52782
|
+
}
|
|
52783
|
+
throw error2;
|
|
52784
|
+
}
|
|
52785
|
+
closeSync5(out);
|
|
52786
|
+
}
|
|
52713
52787
|
var ReferenceWriter = class {
|
|
52714
52788
|
constructor(linkBase) {
|
|
52715
52789
|
this.linkBase = linkBase;
|
|
@@ -53276,7 +53350,7 @@ function pasteboardFilename(requested, frozenFilename) {
|
|
|
53276
53350
|
function readRegularFile(path10) {
|
|
53277
53351
|
let descriptor;
|
|
53278
53352
|
try {
|
|
53279
|
-
descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW);
|
|
53353
|
+
descriptor = openSync6(path10, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
|
|
53280
53354
|
} catch {
|
|
53281
53355
|
throw new PasteboardError("file_unreadable", MESSAGES.file_unreadable);
|
|
53282
53356
|
}
|
|
@@ -54031,19 +54105,19 @@ function registerFolderDelete(server2, manager, deps = defaultDeps) {
|
|
|
54031
54105
|
}
|
|
54032
54106
|
|
|
54033
54107
|
// src/services/templateStore.ts
|
|
54034
|
-
import { randomBytes } from "node:crypto";
|
|
54108
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
54035
54109
|
import {
|
|
54036
54110
|
closeSync as closeSync8,
|
|
54037
54111
|
constants as constants8,
|
|
54038
54112
|
fstatSync as fstatSync8,
|
|
54039
|
-
linkSync,
|
|
54113
|
+
linkSync as linkSync2,
|
|
54040
54114
|
lstatSync as lstatSync5,
|
|
54041
54115
|
mkdirSync as mkdirSync7,
|
|
54042
54116
|
openSync as openSync8,
|
|
54043
54117
|
readdirSync as readdirSync4,
|
|
54044
54118
|
readSync as readSync6,
|
|
54045
54119
|
renameSync,
|
|
54046
|
-
unlinkSync as
|
|
54120
|
+
unlinkSync as unlinkSync4,
|
|
54047
54121
|
writeSync as writeSync5
|
|
54048
54122
|
} from "node:fs";
|
|
54049
54123
|
import { homedir as homedir19 } from "node:os";
|
|
@@ -54106,14 +54180,26 @@ var TemplateStore = class {
|
|
|
54106
54180
|
file(name) {
|
|
54107
54181
|
return join26(this.dir, `${name}.json`);
|
|
54108
54182
|
}
|
|
54109
|
-
/**
|
|
54183
|
+
/**
|
|
54184
|
+
* Read a saved template's text, refusing symlinks and non-regular files.
|
|
54185
|
+
* O_NONBLOCK keeps a FIFO from blocking the event loop (it is then refused as
|
|
54186
|
+
* not a regular file); it does not affect reading a regular file. Only a
|
|
54187
|
+
* symlink (ELOOP) is reported as unsafe-path; other open failures, such as
|
|
54188
|
+
* EACCES, are rethrown as they are.
|
|
54189
|
+
*/
|
|
54110
54190
|
readText(path10) {
|
|
54111
54191
|
let fd;
|
|
54112
54192
|
try {
|
|
54113
|
-
fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW);
|
|
54193
|
+
fd = openSync8(path10, constants8.O_RDONLY | constants8.O_NOFOLLOW | constants8.O_NONBLOCK);
|
|
54114
54194
|
} catch (error2) {
|
|
54115
|
-
|
|
54116
|
-
|
|
54195
|
+
const errno = error2.code;
|
|
54196
|
+
if (errno === "ENOENT") return void 0;
|
|
54197
|
+
if (errno === "ELOOP")
|
|
54198
|
+
throw new TemplateStoreError(
|
|
54199
|
+
"unsafe-path",
|
|
54200
|
+
`Refusing to read ${path10}: it is a symbolic link.`
|
|
54201
|
+
);
|
|
54202
|
+
throw error2;
|
|
54117
54203
|
}
|
|
54118
54204
|
try {
|
|
54119
54205
|
const stat = fstatSync8(fd);
|
|
@@ -54218,7 +54304,7 @@ var TemplateStore = class {
|
|
|
54218
54304
|
"template-exists",
|
|
54219
54305
|
`A template named "${name}" already exists. Pass force: true to replace it.`
|
|
54220
54306
|
);
|
|
54221
|
-
const temp = join26(this.dir, `.${name}.${
|
|
54307
|
+
const temp = join26(this.dir, `.${name}.${randomBytes2(6).toString("hex")}.tmp`);
|
|
54222
54308
|
const fd = openSync8(
|
|
54223
54309
|
temp,
|
|
54224
54310
|
constants8.O_WRONLY | constants8.O_CREAT | constants8.O_EXCL | constants8.O_NOFOLLOW,
|
|
@@ -54228,14 +54314,20 @@ var TemplateStore = class {
|
|
|
54228
54314
|
const data = Buffer.from(body, "utf8");
|
|
54229
54315
|
let written = 0;
|
|
54230
54316
|
while (written < data.length) written += writeSync5(fd, data, written);
|
|
54231
|
-
}
|
|
54317
|
+
} catch (error2) {
|
|
54232
54318
|
closeSync8(fd);
|
|
54319
|
+
try {
|
|
54320
|
+
unlinkSync4(temp);
|
|
54321
|
+
} catch {
|
|
54322
|
+
}
|
|
54323
|
+
throw error2;
|
|
54233
54324
|
}
|
|
54325
|
+
closeSync8(fd);
|
|
54234
54326
|
try {
|
|
54235
54327
|
if (force) renameSync(temp, path10);
|
|
54236
54328
|
else {
|
|
54237
54329
|
try {
|
|
54238
|
-
|
|
54330
|
+
linkSync2(temp, path10);
|
|
54239
54331
|
} catch (error2) {
|
|
54240
54332
|
if (error2.code === "EEXIST")
|
|
54241
54333
|
throw new TemplateStoreError(
|
|
@@ -54247,7 +54339,7 @@ var TemplateStore = class {
|
|
|
54247
54339
|
}
|
|
54248
54340
|
} finally {
|
|
54249
54341
|
try {
|
|
54250
|
-
|
|
54342
|
+
unlinkSync4(temp);
|
|
54251
54343
|
} catch {
|
|
54252
54344
|
}
|
|
54253
54345
|
}
|
|
@@ -54270,7 +54362,7 @@ var TemplateStore = class {
|
|
|
54270
54362
|
"unsafe-path",
|
|
54271
54363
|
`Refusing to delete ${path10}: not a regular file.`
|
|
54272
54364
|
);
|
|
54273
|
-
|
|
54365
|
+
unlinkSync4(path10);
|
|
54274
54366
|
return { path: path10 };
|
|
54275
54367
|
}
|
|
54276
54368
|
};
|
|
@@ -54656,7 +54748,7 @@ function parseColor(value) {
|
|
|
54656
54748
|
}
|
|
54657
54749
|
function parsePaint(value) {
|
|
54658
54750
|
const v = value.trim();
|
|
54659
|
-
if (v
|
|
54751
|
+
if (/^none$/i.test(v)) return { kind: "none" };
|
|
54660
54752
|
if (/^currentcolor$/i.test(v)) return { kind: "current" };
|
|
54661
54753
|
const url = /^url\(\s*['"]?([^'")]*)['"]?\s*\)/i.exec(v);
|
|
54662
54754
|
if (url) return { kind: "url", target: url[1].trim() };
|
|
@@ -55031,8 +55123,9 @@ function distanceToChord(p, a, b) {
|
|
|
55031
55123
|
if (len === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
|
|
55032
55124
|
return Math.abs((p[0] - a[0]) * dy - (p[1] - a[1]) * dx) / len;
|
|
55033
55125
|
}
|
|
55034
|
-
function flattenCubic(p0, c1, c2, p3, tolerance, out, depth = 0) {
|
|
55126
|
+
function flattenCubic(p0, c1, c2, p3, tolerance, out, depth = 0, charge) {
|
|
55035
55127
|
if (depth >= 16 || Math.max(distanceToChord(c1, p0, p3), distanceToChord(c2, p0, p3)) <= tolerance) {
|
|
55128
|
+
charge?.();
|
|
55036
55129
|
out.push(p3);
|
|
55037
55130
|
return;
|
|
55038
55131
|
}
|
|
@@ -55043,32 +55136,48 @@ function flattenCubic(p0, c1, c2, p3, tolerance, out, depth = 0) {
|
|
|
55043
55136
|
const d = mid(a, b);
|
|
55044
55137
|
const e = mid(b, c);
|
|
55045
55138
|
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);
|
|
55139
|
+
flattenCubic(p0, a, d, m, tolerance, out, depth + 1, charge);
|
|
55140
|
+
flattenCubic(m, e, c, p3, tolerance, out, depth + 1, charge);
|
|
55048
55141
|
}
|
|
55049
|
-
function flattenSubpath(sub4, m, tolerance) {
|
|
55142
|
+
function flattenSubpath(sub4, m, tolerance, charge) {
|
|
55143
|
+
charge?.();
|
|
55050
55144
|
const points = [apply(m, sub4.start)];
|
|
55051
55145
|
let pen = sub4.start;
|
|
55052
55146
|
for (const seg of sub4.segments) {
|
|
55053
|
-
if (seg.kind === "L")
|
|
55054
|
-
|
|
55147
|
+
if (seg.kind === "L") {
|
|
55148
|
+
charge?.();
|
|
55149
|
+
points.push(apply(m, seg.to));
|
|
55150
|
+
} else
|
|
55055
55151
|
flattenCubic(
|
|
55056
55152
|
apply(m, pen),
|
|
55057
55153
|
apply(m, seg.c1),
|
|
55058
55154
|
apply(m, seg.c2),
|
|
55059
55155
|
apply(m, seg.to),
|
|
55060
55156
|
tolerance,
|
|
55061
|
-
points
|
|
55157
|
+
points,
|
|
55158
|
+
0,
|
|
55159
|
+
charge
|
|
55062
55160
|
);
|
|
55063
55161
|
pen = seg.to;
|
|
55064
55162
|
}
|
|
55065
55163
|
if (sub4.closed) {
|
|
55066
55164
|
const first2 = points[0];
|
|
55067
55165
|
const last = points[points.length - 1];
|
|
55068
|
-
if (last[0] !== first2[0] || last[1] !== first2[1])
|
|
55166
|
+
if (last[0] !== first2[0] || last[1] !== first2[1]) {
|
|
55167
|
+
charge?.();
|
|
55168
|
+
points.push(first2);
|
|
55169
|
+
}
|
|
55069
55170
|
}
|
|
55070
55171
|
return points;
|
|
55071
55172
|
}
|
|
55173
|
+
function subpathControlPoints(sub4, m) {
|
|
55174
|
+
const out = [apply(m, sub4.start)];
|
|
55175
|
+
for (const seg of sub4.segments) {
|
|
55176
|
+
if (seg.kind === "C") out.push(apply(m, seg.c1), apply(m, seg.c2));
|
|
55177
|
+
out.push(apply(m, seg.to));
|
|
55178
|
+
}
|
|
55179
|
+
return out;
|
|
55180
|
+
}
|
|
55072
55181
|
function clipSegment(a, b, r) {
|
|
55073
55182
|
const dx = b[0] - a[0];
|
|
55074
55183
|
const dy = b[1] - a[1];
|
|
@@ -55248,6 +55357,13 @@ var SvgError = class extends Error {
|
|
|
55248
55357
|
code;
|
|
55249
55358
|
location;
|
|
55250
55359
|
};
|
|
55360
|
+
function lookupNamespace(scope2, prefix) {
|
|
55361
|
+
for (let s = scope2; s; s = s.parent) {
|
|
55362
|
+
const ns = s.own?.get(prefix);
|
|
55363
|
+
if (ns !== void 0) return ns;
|
|
55364
|
+
}
|
|
55365
|
+
return void 0;
|
|
55366
|
+
}
|
|
55251
55367
|
var NAME_START = /[A-Za-z_À-]/;
|
|
55252
55368
|
var NAME_CHAR = /[A-Za-z0-9_.\-:·À-]/;
|
|
55253
55369
|
var PREDEFINED = { lt: "<", gt: ">", amp: "&", quot: '"', apos: "'" };
|
|
@@ -55276,6 +55392,9 @@ function parseXml(source, limits) {
|
|
|
55276
55392
|
let pos = 0;
|
|
55277
55393
|
let line = 1;
|
|
55278
55394
|
let elementCount = 0;
|
|
55395
|
+
let namespaceDeclarations = 0;
|
|
55396
|
+
const maxAttributes = limits.maxAttributes ?? 1024;
|
|
55397
|
+
const maxNamespaceDeclarations = limits.maxNamespaceDeclarations ?? 1024;
|
|
55279
55398
|
const stack = [];
|
|
55280
55399
|
let root = null;
|
|
55281
55400
|
const fail = (message) => {
|
|
@@ -55337,6 +55456,7 @@ function parseXml(source, limits) {
|
|
|
55337
55456
|
while (i < source.length && NAME_CHAR.test(source[i])) i++;
|
|
55338
55457
|
const name = source.slice(pos + 1, i);
|
|
55339
55458
|
const rawAttributes = [];
|
|
55459
|
+
const attributeNames = /* @__PURE__ */ new Set();
|
|
55340
55460
|
let selfClosing = false;
|
|
55341
55461
|
for (; ; ) {
|
|
55342
55462
|
const ws = i;
|
|
@@ -55366,8 +55486,13 @@ function parseXml(source, limits) {
|
|
|
55366
55486
|
if (close < 0) fail("Unterminated attribute value");
|
|
55367
55487
|
const rawValue = source.slice(i + 1, close);
|
|
55368
55488
|
if (rawValue.includes("<")) fail("'<' in an attribute value");
|
|
55369
|
-
if (
|
|
55370
|
-
|
|
55489
|
+
if (attributeNames.has(attrName)) fail(`Duplicate attribute ${attrName.slice(0, 40)}`);
|
|
55490
|
+
attributeNames.add(attrName);
|
|
55491
|
+
if (attributeNames.size > maxAttributes)
|
|
55492
|
+
throw new SvgError(
|
|
55493
|
+
"svg_complexity_limit",
|
|
55494
|
+
`An element has more than ${maxAttributes} attributes (line ${line})`
|
|
55495
|
+
);
|
|
55371
55496
|
rawAttributes.push({
|
|
55372
55497
|
name: attrName,
|
|
55373
55498
|
value: decodeEntities(rawValue.replace(/[\t\n\r]/g, " "), line)
|
|
@@ -55384,19 +55509,30 @@ function parseXml(source, limits) {
|
|
|
55384
55509
|
);
|
|
55385
55510
|
if (stack.length + 1 > limits.maxDepth)
|
|
55386
55511
|
throw new SvgError("svg_complexity_limit", `Elements nest deeper than ${limits.maxDepth}`);
|
|
55387
|
-
const parentScope = stack.length ? stack[stack.length - 1].scope :
|
|
55388
|
-
|
|
55512
|
+
const parentScope = stack.length ? stack[stack.length - 1].scope : null;
|
|
55513
|
+
let own = null;
|
|
55389
55514
|
for (const a of rawAttributes) {
|
|
55390
|
-
|
|
55391
|
-
|
|
55515
|
+
const prefix = a.name === "xmlns" ? "" : a.name.startsWith("xmlns:") ? a.name.slice(6) : null;
|
|
55516
|
+
if (prefix === null) continue;
|
|
55517
|
+
if (++namespaceDeclarations > maxNamespaceDeclarations)
|
|
55518
|
+
throw new SvgError(
|
|
55519
|
+
"svg_complexity_limit",
|
|
55520
|
+
`The document declares more than ${maxNamespaceDeclarations} namespaces`
|
|
55521
|
+
);
|
|
55522
|
+
(own ??= /* @__PURE__ */ new Map()).set(prefix, a.value);
|
|
55392
55523
|
}
|
|
55524
|
+
const scope2 = own ? { own, parent: parentScope } : parentScope ?? { own: null, parent: null };
|
|
55393
55525
|
const resolve9 = (qualified, isAttribute) => {
|
|
55394
55526
|
const colon = qualified.indexOf(":");
|
|
55395
|
-
if (colon < 0)
|
|
55527
|
+
if (colon < 0)
|
|
55528
|
+
return {
|
|
55529
|
+
local: qualified,
|
|
55530
|
+
ns: isAttribute ? null : lookupNamespace(scope2, "") ?? null
|
|
55531
|
+
};
|
|
55396
55532
|
const prefix = qualified.slice(0, colon);
|
|
55397
55533
|
const local = qualified.slice(colon + 1);
|
|
55398
55534
|
if (prefix === "xml") return { local, ns: XML_NS };
|
|
55399
|
-
const ns = scope2
|
|
55535
|
+
const ns = lookupNamespace(scope2, prefix);
|
|
55400
55536
|
if (ns === void 0) fail(`Undeclared namespace prefix "${prefix.slice(0, 40)}"`);
|
|
55401
55537
|
return { local, ns };
|
|
55402
55538
|
};
|
|
@@ -55424,6 +55560,8 @@ var SVG_ANALYZER_VERSION = "apple-notes-mcp/svg-analyzer@1";
|
|
|
55424
55560
|
var SVG_LIMITS = {
|
|
55425
55561
|
maxSourceBytes: 1048576,
|
|
55426
55562
|
maxSourceElements: 16384,
|
|
55563
|
+
maxAttributesPerElement: 1024,
|
|
55564
|
+
maxNamespaceDeclarations: 1024,
|
|
55427
55565
|
maxExpandedElements: 16384,
|
|
55428
55566
|
maxReferenceExpansions: 8192,
|
|
55429
55567
|
maxDepth: 64,
|
|
@@ -55595,7 +55733,7 @@ function attr(el, local) {
|
|
|
55595
55733
|
return el.attributes.find((a) => a.local === local && a.ns === null)?.value;
|
|
55596
55734
|
}
|
|
55597
55735
|
function href(el) {
|
|
55598
|
-
return el.attributes.find((a) => a.local === "href" &&
|
|
55736
|
+
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
55737
|
}
|
|
55600
55738
|
function parseStyleAttribute(value) {
|
|
55601
55739
|
const out = [];
|
|
@@ -55611,7 +55749,24 @@ function parseStyleAttribute(value) {
|
|
|
55611
55749
|
function hasText(el) {
|
|
55612
55750
|
return el.text.trim().length > 0 || el.children.some(hasText);
|
|
55613
55751
|
}
|
|
55614
|
-
var ACTIVE_SCHEME =
|
|
55752
|
+
var ACTIVE_SCHEME = /^(?:javascript|vbscript|data)\s*:/i;
|
|
55753
|
+
function urlAsParsed(value) {
|
|
55754
|
+
return value.replace(/[\t\n\r]/g, "").replace(/^[\x00-\x20]+|[\x00-\x20]+$/g, "");
|
|
55755
|
+
}
|
|
55756
|
+
function unescapeCss(value) {
|
|
55757
|
+
if (!value.includes("\\")) return value;
|
|
55758
|
+
return value.replace(
|
|
55759
|
+
/\\(?:([0-9A-Fa-f]{1,6})(?:\r\n|[ \t\n\r\f])?|(\r\n|[\n\r\f])|([\s\S]))/g,
|
|
55760
|
+
(_m, hex3, newline, other) => {
|
|
55761
|
+
if (hex3 !== void 0) {
|
|
55762
|
+
const code = Number.parseInt(hex3, 16);
|
|
55763
|
+
return code === 0 || code > 1114111 || code >= 55296 && code <= 57343 ? "\uFFFD" : String.fromCodePoint(code);
|
|
55764
|
+
}
|
|
55765
|
+
if (newline !== void 0) return "";
|
|
55766
|
+
return other;
|
|
55767
|
+
}
|
|
55768
|
+
);
|
|
55769
|
+
}
|
|
55615
55770
|
function assertInert(el, location) {
|
|
55616
55771
|
if (UNSAFE_ELEMENTS.has(el.local))
|
|
55617
55772
|
throw new SvgError(
|
|
@@ -55628,7 +55783,7 @@ function assertInert(el, location) {
|
|
|
55628
55783
|
);
|
|
55629
55784
|
const value = a.value;
|
|
55630
55785
|
if (a.local === "href" && (a.ns === null || a.ns === XLINK_NS)) {
|
|
55631
|
-
const target = value
|
|
55786
|
+
const target = urlAsParsed(value);
|
|
55632
55787
|
if (el.local === "a") {
|
|
55633
55788
|
if (ACTIVE_SCHEME.test(target))
|
|
55634
55789
|
throw new SvgError("svg_unsafe", "Link uses an active URL scheme", location);
|
|
@@ -55641,10 +55796,11 @@ function assertInert(el, location) {
|
|
|
55641
55796
|
);
|
|
55642
55797
|
}
|
|
55643
55798
|
}
|
|
55644
|
-
|
|
55645
|
-
|
|
55799
|
+
const css = unescapeCss(value);
|
|
55800
|
+
for (const m of css.matchAll(/url\(\s*['"]?([^'")]*)/gi))
|
|
55801
|
+
if (!urlAsParsed(m[1]).startsWith("#"))
|
|
55646
55802
|
throw new SvgError("svg_unsafe", "url() references must be local (#id)", location);
|
|
55647
|
-
if (a.local === "style" && /@import|expression\s*\(|javascript\s*:/i.test(
|
|
55803
|
+
if (a.local === "style" && /@import|expression\s*\(|javascript\s*:/i.test(urlAsParsed(css)))
|
|
55648
55804
|
throw new SvgError("svg_unsafe", "Active content in a style attribute", location);
|
|
55649
55805
|
}
|
|
55650
55806
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -55726,7 +55882,8 @@ var Analyzer = class {
|
|
|
55726
55882
|
if (styleAttr) declarations.push(...parseStyleAttribute(styleAttr));
|
|
55727
55883
|
const diag = Math.hypot(ctx.viewport[0], ctx.viewport[1]) / Math.SQRT2;
|
|
55728
55884
|
for (const [name, value] of declarations) {
|
|
55729
|
-
|
|
55885
|
+
const keyword = value.toLowerCase();
|
|
55886
|
+
if (keyword === "inherit") continue;
|
|
55730
55887
|
const invalid2 = () => this.issue(
|
|
55731
55888
|
"invalid_value",
|
|
55732
55889
|
null,
|
|
@@ -55765,23 +55922,23 @@ var Analyzer = class {
|
|
|
55765
55922
|
break;
|
|
55766
55923
|
}
|
|
55767
55924
|
case "fill-rule":
|
|
55768
|
-
if (
|
|
55925
|
+
if (keyword === "evenodd" || keyword === "nonzero") style.evenOdd = keyword === "evenodd";
|
|
55769
55926
|
else invalid2();
|
|
55770
55927
|
break;
|
|
55771
55928
|
case "visibility":
|
|
55772
|
-
style.visible =
|
|
55929
|
+
style.visible = keyword === "visible";
|
|
55773
55930
|
break;
|
|
55774
55931
|
case "display":
|
|
55775
|
-
|
|
55932
|
+
display = keyword !== "none";
|
|
55776
55933
|
break;
|
|
55777
55934
|
case "stroke-linecap":
|
|
55778
|
-
style.linecap =
|
|
55935
|
+
style.linecap = keyword;
|
|
55779
55936
|
break;
|
|
55780
55937
|
case "stroke-linejoin":
|
|
55781
|
-
style.linejoin =
|
|
55938
|
+
style.linejoin = keyword;
|
|
55782
55939
|
break;
|
|
55783
55940
|
case "stroke-dasharray": {
|
|
55784
|
-
if (
|
|
55941
|
+
if (keyword === "none") {
|
|
55785
55942
|
style.dasharray = null;
|
|
55786
55943
|
break;
|
|
55787
55944
|
}
|
|
@@ -55802,7 +55959,7 @@ var Analyzer = class {
|
|
|
55802
55959
|
case "overflow":
|
|
55803
55960
|
break;
|
|
55804
55961
|
case "mix-blend-mode":
|
|
55805
|
-
if (
|
|
55962
|
+
if (keyword !== "normal")
|
|
55806
55963
|
this.issue(
|
|
55807
55964
|
"blend_mode_ignored",
|
|
55808
55965
|
"paint-approximation",
|
|
@@ -55811,7 +55968,7 @@ var Analyzer = class {
|
|
|
55811
55968
|
);
|
|
55812
55969
|
break;
|
|
55813
55970
|
case "paint-order":
|
|
55814
|
-
if (
|
|
55971
|
+
if (keyword !== "normal" && !/^fill(\s+stroke)?(\s+markers)?$/.test(keyword))
|
|
55815
55972
|
this.issue(
|
|
55816
55973
|
"paint_order_ignored",
|
|
55817
55974
|
"paint-approximation",
|
|
@@ -55821,7 +55978,7 @@ var Analyzer = class {
|
|
|
55821
55978
|
break;
|
|
55822
55979
|
default:
|
|
55823
55980
|
if (DROPPED_MODIFIERS.has(name)) {
|
|
55824
|
-
if (
|
|
55981
|
+
if (keyword !== "none")
|
|
55825
55982
|
this.issue(
|
|
55826
55983
|
"modifier_dropped",
|
|
55827
55984
|
"drop-content",
|
|
@@ -55843,7 +56000,7 @@ var Analyzer = class {
|
|
|
55843
56000
|
propertyValue(el, name) {
|
|
55844
56001
|
const styleAttr = attr(el, "style");
|
|
55845
56002
|
const fromStyle = styleAttr ? parseStyleAttribute(styleAttr).filter(([n]) => n === name).pop() : void 0;
|
|
55846
|
-
return fromStyle ? fromStyle[1] : attr(el, name);
|
|
56003
|
+
return (fromStyle ? fromStyle[1] : attr(el, name))?.trim().toLowerCase();
|
|
55847
56004
|
}
|
|
55848
56005
|
run() {
|
|
55849
56006
|
const root = this.root;
|
|
@@ -56243,9 +56400,7 @@ var Analyzer = class {
|
|
|
56243
56400
|
if (!geometry || !ctx.style.visible) return;
|
|
56244
56401
|
const { style } = ctx;
|
|
56245
56402
|
const polylines = geometry.subpaths.filter((s) => s.segments.length > 0).map((s) => {
|
|
56246
|
-
|
|
56247
|
-
this.charge("geometryWork", pts.length);
|
|
56248
|
-
if (pts.some(
|
|
56403
|
+
if (subpathControlPoints(s, ctx.matrix).some(
|
|
56249
56404
|
(p) => !(Math.abs(p[0]) <= SVG_LIMITS.maxCoordinate && Math.abs(p[1]) <= SVG_LIMITS.maxCoordinate)
|
|
56250
56405
|
))
|
|
56251
56406
|
throw new SvgError(
|
|
@@ -56253,7 +56408,7 @@ var Analyzer = class {
|
|
|
56253
56408
|
"Geometry is not finite or exceeds the coordinate limit",
|
|
56254
56409
|
ctx.location
|
|
56255
56410
|
);
|
|
56256
|
-
return
|
|
56411
|
+
return flattenSubpath(s, ctx.matrix, TOLERANCE, () => this.charge("geometryWork", 1));
|
|
56257
56412
|
});
|
|
56258
56413
|
if (!polylines.length) return;
|
|
56259
56414
|
const fill = this.paintColor(style.fill, style);
|
|
@@ -56409,7 +56564,9 @@ function analyzeSvgBuffer(source) {
|
|
|
56409
56564
|
if (text2.charCodeAt(0) === 65279) text2 = text2.slice(1);
|
|
56410
56565
|
const root = parseXml(text2, {
|
|
56411
56566
|
maxElements: SVG_LIMITS.maxSourceElements,
|
|
56412
|
-
maxDepth: SVG_LIMITS.maxDepth
|
|
56567
|
+
maxDepth: SVG_LIMITS.maxDepth,
|
|
56568
|
+
maxAttributes: SVG_LIMITS.maxAttributesPerElement,
|
|
56569
|
+
maxNamespaceDeclarations: SVG_LIMITS.maxNamespaceDeclarations
|
|
56413
56570
|
});
|
|
56414
56571
|
const analyzer = new Analyzer(root);
|
|
56415
56572
|
const viewport = analyzer.run();
|
|
@@ -58980,7 +59137,7 @@ registerTool(
|
|
|
58980
59137
|
'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
59138
|
),
|
|
58982
59139
|
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.`
|
|
59140
|
+
`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
59141
|
),
|
|
58985
59142
|
format: external_exports.enum(["plaintext", "html", "markdown"]).optional().default("plaintext").describe(
|
|
58986
59143
|
"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."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apple-notes-mcp",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.24",
|
|
4
4
|
"description": "MCP server for Apple Notes - read, search, create, edit, organize, and export notes on macOS via Claude and other AI assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|