obsidian-mcp-server 1.5.2 → 1.5.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.
- package/README.md +2 -2
- package/build/tools/properties/types.js +2 -2
- package/debug.js +3 -4
- package/docs/tree.md +63 -0
- package/package.json +9 -11
- package/scripts/clean.ts +103 -0
- package/scripts/tree.ts +599 -0
- package/src/tools/properties/types.ts +3 -3
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Obsidian MCP Server
|
|
2
2
|
|
|
3
3
|
[](https://www.typescriptlang.org/)
|
|
4
|
-
[](https://modelcontextprotocol.io/)
|
|
5
|
+
[]()
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[]()
|
|
8
8
|
[](https://github.com/cyanheads/obsidian-mcp-server)
|
|
@@ -40,7 +40,7 @@ export const ObsidianPropertiesSchema = z.object({
|
|
|
40
40
|
urls: z.array(z.string().url()).optional(),
|
|
41
41
|
papers: z.array(z.string()).optional(),
|
|
42
42
|
// Custom Fields
|
|
43
|
-
custom: z.
|
|
43
|
+
custom: z.object({}).catchall(z.unknown()).optional() // Ensure 'custom' is an object if present
|
|
44
44
|
});
|
|
45
45
|
/**
|
|
46
46
|
* Schema for validating property updates (excludes timestamps)
|
|
@@ -64,6 +64,6 @@ export const PropertyUpdateSchema = z.object({
|
|
|
64
64
|
urls: z.array(z.string().url()).optional(),
|
|
65
65
|
papers: z.array(z.string()).optional(),
|
|
66
66
|
// Custom Fields
|
|
67
|
-
custom: z.
|
|
67
|
+
custom: z.object({}).catchall(z.unknown()).optional() // Ensure 'custom' is an object if present
|
|
68
68
|
});
|
|
69
69
|
//# sourceMappingURL=types.js.map
|
package/debug.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// Debug script to run the server with verbose logging
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import path from 'path';
|
|
6
4
|
import { spawn } from 'child_process';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path, { dirname } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
|
-
import { dirname } from 'path';
|
|
9
8
|
|
|
10
9
|
// Get current directory from import.meta
|
|
11
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -30,7 +29,7 @@ const env = {
|
|
|
30
29
|
DEBUG_JSONRPC: 'true',
|
|
31
30
|
NODE_ENV: 'development',
|
|
32
31
|
// Required environment variables from MCP settings
|
|
33
|
-
OBSIDIAN_API_KEY: '
|
|
32
|
+
OBSIDIAN_API_KEY: 'your_actual_api_key_here', // Add your actual API key here for debugging
|
|
34
33
|
VERIFY_SSL: 'false',
|
|
35
34
|
OBSIDIAN_PROTOCOL: 'https',
|
|
36
35
|
OBSIDIAN_HOST: '127.0.0.1',
|
package/docs/tree.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# obsidian-mcp-server - Directory Structure
|
|
2
|
+
|
|
3
|
+
Generated on: 2025-04-13 20:39:14
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
obsidian-mcp-server
|
|
8
|
+
├── backups
|
|
9
|
+
├── scripts
|
|
10
|
+
├── clean.ts
|
|
11
|
+
└── tree.ts
|
|
12
|
+
├── src
|
|
13
|
+
├── mcp
|
|
14
|
+
│ ├── handlers.ts
|
|
15
|
+
│ ├── index.ts
|
|
16
|
+
│ ├── server.ts
|
|
17
|
+
│ └── types.ts
|
|
18
|
+
├── obsidian
|
|
19
|
+
│ ├── client.ts
|
|
20
|
+
│ ├── errors.ts
|
|
21
|
+
│ ├── index.ts
|
|
22
|
+
│ └── types.ts
|
|
23
|
+
├── resources
|
|
24
|
+
│ ├── index.ts
|
|
25
|
+
│ ├── tags.ts
|
|
26
|
+
│ └── types.ts
|
|
27
|
+
├── tools
|
|
28
|
+
│ ├── files
|
|
29
|
+
│ │ ├── content.ts
|
|
30
|
+
│ │ ├── index.ts
|
|
31
|
+
│ │ └── list.ts
|
|
32
|
+
│ ├── properties
|
|
33
|
+
│ │ ├── index.ts
|
|
34
|
+
│ │ ├── manager.ts
|
|
35
|
+
│ │ ├── tools.ts
|
|
36
|
+
│ │ └── types.ts
|
|
37
|
+
│ ├── search
|
|
38
|
+
│ │ ├── complex.ts
|
|
39
|
+
│ │ ├── index.ts
|
|
40
|
+
│ │ └── simple.ts
|
|
41
|
+
│ ├── base.ts
|
|
42
|
+
│ └── index.ts
|
|
43
|
+
├── utils
|
|
44
|
+
│ ├── errors.ts
|
|
45
|
+
│ ├── idGenerator.ts
|
|
46
|
+
│ ├── index.ts
|
|
47
|
+
│ ├── logging.ts
|
|
48
|
+
│ ├── rate-limiting.ts
|
|
49
|
+
│ ├── tokenization.ts
|
|
50
|
+
│ └── validation.ts
|
|
51
|
+
└── index.ts
|
|
52
|
+
├── debug.js
|
|
53
|
+
├── LICENSE
|
|
54
|
+
├── mcp-client-config.example.json
|
|
55
|
+
├── package-lock.json
|
|
56
|
+
├── package.json
|
|
57
|
+
├── README.md
|
|
58
|
+
├── repomix.config.json
|
|
59
|
+
└── tsconfig.json
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
_Note: This tree excludes files and directories matched by .gitignore and common patterns like node_modules._
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obsidian-mcp-server",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server designed for LLMs to interact with Obsidian vaults. Provides secure, token-aware tools for seamless knowledge base management through a standardized interface.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,21 +12,19 @@
|
|
|
12
12
|
"postbuild": "node -e \"if (process.platform !== 'win32') require('fs').chmodSync('build/index.js', '755')\"",
|
|
13
13
|
"start": "node build/index.js",
|
|
14
14
|
"dev": "tsc -w",
|
|
15
|
-
"clean": "
|
|
15
|
+
"clean": "npx ts-node scripts/clean.ts",
|
|
16
16
|
"rebuild": "npm run clean && npm run build",
|
|
17
|
-
"
|
|
18
|
-
"lint": "eslint . --ext .ts",
|
|
19
|
-
"format": "prettier --write \"src/**/*.ts\""
|
|
17
|
+
"tree": "npx ts-node --esm scripts/tree.ts"
|
|
20
18
|
},
|
|
21
19
|
"dependencies": {
|
|
22
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
23
|
-
"@types/node": "^22.14.
|
|
24
|
-
"@typescript-eslint/eslint-plugin": "^8.29.
|
|
25
|
-
"@typescript-eslint/parser": "^8.29.
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.9.0",
|
|
21
|
+
"@types/node": "^22.14.1",
|
|
22
|
+
"@typescript-eslint/eslint-plugin": "^8.29.1",
|
|
23
|
+
"@typescript-eslint/parser": "^8.29.1",
|
|
26
24
|
"axios": "^1.8.4",
|
|
27
|
-
"dotenv": "^16.
|
|
25
|
+
"dotenv": "^16.5.0",
|
|
28
26
|
"eslint": "^9.24.0",
|
|
29
|
-
"eslint-config-prettier": "^10.1.
|
|
27
|
+
"eslint-config-prettier": "^10.1.2",
|
|
30
28
|
"eslint-plugin-prettier": "^5.2.6",
|
|
31
29
|
"nanoid": "^5.1.5",
|
|
32
30
|
"p-limit": "^6.2.0",
|
package/scripts/clean.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Clean Script
|
|
5
|
+
* ============
|
|
6
|
+
*
|
|
7
|
+
* Description:
|
|
8
|
+
* A utility script to clean build artifacts and temporary directories from your project.
|
|
9
|
+
* By default, it removes the 'dist' and 'logs' directories if they exist.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* - Add to package.json: "clean": "node dist/scripts/clean.js"
|
|
13
|
+
* - Can be run directly: npm run clean
|
|
14
|
+
* - Often used in rebuild scripts: "rebuild": "npm run clean && npm run build"
|
|
15
|
+
* - Can be used with arguments to specify custom directories: node dist/scripts/clean.js temp coverage
|
|
16
|
+
*
|
|
17
|
+
* Platform compatibility:
|
|
18
|
+
* - Works on all platforms (Windows, macOS, Linux) using Node.js path normalization
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { rm, access } from 'fs/promises';
|
|
22
|
+
import { join } from 'path';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Interface for clean operation result
|
|
26
|
+
*/
|
|
27
|
+
interface CleanResult {
|
|
28
|
+
dir: string;
|
|
29
|
+
status: 'success' | 'skipped';
|
|
30
|
+
reason?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a directory exists without using fs.Stats
|
|
35
|
+
*/
|
|
36
|
+
async function directoryExists(dirPath: string): Promise<boolean> {
|
|
37
|
+
try {
|
|
38
|
+
await access(dirPath);
|
|
39
|
+
return true;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Main clean function
|
|
47
|
+
*/
|
|
48
|
+
const clean = async (): Promise<void> => {
|
|
49
|
+
try {
|
|
50
|
+
// Default directories to clean
|
|
51
|
+
let dirsToClean: string[] = ['dist', 'logs'];
|
|
52
|
+
|
|
53
|
+
// If directories are specified as command line arguments, use those instead
|
|
54
|
+
const args = process.argv.slice(2);
|
|
55
|
+
if (args.length > 0) {
|
|
56
|
+
dirsToClean = args;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`Cleaning directories: ${dirsToClean.join(', ')}`);
|
|
60
|
+
|
|
61
|
+
// Process each directory
|
|
62
|
+
const results = await Promise.allSettled(
|
|
63
|
+
dirsToClean.map(async (dir): Promise<CleanResult> => {
|
|
64
|
+
const dirPath = join(process.cwd(), dir);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Check if directory exists before attempting to remove it
|
|
68
|
+
const exists = await directoryExists(dirPath);
|
|
69
|
+
|
|
70
|
+
if (!exists) {
|
|
71
|
+
return { dir, status: 'skipped', reason: 'does not exist' };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Remove directory if it exists
|
|
75
|
+
await rm(dirPath, { recursive: true, force: true });
|
|
76
|
+
return { dir, status: 'success' };
|
|
77
|
+
} catch (error) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Report results
|
|
84
|
+
for (const result of results) {
|
|
85
|
+
if (result.status === 'fulfilled') {
|
|
86
|
+
const { dir, status, reason } = result.value;
|
|
87
|
+
if (status === 'success') {
|
|
88
|
+
console.log(`✓ Successfully cleaned ${dir} directory`);
|
|
89
|
+
} else {
|
|
90
|
+
console.log(`- ${dir} directory ${reason}, skipping cleanup`);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
console.error(`× Error cleaning directory: ${result.reason}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('× Error during cleanup:', error instanceof Error ? error.message : error);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Execute the clean function
|
|
103
|
+
clean();
|
package/scripts/tree.ts
ADDED
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Directory Tree Generation Operation
|
|
5
|
+
* ==================================
|
|
6
|
+
*
|
|
7
|
+
* A utility for generating visual tree representations of the project's directory
|
|
8
|
+
* structure with configurable depth control and gitignore integration.
|
|
9
|
+
*
|
|
10
|
+
* This operation creates a formatted markdown file containing a hierarchical
|
|
11
|
+
* representation of directories and files, respecting ignore patterns and
|
|
12
|
+
* applying configurable filtering.
|
|
13
|
+
*
|
|
14
|
+
* Features:
|
|
15
|
+
* - Respects .gitignore patterns and common exclusions
|
|
16
|
+
* - Configurable maximum depth traversal
|
|
17
|
+
* - Customizable output location
|
|
18
|
+
* - Sorting with directories first
|
|
19
|
+
* - Cross-platform compatibility
|
|
20
|
+
*
|
|
21
|
+
* @module utilities/generate.directory.tree.operation
|
|
22
|
+
*
|
|
23
|
+
* Usage examples:
|
|
24
|
+
* - Add to package.json: "tree": "ts-node scripts/tree.ts"
|
|
25
|
+
* - Run directly: npm run tree
|
|
26
|
+
* - Custom output: ts-node scripts/tree.ts ./documentation/structure.md
|
|
27
|
+
* - Limit depth: ts-node scripts/tree.ts --depth=3
|
|
28
|
+
* - Show help: ts-node scripts/tree.ts --help
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import fs from 'fs/promises';
|
|
32
|
+
import path from 'path';
|
|
33
|
+
|
|
34
|
+
// -----------------------------------
|
|
35
|
+
// Type Definitions
|
|
36
|
+
// -----------------------------------
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Standardized error category classification (using type alias instead of enum)
|
|
40
|
+
*/
|
|
41
|
+
type ErrorCategoryType =
|
|
42
|
+
| 'VALIDATION'
|
|
43
|
+
| 'FILESYSTEM'
|
|
44
|
+
| 'SYSTEM'
|
|
45
|
+
| 'UNKNOWN';
|
|
46
|
+
|
|
47
|
+
const ErrorCategory = {
|
|
48
|
+
VALIDATION: 'VALIDATION' as ErrorCategoryType,
|
|
49
|
+
FILESYSTEM: 'FILESYSTEM' as ErrorCategoryType,
|
|
50
|
+
SYSTEM: 'SYSTEM' as ErrorCategoryType,
|
|
51
|
+
UNKNOWN: 'UNKNOWN' as ErrorCategoryType,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Error severity classification (using type alias instead of enum)
|
|
56
|
+
*/
|
|
57
|
+
type ErrorSeverityLevel = 0 | 1 | 2 | 3 | 4;
|
|
58
|
+
|
|
59
|
+
const ErrorSeverity = {
|
|
60
|
+
DEBUG: 0 as ErrorSeverityLevel,
|
|
61
|
+
INFO: 1 as ErrorSeverityLevel,
|
|
62
|
+
WARN: 2 as ErrorSeverityLevel,
|
|
63
|
+
ERROR: 3 as ErrorSeverityLevel,
|
|
64
|
+
FATAL: 4 as ErrorSeverityLevel,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Standardized error structure for consistent error handling
|
|
69
|
+
*/
|
|
70
|
+
interface StandardizedApplicationErrorObject {
|
|
71
|
+
errorMessage: string; // Human-readable description
|
|
72
|
+
errorCode: string; // Machine-readable identifier
|
|
73
|
+
errorCategory: ErrorCategoryType; // System area affected (using type alias)
|
|
74
|
+
errorSeverity: ErrorSeverityLevel; // How critical the error is (using type alias)
|
|
75
|
+
errorTimestamp: string; // When the error occurred
|
|
76
|
+
errorContext: Record<string, unknown>; // Additional relevant data
|
|
77
|
+
errorStack?: string; // Stack trace if available
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Successful result from an operation
|
|
82
|
+
*/
|
|
83
|
+
interface OperationResultSuccess<DataType> {
|
|
84
|
+
resultSuccessful: true;
|
|
85
|
+
resultData: DataType;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Failed result from an operation
|
|
90
|
+
*/
|
|
91
|
+
interface OperationResultFailure<ErrorType> {
|
|
92
|
+
resultSuccessful: false;
|
|
93
|
+
resultError: ErrorType;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Combined result type for operations
|
|
98
|
+
*/
|
|
99
|
+
type OperationResult<DataType, ErrorType = StandardizedApplicationErrorObject> =
|
|
100
|
+
| OperationResultSuccess<DataType>
|
|
101
|
+
| OperationResultFailure<ErrorType>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Configuration options for the tree generation operation
|
|
105
|
+
*/
|
|
106
|
+
interface TreeGenerationConfiguration {
|
|
107
|
+
treeOutputFilePath: string;
|
|
108
|
+
maximumDirectoryDepth: number;
|
|
109
|
+
showHelpText: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Definition of a gitignore pattern with parsing metadata
|
|
114
|
+
*/
|
|
115
|
+
interface GitignorePatternDefinition {
|
|
116
|
+
patternText: string;
|
|
117
|
+
isNegatedPattern: boolean;
|
|
118
|
+
regexPattern: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Result from the tree generation operation
|
|
123
|
+
*/
|
|
124
|
+
interface TreeGenerationResult {
|
|
125
|
+
projectName: string;
|
|
126
|
+
treeOutputFilePath: string;
|
|
127
|
+
treeContentLength: number;
|
|
128
|
+
maximumDepthApplied: number;
|
|
129
|
+
generationTimestamp: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// -----------------------------------
|
|
133
|
+
// Constants
|
|
134
|
+
// -----------------------------------
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Default patterns to always ignore regardless of gitignore contents
|
|
138
|
+
*/
|
|
139
|
+
const DEFAULT_IGNORE_PATTERNS: string[] = [
|
|
140
|
+
'.git',
|
|
141
|
+
'node_modules',
|
|
142
|
+
'.DS_Store',
|
|
143
|
+
'dist',
|
|
144
|
+
'build'
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Default output path for the generated tree
|
|
149
|
+
*/
|
|
150
|
+
const DEFAULT_OUTPUT_PATH = 'docs/tree.md';
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Help text displayed when requested
|
|
154
|
+
*/
|
|
155
|
+
const HELP_TEXT = `
|
|
156
|
+
Directory Tree Generator - Project structure visualization tool
|
|
157
|
+
|
|
158
|
+
Usage:
|
|
159
|
+
node dist/utilities/generate.directory.tree.operation.js [output-path] [--depth=<number>] [--help]
|
|
160
|
+
|
|
161
|
+
Options:
|
|
162
|
+
output-path Custom file path for the tree output (default: docs/tree.md)
|
|
163
|
+
--depth=<number> Maximum directory depth to display (default: unlimited)
|
|
164
|
+
--help Show this help message
|
|
165
|
+
`;
|
|
166
|
+
|
|
167
|
+
// -----------------------------------
|
|
168
|
+
// Utility Functions
|
|
169
|
+
// -----------------------------------
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Creates a standardized success result
|
|
173
|
+
*
|
|
174
|
+
* @param data - The data to include in the success result
|
|
175
|
+
* @returns A standardized success result object
|
|
176
|
+
*/
|
|
177
|
+
function createSuccessResult<DataType>(data: DataType): OperationResultSuccess<DataType> {
|
|
178
|
+
return { resultSuccessful: true, resultData: data };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Creates a standardized failure result
|
|
183
|
+
*
|
|
184
|
+
* @param error - The error to include in the failure result
|
|
185
|
+
* @returns A standardized failure result object
|
|
186
|
+
*/
|
|
187
|
+
function createFailureResult<ErrorType>(error: ErrorType): OperationResultFailure<ErrorType> {
|
|
188
|
+
return { resultSuccessful: false, resultError: error };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Creates a standardized error object
|
|
193
|
+
*
|
|
194
|
+
* @param message - Human-readable error message
|
|
195
|
+
* @param code - Machine-readable error code
|
|
196
|
+
* @param category - Error category classification
|
|
197
|
+
* @param severity - Error severity level
|
|
198
|
+
* @param context - Additional context data
|
|
199
|
+
* @returns A standardized error object
|
|
200
|
+
*/
|
|
201
|
+
function createStandardizedError(
|
|
202
|
+
message: string,
|
|
203
|
+
code: string,
|
|
204
|
+
category: ErrorCategoryType, // Use the type alias
|
|
205
|
+
severity: ErrorSeverityLevel, // Use the type alias
|
|
206
|
+
context: Record<string, unknown> = {}
|
|
207
|
+
): StandardizedApplicationErrorObject {
|
|
208
|
+
return {
|
|
209
|
+
errorMessage: message,
|
|
210
|
+
errorCode: code,
|
|
211
|
+
errorCategory: category,
|
|
212
|
+
errorSeverity: severity,
|
|
213
|
+
errorTimestamp: new Date().toISOString(),
|
|
214
|
+
errorContext: context
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Converts an exception to a standardized error object
|
|
220
|
+
*
|
|
221
|
+
* @param exception - The caught exception
|
|
222
|
+
* @param defaultMessage - Fallback message if exception is not an Error object
|
|
223
|
+
* @returns A standardized error object
|
|
224
|
+
*/
|
|
225
|
+
function wrapExceptionAsStandardizedError(
|
|
226
|
+
exception: unknown,
|
|
227
|
+
defaultMessage: string
|
|
228
|
+
): StandardizedApplicationErrorObject {
|
|
229
|
+
const errorMessage = exception instanceof Error ? exception.message : defaultMessage;
|
|
230
|
+
const errorStack = exception instanceof Error ? exception.stack : undefined;
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
errorMessage,
|
|
234
|
+
errorCode: 'UNEXPECTED_ERROR',
|
|
235
|
+
errorCategory: ErrorCategory.UNKNOWN, // Use the constant object
|
|
236
|
+
errorSeverity: ErrorSeverity.ERROR, // Use the constant object
|
|
237
|
+
errorTimestamp: new Date().toISOString(),
|
|
238
|
+
errorContext: { originalException: exception },
|
|
239
|
+
errorStack
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// -----------------------------------
|
|
244
|
+
// Implementation Functions
|
|
245
|
+
// -----------------------------------
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Parses command line arguments to extract configuration options
|
|
249
|
+
*
|
|
250
|
+
* @param commandLineArguments - Array of arguments from process.argv
|
|
251
|
+
* @returns Configuration object for tree generation
|
|
252
|
+
*/
|
|
253
|
+
function parseCommandLineArguments(
|
|
254
|
+
commandLineArguments: string[]
|
|
255
|
+
): TreeGenerationConfiguration {
|
|
256
|
+
let treeOutputFilePath = DEFAULT_OUTPUT_PATH;
|
|
257
|
+
let maximumDirectoryDepth = Infinity;
|
|
258
|
+
let showHelpText = false;
|
|
259
|
+
|
|
260
|
+
for (const argumentValue of commandLineArguments) {
|
|
261
|
+
if (argumentValue === '--help') {
|
|
262
|
+
showHelpText = true;
|
|
263
|
+
} else if (argumentValue.startsWith('--depth=')) {
|
|
264
|
+
const depthValue = argumentValue.split('=')[1];
|
|
265
|
+
const parsedDepth = parseInt(depthValue, 10);
|
|
266
|
+
|
|
267
|
+
if (isNaN(parsedDepth) || parsedDepth < 1) {
|
|
268
|
+
console.error('Invalid depth value. Using unlimited depth.');
|
|
269
|
+
maximumDirectoryDepth = Infinity;
|
|
270
|
+
} else {
|
|
271
|
+
maximumDirectoryDepth = parsedDepth;
|
|
272
|
+
}
|
|
273
|
+
} else if (!argumentValue.startsWith('--')) {
|
|
274
|
+
// If it's not an option flag, assume it's the output path
|
|
275
|
+
treeOutputFilePath = argumentValue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
treeOutputFilePath,
|
|
281
|
+
maximumDirectoryDepth,
|
|
282
|
+
showHelpText
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Loads and parses patterns from the .gitignore file
|
|
288
|
+
*
|
|
289
|
+
* @returns Promise resolving to an array of parsed gitignore patterns
|
|
290
|
+
*/
|
|
291
|
+
async function loadGitignorePatternDefinitions(): Promise<OperationResult<GitignorePatternDefinition[]>> {
|
|
292
|
+
try {
|
|
293
|
+
const gitignoreContent = await fs.readFile('.gitignore', 'utf-8');
|
|
294
|
+
|
|
295
|
+
const patternDefinitions = gitignoreContent
|
|
296
|
+
.split('\n')
|
|
297
|
+
.map(line => line.trim())
|
|
298
|
+
// Remove comments, empty lines, and lines with just whitespace
|
|
299
|
+
.filter(line => line && !line.startsWith('#') && line.trim() !== '')
|
|
300
|
+
// Process each pattern
|
|
301
|
+
.map(pattern => ({
|
|
302
|
+
patternText: pattern.startsWith('!') ? pattern.slice(1) : pattern,
|
|
303
|
+
isNegatedPattern: pattern.startsWith('!'),
|
|
304
|
+
// Convert glob patterns to regex-compatible strings (simplified approach)
|
|
305
|
+
regexPattern: pattern
|
|
306
|
+
.replace(/\./g, '\\.') // Escape dots first
|
|
307
|
+
.replace(/\*/g, '.*') // Convert * to .*
|
|
308
|
+
.replace(/\?/g, '.') // Convert ? to .
|
|
309
|
+
.replace(/\/$/, '(/.*)?') // Handle directory indicators
|
|
310
|
+
}));
|
|
311
|
+
|
|
312
|
+
return createSuccessResult(patternDefinitions);
|
|
313
|
+
} catch (exceptionObject) {
|
|
314
|
+
console.warn('No .gitignore file found, using default patterns only');
|
|
315
|
+
return createSuccessResult([]);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Checks if a given file path should be ignored based on patterns
|
|
321
|
+
*
|
|
322
|
+
* @param entryPath - The relative path to check
|
|
323
|
+
* @param ignorePatternDefinitions - Array of parsed gitignore patterns
|
|
324
|
+
* @returns Boolean indicating if the path should be ignored
|
|
325
|
+
*/
|
|
326
|
+
function checkPathShouldBeIgnored(
|
|
327
|
+
entryPath: string,
|
|
328
|
+
ignorePatternDefinitions: GitignorePatternDefinition[]
|
|
329
|
+
): boolean {
|
|
330
|
+
// Always check default patterns first
|
|
331
|
+
if (DEFAULT_IGNORE_PATTERNS.some(pattern => entryPath.includes(pattern))) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let shouldBeIgnored = false;
|
|
336
|
+
|
|
337
|
+
for (const { patternText, isNegatedPattern, regexPattern } of ignorePatternDefinitions) {
|
|
338
|
+
// Convert the pattern to a proper regex
|
|
339
|
+
const compiledRegexPattern = new RegExp(`^${regexPattern}$|/${regexPattern}$|/${regexPattern}/`);
|
|
340
|
+
|
|
341
|
+
if (compiledRegexPattern.test(entryPath)) {
|
|
342
|
+
// If it's a negation pattern (!pattern), this file should NOT be ignored
|
|
343
|
+
// Otherwise, it should be ignored
|
|
344
|
+
shouldBeIgnored = !isNegatedPattern;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return shouldBeIgnored;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Recursively generates a tree representation of the directory structure
|
|
353
|
+
*
|
|
354
|
+
* @param directoryPath - Path to the directory to process
|
|
355
|
+
* @param ignorePatternDefinitions - Array of gitignore pattern definitions
|
|
356
|
+
* @param prefixString - Prefix string for the current level (used for indentation)
|
|
357
|
+
* @param isLastEntry - Whether this is the last entry at the current level
|
|
358
|
+
* @param relativePathString - Relative path from the root directory
|
|
359
|
+
* @param currentDepthLevel - Current depth level in the traversal
|
|
360
|
+
* @returns Promise resolving to the string representation of the tree
|
|
361
|
+
*/
|
|
362
|
+
async function generateDirectoryTreeRepresentation(
|
|
363
|
+
directoryPath: string,
|
|
364
|
+
ignorePatternDefinitions: GitignorePatternDefinition[],
|
|
365
|
+
prefixString = '',
|
|
366
|
+
isLastEntry = true,
|
|
367
|
+
relativePathString = '',
|
|
368
|
+
currentDepthLevel = 0,
|
|
369
|
+
maximumDepthLevel = Infinity
|
|
370
|
+
): Promise<OperationResult<string>> {
|
|
371
|
+
try {
|
|
372
|
+
const directoryEntries = await fs.readdir(directoryPath, { withFileTypes: true });
|
|
373
|
+
let treeOutputContent = '';
|
|
374
|
+
|
|
375
|
+
// Filter and sort entries
|
|
376
|
+
const filteredEntries = directoryEntries
|
|
377
|
+
.filter(entry => {
|
|
378
|
+
const entryPath = path.join(relativePathString, entry.name);
|
|
379
|
+
return !checkPathShouldBeIgnored(entryPath, ignorePatternDefinitions);
|
|
380
|
+
})
|
|
381
|
+
.sort((a, b) => {
|
|
382
|
+
// Directories first, then files
|
|
383
|
+
if (a.isDirectory() && !b.isDirectory()) return -1;
|
|
384
|
+
if (!a.isDirectory() && b.isDirectory()) return 1;
|
|
385
|
+
return a.name.localeCompare(b.name);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
for (let entryIndex = 0; entryIndex < filteredEntries.length; entryIndex++) {
|
|
389
|
+
const entryItem = filteredEntries[entryIndex];
|
|
390
|
+
const isLastItem = entryIndex === filteredEntries.length - 1;
|
|
391
|
+
const newPrefixString = prefixString + (isLastEntry ? ' ' : '│ ');
|
|
392
|
+
const newRelativePath = path.join(relativePathString, entryItem.name);
|
|
393
|
+
|
|
394
|
+
treeOutputContent += prefixString + (isLastItem ? '└── ' : '├── ') + entryItem.name + '\n';
|
|
395
|
+
|
|
396
|
+
// Only traverse deeper if we haven't reached maximumDepthLevel
|
|
397
|
+
if (entryItem.isDirectory() && currentDepthLevel < maximumDepthLevel) {
|
|
398
|
+
const subTreeResult = await generateDirectoryTreeRepresentation(
|
|
399
|
+
path.join(directoryPath, entryItem.name),
|
|
400
|
+
ignorePatternDefinitions,
|
|
401
|
+
newPrefixString,
|
|
402
|
+
isLastItem,
|
|
403
|
+
newRelativePath,
|
|
404
|
+
currentDepthLevel + 1,
|
|
405
|
+
maximumDepthLevel
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
if (subTreeResult.resultSuccessful) {
|
|
409
|
+
treeOutputContent += subTreeResult.resultData;
|
|
410
|
+
} else {
|
|
411
|
+
return subTreeResult; // Propagate error
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return createSuccessResult(treeOutputContent);
|
|
417
|
+
} catch (exceptionObject) {
|
|
418
|
+
return createFailureResult(
|
|
419
|
+
wrapExceptionAsStandardizedError(
|
|
420
|
+
exceptionObject,
|
|
421
|
+
`Failed to generate tree for directory: ${directoryPath}`
|
|
422
|
+
)
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Ensures the directory for the output file exists, creating it if needed
|
|
429
|
+
*
|
|
430
|
+
* @param directoryPath - Path to the directory to check/create
|
|
431
|
+
* @returns Promise resolving to operation result
|
|
432
|
+
*/
|
|
433
|
+
async function ensureDirectoryExists(
|
|
434
|
+
directoryPath: string
|
|
435
|
+
): Promise<OperationResult<boolean>> {
|
|
436
|
+
try {
|
|
437
|
+
await fs.access(directoryPath);
|
|
438
|
+
return createSuccessResult(true);
|
|
439
|
+
} catch {
|
|
440
|
+
try {
|
|
441
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
442
|
+
console.log(`Creating directory: ${directoryPath}`);
|
|
443
|
+
return createSuccessResult(true);
|
|
444
|
+
} catch (exceptionObject) {
|
|
445
|
+
return createFailureResult(
|
|
446
|
+
wrapExceptionAsStandardizedError(
|
|
447
|
+
exceptionObject,
|
|
448
|
+
`Failed to create directory: ${directoryPath}`
|
|
449
|
+
)
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Writes the generated tree content to a markdown file
|
|
457
|
+
*
|
|
458
|
+
* @param projectName - Name of the project
|
|
459
|
+
* @param treeContent - Generated tree content
|
|
460
|
+
* @param outputFilePath - Path where the output file should be written
|
|
461
|
+
* @param maximumDepthValue - Maximum depth value that was applied
|
|
462
|
+
* @returns Promise resolving to operation result
|
|
463
|
+
*/
|
|
464
|
+
async function writeTreeContentToFile(
|
|
465
|
+
projectName: string,
|
|
466
|
+
treeContent: string,
|
|
467
|
+
outputFilePath: string,
|
|
468
|
+
maximumDepthValue: number
|
|
469
|
+
): Promise<OperationResult<TreeGenerationResult>> {
|
|
470
|
+
try {
|
|
471
|
+
const rootDirectoryPath = process.cwd();
|
|
472
|
+
const outputDirectoryPath = path.dirname(path.resolve(rootDirectoryPath, outputFilePath));
|
|
473
|
+
|
|
474
|
+
// Ensure output directory exists
|
|
475
|
+
const directoryResult = await ensureDirectoryExists(outputDirectoryPath);
|
|
476
|
+
if (!directoryResult.resultSuccessful) {
|
|
477
|
+
return directoryResult;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Format the timestamp
|
|
481
|
+
const timestamp = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
|
|
482
|
+
|
|
483
|
+
// Format the markdown content
|
|
484
|
+
const markdownContent = `# ${projectName} - Directory Structure
|
|
485
|
+
|
|
486
|
+
Generated on: ${timestamp}
|
|
487
|
+
|
|
488
|
+
${maximumDepthValue !== Infinity ? `_Depth limited to ${maximumDepthValue} levels_\n\n` : ''}
|
|
489
|
+
\`\`\`
|
|
490
|
+
${projectName}
|
|
491
|
+
${treeContent}
|
|
492
|
+
\`\`\`
|
|
493
|
+
|
|
494
|
+
_Note: This tree excludes files and directories matched by .gitignore and common patterns like node_modules._
|
|
495
|
+
`;
|
|
496
|
+
|
|
497
|
+
// Write the content to the file
|
|
498
|
+
await fs.writeFile(
|
|
499
|
+
path.resolve(rootDirectoryPath, outputFilePath),
|
|
500
|
+
markdownContent
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
return createSuccessResult({
|
|
504
|
+
projectName,
|
|
505
|
+
treeOutputFilePath: outputFilePath,
|
|
506
|
+
treeContentLength: treeContent.length,
|
|
507
|
+
maximumDepthApplied: maximumDepthValue,
|
|
508
|
+
generationTimestamp: timestamp
|
|
509
|
+
});
|
|
510
|
+
} catch (exceptionObject) {
|
|
511
|
+
return createFailureResult(
|
|
512
|
+
wrapExceptionAsStandardizedError(
|
|
513
|
+
exceptionObject,
|
|
514
|
+
`Failed to write tree to file: ${outputFilePath}`
|
|
515
|
+
)
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Main operation function that orchestrates the tree generation process
|
|
522
|
+
*
|
|
523
|
+
* @returns Promise that resolves when the operation completes
|
|
524
|
+
*/
|
|
525
|
+
async function generateProjectDirectoryTree(): Promise<void> {
|
|
526
|
+
try {
|
|
527
|
+
// Parse command line arguments
|
|
528
|
+
const commandLineArguments = process.argv.slice(2);
|
|
529
|
+
const configurationSettings = parseCommandLineArguments(commandLineArguments);
|
|
530
|
+
|
|
531
|
+
// Display help if requested
|
|
532
|
+
if (configurationSettings.showHelpText) {
|
|
533
|
+
console.log(HELP_TEXT);
|
|
534
|
+
process.exit(0);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const rootDirectoryPath = process.cwd();
|
|
538
|
+
const projectName = path.basename(rootDirectoryPath);
|
|
539
|
+
|
|
540
|
+
// Load gitignore patterns
|
|
541
|
+
const ignorePatternResult = await loadGitignorePatternDefinitions();
|
|
542
|
+
if (!ignorePatternResult.resultSuccessful) {
|
|
543
|
+
throw new Error(`Failed to load gitignore patterns: ${ignorePatternResult.resultError.errorMessage}`);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const ignorePatternDefinitions = ignorePatternResult.resultData;
|
|
547
|
+
|
|
548
|
+
console.log(`Generating directory tree for: ${projectName}`);
|
|
549
|
+
console.log(`Output path: ${configurationSettings.treeOutputFilePath}`);
|
|
550
|
+
|
|
551
|
+
if (configurationSettings.maximumDirectoryDepth !== Infinity) {
|
|
552
|
+
console.log(`Maximum depth: ${configurationSettings.maximumDirectoryDepth}`);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Generate the tree structure
|
|
556
|
+
const treeGenerationResult = await generateDirectoryTreeRepresentation(
|
|
557
|
+
rootDirectoryPath,
|
|
558
|
+
ignorePatternDefinitions,
|
|
559
|
+
'',
|
|
560
|
+
true,
|
|
561
|
+
'',
|
|
562
|
+
0,
|
|
563
|
+
configurationSettings.maximumDirectoryDepth
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
if (!treeGenerationResult.resultSuccessful) {
|
|
567
|
+
throw new Error(`Failed to generate tree: ${treeGenerationResult.resultError.errorMessage}`);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Write the tree to a file
|
|
571
|
+
const writeResult = await writeTreeContentToFile(
|
|
572
|
+
projectName,
|
|
573
|
+
treeGenerationResult.resultData,
|
|
574
|
+
configurationSettings.treeOutputFilePath,
|
|
575
|
+
configurationSettings.maximumDirectoryDepth
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
if (!writeResult.resultSuccessful) {
|
|
579
|
+
throw new Error(`Failed to write tree: ${writeResult.resultError.errorMessage}`);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
console.log(`✓ Successfully generated tree structure in ${configurationSettings.treeOutputFilePath}`);
|
|
583
|
+
} catch (exceptionObject) {
|
|
584
|
+
const standardizedError = wrapExceptionAsStandardizedError(
|
|
585
|
+
exceptionObject,
|
|
586
|
+
'Unhandled error during tree generation'
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
console.error(`× Error generating tree: ${standardizedError.errorMessage}`);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// -----------------------------------
|
|
595
|
+
// Script Execution
|
|
596
|
+
// -----------------------------------
|
|
597
|
+
|
|
598
|
+
// Execute the main operation function
|
|
599
|
+
generateProjectDirectoryTree();
|
|
@@ -48,7 +48,7 @@ export const ObsidianPropertiesSchema = z.object({
|
|
|
48
48
|
papers: z.array(z.string()).optional(),
|
|
49
49
|
|
|
50
50
|
// Custom Fields
|
|
51
|
-
custom: z.
|
|
51
|
+
custom: z.object({}).catchall(z.unknown()).optional() // Ensure 'custom' is an object if present
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
/**
|
|
@@ -78,7 +78,7 @@ export const PropertyUpdateSchema = z.object({
|
|
|
78
78
|
papers: z.array(z.string()).optional(),
|
|
79
79
|
|
|
80
80
|
// Custom Fields
|
|
81
|
-
custom: z.
|
|
81
|
+
custom: z.object({}).catchall(z.unknown()).optional() // Ensure 'custom' is an object if present
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
/**
|
|
@@ -113,4 +113,4 @@ export interface PropertyManagerResult {
|
|
|
113
113
|
message: string;
|
|
114
114
|
properties?: ObsidianProperties;
|
|
115
115
|
errors?: string[];
|
|
116
|
-
}
|
|
116
|
+
}
|