@roam-research/roam-tools-core 0.5.1 → 0.6.1

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.
Files changed (40) hide show
  1. package/README.md +23 -8
  2. package/dist/index.d.ts +3 -7
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +8 -8
  5. package/dist/operations/blocks.d.ts +21 -22
  6. package/dist/operations/blocks.d.ts.map +1 -1
  7. package/dist/operations/datalog.d.ts +2 -3
  8. package/dist/operations/datalog.d.ts.map +1 -1
  9. package/dist/operations/files.d.ts +6 -7
  10. package/dist/operations/files.d.ts.map +1 -1
  11. package/dist/operations/navigation.d.ts +9 -10
  12. package/dist/operations/navigation.d.ts.map +1 -1
  13. package/dist/operations/pages.d.ts +10 -11
  14. package/dist/operations/pages.d.ts.map +1 -1
  15. package/dist/operations/query.d.ts +10 -11
  16. package/dist/operations/query.d.ts.map +1 -1
  17. package/dist/operations/search.d.ts +7 -8
  18. package/dist/operations/search.d.ts.map +1 -1
  19. package/dist/tools.d.ts +37 -4
  20. package/dist/tools.d.ts.map +1 -1
  21. package/dist/tools.js +47 -52
  22. package/dist/types.d.ts +17 -10
  23. package/dist/types.d.ts.map +1 -1
  24. package/dist/types.js +9 -0
  25. package/package.json +7 -10
  26. package/dist/client.d.ts +0 -34
  27. package/dist/client.d.ts.map +0 -1
  28. package/dist/client.js +0 -275
  29. package/dist/connect.d.ts +0 -10
  30. package/dist/connect.d.ts.map +0 -1
  31. package/dist/connect.js +0 -475
  32. package/dist/graph-resolver.d.ts +0 -54
  33. package/dist/graph-resolver.d.ts.map +0 -1
  34. package/dist/graph-resolver.js +0 -339
  35. package/dist/operations/graphs.d.ts +0 -26
  36. package/dist/operations/graphs.d.ts.map +0 -1
  37. package/dist/operations/graphs.js +0 -213
  38. package/dist/roam-api.d.ts +0 -32
  39. package/dist/roam-api.d.ts.map +0 -1
  40. package/dist/roam-api.js +0 -50
