obsidian-native-mcp 0.2.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 (54) hide show
  1. package/.github/workflows/ci.yml +69 -0
  2. package/.husky/pre-commit +1 -0
  3. package/.prettierignore +3 -0
  4. package/.prettierrc +7 -0
  5. package/.releaserc.json +24 -0
  6. package/DEVELOPER.md +158 -0
  7. package/LICENSE +21 -0
  8. package/README.md +179 -0
  9. package/dist/cli/index.js +22 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/handlers/prompts.js +127 -0
  12. package/dist/handlers/prompts.js.map +1 -0
  13. package/dist/handlers/tools.js +113 -0
  14. package/dist/handlers/tools.js.map +1 -0
  15. package/dist/mcp/http-transport.js +124 -0
  16. package/dist/mcp/http-transport.js.map +1 -0
  17. package/dist/mcp/protocol.js +38 -0
  18. package/dist/mcp/protocol.js.map +1 -0
  19. package/dist/mcp/server.js +257 -0
  20. package/dist/mcp/server.js.map +1 -0
  21. package/dist/mcp/stdio-transport.js +41 -0
  22. package/dist/mcp/stdio-transport.js.map +1 -0
  23. package/dist/mcp/transport.js +3 -0
  24. package/dist/mcp/transport.js.map +1 -0
  25. package/dist/plugin/main.js +1201 -0
  26. package/dist/plugin/main.js.map +1 -0
  27. package/dist/plugin/manifest.json +10 -0
  28. package/dist/plugin/settings.js +63 -0
  29. package/dist/plugin/settings.js.map +1 -0
  30. package/dist/utils/fs-utils.js +268 -0
  31. package/dist/utils/fs-utils.js.map +1 -0
  32. package/dist/utils/search.js +62 -0
  33. package/dist/utils/search.js.map +1 -0
  34. package/dist/utils/vaults.js +165 -0
  35. package/dist/utils/vaults.js.map +1 -0
  36. package/eslint.config.mjs +16 -0
  37. package/manifest.json +10 -0
  38. package/package.json +48 -0
  39. package/scripts/build-plugin.mjs +35 -0
  40. package/scripts/sync-version.cjs +12 -0
  41. package/src/cli/index.ts +25 -0
  42. package/src/handlers/prompts.ts +148 -0
  43. package/src/handlers/tools.ts +146 -0
  44. package/src/mcp/http-transport.ts +138 -0
  45. package/src/mcp/protocol.ts +69 -0
  46. package/src/mcp/server.ts +272 -0
  47. package/src/mcp/stdio-transport.ts +43 -0
  48. package/src/mcp/transport.ts +8 -0
  49. package/src/plugin/main.ts +91 -0
  50. package/src/plugin/settings.ts +69 -0
  51. package/src/utils/fs-utils.ts +358 -0
  52. package/src/utils/search.ts +84 -0
  53. package/src/utils/vaults.ts +175 -0
  54. package/tsconfig.json +20 -0
