mcp-obsidian-cli 1.0.1 → 1.1.0

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/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "mcp-obsidian-cli",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "MCP server wrapping the Obsidian CLI — full native API access over Model Context Protocol",
6
6
  "main": "server.js",
7
7
  "bin": {
8
8
  "mcp-obsidian-cli": "server.js"
9
9
  },
10
+ "files": [
11
+ "server.js",
12
+ "prompts/"
13
+ ],
10
14
  "scripts": {
11
15
  "start": "node server.js",
12
16
  "test": "node --test test/run.test.js"
@@ -0,0 +1,380 @@
1
+ # Obsidian Bases Reference
2
+
3
+ Bases are database-like views of vault notes, defined as `.base` YAML files. They let you query, filter, and display notes as tables, card galleries, lists, or maps — without writing custom code.
4
+
5
+ ## Overview
6
+
7
+ - **File format:** `.base` files containing valid YAML
8
+ - **How they work:** Obsidian reads all vault notes, applies your filters, computes any formulas, and renders results in the configured views
9
+ - **When to use:** Tracking tasks, reading lists, project dashboards, meeting logs, any situation where you want a live view across multiple notes
10
+
11
+ ## YAML Schema
12
+
13
+ ```yaml
14
+ # Global filters apply to ALL views
15
+ filters:
16
+ and: []
17
+ or: []
18
+ not: []
19
+
20
+ # Computed properties available across all views
21
+ formulas:
22
+ formula_name: 'expression'
23
+
24
+ # Display name overrides for properties
25
+ properties:
26
+ property_name:
27
+ displayName: "Display Name"
28
+ formula.formula_name:
29
+ displayName: "Formula Display Name"
30
+ file.ext:
31
+ displayName: "Extension"
32
+
33
+ # Custom summary formulas
34
+ summaries:
35
+ custom_name: 'values.mean().round(3)'
36
+
37
+ # One or more views
38
+ views:
39
+ - type: table | cards | list | map
40
+ name: "View Name"
41
+ limit: 10
42
+ groupBy:
43
+ property: property_name
44
+ direction: ASC | DESC
45
+ filters:
46
+ and: []
47
+ order:
48
+ - file.name
49
+ - property_name
50
+ - formula.formula_name
51
+ summaries:
52
+ property_name: Average
53
+ ```
54
+
55
+ ## Filters
56
+
57
+ Filters narrow which notes appear. Apply globally (to all views) or per-view.
58
+
59
+ ### Filter Structure
60
+
61
+ ```yaml
62
+ # Single filter expression
63
+ filters: 'status == "done"'
64
+
65
+ # AND - all conditions must be true
66
+ filters:
67
+ and:
68
+ - 'status == "done"'
69
+ - 'priority > 3'
70
+
71
+ # OR - any condition can be true
72
+ filters:
73
+ or:
74
+ - 'file.hasTag("book")'
75
+ - 'file.hasTag("article")'
76
+
77
+ # NOT - exclude matching items
78
+ filters:
79
+ not:
80
+ - 'file.hasTag("archived")'
81
+
82
+ # Nested filters
83
+ filters:
84
+ or:
85
+ - file.hasTag("tag")
86
+ - and:
87
+ - file.hasTag("book")
88
+ - file.hasLink("Textbook")
89
+ - not:
90
+ - file.hasTag("book")
91
+ - file.inFolder("Required Reading")
92
+ ```
93
+
94
+ ### Filter Operators
95
+
96
+ | Operator | Description |
97
+ |----------|-------------|
98
+ | `==` | equals |
99
+ | `!=` | not equal |
100
+ | `>` | greater than |
101
+ | `<` | less than |
102
+ | `>=` | greater than or equal |
103
+ | `<=` | less than or equal |
104
+ | `&&` | logical and |
105
+ | `\|\|` | logical or |
106
+ | `!` | logical not |
107
+
108
+ Useful filter functions: `file.hasTag("tag")`, `file.inFolder("path/")`, `file.hasLink("Note Name")`.
109
+
110
+ ## Properties
111
+
112
+ ### Three Property Types
113
+
114
+ 1. **Note properties** — from note frontmatter: `status`, `priority`, `due`
115
+ 2. **File properties** — computed file metadata: `file.name`, `file.path`, `file.mtime`, etc.
116
+ 3. **Formula properties** — your computed values: `formula.days_until_due`
117
+
118
+ ### File Properties Reference
119
+
120
+ | Property | Type | Description |
121
+ |----------|------|-------------|
122
+ | `file.name` | String | File name with extension |
123
+ | `file.basename` | String | File name without extension |
124
+ | `file.path` | String | Full path from vault root |
125
+ | `file.folder` | String | Parent folder path |
126
+ | `file.ext` | String | File extension |
127
+ | `file.size` | Number | File size in bytes |
128
+ | `file.ctime` | Date | Created time |
129
+ | `file.mtime` | Date | Modified time |
130
+ | `file.tags` | List | All tags in file |
131
+ | `file.links` | List | Internal links in file |
132
+ | `file.backlinks` | List | Files linking to this file |
133
+
134
+ ## Formula Syntax
135
+
136
+ Formulas compute values from properties. Define them in the `formulas` section, reference them as `formula.name` in views.
137
+
138
+ ```yaml
139
+ formulas:
140
+ # Simple arithmetic
141
+ total: "price * quantity"
142
+
143
+ # Conditional logic
144
+ status_icon: 'if(done, "Done", "Pending")'
145
+
146
+ # String formatting
147
+ formatted_price: 'if(price, price.toFixed(2) + " USD", "")'
148
+
149
+ # Date formatting
150
+ created_label: 'file.ctime.format("YYYY-MM-DD")'
151
+
152
+ # Days since creation
153
+ days_old: '(now() - file.ctime).days'
154
+
155
+ # Days until due date (with null guard)
156
+ days_until_due: 'if(due_date, (date(due_date) - today()).days, "")'
157
+ ```
158
+
159
+ ### Key Functions
160
+
161
+ | Function | Description |
162
+ |----------|-------------|
163
+ | `date(string)` | Parse string to date (`YYYY-MM-DD`) |
164
+ | `now()` | Current date and time |
165
+ | `today()` | Current date (time = 00:00:00) |
166
+ | `if(condition, trueVal, falseVal?)` | Conditional expression |
167
+ | `duration(string)` | Parse duration string |
168
+ | `file(path)` | Get file object |
169
+ | `link(path, display?)` | Create a link |
170
+
171
+ ### Duration Type
172
+
173
+ Subtracting two dates returns a **Duration** — not a number. Access a numeric field before applying math:
174
+
175
+ ```yaml
176
+ # CORRECT
177
+ "(now() - file.ctime).days" # Returns days as number
178
+ "(date(due) - today()).days.round(0)" # Rounded days
179
+
180
+ # WRONG — Duration doesn't support direct round()
181
+ # "(date(due) - today()).round(0)"
182
+ ```
183
+
184
+ ### Date Arithmetic
185
+
186
+ ```yaml
187
+ "now() + \"1 day\"" # Tomorrow
188
+ "today() + \"7d\"" # A week from today
189
+ "now() - file.ctime" # Returns Duration type
190
+ ```
191
+
192
+ ## View Types
193
+
194
+ ### Table View
195
+
196
+ Displays notes as rows with sortable columns.
197
+
198
+ ```yaml
199
+ views:
200
+ - type: table
201
+ name: "Task List"
202
+ order:
203
+ - file.name
204
+ - status
205
+ - due_date
206
+ summaries:
207
+ priority: Average
208
+ ```
209
+
210
+ ### Cards View
211
+
212
+ Displays notes as a visual card gallery.
213
+
214
+ ```yaml
215
+ views:
216
+ - type: cards
217
+ name: "Gallery"
218
+ order:
219
+ - file.name
220
+ - cover_image
221
+ - description
222
+ ```
223
+
224
+ ### List View
225
+
226
+ Displays a minimal list of notes.
227
+
228
+ ```yaml
229
+ views:
230
+ - type: list
231
+ name: "Quick List"
232
+ order:
233
+ - file.name
234
+ - status
235
+ ```
236
+
237
+ ### Map View
238
+
239
+ Requires latitude/longitude properties and the Maps community plugin.
240
+
241
+ ```yaml
242
+ views:
243
+ - type: map
244
+ name: "Locations"
245
+ ```
246
+
247
+ ## Summaries
248
+
249
+ Summarize numeric or date columns in table footers.
250
+
251
+ | Summary | Input | Description |
252
+ |---------|-------|-------------|
253
+ | `Average` | Number | Mean value |
254
+ | `Sum` | Number | Total |
255
+ | `Min` | Number | Smallest value |
256
+ | `Max` | Number | Largest value |
257
+ | `Range` | Number | Max minus Min |
258
+ | `Median` | Number | Median value |
259
+ | `Stddev` | Number | Standard deviation |
260
+ | `Earliest` | Date | Earliest date |
261
+ | `Latest` | Date | Latest date |
262
+ | `Checked` | Boolean | Count of true |
263
+ | `Unchecked` | Boolean | Count of false |
264
+ | `Empty` | Any | Count of empty |
265
+ | `Filled` | Any | Count of non-empty |
266
+ | `Unique` | Any | Count of distinct values |
267
+
268
+ ## Complete Examples
269
+
270
+ ### Task Tracker
271
+
272
+ ```yaml
273
+ filters:
274
+ and:
275
+ - file.hasTag("task")
276
+
277
+ formulas:
278
+ days_until_due: 'if(due, (date(due) - today()).days, "")'
279
+ is_overdue: 'if(due, date(due) < today() && status != "done", false)'
280
+ priority_label: 'if(priority == 1, "High", if(priority == 2, "Medium", "Low"))'
281
+
282
+ properties:
283
+ formula.days_until_due:
284
+ displayName: "Days Until Due"
285
+ formula.priority_label:
286
+ displayName: Priority
287
+
288
+ views:
289
+ - type: table
290
+ name: "Active Tasks"
291
+ filters:
292
+ and:
293
+ - 'status != "done"'
294
+ order:
295
+ - file.name
296
+ - status
297
+ - formula.priority_label
298
+ - due
299
+ - formula.days_until_due
300
+ groupBy:
301
+ property: status
302
+ direction: ASC
303
+ ```
304
+
305
+ ### Reading List
306
+
307
+ ```yaml
308
+ filters:
309
+ or:
310
+ - file.hasTag("book")
311
+ - file.hasTag("article")
312
+
313
+ formulas:
314
+ status_icon: 'if(status == "reading", "Reading", if(status == "done", "Done", "To Read"))'
315
+ year_read: 'if(finished_date, date(finished_date).year, "")'
316
+
317
+ views:
318
+ - type: cards
319
+ name: "Library"
320
+ order:
321
+ - cover
322
+ - file.name
323
+ - author
324
+ - formula.status_icon
325
+ filters:
326
+ not:
327
+ - 'status == "dropped"'
328
+ ```
329
+
330
+ ### Daily Notes Index
331
+
332
+ ```yaml
333
+ filters:
334
+ and:
335
+ - file.inFolder("Daily Notes")
336
+
337
+ formulas:
338
+ word_estimate: '(file.size / 5).round(0)'
339
+ day_of_week: 'date(file.basename).format("dddd")'
340
+
341
+ properties:
342
+ formula.day_of_week:
343
+ displayName: "Day"
344
+ formula.word_estimate:
345
+ displayName: "~Words"
346
+
347
+ views:
348
+ - type: table
349
+ name: "Recent Notes"
350
+ limit: 30
351
+ order:
352
+ - file.name
353
+ - formula.day_of_week
354
+ - formula.word_estimate
355
+ - file.mtime
356
+ ```
357
+
358
+ ## YAML Quoting Rules
359
+
360
+ - Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'`
361
+ - Use double quotes for simple string values: `"My View Name"`
362
+ - Strings containing `:`, `{`, `}`, `[`, `]`, `#` must be quoted
363
+
364
+ ## Embedding Bases in Notes
365
+
366
+ ```markdown
367
+ ![[MyBase.base]]
368
+
369
+ ![[MyBase.base#View Name]]
370
+ ```
371
+
372
+ ## Using This Knowledge with MCP Tools
373
+
374
+ When working with Bases through this MCP server:
375
+
376
+ - Create a new base: `obsidian_create({ name: "Tasks", path: "bases/Tasks.base", content: "<yaml content>" })`
377
+ - Read an existing base definition: `obsidian_read({ path: "bases/Tasks.base" })`
378
+ - List all base files: `obsidian_files({ ext: "base" })`
379
+ - Set a property on a note that a base queries: `obsidian_property_set({ name: "status", value: "done", file: "My Task Note" })`
380
+ - Search for notes that match base criteria: `obsidian_search({ query: "tag:#task" })`
@@ -0,0 +1,312 @@
1
+ # JSON Canvas Reference
2
+
3
+ Canvas files (`.canvas`) are JSON files that define visual layouts of notes, text, links, and groups on an infinite canvas. Use them for brainstorming, relationship mapping, project planning, and content organization.
4
+
5
+ ## File Structure
6
+
7
+ A canvas file contains two top-level arrays following the JSON Canvas Spec 1.0:
8
+
9
+ ```json
10
+ {
11
+ "nodes": [],
12
+ "edges": []
13
+ }
14
+ ```
15
+
16
+ - `nodes` — array of node objects placed on the canvas
17
+ - `edges` — array of connections between nodes
18
+
19
+ Both arrays are optional (empty canvas is valid).
20
+
21
+ ## Common Workflows
22
+
23
+ ### Create a New Canvas
24
+
25
+ 1. Create a `.canvas` file with base structure `{"nodes": [], "edges": []}`
26
+ 2. Generate unique 16-character hex IDs for each node (e.g., `"6f0ad84f44ce9c17"`)
27
+ 3. Add nodes with required fields: `id`, `type`, `x`, `y`, `width`, `height`
28
+ 4. Add edges referencing valid node IDs via `fromNode` and `toNode`
29
+ 5. Validate: ensure all `fromNode`/`toNode` values reference existing node IDs
30
+
31
+ ### Add a Node to an Existing Canvas
32
+
33
+ 1. Read and parse the existing `.canvas` file
34
+ 2. Generate a unique ID that does not collide with existing IDs
35
+ 3. Choose `x`, `y` position that avoids overlapping existing nodes (50-100px spacing)
36
+ 4. Append the new node to the `nodes` array
37
+ 5. Optionally add edges connecting it to existing nodes
38
+
39
+ ### Connect Two Nodes
40
+
41
+ 1. Identify source and target node IDs
42
+ 2. Generate a unique edge ID
43
+ 3. Set `fromNode` and `toNode`; optionally set `fromSide`/`toSide` for anchor points
44
+ 4. Optionally add a `label`
45
+ 5. Append to the `edges` array
46
+
47
+ ## Node Types
48
+
49
+ All nodes share these required attributes:
50
+
51
+ | Attribute | Required | Type | Description |
52
+ |-----------|----------|------|-------------|
53
+ | `id` | Yes | string | Unique 16-char hex identifier |
54
+ | `type` | Yes | string | `text`, `file`, `link`, or `group` |
55
+ | `x` | Yes | integer | X position in pixels (left edge) |
56
+ | `y` | Yes | integer | Y position in pixels (top edge) |
57
+ | `width` | Yes | integer | Width in pixels |
58
+ | `height` | Yes | integer | Height in pixels |
59
+ | `color` | No | string | Preset `"1"`-`"6"` or hex `"#RRGGBB"` |
60
+
61
+ ### Text Nodes
62
+
63
+ Display rich text with Markdown content.
64
+
65
+ | Attribute | Required | Description |
66
+ |-----------|----------|-------------|
67
+ | `text` | Yes | Markdown string |
68
+
69
+ ```json
70
+ {
71
+ "id": "6f0ad84f44ce9c17",
72
+ "type": "text",
73
+ "x": 0,
74
+ "y": 0,
75
+ "width": 400,
76
+ "height": 200,
77
+ "text": "# Hello World\n\nThis is **Markdown** content."
78
+ }
79
+ ```
80
+
81
+ Use `\n` for line breaks in JSON strings. Do NOT use literal `\\n`.
82
+
83
+ ### File Nodes
84
+
85
+ Display a vault note or attachment inline.
86
+
87
+ | Attribute | Required | Description |
88
+ |-----------|----------|-------------|
89
+ | `file` | Yes | Path to file from vault root |
90
+ | `subpath` | No | Link to heading or block (starts with `#`) |
91
+
92
+ ```json
93
+ {
94
+ "id": "a1b2c3d4e5f67890",
95
+ "type": "file",
96
+ "x": 500,
97
+ "y": 0,
98
+ "width": 400,
99
+ "height": 300,
100
+ "file": "Projects/Alpha.md"
101
+ }
102
+ ```
103
+
104
+ ### Link Nodes
105
+
106
+ Display an external URL as a web preview.
107
+
108
+ | Attribute | Required | Description |
109
+ |-----------|----------|-------------|
110
+ | `url` | Yes | External URL |
111
+
112
+ ```json
113
+ {
114
+ "id": "c3d4e5f678901234",
115
+ "type": "link",
116
+ "x": 1000,
117
+ "y": 0,
118
+ "width": 400,
119
+ "height": 200,
120
+ "url": "https://obsidian.md"
121
+ }
122
+ ```
123
+
124
+ ### Group Nodes
125
+
126
+ Visual containers for organizing other nodes. Position child nodes inside the group's bounds.
127
+
128
+ | Attribute | Required | Description |
129
+ |-----------|----------|-------------|
130
+ | `label` | No | Text label for the group |
131
+ | `background` | No | Path to background image |
132
+ | `backgroundStyle` | No | `cover`, `ratio`, or `repeat` |
133
+
134
+ ```json
135
+ {
136
+ "id": "d4e5f6789012345a",
137
+ "type": "group",
138
+ "x": -50,
139
+ "y": -50,
140
+ "width": 1000,
141
+ "height": 600,
142
+ "label": "Project Overview",
143
+ "color": "4"
144
+ }
145
+ ```
146
+
147
+ Groups do not automatically contain nodes — nodes are "inside" a group visually based on position overlap.
148
+
149
+ ## Edges
150
+
151
+ Edges connect nodes via their IDs.
152
+
153
+ | Attribute | Required | Default | Description |
154
+ |-----------|----------|---------|-------------|
155
+ | `id` | Yes | - | Unique identifier |
156
+ | `fromNode` | Yes | - | Source node ID |
157
+ | `toNode` | Yes | - | Target node ID |
158
+ | `fromSide` | No | - | `top`, `right`, `bottom`, or `left` |
159
+ | `toSide` | No | - | `top`, `right`, `bottom`, or `left` |
160
+ | `fromEnd` | No | `none` | `none` or `arrow` |
161
+ | `toEnd` | No | `arrow` | `none` or `arrow` |
162
+ | `color` | No | - | Line color (preset or hex) |
163
+ | `label` | No | - | Text label on the edge |
164
+
165
+ ```json
166
+ {
167
+ "id": "0123456789abcdef",
168
+ "fromNode": "6f0ad84f44ce9c17",
169
+ "fromSide": "right",
170
+ "toNode": "a1b2c3d4e5f67890",
171
+ "toSide": "left",
172
+ "toEnd": "arrow",
173
+ "label": "leads to"
174
+ }
175
+ ```
176
+
177
+ ## Color Presets
178
+
179
+ | Preset | Color |
180
+ |--------|-------|
181
+ | `"1"` | Red |
182
+ | `"2"` | Orange |
183
+ | `"3"` | Yellow |
184
+ | `"4"` | Green |
185
+ | `"5"` | Cyan |
186
+ | `"6"` | Purple |
187
+
188
+ Also accepts hex strings: `"#FF0000"`. Exact shades depend on the Obsidian theme.
189
+
190
+ ## ID Generation
191
+
192
+ Use 16-character lowercase hexadecimal strings (64-bit random value):
193
+
194
+ ```
195
+ "6f0ad84f44ce9c17"
196
+ "a3b2c1d0e9f8a7b6"
197
+ ```
198
+
199
+ IDs must be unique across all nodes and edges in the file.
200
+
201
+ ## Layout Guidelines
202
+
203
+ - Coordinates can be negative — the canvas is infinite
204
+ - `x` increases rightward, `y` increases downward
205
+ - `x`, `y` mark the top-left corner of the node
206
+ - Space nodes 50-100px apart; leave 20-50px padding inside groups
207
+ - Align to grid (multiples of 10 or 20) for cleaner layouts
208
+ - Array order determines z-index: first node = bottom layer, last = top layer
209
+
210
+ | Node Type | Suggested Width | Suggested Height |
211
+ |-----------|-----------------|------------------|
212
+ | Small text | 200-300 | 80-150 |
213
+ | Medium text | 300-450 | 150-300 |
214
+ | Large text | 400-600 | 300-500 |
215
+ | File preview | 300-500 | 200-400 |
216
+ | Link preview | 250-400 | 100-200 |
217
+
218
+ ## Validation Checklist
219
+
220
+ Before writing a canvas file, verify:
221
+
222
+ 1. All `id` values are unique across nodes and edges
223
+ 2. Every `fromNode` and `toNode` references an existing node ID
224
+ 3. Required fields are present for each node type:
225
+ - text nodes: `text`
226
+ - file nodes: `file`
227
+ - link nodes: `url`
228
+ 4. `type` is one of: `text`, `file`, `link`, `group`
229
+ 5. `fromSide`/`toSide` are one of: `top`, `right`, `bottom`, `left`
230
+ 6. `fromEnd`/`toEnd` are one of: `none`, `arrow`
231
+ 7. Color presets are `"1"` through `"6"` or valid hex `"#RRGGBB"`
232
+ 8. JSON is valid and parseable (especially: `\n` not `\\n` in text)
233
+
234
+ ## Complete Example
235
+
236
+ A minimal project overview canvas with a group, two notes, a text summary, and a connecting edge:
237
+
238
+ ```json
239
+ {
240
+ "nodes": [
241
+ {
242
+ "id": "d4e5f6789012345a",
243
+ "type": "group",
244
+ "x": -60,
245
+ "y": -60,
246
+ "width": 1100,
247
+ "height": 500,
248
+ "label": "Project Alpha",
249
+ "color": "4"
250
+ },
251
+ {
252
+ "id": "6f0ad84f44ce9c17",
253
+ "type": "text",
254
+ "x": 0,
255
+ "y": 0,
256
+ "width": 350,
257
+ "height": 150,
258
+ "text": "## Goal\n\nDeliver MVP by end of quarter.\n\n- Backend API\n- Frontend UI\n- Documentation"
259
+ },
260
+ {
261
+ "id": "a1b2c3d4e5f67890",
262
+ "type": "file",
263
+ "x": 450,
264
+ "y": 0,
265
+ "width": 400,
266
+ "height": 300,
267
+ "file": "Projects/Alpha/Requirements.md"
268
+ },
269
+ {
270
+ "id": "b2c3d4e5f6789012",
271
+ "type": "link",
272
+ "x": 450,
273
+ "y": 320,
274
+ "width": 400,
275
+ "height": 120,
276
+ "url": "https://github.com/example/project-alpha"
277
+ }
278
+ ],
279
+ "edges": [
280
+ {
281
+ "id": "0123456789abcdef",
282
+ "fromNode": "6f0ad84f44ce9c17",
283
+ "fromSide": "right",
284
+ "toNode": "a1b2c3d4e5f67890",
285
+ "toSide": "left",
286
+ "toEnd": "arrow",
287
+ "label": "defines"
288
+ }
289
+ ]
290
+ }
291
+ ```
292
+
293
+ ## Using This Knowledge with MCP Tools
294
+
295
+ When working with canvas files through this MCP server:
296
+
297
+ - Create a new canvas:
298
+ ```
299
+ obsidian_create({
300
+ name: "Project Map",
301
+ path: "canvas/Project Map.canvas",
302
+ content: "{\"nodes\": [], \"edges\": []}"
303
+ })
304
+ ```
305
+ - Read an existing canvas (parse the JSON to see its structure):
306
+ `obsidian_read({ path: "canvas/Project Map.canvas" })`
307
+ - List all canvas files in the vault:
308
+ `obsidian_files({ ext: "canvas" })`
309
+ - Update a canvas (read, modify JSON, write back):
310
+ `obsidian_create({ path: "canvas/Project Map.canvas", content: "<updated json>" })`
311
+ - Search for notes referenced in canvases:
312
+ `obsidian_search({ query: "canvas" })`
@@ -0,0 +1,115 @@
1
+ # Obsidian CLI Reference
2
+
3
+ Use the `obsidian` CLI to interact with a running Obsidian instance. Obsidian must be open.
4
+
5
+ ## Command Syntax
6
+
7
+ Commands follow this pattern:
8
+
9
+ ```bash
10
+ obsidian <command> [key=value ...]
11
+ ```
12
+
13
+ **Parameters** take a value with `=`. Quote values that contain spaces:
14
+
15
+ ```bash
16
+ obsidian create name="My Note" content="Hello world"
17
+ ```
18
+
19
+ **Flags** are boolean switches with no value:
20
+
21
+ ```bash
22
+ obsidian create name="My Note" silent overwrite
23
+ ```
24
+
25
+ For multiline content, use `\n` for newline and `\t` for tab.
26
+
27
+ ## File Targeting
28
+
29
+ Many commands accept `file` or `path` to target a note. Without either, the active file is used.
30
+
31
+ - `file=<name>` — resolves like a wikilink (name only, no path or extension needed)
32
+ - `path=<path>` — exact path from vault root, e.g. `folder/note.md`
33
+
34
+ ## Vault Targeting
35
+
36
+ Commands target the most recently focused vault by default. Use `vault=<name>` as the first parameter to target a specific vault:
37
+
38
+ ```bash
39
+ obsidian vault="My Vault" search query="test"
40
+ ```
41
+
42
+ ## Common Commands
43
+
44
+ | Command | Description |
45
+ |---------|-------------|
46
+ | `read file="Note"` | Read note contents by wikilink name |
47
+ | `read path="folder/note.md"` | Read note contents by exact path |
48
+ | `create name="New Note" content="..."` | Create a new note |
49
+ | `append file="Note" content="..."` | Append content to a note |
50
+ | `search query="term" limit=10` | Full-text vault search |
51
+ | `search:context query="term"` | Search with surrounding line context |
52
+ | `daily:read` | Read today's daily note |
53
+ | `daily:append content="..."` | Append to today's daily note |
54
+ | `daily:path` | Get file path of today's daily note |
55
+ | `tasks daily todo` | List incomplete tasks from daily note |
56
+ | `tasks` | List all tasks in the vault |
57
+ | `tags sort=count counts` | List tags sorted by frequency |
58
+ | `properties` | List all frontmatter properties in vault |
59
+ | `properties file="Note" counts` | List properties for a specific note |
60
+ | `property:read name="status" file="Note"` | Read a specific property value |
61
+ | `property:set name="status" value="done" file="Note"` | Set a property |
62
+ | `backlinks file="Note" counts` | List notes that link to this note |
63
+ | `files folder="Projects/"` | List files in a folder |
64
+ | `files ext=canvas` | List files by extension |
65
+ | `recents` | List recently opened files |
66
+ | `help` | Show all available commands |
67
+ | `help <command>` | Show help for a specific command |
68
+
69
+ ## Parameter Patterns
70
+
71
+ | Parameter | Purpose | Example |
72
+ |-----------|---------|---------|
73
+ | `file=` | Target note by wikilink name | `file="My Note"` |
74
+ | `path=` | Target note by exact vault path | `path="Work/Projects/alpha.md"` |
75
+ | `query=` | Search terms | `query="meeting notes"` |
76
+ | `content=` | Text content to write or append | `content="- [ ] New task"` |
77
+ | `name=` | Note name (create) or property name | `name="New Note"` |
78
+ | `value=` | Property value | `value="done"` |
79
+ | `limit=` | Max results to return | `limit=10` |
80
+ | `sort=` | Sort order | `sort=count` |
81
+ | `folder=` | Folder to filter by | `folder="Projects/"` |
82
+ | `ext=` | File extension filter | `ext=canvas` |
83
+
84
+ Quote any value that contains spaces. Use `\n` for newlines within `content=` values.
85
+
86
+ ## Output Behavior
87
+
88
+ - Commands write their output to stdout
89
+ - Errors appear on stderr
90
+ - Use `--copy` on any command to copy output to clipboard
91
+ - Use `silent` flag to prevent files from opening in the Obsidian UI
92
+ - Use `total` on list commands to get a count appended to output
93
+
94
+ ## Using This Knowledge with MCP Tools
95
+
96
+ When working with an Obsidian vault via this MCP server, use these tools instead of running the CLI directly:
97
+
98
+ - Read a note: `obsidian_read({ file: "Note Name" })`
99
+ - Read by exact path: `obsidian_read({ path: "folder/note.md" })`
100
+ - Search with context: `obsidian_search({ query: "term", path: "folder/", limit: 5 })`
101
+ - Read today's daily note: `obsidian_daily_read()`
102
+ - Get daily note path: `obsidian_daily_path()`
103
+ - Append to daily note: `obsidian_daily_append({ content: "- [ ] New task" })`
104
+ - Create a note: `obsidian_create({ name: "New Note", content: "# Heading\n..." })`
105
+ - List tasks from daily note: `obsidian_tasks({ daily: true, todo: true })`
106
+ - List all vault tasks: `obsidian_tasks({})`
107
+ - List tags by frequency: `obsidian_tags({ sort: "count" })`
108
+ - Read all properties for a note: `obsidian_properties({ file: "Note Name" })`
109
+ - Read a specific property: `obsidian_properties({ file: "Note Name", name: "status" })`
110
+ - Set a property: `obsidian_property_set({ name: "status", value: "done", file: "Note Name" })`
111
+ - List backlinks: `obsidian_backlinks({ file: "Note Name" })`
112
+ - List files in folder: `obsidian_files({ folder: "Projects/" })`
113
+ - List canvas files: `obsidian_files({ ext: "canvas" })`
114
+ - List recent files: `obsidian_recents()`
115
+ - Run any CLI command directly: `obsidian({ command: "help search" })`
@@ -0,0 +1,224 @@
1
+ # Obsidian Flavored Markdown Reference
2
+
3
+ Obsidian extends CommonMark and GitHub Flavored Markdown with wikilinks, embeds, callouts, properties, and other vault-specific syntax. This reference covers only Obsidian-specific extensions — standard Markdown (headings, bold, italic, lists, code blocks, tables) is assumed knowledge.
4
+
5
+ ## Wikilinks (Internal Links)
6
+
7
+ Use wikilinks for all links within the vault. Obsidian tracks renames automatically.
8
+
9
+ ```markdown
10
+ [[Note Name]] Link to a note
11
+ [[Note Name|Display Text]] Custom display text
12
+ [[Note Name#Heading]] Link to a specific heading
13
+ [[Note Name#^block-id]] Link to a specific block
14
+ [[Note Name#^block-id|Display Text]] Link to block with custom text
15
+ [[#Heading in same note]] Link to heading in current note
16
+ ```
17
+
18
+ Define a block ID by appending `^block-id` at the end of a paragraph:
19
+
20
+ ```markdown
21
+ This paragraph can be linked to. ^my-block-id
22
+ ```
23
+
24
+ For lists or quotes, place the block ID on a separate line after the block:
25
+
26
+ ```markdown
27
+ > A quote block
28
+
29
+ ^quote-id
30
+ ```
31
+
32
+ Use `[text](url)` for external URLs only — not for vault notes.
33
+
34
+ ## Embeds
35
+
36
+ Prefix any wikilink with `!` to embed content inline:
37
+
38
+ ```markdown
39
+ ![[Note Name]] Embed full note
40
+ ![[Note Name#Heading]] Embed a specific section
41
+ ![[image.png]] Embed an image
42
+ ![[image.png|300]] Embed image with pixel width
43
+ ![[document.pdf#page=3]] Embed a specific PDF page
44
+ ![[audio.mp3]] Embed audio player
45
+ ```
46
+
47
+ ## Callouts
48
+
49
+ Callouts highlight information with a colored block and icon:
50
+
51
+ ```markdown
52
+ > [!note]
53
+ > Basic callout with default title.
54
+
55
+ > [!warning] Custom Title
56
+ > Callout with a custom title.
57
+
58
+ > [!tip]- Collapsed by default
59
+ > Use `-` after the type to collapse. Use `+` to expand by default.
60
+
61
+ > [!faq]+ Expanded by default
62
+ > Starts open.
63
+ ```
64
+
65
+ Common callout types: `note`, `tip`, `info`, `warning`, `danger`, `bug`, `success`, `failure`, `question`, `example`, `quote`, `abstract`, `todo`.
66
+
67
+ Callouts can be nested:
68
+
69
+ ```markdown
70
+ > [!note] Outer
71
+ > > [!tip] Inner
72
+ > > Nested callout content.
73
+ ```
74
+
75
+ ## Properties (Frontmatter)
76
+
77
+ Properties are YAML metadata in a `---` delimited block at the top of the file:
78
+
79
+ ```yaml
80
+ ---
81
+ title: My Note
82
+ date: 2024-01-15
83
+ tags:
84
+ - project
85
+ - active
86
+ aliases:
87
+ - Alternative Name
88
+ cssclasses:
89
+ - custom-class
90
+ status: in-progress
91
+ priority: 2
92
+ done: false
93
+ ---
94
+ ```
95
+
96
+ **Common property types:**
97
+
98
+ | Type | YAML example |
99
+ |------|-------------|
100
+ | Text | `status: "active"` |
101
+ | Number | `priority: 2` |
102
+ | Date | `due: 2024-03-01` |
103
+ | Boolean | `done: false` |
104
+ | List | `tags:\n - item1\n - item2` |
105
+ | Link | `related: "[[Other Note]]"` |
106
+
107
+ **Built-in Obsidian properties:**
108
+ - `tags` — searchable labels (`#tag` syntax, also works inline in body)
109
+ - `aliases` — alternative note names for link suggestions
110
+ - `cssclasses` — CSS classes applied to the note in reading view
111
+
112
+ ## Tags
113
+
114
+ ```markdown
115
+ #tag Inline tag in body text
116
+ #nested/tag Nested tag (creates hierarchy)
117
+ ```
118
+
119
+ Tags can also be defined in frontmatter under `tags`. Rules: letters, numbers (not first char), underscores, hyphens, and forward slashes.
120
+
121
+ ## Comments
122
+
123
+ Comments are hidden in reading view:
124
+
125
+ ```markdown
126
+ This is visible %%but this is hidden%% text.
127
+
128
+ %%
129
+ This entire block is hidden in reading view.
130
+ %%
131
+ ```
132
+
133
+ ## Highlights
134
+
135
+ ```markdown
136
+ ==This text is highlighted==
137
+ ```
138
+
139
+ ## Math (LaTeX)
140
+
141
+ ```markdown
142
+ Inline: $e^{i\pi} + 1 = 0$
143
+
144
+ Block:
145
+ $$
146
+ \frac{a}{b} = c
147
+ $$
148
+ ```
149
+
150
+ ## Diagrams (Mermaid)
151
+
152
+ ````markdown
153
+ ```mermaid
154
+ graph TD
155
+ A[Start] --> B{Decision}
156
+ B -->|Yes| C[Do this]
157
+ B -->|No| D[Do that]
158
+ ```
159
+ ````
160
+
161
+ To link Mermaid nodes to Obsidian notes, add `class NodeName internal-link;`.
162
+
163
+ ## Footnotes
164
+
165
+ ```markdown
166
+ Text with a footnote[^1].
167
+
168
+ [^1]: Footnote content goes here.
169
+
170
+ Inline footnote.^[This is the inline footnote text.]
171
+ ```
172
+
173
+ ## Complete Note Example
174
+
175
+ ````markdown
176
+ ---
177
+ title: Project Alpha
178
+ date: 2024-01-15
179
+ tags:
180
+ - project
181
+ - active
182
+ status: in-progress
183
+ ---
184
+
185
+ # Project Alpha
186
+
187
+ This project aims to [[improve workflow]] using modern techniques.
188
+
189
+ > [!important] Key Deadline
190
+ > The first milestone is due on ==January 30th==.
191
+
192
+ ## Tasks
193
+
194
+ - [x] Initial planning
195
+ - [ ] Development phase
196
+
197
+ ## Notes
198
+
199
+ The algorithm uses $O(n \log n)$ sorting. See [[Algorithm Notes#Sorting]] for details.
200
+
201
+ ![[Architecture Diagram.png|600]]
202
+
203
+ Reviewed in [[Meeting Notes 2024-01-10#Decisions]].
204
+ ````
205
+
206
+ ## Using This Knowledge with MCP Tools
207
+
208
+ When creating or editing notes through this MCP server, apply these patterns:
209
+
210
+ - Read a note (see its markdown): `obsidian_read({ file: "Note Name" })`
211
+ - Create a note with frontmatter:
212
+ ```
213
+ obsidian_create({
214
+ name: "New Note",
215
+ content: "---\ntags:\n - project\nstatus: active\n---\n# Heading\n\nContent here."
216
+ })
217
+ ```
218
+ - Append a callout to daily note:
219
+ ```
220
+ obsidian_daily_append({ content: "> [!tip] Reminder\n> Don't forget the meeting." })
221
+ ```
222
+ - Set a frontmatter property: `obsidian_property_set({ name: "status", value: "done", file: "Note Name" })`
223
+ - Search by tag: `obsidian_search({ query: "tag:#project" })`
224
+ - Find notes linking to another: `obsidian_backlinks({ file: "Note Name" })`
package/server.js CHANGED
@@ -25,12 +25,23 @@ import { promisify } from "node:util";
25
25
  import { readFileSync, mkdirSync, existsSync } from "node:fs";
26
26
  import { load as yamlLoad } from "js-yaml";
27
27
  import { homedir } from "node:os";
28
- import { join } from "node:path";
28
+ import { join, dirname } from "node:path";
29
+ import { fileURLToPath } from "node:url";
29
30
  import { exec } from "node:child_process";
30
31
 
31
32
  const execFileAsync = promisify(execFile);
32
33
  const execAsync = promisify(exec);
33
34
 
35
+ const __dirname = dirname(fileURLToPath(import.meta.url));
36
+ const PROMPTS_DIR = join(__dirname, "prompts");
37
+
38
+ const promptContent = {
39
+ "obsidian-cli": readFileSync(join(PROMPTS_DIR, "obsidian-cli.md"), "utf8"),
40
+ "obsidian-markdown": readFileSync(join(PROMPTS_DIR, "obsidian-markdown.md"), "utf8"),
41
+ "obsidian-bases": readFileSync(join(PROMPTS_DIR, "obsidian-bases.md"), "utf8"),
42
+ "obsidian-canvas": readFileSync(join(PROMPTS_DIR, "obsidian-canvas.md"), "utf8"),
43
+ };
44
+
34
45
  const configBase = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
35
46
  const CONFIG_DIR = join(configBase, "mcp-obsidian-cli");
36
47
  const CONFIG_FILE = join(CONFIG_DIR, "config.yaml");
@@ -210,7 +221,7 @@ async function runTool(argString) {
210
221
 
211
222
  const server = new McpServer({
212
223
  name: "obsidian-mcp",
213
- version: "1.0.0",
224
+ version: "1.1.0",
214
225
  capabilities: { tools: {} },
215
226
  });
216
227
 
@@ -236,28 +247,28 @@ Examples:
236
247
 
237
248
  server.tool(
238
249
  "obsidian_daily_read",
239
- "Read today's daily note contents.",
250
+ "Read today's daily note contents.\n\nReturns the full markdown content of today's daily note. Returns an error if no daily note exists for today.",
240
251
  {},
241
252
  async () => runTool("daily:read"),
242
253
  );
243
254
 
244
255
  server.tool(
245
256
  "obsidian_daily_path",
246
- "Get the file path of today's daily note.",
257
+ "Get the file path of today's daily note.\n\nReturns the vault-relative path (e.g. 'Daily/2026-04-03.md'). Useful for constructing paths for other tools.",
247
258
  {},
248
259
  async () => runTool("daily:path"),
249
260
  );
250
261
 
251
262
  server.tool(
252
263
  "obsidian_daily_append",
253
- "Append content to today's daily note.",
264
+ "Append content to today's daily note.\n\nParameters:\n content (required) — markdown text to append at the end of today's daily note\n\nExamples:\n obsidian_daily_append({ content: \"- Meeting with team at 3pm\" })\n obsidian_daily_append({ content: \"> [!tip] Remember\\n> Review PR before EOD\" })",
254
265
  { content: z.string().describe("Content to append") },
255
266
  async ({ content }) => runTool(`daily:append content="${content.replace(/"/g, '\\"')}"`),
256
267
  );
257
268
 
258
269
  server.tool(
259
270
  "obsidian_read",
260
- "Read a note by file name (wikilink-style) or exact path.",
271
+ "Read a note by file name (wikilink-style) or exact path.\n\nParameters:\n file (optional) — note name using wikilink resolution (e.g. 'My Note')\n path (optional) — exact vault-relative path (e.g. 'folder/My Note.md')\n One of file or path is required.\n\nExamples:\n obsidian_read({ file: \"Meeting Notes\" })\n obsidian_read({ path: \"Projects/todo.md\" })",
261
272
  {
262
273
  file: z.string().optional().describe("File name (wikilink resolution)"),
263
274
  path: z.string().optional().describe("Exact file path"),
@@ -271,7 +282,7 @@ server.tool(
271
282
 
272
283
  server.tool(
273
284
  "obsidian_search",
274
- "Full-text search across the vault with line context.",
285
+ "Full-text search across the vault with line context.\n\nParameters:\n query (required) — search terms, supports Obsidian query syntax\n path (optional) — restrict results to a folder path\n limit (optional) — max number of files to return\n\nExamples:\n obsidian_search({ query: \"meeting notes\" })\n obsidian_search({ query: \"project status\", path: \"Work/\", limit: 5 })",
275
286
  {
276
287
  query: z.string().describe("Search query"),
277
288
  path: z.string().optional().describe("Limit to folder"),
@@ -287,7 +298,7 @@ server.tool(
287
298
 
288
299
  server.tool(
289
300
  "obsidian_tags",
290
- "List tags in the vault with counts.",
301
+ "List tags in the vault with counts.\n\nParameters:\n sort (optional) — 'name' or 'count' (default: name)\n\nExamples:\n obsidian_tags({})\n obsidian_tags({ sort: \"count\" })",
291
302
  {
292
303
  sort: z.enum(["name", "count"]).optional().describe("Sort order"),
293
304
  },
@@ -300,7 +311,7 @@ server.tool(
300
311
 
301
312
  server.tool(
302
313
  "obsidian_tasks",
303
- "List tasks. Use daily=true for today's tasks only.",
314
+ "List tasks from vault notes.\n\nParameters:\n daily (optional) — true to show only today's daily note tasks\n todo (optional) — true to show only incomplete tasks\n done (optional) — true to show only completed tasks\n path (optional) — filter by file path\n\nExamples:\n obsidian_tasks({ daily: true })\n obsidian_tasks({ todo: true, path: \"Projects/\" })",
304
315
  {
305
316
  daily: z.boolean().optional().describe("Show only daily note tasks"),
306
317
  todo: z.boolean().optional().describe("Show incomplete tasks only"),
@@ -319,7 +330,7 @@ server.tool(
319
330
 
320
331
  server.tool(
321
332
  "obsidian_properties",
322
- "List or read frontmatter properties.",
333
+ "List or read frontmatter properties.\n\nParameters:\n file (optional) — note name for wikilink resolution\n path (optional) — exact file path\n name (optional) — specific property name to read (requires file or path)\n\nExamples:\n obsidian_properties({}) — list all properties with counts\n obsidian_properties({ file: \"My Note\" }) — properties of a specific note\n obsidian_properties({ file: \"My Note\", name: \"status\" }) — read one property",
323
334
  {
324
335
  file: z.string().optional().describe("File name"),
325
336
  path: z.string().optional().describe("File path"),
@@ -341,7 +352,7 @@ server.tool(
341
352
 
342
353
  server.tool(
343
354
  "obsidian_create",
344
- "Create a new note.",
355
+ "Create a new note.\n\nParameters:\n name (optional) — file name for the new note\n path (optional) — vault-relative path\n content (optional) — initial markdown content\n template (optional) — template name to use\n\nExamples:\n obsidian_create({ name: \"Meeting 2026-04-03\", content: \"# Meeting Notes\\n\\n- Attendees: ...\" })\n obsidian_create({ path: \"Projects/new-idea.md\", template: \"project\" })",
345
356
  {
346
357
  name: z.string().optional().describe("File name"),
347
358
  path: z.string().optional().describe("File path"),
@@ -360,7 +371,7 @@ server.tool(
360
371
 
361
372
  server.tool(
362
373
  "obsidian_property_set",
363
- "Set a frontmatter property on a note.",
374
+ "Set a frontmatter property on a note.\n\nParameters:\n name (required) — property name\n value (required) — property value\n file (optional) — note name (wikilink resolution)\n path (optional) — exact file path\n One of file or path is required.\n\nExamples:\n obsidian_property_set({ name: \"status\", value: \"done\", file: \"My Task\" })\n obsidian_property_set({ name: \"tags\", value: \"project, active\", path: \"Work/todo.md\" })",
364
375
  {
365
376
  name: z.string().describe("Property name"),
366
377
  value: z.string().describe("Property value"),
@@ -376,7 +387,7 @@ server.tool(
376
387
 
377
388
  server.tool(
378
389
  "obsidian_backlinks",
379
- "List backlinks to a note.",
390
+ "List backlinks to a note.\n\nParameters:\n file (optional) — note name (wikilink resolution)\n path (optional) — exact file path\n\nExamples:\n obsidian_backlinks({ file: \"Project Plan\" })\n obsidian_backlinks({ path: \"Ideas/brainstorm.md\" })",
380
391
  {
381
392
  file: z.string().optional().describe("File name"),
382
393
  path: z.string().optional().describe("File path"),
@@ -389,7 +400,7 @@ server.tool(
389
400
 
390
401
  server.tool(
391
402
  "obsidian_files",
392
- "List files in the vault or a specific folder.",
403
+ "List files in the vault or a specific folder.\n\nParameters:\n folder (optional) — filter by folder path\n ext (optional) — filter by file extension (e.g. 'md', 'canvas')\n\nExamples:\n obsidian_files({})\n obsidian_files({ folder: \"Projects/\", ext: \"md\" })",
393
404
  {
394
405
  folder: z.string().optional().describe("Filter by folder path"),
395
406
  ext: z.string().optional().describe("Filter by extension"),
@@ -404,11 +415,52 @@ server.tool(
404
415
 
405
416
  server.tool(
406
417
  "obsidian_recents",
407
- "List recently opened files.",
418
+ "List recently opened files.\n\nReturns the most recently opened files in the vault, ordered by last access time.",
408
419
  {},
409
420
  async () => runTool("recents"),
410
421
  );
411
422
 
423
+ server.tool(
424
+ "obsidian_help",
425
+ "Get Obsidian reference documentation on a topic.\n\nParameters:\n topic (required) — one of: cli, markdown, bases, canvas\n\nExamples:\n obsidian_help({ topic: \"markdown\" }) — wikilinks, embeds, callouts, properties, tags\n obsidian_help({ topic: \"bases\" }) — Bases YAML schema, filters, formulas, views\n obsidian_help({ topic: \"canvas\" }) — JSON Canvas nodes, edges, colors, layout\n obsidian_help({ topic: \"cli\" }) — CLI command syntax and parameter patterns",
426
+ { topic: z.enum(["cli", "markdown", "bases", "canvas"]).describe("Reference topic") },
427
+ async ({ topic }) => ({
428
+ content: [{ type: "text", text: promptContent[`obsidian-${topic}`] }],
429
+ }),
430
+ );
431
+
432
+ // ---- MCP Prompts -----------------------------------------------------------
433
+
434
+ const promptMeta = {
435
+ "obsidian-cli": {
436
+ title: "Obsidian CLI Reference",
437
+ description: "CLI usage patterns, parameter syntax, and command examples for the Obsidian CLI. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
438
+ },
439
+ "obsidian-markdown": {
440
+ title: "Obsidian Flavored Markdown Reference",
441
+ description: "Wikilinks, embeds, callouts, properties, tags, and other Obsidian-specific markdown syntax. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
442
+ },
443
+ "obsidian-bases": {
444
+ title: "Obsidian Bases Reference",
445
+ description: "Bases syntax for database-like views: filters, formulas, view types, and summaries. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
446
+ },
447
+ "obsidian-canvas": {
448
+ title: "JSON Canvas Reference",
449
+ description: "JSON Canvas format for visual canvases: node types, edges, groups, and layout. Adapted from kepano/obsidian-skills (MIT License, https://github.com/kepano/obsidian-skills).",
450
+ },
451
+ };
452
+
453
+ for (const [name, content] of Object.entries(promptContent)) {
454
+ const meta = promptMeta[name];
455
+ server.registerPrompt(
456
+ name,
457
+ { title: meta.title, description: meta.description },
458
+ () => ({
459
+ messages: [{ role: "user", content: { type: "text", text: content } }],
460
+ })
461
+ );
462
+ }
463
+
412
464
  // ---- Start ---------------------------------------------------------------
413
465
 
414
466
  async function main() {
@@ -1,15 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(node:*)",
5
- "Bash(git checkout:*)",
6
- "Bash(git push:*)",
7
- "Bash(gh api:*)",
8
- "Bash(gh pr:*)",
9
- "Bash(git pull:*)",
10
- "Bash(git stash:*)",
11
- "Bash(npm version:*)",
12
- "Bash(npm publish:*)"
13
- ]
14
- }
15
- }