roam-research-mcp 1.6.0 → 2.4.3

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.
@@ -1,8 +1,8 @@
1
1
  import { Command } from 'commander';
2
- import { initializeGraph } from '@roam-research/roam-api-sdk';
3
- import { API_TOKEN, GRAPH_NAME } from '../../config/environment.js';
4
2
  import { SearchOperations } from '../../tools/operations/search/index.js';
5
3
  import { printDebug, exitWithError } from '../utils/output.js';
4
+ import { resolveGraph } from '../utils/graph.js';
5
+ import { readStdin } from '../utils/input.js';
6
6
  /**
7
7
  * Format results grouped by page (default output)
8
8
  */
@@ -72,46 +72,77 @@ function parseIdentifier(identifier) {
72
72
  }
73
73
  export function createRefsCommand() {
74
74
  return new Command('refs')
75
- .description('Find blocks referencing a page or block')
76
- .argument('<identifier>', 'Page title or block UID (use ((uid)) for block refs)')
75
+ .description('Find all blocks that reference a page, tag, or block')
76
+ .argument('[identifier]', 'Page title, #tag, [[Page]], or ((block-uid)). Reads from stdin if "-" or omitted.')
77
77
  .option('-n, --limit <n>', 'Limit number of results', '50')
78
78
  .option('--json', 'Output as JSON array')
79
79
  .option('--raw', 'Output raw UID + content lines (no grouping)')
80
80
  .option('--debug', 'Show query metadata')
81
+ .option('-g, --graph <name>', 'Target graph key (for multi-graph mode)')
82
+ .addHelpText('after', `
83
+ Examples:
84
+ # Page references
85
+ roam refs "Project Alpha" # Blocks linking to page
86
+ roam refs "#TODO" # Blocks with #TODO tag
87
+
88
+ # Stdin / Batch references
89
+ echo "Project A" | roam refs # Pipe page title
90
+ cat uids.txt | roam refs --json # Find refs for multiple UIDs
91
+
92
+ # Block references
93
+ roam refs "((abc123def))" # Blocks embedding this block
94
+ `)
81
95
  .action(async (identifier, options) => {
82
96
  try {
83
- const graph = initializeGraph({
84
- token: API_TOKEN,
85
- graph: GRAPH_NAME
86
- });
97
+ const graph = resolveGraph(options, false);
87
98
  const limit = parseInt(options.limit || '50', 10);
88
- const { block_uid, title } = parseIdentifier(identifier);
89
- if (options.debug) {
90
- printDebug('Identifier', identifier);
91
- printDebug('Parsed', { block_uid, title });
92
- printDebug('Options', options);
99
+ // Determine identifiers
100
+ let identifiers = [];
101
+ if (identifier && identifier !== '-') {
102
+ identifiers = [identifier];
93
103
  }
94
- const searchOps = new SearchOperations(graph);
95
- const result = await searchOps.searchBlockRefs({ block_uid, title });
96
- if (options.debug) {
97
- printDebug('Total matches', result.matches.length);
98
- }
99
- // Apply limit
100
- const limitedMatches = result.matches.slice(0, limit);
101
- // Format output
102
- if (options.json) {
103
- const jsonOutput = limitedMatches.map(m => ({
104
- uid: m.block_uid,
105
- content: m.content,
106
- page: m.page_title
107
- }));
108
- console.log(JSON.stringify(jsonOutput, null, 2));
104
+ else {
105
+ if (process.stdin.isTTY && identifier !== '-') {
106
+ exitWithError('Identifier is required. Use: roam refs <title> or pipe identifiers via stdin');
107
+ }
108
+ const input = await readStdin();
109
+ if (input) {
110
+ identifiers = input.split('\n').map(t => t.trim()).filter(Boolean);
111
+ }
109
112
  }
110
- else if (options.raw) {
111
- console.log(formatRaw(limitedMatches));
113
+ if (identifiers.length === 0) {
114
+ exitWithError('No identifiers provided');
112
115
  }
113
- else {
114
- console.log(formatGrouped(limitedMatches));
116
+ const searchOps = new SearchOperations(graph);
117
+ // Helper to process a single identifier
118
+ const processIdentifier = async (id) => {
119
+ const { block_uid, title } = parseIdentifier(id);
120
+ if (options.debug) {
121
+ printDebug('Identifier', id);
122
+ printDebug('Parsed', { block_uid, title });
123
+ }
124
+ const result = await searchOps.searchBlockRefs({ block_uid, title });
125
+ const limitedMatches = result.matches.slice(0, limit);
126
+ if (options.json) {
127
+ return JSON.stringify(limitedMatches.map(m => ({
128
+ uid: m.block_uid,
129
+ content: m.content,
130
+ page: m.page_title
131
+ })));
132
+ }
133
+ else if (options.raw) {
134
+ return formatRaw(limitedMatches);
135
+ }
136
+ else {
137
+ return formatGrouped(limitedMatches);
138
+ }
139
+ };
140
+ // Execute
141
+ for (const id of identifiers) {
142
+ const output = await processIdentifier(id);
143
+ console.log(output);
144
+ if (identifiers.length > 1 && !options.json)
145
+ console.log('\n---\n');
115
146
  }
116
147
  }
117
148
  catch (error) {
@@ -0,0 +1,58 @@
1
+ import { Command } from 'commander';
2
+ import { updatePage } from '@roam-research/roam-api-sdk';
3
+ import { printDebug, exitWithError } from '../utils/output.js';
4
+ import { resolveGraph } from '../utils/graph.js';
5
+ export function createRenameCommand() {
6
+ return new Command('rename')
7
+ .description('Rename a page')
8
+ .argument('<old-title>', 'Current page title (or use --uid for UID)')
9
+ .argument('<new-title>', 'New page title')
10
+ .option('-u, --uid <uid>', 'Use page UID instead of title')
11
+ .option('-g, --graph <name>', 'Target graph key (multi-graph mode)')
12
+ .option('--write-key <key>', 'Write confirmation key (non-default graphs)')
13
+ .option('--debug', 'Show debug information')
14
+ .addHelpText('after', `
15
+ Examples:
16
+ # Rename by title
17
+ roam rename "Old Page Name" "New Page Name"
18
+
19
+ # Rename by UID
20
+ roam rename --uid abc123def "New Page Name"
21
+
22
+ # Multi-graph
23
+ roam rename "Draft" "Published" -g work --write-key confirm
24
+ `)
25
+ .action(async (oldTitle, newTitle, options) => {
26
+ try {
27
+ if (options.debug) {
28
+ printDebug('Old title', oldTitle);
29
+ printDebug('New title', newTitle);
30
+ printDebug('UID', options.uid || 'none (using title)');
31
+ printDebug('Graph', options.graph || 'default');
32
+ }
33
+ const graph = resolveGraph(options, true); // Write operation
34
+ // Build the page identifier
35
+ const pageIdentifier = options.uid
36
+ ? { uid: options.uid }
37
+ : { title: oldTitle };
38
+ if (options.debug) {
39
+ printDebug('Page identifier', pageIdentifier);
40
+ }
41
+ const success = await updatePage(graph, {
42
+ page: pageIdentifier,
43
+ title: newTitle
44
+ });
45
+ if (success) {
46
+ const identifier = options.uid ? `((${options.uid}))` : `"${oldTitle}"`;
47
+ console.log(`Renamed ${identifier} → "${newTitle}"`);
48
+ }
49
+ else {
50
+ exitWithError('Failed to rename page (API returned false)');
51
+ }
52
+ }
53
+ catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ exitWithError(message);
56
+ }
57
+ });
58
+ }