obsidian-mcp-server 1.2.6 → 1.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.
Files changed (79) hide show
  1. package/README.md +54 -10
  2. package/build/index.js +12 -3
  3. package/build/mcp/handlers.js +138 -0
  4. package/build/mcp/index.js +7 -0
  5. package/build/mcp/server.js +131 -0
  6. package/build/mcp/types.js +7 -0
  7. package/build/{obsidian.js → obsidian/client.js} +156 -104
  8. package/build/obsidian/errors.js +75 -0
  9. package/build/obsidian/index.js +7 -0
  10. package/build/obsidian/types.js +12 -0
  11. package/build/resources/index.js +15 -0
  12. package/build/{resources.js → resources/tags.js} +35 -14
  13. package/build/resources/types.js +5 -0
  14. package/build/tools/base.js +78 -0
  15. package/build/tools/files/content.js +171 -0
  16. package/build/tools/files/index.js +22 -0
  17. package/build/tools/files/list.js +133 -0
  18. package/build/tools/index.js +31 -0
  19. package/build/tools/properties/index.js +19 -0
  20. package/build/{properties.js → tools/properties/manager.js} +50 -13
  21. package/build/{propertyTools.js → tools/properties/tools.js} +22 -6
  22. package/build/{propertyTypes.js → tools/properties/types.js} +19 -7
  23. package/build/tools/search/complex.js +203 -0
  24. package/build/tools/search/index.js +20 -0
  25. package/build/tools/search/simple.js +127 -0
  26. package/build/utils/errors.js +59 -0
  27. package/build/utils/index.js +9 -0
  28. package/build/utils/logging.js +119 -0
  29. package/build/utils/rate-limiting.js +94 -0
  30. package/build/utils/tokenization.js +62 -0
  31. package/build/utils/validation.js +88 -0
  32. package/examples/README.md +48 -0
  33. package/examples/append-content.md +63 -0
  34. package/examples/complex-search.md +117 -0
  35. package/examples/find-in-file.md +94 -0
  36. package/examples/get-file-contents.md +72 -0
  37. package/examples/get-properties.md +89 -0
  38. package/examples/list-files-in-dir.md +55 -0
  39. package/examples/list-files-in-vault.md +53 -0
  40. package/examples/patch-content.md +60 -0
  41. package/examples/update-properties.md +126 -0
  42. package/mcp-client-config.example.json +23 -0
  43. package/package.json +4 -3
  44. package/src/index.ts +13 -3
  45. package/src/mcp/handlers.ts +183 -0
  46. package/src/mcp/index.ts +6 -0
  47. package/src/mcp/server.ts +162 -0
  48. package/src/mcp/types.ts +46 -0
  49. package/src/{obsidian.ts → obsidian/client.ts} +174 -134
  50. package/src/obsidian/errors.ts +105 -0
  51. package/src/obsidian/index.ts +6 -0
  52. package/src/obsidian/types.ts +124 -0
  53. package/src/resources/index.ts +17 -0
  54. package/src/{resources.ts → resources/tags.ts} +42 -16
  55. package/src/resources/types.ts +30 -0
  56. package/src/tools/base.ts +112 -0
  57. package/src/tools/files/content.ts +206 -0
  58. package/src/tools/files/index.ts +31 -0
  59. package/src/tools/files/list.ts +150 -0
  60. package/src/tools/index.ts +38 -0
  61. package/src/tools/properties/index.ts +21 -0
  62. package/src/{properties.ts → tools/properties/manager.ts} +54 -13
  63. package/src/{propertyTools.ts → tools/properties/tools.ts} +33 -7
  64. package/src/{propertyTypes.ts → tools/properties/types.ts} +31 -7
  65. package/src/tools/search/complex.ts +231 -0
  66. package/src/tools/search/index.ts +22 -0
  67. package/src/tools/search/simple.ts +147 -0
  68. package/src/utils/errors.ts +69 -0
  69. package/src/utils/index.ts +8 -0
  70. package/src/utils/logging.ts +146 -0
  71. package/src/utils/rate-limiting.ts +114 -0
  72. package/src/utils/tokenization.ts +71 -0
  73. package/src/utils/validation.ts +95 -0
  74. package/build/server.js +0 -238
  75. package/build/tools.js +0 -863
  76. package/build/types.js +0 -37
  77. package/src/server.ts +0 -308
  78. package/src/tools.ts +0 -946
  79. package/src/types.ts +0 -184
