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 +200 -13
- package/build/Roam_Markdown_Cheatsheet.md +30 -12
- package/build/cli/batch/resolver.js +138 -0
- package/build/cli/batch/translator.js +363 -0
- package/build/cli/batch/types.js +4 -0
- package/build/cli/commands/batch.js +352 -0
- package/build/cli/commands/get.js +101 -19
- package/build/cli/commands/refs.js +21 -8
- package/build/cli/commands/rename.js +58 -0
- package/build/cli/commands/save.js +433 -56
- package/build/cli/commands/search.js +179 -18
- package/build/cli/commands/status.js +91 -0
- package/build/cli/commands/update.js +151 -0
- package/build/cli/roam.js +18 -1
- package/build/cli/utils/graph.js +56 -0
- package/build/cli/utils/output.js +34 -0
- package/build/config/environment.js +70 -34
- package/build/config/graph-registry.js +221 -0
- package/build/config/graph-registry.test.js +30 -0
- package/build/search/status-search.js +5 -4
- package/build/server/roam-server.js +98 -53
- package/build/shared/validation.js +10 -5
- package/build/tools/helpers/refs.js +50 -31
- package/build/tools/operations/blocks.js +38 -1
- package/build/tools/operations/memory.js +51 -5
- package/build/tools/operations/pages.js +186 -111
- package/build/tools/operations/search/index.js +5 -1
- package/build/tools/operations/todos.js +1 -1
- package/build/tools/schemas.js +115 -39
- package/build/tools/tool-handlers.js +9 -2
- package/build/utils/helpers.js +22 -0
- package/package.json +8 -5
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translator: converts CLI-style commands to Roam batch actions
|
|
3
|
+
*/
|
|
4
|
+
import { resolveParentRef, generatePlaceholder } from './resolver.js';
|
|
5
|
+
/**
|
|
6
|
+
* Translate a single command to batch actions
|
|
7
|
+
* May return multiple actions (e.g., table, outline)
|
|
8
|
+
*/
|
|
9
|
+
export function translateCommand(command, context) {
|
|
10
|
+
switch (command.command) {
|
|
11
|
+
case 'create':
|
|
12
|
+
return translateCreate(command, context);
|
|
13
|
+
case 'update':
|
|
14
|
+
return translateUpdate(command);
|
|
15
|
+
case 'delete':
|
|
16
|
+
return translateDelete(command);
|
|
17
|
+
case 'move':
|
|
18
|
+
return translateMove(command);
|
|
19
|
+
case 'todo':
|
|
20
|
+
return translateTodo(command, context);
|
|
21
|
+
case 'table':
|
|
22
|
+
return translateTable(command, context);
|
|
23
|
+
case 'outline':
|
|
24
|
+
return translateOutline(command, context);
|
|
25
|
+
case 'remember':
|
|
26
|
+
return translateRemember(command, context);
|
|
27
|
+
case 'page':
|
|
28
|
+
return translatePage(command, context);
|
|
29
|
+
case 'codeblock':
|
|
30
|
+
return translateCodeblock(command, context);
|
|
31
|
+
default:
|
|
32
|
+
throw new Error(`Unknown command: ${command.command}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve parent UID from command params
|
|
37
|
+
*/
|
|
38
|
+
function getParentUid(params, context) {
|
|
39
|
+
// Direct parent UID or placeholder
|
|
40
|
+
if (params.parent) {
|
|
41
|
+
return resolveParentRef(params.parent, context) || params.parent;
|
|
42
|
+
}
|
|
43
|
+
// Page UID
|
|
44
|
+
if (params.pageUid) {
|
|
45
|
+
return params.pageUid;
|
|
46
|
+
}
|
|
47
|
+
// Page title -> resolved UID
|
|
48
|
+
if (params.page) {
|
|
49
|
+
const uid = context.pageUids.get(params.page);
|
|
50
|
+
if (!uid) {
|
|
51
|
+
throw new Error(`Page "${params.page}" not found`);
|
|
52
|
+
}
|
|
53
|
+
return uid;
|
|
54
|
+
}
|
|
55
|
+
// Default to daily page
|
|
56
|
+
if (context.dailyPageUid) {
|
|
57
|
+
return context.dailyPageUid;
|
|
58
|
+
}
|
|
59
|
+
throw new Error('No parent specified and daily page not resolved');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Register a placeholder in the context
|
|
63
|
+
*/
|
|
64
|
+
function registerPlaceholder(name, context) {
|
|
65
|
+
const placeholder = generatePlaceholder(name);
|
|
66
|
+
context.placeholders.set(name, placeholder);
|
|
67
|
+
return placeholder;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Update level stack for hierarchy tracking
|
|
71
|
+
*/
|
|
72
|
+
function updateLevelStack(level, uid, context) {
|
|
73
|
+
// Ensure stack is long enough
|
|
74
|
+
while (context.levelStack.length < level) {
|
|
75
|
+
context.levelStack.push('');
|
|
76
|
+
}
|
|
77
|
+
context.levelStack[level - 1] = uid;
|
|
78
|
+
// Truncate stack above current level
|
|
79
|
+
context.levelStack.length = level;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get parent from level stack for hierarchical nesting
|
|
83
|
+
*/
|
|
84
|
+
function getParentFromLevel(level, context) {
|
|
85
|
+
if (level <= 1) {
|
|
86
|
+
return context.currentParent;
|
|
87
|
+
}
|
|
88
|
+
const parentLevel = level - 1;
|
|
89
|
+
if (parentLevel <= context.levelStack.length && context.levelStack[parentLevel - 1]) {
|
|
90
|
+
return context.levelStack[parentLevel - 1];
|
|
91
|
+
}
|
|
92
|
+
return context.currentParent;
|
|
93
|
+
}
|
|
94
|
+
// --- Command translators ---
|
|
95
|
+
function translateCreate(cmd, context) {
|
|
96
|
+
const { params } = cmd;
|
|
97
|
+
let parentUid;
|
|
98
|
+
// Handle level-based hierarchy
|
|
99
|
+
if (params.level !== undefined && params.level > 1) {
|
|
100
|
+
const levelParent = getParentFromLevel(params.level, context);
|
|
101
|
+
if (levelParent) {
|
|
102
|
+
parentUid = levelParent;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
parentUid = getParentUid(params, context);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
parentUid = getParentUid(params, context);
|
|
110
|
+
// Set as current parent for level-based children
|
|
111
|
+
if (params.level === 1 || params.level === undefined) {
|
|
112
|
+
context.currentParent = parentUid;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const action = {
|
|
116
|
+
action: 'create-block',
|
|
117
|
+
string: params.text,
|
|
118
|
+
location: {
|
|
119
|
+
'parent-uid': parentUid,
|
|
120
|
+
order: params.order ?? 'last'
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
// Register placeholder if 'as' is specified
|
|
124
|
+
if (params.as) {
|
|
125
|
+
action.uid = registerPlaceholder(params.as, context);
|
|
126
|
+
}
|
|
127
|
+
// Optional properties
|
|
128
|
+
if (params.heading) {
|
|
129
|
+
action.heading = params.heading;
|
|
130
|
+
}
|
|
131
|
+
if (params['children-view-type']) {
|
|
132
|
+
action['children-view-type'] = params['children-view-type'];
|
|
133
|
+
}
|
|
134
|
+
// Update level stack if level is specified
|
|
135
|
+
if (params.level !== undefined && params.as) {
|
|
136
|
+
updateLevelStack(params.level, `{{uid:${params.as}}}`, context);
|
|
137
|
+
}
|
|
138
|
+
return [action];
|
|
139
|
+
}
|
|
140
|
+
function translateUpdate(cmd) {
|
|
141
|
+
const { params } = cmd;
|
|
142
|
+
const action = {
|
|
143
|
+
action: 'update-block',
|
|
144
|
+
uid: params.uid,
|
|
145
|
+
string: params.text
|
|
146
|
+
};
|
|
147
|
+
if (params.heading !== undefined) {
|
|
148
|
+
action.heading = params.heading;
|
|
149
|
+
}
|
|
150
|
+
if (params.open !== undefined) {
|
|
151
|
+
action.open = params.open;
|
|
152
|
+
}
|
|
153
|
+
if (params['text-align']) {
|
|
154
|
+
action['text-align'] = params['text-align'];
|
|
155
|
+
}
|
|
156
|
+
if (params['children-view-type']) {
|
|
157
|
+
action['children-view-type'] = params['children-view-type'];
|
|
158
|
+
}
|
|
159
|
+
return [action];
|
|
160
|
+
}
|
|
161
|
+
function translateDelete(cmd) {
|
|
162
|
+
return [{
|
|
163
|
+
action: 'delete-block',
|
|
164
|
+
uid: cmd.params.uid
|
|
165
|
+
}];
|
|
166
|
+
}
|
|
167
|
+
function translateMove(cmd) {
|
|
168
|
+
const { params } = cmd;
|
|
169
|
+
return [{
|
|
170
|
+
action: 'move-block',
|
|
171
|
+
uid: params.uid,
|
|
172
|
+
location: {
|
|
173
|
+
'parent-uid': params.parent,
|
|
174
|
+
order: params.order ?? 'last'
|
|
175
|
+
}
|
|
176
|
+
}];
|
|
177
|
+
}
|
|
178
|
+
function translateTodo(cmd, context) {
|
|
179
|
+
const { params } = cmd;
|
|
180
|
+
const parentUid = getParentUid(params, context);
|
|
181
|
+
const action = {
|
|
182
|
+
action: 'create-block',
|
|
183
|
+
string: `{{[[TODO]]}} ${params.text}`,
|
|
184
|
+
location: {
|
|
185
|
+
'parent-uid': parentUid,
|
|
186
|
+
order: params.order ?? 'last'
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
if (params.as) {
|
|
190
|
+
action.uid = registerPlaceholder(params.as, context);
|
|
191
|
+
}
|
|
192
|
+
return [action];
|
|
193
|
+
}
|
|
194
|
+
function translateTable(cmd, context) {
|
|
195
|
+
const { params } = cmd;
|
|
196
|
+
const parentUid = getParentUid(params, context);
|
|
197
|
+
const actions = [];
|
|
198
|
+
// Table container
|
|
199
|
+
const tableContainerPlaceholder = params.as
|
|
200
|
+
? registerPlaceholder(params.as, context)
|
|
201
|
+
: registerPlaceholder(`_table_${Date.now()}`, context);
|
|
202
|
+
actions.push({
|
|
203
|
+
action: 'create-block',
|
|
204
|
+
uid: tableContainerPlaceholder,
|
|
205
|
+
string: '{{[[table]]}}',
|
|
206
|
+
location: {
|
|
207
|
+
'parent-uid': parentUid,
|
|
208
|
+
order: params.order ?? 'last'
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
// Create columns (headers)
|
|
212
|
+
const columnPlaceholders = [];
|
|
213
|
+
for (let i = 0; i < params.headers.length; i++) {
|
|
214
|
+
const colPlaceholder = registerPlaceholder(`_col_${i}_${Date.now()}`, context);
|
|
215
|
+
columnPlaceholders.push(colPlaceholder);
|
|
216
|
+
actions.push({
|
|
217
|
+
action: 'create-block',
|
|
218
|
+
uid: colPlaceholder,
|
|
219
|
+
string: params.headers[i] || ' ',
|
|
220
|
+
location: {
|
|
221
|
+
'parent-uid': tableContainerPlaceholder,
|
|
222
|
+
order: i
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
// Create rows under each column
|
|
227
|
+
for (let rowIdx = 0; rowIdx < params.rows.length; rowIdx++) {
|
|
228
|
+
const row = params.rows[rowIdx];
|
|
229
|
+
// First column gets the row label
|
|
230
|
+
actions.push({
|
|
231
|
+
action: 'create-block',
|
|
232
|
+
string: row.label || ' ',
|
|
233
|
+
location: {
|
|
234
|
+
'parent-uid': columnPlaceholders[0],
|
|
235
|
+
order: rowIdx
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
// Remaining columns get the cells
|
|
239
|
+
for (let cellIdx = 0; cellIdx < row.cells.length; cellIdx++) {
|
|
240
|
+
actions.push({
|
|
241
|
+
action: 'create-block',
|
|
242
|
+
string: row.cells[cellIdx] || ' ',
|
|
243
|
+
location: {
|
|
244
|
+
'parent-uid': columnPlaceholders[cellIdx + 1],
|
|
245
|
+
order: rowIdx
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return actions;
|
|
251
|
+
}
|
|
252
|
+
function translateOutline(cmd, context) {
|
|
253
|
+
const { params } = cmd;
|
|
254
|
+
const parentUid = getParentUid(params, context);
|
|
255
|
+
const actions = [];
|
|
256
|
+
// Reset level stack with outline parent
|
|
257
|
+
context.currentParent = parentUid;
|
|
258
|
+
context.levelStack = [];
|
|
259
|
+
for (const item of params.items) {
|
|
260
|
+
let itemParentUid;
|
|
261
|
+
if (item.level === 1) {
|
|
262
|
+
itemParentUid = parentUid;
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
const levelParent = getParentFromLevel(item.level, context);
|
|
266
|
+
if (!levelParent) {
|
|
267
|
+
throw new Error(`Invalid outline hierarchy: level ${item.level} has no parent`);
|
|
268
|
+
}
|
|
269
|
+
itemParentUid = levelParent;
|
|
270
|
+
}
|
|
271
|
+
const action = {
|
|
272
|
+
action: 'create-block',
|
|
273
|
+
string: item.text,
|
|
274
|
+
location: {
|
|
275
|
+
'parent-uid': itemParentUid,
|
|
276
|
+
order: 'last'
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
// Register placeholder
|
|
280
|
+
const placeholderName = item.as || `_outline_${actions.length}_${Date.now()}`;
|
|
281
|
+
action.uid = registerPlaceholder(placeholderName, context);
|
|
282
|
+
if (item.heading) {
|
|
283
|
+
action.heading = item.heading;
|
|
284
|
+
}
|
|
285
|
+
// Update level stack
|
|
286
|
+
updateLevelStack(item.level, action.uid, context);
|
|
287
|
+
actions.push(action);
|
|
288
|
+
}
|
|
289
|
+
return actions;
|
|
290
|
+
}
|
|
291
|
+
function translateRemember(cmd, context) {
|
|
292
|
+
const { params } = cmd;
|
|
293
|
+
// Build memory text with categories
|
|
294
|
+
let memoryText = params.text;
|
|
295
|
+
if (params.categories && params.categories.length > 0) {
|
|
296
|
+
const tags = params.categories.map(cat => `#[[${cat}]]`).join(' ');
|
|
297
|
+
memoryText = `${params.text} ${tags}`;
|
|
298
|
+
}
|
|
299
|
+
// Add MEMORIES_TAG if configured (we'll handle this in the CLI command)
|
|
300
|
+
// For now, just create the block
|
|
301
|
+
let parentUid;
|
|
302
|
+
// If heading is specified, we'd need to look it up or create it
|
|
303
|
+
// For simplicity, require parent UID when heading is used
|
|
304
|
+
// TODO: Add heading resolution support
|
|
305
|
+
if (params.heading && !params.parent) {
|
|
306
|
+
throw new Error('remember with --heading requires --parent or heading resolution not yet implemented');
|
|
307
|
+
}
|
|
308
|
+
parentUid = getParentUid(params, context);
|
|
309
|
+
const action = {
|
|
310
|
+
action: 'create-block',
|
|
311
|
+
string: memoryText,
|
|
312
|
+
location: {
|
|
313
|
+
'parent-uid': parentUid,
|
|
314
|
+
order: params.order ?? 'last'
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
if (params.as) {
|
|
318
|
+
action.uid = registerPlaceholder(params.as, context);
|
|
319
|
+
}
|
|
320
|
+
return [action];
|
|
321
|
+
}
|
|
322
|
+
function translatePage(cmd, context) {
|
|
323
|
+
// Note: Page creation uses create-page API, not batch actions
|
|
324
|
+
// We'll handle this specially in the batch command
|
|
325
|
+
// For now, throw an error to indicate this needs special handling
|
|
326
|
+
throw new Error('page command requires special handling outside batch actions');
|
|
327
|
+
}
|
|
328
|
+
function translateCodeblock(cmd, context) {
|
|
329
|
+
const { params } = cmd;
|
|
330
|
+
const parentUid = getParentUid(params, context);
|
|
331
|
+
// Format code with triple backticks
|
|
332
|
+
const codeContent = '```' + params.language + '\n' + params.code + '\n```';
|
|
333
|
+
const action = {
|
|
334
|
+
action: 'create-block',
|
|
335
|
+
string: codeContent,
|
|
336
|
+
location: {
|
|
337
|
+
'parent-uid': parentUid,
|
|
338
|
+
order: params.order ?? 'last'
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
if (params.as) {
|
|
342
|
+
action.uid = registerPlaceholder(params.as, context);
|
|
343
|
+
}
|
|
344
|
+
return [action];
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Translate all commands to batch actions
|
|
348
|
+
*/
|
|
349
|
+
export function translateAllCommands(commands, context) {
|
|
350
|
+
const actions = [];
|
|
351
|
+
const pageCommands = [];
|
|
352
|
+
for (const cmd of commands) {
|
|
353
|
+
if (cmd.command === 'page') {
|
|
354
|
+
// Collect page commands for special handling
|
|
355
|
+
pageCommands.push(cmd);
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
const cmdActions = translateCommand(cmd, context);
|
|
359
|
+
actions.push(...cmdActions);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return { actions, pageCommands };
|
|
363
|
+
}
|