@dollhousemcp/mcp-server 2.0.36 → 2.0.38

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,8 +1,97 @@
1
+ import * as fs from 'node:fs';
1
2
  import * as path from 'node:path';
3
+ function isMissingPathError(error) {
4
+ const code = typeof error === 'object' && error !== null && 'code' in error
5
+ ? error.code
6
+ : undefined;
7
+ return code === 'ENOENT' || code === 'ENOTDIR';
8
+ }
9
+ /**
10
+ * lstat of the deepest ancestor of `startPath` that exists, or null when
11
+ * nothing up to the filesystem root exists. Used to vet paths that will be
12
+ * created later: their containment depends on the directory they will be
13
+ * created inside.
14
+ *
15
+ * KNOWN LIMIT (#2344): this stops at the first ancestor lstat can see, and
16
+ * lstat resolves intermediate symlinks — so a symlink whose target already
17
+ * contains a matching subdirectory is reported as a plain directory and the
18
+ * symlink component itself is never inspected. Walking every lexical ancestor
19
+ * instead is not an option: system symlinks (macOS /tmp -> /private/tmp,
20
+ * /var -> /private/var) sit above every temp path and would false-positive.
21
+ * Full protection needs a caller-known boundary, which is exactly what
22
+ * vetOutputBase() provides — see the contract note on resolvePathWithinBase.
23
+ */
24
+ function nearestExistingAncestorStats(startPath) {
25
+ let previous = startPath;
26
+ let current = path.dirname(startPath);
27
+ while (current !== previous) {
28
+ try {
29
+ return fs.lstatSync(current);
30
+ }
31
+ catch (error) {
32
+ if (!isMissingPathError(error)) {
33
+ throw error;
34
+ }
35
+ }
36
+ previous = current;
37
+ current = path.dirname(current);
38
+ }
39
+ return null;
40
+ }
41
+ function assertNotSymlink(pathToCheck, message) {
42
+ let stats;
43
+ try {
44
+ stats = fs.lstatSync(pathToCheck);
45
+ }
46
+ catch (error) {
47
+ if (isMissingPathError(error)) {
48
+ // The path does not exist yet, so it will be created by a later
49
+ // mkdir/write. That is only safe if the directory it gets created
50
+ // inside is real: a symlinked existing ancestor would redirect the
51
+ // recursive create outside the containment boundary (#2342).
52
+ const ancestorStats = nearestExistingAncestorStats(pathToCheck);
53
+ if (ancestorStats?.isSymbolicLink()) {
54
+ throw new Error(message);
55
+ }
56
+ return;
57
+ }
58
+ throw error;
59
+ }
60
+ if (stats.isSymbolicLink()) {
61
+ throw new Error(message);
62
+ }
63
+ }
64
+ function assertNoSymlinkedDescendants(resolvedBase, resolvedTarget) {
65
+ const relativePath = path.relative(resolvedBase, resolvedTarget);
66
+ if (!relativePath) {
67
+ return;
68
+ }
69
+ let currentPath = resolvedBase;
70
+ for (const segment of relativePath.split(path.sep)) {
71
+ if (!segment) {
72
+ continue;
73
+ }
74
+ currentPath = path.join(currentPath, segment);
75
+ assertNotSymlink(currentPath, 'Path segment resolves through a symbolic link');
76
+ }
77
+ }
2
78
  /**
3
79
  * Resolve a target path and verify that it remains inside the intended base
4
80
  * directory. This is separator-aware, so sibling paths such as `/tmp/base2`
5
- * are not treated as children of `/tmp/base`.
81
+ * are not treated as children of `/tmp/base`. Existing symlinked base paths
82
+ * and symlinked descendants under the base are rejected because writes would
83
+ * follow them outside the lexical containment boundary. A base that does not
84
+ * exist yet is vetted through its nearest existing ancestor: if that ancestor
85
+ * is a symlink, the recursive mkdir/write that follows would be redirected
86
+ * outside the intended tree, so it is rejected too (#2342).
87
+ *
88
+ * CONTRACT (#2344): the base's own ancestry can only be best-effort vetted
89
+ * here — a symlink whose target pre-contains matching subdirectories is not
90
+ * detectable without a caller-known boundary (see nearestExistingAncestorStats
91
+ * for why walking every ancestor false-positives on system symlinks). Do NOT
92
+ * pass user-controlled base paths directly to this function: vet them first
93
+ * with vetOutputBase(), which contains them canonically against an anchor and
94
+ * returns the canonical base to use here. Every current caller does this.
6
95
  */
