apple-notes-mcp 2.9.14 → 2.9.15
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 +15 -0
- package/build/index.js +1772 -368
- package/docs/markdown-templates.md +239 -0
- package/package.json +1 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# Markdown export templates
|
|
2
|
+
|
|
3
|
+
`export-notes-markdown` can render notes through a template instead of its
|
|
4
|
+
fixed Markdown renderer. A template is portable JSON that says how each block
|
|
5
|
+
style, inline format, attachment, per-note header and footer, and the
|
|
6
|
+
separator between notes are written. It is data only: there are no
|
|
7
|
+
expressions, conditions or scripts. Each rule picks one of five modes and may
|
|
8
|
+
use `{{placeholder}}` tokens, which are replaced literally.
|
|
9
|
+
|
|
10
|
+
Templates change only the exported Markdown. They never change a note.
|
|
11
|
+
|
|
12
|
+
## Choosing a template
|
|
13
|
+
|
|
14
|
+
Pass exactly one of these to `export-notes-markdown`:
|
|
15
|
+
|
|
16
|
+
| Parameter | Meaning |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `template` | A built-in name: `standard-markdown` or `obsidian` |
|
|
19
|
+
| `templateFile` | An absolute path to a JSON template file ending in `.json` (home, a temp directory, or `/Volumes`; at most 256 KiB; symlinks refused) |
|
|
20
|
+
|
|
21
|
+
The template is read and validated before any note is opened. An invalid
|
|
22
|
+
template is refused with `[invalid-template]` and one line per problem, each
|
|
23
|
+
with a JSON path:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
Error exporting Markdown [invalid-template]: the template is invalid:
|
|
27
|
+
$.rules["inline.bold"].after: is required when mode is "wrap"
|
|
28
|
+
$.inlineOrder: must list every inline format; missing "link"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Errors never quote the file's contents. A JSON syntax error reports only its
|
|
32
|
+
line and column, a wrong value reports the allowed values but not the one
|
|
33
|
+
given, and a file without `"schemaVersion": 1` is reported only as missing
|
|
34
|
+
it, with nothing else about the file.
|
|
35
|
+
|
|
36
|
+
### Built-in templates
|
|
37
|
+
|
|
38
|
+
- **`standard-markdown`** reproduces the default export exactly. Use it as the
|
|
39
|
+
base for your own templates.
|
|
40
|
+
- **`obsidian`** extends `standard-markdown` with YAML front matter for every
|
|
41
|
+
note (title, created, modified, folder, tags, note id), renders link cards
|
|
42
|
+
whose URL is an image file as images (taking a following all-italic
|
|
43
|
+
paragraph as the alt text), and copies attachments into `<file>.assets`
|
|
44
|
+
beside `outputPath`.
|
|
45
|
+
|
|
46
|
+
## A first template
|
|
47
|
+
|
|
48
|
+
This template keeps every standard rule and wraps blue highlights in a
|
|
49
|
+
callout. Listing `highlight` last in `inlineOrder` makes it the outermost
|
|
50
|
+
wrapper, so a bold blue run renders as `[callout]**text**[/callout]`.
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"schemaVersion": 1,
|
|
55
|
+
"name": "editorial",
|
|
56
|
+
"inlineOrder": [
|
|
57
|
+
"subscript",
|
|
58
|
+
"superscript",
|
|
59
|
+
"underline",
|
|
60
|
+
"strikethrough",
|
|
61
|
+
"bold",
|
|
62
|
+
"italic",
|
|
63
|
+
"color",
|
|
64
|
+
"link",
|
|
65
|
+
"highlight"
|
|
66
|
+
],
|
|
67
|
+
"rules": {
|
|
68
|
+
"inline.highlight.blue": { "mode": "wrap", "before": "[callout]", "after": "[/callout]" }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Schema version 1
|
|
74
|
+
|
|
75
|
+
| Key | Required | Meaning |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| `schemaVersion` | yes | The integer `1` |
|
|
78
|
+
| `name` | no | Display name, at most 512 characters |
|
|
79
|
+
| `description` | no | At most 512 characters |
|
|
80
|
+
| `extends` | no | `standard-markdown` (the default) or `obsidian` |
|
|
81
|
+
| `assets` | no | Merged field by field with the base |
|
|
82
|
+
| `inlineOrder` | no | Every inline format exactly once, innermost first |
|
|
83
|
+
| `rules` | no | Rule overrides keyed by rule id; each replaces the whole base rule |
|
|
84
|
+
| `options` | no | Merged field by field with the base |
|
|
85
|
+
|
|
86
|
+
Unknown keys, rule ids, rule fields, placeholders and modifiers are errors.
|
|
87
|
+
|
|
88
|
+
### Rule modes
|
|
89
|
+
|
|
90
|
+
| Mode | Fields | Output |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `wrap` | `before`, `after` | `before` + content + `after` |
|
|
93
|
+
| `linePrefix` | `value` | `value` before every line of the content (a blank line gets the trimmed prefix) |
|
|
94
|
+
| `pattern` | `value` | `value`, with `{{content}}` where the content goes |
|
|
95
|
+
| `plain` | none | The content unchanged |
|
|
96
|
+
| `omit` | none | Nothing |
|
|
97
|
+
|
|
98
|
+
A field that the mode does not use is an error. Any rule may add
|
|
99
|
+
`"join": "line"` or `"join": "paragraph"`. Adjacent blocks whose rule joins by
|
|
100
|
+
`line` and that share a group stay on consecutive lines; everything else is
|
|
101
|
+
separated by a blank line. All list styles share one group, so a bulleted item
|
|
102
|
+
followed by a numbered item stays tight. For `attachment.gallery`, `join` sets
|
|
103
|
+
the spacing between gallery items.
|
|
104
|
+
|
|
105
|
+
Inline rules apply to a run's text without its leading and trailing
|
|
106
|
+
whitespace, which stays outside the wrapper. An inline rule set to `plain`
|
|
107
|
+
does not split runs, and one set to `omit` drops the text it covers.
|
|
108
|
+
|
|
109
|
+
### Rules and their standard values
|
|
110
|
+
|
|
111
|
+
| Rule | Standard |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `document.header` | `pattern` `""` (runs once per note, before it) |
|
|
114
|
+
| `document.footer` | `pattern` `""` (once per note, after it) |
|
|
115
|
+
| `document.separator` | `pattern` `"\n\n---\n\n"` (between notes; placeholders describe the next note) |
|
|
116
|
+
| `block.title` | `wrap` `"# "`, paragraph join |
|
|
117
|
+
| `block.heading` | `wrap` `"## "`, paragraph join |
|
|
118
|
+
| `block.subheading` | `wrap` `"### "`, paragraph join |
|
|
119
|
+
| `block.body` | `plain`, paragraph join |
|
|
120
|
+
| `block.bulleted`, `block.dashed` | `wrap` `"- "`, line join |
|
|
121
|
+
| `block.numbered` | `pattern` `"{{index}}. {{content}}"`, line join |
|
|
122
|
+
| `block.checklist.checked` | `wrap` `"- [x] "`, line join |
|
|
123
|
+
| `block.checklist.unchecked` | `wrap` `"- [ ] "`, line join |
|
|
124
|
+
| `block.code` | `wrap` `"{{fence}}\n"` / `"\n{{fence}}"`, paragraph join |
|
|
125
|
+
| `paragraph.quote` | `linePrefix` `"> "` (applied after the block's own rule) |
|
|
126
|
+
| `inline.bold` / `inline.italic` | `wrap` `**` / `*` |
|
|
127
|
+
| `inline.underline` / `inline.strikethrough` | `wrap` `<u>`…`</u>` / `~~` |
|
|
128
|
+
| `inline.superscript` / `inline.subscript` | `wrap` `<sup>`…`</sup>` / `<sub>`…`</sub>` |
|
|
129
|
+
| `inline.highlight.purple`, `.pink`, `.orange`, `.mint`, `.blue`, `.other` | `wrap` `==` |
|
|
130
|
+
| `inline.color` | `plain` |
|
|
131
|
+
| `inline.link` | `pattern` `"[{{content}}]({{url}})"` |
|
|
132
|
+
| `attachment.image`, `attachment.drawing.classic`, `attachment.drawing.paper` | `pattern` `""` |
|
|
133
|
+
| `attachment.scan`, `.pdf`, `.audio`, `.video`, `.other` | `pattern` `"[{{linkText}}]({{path}})"` |
|
|
134
|
+
| `attachment.table` | `plain` (content is a GitHub table) |
|
|
135
|
+
| `attachment.divider` | `pattern` `"---"` |
|
|
136
|
+
| `attachment.gallery` | `plain`, paragraph join (content is the gallery's items) |
|
|
137
|
+
| `attachment.url` / `attachment.map` | `pattern` `"[{{alt}}]({{url}})"` |
|
|
138
|
+
| `attachment.url.image` | `pattern` `""` |
|
|
139
|
+
| `attachment.placeholder` | `pattern` `"\\[{{content}}\\]"` (an attachment with no file, or a missing one) |
|
|
140
|
+
|
|
141
|
+
Drawings are exported through the image Notes stores for them. A link card to
|
|
142
|
+
`maps.apple.com` uses `attachment.map`. A link card whose URL is not http(s),
|
|
143
|
+
`notes:`, `applenotes:` or `mailto:` is always written as plain text. Hashtags
|
|
144
|
+
and mentions keep their text; inline note links use `inline.link`.
|
|
145
|
+
|
|
146
|
+
The standard `inlineOrder`, innermost first, is:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
["subscript", "superscript", "highlight", "underline", "strikethrough", "bold", "italic", "color", "link"]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Placeholders
|
|
153
|
+
|
|
154
|
+
| Placeholder | Value |
|
|
155
|
+
| --- | --- |
|
|
156
|
+
| `{{content}}` | The rule's input |
|
|
157
|
+
| `{{title}}`, `{{id}}`, `{{uuid}}` | Note title, `x-coredata` id, and Notes UUID |
|
|
158
|
+
| `{{folder}}`, `{{account}}` | Folder and account names |
|
|
159
|
+
| `{{created}}`, `{{modified}}` | ISO 8601 UTC dates |
|
|
160
|
+
| `{{tags}}` | The note's hashtags without `#`, comma-separated |
|
|
161
|
+
| `{{exportStem}}` | `outputPath`'s file name without its extension (`export` without one) |
|
|
162
|
+
| `{{index}}`, `{{checked}}` | Numbered-list position; `true`/`false` for checklists |
|
|
163
|
+
| `{{url}}`, `{{path}}` | Link target; asset path (both escaped for a Markdown link) |
|
|
164
|
+
| `{{alt}}`, `{{linkText}}`, `{{caption}}` | Attachment name; its name or preview image; image-link caption |
|
|
165
|
+
| `{{filename}}`, `{{kind}}`, `{{uti}}` | Attachment file name, kind, and type identifier |
|
|
166
|
+
| `{{color}}`, `{{highlight}}` | Run color (`#RRGGBB`) and highlight name |
|
|
167
|
+
| `{{fence}}` | A backtick fence longer than any backtick run in the code |
|
|
168
|
+
|
|
169
|
+
Text values are escaped for Markdown. Add a modifier for other contexts:
|
|
170
|
+
`{{title:raw}}` inserts the value as stored, and `{{title:yaml}}` inserts a
|
|
171
|
+
double-quoted YAML string (tags become a YAML list, and a missing value becomes
|
|
172
|
+
`null`). A missing value is otherwise empty. This header writes YAML front
|
|
173
|
+
matter that stays valid whatever the title contains:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"schemaVersion": 1,
|
|
178
|
+
"rules": {
|
|
179
|
+
"document.header": {
|
|
180
|
+
"mode": "pattern",
|
|
181
|
+
"value": "---\ntitle: {{title:yaml}}\ncreated: {{created:yaml}}\ntags: {{tags:yaml}}\n---\n\n"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Options
|
|
188
|
+
|
|
189
|
+
| Option | Standard | Meaning |
|
|
190
|
+
| --- | --- | --- |
|
|
191
|
+
| `titleFallback` | `true` | Add a `block.title` line when the body does not start with the note's title |
|
|
192
|
+
| `richLinkImages` | `false` | Render http(s) link cards whose URL path ends in an image extension through `attachment.url.image` |
|
|
193
|
+
| `richLinkImageCaption` | `"none"` | `"followingItalicParagraph"`: use the next paragraph as the image's alt text (and `{{caption}}`) when the card stands alone in its paragraph and every character of the next body paragraph is italic; that paragraph is then not repeated |
|
|
194
|
+
| `listIndent` | four spaces | Added once per indent level before list items (at most 16 spaces or tabs) |
|
|
195
|
+
|
|
196
|
+
## Assets
|
|
197
|
+
|
|
198
|
+
| Field | Standard | Values |
|
|
199
|
+
| --- | --- | --- |
|
|
200
|
+
| `mode` | `"copy"` | `copy` attachment files, link to the originals in place (`reference`), or `omit` file attachments entirely |
|
|
201
|
+
| `pathStyle` | `"relative"` | Links relative to `outputPath`'s directory, or `absolute` |
|
|
202
|
+
| `directory` | `null` | A relative directory beneath `outputPath`'s directory, with note placeholders such as `{{exportStem}}`, `{{folder}}` or `{{title}}` |
|
|
203
|
+
|
|
204
|
+
- In `copy` mode, files go to `assetsDir` when it is passed; otherwise to
|
|
205
|
+
`directory` when it is set and there is an `outputPath`. With neither,
|
|
206
|
+
attachments render through `attachment.placeholder`, and a template that
|
|
207
|
+
sets `directory` reports an `assets_dir_required` warning for each one.
|
|
208
|
+
- Copies are named with a sanitized stem and the first eight hex digits of
|
|
209
|
+
the file's SHA-256 (`photo-1a2b3c4d.jpg`), so a repeated export reuses the
|
|
210
|
+
same file. An existing file is never replaced: a different file under the
|
|
211
|
+
same name is reported as `asset_copy_failed`.
|
|
212
|
+
- `directory` must be relative with no `..` component. Placeholder values are
|
|
213
|
+
made safe as single path components, and the result must stay inside
|
|
214
|
+
`outputPath`'s directory, or the export is refused before anything is
|
|
215
|
+
written.
|
|
216
|
+
- `reference` writes links to the files inside the Notes library. Those links
|
|
217
|
+
work only on this Mac. `assetsDir` is refused in `reference` and `omit`
|
|
218
|
+
modes.
|
|
219
|
+
- Relative links need `outputPath`; without it, links are absolute.
|
|
220
|
+
|
|
221
|
+
## Results and warnings
|
|
222
|
+
|
|
223
|
+
A templated export returns the usual receipt plus `template` (`name` and
|
|
224
|
+
`source`: `builtin`, `saved` or `file`), `warnings`, and `assetFiles` (the
|
|
225
|
+
absolute paths of the files copied or reused). Warnings do not stop the
|
|
226
|
+
export:
|
|
227
|
+
|
|
228
|
+
| Code | Meaning |
|
|
229
|
+
| --- | --- |
|
|
230
|
+
| `attachment_not_found` | The body names an attachment the note does not have |
|
|
231
|
+
| `inline_token_metadata_missing` | A hashtag, mention or note link has no text |
|
|
232
|
+
| `table_decode_failed` | A table could not be decoded |
|
|
233
|
+
| `missing_asset` | An attachment's file was not found |
|
|
234
|
+
| `asset_copy_failed` | A file could not be read or copied safely |
|
|
235
|
+
| `assets_dir_required` | The template wants copies but there is nowhere to put them |
|
|
236
|
+
| `gallery_children_missing` | A gallery has no items |
|
|
237
|
+
|
|
238
|
+
Each warning carries `noteId` and, when known, `attachmentId`. At most 200
|
|
239
|
+
are listed; `warningsOmitted` counts the rest.
|
package/package.json
CHANGED