roam-research-mcp 1.6.0 → 2.4.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/README.md CHANGED
@@ -1,13 +1,13 @@
1
- ![](./roam-research-mcp-image.jpeg)
1
+ ![Roam Research MCP + CLI](./roam-research-mcp-header.png)
2
2
 
3
- # Roam Research MCP Server
3
+ # Roam Research MCP + CLI
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/roam-research-mcp.svg)](https://badge.fury.io/js/roam-research-mcp)
6
- [![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip)
6
+ [![Project Status: Active](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
8
  [![GitHub](https://img.shields.io/github/license/2b3pro/roam-research-mcp)](https://github.com/2b3pro/roam-research-mcp/blob/main/LICENSE)
9
9
 
10
- A Model Context Protocol (MCP) server and standalone CLI that provides comprehensive access to Roam Research's API functionality. The MCP server enables AI assistants like Claude to interact with your Roam Research graph through a standardized interface, while the CLI (`roam`) lets you fetch, search, and import content directly from the command line. Supports standard input/output (stdio) and HTTP Stream communication. (A WORK-IN-PROGRESS, personal project not officially endorsed by Roam Research)
10
+ A Model Context Protocol (MCP) server and standalone CLI that provides comprehensive access to Roam Research's API functionality. The MCP server enables AI assistants like Claude to interact with your Roam Research graph through a standardized interface, while the CLI (`roam`) lets you fetch, search, and import content directly from the command line. Supports multi-graph configurations, write protection, standard input/output (stdio) and HTTP Stream communication. (Personal project, not officially endorsed by Roam Research)
11
11
 
12
12
  <a href="https://glama.ai/mcp/servers/fzfznyaflu"><img width="380" height="200" src="https://glama.ai/mcp/servers/fzfznyaflu/badge" alt="Roam Research MCP server" /></a>
13
13
  <a href="https://mseep.ai/app/2b3pro-roam-research-mcp"><img width="380" height="200" src="https://mseep.net/pr/2b3pro-roam-research-mcp-badge.png" alt="MseeP.ai Security Assessment Badge" /></a>
@@ -85,7 +85,7 @@ docker run -p 3000:3000 -p 8088:8088 --env-file .env roam-research-mcp
85
85
 
86
86
  ## Standalone CLI: `roam`
87
87
 
88
- A standalone command-line tool for interacting with Roam Research directly, without running the MCP server. Provides four subcommands: `get`, `search`, `save`, and `refs`.
88
+ A standalone command-line tool for interacting with Roam Research directly, without running the MCP server. Provides eight subcommands: `get`, `search`, `save`, `refs`, `update`, `batch`, `rename`, and `status`.
89
89
 
90
90
  ### Installation
91
91
 
@@ -111,7 +111,7 @@ Configure via `.env` file in the project root or set as environment variables.
111
111
 
112
112
  ---
113
113
 
114
- ### `roam get` - Fetch pages or blocks
114
+ ### `roam get` - Fetch pages, blocks, or TODOs
115
115
 
116
116
  Fetch content from Roam and output as markdown or JSON.
117
117
 
@@ -132,6 +132,18 @@ roam get "Daily Notes" --depth 2
132
132
  # Flatten hierarchy
133
133
  roam get "Daily Notes" --flat
134
134
 
135
+ # Fetch TODO items
136
+ roam get --todo
137
+
138
+ # Fetch DONE items
139
+ roam get --done
140
+
141
+ # Filter TODOs by page
142
+ roam get --todo -p "January 2nd, 2026"
143
+
144
+ # Include/exclude filter
145
+ roam get --todo -i "urgent,important" -e "someday"
146
+
135
147
  # Debug mode
136
148
  roam get "Daily Notes" --debug
137
149
  ```
@@ -141,6 +153,11 @@ roam get "Daily Notes" --debug
141
153
  - `--depth <n>` - Child levels to fetch (default: 4)
142
154
  - `--refs <n>` - Block ref expansion depth (default: 1)
143
155
  - `--flat` - Flatten hierarchy to single-level list
156
+ - `--todo` - Fetch TODO items
157
+ - `--done` - Fetch DONE items
158
+ - `-p, --page <title>` - Filter TODOs/DONEs by page title
159
+ - `-i, --include <terms>` - Include only items containing these terms (comma-separated)
160
+ - `-e, --exclude <terms>` - Exclude items containing these terms (comma-separated)
144
161
  - `--debug` - Show query metadata
145
162
 
146
163
  ---
@@ -186,9 +203,9 @@ roam search "keyword" --json
186
203
 
187
204
  ---
188
205
 
189
- ### `roam save` - Import markdown
206
+ ### `roam save` - Import markdown or create TODOs
190
207
 
191
- Import markdown content to Roam, creating or updating pages.
208
+ Import markdown content to Roam, creating or updating pages, or add TODO items.
192
209
 
193
210
  ```bash
194
211
  # From a file (title derived from filename)
@@ -211,11 +228,21 @@ roam save --title "Quick Note" << EOF
211
228
  - Item 2
212
229
  - Nested item
213
230
  EOF
231
+
232
+ # Create a TODO item on today's daily page
233
+ roam save --todo "Buy groceries"
234
+
235
+ # Create multiple TODOs from stdin (newline-separated)
236
+ echo -e "Task 1\nTask 2\nTask 3" | roam save --todo
237
+
238
+ # Pipe TODO list from file
239
+ cat todos.txt | roam save --todo
214
240
  ```
215
241
 
216
242
  **Options:**
217
243
  - `--title <title>` - Page title (defaults to filename without `.md`)
218
244
  - `--update` - Update existing page using smart diff (preserves block UIDs)
245
+ - `-t, --todo [text]` - Add a TODO item to today's daily page (text or stdin)
219
246
  - `--debug` - Show debug information
220
247
 
221
248
  **Features:**
@@ -223,6 +250,7 @@ EOF
223
250
  - Automatically links the new page from today's daily page
224
251
  - Converts standard markdown to Roam-flavored markdown
225
252
  - Smart diff mode (`--update`) preserves block UIDs for existing content
253
+ - TODO mode creates `{{[[TODO]]}}` items on the daily page
226
254
 
227
255
  ---
228
256
 
@@ -278,6 +306,103 @@ JSON output for programmatic use:
278
306
 
279
307
  ---
280
308
 
309
+ ### `roam batch` - Execute multiple operations in one API call
310
+
311
+ Execute multiple operations in a single batch API call to reduce rate limiting issues.
312
+
313
+ ```bash
314
+ # From a JSON file
315
+ roam batch commands.json
316
+
317
+ # From stdin
318
+ cat commands.json | roam batch
319
+
320
+ # Dry run (validate without executing)
321
+ roam batch --dry-run commands.json
322
+
323
+ # With debug output
324
+ roam batch --debug commands.json
325
+ ```
326
+
327
+ **Command Format:**
328
+
329
+ Input is a JSON array of command objects:
330
+
331
+ ```json
332
+ [
333
+ { "command": "create", "params": { "text": "Parent block", "parent": "pageUid", "as": "p1" } },
334
+ { "command": "create", "params": { "text": "Child block", "parent": "{{p1}}" } },
335
+ { "command": "todo", "params": { "text": "Task item" } },
336
+ { "command": "codeblock", "params": { "parent": "{{p1}}", "language": "ts", "code": "const x = 1;" } }
337
+ ]
338
+ ```
339
+
340
+ **Supported Commands:**
341
+
342
+ | Command | Description |
343
+ |---------|-------------|
344
+ | `create` | Create a block |
345
+ | `update` | Update block content |
346
+ | `delete` | Delete a block |
347
+ | `move` | Move block to new location |
348
+ | `todo` | Create TODO item |
349
+ | `table` | Create table structure |
350
+ | `outline` | Create nested outline |
351
+ | `remember` | Create tagged memory |
352
+ | `page` | Create page with content |
353
+ | `codeblock` | Create code block |
354
+
355
+ **Features:**
356
+
357
+ - Placeholder references (`{{name}}`) for cross-command dependencies
358
+ - Automatic page title → UID resolution
359
+ - Daily page auto-resolution for `todo` and `remember`
360
+ - Level-based hierarchy for `outline` command
361
+ - `--dry-run` mode for validation
362
+
363
+ **Options:**
364
+ - `--dry-run` - Validate and show planned actions without executing
365
+ - `--debug` - Show debug information
366
+ - `-g, --graph <name>` - Target graph (multi-graph mode)
367
+ - `--write-key <key>` - Write confirmation key
368
+
369
+ See [docs/batch-cli-spec.md](docs/batch-cli-spec.md) for full specification.
370
+
371
+ ---
372
+
373
+ ### `roam status` - Show available graphs
374
+
375
+ Display configured graphs and their connection status.
376
+
377
+ ```bash
378
+ # Show available graphs
379
+ roam status
380
+
381
+ # Test connectivity to all graphs
382
+ roam status --ping
383
+
384
+ # Output as JSON
385
+ roam status --json
386
+ ```
387
+
388
+ **Example Output:**
389
+
390
+ ```
391
+ Roam Research MCP v2.4.0
392
+
393
+ Graphs:
394
+ • personal (default) ✓ connected
395
+ • work [protected] ✓ connected
396
+
397
+ Write-protected graphs require --write-key flag for modifications.
398
+ ```
399
+
400
+ **Options:**
401
+ - `--ping` - Test connection to each graph
402
+ - `--json` - Output as JSON for scripting
403
+
404
+ ---
405
+
281
406
  ## To Test
282
407
 
283
408
  Run [MCP Inspector](https://github.com/modelcontextprotocol/inspector) after build using the provided npm script:
@@ -304,7 +429,7 @@ The server provides powerful tools for interacting with Roam Research:
304
429
 
305
430
  1. `roam_fetch_page_by_title`: Fetch page content by title. Returns content in the specified format.
306
431
  2. `roam_fetch_block_with_children`: Fetch a block by its UID along with its hierarchical children down to a specified depth. Automatically handles `((UID))` formatting.
307
- 3. `roam_create_page`: Create new pages with optional content and headings. **Now supports mixed content types** - content array can include both text blocks and tables in a single call using `{type: "table", headers, rows}` format. Creates a block on the daily page linking to the newly created page.
432
+ 3. `roam_create_page`: Create new pages with optional content and headings. **Now supports mixed content types** - content array can include both text blocks and tables in a single call using `{type: "table", headers, rows}` format. Automatically adds a "Processed: [[date]]" block at the end of the page linking to today's daily page.
308
433
  4. `roam_create_table`: Create a properly formatted Roam table with specified headers and rows. Abstracts Roam's complex nested table structure, validates row/column consistency, and handles empty cells automatically.
309
434
  5. `roam_import_markdown`: Import nested markdown content under a specific block. (Internally uses `roam_process_batch_actions`.)
310
435
  6. `roam_add_todo`: Add a list of todo items to today's daily page. (Internally uses `roam_process_batch_actions`.)
@@ -316,12 +441,13 @@ The server provides powerful tools for interacting with Roam Research:
316
441
  12. `roam_search_by_status`: Search for blocks with a specific status (TODO/DONE) across all pages or within a specific page.
317
442
  13. `roam_search_by_date`: Search for blocks or pages based on creation or modification dates.
318
443
  14. `roam_search_for_tag`: Search for blocks containing a specific tag and optionally filter by blocks that also contain another tag nearby or exclude blocks with a specific tag. This tool supports pagination via the `limit` and `offset` parameters.
319
- 15. `roam_remember`: Add a memory or piece of information to remember. (Internally uses `roam_process_batch_actions`.)
444
+ 15. `roam_remember`: Add a memory or piece of information to remember. Supports optional `heading` parameter to nest under a specific heading on the daily page (created if missing), or `parent_uid` to nest under a specific block. (Internally uses `roam_process_batch_actions`.)
320
445
  16. `roam_recall`: Retrieve all stored memories.
321
446
  17. `roam_datomic_query`: Execute a custom Datomic query on the Roam graph for advanced data retrieval beyond the available search tools. Now supports client-side regex filtering for enhanced post-query processing. Optimal for complex filtering (including regex), highly complex boolean logic, arbitrary sorting criteria, and proximity search.
322
447
  18. `roam_markdown_cheatsheet`: Provides the content of the Roam Markdown Cheatsheet resource, optionally concatenated with custom instructions if `CUSTOM_INSTRUCTIONS_PATH` environment variable is set.
323
448
  19. `roam_process_batch_actions`: Execute a sequence of low-level block actions (create, update, move, delete) in a single, non-transactional batch. Provides granular control for complex nesting like tables. **Now includes pre-validation** that catches errors before API execution, with structured error responses and automatic rate limit retry with exponential backoff. (Note: For actions on existing blocks or within a specific page context, it is often necessary to first obtain valid page or block UIDs using tools like `roam_fetch_page_by_title`.)
324
449
  20. `roam_update_page_markdown`: Update an existing page with new markdown content using smart diff. **Preserves block UIDs** where possible, keeping references intact across the graph. Uses three-phase matching (exact text → normalized → position fallback) to generate minimal operations. Supports `dry_run` mode to preview changes. Ideal for syncing external markdown files, AI-assisted content updates, and batch modifications without losing block references.
450
+ 21. `roam_move_block`: Move a block to a new location (different parent or position). Convenience wrapper around `roam_process_batch_actions` for single block moves. Parameters: `block_uid` (required), `parent_uid` (required), `order` (optional, defaults to "last").
325
451
 
326
452
  **Deprecated Tools**:
327
453
  The following tools have been deprecated as of `v0.36.2` in favor of the more powerful and flexible `roam_process_batch_actions`:
@@ -462,9 +588,13 @@ The tool will match existing blocks by content, update changed text, add new blo
462
588
  - Create a new token
463
589
 
464
590
  2. Configure the environment variables:
465
- You have two options for configuring the required environment variables:
466
591
 
467
- Option 1: Using a .env file (Recommended for development)
592
+ ### Single Graph Mode (Default)
593
+
594
+ For most users with one Roam graph, use the simple configuration:
595
+
596
+ **Option 1: Using a .env file (Recommended for development)**
597
+
468
598
  Create a `.env` file in the roam-research directory:
469
599
 
470
600
  ```
@@ -475,7 +605,8 @@ The tool will match existing blocks by content, update changed text, add new blo
475
605
  HTTP_STREAM_PORT=8088 # Or your desired port for HTTP Stream communication
476
606
  ```
477
607
 
478
- Option 2: Using MCP settings (Alternative method)
608
+ **Option 2: Using MCP settings (Alternative method)**
609
+
479
610
  Add the configuration to your MCP settings file. Note that you may need to update the `args` to `["/path/to/roam-research-mcp/build/index.js"]` if you are running the server directly.
480
611
 
481
612
  - For Cline (`~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`):
@@ -501,6 +632,62 @@ The tool will match existing blocks by content, update changed text, add new blo
501
632
 
502
633
  Note: The server will first try to load from .env file, then fall back to environment variables from MCP settings.
503
634
 
635
+ ---
636
+
637
+ ### Multi-Graph Mode (v2.0.0+)
638
+
639
+ For users with multiple Roam graphs, you can configure a single MCP server instance to connect to all of them. This is more token-efficient than running multiple server instances.
640
+
641
+ **Configuration:**
642
+
643
+ ```json
644
+ ROAM_GRAPHS="{\"personal\":{\"token\":\"roam-graph-token-xxx\",\"graph\":\"my-personal-graph\"},\"work\":{\"token\":\"roam-graph-token-yyy\",\"graph\":\"company-graph\",\"write_key\":\"confirm-work-write\"}}"
645
+ ROAM_DEFAULT_GRAPH=personal
646
+ ```
647
+
648
+ | Field | Required | Description |
649
+ |-------|----------|-------------|
650
+ | `token` | Yes | Roam API token for this graph |
651
+ | `graph` | Yes | Roam graph name |
652
+ | `write_key` | No | Required confirmation string for writes to non-default graphs |
653
+
654
+ **Usage in Tools:**
655
+
656
+ All tools accept optional `graph` and `write_key` parameters:
657
+
658
+ ```json
659
+ {
660
+ "title": "My Page",
661
+ "graph": "work",
662
+ "write_key": "confirm-work-write"
663
+ }
664
+ ```
665
+
666
+ - **Read operations**: Can target any graph using the `graph` parameter
667
+ - **Write operations on default graph**: Work without additional parameters
668
+ - **Write operations on non-default graphs**: Require the `write_key` if configured
669
+
670
+ **CLI Usage:**
671
+
672
+ All CLI commands support the `-g, --graph` flag:
673
+
674
+ ```bash
675
+ # Read from work graph
676
+ roam get "Meeting Notes" -g work
677
+
678
+ # Write to work graph (requires --write-key if configured)
679
+ roam save notes.md -g work --write-key "confirm-work-write"
680
+ ```
681
+
682
+ **Safety Model:**
683
+
684
+ The `write_key` serves as a confirmation gate (not a secret) to prevent accidental writes to non-default graphs. When a write is attempted without the required key, the error message reveals the expected key:
685
+
686
+ ```
687
+ Write to "work" graph requires write_key confirmation.
688
+ Provide write_key: "confirm-work-write" to proceed.
689
+ ```
690
+
504
691
  3. Build the server (make sure you're in the root directory of the MCP):
505
692
 
506
693
  Note: Customize 'Roam_Markdown_Cheatsheet.md' with any notes and preferences specific to your graph BEFORE building.
@@ -1,4 +1,4 @@
1
- # Roam Markdown Cheatsheet — Generic Foundation v2.0.0
1
+ # Roam Markdown Cheatsheet — Generic Foundation v2.0.1
2
2
 
3
3
  > ⚠️ **MODEL DIRECTIVE**: Always consult this cheatsheet BEFORE making any Roam tool calls. Syntax errors in Roam are unforgiving.
4
4
 
@@ -36,6 +36,8 @@
36
36
 
37
37
  ⚠️ **CRITICAL**: Never concatenate multi-word tags. `#knowledgemanagement` ≠ `#[[knowledge management]]`
38
38
 
39
+ ⚠️ **CRITICAL**: Never use `#` to mean "number" (e.g., `#1`, `#2`). In Roam, `#` **always** creates a hashtag. Write `Step 1`, `No. 1`, or just spell out the number instead.
40
+
39
41
  ### Dates
40
42
  - **Always use ordinal format**: `[[January 1st, 2025]]`, `[[December 23rd, 2024]]`
41
43
  - Ordinals: 1st, 2nd, 3rd, 4th–20th, 21st, 22nd, 23rd, 24th–30th, 31st
@@ -72,7 +74,7 @@ Source:: https://example.com
72
74
  | `Note:: Some observation` | Just write the text, or use `#note` | One-off labels don't need attribute syntax |
73
75
  | `Summary:: The main point` | `**Summary:** The main point` | Section headers are formatting, not metadata |
74
76
  | `Definition:: Some text` | `Term:: Definition` | Only use for actual definitions you want to query |
75
- | `Implementation Tier 3 (Societal Restructuring):: Some text` | `** Implementation Tier 3 (Societal Restructuring)**: Some text` | Label is specific to current concept |
77
+ | `Implementation Tier 3 (Societal Restructuring):: Some text` | `**Implementation Tier 3 (Societal Restructuring):** Some text` | Label is specific to current concept |
76
78
 
77
79
  ⚠️ **The Test**: Ask yourself: "Will I ever query for all blocks with this attribute across my graph?" If no, use **bold formatting** (`**Label:**`) instead of `::` syntax.
78
80
 
@@ -174,6 +176,7 @@ Tables use nested indentation. Each column header/cell nests ONE LEVEL DEEPER th
174
176
  |----------|-----------|-----|
175
177
  | `Step 1:: Do this` | `**Step 1:** Do this` | `::` creates queryable attributes; use bold for page-specific labels |
176
178
  | `#multiplewords` | `#[[multiple words]]` | Concatenated tags create dead references |
179
+ | `#1`, `#2`, `#3` | `Step 1`, `No. 1`, or spell out | `#` always creates hashtags, never means "number" |
177
180
  | `[[january 1, 2025]]` | `[[January 1st, 2025]]` | Must use ordinal format with proper capitalization |
178
181
  | `[text](((block-uid)))` | `[text](<((block-uid))>)` | Block ref links need angle bracket wrapper |
179
182
  | `{{embed: ((uid))}}` | `{{[[embed]]: ((uid))}}` | Embed requires double brackets around keyword |
@@ -198,9 +201,17 @@ CREATING CONTENT IN ROAM:
198
201
  │ └─ Complex/nested markdown → roam_import_markdown
199
202
  │ (for deeply nested content, tables, etc.)
200
203
 
204
+ ├─ Replacing/revising ENTIRE page content?
205
+ │ └─ roam_update_page_markdown
206
+ │ (fetches page internally, computes smart diff, preserves UIDs)
207
+
201
208
  ├─ Need to CREATE, UPDATE, MOVE, or DELETE individual blocks?
202
209
  │ └─ roam_process_batch_actions
203
- │ (fine-grained control, temporary UIDs for parent refs)
210
+ │ (fine-grained control, UID placeholders for parent refs)
211
+
212
+ ├─ Creating a TABLE?
213
+ │ └─ roam_create_table
214
+ │ (handles complex nested structure automatically)
204
215
 
205
216
  ├─ Adding a memory/note to remember?
206
217
  │ └─ roam_remember (auto-tags with MEMORIES_TAG)
@@ -210,9 +221,11 @@ CREATING CONTENT IN ROAM:
210
221
 
211
222
  └─ SEARCHING/READING:
212
223
  ├─ Find by tag → roam_search_for_tag
213
- ├─ Find by text → roam_search_by_text
224
+ ├─ Find by text → roam_search_by_text
214
225
  ├─ Find by date range → roam_search_by_date
215
226
  ├─ Find by status → roam_search_by_status
227
+ ├─ Find block/page references → roam_search_block_refs
228
+ ├─ Find pages modified today → roam_find_pages_modified_today
216
229
  ├─ Get page content → roam_fetch_page_by_title
217
230
  ├─ Get block + children → roam_fetch_block_with_children
218
231
  ├─ Recall memories → roam_recall
@@ -226,18 +239,18 @@ CREATING CONTENT IN ROAM:
226
239
  The Roam API has rate limits. Follow these guidelines to minimize API calls:
227
240
 
228
241
  ### Tool Efficiency Ranking (Best to Worst)
229
- 1. **`roam_process_batch_actions`** - Single API call for multiple operations (MOST EFFICIENT)
230
- 2. **`roam_create_page`** - Batches content with page creation
231
- 3. **`roam_create_outline` / `roam_import_markdown`** - Include verification queries (use for smaller operations)
232
- 4. **Multiple sequential tool calls** - Each call = multiple API requests (AVOID)
242
+ 1. **`roam_update_page_markdown`** - Single call: fetches, diffs, and updates (MOST EFFICIENT for revisions)
243
+ 2. **`roam_process_batch_actions`** - Single API call for multiple operations
244
+ 3. **`roam_create_page`** - Batches content with page creation
245
+ 4. **`roam_create_outline` / `roam_import_markdown`** - Include verification queries (use for smaller operations)
246
+ 5. **Multiple sequential tool calls** - Each call = multiple API requests (AVOID)
233
247
 
234
248
  ### Best Practices for Intensive Operations
235
249
 
236
250
  #### When Updating/Revising a Page:
237
- 1. Fetch the page content ONCE at the start
238
- 2. Plan ALL changes needed (creates, updates, deletes)
239
- 3. Execute ALL changes in a SINGLE `roam_process_batch_actions` call
240
- 4. Do NOT fetch-modify-fetch-modify in a loop
251
+ 1. **Preferred**: Use `roam_update_page_markdown` it fetches, diffs, and updates in one call
252
+ 2. **Alternative** (for fine-grained control): Fetch once with `roam_fetch_page_by_title`, then execute ALL changes in a SINGLE `roam_process_batch_actions` call
253
+ 3. Do NOT fetch-modify-fetch-modify in a loop
241
254
 
242
255
  #### When Creating Large Content:
243
256
  - For 10+ blocks: Use `roam_process_batch_actions` with nested structure
@@ -285,6 +298,11 @@ When using `roam_process_batch_actions` to create nested blocks, use **placehold
285
298
 
286
299
  **Do this:**
287
300
  ```
301
+ 1. roam_update_page_markdown → single call handles fetch, diff, and updates
302
+ ```
303
+
304
+ **Alternative** (when you need fine-grained control):
305
+ ```
288
306
  1. roam_fetch_page_by_title → get page UID and content
289
307
  2. roam_process_batch_actions → ALL creates/updates in one call
290
308
  ```
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Resolver for page/block title lookups before batch execution
3
+ */
4
+ import { q } from '@roam-research/roam-api-sdk';
5
+ import { capitalizeWords } from '../../tools/helpers/text.js';
6
+ import { formatRoamDate } from '../../utils/helpers.js';
7
+ import { pageUidCache } from '../../cache/page-uid-cache.js';
8
+ /**
9
+ * Resolve a page title to its UID, trying multiple case variations
10
+ */
11
+ export async function resolvePageUid(graph, title) {
12
+ // Check cache first
13
+ const cachedUid = pageUidCache.get(title);
14
+ if (cachedUid) {
15
+ return cachedUid;
16
+ }
17
+ // Try different case variations
18
+ const variations = [
19
+ title,
20
+ capitalizeWords(title),
21
+ title.toLowerCase()
22
+ ];
23
+ const orClause = variations.map(v => `[?e :node/title "${v}"]`).join(' ');
24
+ const searchQuery = `[:find ?uid .
25
+ :where [?e :block/uid ?uid]
26
+ (or ${orClause})]`;
27
+ const result = await q(graph, searchQuery, []);
28
+ const uid = (result === null || result === undefined) ? null : String(result);
29
+ // Cache the result
30
+ if (uid) {
31
+ pageUidCache.set(title, uid);
32
+ }
33
+ return uid;
34
+ }
35
+ /**
36
+ * Get today's daily page title in Roam format
37
+ */
38
+ export function getDailyPageTitle() {
39
+ return formatRoamDate(new Date());
40
+ }
41
+ /**
42
+ * Resolve today's daily page UID
43
+ */
44
+ export async function resolveDailyPageUid(graph) {
45
+ const dailyTitle = getDailyPageTitle();
46
+ return resolvePageUid(graph, dailyTitle);
47
+ }
48
+ /**
49
+ * Collect all unique page titles that need resolution from commands
50
+ */
51
+ export function collectPageTitles(commands) {
52
+ const titles = new Set();
53
+ for (const cmd of commands) {
54
+ const params = cmd.params;
55
+ // Commands that can have 'page' param
56
+ if ('page' in params && typeof params.page === 'string') {
57
+ titles.add(params.page);
58
+ }
59
+ // Remember command can have heading that needs parent page resolution
60
+ // But heading lookup is handled separately
61
+ // Todo/remember without explicit page need daily page
62
+ if (cmd.command === 'todo' || cmd.command === 'remember') {
63
+ if (!('page' in params) && !('pageUid' in params) && !('parent' in params)) {
64
+ titles.add(getDailyPageTitle());
65
+ }
66
+ }
67
+ }
68
+ return titles;
69
+ }
70
+ /**
71
+ * Check if any commands need daily page resolution
72
+ */
73
+ export function needsDailyPage(commands) {
74
+ for (const cmd of commands) {
75
+ const params = cmd.params;
76
+ if (cmd.command === 'todo' || cmd.command === 'remember') {
77
+ if (!('page' in params) && !('pageUid' in params) && !('parent' in params)) {
78
+ return true;
79
+ }
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+ /**
85
+ * Resolve all page titles to UIDs
86
+ * Returns a map of title -> uid
87
+ */
88
+ export async function resolveAllPages(graph, titles) {
89
+ const resolved = new Map();
90
+ // Resolve in parallel for efficiency
91
+ const entries = Array.from(titles);
92
+ const results = await Promise.all(entries.map(async (title) => {
93
+ const uid = await resolvePageUid(graph, title);
94
+ return [title, uid];
95
+ }));
96
+ for (const [title, uid] of results) {
97
+ if (uid) {
98
+ resolved.set(title, uid);
99
+ }
100
+ }
101
+ return resolved;
102
+ }
103
+ /**
104
+ * Create initial resolution context
105
+ */
106
+ export function createResolutionContext() {
107
+ return {
108
+ pageUids: new Map(),
109
+ placeholders: new Map(),
110
+ levelStack: [],
111
+ currentParent: null,
112
+ dailyPageUid: null
113
+ };
114
+ }
115
+ /**
116
+ * Resolve a parent reference - could be a UID, placeholder, or page title
117
+ */
118
+ export function resolveParentRef(ref, context) {
119
+ // Check if it's a placeholder reference {{name}}
120
+ const placeholderMatch = ref.match(/^\{\{([^}]+)\}\}$/);
121
+ if (placeholderMatch) {
122
+ const name = placeholderMatch[1];
123
+ return context.placeholders.get(name) || `{{uid:${name}}}`;
124
+ }
125
+ // Check if it's a resolved page title
126
+ if (context.pageUids.has(ref)) {
127
+ return context.pageUids.get(ref);
128
+ }
129
+ // Assume it's a direct UID
130
+ return ref;
131
+ }
132
+ /**
133
+ * Generate a placeholder UID for tracking
134
+ * Returns the placeholder in {{uid:name}} format for batch processing
135
+ */
136
+ export function generatePlaceholder(name) {
137
+ return `{{uid:${name}}}`;
138
+ }