@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.
@@ -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;IAkDb,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,CAoF1D;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"}
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"}
@@ -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
- // Security check: ensure symlinks don't escape vault boundary
108
- try {
109
- const realPath = realpathSync(fullPath);
110
- const realRelative = relative(this.vaultPath, realPath);
111
- if (realRelative.startsWith('..')) {
112
- throw new Error(`Symlink target is outside vault: ${relativePath}. Symbolic links must resolve to a path within the vault directory.`);
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
- catch (err) {
116
- if (err instanceof Error && 'code' in err) {
117
- const code = err.code;
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
- const parentReal = realpathSync(dirname(fullPath));
122
- const parentRelative = relative(this.vaultPath, parentReal);
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 (parentErr) {
128
- // Parent doesn't exist either (will be created by mkdir). Lexical check above is sufficient.
129
- if (parentErr instanceof Error && parentErr.message.includes('outside vault')) {
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
- success: false,
540
- oldPath,
541
- newPath,
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 && error.code === 'EEXIST') {
561
- return {
562
- success: false,
563
- oldPath,
564
- newPath,
565
- message: `Target file already exists: ${newPath}. Use overwrite=true to replace it.`
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitbonsai/mcpvault",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "Universal AI bridge for Obsidian vaults - connect any MCP-compatible assistant",
5
5
  "homepage": "https://mcpvault.org",
6
6
  "author": "bitbonsai",