@@ -0,0 +1,358 @@
1
+ import { readdirSync, statSync, unlinkSync, mkdirSync, existsSync } from "fs";
2
+ import { readFile, writeFile, access } from "fs/promises";
3
+ import { join, dirname, relative } from "path";
4
+
5
+ export interface FileEntry {
6
+ name: string;
7
+ path: string;
8
+ type: "file" | "directory";
9
+ size?: number;
10
+ mtime?: Date;
11
+ }
12
+
13
+ async function fileExists(path: string): Promise<boolean> {
14
+ try {
15
+ await access(path);
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ async function readTextFile(path: string): Promise<string> {
23
+ return readFile(path, "utf-8");
24
+ }
25
+
26
+ async function writeTextFile(path: string, content: string): Promise<void> {
27
+ await writeFile(path, content, "utf-8");
28
+ }
29
+
30
+ export function listFiles(vaultPath: string, directory?: string): FileEntry[] {
31
+ const targetDir = directory ? join(vaultPath, directory) : vaultPath;
32
+
33
+ if (!existsSync(targetDir)) {
34
+ throw new Error(`Directory not found: ${targetDir}`);
35
+ }
36
+
37
+ const entries = readdirSync(targetDir);
38
+ const result: FileEntry[] = [];
39
+
40
+ for (const entry of entries) {
41
+ const fullPath = join(targetDir, entry);
42
+ const stat = statSync(fullPath);
43
+ result.push({
44
+ name: entry,
45
+ path: relative(vaultPath, fullPath),
46
+ type: stat.isDirectory() ? "directory" : "file",
47
+ size: stat.size,
48
+ mtime: stat.mtime,
49
+ });
50
+ }
51
+
52
+ return result.sort((a, b) => {
53
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
54
+ return a.name.localeCompare(b.name);
55
+ });
56
+ }
57
+
58
+ export async function readFileHandler(
59
+ vaultPath: string,
60
+ filename: string,
61
+ ): Promise<{ content: string; frontmatter?: Record<string, any> }> {
62
+ const fullPath = join(vaultPath, filename);
63
+
64
+ if (!(await fileExists(fullPath))) {
65
+ throw new Error(`File not found: ${filename}`);
66
+ }
67
+
68
+ const stat = statSync(fullPath);
69
+ if (!stat.isFile()) {
70
+ throw new Error(`Not a file: ${filename}`);
71
+ }
72
+
73
+ const content = await readTextFile(fullPath);
74
+ const parsed = parseFrontmatter(content);
75
+
76
+ return {
77
+ content,
78
+ frontmatter: parsed?.frontmatter,
79
+ };
80
+ }
81
+
82
+ export async function createFile(
83
+ vaultPath: string,
84
+ filename: string,
85
+ content: string,
86
+ ): Promise<void> {
87
+ const fullPath = join(vaultPath, filename);
88
+ const dir = dirname(fullPath);
89
+
90
+ if (!existsSync(dir)) {
91
+ mkdirSync(dir, { recursive: true });
92
+ }
93
+
94
+ await writeTextFile(fullPath, content);
95
+ }
96
+
97
+ export async function appendFile(
98
+ vaultPath: string,
99
+ filename: string,
100
+ content: string,
101
+ ): Promise<void> {
102
+ const fullPath = join(vaultPath, filename);
103
+ const dir = dirname(fullPath);
104
+
105
+ if (!existsSync(dir)) {
106
+ mkdirSync(dir, { recursive: true });
107
+ }
108
+
109
+ let existing = "";
110
+ if (await fileExists(fullPath)) {
111
+ existing = await readTextFile(fullPath);
112
+ }
113
+
114
+ await writeTextFile(fullPath, existing + content);
115
+ }
116
+
117
+ export function deleteFileHandler(vaultPath: string, filename: string): void {
118
+ const fullPath = join(vaultPath, filename);
119
+
120
+ if (!existsSync(fullPath)) {
121
+ throw new Error(`File not found: ${filename}`);
122
+ }
123
+
124
+ unlinkSync(fullPath);
125
+ }
126
+
127
+ export async function patchFile(
128
+ vaultPath: string,
129
+ filename: string,
130
+ operation: "append" | "prepend" | "replace",
131
+ targetType: "heading" | "block" | "frontmatter",
132
+ target: string,
133
+ content: string,
134
+ options?: {
135
+ contentType?: string;
136
+ targetDelimiter?: string;
137
+ trimTargetWhitespace?: boolean;
138
+ },
139
+ ): Promise<string> {
140
+ const fullPath = join(vaultPath, filename);
141
+
142
+ if (!(await fileExists(fullPath))) {
143
+ throw new Error(`File not found: ${filename}`);
144
+ }
145
+
146
+ const fileContent = await readTextFile(fullPath);
147
+ let modified: string;
148
+
149
+ if (targetType === "frontmatter") {
150
+ modified = patchFrontmatter(fileContent, operation, target, content);
151
+ } else if (targetType === "heading") {
152
+ modified = patchHeading(fileContent, operation, target, content, options);
153
+ } else if (targetType === "block") {
154
+ modified = patchBlock(fileContent, operation, target, content);
155
+ } else {
156
+ throw new Error(`Unsupported target type: ${targetType}`);
157
+ }
158
+
159
+ await writeTextFile(fullPath, modified);
160
+ return modified;
161
+ }
162
+
163
+ function parseFrontmatter(
164
+ content: string,
165
+ ): { frontmatter: Record<string, any>; body: string } | null {
166
+ if (!content.startsWith("---")) return null;
167
+
168
+ const endIndex = content.indexOf("---", 3);
169
+ if (endIndex === -1) return null;
170
+
171
+ const raw = content.slice(3, endIndex).trim();
172
+ const body = content.slice(endIndex + 3).trim();
173
+ const frontmatter: Record<string, any> = {};
174
+
175
+ for (const line of raw.split("\n")) {
176
+ const colonIndex = line.indexOf(":");
177
+ if (colonIndex === -1) continue;
178
+ const key = line.slice(0, colonIndex).trim();
179
+ let value: any = line.slice(colonIndex + 1).trim();
180
+
181
+ if (value.startsWith('"') && value.endsWith('"')) {
182
+ value = value.slice(1, -1);
183
+ } else if (value.startsWith("[") && value.endsWith("]")) {
184
+ value = value
185
+ .slice(1, -1)
186
+ .split(",")
187
+ .map((s: string) => s.trim().replace(/^["']|["']$/g, ""))
188
+ .filter(Boolean);
189
+ }
190
+
191
+ frontmatter[key] = value;
192
+ }
193
+
194
+ return { frontmatter, body };
195
+ }
196
+
197
+ function patchFrontmatter(
198
+ content: string,
199
+ operation: "append" | "prepend" | "replace",
200
+ target: string,
201
+ newContent: string,
202
+ ): string {
203
+ const parsed = parseFrontmatter(content);
204
+
205
+ if (operation === "replace" && target === "all") {
206
+ if (parsed) {
207
+ return `---\n${newContent}\n---\n\n${parsed.body}`;
208
+ }
209
+ return `---\n${newContent}\n---\n\n${content}`;
210
+ }
211
+
212
+ if (parsed) {
213
+ const lines = content.split("\n");
214
+ let fmEnd = -1;
215
+ for (let i = 1; i < lines.length; i++) {
216
+ if (lines[i].trim() === "---") {
217
+ fmEnd = i;
218
+ break;
219
+ }
220
+ }
221
+
222
+ const fmLines = lines.slice(1, fmEnd);
223
+ const bodyLines = lines.slice(fmEnd + 1);
224
+
225
+ if (operation === "replace") {
226
+ const existing = fmLines.findIndex((l) => l.trim().startsWith(target + ":"));
227
+ if (existing !== -1) {
228
+ fmLines[existing] = `${target}: ${newContent}`;
229
+ } else {
230
+ fmLines.push(`${target}: ${newContent}`);
231
+ }
232
+ } else if (operation === "append") {
233
+ fmLines.push(`${target}: ${newContent}`);
234
+ }
235
+
236
+ return ["---", ...fmLines, "---", "", ...bodyLines].join("\n");
237
+ }
238
+
239
+ if (operation === "replace" || operation === "append") {
240
+ return `---\n${target}: ${newContent}\n---\n\n${content}`;
241
+ }
242
+
243
+ return content;
244
+ }
245
+
246
+ function findHeadingLine(
247
+ lines: string[],
248
+ target: string,
249
+ delimiter: string,
250
+ ): { lineIndex: number; level: number } | null {
251
+ const parts = target.split(delimiter).map((s) => s.trim());
252
+
253
+ let currentLine = 0;
254
+ for (const part of parts) {
255
+ let found = false;
256
+ for (let i = currentLine; i < lines.length; i++) {
257
+ const headingMatch = lines[i].match(/^(#{1,6})\s+(.+)$/);
258
+ if (headingMatch && headingMatch[2].trim() === part) {
259
+ currentLine = i;
260
+ found = true;
261
+ break;
262
+ }
263
+ }
264
+ if (!found) return null;
265
+ }
266
+
267
+ const match = lines[currentLine].match(/^(#{1,6})\s/);
268
+ return { lineIndex: currentLine, level: match ? match[1].length : 1 };
269
+ }
270
+
271
+ function getHeadingContentEnd(lines: string[], startLine: number, level: number): number {
272
+ for (let i = startLine + 1; i < lines.length; i++) {
273
+ const match = lines[i].match(/^(#{1,6})\s/);
274
+ if (match && match[1].length <= level) {
275
+ return i;
276
+ }
277
+ }
278
+ return lines.length;
279
+ }
280
+
281
+ function patchHeading(
282
+ content: string,
283
+ operation: "append" | "prepend" | "replace",
284
+ target: string,
285
+ newContent: string,
286
+ options?: any,
287
+ ): string {
288
+ const delimiter = options?.targetDelimiter || "::";
289
+ const lines = content.split("\n");
290
+ const heading = findHeadingLine(lines, target, delimiter);
291
+
292
+ if (!heading) {
293
+ if (operation === "replace") return content;
294
+ const targetParts = target.split(delimiter).map((s) => s.trim());
295
+ const lastPart = targetParts[targetParts.length - 1];
296
+ return content + `\n\n## ${lastPart}\n\n${newContent}`;
297
+ }
298
+
299
+ const sectionEnd = getHeadingContentEnd(lines, heading.lineIndex, heading.level);
300
+
301
+ if (operation === "replace") {
302
+ const newLines = [
303
+ ...lines.slice(0, heading.lineIndex + 1),
304
+ newContent,
305
+ ...lines.slice(sectionEnd),
306
+ ];
307
+ return newLines.join("\n");
308
+ }
309
+
310
+ if (operation === "append") {
311
+ const newLines = [...lines.slice(0, sectionEnd), newContent, ...lines.slice(sectionEnd)];
312
+ return newLines.join("\n");
313
+ }
314
+
315
+ if (operation === "prepend") {
316
+ const newLines = [
317
+ ...lines.slice(0, heading.lineIndex + 1),
318
+ newContent,
319
+ ...lines.slice(heading.lineIndex + 1),
320
+ ];
321
+ return newLines.join("\n");
322
+ }
323
+
324
+ return content;
325
+ }
326
+
327
+ function patchBlock(
328
+ content: string,
329
+ operation: "append" | "prepend" | "replace",
330
+ target: string,
331
+ newContent: string,
332
+ ): string {
333
+ const blockId = target.startsWith("^") ? target : `^${target}`;
334
+ const lines = content.split("\n");
335
+ const blockIndex = lines.findIndex((l) => l.trim().endsWith(blockId));
336
+
337
+ if (blockIndex === -1) {
338
+ if (operation === "replace") return content;
339
+ return content + `\n\n${newContent} ${blockId}`;
340
+ }
341
+
342
+ if (operation === "replace") {
343
+ lines[blockIndex] = lines[blockIndex].replace(/^(.*?)(\s+\^\w+)?$/, `${newContent} ${blockId}`);
344
+ return lines.join("\n");
345
+ }
346
+
347
+ if (operation === "append") {
348
+ lines.splice(blockIndex + 1, 0, newContent);
349
+ return lines.join("\n");
350
+ }
351
+
352
+ if (operation === "prepend") {
353
+ lines.splice(blockIndex, 0, newContent);
354
+ return lines.join("\n");
355
+ }
356
+
357
+ return content;
358
+ }
@@ -0,0 +1,84 @@
1
+ import { readdirSync, statSync } from "fs";
2
+ import { readFile } from "fs/promises";
3
+ import { join, relative } from "path";
4
+
5
+ export interface SearchMatch {
6
+ file: string;
7
+ line: number;
8
+ content: string;
9
+ contextBefore?: string;
10
+ contextAfter?: string;
11
+ }
12
+
13
+ export async function searchInVault(
14
+ vaultPath: string,
15
+ query: string,
16
+ directory?: string,
17
+ contextLength: number = 100,
18
+ ): Promise<SearchMatch[]> {
19
+ const searchDir = directory ? join(vaultPath, directory) : vaultPath;
20
+ const results: SearchMatch[] = [];
21
+ const searchTerms = query.toLowerCase().split(/\s+/).filter(Boolean);
22
+
23
+ await walkDir(searchDir, vaultPath, results, searchTerms, contextLength);
24
+ return results;
25
+ }
26
+
27
+ async function walkDir(
28
+ dirPath: string,
29
+ vaultPath: string,
30
+ results: SearchMatch[],
31
+ searchTerms: string[],
32
+ contextLength: number,
33
+ ): Promise<void> {
34
+ let entries: string[];
35
+ try {
36
+ entries = readdirSync(dirPath);
37
+ } catch {
38
+ return;
39
+ }
40
+
41
+ for (const entry of entries) {
42
+ const fullPath = join(dirPath, entry);
43
+
44
+ let stat: ReturnType<typeof statSync>;
45
+ try {
46
+ stat = statSync(fullPath);
47
+ } catch {
48
+ continue;
49
+ }
50
+
51
+ if (stat.isDirectory()) {
52
+ if (!entry.startsWith(".")) {
53
+ await walkDir(fullPath, vaultPath, results, searchTerms, contextLength);
54
+ }
55
+ continue;
56
+ }
57
+
58
+ if (!entry.endsWith(".md")) continue;
59
+
60
+ try {
61
+ const content = await readFile(fullPath, "utf-8");
62
+ const lines = content.split("\n");
63
+ const relPath = relative(vaultPath, fullPath);
64
+
65
+ for (let i = 0; i < lines.length; i++) {
66
+ const lowerLine = lines[i].toLowerCase();
67
+ const matchesAll = searchTerms.every((term) => lowerLine.includes(term));
68
+
69
+ if (matchesAll) {
70
+ results.push({
71
+ file: relPath,
72
+ line: i + 1,
73
+ content: lines[i].substring(0, 500),
74
+ contextBefore: i > 0 ? lines[i - 1].substring(0, contextLength) : undefined,
75
+ contextAfter:
76
+ i < lines.length - 1 ? lines[i + 1].substring(0, contextLength) : undefined,
77
+ });
78
+ }
79
+ }
80
+ } catch {
81
+ // skip unreadable files
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,175 @@
1
+ import { homedir, platform } from "os";
2
+ import { resolve, basename, join } from "path";
3
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
4
+
5
+ const IS_WIN = platform() === "win32";
6
+
7
+ export interface VaultConfig {
8
+ name: string;
9
+ path: string;
10
+ }
11
+
12
+ export class VaultRegistry {
13
+ private vaults: Map<string, string> = new Map();
14
+
15
+ constructor() {
16
+ const fromEnv = this.parseEnv();
17
+ const fromConfig = this.parseConfigFile();
18
+
19
+ if (fromEnv.length > 0) {
20
+ for (const v of fromEnv) this.vaults.set(v.name, v.path);
21
+ } else if (fromConfig.length > 0) {
22
+ for (const v of fromConfig) this.vaults.set(v.name, v.path);
23
+ }
24
+ }
25
+
26
+ configure(vaults: VaultConfig[]): void {
27
+ this.vaults.clear();
28
+ for (const v of vaults) {
29
+ this.vaults.set(v.name, v.path);
30
+ }
31
+ }
32
+
33
+ static discoverFromObsidian(): VaultConfig[] {
34
+ const configPath = obsidianConfigPath();
35
+ if (!configPath || !existsSync(configPath)) return [];
36
+
37
+ try {
38
+ const raw = readFileSync(configPath, "utf-8");
39
+ const config = JSON.parse(raw);
40
+ const vaults = config.vaults as Record<string, { path: string }>;
41
+ if (!vaults) return [];
42
+
43
+ return Object.entries(vaults)
44
+ .filter(([, v]) => v && typeof v.path === "string")
45
+ .map(([name, v]) => ({
46
+ name,
47
+ path: resolve(v.path.replace(/^~/, homedir())),
48
+ }));
49
+ } catch {
50
+ return [];
51
+ }
52
+ }
53
+
54
+ resolve(name?: string): string {
55
+ if (name) {
56
+ const path = this.vaults.get(name);
57
+ if (!path) {
58
+ const available = this.list()
59
+ .map((v) => v.name)
60
+ .join(", ");
61
+ throw new Error(`Unknown vault "${name}". Available vaults: ${available}`);
62
+ }
63
+ return path;
64
+ }
65
+
66
+ if (this.vaults.size === 1) {
67
+ return this.vaults.values().next().value!;
68
+ }
69
+
70
+ const available = this.list()
71
+ .map((v) => v.name)
72
+ .join(", ");
73
+ throw new Error(`Multiple vaults configured but no vault specified. Choose one: ${available}`);
74
+ }
75
+
76
+ list(): VaultConfig[] {
77
+ return Array.from(this.vaults.entries()).map(([name, path]) => ({
78
+ name,
79
+ path,
80
+ }));
81
+ }
82
+
83
+ info(name?: string): { name: string; path: string; fileCount: number } {
84
+ const vaultPath = this.resolve(name);
85
+ let fileCount = 0;
86
+ try {
87
+ const walkDir = (dir: string): void => {
88
+ for (const entry of readdirSync(dir)) {
89
+ const full = join(dir, entry);
90
+ try {
91
+ const stat = statSync(full);
92
+ if (stat.isDirectory()) {
93
+ if (!entry.startsWith(".")) walkDir(full);
94
+ } else if (entry.endsWith(".md")) {
95
+ fileCount++;
96
+ }
97
+ } catch {
98
+ // skip unreadable entries
99
+ }
100
+ }
101
+ };
102
+ walkDir(vaultPath);
103
+ } catch {
104
+ // skip unreadable directories
105
+ }
106
+ return { name: basename(vaultPath), path: vaultPath, fileCount };
107
+ }
108
+
109
+ private parseEnv(): VaultConfig[] {
110
+ const envVal = process.env.OBSIDIAN_VAULT_PATHS;
111
+ if (!envVal) return [];
112
+
113
+ const separator = /[;\n]/;
114
+ const parts = envVal
115
+ .split(separator)
116
+ .map((s) => s.trim())
117
+ .filter(Boolean);
118
+
119
+ return parts.map((p) => {
120
+ const resolved = resolve(p.replace(/^~/, homedir()));
121
+ return { name: basename(resolved), path: resolved };
122
+ });
123
+ }
124
+
125
+ private configDir(): string {
126
+ if (IS_WIN) {
127
+ return resolve(
128
+ process.env.APPDATA || join(homedir(), "AppData", "Roaming"),
129
+ "obsidian-native-mcp",
130
+ );
131
+ }
132
+ const xdg = process.env.XDG_CONFIG_HOME;
133
+ if (xdg) return resolve(xdg, "obsidian-native-mcp");
134
+ return resolve(homedir(), ".config", "obsidian-native-mcp");
135
+ }
136
+
137
+ private parseConfigFile(): VaultConfig[] {
138
+ const configPath = join(this.configDir(), "vaults.json");
139
+ if (!existsSync(configPath)) return [];
140
+
141
+ try {
142
+ const raw = readFileSync(configPath, "utf-8");
143
+ const config = JSON.parse(raw);
144
+ const resolved: VaultConfig[] = [];
145
+
146
+ const entries = config.vaults
147
+ ? (Object.entries(config.vaults) as [string, unknown][])
148
+ : (Object.entries(config).filter(([k]) => k !== "default") as [string, unknown][]);
149
+
150
+ for (const [name, path] of entries) {
151
+ if (typeof path === "string") {
152
+ resolved.push({ name, path: resolve(path.replace(/^~/, homedir())) });
153
+ }
154
+ }
155
+
156
+ return resolved;
157
+ } catch {
158
+ return [];
159
+ }
160
+ }
161
+ }
162
+
163
+ function obsidianConfigPath(): string | null {
164
+ if (IS_WIN) {
165
+ const appData = process.env.APPDATA;
166
+ if (!appData) return null;
167
+ return resolve(appData, "obsidian", "obsidian.json");
168
+ }
169
+ if (platform() === "darwin") {
170
+ return resolve(homedir(), "Library", "Application Support", "obsidian", "obsidian.json");
171
+ }
172
+ const xdg = process.env.XDG_CONFIG_HOME;
173
+ if (xdg) return resolve(xdg, "obsidian", "obsidian.json");
174
+ return resolve(homedir(), ".config", "obsidian", "obsidian.json");
175
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ES2022"],
4
+ "target": "ES2022",
5
+ "module": "commonjs",
6
+ "moduleResolution": "node",
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "types": ["node"],
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "noFallthroughCasesInSwitch": true,
13
+ "noUnusedLocals": false,
14
+ "noUnusedParameters": false,
15
+ "esModuleInterop": true,
16
+ "sourceMap": true,
17
+ "declaration": false
18
+ },
19
+ "include": ["src"]
20
+ }