@@ -1,43 +1,78 @@
1
+ /**
2
+ * Obsidian REST API client implementation
3
+ */
1
4
  import axios from "axios";
2
- import { ObsidianError, DEFAULT_OBSIDIAN_CONFIG } from "./types.js";
3
5
  import { Agent } from "node:https";
4
6
  import { readFileSync } from "fs";
5
7
  import { fileURLToPath } from 'url';
6
8
  import { dirname, join } from "path";
9
+ import { createLogger } from '../utils/logging.js';
10
+ import { ObsidianError } from '../utils/errors.js';
11
+ import { validateFilePath, sanitizeHeader } from '../utils/validation.js';
12
+ import { DEFAULT_OBSIDIAN_CONFIG } from './types.js';
13
+ import { createMissingAPIKeyMessage, handleAxiosError } from './errors.js';
14
+ // Logger for the ObsidianClient
15
+ const logger = createLogger('ObsidianClient');
7
16
  // Get package version for user agent
8
17
  const __filename = fileURLToPath(import.meta.url);
9
18
  const __dirname = dirname(__filename);
10
- const packagePath = join(__dirname, '..', '..', 'package.json');
11
19
  const VERSION = (() => {
12
20
  try {
21
+ // Look for package.json in the same directory as the built files
22
+ const packagePath = join(__dirname, '..', '..', 'package.json');
13
23
  const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
14
24
  return pkg.version;
15
25
  }
16
26
  catch (error) {
17
- console.warn('Could not read package.json version:', error);
18
- return '1.1.0'; // Fallback version
27
+ // Try alternative location for development
28
+ try {
29
+ const devPackagePath = join(__dirname, '..', '..', '..', 'package.json');
30
+ const pkg = JSON.parse(readFileSync(devPackagePath, 'utf-8'));
31
+ return pkg.version;
32
+ }
33
+ catch (devError) {
34
+ logger.warn('Could not read package.json version, using fallback');
35
+ return '1.1.0'; // Fallback version
36
+ }
19
37
  }
20
38
  })();