7
96
  export function resolvePathWithinBase(baseDir, ...segments) {
8
97
  if (!baseDir || typeof baseDir !== 'string') {
@@ -23,8 +112,89 @@ export function resolvePathWithinBase(baseDir, ...segments) {
23
112
  || relativePath.startsWith('..' + path.sep)
24
113
  || path.isAbsolute(relativePath);
25
114
  if (!isTraversal) {
115
+ assertNotSymlink(resolvedBase, 'Base directory resolves through a symbolic link');
116
+ assertNoSymlinkedDescendants(resolvedBase, resolvedTarget);
26
117
  return resolvedTarget;
27
118
  }
28
119
  throw new Error('Resolved path escapes the base directory');
29
120
  }
30
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF0aFNlY3VyaXR5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL3BhdGhTZWN1cml0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUVsQzs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLHFCQUFxQixDQUFDLE9BQWUsRUFBRSxHQUFHLFFBQWtCO0lBQzFFLElBQUksQ0FBQyxPQUFPLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDNUMsTUFBTSxJQUFJLFNBQVMsQ0FBQywyQ0FBMkMsQ0FBQyxDQUFDO0lBQ25FLENBQUM7SUFFRCxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsRUFBRSxDQUFDO1FBQy9CLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDaEMsTUFBTSxJQUFJLFNBQVMsQ0FBQywrQkFBK0IsQ0FBQyxDQUFDO1FBQ3ZELENBQUM7UUFDRCxJQUFJLE9BQU8sQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUMzQixNQUFNLElBQUksS0FBSyxDQUFDLG1DQUFtQyxDQUFDLENBQUM7UUFDdkQsQ0FBQztJQUNILENBQUM7SUFFRCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzNDLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsWUFBWSxFQUFFLEdBQUcsUUFBUSxDQUFDLENBQUM7SUFDL0QsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxZQUFZLEVBQUUsY0FBYyxDQUFDLENBQUM7SUFDakUsTUFBTSxXQUFXLEdBQUcsWUFBWSxLQUFLLElBQUk7V0FDcEMsWUFBWSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQztXQUN4QyxJQUFJLENBQUMsVUFBVSxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBRW5DLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztRQUNqQixPQUFPLGNBQWMsQ0FBQztJQUN4QixDQUFDO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywwQ0FBMEMsQ0FBQyxDQUFDO0FBQzlELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5cbi8qKlxuICogUmVzb2x2ZSBhIHRhcmdldCBwYXRoIGFuZCB2ZXJpZnkgdGhhdCBpdCByZW1haW5zIGluc2lkZSB0aGUgaW50ZW5kZWQgYmFzZVxuICogZGlyZWN0b3J5LiBUaGlzIGlzIHNlcGFyYXRvci1hd2FyZSwgc28gc2libGluZyBwYXRocyBzdWNoIGFzIGAvdG1wL2Jhc2UyYFxuICogYXJlIG5vdCB0cmVhdGVkIGFzIGNoaWxkcmVuIG9mIGAvdG1wL2Jhc2VgLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVBhdGhXaXRoaW5CYXNlKGJhc2VEaXI6IHN0cmluZywgLi4uc2VnbWVudHM6IHN0cmluZ1tdKTogc3RyaW5nIHtcbiAgaWYgKCFiYXNlRGlyIHx8IHR5cGVvZiBiYXNlRGlyICE9PSAnc3RyaW5nJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0Jhc2UgZGlyZWN0b3J5IG11c3QgYmUgYSBub24tZW1wdHkgc3RyaW5nJyk7XG4gIH1cblxuICBmb3IgKGNvbnN0IHNlZ21lbnQgb2Ygc2VnbWVudHMpIHtcbiAgICBpZiAodHlwZW9mIHNlZ21lbnQgIT09ICdzdHJpbmcnKSB7XG4gICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdQYXRoIHNlZ21lbnRzIG11c3QgYmUgc3RyaW5ncycpO1xuICAgIH1cbiAgICBpZiAoc2VnbWVudC5pbmNsdWRlcygnXFwwJykpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignUGF0aCBzZWdtZW50IGNvbnRhaW5zIGEgbnVsbCBieXRlJyk7XG4gICAgfVxuICB9XG5cbiAgY29uc3QgcmVzb2x2ZWRCYXNlID0gcGF0aC5yZXNvbHZlKGJhc2VEaXIpO1xuICBjb25zdCByZXNvbHZlZFRhcmdldCA9IHBhdGgucmVzb2x2ZShyZXNvbHZlZEJhc2UsIC4uLnNlZ21lbnRzKTtcbiAgY29uc3QgcmVsYXRpdmVQYXRoID0gcGF0aC5yZWxhdGl2ZShyZXNvbHZlZEJhc2UsIHJlc29sdmVkVGFyZ2V0KTtcbiAgY29uc3QgaXNUcmF2ZXJzYWwgPSByZWxhdGl2ZVBhdGggPT09ICcuLidcbiAgICB8fCByZWxhdGl2ZVBhdGguc3RhcnRzV2l0aCgnLi4nICsgcGF0aC5zZXApXG4gICAgfHwgcGF0aC5pc0Fic29sdXRlKHJlbGF0aXZlUGF0aCk7XG5cbiAgaWYgKCFpc1RyYXZlcnNhbCkge1xuICAgIHJldHVybiByZXNvbHZlZFRhcmdldDtcbiAgfVxuXG4gIHRocm93IG5ldyBFcnJvcignUmVzb2x2ZWQgcGF0aCBlc2NhcGVzIHRoZSBiYXNlIGRpcmVjdG9yeScpO1xufVxuIl19
121
+ /** True when a base-relative path points outside the base. */
122
+ function escapesBase(relativePath) {
123
+ return relativePath === '..'
124
+ || relativePath.startsWith('..' + path.sep)
125
+ || path.isAbsolute(relativePath);
126
+ }
127
+ /**
128
+ * Canonical (realpath) form of a path that may not fully exist yet: the
129
+ * deepest existing ancestor is resolved through every symlink, and the
130
+ * not-yet-created suffix is appended unchanged. This answers "where would a
131
+ * recursive mkdir/write on this path REALLY land?" — which per-component
132
+ * lstat checks cannot, because lstat resolves intermediate symlinks and only
133
+ * reports on the final component (#2344).
134
+ */
135
+ export function canonicalizePath(inputPath) {
136
+ const resolved = path.resolve(inputPath);
137
+ const missing = [];
138
+ let current = resolved;
139
+ for (;;) {
140
+ try {
141
+ const canonical = fs.realpathSync(current);
142
+ return missing.length ? path.join(canonical, ...missing) : canonical;
143
+ }
144
+ catch (error) {
145
+ if (!isMissingPathError(error)) {
146
+ throw error;
147
+ }
148
+ }
149
+ const parent = path.dirname(current);
150
+ if (parent === current) {
151
+ // Nothing on the chain exists (should not happen for absolute paths —
152
+ // the root exists — but keep a lexical fallback rather than throwing).
153
+ return missing.length ? path.join(current, ...missing) : current;
154
+ }
155
+ missing.unshift(path.basename(current));
156
+ current = parent;
157
+ }
158
+ }
159
+ /**
160
+ * Vets a user-supplied output base for CLI writes and returns the canonical
161
+ * path all subsequent writes should use (#2344).
162
+ *
163
+ * Two regimes, split by what the user lexically named:
164
+ *
165
+ * - Base inside the anchor (relative outputs like the './anthropic-skills'
166
+ * default): the canonical base must stay inside the canonical anchor.
167
+ * A symlink that redirects it elsewhere — including one whose target
168
+ * already contains matching subdirectories, the case per-component lstat
169
+ * checks miss — is rejected. Comparing in canonical space means system
170
+ * links above the anchor (macOS /tmp -> /private/tmp) never false-positive.
171
+ *
172
+ * - Base outside the anchor (explicit absolute or ../ outputs): the user
173
+ * named that destination, so there is no boundary to defend. Instead of
174
+ * guessing intent, the real destination is disclosed via `onDisclose`
175
+ * whenever it differs from the lexical path, and the canonical path is
176
+ * returned so what was vetted is what gets written.
177
+ */
178
+ export function vetOutputBase(baseDir, options) {
179
+ if (!baseDir || typeof baseDir !== 'string') {
180
+ throw new TypeError('Base directory must be a non-empty string');
181
+ }
182
+ if (baseDir.includes('\0')) {
183
+ throw new Error('Path segment contains a null byte');
184
+ }
185
+ const resolvedAnchor = path.resolve(options?.anchor ?? process.cwd());
186
+ const resolvedBase = path.resolve(baseDir);
187
+ const canonicalBase = canonicalizePath(resolvedBase);
188
+ if (!escapesBase(path.relative(resolvedAnchor, resolvedBase))) {
189
+ const canonicalAnchor = canonicalizePath(resolvedAnchor);
190
+ if (escapesBase(path.relative(canonicalAnchor, canonicalBase))) {
191
+ throw new Error(`Output path resolves outside the working directory through a symbolic link (real destination: ${canonicalBase})`);
192
+ }
193
+ return canonicalBase;
194
+ }
195
+ if (canonicalBase !== resolvedBase) {
196
+ options?.onDisclose?.(canonicalBase);
197
+ }
198
+ return canonicalBase;
199
+ }
200
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF0aFNlY3VyaXR5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3V0aWxzL3BhdGhTZWN1cml0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLFNBQVMsQ0FBQztBQUM5QixPQUFPLEtBQUssSUFBSSxNQUFNLFdBQVcsQ0FBQztBQUVsQyxTQUFTLGtCQUFrQixDQUFDLEtBQWM7SUFDeEMsTUFBTSxJQUFJLEdBQUcsT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssS0FBSyxJQUFJLElBQUksTUFBTSxJQUFJLEtBQUs7UUFDekUsQ0FBQyxDQUFFLEtBQStCLENBQUMsSUFBSTtRQUN2QyxDQUFDLENBQUMsU0FBUyxDQUFDO0lBQ2QsT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLElBQUksS0FBSyxTQUFTLENBQUM7QUFDakQsQ0FBQztBQUVEOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0gsU0FBUyw0QkFBNEIsQ0FBQyxTQUFpQjtJQUNyRCxJQUFJLFFBQVEsR0FBRyxTQUFTLENBQUM7SUFDekIsSUFBSSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUN0QyxPQUFPLE9BQU8sS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUM1QixJQUFJLENBQUM7WUFDSCxPQUFPLEVBQUUsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDL0IsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixJQUFJLENBQUMsa0JBQWtCLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztnQkFDL0IsTUFBTSxLQUFLLENBQUM7WUFDZCxDQUFDO1FBQ0gsQ0FBQztRQUNELFFBQVEsR0FBRyxPQUFPLENBQUM7UUFDbkIsT0FBTyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbEMsQ0FBQztJQUNELE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQztB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QyxRQUFRLENBQUMsZUFBZSxFQUFFLGFBQWEsQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUMvRCxNQUFNLElBQUksS0FBSyxDQUNiLGlHQUFpRyxhQUFhLEdBQUcsQ0FDbEgsQ0FBQztRQUNKLENBQUM7UUFDRCxPQUFPLGFBQWEsQ0FBQztJQUN2QixDQUFDO0lBRUQsSUFBSSxhQUFhLEtBQUssWUFBWSxFQUFFLENBQUM7UUFDbkMsT0FBTyxFQUFFLFVBQVUsRUFBRSxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQ3ZDLENBQUM7SUFDRCxPQUFPLGFBQWEsQ0FBQztBQUN2QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgZnMgZnJvbSAnbm9kZTpmcyc7XG5pbXBvcnQgKiBhcyBwYXRoIGZyb20gJ25vZGU6cGF0aCc7XG5cbmZ1bmN0aW9uIGlzTWlzc2luZ1BhdGhFcnJvcihlcnJvcjogdW5rbm93bik6IGJvb2xlYW4ge1xuICBjb25zdCBjb2RlID0gdHlwZW9mIGVycm9yID09PSAnb2JqZWN0JyAmJiBlcnJvciAhPT0gbnVsbCAmJiAnY29kZScgaW4gZXJyb3JcbiAgICA/IChlcnJvciBhcyBOb2RlSlMuRXJybm9FeGNlcHRpb24pLmNvZGVcbiAgICA6IHVuZGVmaW5lZDtcbiAgcmV0dXJuIGNvZGUgPT09ICdFTk9FTlQnIHx8IGNvZGUgPT09ICdFTk9URElSJztcbn1cblxuLyoqXG4gKiBsc3RhdCBvZiB0aGUgZGVlcGVzdCBhbmNlc3RvciBvZiBgc3RhcnRQYXRoYCB0aGF0IGV4aXN0cywgb3IgbnVsbCB3aGVuXG4gKiBub3RoaW5nIHVwIHRvIHRoZSBmaWxlc3lzdGVtIHJvb3QgZXhpc3RzLiBVc2VkIHRvIHZldCBwYXRocyB0aGF0IHdpbGwgYmVcbiAqIGNyZWF0ZWQgbGF0ZXI6IHRoZWlyIGNvbnRhaW5tZW50IGRlcGVuZHMgb24gdGhlIGRpcmVjdG9yeSB0aGV5IHdpbGwgYmVcbiAqIGNyZWF0ZWQgaW5zaWRlLlxuICpcbiAqIEtOT1dOIExJTUlUICgjMjM0NCk6IHRoaXMgc3RvcHMgYXQgdGhlIGZpcnN0IGFuY2VzdG9yIGxzdGF0IGNhbiBzZWUsIGFuZFxuICogbHN0YXQgcmVzb2x2ZXMgaW50ZXJtZWRpYXRlIHN5bWxpbmtzIOKAlCBzbyBhIHN5bWxpbmsgd2hvc2UgdGFyZ2V0IGFscmVhZHlcbiAqIGNvbnRhaW5zIGEgbWF0Y2hpbmcgc3ViZGlyZWN0b3J5IGlzIHJlcG9ydGVkIGFzIGEgcGxhaW4gZGlyZWN0b3J5IGFuZCB0aGVcbiAqIHN5bWxpbmsgY29tcG9uZW50IGl0c2VsZiBpcyBuZXZlciBpbnNwZWN0ZWQuIFdhbGtpbmcgZXZlcnkgbGV4aWNhbCBhbmNlc3RvclxuICogaW5zdGVhZCBpcyBub3QgYW4gb3B0aW9uOiBzeXN0ZW0gc3ltbGlua3MgKG1hY09TIC90bXAgLT4gL3ByaXZhdGUvdG1wLFxuICogL3ZhciAtPiAvcHJpdmF0ZS92YXIpIHNpdCBhYm92ZSBldmVyeSB0ZW1wIHBhdGggYW5kIHdvdWxkIGZhbHNlLXBvc2l0aXZlLlxuICogRnVsbCBwcm90ZWN0aW9uIG5lZWRzIGEgY2FsbGVyLWtub3duIGJvdW5kYXJ5LCB3aGljaCBpcyBleGFjdGx5IHdoYXRcbiAqIHZldE91dHB1dEJhc2UoKSBwcm92aWRlcyDigJQgc2VlIHRoZSBjb250cmFjdCBub3RlIG9uIHJlc29sdmVQYXRoV2l0aGluQmFzZS5cbiAqL1xuZnVuY3Rpb24gbmVhcmVzdEV4aXN0aW5nQW5jZXN0b3JTdGF0cyhzdGFydFBhdGg6IHN0cmluZyk6IGZzLlN0YXRzIHwgbnVsbCB7XG4gIGxldCBwcmV2aW91cyA9IHN0YXJ0UGF0aDtcbiAgbGV0IGN1cnJlbnQgPSBwYXRoLmRpcm5hbWUoc3RhcnRQYXRoKTtcbiAgd2hpbGUgKGN1cnJlbnQgIT09IHByZXZpb3VzKSB7XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiBmcy5sc3RhdFN5bmMoY3VycmVudCk7XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmICghaXNNaXNzaW5nUGF0aEVycm9yKGVycm9yKSkge1xuICAgICAgICB0aHJvdyBlcnJvcjtcbiAgICAgIH1cbiAgICB9XG4gICAgcHJldmlvdXMgPSBjdXJyZW50O1xuICAgIGN1cnJlbnQgPSBwYXRoLmRpcm5hbWUoY3VycmVudCk7XG4gIH1cbiAgcmV0dXJuIG51bGw7XG59XG5cbmZ1bmN0aW9uIGFzc2VydE5vdFN5bWxpbmsocGF0aFRvQ2hlY2s6IHN0cmluZywgbWVzc2FnZTogc3RyaW5nKTogdm9pZCB7XG4gIGxldCBzdGF0czogZnMuU3RhdHM7XG4gIHRyeSB7XG4gICAgc3RhdHMgPSBmcy5sc3RhdFN5bmMocGF0aFRvQ2hlY2spO1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIGlmIChpc01pc3NpbmdQYXRoRXJyb3IoZXJyb3IpKSB7XG4gICAgICAvLyBUaGUgcGF0aCBkb2VzIG5vdCBleGlzdCB5ZXQsIHNvIGl0IHdpbGwgYmUgY3JlYXRlZCBieSBhIGxhdGVyXG4gICAgICAvLyBta2Rpci93cml0ZS4gVGhhdCBpcyBvbmx5IHNhZmUgaWYgdGhlIGRpcmVjdG9yeSBpdCBnZXRzIGNyZWF0ZWRcbiAgICAgIC8vIGluc2lkZSBpcyByZWFsOiBhIHN5bWxpbmtlZCBleGlzdGluZyBhbmNlc3RvciB3b3VsZCByZWRpcmVjdCB0aGVcbiAgICAgIC8vIHJlY3Vyc2l2ZSBjcmVhdGUgb3V0c2lkZSB0aGUgY29udGFpbm1lbnQgYm91bmRhcnkgKCMyMzQyKS5cbiAgICAgIGNvbnN0IGFuY2VzdG9yU3RhdHMgPSBuZWFyZXN0RXhpc3RpbmdBbmNlc3RvclN0YXRzKHBhdGhUb0NoZWNrKTtcbiAgICAgIGlmIChhbmNlc3RvclN0YXRzPy5pc1N5bWJvbGljTGluaygpKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihtZXNzYWdlKTtcbiAgICAgIH1cbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdGhyb3cgZXJyb3I7XG4gIH1cbiAgaWYgKHN0YXRzLmlzU3ltYm9saWNMaW5rKCkpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IobWVzc2FnZSk7XG4gIH1cbn1cblxuZnVuY3Rpb24gYXNzZXJ0Tm9TeW1saW5rZWREZXNjZW5kYW50cyhyZXNvbHZlZEJhc2U6IHN0cmluZywgcmVzb2x2ZWRUYXJnZXQ6IHN0cmluZyk6IHZvaWQge1xuICBjb25zdCByZWxhdGl2ZVBhdGggPSBwYXRoLnJlbGF0aXZlKHJlc29sdmVkQmFzZSwgcmVzb2x2ZWRUYXJnZXQpO1xuICBpZiAoIXJlbGF0aXZlUGF0aCkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGxldCBjdXJyZW50UGF0aCA9IHJlc29sdmVkQmFzZTtcbiAgZm9yIChjb25zdCBzZWdtZW50IG9mIHJlbGF0aXZlUGF0aC5zcGxpdChwYXRoLnNlcCkpIHtcbiAgICBpZiAoIXNlZ21lbnQpIHtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGN1cnJlbnRQYXRoID0gcGF0aC5qb2luKGN1cnJlbnRQYXRoLCBzZWdtZW50KTtcbiAgICBhc3NlcnROb3RTeW1saW5rKGN1cnJlbnRQYXRoLCAnUGF0aCBzZWdtZW50IHJlc29sdmVzIHRocm91Z2ggYSBzeW1ib2xpYyBsaW5rJyk7XG4gIH1cbn1cblxuLyoqXG4gKiBSZXNvbHZlIGEgdGFyZ2V0IHBhdGggYW5kIHZlcmlmeSB0aGF0IGl0IHJlbWFpbnMgaW5zaWRlIHRoZSBpbnRlbmRlZCBiYXNlXG4gKiBkaXJlY3RvcnkuIFRoaXMgaXMgc2VwYXJhdG9yLWF3YXJlLCBzbyBzaWJsaW5nIHBhdGhzIHN1Y2ggYXMgYC90bXAvYmFzZTJgXG4gKiBhcmUgbm90IHRyZWF0ZWQgYXMgY2hpbGRyZW4gb2YgYC90bXAvYmFzZWAuIEV4aXN0aW5nIHN5bWxpbmtlZCBiYXNlIHBhdGhzXG4gKiBhbmQgc3ltbGlua2VkIGRlc2NlbmRhbnRzIHVuZGVyIHRoZSBiYXNlIGFyZSByZWplY3RlZCBiZWNhdXNlIHdyaXRlcyB3b3VsZFxuICogZm9sbG93IHRoZW0gb3V0c2lkZSB0aGUgbGV4aWNhbCBjb250YWlubWVudCBib3VuZGFyeS4gQSBiYXNlIHRoYXQgZG9lcyBub3RcbiAqIGV4aXN0IHlldCBpcyB2ZXR0ZWQgdGhyb3VnaCBpdHMgbmVhcmVzdCBleGlzdGluZyBhbmNlc3RvcjogaWYgdGhhdCBhbmNlc3RvclxuICogaXMgYSBzeW1saW5rLCB0aGUgcmVjdXJzaXZlIG1rZGlyL3dyaXRlIHRoYXQgZm9sbG93cyB3b3VsZCBiZSByZWRpcmVjdGVkXG4gKiBvdXRzaWRlIHRoZSBpbnRlbmRlZCB0cmVlLCBzbyBpdCBpcyByZWplY3RlZCB0b28gKCMyMzQyKS5cbiAqXG4gKiBDT05UUkFDVCAoIzIzNDQpOiB0aGUgYmFzZSdzIG93biBhbmNlc3RyeSBjYW4gb25seSBiZSBiZXN0LWVmZm9ydCB2ZXR0ZWRcbiAqIGhlcmUg4oCUIGEgc3ltbGluayB3aG9zZSB0YXJnZXQgcHJlLWNvbnRhaW5zIG1hdGNoaW5nIHN1YmRpcmVjdG9yaWVzIGlzIG5vdFxuICogZGV0ZWN0YWJsZSB3aXRob3V0IGEgY2FsbGVyLWtub3duIGJvdW5kYXJ5IChzZWUgbmVhcmVzdEV4aXN0aW5nQW5jZXN0b3JTdGF0c1xuICogZm9yIHdoeSB3YWxraW5nIGV2ZXJ5IGFuY2VzdG9yIGZhbHNlLXBvc2l0aXZlcyBvbiBzeXN0ZW0gc3ltbGlua3MpLiBEbyBOT1RcbiAqIHBhc3MgdXNlci1jb250cm9sbGVkIGJhc2UgcGF0aHMgZGlyZWN0bHkgdG8gdGhpcyBmdW5jdGlvbjogdmV0IHRoZW0gZmlyc3RcbiAqIHdpdGggdmV0T3V0cHV0QmFzZSgpLCB3aGljaCBjb250YWlucyB0aGVtIGNhbm9uaWNhbGx5IGFnYWluc3QgYW4gYW5jaG9yIGFuZFxuICogcmV0dXJucyB0aGUgY2Fub25pY2FsIGJhc2UgdG8gdXNlIGhlcmUuIEV2ZXJ5IGN1cnJlbnQgY2FsbGVyIGRvZXMgdGhpcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVQYXRoV2l0aGluQmFzZShiYXNlRGlyOiBzdHJpbmcsIC4uLnNlZ21lbnRzOiBzdHJpbmdbXSk6IHN0cmluZyB7XG4gIGlmICghYmFzZURpciB8fCB0eXBlb2YgYmFzZURpciAhPT0gJ3N0cmluZycpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdCYXNlIGRpcmVjdG9yeSBtdXN0IGJlIGEgbm9uLWVtcHR5IHN0cmluZycpO1xuICB9XG5cbiAgZm9yIChjb25zdCBzZWdtZW50IG9mIHNlZ21lbnRzKSB7XG4gICAgaWYgKHR5cGVvZiBzZWdtZW50ICE9PSAnc3RyaW5nJykge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignUGF0aCBzZWdtZW50cyBtdXN0IGJlIHN0cmluZ3MnKTtcbiAgICB9XG4gICAgaWYgKHNlZ21lbnQuaW5jbHVkZXMoJ1xcMCcpKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggc2VnbWVudCBjb250YWlucyBhIG51bGwgYnl0ZScpO1xuICAgIH1cbiAgfVxuXG4gIGNvbnN0IHJlc29sdmVkQmFzZSA9IHBhdGgucmVzb2x2ZShiYXNlRGlyKTtcbiAgY29uc3QgcmVzb2x2ZWRUYXJnZXQgPSBwYXRoLnJlc29sdmUocmVzb2x2ZWRCYXNlLCAuLi5zZWdtZW50cyk7XG4gIGNvbnN0IHJlbGF0aXZlUGF0aCA9IHBhdGgucmVsYXRpdmUocmVzb2x2ZWRCYXNlLCByZXNvbHZlZFRhcmdldCk7XG4gIGNvbnN0IGlzVHJhdmVyc2FsID0gcmVsYXRpdmVQYXRoID09PSAnLi4nXG4gICAgfHwgcmVsYXRpdmVQYXRoLnN0YXJ0c1dpdGgoJy4uJyArIHBhdGguc2VwKVxuICAgIHx8IHBhdGguaXNBYnNvbHV0ZShyZWxhdGl2ZVBhdGgpO1xuXG4gIGlmICghaXNUcmF2ZXJzYWwpIHtcbiAgICBhc3NlcnROb3RTeW1saW5rKHJlc29sdmVkQmFzZSwgJ0Jhc2UgZGlyZWN0b3J5IHJlc29sdmVzIHRocm91Z2ggYSBzeW1ib2xpYyBsaW5rJyk7XG4gICAgYXNzZXJ0Tm9TeW1saW5rZWREZXNjZW5kYW50cyhyZXNvbHZlZEJhc2UsIHJlc29sdmVkVGFyZ2V0KTtcbiAgICByZXR1cm4gcmVzb2x2ZWRUYXJnZXQ7XG4gIH1cblxuICB0aHJvdyBuZXcgRXJyb3IoJ1Jlc29sdmVkIHBhdGggZXNjYXBlcyB0aGUgYmFzZSBkaXJlY3RvcnknKTtcbn1cblxuLyoqIFRydWUgd2hlbiBhIGJhc2UtcmVsYXRpdmUgcGF0aCBwb2ludHMgb3V0c2lkZSB0aGUgYmFzZS4gKi9cbmZ1bmN0aW9uIGVzY2FwZXNCYXNlKHJlbGF0aXZlUGF0aDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIHJldHVybiByZWxhdGl2ZVBhdGggPT09ICcuLidcbiAgICB8fCByZWxhdGl2ZVBhdGguc3RhcnRzV2l0aCgnLi4nICsgcGF0aC5zZXApXG4gICAgfHwgcGF0aC5pc0Fic29sdXRlKHJlbGF0aXZlUGF0aCk7XG59XG5cbi8qKlxuICogQ2Fub25pY2FsIChyZWFscGF0aCkgZm9ybSBvZiBhIHBhdGggdGhhdCBtYXkgbm90IGZ1bGx5IGV4aXN0IHlldDogdGhlXG4gKiBkZWVwZXN0IGV4aXN0aW5nIGFuY2VzdG9yIGlzIHJlc29sdmVkIHRocm91Z2ggZXZlcnkgc3ltbGluaywgYW5kIHRoZVxuICogbm90LXlldC1jcmVhdGVkIHN1ZmZpeCBpcyBhcHBlbmRlZCB1bmNoYW5nZWQuIFRoaXMgYW5zd2VycyBcIndoZXJlIHdvdWxkIGFcbiAqIHJlY3Vyc2l2ZSBta2Rpci93cml0ZSBvbiB0aGlzIHBhdGggUkVBTExZIGxhbmQ/XCIg4oCUIHdoaWNoIHBlci1jb21wb25lbnRcbiAqIGxzdGF0IGNoZWNrcyBjYW5ub3QsIGJlY2F1c2UgbHN0YXQgcmVzb2x2ZXMgaW50ZXJtZWRpYXRlIHN5bWxpbmtzIGFuZCBvbmx5XG4gKiByZXBvcnRzIG9uIHRoZSBmaW5hbCBjb21wb25lbnQgKCMyMzQ0KS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNhbm9uaWNhbGl6ZVBhdGgoaW5wdXRQYXRoOiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCByZXNvbHZlZCA9IHBhdGgucmVzb2x2ZShpbnB1dFBhdGgpO1xuICBjb25zdCBtaXNzaW5nOiBzdHJpbmdbXSA9IFtdO1xuICBsZXQgY3VycmVudCA9IHJlc29sdmVkO1xuICBmb3IgKDs7KSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGNhbm9uaWNhbCA9IGZzLnJlYWxwYXRoU3luYyhjdXJyZW50KTtcbiAgICAgIHJldHVybiBtaXNzaW5nLmxlbmd0aCA/IHBhdGguam9pbihjYW5vbmljYWwsIC4uLm1pc3NpbmcpIDogY2Fub25pY2FsO1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBpZiAoIWlzTWlzc2luZ1BhdGhFcnJvcihlcnJvcikpIHtcbiAgICAgICAgdGhyb3cgZXJyb3I7XG4gICAgICB9XG4gICAgfVxuICAgIGNvbnN0IHBhcmVudCA9IHBhdGguZGlybmFtZShjdXJyZW50KTtcbiAgICBpZiAocGFyZW50ID09PSBjdXJyZW50KSB7XG4gICAgICAvLyBOb3RoaW5nIG9uIHRoZSBjaGFpbiBleGlzdHMgKHNob3VsZCBub3QgaGFwcGVuIGZvciBhYnNvbHV0ZSBwYXRocyDigJRcbiAgICAgIC8vIHRoZSByb290IGV4aXN0cyDigJQgYnV0IGtlZXAgYSBsZXhpY2FsIGZhbGxiYWNrIHJhdGhlciB0aGFuIHRocm93aW5nKS5cbiAgICAgIHJldHVybiBtaXNzaW5nLmxlbmd0aCA/IHBhdGguam9pbihjdXJyZW50LCAuLi5taXNzaW5nKSA6IGN1cnJlbnQ7XG4gICAgfVxuICAgIG1pc3NpbmcudW5zaGlmdChwYXRoLmJhc2VuYW1lKGN1cnJlbnQpKTtcbiAgICBjdXJyZW50ID0gcGFyZW50O1xuICB9XG59XG5cbi8qKlxuICogVmV0cyBhIHVzZXItc3VwcGxpZWQgb3V0cHV0IGJhc2UgZm9yIENMSSB3cml0ZXMgYW5kIHJldHVybnMgdGhlIGNhbm9uaWNhbFxuICogcGF0aCBhbGwgc3Vic2VxdWVudCB3cml0ZXMgc2hvdWxkIHVzZSAoIzIzNDQpLlxuICpcbiAqIFR3byByZWdpbWVzLCBzcGxpdCBieSB3aGF0IHRoZSB1c2VyIGxleGljYWxseSBuYW1lZDpcbiAqXG4gKiAgLSBCYXNlIGluc2lkZSB0aGUgYW5jaG9yIChyZWxhdGl2ZSBvdXRwdXRzIGxpa2UgdGhlICcuL2FudGhyb3BpYy1za2lsbHMnXG4gKiAgICBkZWZhdWx0KTogdGhlIGNhbm9uaWNhbCBiYXNlIG11c3Qgc3RheSBpbnNpZGUgdGhlIGNhbm9uaWNhbCBhbmNob3IuXG4gKiAgICBBIHN5bWxpbmsgdGhhdCByZWRpcmVjdHMgaXQgZWxzZXdoZXJlIOKAlCBpbmNsdWRpbmcgb25lIHdob3NlIHRhcmdldFxuICogICAgYWxyZWFkeSBjb250YWlucyBtYXRjaGluZyBzdWJkaXJlY3RvcmllcywgdGhlIGNhc2UgcGVyLWNvbXBvbmVudCBsc3RhdFxuICogICAgY2hlY2tzIG1pc3Mg4oCUIGlzIHJlamVjdGVkLiBDb21wYXJpbmcgaW4gY2Fub25pY2FsIHNwYWNlIG1lYW5zIHN5c3RlbVxuICogICAgbGlua3MgYWJvdmUgdGhlIGFuY2hvciAobWFjT1MgL3RtcCAtPiAvcHJpdmF0ZS90bXApIG5ldmVyIGZhbHNlLXBvc2l0aXZlLlxuICpcbiAqICAtIEJhc2Ugb3V0c2lkZSB0aGUgYW5jaG9yIChleHBsaWNpdCBhYnNvbHV0ZSBvciAuLi8gb3V0cHV0cyk6IHRoZSB1c2VyXG4gKiAgICBuYW1lZCB0aGF0IGRlc3RpbmF0aW9uLCBzbyB0aGVyZSBpcyBubyBib3VuZGFyeSB0byBkZWZlbmQuIEluc3RlYWQgb2ZcbiAqICAgIGd1ZXNzaW5nIGludGVudCwgdGhlIHJlYWwgZGVzdGluYXRpb24gaXMgZGlzY2xvc2VkIHZpYSBgb25EaXNjbG9zZWBcbiAqICAgIHdoZW5ldmVyIGl0IGRpZmZlcnMgZnJvbSB0aGUgbGV4aWNhbCBwYXRoLCBhbmQgdGhlIGNhbm9uaWNhbCBwYXRoIGlzXG4gKiAgICByZXR1cm5lZCBzbyB3aGF0IHdhcyB2ZXR0ZWQgaXMgd2hhdCBnZXRzIHdyaXR0ZW4uXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB2ZXRPdXRwdXRCYXNlKFxuICBiYXNlRGlyOiBzdHJpbmcsXG4gIG9wdGlvbnM/OiB7IGFuY2hvcj86IHN0cmluZzsgb25EaXNjbG9zZT86IChjYW5vbmljYWxCYXNlOiBzdHJpbmcpID0+IHZvaWQgfSxcbik6IHN0cmluZyB7XG4gIGlmICghYmFzZURpciB8fCB0eXBlb2YgYmFzZURpciAhPT0gJ3N0cmluZycpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdCYXNlIGRpcmVjdG9yeSBtdXN0IGJlIGEgbm9uLWVtcHR5IHN0cmluZycpO1xuICB9XG4gIGlmIChiYXNlRGlyLmluY2x1ZGVzKCdcXDAnKSkge1xuICAgIHRocm93IG5ldyBFcnJvcignUGF0aCBzZWdtZW50IGNvbnRhaW5zIGEgbnVsbCBieXRlJyk7XG4gIH1cblxuICBjb25zdCByZXNvbHZlZEFuY2hvciA9IHBhdGgucmVzb2x2ZShvcHRpb25zPy5hbmNob3IgPz8gcHJvY2Vzcy5jd2QoKSk7XG4gIGNvbnN0IHJlc29sdmVkQmFzZSA9IHBhdGgucmVzb2x2ZShiYXNlRGlyKTtcbiAgY29uc3QgY2Fub25pY2FsQmFzZSA9IGNhbm9uaWNhbGl6ZVBhdGgocmVzb2x2ZWRCYXNlKTtcblxuICBpZiAoIWVzY2FwZXNCYXNlKHBhdGgucmVsYXRpdmUocmVzb2x2ZWRBbmNob3IsIHJlc29sdmVkQmFzZSkpKSB7XG4gICAgY29uc3QgY2Fub25pY2FsQW5jaG9yID0gY2Fub25pY2FsaXplUGF0aChyZXNvbHZlZEFuY2hvcik7XG4gICAgaWYgKGVzY2FwZXNCYXNlKHBhdGgucmVsYXRpdmUoY2Fub25pY2FsQW5jaG9yLCBjYW5vbmljYWxCYXNlKSkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgYE91dHB1dCBwYXRoIHJlc29sdmVzIG91dHNpZGUgdGhlIHdvcmtpbmcgZGlyZWN0b3J5IHRocm91Z2ggYSBzeW1ib2xpYyBsaW5rIChyZWFsIGRlc3RpbmF0aW9uOiAke2Nhbm9uaWNhbEJhc2V9KWAsXG4gICAgICApO1xuICAgIH1cbiAgICByZXR1cm4gY2Fub25pY2FsQmFzZTtcbiAgfVxuXG4gIGlmIChjYW5vbmljYWxCYXNlICE9PSByZXNvbHZlZEJhc2UpIHtcbiAgICBvcHRpb25zPy5vbkRpc2Nsb3NlPy4oY2Fub25pY2FsQmFzZSk7XG4gIH1cbiAgcmV0dXJuIGNhbm9uaWNhbEJhc2U7XG59XG4iXX0=
@@ -33,6 +33,13 @@ export declare function createSetupRoutes(opts?: {
33
33
  _installPermissionHook?: (client: string) => Promise<InstallPermissionHookResult>;
34
34
  /** Override permission hook status reconciler. For testing only. */
35
35
  _reconcilePermissionHookStatus?: (client: string) => Promise<PermissionHookStatus>;
36
+ /**
37
+ * Override the NVM launcher mitigation applied after a successful install.
38
+ * For testing only. Without this seam the install handler runs the real
39
+ * applyNvmLauncherIfNeeded against the real homedir(); on a machine WITH NVM
40
+ * that patches the developer's real client configs (issue #2338).
41
+ */
42
+ _applyNvmLauncher?: (client: string) => Promise<NvmLauncherResult>;
36
43
  /** Enable automatic hook asset repair during detect. Defaults off in tests. */
37
44
  _autoRepairPermissionHooksOnDetect?: boolean;
38
45
  /** Skip the sliding-window rate limiter. For testing only. */
@@ -59,18 +66,27 @@ export type NvmLauncherResult = 'applied' | 'not-applicable' | 'failed';
59
66
  */
60
67
  export declare function applyNvmLauncherIfNeeded(client: string, home?: string): Promise<NvmLauncherResult>;
61
68
  /**
62
- * Startup repair: re-creates the wrapper and re-patches all known JSON-format
63
- * client configs on every server start. Handles two cases:
64
- * 1. Wrapper was deleted recreates it so configs pointing to it keep working.
65
- * 2. Pre-existing install (user installed before this fix shipped) — patches
66
- * configs that still use bare `npx`.
69
+ * Startup repair + self-heal: reconciles every known JSON-format client config
70
+ * on each server start. Behaviour depends on whether NVM is installed, matching
71
+ * the acceptance matrix in https://github.com/DollhouseMCP/mcp-server/issues/2338:
72
+ *
73
+ * NVM present → (re)create the wrapper and point each config at it. Covers a
74
+ * deleted wrapper, a pre-fix install still on bare `npx`, and a config left
75
+ * pointing at a dead temp-dir wrapper (regenerates the real one). Healthy
76
+ * configs re-serialise byte-identically, so this is a no-op for them.
77
+ * • NVM absent → never create a wrapper, but SELF-HEAL any config whose
78
+ * command points at a `dollhousemcp-nvm.sh` that no longer exists (e.g. the
79
+ * #2338 test leak, or a user who removed ~/.dollhouse/bin) back to bare `npx`,
80
+ * preserving args so the client can launch DollhouseMCP again.
67
81
  *
68
82
  * Fire-and-forget from startWebServer. All errors are swallowed and logged.
69
83
  *
70
84
  * @param home - Override home directory (injectable for tests)
71
85
  * @param configPathResolver - Override config path lookup (injectable for tests).
72
- * Return null to skip a client entirely.
73
- * Defaults to the production getConfigPath.
86
+ * Return null to skip a client entirely. Defaults to a
87
+ * home-derived resolver so `home` stays authoritative
88
+ * — the default must NOT reach the real homedir() when
89
+ * a fake home is supplied (#2338).
74
90
  */
75
91
  export declare function repairNvmLauncherOnStartup(home?: string, configPathResolver?: (client: string) => string | null): Promise<void>;
76
92
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"setupRoutes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/setupRoutes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setupRoutes.d.ts","sourceRoot":"","sources":["../../../src/web/routes/setupRoutes.ts"],"names":[],"mappings":"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"}