package/dist/client.js DELETED
@@ -1,275 +0,0 @@
1
- // src/core/client.ts
2
- // v2.0.0 - Token-authenticated Roam Local API client
3
- import { readFile } from "fs/promises";
4
- import { homedir } from "os";
5
- import { join } from "path";
6
- import open from "open";
7
- import { EXPECTED_API_VERSION, getErrorMessage, RoamError, ErrorCodes } from "./types.js";
8
- export class RoamClient {
9
- graphName;
10
- graphType;
11
- token;
12
- port = null;
13
- constructor(config) {
14
- if (!config.graphName) {
15
- throw new Error("graphName is required");
16
- }
17
- if (!/^[A-Za-z0-9_-]+$/.test(config.graphName)) {
18
- throw new Error(`Invalid graph name "${config.graphName}". Graph names can only contain letters, numbers, hyphens, and underscores.`);
19
- }
20
- if (!config.token) {
21
- throw new Error("token is required");
22
- }
23
- this.graphName = config.graphName;
24
- this.graphType = config.graphType;
25
- this.token = config.token;
26
- if (config.port) {
27
- this.port = config.port;
28
- }
29
- }
30
- async getPort() {
31
- if (this.port)
32
- return this.port;
33
- try {
34
- const configFile = join(homedir(), ".roam-local-api.json");
35
- const content = await readFile(configFile, "utf-8");
36
- const config = JSON.parse(content);
37
- this.port = config.port;
38
- return this.port;
39
- }
40
- catch {
41
- // Default port if file doesn't exist
42
- return 3333;
43
- }
44
- }
45
- async openRoamDeepLink() {
46
- const deepLink = `roam://#/app/${this.graphName}`;
47
- await open(deepLink);
48
- }
49
- async sleep(ms) {
50
- return new Promise((resolve) => setTimeout(resolve, ms));
51
- }
52
- isConnectionError(error) {
53
- if (error instanceof Error) {
54
- if (error.message.includes("ECONNREFUSED") ||
55
- error.message.includes("fetch failed") ||
56
- error.message.includes("network")) {
57
- return true;
58
- }
59
- // Check error.cause for Node fetch network errors
60
- if (error.cause && error.cause instanceof Error) {
61
- const causeCode = error.cause.code;
62
- return (causeCode === "ECONNREFUSED" ||
63
- causeCode === "ECONNRESET" ||
64
- causeCode === "ENOTFOUND" ||
65
- causeCode === "ETIMEDOUT");
66
- }
67
- }
68
- return false;
69
- }
70
- isVersionMismatch(response) {
71
- if (response.success)
72
- return false;
73
- const error = response.error;
74
- if (typeof error === "object" && error !== null) {
75
- return error.code === "VERSION_MISMATCH";
76
- }
77
- return false;
78
- }
79
- handleVersionMismatch(response) {
80
- const serverVersion = response.apiVersion ?? "unknown";
81
- let advice = "Please update Roam or the MCP server so versions match.";
82
- if (serverVersion !== "unknown") {
83
- const [serverMajor, serverMinor] = serverVersion.split(".").map(Number);
84
- const [expectedMajor, expectedMinor] = EXPECTED_API_VERSION.split(".").map(Number);
85
- if (serverMajor > expectedMajor ||
86
- (serverMajor === expectedMajor && serverMinor > expectedMinor)) {
87
- advice = "Please update the MCP server.";
88
- }
89
- else {
90
- advice = "Please update Roam.";
91
- }
92
- }
93
- throw new RoamError(`Roam API version mismatch! Roam API: ${serverVersion}, MCP expected: ${EXPECTED_API_VERSION}. ${advice}`, ErrorCodes.VERSION_MISMATCH);
94
- }
95
- /**
96
- * Get user-friendly guidance for authentication errors
97
- */
98
- getAuthErrorGuidance(code) {
99
- const baseMsg = "Authentication failed. ";
100
- switch (code) {
101
- case ErrorCodes.MISSING_TOKEN:
102
- return (baseMsg +
103
- "No API token provided. Please ensure your ~/.roam-tools.json has a valid token.");
104
- case ErrorCodes.INVALID_TOKEN_FORMAT:
105
- return (baseMsg +
106
- "The token format is invalid. Tokens should start with 'roam-graph-local-token-'.");
107
- case ErrorCodes.WRONG_GRAPH_TYPE:
108
- return (baseMsg +
109
- "This token is for a different graph type. Check that 'type' matches in your config.");
110
- case ErrorCodes.TOKEN_NOT_FOUND:
111
- return (baseMsg +
112
- "The token was not recognized. It may have been revoked. Create a new token in Roam Settings > Graph > Local API Tokens.");
113
- default:
114
- return (baseMsg +
115
- "Please check your token in ~/.roam-tools.json. Create a token in Roam Settings > Graph > Local API Tokens.");
116
- }
117
- }
118
- /**
119
- * Get user-friendly guidance for permission errors
120
- */
121
- getPermissionErrorGuidance(code, error) {
122
- switch (code) {
123
- case ErrorCodes.INSUFFICIENT_SCOPE:
124
- return (`Permission denied. ${error?.message || "This operation requires higher permissions."}\n` +
125
- "Create a token with the required scope in Roam Settings > Graph > Local API Tokens.");
126
- case ErrorCodes.SCOPE_EXCEEDS_PERMISSION:
127
- return ("The token has more permissions than your user account allows. " +
128
- "Please check your Roam user permissions for this graph.");
129
- default:
130
- return error?.message || "Access denied. Please check your permissions.";
131
- }
132
- }
133
- /**
134
- * Handle API error responses based on HTTP status and error code
135
- */
136
- handleApiError(status, response) {
137
- const error = typeof response.error === "object" ? response.error : undefined;
138
- const code = error?.code;
139
- const message = getErrorMessage(response.error);
140
- // Version mismatch - fatal
141
- if (this.isVersionMismatch(response)) {
142
- this.handleVersionMismatch(response);
143
- }
144
- // 401 - Authentication errors
145
- if (status === 401) {
146
- throw new RoamError(this.getAuthErrorGuidance(code), code);
147
- }
148
- // 403 - Permission errors
149
- if (status === 403) {
150
- throw new RoamError(this.getPermissionErrorGuidance(code, error), code);
151
- }
152
- // 404 - Unknown action
153
- if (status === 404) {
154
- throw new RoamError(`Unknown API action: ${message}`, ErrorCodes.UNKNOWN_ACTION);
155
- }
156
- // 500 - Server errors
157
- if (status >= 500) {
158
- const hint = message.toLowerCase().includes("promise error")
159
- ? "\n\nThis can happen if the graph was closed before the request completed, " +
160
- "especially for encrypted graphs, when closed before the password was entered."
161
- : "";
162
- throw new RoamError(`Server error: ${message}${hint}`, ErrorCodes.INTERNAL_ERROR);
163
- }
164
- // Other errors
165
- throw new RoamError(message, code);
166
- }
167
- checkResponse(response, httpStatus) {
168
- if (!response.success) {
169
- this.handleApiError(httpStatus, response);
170
- }
171
- }
172
- /**
173
- * Query the token info endpoint for current permissions.
174
- * Best-effort: returns "unknown" on any failure except confirmed revocation.
175
- */
176
- async getTokenInfo() {
177
- try {
178
- const port = await this.getPort();
179
- const response = await fetch(`http://127.0.0.1:${port}/api/graphs/tokens/info`, {
180
- method: "POST",
181
- headers: { "Content-Type": "application/json" },
182
- body: JSON.stringify({
183
- token: this.token,
184
- graph: this.graphName,
185
- type: this.graphType,
186
- }),
187
- });
188
- if (response.status === 401) {
189
- try {
190
- const data = (await response.json());
191
- const code = typeof data.error === "object" ? data.error?.code : undefined;
192
- if (code === "TOKEN_NOT_FOUND") {
193
- return { status: "revoked" };
194
- }
195
- }
196
- catch {
197
- // Couldn't parse 401 body
198
- }
199
- return { status: "unknown" };
200
- }
201
- if (!response.ok)
202
- return { status: "unknown" };
203
- const data = (await response.json());
204
- if (!data.success)
205
- return { status: "unknown" };
206
- return { status: "active", info: data };
207
- }
208
- catch {
209
- return { status: "unknown" };
210
- }
211
- }
212
- async call(action, args = []) {
213
- const doRequest = async () => {
214
- const port = await this.getPort();
215
- // Build URL with graph name and optional type parameter
216
- let url = `http://127.0.0.1:${port}/api/${this.graphName}`;
217
- if (this.graphType === "offline") {
218
- url += "?type=offline";
219
- }
220
- const response = await fetch(url, {
221
- method: "POST",
222
- headers: {
223
- "Content-Type": "application/json",
224
- Authorization: `Bearer ${this.token}`,
225
- },
226
- body: JSON.stringify({
227
- action,
228
- args,
229
- expectedApiVersion: EXPECTED_API_VERSION,
230
- }),
231
- });
232
- const data = (await response.json());
233
- return { data, status: response.status };
234
- };
235
- try {
236
- const { data, status } = await doRequest();
237
- this.checkResponse(data, status);
238
- return data;
239
- }
240
- catch (error) {
241
- // If connection failed, try opening Roam and retry
242
- if (this.isConnectionError(error)) {
243
- // Reset cached port so we re-read from config after Roam starts
244
- this.port = null;
245
- try {
246
- await this.openRoamDeepLink();
247
- }
248
- catch {
249
- // Best-effort — don't let deep link failure prevent retries
250
- }
251
- let delay = 500;
252
- const maxDelay = 15000;
253
- for (let attempt = 0; attempt < 8; attempt += 1) {
254
- await this.sleep(delay);
255
- try {
256
- const { data, status } = await doRequest();
257
- this.checkResponse(data, status);
258
- return data;
259
- }
260
- catch (retryError) {
261
- if (!this.isConnectionError(retryError)) {
262
- throw retryError;
263
- }
264
- }
265
- delay = Math.min(delay * 2, maxDelay);
266
- }
267
- // All retries exhausted
268
- throw new RoamError("Could not connect to Roam Desktop after multiple attempts. " +
269
- "Please restart the Roam desktop app and also this app and then retry again. " +
270
- "If you continue having issues, please let us know at support@roamresearch.com.", ErrorCodes.CONNECTION_FAILED);
271
- }
272
- throw error;
273
- }
274
- }
275
- }
package/dist/connect.d.ts DELETED
@@ -1,10 +0,0 @@
1
- export interface ConnectOptions {
2
- graph?: string;
3
- nickname?: string;
4
- accessLevel?: string;
5
- public?: boolean;
6
- type?: string;
7
- remove?: boolean;
8
- }
9
- export declare function connect(options?: ConnectOptions): Promise<void>;
10
- //# sourceMappingURL=connect.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA0CD,wBAAsB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4gBzE"}