@mauricio.wolff/mcp-obsidian 0.4.1 → 0.5.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.
@@ -1,124 +0,0 @@
1
- import matter from 'gray-matter';
2
- import type { ParsedNote, FrontmatterValidationResult } from './types.js';
3
-
4
- export class FrontmatterHandler {
5
- parse(content: string): ParsedNote {
6
- try {
7
- const parsed = matter(content);
8
- return {
9
- frontmatter: parsed.data,
10
- content: parsed.content,
11
- originalContent: content
12
- };
13
- } catch (error) {
14
- // If parsing fails, treat as content without frontmatter
15
- return {
16
- frontmatter: {},
17
- content: content,
18
- originalContent: content
19
- };
20
- }
21
- }
22
-
23
- stringify(frontmatterData: Record<string, any>, content: string): string {
24
- try {
25
- // If no frontmatter, return content as-is
26
- if (!frontmatterData || Object.keys(frontmatterData).length === 0) {
27
- return content;
28
- }
29
-
30
- return matter.stringify(content, frontmatterData);
31
- } catch (error) {
32
- throw new Error(`Failed to stringify frontmatter: ${error instanceof Error ? error.message : 'Unknown error'}`);
33
- }
34
- }
35
-
36
- validate(frontmatterData: Record<string, any>): FrontmatterValidationResult {
37
- const result: FrontmatterValidationResult = {
38
- isValid: true,
39
- errors: [],
40
- warnings: []
41
- };
42
-
43
- try {
44
- // Test if the frontmatter can be serialized to valid YAML using Bun's YAML
45
- Bun.YAML.stringify(frontmatterData);
46
- } catch (error) {
47
- result.isValid = false;
48
- result.errors.push(`Invalid YAML structure: ${error instanceof Error ? error.message : 'Unknown error'}`);
49
- }
50
-
51
- // Check for problematic values
52
- this.checkForProblematicValues(frontmatterData, result, '');
53
-
54
- return result;
55
- }
56
-
57
- private checkForProblematicValues(
58
- obj: any,
59
- result: FrontmatterValidationResult,
60
- path: string
61
- ): void {
62
- if (obj === null || obj === undefined) {
63
- return;
64
- }
65
-
66
- if (typeof obj === 'function') {
67
- result.errors.push(`Functions are not allowed in frontmatter at path: ${path}`);
68
- result.isValid = false;
69
- return;
70
- }
71
-
72
- if (typeof obj === 'symbol') {
73
- result.errors.push(`Symbols are not allowed in frontmatter at path: ${path}`);
74
- result.isValid = false;
75
- return;
76
- }
77
-
78
- if (obj instanceof Date) {
79
- // Dates are fine, but warn if they're invalid
80
- if (isNaN(obj.getTime())) {
81
- result.warnings.push(`Invalid date at path: ${path}`);
82
- }
83
- return;
84
- }
85
-
86
- if (Array.isArray(obj)) {
87
- obj.forEach((item, index) => {
88
- this.checkForProblematicValues(item, result, `${path}[${index}]`);
89
- });
90
- return;
91
- }
92
-
93
- if (typeof obj === 'object' && obj !== null) {
94
- for (const [key, value] of Object.entries(obj)) {
95
- const currentPath = path ? `${path}.${key}` : key;
96
-
97
- // Check for problematic keys
98
- if (typeof key !== 'string') {
99
- result.errors.push(`Non-string keys are not allowed: ${key}`);
100
- result.isValid = false;
101
- }
102
-
103
- this.checkForProblematicValues(value, result, currentPath);
104
- }
105
- }
106
- }
107
-
108
- extractFrontmatter(content: string): Record<string, any> {
109
- const parsed = this.parse(content);
110
- return parsed.frontmatter;
111
- }
112
-
113
- updateFrontmatter(content: string, updates: Record<string, any>): string {
114
- const parsed = this.parse(content);
115
- const updatedFrontmatter = { ...parsed.frontmatter, ...updates };
116
-
117
- const validation = this.validate(updatedFrontmatter);
118
- if (!validation.isValid) {
119
- throw new Error(`Invalid frontmatter: ${validation.errors.join(', ')}`);
120
- }
121
-
122
- return this.stringify(updatedFrontmatter, parsed.content);
123
- }
124
- }
package/src/pathfilter.ts DELETED
@@ -1,72 +0,0 @@
1
- import type { PathFilterConfig } from "./types.js";
2
-
3
- export class PathFilter {
4
- private ignoredPatterns: string[];
5
- private allowedExtensions: string[];
6
-
7
- constructor(config?: Partial<PathFilterConfig>) {
8
- this.ignoredPatterns = [
9
- '.obsidian/**',
10
- '.git/**',
11
- 'node_modules/**',
12
- '.DS_Store',
13
- 'Thumbs.db',
14
- ...config?.ignoredPatterns || []
15
- ];
16
-
17
- this.allowedExtensions = [
18
- '.md',
19
- '.markdown',
20
- '.txt',
21
- ...config?.allowedExtensions || []
22
- ];
23
- }
24
-
25
- private simpleGlobMatch(pattern: string, path: string): boolean {
26
- // Convert glob pattern to regex
27
- // Handle ** (any number of directories)
28
- let regexPattern = pattern
29
- .replace(/\*\*/g, '.*') // ** matches any number of directories
30
- .replace(/\*/g, '[^/]*') // * matches anything except /
31
- .replace(/\?/g, '[^/]') // ? matches single character except /
32
- .replace(/\./g, '\\.'); // Escape dots
33
-
34
- // Ensure we match the full path
35
- regexPattern = '^' + regexPattern + '$';
36
-
37
- const regex = new RegExp(regexPattern);
38
- return regex.test(path);
39
- }
40
-
41
- isAllowed(path: string): boolean {
42
- // Normalize path separators
43
- const normalizedPath = path.replace(/\\/g, '/');
44
-
45
- // Check if path matches any ignored pattern
46
- for (const pattern of this.ignoredPatterns) {
47
- if (this.simpleGlobMatch(pattern, normalizedPath)) {
48
- return false;
49
- }
50
- }
51
-
52
- // For files, check extension if allowedExtensions is configured
53
- if (this.allowedExtensions.length > 0 && this.isFile(normalizedPath)) {
54
- const hasAllowedExtension = this.allowedExtensions.some(ext =>
55
- normalizedPath.toLowerCase().endsWith(ext.toLowerCase())
56
- );
57
- if (!hasAllowedExtension) {
58
- return false;
59
- }
60
- }
61
-
62
- return true;
63
- }
64
-
65
- private isFile(path: string): boolean {
66
- return path.includes('.') && !path.endsWith('/');
67
- }
68
-
69
- filterPaths(paths: string[]): string[] {
70
- return paths.filter(path => this.isAllowed(path));
71
- }
72
- }
package/src/search.ts DELETED
@@ -1,97 +0,0 @@
1
- import { join } from 'path';
2
- import type { PathFilter } from './pathfilter.js';
3
- import type { SearchParams, SearchResult } from './types.js';
4
-
5
- export class SearchService {
6
- constructor(
7
- private vaultPath: string,
8
- private pathFilter: PathFilter
9
- ) {}
10
-
11
- async search(params: SearchParams): Promise<SearchResult[]> {
12
- const {
13
- query,
14
- limit = 5,
15
- searchContent = true,
16
- searchFrontmatter = false,
17
- caseSensitive = false
18
- } = params;
19
-
20
- if (!query || query.trim().length === 0) {
21
- throw new Error('Search query cannot be empty');
22
- }
23
-
24
- const results: SearchResult[] = [];
25
- const glob = new Bun.Glob("**/*.md");
26
- const maxLimit = Math.min(limit, 20);
27
-
28
- for await (const relativePath of glob.scan(this.vaultPath)) {
29
- if (!this.pathFilter.isAllowed(relativePath)) continue;
30
- if (results.length >= maxLimit) break;
31
-
32
- const fullPath = join(this.vaultPath, relativePath);
33
- const file = Bun.file(fullPath);
34
-
35
- try {
36
- const content = await file.text();
37
- let searchableText = '';
38
-
39
- // Prepare search text based on options
40
- if (searchContent && searchFrontmatter) {
41
- searchableText = content;
42
- } else if (searchContent) {
43
- // Remove frontmatter from search
44
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
45
- searchableText = frontmatterMatch ? content.slice(frontmatterMatch[0].length) : content;
46
- } else if (searchFrontmatter) {
47
- // Search only frontmatter
48
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
49
- searchableText = frontmatterMatch ? frontmatterMatch[1] : '';
50
- }
51
-
52
- const searchIn = caseSensitive ? searchableText : searchableText.toLowerCase();
53
- const searchQuery = caseSensitive ? query : query.toLowerCase();
54
-
55
- const index = searchIn.indexOf(searchQuery);
56
- if (index !== -1) {
57
- // Extract excerpt around first match
58
- const excerptStart = Math.max(0, index - 50);
59
- const excerptEnd = Math.min(searchableText.length, index + searchQuery.length + 50);
60
- let excerpt = searchableText.slice(excerptStart, excerptEnd).trim();
61
-
62
- // Add ellipsis if excerpt is truncated
63
- if (excerptStart > 0) excerpt = '...' + excerpt;
64
- if (excerptEnd < searchableText.length) excerpt = excerpt + '...';
65
-
66
- // Count total matches
67
- let matchCount = 0;
68
- let searchIndex = 0;
69
- while ((searchIndex = searchIn.indexOf(searchQuery, searchIndex)) !== -1) {
70
- matchCount++;
71
- searchIndex += searchQuery.length;
72
- }
73
-
74
- // Find line number of first match
75
- const lines = searchableText.slice(0, index).split('\n');
76
- const lineNumber = lines.length;
77
-
78
- // Extract title from filename
79
- const title = relativePath.split('/').pop()?.replace(/\.md$/, '') || relativePath;
80
-
81
- results.push({
82
- path: relativePath,
83
- title: title,
84
- excerpt: excerpt,
85
- matchCount: matchCount,
86
- lineNumber: lineNumber
87
- });
88
- }
89
- } catch (error) {
90
- // Skip files that can't be read
91
- continue;
92
- }
93
- }
94
-
95
- return results;
96
- }
97
- }
package/src/types.ts DELETED
@@ -1,119 +0,0 @@
1
- export interface ParsedNote {
2
- frontmatter: Record<string, any>;
3
- content: string;
4
- originalContent: string;
5
- }
6
-
7
- export interface NoteWriteParams {
8
- path: string;
9
- content: string;
10
- frontmatter?: Record<string, any>;
11
- mode?: 'overwrite' | 'append' | 'prepend';
12
- }
13
-
14
- export interface DeleteNoteParams {
15
- path: string;
16
- confirmPath: string;
17
- }
18
-
19
- export interface DeleteResult {
20
- success: boolean;
21
- path: string;
22
- message: string;
23
- }
24
-
25
- export interface DirectoryListing {
26
- files: string[];
27
- directories: string[];
28
- }
29
-
30
- export interface FrontmatterValidationResult {
31
- isValid: boolean;
32
- errors: string[];
33
- warnings: string[];
34
- }
35
-
36
- export interface PathFilterConfig {
37
- ignoredPatterns: string[];
38
- allowedExtensions: string[];
39
- }
40
-
41
- // Search types
42
- export interface SearchParams {
43
- query: string;
44
- limit?: number;
45
- searchContent?: boolean;
46
- searchFrontmatter?: boolean;
47
- caseSensitive?: boolean;
48
- }
49
-
50
- export interface SearchResult {
51
- path: string;
52
- title: string;
53
- excerpt: string;
54
- matchCount: number;
55
- lineNumber?: number;
56
- }
57
-
58
- // Move types
59
- export interface MoveNoteParams {
60
- oldPath: string;
61
- newPath: string;
62
- overwrite?: boolean;
63
- }
64
-
65
- export interface MoveResult {
66
- success: boolean;
67
- oldPath: string;
68
- newPath: string;
69
- message: string;
70
- }
71
-
72
- // Batch read types
73
- export interface BatchReadParams {
74
- paths: string[];
75
- includeContent?: boolean;
76
- includeFrontmatter?: boolean;
77
- }
78
-
79
- export interface BatchReadResult {
80
- successful: Array<{
81
- path: string;
82
- frontmatter?: Record<string, any>;
83
- content?: string;
84
- }>;
85
- failed: Array<{
86
- path: string;
87
- error: string;
88
- }>;
89
- }
90
-
91
- // Update frontmatter types
92
- export interface UpdateFrontmatterParams {
93
- path: string;
94
- frontmatter: Record<string, any>;
95
- merge?: boolean;
96
- }
97
-
98
- // Note info types
99
- export interface NoteInfo {
100
- path: string;
101
- size: number;
102
- modified: number; // timestamp
103
- hasFrontmatter: boolean;
104
- }
105
-
106
- // Tag management types
107
- export interface TagManagementParams {
108
- path: string;
109
- operation: 'add' | 'remove' | 'list';
110
- tags?: string[];
111
- }
112
-
113
- export interface TagManagementResult {
114
- path: string;
115
- operation: string;
116
- tags: string[];
117
- success: boolean;
118
- message?: string;
119
- }