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.
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Batch CLI command types
3
+ */
4
+ export {};