39
+ /**
40
+ * Client for interacting with the Obsidian Local REST API
41
+ */
21
42
  export class ObsidianClient {
22
43
  client;
23
44
  config;
45
+ /**
46
+ * Create a new ObsidianClient
47
+ * @param config Configuration for the client
48
+ */
24
49
  constructor(config) {
25
50
  if (!config.apiKey) {
26
- throw new ObsidianError("Missing API key. To fix this:\n" +
27
- "1. Install the 'Local REST API' plugin in Obsidian\n" +
28
- "2. Enable the plugin in Obsidian Settings\n" +
29
- "3. Copy your API key from Obsidian Settings > Local REST API\n" +
30
- "4. Provide the API key in your configuration", 40100 // Unauthorized
51
+ throw new ObsidianError(createMissingAPIKeyMessage(), 40100 // Unauthorized
31
52
  );
32
53
  }
33
- // Combine defaults with provided config
54
+ // Determine if we're in a development environment
55
+ const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
56
+ // Read environment variables with fallbacks
57
+ const envConfig = {
58
+ protocol: process.env.OBSIDIAN_PROTOCOL || DEFAULT_OBSIDIAN_CONFIG.protocol,
59
+ host: process.env.OBSIDIAN_HOST || DEFAULT_OBSIDIAN_CONFIG.host,
60
+ port: parseInt(process.env.OBSIDIAN_PORT || String(DEFAULT_OBSIDIAN_CONFIG.port)),
61
+ verifySSL: process.env.VERIFY_SSL ? process.env.VERIFY_SSL === 'true' : (isDev ? false : true),
62
+ timeout: parseInt(process.env.REQUEST_TIMEOUT || '5000'),
63
+ maxContentLength: parseInt(process.env.MAX_CONTENT_LENGTH || String(50 * 1024 * 1024)),
64
+ maxBodyLength: parseInt(process.env.MAX_BODY_LENGTH || String(50 * 1024 * 1024))
65
+ };
66
+ // Combine defaults with provided config and environment variables
34
67
  this.config = {
35
- ...DEFAULT_OBSIDIAN_CONFIG,
36
- verifySSL: config.verifySSL ?? true, // Default to true as required by Obsidian REST API plugin
68
+ protocol: envConfig.protocol,
69
+ host: envConfig.host,
70
+ port: envConfig.port,
71
+ verifySSL: config.verifySSL ?? envConfig.verifySSL,
37
72
  apiKey: config.apiKey,
38
- timeout: config.timeout ?? 5000, // 5 second default timeout
39
- maxContentLength: config.maxContentLength ?? 50 * 1024 * 1024, // 50MB
40
- maxBodyLength: config.maxBodyLength ?? 50 * 1024 * 1024 // 50MB
73
+ timeout: config.timeout ?? envConfig.timeout,
74
+ maxContentLength: config.maxContentLength ?? envConfig.maxContentLength,
75
+ maxBodyLength: config.maxBodyLength ?? envConfig.maxBodyLength
41
76
  };
42
77
  // Configure HTTPS agent
43
78
  const httpsAgent = new Agent({
@@ -66,15 +101,30 @@ export class ObsidianClient {
66
101
  decompress: true
67
102
  };
68
103
  if (!this.config.verifySSL) {
69
- console.warn("WARNING: SSL verification is disabled. The Obsidian REST API plugin requires HTTPS by default.\n" +
70
- "Make sure you have configured the certificate as a trusted certificate authority.\n" +
71
- "See Obsidian Settings > Local REST API > 'How to Access' for setup instructions.");
104
+ logger.warn("WARNING: SSL verification is disabled. While this works for development, it's not recommended for production.\n" +
105
+ "To properly configure SSL certificates:\n" +
106
+ "1. Go to Obsidian Settings > Local REST API\n" +
107
+ "2. Under 'How to Access', copy the certificate\n" +
108
+ "3. For Windows users:\n" +
109
+ " - Open 'certmgr.msc' (Windows Certificate Manager)\n" +
110
+ " - Go to 'Trusted Root Certification Authorities' > 'Certificates'\n" +
111
+ " - Right-click > 'All Tasks' > 'Import' and follow the wizard\n" +
112
+ " - Select the certificate file you copied from Obsidian\n" +
113
+ "4. For other systems:\n" +
114
+ " - macOS: Add to Keychain Access\n" +
115
+ " - Linux: Add to ca-certificates");
72
116
  }
73
117
  this.client = axios.create(axiosConfig);
74
118
  }
119
+ /**
120
+ * Get the base URL for the Obsidian API
121
+ */
75
122
  getBaseUrl() {
76
123
  return `${this.config.protocol}://${this.config.host}:${this.config.port}`;
77
124
  }
125
+ /**
126
+ * Get headers for requests to the Obsidian API
127
+ */
78
128
  getHeaders() {
79
129
  const headers = {
80
130
  Authorization: `Bearer ${this.config.apiKey}`,
@@ -84,93 +134,19 @@ export class ObsidianClient {
84
134
  // Sanitize headers
85
135
  return Object.fromEntries(Object.entries(headers).map(([key, value]) => [
86
136
  key,
87
- this.sanitizeHeader(value)
137
+ sanitizeHeader(value)
88
138
  ]));
89
139
  }
90
- sanitizeHeader(value) {
91
- // Remove any potentially harmful characters from header values
92
- return value.replace(/[^\w\s\-\._~:/?#\[\]@!$&'()*+,;=]/g, '');
93
- }
94
- validateFilePath(filepath) {
95
- // Prevent path traversal attacks
96
- const normalizedPath = filepath.replace(/\\/g, '/');
97
- if (normalizedPath.includes('../') || normalizedPath.includes('..\\')) {
98
- throw new ObsidianError('Invalid file path: Path traversal not allowed', 40001);
99
- }
100
- // Additional path validations
101
- if (normalizedPath.startsWith('/') || /^[a-zA-Z]:/.test(normalizedPath)) {
102
- throw new ObsidianError('Invalid file path: Absolute paths not allowed', 40002);
103
- }
104
- }
105
- getErrorCode(status) {
106
- switch (status) {
107
- case 400: return 40000; // Bad request
108
- case 401: return 40100; // Unauthorized
109
- case 403: return 40300; // Forbidden
110
- case 404: return 40400; // Not found
111
- case 405: return 40500; // Method not allowed
112
- case 409: return 40900; // Conflict
113
- case 429: return 42900; // Too many requests
114
- case 500: return 50000; // Internal server error
115
- case 501: return 50100; // Not implemented
116
- case 502: return 50200; // Bad gateway
117
- case 503: return 50300; // Service unavailable
118
- case 504: return 50400; // Gateway timeout
119
- default:
120
- if (status >= 400 && status < 500)
121
- return 40000 + (status - 400) * 100;
122
- if (status >= 500 && status < 600)
123
- return 50000 + (status - 500) * 100;
124
- return 50000;
125
- }
126
- }
140
+ /**
141
+ * Safely execute an API request with error handling
142
+ */
127
143
  async safeRequest(operation) {
128
144
  try {
129
145
  return await operation();
130
146
  }
131
147
  catch (error) {
132
148
  if (axios.isAxiosError(error)) {
133
- const axiosError = error;
134
- const response = axiosError.response;
135
- const errorData = response?.data;
136
- // Handle common connection errors with helpful messages
137
- if (error.code === 'DEPTH_ZERO_SELF_SIGNED_CERT' || error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
138
- throw new ObsidianError(`SSL certificate verification failed. You have two options:\n\n` +
139
- `Option 1 - Enable HTTP (not recommended for production):\n` +
140
- `1. Go to Obsidian Settings > Local REST API\n` +
141
- `2. Enable "Enable Non-encrypted (HTTP) Server"\n` +
142
- `3. Update your client config to use "http" protocol\n\n` +
143
- `Option 2 - Configure HTTPS (recommended):\n` +
144
- `1. Go to Obsidian Settings > Local REST API\n` +
145
- `2. Under 'How to Access', copy the certificate\n` +
146
- `3. Add the certificate to your system's trusted certificates:\n` +
147
- ` - On macOS: Add to Keychain Access\n` +
148
- ` - On Windows: Add to Certificate Manager\n` +
149
- ` - On Linux: Add to ca-certificates\n` +
150
- ` For development only: Set verifySSL: false in client config\n\n` +
151
- `Original error: ${error.message}`, 50001, // SSL error code
152
- { code: error.code, config: { verifySSL: this.config.verifySSL } });
153
- }
154
- if (error.code === 'ECONNREFUSED') {
155
- throw new ObsidianError(`Connection refused. To fix this:\n` +
156
- `1. Ensure Obsidian is running\n` +
157
- `2. Verify the 'Local REST API' plugin is enabled in Obsidian Settings\n` +
158
- `3. Check that you're using the correct host (${this.config.host}) and port (${this.config.port})\n` +
159
- `4. Make sure HTTPS is enabled in the plugin settings`, 50002, // Connection refused
160
- { code: error.code });
161
- }
162
- if (response?.status === 401) {
163
- throw new ObsidianError(`Authentication failed. To fix this:\n` +
164
- `1. Go to Obsidian Settings > Local REST API\n` +
165
- `2. Copy your API key from the settings\n` +
166
- `3. Update your configuration with the new API key\n` +
167
- `Note: The API key changes when you regenerate certificates`, 40100, // Unauthorized
168
- { code: error.code });
169
- }
170
- // For other errors, use API error code if available
171
- const errorCode = errorData?.errorCode ?? this.getErrorCode(response?.status ?? 500);
172
- const message = errorData?.message ?? axiosError.message ?? "Unknown error";
173
- throw new ObsidianError(message, errorCode, errorData);
149
+ throw handleAxiosError(error, this.config.host, this.config.port);
174
150
  }
175
151
  if (error instanceof Error) {
176
152
  throw new ObsidianError(error.message, 50000, error);
@@ -178,38 +154,58 @@ export class ObsidianClient {
178
154
  throw new ObsidianError("Unknown error occurred", 50000, error);
179
155
  }
180
156
  }
157
+ /**
158
+ * List all files in the vault
159
+ */
181
160
  async listFilesInVault() {
182
161
  return this.safeRequest(async () => {
162
+ logger.debug('Listing all files in vault');
183
163
  const response = await this.client.get("/vault/");
184
164
  return response.data.files;
185
165
  });
186
166
  }
167
+ /**
168
+ * List files in a specific directory
169
+ */
187
170
  async listFilesInDir(dirpath) {
188
- this.validateFilePath(dirpath);
171
+ validateFilePath(dirpath);
189
172
  return this.safeRequest(async () => {
173
+ logger.debug(`Listing files in directory: ${dirpath}`);
190
174
  const response = await this.client.get(`/vault/${dirpath}/`);
191
175
  return response.data.files;
192
176
  });
193
177
  }
178
+ /**
179
+ * Get the contents of a file
180
+ */
194
181
  async getFileContents(filepath) {
195
- this.validateFilePath(filepath);
182
+ validateFilePath(filepath);
196
183
  return this.safeRequest(async () => {
184
+ logger.debug(`Getting contents of file: ${filepath}`);
197
185
  const response = await this.client.get(`/vault/${filepath}`);
198
186
  return response.data;
199
187
  });
200
188
  }
189
+ /**
190
+ * Search for a string across all files
191
+ */
201
192
  async search(query, contextLength = 100) {
202
193
  return this.safeRequest(async () => {
194
+ logger.debug(`Searching for: ${query} with context length: ${contextLength}`);
203
195
  const response = await this.client.post("/search/simple/", null, { params: { query, contextLength } });
204
196
  return response.data;
205
197
  });
206
198
  }
199
+ /**
200
+ * Append content to a file
201
+ */
207
202
  async appendContent(filepath, content) {
208
- this.validateFilePath(filepath);
203
+ validateFilePath(filepath);
209
204
  if (!content || typeof content !== 'string') {
210
205
  throw new ObsidianError('Invalid content: Content must be a non-empty string', 40003);
211
206
  }
212
207
  return this.safeRequest(async () => {
208
+ logger.debug(`Appending content to file: ${filepath}`);
213
209
  await this.client.post(`/vault/${filepath}`, content, {
214
210
  headers: {
215
211
  "Content-Type": "text/markdown"
@@ -217,12 +213,16 @@ export class ObsidianClient {
217
213
  });
218
214
  });
219
215
  }
216
+ /**
217
+ * Update the entire content of a file
218
+ */
220
219
  async updateContent(filepath, content) {
221
- this.validateFilePath(filepath);
220
+ validateFilePath(filepath);
222
221
  if (!content || typeof content !== 'string') {
223
222
  throw new ObsidianError('Invalid content: Content must be a non-empty string', 40003);
224
223
  }
225
224
  return this.safeRequest(async () => {
225
+ logger.debug(`Updating content of file: ${filepath}`);
226
226
  await this.client.put(`/vault/${filepath}`, content, {
227
227
  headers: {
228
228
  "Content-Type": "text/markdown"
@@ -230,8 +230,12 @@ export class ObsidianClient {
230
230
  });
231
231
  });
232
232
  }
233
+ /**
234
+ * Execute a complex search using JsonLogic query
235
+ */
233
236
  async searchJson(query) {
234
237
  return this.safeRequest(async () => {
238
+ logger.debug(`Executing JSON search with query: ${JSON.stringify(query)}`);
235
239
  const isTagSearch = JSON.stringify(query).includes('"contains"') &&
236
240
  JSON.stringify(query).includes('"#"');
237
241
  const response = await this.client.post("/search/", query, {
@@ -240,36 +244,56 @@ export class ObsidianClient {
240
244
  "Accept": "application/vnd.olrapi.note+json"
241
245
  }
242
246
  });
243
- return isTagSearch ? response.data : response.data;
247
+ return response.data;
244
248
  });
245
249
  }
250
+ /**
251
+ * Get server status
252
+ */
246
253
  async getStatus() {
247
254
  return this.safeRequest(async () => {
255
+ logger.debug('Getting server status');
248
256
  const response = await this.client.get("/");
249
257
  return response.data;
250
258
  });
251
259
  }
260
+ /**
261
+ * List available commands
262
+ */
252
263
  async listCommands() {
253
264
  return this.safeRequest(async () => {
265
+ logger.debug('Listing commands');
254
266
  const response = await this.client.get("/commands/");
255
267
  return response.data.commands;
256
268
  });
257
269
  }
270
+ /**
271
+ * Execute a command by ID
272
+ */
258
273
  async executeCommand(commandId) {
259
274
  return this.safeRequest(async () => {
275
+ logger.debug(`Executing command: ${commandId}`);
260
276
  await this.client.post(`/commands/${commandId}/`);
261
277
  });
262
278
  }
279
+ /**
280
+ * Open a file in Obsidian
281
+ */
263
282
  async openFile(filepath, newLeaf = false) {
264
- this.validateFilePath(filepath);
283
+ validateFilePath(filepath);
265
284
  return this.safeRequest(async () => {
285
+ logger.debug(`Opening file: ${filepath}, newLeaf: ${newLeaf}`);
266
286
  await this.client.post(`/open/${filepath}`, null, {
267
287
  params: { newLeaf }
268
288
  });
269
289
  });
270
290
  }
291
+ /**
292
+ * Get the currently active file
293
+ */
271
294
  async getActiveFile() {
272
295
  return this.safeRequest(async () => {
296
+ logger.debug('Getting active file');
273
297
  const response = await this.client.get("/active/", {
274
298
  headers: {
275
299
  "Accept": "application/vnd.olrapi.note+json"
@@ -278,8 +302,12 @@ export class ObsidianClient {
278
302
  return response.data;
279
303
  });
280
304
  }
305
+ /**
306
+ * Update the active file
307
+ */
281
308
  async updateActiveFile(content) {
282
309
  return this.safeRequest(async () => {
310
+ logger.debug('Updating active file');
283
311
  await this.client.put("/active/", content, {
284
312
  headers: {
285
313
  "Content-Type": "text/markdown"
@@ -287,13 +315,21 @@ export class ObsidianClient {
287
315
  });
288
316
  });
289
317
  }
318
+ /**
319
+ * Delete the active file
320
+ */
290
321
  async deleteActiveFile() {
291
322
  return this.safeRequest(async () => {
323
+ logger.debug('Deleting active file');
292
324
  await this.client.delete("/active/");
293
325
  });
294
326
  }
327
+ /**
328
+ * Patch the active file
329
+ */
295
330
  async patchActiveFile(operation, targetType, target, content, options) {
296
331
  return this.safeRequest(async () => {
332
+ logger.debug(`Patching active file: ${operation} ${targetType} "${target}"`);
297
333
  const headers = {
298
334
  "Operation": operation,
299
335
  "Target-Type": targetType,
@@ -309,8 +345,12 @@ export class ObsidianClient {
309
345
  await this.client.patch("/active/", content, { headers });
310
346
  });
311
347
  }
348
+ /**
349
+ * Get a periodic note (e.g., daily, weekly)
350
+ */
312
351
  async getPeriodicNote(period) {
313
352
  return this.safeRequest(async () => {
353
+ logger.debug(`Getting ${period} periodic note`);
314
354
  const response = await this.client.get(`/periodic/${period}/`, {
315
355
  headers: {
316
356
  "Accept": "application/vnd.olrapi.note+json"
@@ -319,8 +359,12 @@ export class ObsidianClient {
319
359
  return response.data;
320
360
  });
321
361
  }
362
+ /**
363
+ * Update a periodic note
364
+ */
322
365
  async updatePeriodicNote(period, content) {
323
366
  return this.safeRequest(async () => {
367
+ logger.debug(`Updating ${period} periodic note`);
324
368
  await this.client.put(`/periodic/${period}/`, content, {
325
369
  headers: {
326
370
  "Content-Type": "text/markdown"
@@ -328,13 +372,21 @@ export class ObsidianClient {
328
372
  });
329
373
  });
330
374
  }
375
+ /**
376
+ * Delete a periodic note
377
+ */
331
378
  async deletePeriodicNote(period) {
332
379
  return this.safeRequest(async () => {
380
+ logger.debug(`Deleting ${period} periodic note`);
333
381
  await this.client.delete(`/periodic/${period}/`);
334
382
  });
335
383
  }
384
+ /**
385
+ * Patch a periodic note
386
+ */
336
387
  async patchPeriodicNote(period, operation, targetType, target, content, options) {
337
388
  return this.safeRequest(async () => {
389
+ logger.debug(`Patching ${period} periodic note: ${operation} ${targetType} "${target}"`);
338
390
  const headers = {
339
391
  "Operation": operation,
340
392
  "Target-Type": targetType,
@@ -351,4 +403,4 @@ export class ObsidianClient {
351
403
  });
352
404
  }
353
405
  }
354
- //# sourceMappingURL=obsidian.js.map
406
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,75 @@
1
+ import { ObsidianError, getErrorCodeFromStatus } from '../utils/errors.js';
2
+ /**
3
+ * Helper function to create a descriptive error message for SSL certificate issues
4
+ */
5
+ export function createSSLErrorMessage(error, config) {
6
+ return (`SSL certificate verification failed. You have two options:\n\n` +
7
+ `Option 1 - Enable HTTP (not recommended for production):\n` +
8
+ `1. Go to Obsidian Settings > Local REST API\n` +
9
+ `2. Enable "Enable Non-encrypted (HTTP) Server"\n` +
10
+ `3. Update your client config to use "http" protocol\n\n` +
11
+ `Option 2 - Configure HTTPS (recommended):\n` +
12
+ `1. Go to Obsidian Settings > Local REST API\n` +
13
+ `2. Under 'How to Access', copy the certificate\n` +
14
+ `3. Add the certificate to your system's trusted certificates:\n` +
15
+ ` - On macOS: Add to Keychain Access\n` +
16
+ ` - On Windows: Add to Certificate Manager\n` +
17
+ ` - On Linux: Add to ca-certificates\n` +
18
+ ` For development only: Set verifySSL: false in client config\n\n` +
19
+ `Original error: ${error.message}`);
20
+ }
21
+ /**
22
+ * Helper function to create a descriptive error message for connection refused issues
23
+ */
24
+ export function createConnectionRefusedMessage(host, port) {
25
+ return (`Connection refused. To fix this:\n` +
26
+ `1. Ensure Obsidian is running\n` +
27
+ `2. Verify the 'Local REST API' plugin is enabled in Obsidian Settings\n` +
28
+ `3. Check that you're using the correct host (${host}) and port (${port})\n` +
29
+ `4. Make sure HTTPS is enabled in the plugin settings`);
30
+ }
31
+ /**
32
+ * Helper function to create a descriptive error message for authentication failures
33
+ */
34
+ export function createAuthFailedMessage() {
35
+ return (`Authentication failed. To fix this:\n` +
36
+ `1. Go to Obsidian Settings > Local REST API\n` +
37
+ `2. Copy your API key from the settings\n` +
38
+ `3. Update your configuration with the new API key\n` +
39
+ `Note: The API key changes when you regenerate certificates`);
40
+ }
41
+ /**
42
+ * Helper function to create a descriptive error message for missing API key
43
+ */
44
+ export function createMissingAPIKeyMessage() {
45
+ return (`Missing API key. To fix this:\n` +
46
+ `1. Install the 'Local REST API' plugin in Obsidian\n` +
47
+ `2. Enable the plugin in Obsidian Settings\n` +
48
+ `3. Copy your API key from Obsidian Settings > Local REST API\n` +
49
+ `4. Provide the API key in your configuration`);
50
+ }
51
+ /**
52
+ * Helper function to handle Axios errors consistently
53
+ */
54
+ export function handleAxiosError(error, host, port) {
55
+ const response = error.response;
56
+ const errorData = response?.data;
57
+ // Handle common connection errors with helpful messages
58
+ if (error.code === 'DEPTH_ZERO_SELF_SIGNED_CERT' || error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
59
+ return new ObsidianError(createSSLErrorMessage(error, { verifySSL: true }), 50001, // SSL error code
60
+ { code: error.code });
61
+ }
62
+ if (error.code === 'ECONNREFUSED') {
63
+ return new ObsidianError(createConnectionRefusedMessage(host, port), 50002, // Connection refused
64
+ { code: error.code });
65
+ }
66
+ if (response?.status === 401) {
67
+ return new ObsidianError(createAuthFailedMessage(), 40100, // Unauthorized
68
+ { code: error.code });
69
+ }
70
+ // For other errors, use API error code if available
71
+ const errorCode = errorData?.errorCode ?? getErrorCodeFromStatus(response?.status ?? 500);
72
+ const message = errorData?.message ?? error.message ?? "Unknown error";
73
+ return new ObsidianError(message, errorCode, errorData);
74
+ }
75
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Exports for the Obsidian module
3
+ */
4
+ export * from './client.js';
5
+ export * from './types.js';
6
+ export * from './errors.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Type definitions for Obsidian client
3
+ */
4
+ /**
5
+ * Default configuration for Obsidian server
6
+ */
7
+ export const DEFAULT_OBSIDIAN_CONFIG = {
8
+ protocol: "https", // HTTPS required by default in Obsidian REST API plugin
9
+ host: "127.0.0.1",
10
+ port: 27124
11
+ };
12
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Resources module exports
3
+ */
4
+ export * from './types.js';
5
+ export * from './tags.js';
6
+ import { TagResource } from './tags.js';
7
+ /**
8
+ * Create and return the tag resource
9
+ * @param client The ObsidianClient instance
10
+ * @returns The tag resource
11
+ */
12
+ export function createTagResource(client) {
13
+ return new TagResource(client);
14
+ }
15
+ //# sourceMappingURL=index.js.map