@bitbonsai/mcpvault 0.17.0 → 0.17.2
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/dist/src/filesystem.d.ts.map +1 -1
- package/dist/src/filesystem.js +79 -41
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/filesystem.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE3V;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAiBtE;AAoBD,qBAAa,iBAAiB;IAK1B,OAAO,CAAC,SAAS;IAJnB,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,UAAU,CAAa;IAE/B,YACU,SAAS,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,UAAU,EACvB,kBAAkB,CAAC,EAAE,kBAAkB,EAWxC;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,WAAW;
|
|
1
|
+
{"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/filesystem.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAE,uBAAuB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE3V;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAiBtE;AAoBD,qBAAa,iBAAiB;IAK1B,OAAO,CAAC,SAAS;IAJnB,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,UAAU,CAAa;IAE/B,YACU,SAAS,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,UAAU,EACvB,kBAAkB,CAAC,EAAE,kBAAkB,EAWxC;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,WAAW;IAgDb,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA+BhD;IAEK,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EtD;IAEK,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA4FjE;IAEK,aAAa,CAAC,IAAI,GAAE,MAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4DhE;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAc3C;IAEK,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAchD;IAEK,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAyGhE;IAEK,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CA4H1D;IAEK,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC,CA+H1D;IAEK,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA+CzE;IAEK,iBAAiB,CAAC,MAAM,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCtE;IAEK,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CA0CvD;IAEK,UAAU,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAqG1E;IAED,YAAY,IAAI,MAAM,CAErB;IAED;;;;;;;;;;;;;;OAcG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA+CjE;IAEK,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CA+EzD;IAEK,aAAa,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAehE;IAEK,aAAa,CAAC,WAAW,GAAE,MAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAuDhE;IAEK,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAiDlE;CACF"}
|
package/dist/src/filesystem.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join, resolve, relative, dirname } from 'path';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { readdir, stat, readFile, writeFile, unlink, mkdir, access, rename, copyFile } from 'node:fs/promises';
|
|
4
|
-
import { constants, realpathSync } from 'node:fs';
|
|
4
|
+
import { constants, lstatSync, realpathSync } from 'node:fs';
|
|
5
5
|
import trash from 'trash';
|
|
6
6
|
import { FrontmatterHandler } from './frontmatter.js';
|
|
7
7
|
import { PathFilter } from './pathfilter.js';
|
|
@@ -104,46 +104,45 @@ export class FileSystemService {
|
|
|
104
104
|
if (relativeToVault.startsWith('..')) {
|
|
105
105
|
throw new Error(`Path traversal not allowed: ${relativePath}. Paths must be within the vault directory.`);
|
|
106
106
|
}
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
// Walk missing parents to the nearest existing ancestor, without following dangling links.
|
|
108
|
+
let candidate = fullPath;
|
|
109
|
+
while (true) {
|
|
110
|
+
try {
|
|
111
|
+
const realPath = realpathSync(candidate);
|
|
112
|
+
if (relative(this.vaultPath, realPath).startsWith('..')) {
|
|
113
|
+
throw new Error(`Symlink target is outside vault: ${relativePath}. Symbolic links must resolve to a path within the vault directory.`);
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
113
116
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (code === 'ENOENT') {
|
|
119
|
-
// File doesn't exist yet (e.g. writing a new note). Verify the parent directory resolves inside vault.
|
|
117
|
+
catch (err) {
|
|
118
|
+
if (!(err instanceof Error) || !('code' in err))
|
|
119
|
+
throw err;
|
|
120
|
+
if (err.code === 'ENOENT') {
|
|
120
121
|
try {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (parentRelative.startsWith('..')) {
|
|
124
|
-
throw new Error(`Symlink target is outside vault: ${relativePath}. Symbolic links must resolve to a path within the vault directory.`);
|
|
122
|
+
if (lstatSync(candidate).isSymbolicLink()) {
|
|
123
|
+
throw new Error(`Cannot resolve symlink: ${relativePath}. Symbolic links must point to an existing path within the vault.`);
|
|
125
124
|
}
|
|
126
125
|
}
|
|
127
|
-
catch (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
throw parentErr;
|
|
126
|
+
catch (linkError) {
|
|
127
|
+
if (!(linkError instanceof Error) || !('code' in linkError) || linkError.code !== 'ENOENT') {
|
|
128
|
+
throw linkError;
|
|
131
129
|
}
|
|
132
130
|
}
|
|
131
|
+
const parent = dirname(candidate);
|
|
132
|
+
if (candidate === this.vaultPath || parent === candidate)
|
|
133
|
+
throw err;
|
|
134
|
+
candidate = parent;
|
|
133
135
|
}
|
|
134
|
-
else if (code === 'ELOOP') {
|
|
136
|
+
else if (err.code === 'ELOOP') {
|
|
135
137
|
throw new Error(`Circular symlink detected: ${relativePath}. The symbolic link chain forms a loop.`);
|
|
136
138
|
}
|
|
137
|
-
else if (code === 'EACCES') {
|
|
139
|
+
else if (err.code === 'EACCES' || err.code === 'EPERM') {
|
|
138
140
|
throw new Error(`Permission denied resolving symlink: ${relativePath}. Cannot verify the symbolic link target is within the vault.`);
|
|
139
141
|
}
|
|
140
142
|
else {
|
|
141
143
|
throw err;
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
|
-
else {
|
|
145
|
-
throw err;
|
|
146
|
-
}
|
|
147
146
|
}
|
|
148
147
|
return fullPath;
|
|
149
148
|
}
|
|
@@ -466,7 +465,7 @@ export class FileSystemService {
|
|
|
466
465
|
};
|
|
467
466
|
}
|
|
468
467
|
if (trashMode === 'system') {
|
|
469
|
-
await trash(fullPath);
|
|
468
|
+
await trash(fullPath, { glob: false });
|
|
470
469
|
return {
|
|
471
470
|
success: true,
|
|
472
471
|
path: path,
|
|
@@ -527,20 +526,43 @@ export class FileSystemService {
|
|
|
527
526
|
}
|
|
528
527
|
const oldFullPath = this.resolvePath(oldPath);
|
|
529
528
|
const newFullPath = this.resolvePath(newPath);
|
|
529
|
+
if (oldFullPath === newFullPath) {
|
|
530
|
+
return {
|
|
531
|
+
success: false,
|
|
532
|
+
oldPath,
|
|
533
|
+
newPath,
|
|
534
|
+
message: `Source and target paths are the same: ${oldPath}. Please provide a different target path.`
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
const sourceDirectoryResult = {
|
|
538
|
+
success: false,
|
|
539
|
+
oldPath,
|
|
540
|
+
newPath,
|
|
541
|
+
message: `Source path is a directory: ${oldPath}. Please provide a file path.`
|
|
542
|
+
};
|
|
543
|
+
const targetDirectoryResult = {
|
|
544
|
+
success: false,
|
|
545
|
+
oldPath,
|
|
546
|
+
newPath,
|
|
547
|
+
message: `Target path is a directory: ${newPath}. Please provide a file path.`
|
|
548
|
+
};
|
|
549
|
+
const sourceMissingResult = {
|
|
550
|
+
success: false,
|
|
551
|
+
oldPath,
|
|
552
|
+
newPath,
|
|
553
|
+
message: `Source file not found: ${oldPath}. Use list_directory to see available files.`
|
|
554
|
+
};
|
|
530
555
|
try {
|
|
531
|
-
// Read source content (will throw ENOENT if not found)
|
|
532
556
|
let content;
|
|
533
557
|
try {
|
|
534
558
|
content = await readFile(oldFullPath, 'utf-8');
|
|
535
559
|
}
|
|
536
560
|
catch (error) {
|
|
537
561
|
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
538
|
-
return
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
message: `Source file not found: ${oldPath}. Use list_directory to see available files.`
|
|
543
|
-
};
|
|
562
|
+
return sourceMissingResult;
|
|
563
|
+
}
|
|
564
|
+
if (error instanceof Error && 'code' in error && error.code === 'EISDIR') {
|
|
565
|
+
return sourceDirectoryResult;
|
|
544
566
|
}
|
|
545
567
|
throw error;
|
|
546
568
|
}
|
|
@@ -557,13 +579,29 @@ export class FileSystemService {
|
|
|
557
579
|
}
|
|
558
580
|
}
|
|
559
581
|
catch (error) {
|
|
560
|
-
if (error instanceof Error && 'code' in error
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
582
|
+
if (error instanceof Error && 'code' in error) {
|
|
583
|
+
if (error.code === 'EISDIR') {
|
|
584
|
+
return targetDirectoryResult;
|
|
585
|
+
}
|
|
586
|
+
// Inspect only after a failed write; these codes also cover Windows directories.
|
|
587
|
+
if (error.code === 'EEXIST' || error.code === 'EPERM' || error.code === 'EACCES') {
|
|
588
|
+
try {
|
|
589
|
+
if ((await stat(newFullPath)).isDirectory()) {
|
|
590
|
+
return targetDirectoryResult;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
catch {
|
|
594
|
+
// Preserve the write error if the target changed or cannot be inspected.
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
if (error.code === 'EEXIST') {
|
|
598
|
+
return {
|
|
599
|
+
success: false,
|
|
600
|
+
oldPath,
|
|
601
|
+
newPath,
|
|
602
|
+
message: `Target file already exists: ${newPath}. Use overwrite=true to replace it.`
|
|
603
|
+
};
|
|
604
|
+
}
|
|
567
605
|
}
|
|
568
606
|
throw error;
|
|
569
607
|
}
|
package/package.json
CHANGED