apple-notes-mcp 2.8.32 → 2.8.33
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 +35 -4
- package/build/index.js +181 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -332,13 +332,42 @@ title line, with no seed line.
|
|
|
332
332
|
It also refuses Markdown that Notes would rewrite and the server could not
|
|
333
333
|
verify: `_` emphasis (underscores inside a word, as in `snake_case`, and
|
|
334
334
|
inside a link destination, as in `[docs](https://example.com/_next/static)`,
|
|
335
|
-
are fine), backslash escapes, character references such as `&`,
|
|
336
|
-
|
|
335
|
+
are fine), backslash escapes, character references such as `&`, `===` lines
|
|
336
|
+
and rule lines other than the `---` divider described below, indented headings or list items, `1)` lists, closing `#`s, and
|
|
337
337
|
formatting inside link labels. Content that needs one of these literally, such
|
|
338
338
|
as a `/_next` path or a literal `\*`, has no Markdown form here: use
|
|
339
339
|
`format: "html"` for that note (or `append-native` with `format: "html"` on an
|
|
340
340
|
existing one), which keeps the characters but not the Heading and Subheading
|
|
341
341
|
styles. Markdown punctuation in `title` is escaped, so the title stays literal.
|
|
342
|
+
- Notes' importer also maps these block constructs to native styles. They are
|
|
343
|
+
gated separately, and `get-capabilities` reports them as
|
|
344
|
+
`create-note-markdown-blocks`. Each mapping below was live-verified on
|
|
345
|
+
macOS 27.2 by reading the created note's stored styles.
|
|
346
|
+
|
|
347
|
+
| Markdown | Native result |
|
|
348
|
+
|----------|---------------|
|
|
349
|
+
| `- [ ] item` / `- [x] item` | Checklist item, unchecked / checked |
|
|
350
|
+
| `> text` (consecutive lines form one quote) | Body paragraph with a block quote |
|
|
351
|
+
| A fence of bare ```` ``` ```` lines (no language) | Monospaced paragraphs; the code text is kept literally |
|
|
352
|
+
| `---` on its own line, after a blank line | Divider line |
|
|
353
|
+
| `` `inline code` `` | **Highlighted** text, not monospace |
|
|
354
|
+
|
|
355
|
+
Constructs Notes would not render faithfully, or that this server cannot yet
|
|
356
|
+
verify, are refused before anything is created: `~~~` fences, a language
|
|
357
|
+
after the opening fence, nested (`>>`) or indented quotes, lists or headings
|
|
358
|
+
inside a quote, a quote followed directly by text (Markdown would join that
|
|
359
|
+
text to the quote), `---` directly under text (Markdown would make that text
|
|
360
|
+
a heading), `***`/`___`/`----` rules, `[X]`, `* [ ]` or `+ [ ]` items, checklist
|
|
361
|
+
items directly next to ordinary list items, inline code padded with spaces or
|
|
362
|
+
inside a link label, tables, and `~~strikethrough~~`. The readback checks the
|
|
363
|
+
block-quote text, the Monospaced text, each checklist item's text and done
|
|
364
|
+
state, the divider count, and the highlighted text. These mappings apply to
|
|
365
|
+
the default Shortcut route only; see
|
|
366
|
+
[Markdown through HTML](#markdown-through-html) for `markdownRoute: "html"`.
|
|
367
|
+
- [`append-native`](#append-native)'s `format: "markdown"` refuses all of the
|
|
368
|
+
constructs above: its Shortcuts converter is a different one, which renders
|
|
369
|
+
a quote and a fenced block as plain body text, `- [ ]` as a bullet with
|
|
370
|
+
literal brackets, and inline code as plain text, and drops `---`.
|
|
342
371
|
- The server finds the new note among the notes added to the default folder
|
|
343
372
|
during the run by verifying each one's visible text, heading levels and links
|
|
344
373
|
by exact-ID readback, and moves it only after exactly one verifies. On any
|
|
@@ -366,7 +395,9 @@ On this route, bullet task items (`- [ ] item`, `- [x] item`) become ordinary
|
|
|
366
395
|
list rows that start with a visible `☐` or `☑` character, and the response
|
|
367
396
|
reports how many as `taskItemsRendered`. They are text, not native checkable
|
|
368
397
|
checklist items. Use this route as the glyph fallback when the Shortcut is not
|
|
369
|
-
installed or the note is outside iCloud.
|
|
398
|
+
installed or the note is outside iCloud. Block quotes, fenced code and inline code, which
|
|
399
|
+
the Shortcut route maps natively, are refused on this route, and a `---` line
|
|
400
|
+
stays literal text rather than becoming a divider.
|
|
370
401
|
|
|
371
402
|
```json
|
|
372
403
|
{
|
|
@@ -1870,7 +1901,7 @@ Apple Notes stores checklists as a paragraph style (`style_type=103`) inside a g
|
|
|
1870
1901
|
**Workarounds:**
|
|
1871
1902
|
|
|
1872
1903
|
1. **Create the note with bulleted list items, then convert manually in Notes.app.** Select the items and press <kbd>⇧⌘L</kbd> (or **Format → Checklist**). This converts the list in place and the resulting checklist will be readable by `get-checklist-state` and annotated by `get-note-markdown`.
|
|
1873
|
-
2. **Use the Apple Shortcuts app** to script the checklist creation, since Shortcuts can manipulate Notes content at a higher level than AppleScript.
|
|
1904
|
+
2. **Use the Apple Shortcuts app** to script the checklist creation, since Shortcuts can manipulate Notes content at a higher level than AppleScript. This server does that for you in two ways: [`create-checklist-item`](#create-checklist-item) appends one unchecked item to an existing note, and [`create-note`](#create-note) with `format: "markdown"` turns `- [ ]` / `- [x]` lines into checklist items with that done state through Notes' own Markdown importer, (see [Markdown notes](#markdown-notes)).
|
|
1874
1905
|
3. **Read-only checklist support is fully implemented** — once a checklist exists (created manually or by another app), `get-checklist-state` and `get-note-markdown` will read its done/undone state correctly (with Full Disk Access).
|
|
1875
1906
|
|
|
1876
1907
|
If you need to *track* todos programmatically and don't strictly need them rendered as Apple Notes checklist UI, plain markdown-style `- [ ] item` / `- [x] item` lines in a `plaintext` note are a reasonable alternative — they are searchable, human-readable, and can be parsed by downstream tooling.
|
package/build/index.js
CHANGED
|
@@ -39675,7 +39675,11 @@ function parseRichNote(data, nativeTags = []) {
|
|
|
39675
39675
|
const length = varintValue(getField(fields, 1));
|
|
39676
39676
|
if (length === void 0 || length < 0 || position + length > text.length)
|
|
39677
39677
|
throw new Error("Invalid Notes run length");
|
|
39678
|
+
const paragraph = embeddedMessage(getField(fields, 2));
|
|
39678
39679
|
styleRuns.push({
|
|
39680
|
+
paragraphStyle: paragraph ? varintValue(getField(paragraph, 1)) ?? 3 : 3,
|
|
39681
|
+
blockQuote: Boolean(paragraph && varintValue(getField(paragraph, 8))),
|
|
39682
|
+
highlight: Boolean(varintValue(getField(fields, 14))),
|
|
39679
39683
|
start: position,
|
|
39680
39684
|
length,
|
|
39681
39685
|
signature: JSON.stringify(
|
|
@@ -39703,7 +39707,6 @@ function parseRichNote(data, nativeTags = []) {
|
|
|
39703
39707
|
start: position,
|
|
39704
39708
|
length
|
|
39705
39709
|
});
|
|
39706
|
-
const paragraph = embeddedMessage(getField(fields, 2));
|
|
39707
39710
|
hasChecklist ||= Boolean(paragraph && varintValue(getField(paragraph, 1)) === 103);
|
|
39708
39711
|
if (paragraph && varintValue(getField(paragraph, 1)) === 103) {
|
|
39709
39712
|
const checklist = embeddedMessage(getField(paragraph, 5));
|
|
@@ -45056,40 +45059,152 @@ function countTaskItems(markdown) {
|
|
|
45056
45059
|
return item !== null && TASK_ITEM.test(item[1]);
|
|
45057
45060
|
}).length;
|
|
45058
45061
|
}
|
|
45059
|
-
|
|
45060
|
-
|
|
45062
|
+
var FENCE = "```";
|
|
45063
|
+
var CHECKLIST_ITEM = /^- \[( |x)\] (.+)$/;
|
|
45064
|
+
function withoutMarkdownCode(markdown) {
|
|
45065
|
+
let inCode = false;
|
|
45066
|
+
return markdown.replace(/\r\n/g, "\n").split("\n").map((line) => {
|
|
45067
|
+
if (line === FENCE) {
|
|
45068
|
+
inCode = !inCode;
|
|
45069
|
+
return "";
|
|
45070
|
+
}
|
|
45071
|
+
return inCode ? "" : line.replace(/`[^`\n]+`/g, "x");
|
|
45072
|
+
}).join("\n");
|
|
45073
|
+
}
|
|
45074
|
+
function usesMarkdownBlocks(markdown) {
|
|
45075
|
+
return /`/.test(markdown) || /^>/m.test(markdown) || /^---[ \t]*$/m.test(markdown) || /^- \[[ x]\] /m.test(markdown);
|
|
45076
|
+
}
|
|
45077
|
+
function renderMarkdown(markdown, options = {}) {
|
|
45078
|
+
const blocks = options.blocks === true;
|
|
45079
|
+
if (blocks ? /[-]/u.test(markdown) : /[]/u.test(markdown))
|
|
45061
45080
|
throw new Error("Unsupported reserved characters");
|
|
45062
45081
|
if (Array.from(markdown).some((c) => c.charCodeAt(0) < 32 && !["\n", "\r", " "].includes(c)))
|
|
45063
45082
|
throw new Error("Unsupported control characters");
|
|
45064
|
-
|
|
45083
|
+
const checked = blocks ? withoutMarkdownCode(markdown) : markdown;
|
|
45084
|
+
if (blocks ? /[`~|]|^#{4,}\s|^[ \t]+>|^>[ \t]*>|^\s*\[.+\]:|!\[|<\/?[a-z]/im.test(checked) : /[`~|]|^#{4,}\s|^\s*>|^\s*\[.+\]:|!\[|<\/?[a-z]/im.test(checked))
|
|
45065
45085
|
throw new Error(
|
|
45066
|
-
"Markdown append supports paragraphs, headings, flat lists, emphasis and inline links; use semantic HTML for other formatting"
|
|
45086
|
+
blocks ? "Markdown import supports paragraphs, headings, flat lists, checklist items, block quotes, fenced code, dividers, emphasis, inline code and inline links; use semantic HTML for other formatting" : "Markdown append supports paragraphs, headings, flat lists, emphasis and inline links; use semantic HTML for other formatting"
|
|
45067
45087
|
);
|
|
45088
|
+
const expect = {
|
|
45089
|
+
quotes: [],
|
|
45090
|
+
code: [],
|
|
45091
|
+
checklist: [],
|
|
45092
|
+
dividers: 0,
|
|
45093
|
+
highlights: []
|
|
45094
|
+
};
|
|
45068
45095
|
const inline = (text) => {
|
|
45096
|
+
const spans = [];
|
|
45097
|
+
let value = text;
|
|
45098
|
+
if (blocks)
|
|
45099
|
+
value = value.replace(/`([^`\n]+)`/g, (_s, code2) => {
|
|
45100
|
+
if (/^\s|\s$/.test(code2)) throw new Error("Inline code cannot start or end with a space");
|
|
45101
|
+
expect.highlights.push(code2);
|
|
45102
|
+
spans.push(`<code>${escape2(code2)}</code>`);
|
|
45103
|
+
return `\uE002${spans.length - 1}\uE003`;
|
|
45104
|
+
});
|
|
45069
45105
|
const links = [];
|
|
45070
|
-
|
|
45106
|
+
value = value.replace(/\[([^\]\n]+)\]\(([^()\s]+)\)/g, (_s, label, url) => {
|
|
45071
45107
|
if (!/^(https?:\/\/|notes:\/\/|applenotes:|mailto:)/i.test(url))
|
|
45072
45108
|
throw new Error("Unsupported Markdown link URL");
|
|
45109
|
+
if (/[]/u.test(label)) throw new Error("Inline code inside a link is unsupported");
|
|
45073
45110
|
links.push(`<a href="${escape2(url)}">${escape2(label)}</a>`);
|
|
45074
45111
|
return `\uE000${links.length - 1}\uE001`;
|
|
45075
45112
|
});
|
|
45076
45113
|
value = escape2(value).replace(/\*\*([^*\n]+)\*\*/g, "<b>$1</b>").replace(/\*([^*\n]+)\*/g, "<i>$1</i>");
|
|
45077
45114
|
if (value.includes("[") || value.includes("]") || value.includes("*"))
|
|
45078
45115
|
throw new Error("Unsupported or unbalanced Markdown inline syntax");
|
|
45079
|
-
return value.replace(
|
|
45116
|
+
return value.replace(/(\d+)/g, (_s, i) => links[Number(i)]).replace(/(\d+)/g, (_s, i) => spans[Number(i)]);
|
|
45117
|
+
};
|
|
45118
|
+
const withoutTags = (html2) => {
|
|
45119
|
+
let text = "", inTag = false;
|
|
45120
|
+
for (const ch of html2) {
|
|
45121
|
+
if (ch === "<") inTag = true;
|
|
45122
|
+
else if (ch === ">" && inTag) inTag = false;
|
|
45123
|
+
else if (!inTag) text += ch;
|
|
45124
|
+
}
|
|
45125
|
+
return text;
|
|
45080
45126
|
};
|
|
45081
|
-
|
|
45127
|
+
const visible = (html2) => withoutTags(html2).replace(/"/g, '"').replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
45128
|
+
let html = "", list, quote = false, code, previousBlank = true;
|
|
45082
45129
|
const close = () => {
|
|
45083
45130
|
if (list) {
|
|
45084
|
-
html += `</${list}>`;
|
|
45131
|
+
html += list === "checklist" ? "</ul>" : `</${list}>`;
|
|
45085
45132
|
list = void 0;
|
|
45086
45133
|
}
|
|
45134
|
+
if (quote) {
|
|
45135
|
+
html += "</blockquote>";
|
|
45136
|
+
quote = false;
|
|
45137
|
+
}
|
|
45087
45138
|
};
|
|
45088
45139
|
for (const line of markdown.replace(/\r\n/g, "\n").split("\n")) {
|
|
45140
|
+
if (code) {
|
|
45141
|
+
if (line === FENCE) {
|
|
45142
|
+
if (!code.length) throw new Error("Empty fenced code blocks are unsupported");
|
|
45143
|
+
html += `<pre>${escape2(code.join("\n"))}</pre>`;
|
|
45144
|
+
expect.code.push(code.join("\n"));
|
|
45145
|
+
code = void 0;
|
|
45146
|
+
previousBlank = false;
|
|
45147
|
+
} else code.push(line);
|
|
45148
|
+
continue;
|
|
45149
|
+
}
|
|
45150
|
+
const blank = !line.trim();
|
|
45151
|
+
if (blocks) {
|
|
45152
|
+
if (quote && !blank && !line.startsWith(">"))
|
|
45153
|
+
throw new Error("End a block quote with a blank line before other text");
|
|
45154
|
+
if (line.startsWith(FENCE)) {
|
|
45155
|
+
if (line !== FENCE)
|
|
45156
|
+
throw new Error("Fenced code must open and close with a bare ``` line (no language)");
|
|
45157
|
+
close();
|
|
45158
|
+
code = [];
|
|
45159
|
+
continue;
|
|
45160
|
+
}
|
|
45161
|
+
if (line.startsWith(">")) {
|
|
45162
|
+
const text = line.replace(/^> ?/, "");
|
|
45163
|
+
if (!text.trim()) throw new Error("Empty block quote lines are unsupported");
|
|
45164
|
+
if (/^(?:#|[-+*][ \t]|\d+[.)][ \t]|>|```)/.test(text))
|
|
45165
|
+
throw new Error("Block quotes support text and inline formatting only");
|
|
45166
|
+
if (!quote) {
|
|
45167
|
+
close();
|
|
45168
|
+
html += "<blockquote>";
|
|
45169
|
+
quote = true;
|
|
45170
|
+
expect.quotes.push("");
|
|
45171
|
+
}
|
|
45172
|
+
const rendered = inline(text);
|
|
45173
|
+
html += `<div>${rendered}</div>`;
|
|
45174
|
+
expect.quotes[expect.quotes.length - 1] += ` ${visible(rendered)}`;
|
|
45175
|
+
previousBlank = false;
|
|
45176
|
+
continue;
|
|
45177
|
+
}
|
|
45178
|
+
if (/^---[ \t]*$/.test(line)) {
|
|
45179
|
+
if (!previousBlank) throw new Error("Put a blank line before a --- divider");
|
|
45180
|
+
close();
|
|
45181
|
+
html += "<hr>";
|
|
45182
|
+
expect.dividers++;
|
|
45183
|
+
previousBlank = false;
|
|
45184
|
+
continue;
|
|
45185
|
+
}
|
|
45186
|
+
const task = CHECKLIST_ITEM.exec(line);
|
|
45187
|
+
if (task) {
|
|
45188
|
+
if (list && list !== "checklist")
|
|
45189
|
+
throw new Error("Separate checklist items from other list items with a blank line");
|
|
45190
|
+
if (list !== "checklist") {
|
|
45191
|
+
close();
|
|
45192
|
+
list = "checklist";
|
|
45193
|
+
html += '<ul class="checklist">';
|
|
45194
|
+
}
|
|
45195
|
+
const rendered = inline(task[2]);
|
|
45196
|
+
html += `<li>${rendered}</li>`;
|
|
45197
|
+
expect.checklist.push({ text: visible(rendered).trim(), done: task[1] === "x" });
|
|
45198
|
+
previousBlank = false;
|
|
45199
|
+
continue;
|
|
45200
|
+
}
|
|
45201
|
+
}
|
|
45089
45202
|
if (/^\s{2,}\S/.test(line)) throw new Error("Nested lists and indented code are unsupported");
|
|
45090
45203
|
const heading = /^(#{1,3})\s+(.+)$/.exec(line), item = /^(?:([-+*])|\d+\.)\s+(.+)$/.exec(line);
|
|
45091
45204
|
if (item) {
|
|
45092
45205
|
const kind = item[1] ? "ul" : "ol";
|
|
45206
|
+
if (list === "checklist")
|
|
45207
|
+
throw new Error("Separate checklist items from other list items with a blank line");
|
|
45093
45208
|
if (list !== kind) {
|
|
45094
45209
|
close();
|
|
45095
45210
|
list = kind;
|
|
@@ -45097,13 +45212,20 @@ function appendMarkdownHtml(markdown, options = {}) {
|
|
|
45097
45212
|
}
|
|
45098
45213
|
const task = options.taskGlyphs && item[1] ? TASK_ITEM.exec(item[2]) : null;
|
|
45099
45214
|
html += task ? `<li>${task[1] === " " ? TASK_GLYPHS.open : TASK_GLYPHS.done} ${inline(task[2])}</li>` : `<li>${inline(item[2])}</li>`;
|
|
45215
|
+
previousBlank = false;
|
|
45100
45216
|
continue;
|
|
45101
45217
|
}
|
|
45102
45218
|
close();
|
|
45103
|
-
html += heading ? `<h${heading[1].length}>${inline(heading[2])}</h${heading[1].length}>` :
|
|
45219
|
+
html += heading ? `<h${heading[1].length}>${inline(heading[2])}</h${heading[1].length}>` : !blank ? `<div>${inline(line)}</div>` : "<div><br></div>";
|
|
45220
|
+
previousBlank = blank;
|
|
45104
45221
|
}
|
|
45222
|
+
if (code) throw new Error("Unclosed fenced code block");
|
|
45105
45223
|
close();
|
|
45106
|
-
|
|
45224
|
+
expect.quotes = expect.quotes.map((q) => q.trim());
|
|
45225
|
+
return { html, expect };
|
|
45226
|
+
}
|
|
45227
|
+
function appendMarkdownHtml(markdown, options = {}) {
|
|
45228
|
+
return renderMarkdown(markdown, { taskGlyphs: options.taskGlyphs }).html;
|
|
45107
45229
|
}
|
|
45108
45230
|
|
|
45109
45231
|
// src/utils/noteRevision.ts
|
|
@@ -45197,7 +45319,7 @@ var UNMODELED_MARKDOWN = [
|
|
|
45197
45319
|
[/^#{1,3}[ \t].*[ \t]#+[ \t]*$/m, "closing # sequences"],
|
|
45198
45320
|
[/\[[^\]\n]*[*_][^\]\n]*\]\(/m, "formatting inside link labels"]
|
|
45199
45321
|
];
|
|
45200
|
-
function validateAppendContent(content, format) {
|
|
45322
|
+
function validateAppendContent(content, format, options = {}) {
|
|
45201
45323
|
if (!content || content.length > 1024 * 1024 || content.includes("\0"))
|
|
45202
45324
|
throw new Error("Invalid append content (limit 1 MiB)");
|
|
45203
45325
|
if (format === "html") {
|
|
@@ -45227,11 +45349,12 @@ function validateAppendContent(content, format) {
|
|
|
45227
45349
|
if (/<!--|<!|<\?|<[^>]*$/u.test(content)) throw new Error("Unsupported HTML markup");
|
|
45228
45350
|
}
|
|
45229
45351
|
if (format === "markdown") {
|
|
45230
|
-
|
|
45352
|
+
const checked = options.blocks ? withoutMarkdownCode(content).replace(/^---[ \t]*$/gm, "") : content;
|
|
45353
|
+
if (/!\[|<\/?[a-z]|\]\(\s*(?:javascript|data|file):/i.test(checked))
|
|
45231
45354
|
throw new Error("Markdown images, raw HTML and local/executable links are unsupported");
|
|
45232
|
-
const withoutLinkDestinations =
|
|
45355
|
+
const withoutLinkDestinations = checked.replace(/(\[[^\]\n]+\])\([^()\s]+\)/g, "$1()");
|
|
45233
45356
|
for (const [pattern, name] of UNMODELED_MARKDOWN)
|
|
45234
|
-
if (pattern.test(name === UNDERSCORES_OUTSIDE_A_WORD ? withoutLinkDestinations :
|
|
45357
|
+
if (pattern.test(name === UNDERSCORES_OUTSIDE_A_WORD ? withoutLinkDestinations : checked))
|
|
45235
45358
|
throw new Error(
|
|
45236
45359
|
`Markdown cannot use ${name}; Notes would change that text, so the result could not be verified`
|
|
45237
45360
|
);
|
|
@@ -45455,10 +45578,42 @@ var literalMarkdown = (text) => text.replace(/[!-/:-@[-`{-~]/g, "\\$&");
|
|
|
45455
45578
|
function headingLevels(html) {
|
|
45456
45579
|
return [...html.matchAll(/<h([1-3])\b[^>]*>([\s\S]*?)<\/h\1>/gi)].filter((match) => comparableVisibleText(match[2])).map((match) => Number(match[1]));
|
|
45457
45580
|
}
|
|
45581
|
+
var DIVIDER_UTI = "com.apple.notes.inlinetextattachment.dividerline";
|
|
45582
|
+
function assertMarkdownBlocks(rich, expected) {
|
|
45583
|
+
const tidy = (s) => s.replace(/[\s]+/gu, " ").trim();
|
|
45584
|
+
const runs = rich.styleRuns || [];
|
|
45585
|
+
const styledText = (matches) => {
|
|
45586
|
+
let out = "", previous = false;
|
|
45587
|
+
for (const run of runs) {
|
|
45588
|
+
const hit = matches(run);
|
|
45589
|
+
if (hit) out += (previous ? "" : " ") + rich.text.slice(run.start, run.start + run.length);
|
|
45590
|
+
previous = hit;
|
|
45591
|
+
}
|
|
45592
|
+
return tidy(out);
|
|
45593
|
+
};
|
|
45594
|
+
if (styledText((run) => run.blockQuote === true) !== tidy(expected.quotes.join(" ")))
|
|
45595
|
+
throw new Error("Block quotes not verified");
|
|
45596
|
+
if (styledText((run) => run.paragraphStyle === 4) !== tidy(expected.code.join(" ")))
|
|
45597
|
+
throw new Error("Monospaced code blocks not verified");
|
|
45598
|
+
if (styledText((run) => run.highlight === true) !== tidy(expected.highlights.join(" ")))
|
|
45599
|
+
throw new Error("Inline code highlights not verified");
|
|
45600
|
+
const items = (rich.checklistItems || []).map((item) => ({
|
|
45601
|
+
text: tidy(item.text),
|
|
45602
|
+
done: item.done
|
|
45603
|
+
}));
|
|
45604
|
+
const wanted = expected.checklist.map((item) => ({ text: tidy(item.text), done: item.done }));
|
|
45605
|
+
if (JSON.stringify(items) !== JSON.stringify(wanted))
|
|
45606
|
+
throw new Error("Checklist items or their done state not verified");
|
|
45607
|
+
if ((rich.objects || []).filter((o) => o.type === DIVIDER_UTI).length !== expected.dividers)
|
|
45608
|
+
throw new Error("Dividers not verified");
|
|
45609
|
+
}
|
|
45458
45610
|
function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
|
|
45459
45611
|
if (/[\r\n\0]/u.test(request.title)) throw new Error("A Markdown note title must be one line");
|
|
45460
|
-
validateAppendContent(request.content, "markdown");
|
|
45461
|
-
const bodyHtml =
|
|
45612
|
+
validateAppendContent(request.content, "markdown", { blocks: true });
|
|
45613
|
+
const { html: bodyHtml, expect: blockExpectations } = renderMarkdown(request.content, {
|
|
45614
|
+
blocks: true
|
|
45615
|
+
});
|
|
45616
|
+
const checkBlocks = usesMarkdownBlocks(request.content);
|
|
45462
45617
|
if (request.folder) buildFolderReference(request.folder);
|
|
45463
45618
|
const expectedText = `${request.title} ${comparableVisibleText(bodyHtml)}`.replace(/\s+/gu, " ").trim();
|
|
45464
45619
|
const expectedLevels = [1, ...headingLevels(bodyHtml)].join();
|
|
@@ -45511,6 +45666,7 @@ ${request.content}`
|
|
|
45511
45666
|
if (headingLevels(note2.html).join() !== expectedLevels)
|
|
45512
45667
|
throw new Error("Heading styles not verified");
|
|
45513
45668
|
assertAppendedHtmlLinks(0, note2.rich.links, bodyHtml);
|
|
45669
|
+
if (checkBlocks) assertMarkdownBlocks(note2.rich, blockExpectations);
|
|
45514
45670
|
return [{ id: noteId3, account: account2, note: note2 }];
|
|
45515
45671
|
} catch (error2) {
|
|
45516
45672
|
failures.push(`${noteId3}: ${reason(error2)}`);
|
|
@@ -47084,7 +47240,8 @@ var VERIFIED_BACKGROUND = /* @__PURE__ */ new Set([
|
|
|
47084
47240
|
"set-note-pinned",
|
|
47085
47241
|
"remove-native-tags",
|
|
47086
47242
|
"replace-native-tag",
|
|
47087
|
-
"create-note-markdown"
|
|
47243
|
+
"create-note-markdown",
|
|
47244
|
+
"create-note-markdown-blocks"
|
|
47088
47245
|
]);
|
|
47089
47246
|
var LIVE_VALIDATION_BLOCKERS = {};
|
|
47090
47247
|
var signingRefusal = "Installed Shortcuts refuses to sign this Notes action (unsupported features); no background fallback is enabled";
|
|
@@ -47235,7 +47392,8 @@ function registerNativeOperations(server2, manager) {
|
|
|
47235
47392
|
"remove-native-tags",
|
|
47236
47393
|
"replace-native-tag",
|
|
47237
47394
|
"insert-note-link",
|
|
47238
|
-
"create-note-markdown"
|
|
47395
|
+
"create-note-markdown",
|
|
47396
|
+
"create-note-markdown-blocks"
|
|
47239
47397
|
];
|
|
47240
47398
|
let tagBridgeInstalled = false;
|
|
47241
47399
|
try {
|
|
@@ -47247,7 +47405,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
47247
47405
|
markdownBridgeInstalled = markdownNoteStatus().installed;
|
|
47248
47406
|
} catch {
|
|
47249
47407
|
}
|
|
47250
|
-
const installed = (name) => name
|
|
47408
|
+
const installed = (name) => name.startsWith("create-note-markdown") ? markdownBridgeInstalled : bridge.installed;
|
|
47251
47409
|
return {
|
|
47252
47410
|
bridge,
|
|
47253
47411
|
nativeTagBridgeInstalled: tagBridgeInstalled,
|
|
@@ -47704,7 +47862,7 @@ registerTool(
|
|
|
47704
47862
|
inputSchema: {
|
|
47705
47863
|
title: external_exports.string().min(1, "Title is required").max(MAX.TITLE),
|
|
47706
47864
|
content: external_exports.string().min(1, "Content is required").max(MAX.CONTENT).optional().describe(
|
|
47707
|
-
'Note body; required unless contentPath is given (pass exactly one). AppleScript cannot create true Apple Notes checklists \u2014 `<input type="checkbox">`, checklist CSS classes, and markdown `- [ ]` lines do not render as checkable items
|
|
47865
|
+
'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).'
|
|
47708
47866
|
),
|
|
47709
47867
|
contentPath: external_exports.string().min(1).max(MAX.SAVE_PATH).optional().describe(
|
|
47710
47868
|
`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.`
|
|
@@ -47713,7 +47871,7 @@ registerTool(
|
|
|
47713
47871
|
"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."
|
|
47714
47872
|
),
|
|
47715
47873
|
markdownRoute: external_exports.enum(["shortcut", "html"]).optional().default("shortcut").describe(
|
|
47716
|
-
"How format 'markdown' is imported. 'shortcut' (default) uses the Create Markdown Note Shortcut for real Title/Heading/Subheading styles (iCloud only, no tags; see get-capabilities). 'html' is the fallback when that Shortcut is not installed or the note is outside iCloud: it converts the same bounded Markdown subset to HTML and creates the note through AppleScript in any account, rendering `- [ ]` / `- [x]` task items as ordinary list rows that start with a visible \u2610 / \u2611 character \u2014 not native, checkable checklist items."
|
|
47874
|
+
"How format 'markdown' is imported. 'shortcut' (default) uses the Create Markdown Note Shortcut for real Title/Heading/Subheading styles (iCloud only, no tags; see get-capabilities). 'html' is the fallback when that Shortcut is not installed or the note is outside iCloud: it converts the same bounded Markdown subset to HTML and creates the note through AppleScript in any account, rendering `- [ ]` / `- [x]` task items as ordinary list rows that start with a visible \u2610 / \u2611 character \u2014 not native, checkable checklist items \u2014 refusing block quotes, fenced code and inline code, and keeping a `---` line as literal text."
|
|
47717
47875
|
),
|
|
47718
47876
|
tags: external_exports.array(external_exports.string().max(MAX.TAG)).max(MAX.TAGS).optional().describe(
|
|
47719
47877
|
"Returned-only metadata \u2014 NOT written to Notes.app. Apple Notes tags can't be set via AppleScript, so any values passed here are echoed back in the response but do not appear on the created note. Use #hashtags in the body for searchable text; this does not create native tag objects. Native tags need the Notes Shortcuts action. Refused with format 'markdown': add tags afterwards with add-native-tags."
|
|
@@ -47803,6 +47961,7 @@ registerTool(
|
|
|
47803
47961
|
'tags are not supported with format "markdown"; create the note without tags, then add them with add-native-tags using the returned id'
|
|
47804
47962
|
);
|
|
47805
47963
|
requireValidated("create-note-markdown");
|
|
47964
|
+
if (usesMarkdownBlocks(content)) requireValidated("create-note-markdown-blocks");
|
|
47806
47965
|
const result = createMarkdownNote(notesManager, { title, content, folder });
|
|
47807
47966
|
return successResponse(`Note created from Markdown: "${title}" [id: ${result.id}]`, {
|
|
47808
47967
|
...result,
|
package/package.json
CHANGED