@j0hanz/filesystem-mcp 2.6.3 → 2.7.1
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 +28 -19
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +5 -2
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +22 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +0 -17
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +53 -47
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +7 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +7 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/diff.d.ts +0 -4
- package/dist/core/diff.d.ts.map +1 -1
- package/dist/core/diff.js +1 -15
- package/dist/core/diff.js.map +1 -1
- package/dist/core/file-uri.d.ts +9 -9
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +16 -11
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fs.d.ts +1 -1
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +65 -56
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +6 -0
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +80 -13
- package/dist/core/glob.js.map +1 -1
- package/dist/core/input-required.d.ts +30 -13
- package/dist/core/input-required.d.ts.map +1 -1
- package/dist/core/input-required.js +35 -10
- package/dist/core/input-required.js.map +1 -1
- package/dist/core/mime.d.ts +2 -4
- package/dist/core/mime.d.ts.map +1 -1
- package/dist/core/mime.js +3 -8
- package/dist/core/mime.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +7 -4
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-discovery.d.ts.map +1 -1
- package/dist/core/path-discovery.js +2 -2
- package/dist/core/path-discovery.js.map +1 -1
- package/dist/core/path-utils.d.ts +1 -3
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +0 -3
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +7 -0
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +69 -26
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +1 -0
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +4 -0
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +0 -80
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +0 -89
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +8 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +191 -101
- package/dist/core/search.js.map +1 -1
- package/dist/core/store.d.ts +7 -11
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +19 -21
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +17 -0
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +19 -0
- package/dist/core/util.js.map +1 -1
- package/dist/instructions.js +1 -1
- package/dist/instructions.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +5 -2
- package/dist/resources.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/batch.d.ts +3 -13
- package/dist/tools/batch.d.ts.map +1 -1
- package/dist/tools/batch.js +11 -27
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +25 -46
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +20 -23
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +11 -27
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +10 -37
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.d.ts.map +1 -1
- package/dist/tools/diff.js +2 -13
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +20 -53
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +3 -28
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/list-roots.d.ts.map +1 -1
- package/dist/tools/list-roots.js +1 -12
- package/dist/tools/list-roots.js.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +19 -53
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +54 -37
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.d.ts.map +1 -1
- package/dist/tools/patch.js +4 -24
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/progress.d.ts +0 -2
- package/dist/tools/progress.d.ts.map +1 -1
- package/dist/tools/progress.js +20 -12
- package/dist/tools/progress.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +29 -116
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +20 -43
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +24 -78
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +5 -19
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http-policy.d.ts.map +1 -1
- package/dist/transport/http-policy.js +1 -0
- package/dist/transport/http-policy.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +16 -27
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/shared.d.ts +3 -1
- package/dist/transport/shared.d.ts.map +1 -1
- package/dist/transport/shared.js +14 -1
- package/dist/transport/shared.js.map +1 -1
- package/dist/transport/stdio.d.ts.map +1 -1
- package/dist/transport/stdio.js +18 -3
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +3 -3
package/dist/core/mime.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
|
-
export
|
|
3
|
-
type MimeKind = (typeof MIME_KINDS)[number];
|
|
2
|
+
export type FileKind = 'text' | 'binary' | 'image' | 'audio' | 'pdf';
|
|
4
3
|
export interface MimeInfo {
|
|
5
4
|
mimeType: string;
|
|
6
|
-
kind:
|
|
5
|
+
kind: FileKind;
|
|
7
6
|
}
|
|
8
7
|
export declare const MIME_SAMPLE_SIZE = 512;
|
|
9
8
|
export declare function isKnownBinaryExtension(filePath: string): boolean;
|
|
@@ -12,5 +11,4 @@ export declare function hasUtf16Bom(slice: Buffer): boolean;
|
|
|
12
11
|
export declare function isBinarySample(slice: Buffer): boolean;
|
|
13
12
|
export declare function detectMimeType(path: string, sample?: Buffer): MimeInfo;
|
|
14
13
|
export declare function detectMimeFromContent(path: string, content: string | Buffer): MimeInfo;
|
|
15
|
-
export {};
|
|
16
14
|
//# sourceMappingURL=mime.d.ts.map
|
package/dist/core/mime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../../src/core/mime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAKrC,
|
|
1
|
+
{"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../../src/core/mime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAKrC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;AAErE,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;CAChB;AA0GD,eAAO,MAAM,gBAAgB,MAAM,CAAC;AA0CpC,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGhE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKlD;AAgBD,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKrD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CActE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAOtF"}
|
package/dist/core/mime.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { extname } from 'node:path';
|
|
3
|
-
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
4
|
-
export const MIME_KINDS = ['text', 'binary', 'image', 'audio', 'pdf'];
|
|
5
3
|
// ─── Extension Map ──────────────────────────────────────────────────────────
|
|
6
4
|
// Maps file extensions to MIME types (80+ common extensions)
|
|
7
5
|
const EXT_MAP = {
|
|
@@ -166,12 +164,9 @@ export function isBinarySample(slice) {
|
|
|
166
164
|
export function detectMimeType(path, sample) {
|
|
167
165
|
const lastDot = path.lastIndexOf('.');
|
|
168
166
|
const ext = lastDot > -1 ? path.slice(lastDot + 1).toLowerCase() : '';
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return entry;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
167
|
+
const known = Object.hasOwn(EXT_MAP, ext) ? EXT_MAP[ext] : undefined;
|
|
168
|
+
if (known)
|
|
169
|
+
return known;
|
|
175
170
|
// No known extension: fall back to a binary/text probe of the content.
|
|
176
171
|
if (sample) {
|
|
177
172
|
return isBinarySample(sample.subarray(0, MIME_SAMPLE_SIZE))
|
package/dist/core/mime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../../src/core/mime.ts"],"names":[],"mappings":"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","sourcesContent":["import { Buffer } from 'node:buffer';\nimport { extname } from 'node:path';\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport const MIME_KINDS = ['text', 'binary', 'image', 'audio', 'pdf'] as const;\ntype MimeKind = (typeof MIME_KINDS)[number];\n\nexport interface MimeInfo {\n mimeType: string;\n kind: MimeKind;\n}\n\n// ─── Extension Map ──────────────────────────────────────────────────────────\n// Maps file extensions to MIME types (80+ common extensions)\n\nconst EXT_MAP: Record<string, { mimeType: string; kind: MimeKind }> = {\n // Text: Web & Markup\n html: { mimeType: 'text/html', kind: 'text' },\n htm: { mimeType: 'text/html', kind: 'text' },\n xml: { mimeType: 'text/xml', kind: 'text' },\n css: { mimeType: 'text/css', kind: 'text' },\n svg: { mimeType: 'image/svg+xml', kind: 'image' },\n md: { mimeType: 'text/markdown', kind: 'text' },\n markdown: { mimeType: 'text/markdown', kind: 'text' },\n mdown: { mimeType: 'text/markdown', kind: 'text' },\n\n // Text: Programming Languages\n js: { mimeType: 'text/javascript', kind: 'text' },\n mjs: { mimeType: 'text/javascript', kind: 'text' },\n jsx: { mimeType: 'text/jsx', kind: 'text' },\n ts: { mimeType: 'text/typescript', kind: 'text' },\n tsx: { mimeType: 'text/tsx', kind: 'text' },\n py: { mimeType: 'text/x-python', kind: 'text' },\n java: { mimeType: 'text/x-java', kind: 'text' },\n c: { mimeType: 'text/x-c', kind: 'text' },\n cpp: { mimeType: 'text/x-cpp', kind: 'text' },\n cc: { mimeType: 'text/x-cpp', kind: 'text' },\n cxx: { mimeType: 'text/x-cpp', kind: 'text' },\n h: { mimeType: 'text/x-c', kind: 'text' },\n hpp: { mimeType: 'text/x-cpp', kind: 'text' },\n go: { mimeType: 'text/x-go', kind: 'text' },\n rs: { mimeType: 'text/x-rust', kind: 'text' },\n rb: { mimeType: 'text/x-ruby', kind: 'text' },\n php: { mimeType: 'text/x-php', kind: 'text' },\n sh: { mimeType: 'text/x-shellscript', kind: 'text' },\n bash: { mimeType: 'text/x-shellscript', kind: 'text' },\n zsh: { mimeType: 'text/x-shellscript', kind: 'text' },\n ps1: { mimeType: 'text/x-powershell', kind: 'text' },\n sql: { mimeType: 'text/x-sql', kind: 'text' },\n\n // Text: Data formats\n json: { mimeType: 'application/json', kind: 'text' },\n jsonc: { mimeType: 'application/json', kind: 'text' },\n ndjson: { mimeType: 'application/x-ndjson', kind: 'text' },\n yaml: { mimeType: 'text/yaml', kind: 'text' },\n yml: { mimeType: 'text/yaml', kind: 'text' },\n toml: { mimeType: 'text/toml', kind: 'text' },\n ini: { mimeType: 'text/plain', kind: 'text' },\n cfg: { mimeType: 'text/plain', kind: 'text' },\n conf: { mimeType: 'text/plain', kind: 'text' },\n csv: { mimeType: 'text/csv', kind: 'text' },\n\n // Text: Diff & Patches\n diff: { mimeType: 'text/x-diff', kind: 'text' },\n patch: { mimeType: 'text/x-diff', kind: 'text' },\n\n // Text: Documentation\n txt: { mimeType: 'text/plain', kind: 'text' },\n text: { mimeType: 'text/plain', kind: 'text' },\n log: { mimeType: 'text/plain', kind: 'text' },\n rst: { mimeType: 'text/x-rst', kind: 'text' },\n\n // Image formats\n png: { mimeType: 'image/png', kind: 'image' },\n jpg: { mimeType: 'image/jpeg', kind: 'image' },\n jpeg: { mimeType: 'image/jpeg', kind: 'image' },\n gif: { mimeType: 'image/gif', kind: 'image' },\n webp: { mimeType: 'image/webp', kind: 'image' },\n ico: { mimeType: 'image/x-icon', kind: 'image' },\n bmp: { mimeType: 'image/bmp', kind: 'image' },\n tiff: { mimeType: 'image/tiff', kind: 'image' },\n tif: { mimeType: 'image/tiff', kind: 'image' },\n\n // Audio formats\n mp3: { mimeType: 'audio/mpeg', kind: 'audio' },\n wav: { mimeType: 'audio/wav', kind: 'audio' },\n flac: { mimeType: 'audio/flac', kind: 'audio' },\n aac: { mimeType: 'audio/aac', kind: 'audio' },\n ogg: { mimeType: 'audio/ogg', kind: 'audio' },\n m4a: { mimeType: 'audio/mp4', kind: 'audio' },\n\n // PDF\n pdf: { mimeType: 'application/pdf', kind: 'pdf' },\n\n // Archives\n zip: { mimeType: 'application/zip', kind: 'binary' },\n tar: { mimeType: 'application/x-tar', kind: 'binary' },\n gz: { mimeType: 'application/gzip', kind: 'binary' },\n gzip: { mimeType: 'application/gzip', kind: 'binary' },\n '7z': { mimeType: 'application/x-7z-compressed', kind: 'binary' },\n rar: { mimeType: 'application/x-rar-compressed', kind: 'binary' },\n bz2: { mimeType: 'application/x-bzip2', kind: 'binary' },\n xz: { mimeType: 'application/x-xz', kind: 'binary' },\n\n // Other binary formats\n wasm: { mimeType: 'application/wasm', kind: 'binary' },\n so: { mimeType: 'application/octet-stream', kind: 'binary' },\n dylib: { mimeType: 'application/octet-stream', kind: 'binary' },\n dll: { mimeType: 'application/octet-stream', kind: 'binary' },\n exe: { mimeType: 'application/octet-stream', kind: 'binary' },\n msi: { mimeType: 'application/octet-stream', kind: 'binary' },\n dmg: { mimeType: 'application/octet-stream', kind: 'binary' },\n};\n\n// ─── Helper Functions ───────────────────────────────────────────────────────\n\nexport const MIME_SAMPLE_SIZE = 512;\n\n// Every non-text kind is binary for read purposes, except SVG — an image kind\n// whose bytes are text, and which the read path must not fast-path as binary.\n// EXT_MAP covers the extensions it knows (image/audio/pdf/binary kinds all\n// become binary here, image-kind SVG excepted); EXTRA_BINARY_EXTENSIONS lists\n// the rest that EXT_MAP does not carry but the read path must still treat as\n// binary. The two tables had drifted; this derivation resolves the drift toward\n// binary (only changes whether a file skips its content probe).\nconst EXTRA_BINARY_EXTENSIONS = [\n 'mov',\n 'avi',\n 'mkv',\n 'webm',\n 'ttf',\n 'otf',\n 'woff',\n 'woff2',\n 'doc',\n 'docx',\n 'xls',\n 'xlsx',\n 'ppt',\n 'pptx',\n 'sqlite',\n 'db',\n 'bin',\n 'dat',\n 'mp4',\n];\n\nconst KNOWN_BINARY_EXTENSIONS: ReadonlySet<string> = new Set(\n [\n ...Object.entries(EXT_MAP)\n .filter(([, v]) => v.kind !== 'text')\n .map(([k]) => k),\n ...EXTRA_BINARY_EXTENSIONS,\n ]\n .filter((ext) => ext !== 'svg')\n .map((ext) => `.${ext}`),\n);\n\nexport function isKnownBinaryExtension(filePath: string): boolean {\n const ext = extname(filePath).toLowerCase();\n return KNOWN_BINARY_EXTENSIONS.has(ext);\n}\n\nexport function hasUtf16Bom(slice: Buffer): boolean {\n return (\n slice.length >= 2 &&\n ((slice[0] === 0xff && slice[1] === 0xfe) || (slice[0] === 0xfe && slice[1] === 0xff))\n );\n}\n\n/**\n * True when `slice` is valid UTF-8 *as a prefix*: `stream: true` parks a\n * multi-byte sequence cut by the sample boundary in the decoder's buffer\n * instead of reporting it, while `fatal: true` still throws on invalid bytes.\n */\nfunction isUtf8Prefix(slice: Buffer): boolean {\n try {\n new TextDecoder('utf-8', { fatal: true }).decode(slice, { stream: true });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Single binary-vs-text verdict, shared by `detectMimeType` and the read path. */\nexport function isBinarySample(slice: Buffer): boolean {\n if (slice.length === 0) return false;\n if (hasUtf16Bom(slice)) return false;\n if (slice.includes(0)) return true;\n return !isUtf8Prefix(slice);\n}\n\nexport function detectMimeType(path: string, sample?: Buffer): MimeInfo {\n const lastDot = path.lastIndexOf('.');\n const ext = lastDot > -1 ? path.slice(lastDot + 1).toLowerCase() : '';\n\n if (ext && Object.hasOwn(EXT_MAP, ext)) {\n const entry = EXT_MAP[ext];\n if (entry !== undefined) {\n return entry;\n }\n }\n\n // No known extension: fall back to a binary/text probe of the content.\n if (sample) {\n return isBinarySample(sample.subarray(0, MIME_SAMPLE_SIZE))\n ? { mimeType: 'application/octet-stream', kind: 'binary' }\n : { mimeType: 'text/plain', kind: 'text' };\n }\n\n return { mimeType: 'application/octet-stream', kind: 'binary' };\n}\n\nexport function detectMimeFromContent(path: string, content: string | Buffer): MimeInfo {\n return detectMimeType(\n path,\n Buffer.isBuffer(content)\n ? content.subarray(0, MIME_SAMPLE_SIZE)\n : Buffer.from(content.slice(0, MIME_SAMPLE_SIZE)),\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../../src/core/mime.ts"],"names":[],"mappings":"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","sourcesContent":["import { Buffer } from 'node:buffer';\nimport { extname } from 'node:path';\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\nexport type FileKind = 'text' | 'binary' | 'image' | 'audio' | 'pdf';\n\nexport interface MimeInfo {\n mimeType: string;\n kind: FileKind;\n}\n\n// ─── Extension Map ──────────────────────────────────────────────────────────\n// Maps file extensions to MIME types (80+ common extensions)\n\nconst EXT_MAP: Record<string, MimeInfo> = {\n // Text: Web & Markup\n html: { mimeType: 'text/html', kind: 'text' },\n htm: { mimeType: 'text/html', kind: 'text' },\n xml: { mimeType: 'text/xml', kind: 'text' },\n css: { mimeType: 'text/css', kind: 'text' },\n svg: { mimeType: 'image/svg+xml', kind: 'image' },\n md: { mimeType: 'text/markdown', kind: 'text' },\n markdown: { mimeType: 'text/markdown', kind: 'text' },\n mdown: { mimeType: 'text/markdown', kind: 'text' },\n\n // Text: Programming Languages\n js: { mimeType: 'text/javascript', kind: 'text' },\n mjs: { mimeType: 'text/javascript', kind: 'text' },\n jsx: { mimeType: 'text/jsx', kind: 'text' },\n ts: { mimeType: 'text/typescript', kind: 'text' },\n tsx: { mimeType: 'text/tsx', kind: 'text' },\n py: { mimeType: 'text/x-python', kind: 'text' },\n java: { mimeType: 'text/x-java', kind: 'text' },\n c: { mimeType: 'text/x-c', kind: 'text' },\n cpp: { mimeType: 'text/x-cpp', kind: 'text' },\n cc: { mimeType: 'text/x-cpp', kind: 'text' },\n cxx: { mimeType: 'text/x-cpp', kind: 'text' },\n h: { mimeType: 'text/x-c', kind: 'text' },\n hpp: { mimeType: 'text/x-cpp', kind: 'text' },\n go: { mimeType: 'text/x-go', kind: 'text' },\n rs: { mimeType: 'text/x-rust', kind: 'text' },\n rb: { mimeType: 'text/x-ruby', kind: 'text' },\n php: { mimeType: 'text/x-php', kind: 'text' },\n sh: { mimeType: 'text/x-shellscript', kind: 'text' },\n bash: { mimeType: 'text/x-shellscript', kind: 'text' },\n zsh: { mimeType: 'text/x-shellscript', kind: 'text' },\n ps1: { mimeType: 'text/x-powershell', kind: 'text' },\n sql: { mimeType: 'text/x-sql', kind: 'text' },\n\n // Text: Data formats\n json: { mimeType: 'application/json', kind: 'text' },\n jsonc: { mimeType: 'application/json', kind: 'text' },\n ndjson: { mimeType: 'application/x-ndjson', kind: 'text' },\n yaml: { mimeType: 'text/yaml', kind: 'text' },\n yml: { mimeType: 'text/yaml', kind: 'text' },\n toml: { mimeType: 'text/toml', kind: 'text' },\n ini: { mimeType: 'text/plain', kind: 'text' },\n cfg: { mimeType: 'text/plain', kind: 'text' },\n conf: { mimeType: 'text/plain', kind: 'text' },\n csv: { mimeType: 'text/csv', kind: 'text' },\n\n // Text: Diff & Patches\n diff: { mimeType: 'text/x-diff', kind: 'text' },\n patch: { mimeType: 'text/x-diff', kind: 'text' },\n\n // Text: Documentation\n txt: { mimeType: 'text/plain', kind: 'text' },\n text: { mimeType: 'text/plain', kind: 'text' },\n log: { mimeType: 'text/plain', kind: 'text' },\n rst: { mimeType: 'text/x-rst', kind: 'text' },\n\n // Image formats\n png: { mimeType: 'image/png', kind: 'image' },\n jpg: { mimeType: 'image/jpeg', kind: 'image' },\n jpeg: { mimeType: 'image/jpeg', kind: 'image' },\n gif: { mimeType: 'image/gif', kind: 'image' },\n webp: { mimeType: 'image/webp', kind: 'image' },\n ico: { mimeType: 'image/x-icon', kind: 'image' },\n bmp: { mimeType: 'image/bmp', kind: 'image' },\n tiff: { mimeType: 'image/tiff', kind: 'image' },\n tif: { mimeType: 'image/tiff', kind: 'image' },\n\n // Audio formats\n mp3: { mimeType: 'audio/mpeg', kind: 'audio' },\n wav: { mimeType: 'audio/wav', kind: 'audio' },\n flac: { mimeType: 'audio/flac', kind: 'audio' },\n aac: { mimeType: 'audio/aac', kind: 'audio' },\n ogg: { mimeType: 'audio/ogg', kind: 'audio' },\n m4a: { mimeType: 'audio/mp4', kind: 'audio' },\n\n // PDF\n pdf: { mimeType: 'application/pdf', kind: 'pdf' },\n\n // Archives\n zip: { mimeType: 'application/zip', kind: 'binary' },\n tar: { mimeType: 'application/x-tar', kind: 'binary' },\n gz: { mimeType: 'application/gzip', kind: 'binary' },\n gzip: { mimeType: 'application/gzip', kind: 'binary' },\n '7z': { mimeType: 'application/x-7z-compressed', kind: 'binary' },\n rar: { mimeType: 'application/x-rar-compressed', kind: 'binary' },\n bz2: { mimeType: 'application/x-bzip2', kind: 'binary' },\n xz: { mimeType: 'application/x-xz', kind: 'binary' },\n\n // Other binary formats\n wasm: { mimeType: 'application/wasm', kind: 'binary' },\n so: { mimeType: 'application/octet-stream', kind: 'binary' },\n dylib: { mimeType: 'application/octet-stream', kind: 'binary' },\n dll: { mimeType: 'application/octet-stream', kind: 'binary' },\n exe: { mimeType: 'application/octet-stream', kind: 'binary' },\n msi: { mimeType: 'application/octet-stream', kind: 'binary' },\n dmg: { mimeType: 'application/octet-stream', kind: 'binary' },\n};\n\n// ─── Helper Functions ───────────────────────────────────────────────────────\n\nexport const MIME_SAMPLE_SIZE = 512;\n\n// Every non-text kind is binary for read purposes, except SVG — an image kind\n// whose bytes are text, and which the read path must not fast-path as binary.\n// EXT_MAP covers the extensions it knows (image/audio/pdf/binary kinds all\n// become binary here, image-kind SVG excepted); EXTRA_BINARY_EXTENSIONS lists\n// the rest that EXT_MAP does not carry but the read path must still treat as\n// binary. The two tables had drifted; this derivation resolves the drift toward\n// binary (only changes whether a file skips its content probe).\nconst EXTRA_BINARY_EXTENSIONS = [\n 'mov',\n 'avi',\n 'mkv',\n 'webm',\n 'ttf',\n 'otf',\n 'woff',\n 'woff2',\n 'doc',\n 'docx',\n 'xls',\n 'xlsx',\n 'ppt',\n 'pptx',\n 'sqlite',\n 'db',\n 'bin',\n 'dat',\n 'mp4',\n];\n\nconst KNOWN_BINARY_EXTENSIONS: ReadonlySet<string> = new Set(\n [\n ...Object.entries(EXT_MAP)\n .filter(([, v]) => v.kind !== 'text')\n .map(([k]) => k),\n ...EXTRA_BINARY_EXTENSIONS,\n ]\n .filter((ext) => ext !== 'svg')\n .map((ext) => `.${ext}`),\n);\n\nexport function isKnownBinaryExtension(filePath: string): boolean {\n const ext = extname(filePath).toLowerCase();\n return KNOWN_BINARY_EXTENSIONS.has(ext);\n}\n\nexport function hasUtf16Bom(slice: Buffer): boolean {\n return (\n slice.length >= 2 &&\n ((slice[0] === 0xff && slice[1] === 0xfe) || (slice[0] === 0xfe && slice[1] === 0xff))\n );\n}\n\n/**\n * True when `slice` is valid UTF-8 *as a prefix*: `stream: true` parks a\n * multi-byte sequence cut by the sample boundary in the decoder's buffer\n * instead of reporting it, while `fatal: true` still throws on invalid bytes.\n */\nfunction isUtf8Prefix(slice: Buffer): boolean {\n try {\n new TextDecoder('utf-8', { fatal: true }).decode(slice, { stream: true });\n return true;\n } catch {\n return false;\n }\n}\n\n/** Single binary-vs-text verdict, shared by `detectMimeType` and the read path. */\nexport function isBinarySample(slice: Buffer): boolean {\n if (slice.length === 0) return false;\n if (hasUtf16Bom(slice)) return false;\n if (slice.includes(0)) return true;\n return !isUtf8Prefix(slice);\n}\n\nexport function detectMimeType(path: string, sample?: Buffer): MimeInfo {\n const lastDot = path.lastIndexOf('.');\n const ext = lastDot > -1 ? path.slice(lastDot + 1).toLowerCase() : '';\n const known = Object.hasOwn(EXT_MAP, ext) ? EXT_MAP[ext] : undefined;\n if (known) return known;\n\n // No known extension: fall back to a binary/text probe of the content.\n if (sample) {\n return isBinarySample(sample.subarray(0, MIME_SAMPLE_SIZE))\n ? { mimeType: 'application/octet-stream', kind: 'binary' }\n : { mimeType: 'text/plain', kind: 'text' };\n }\n\n return { mimeType: 'application/octet-stream', kind: 'binary' };\n}\n\nexport function detectMimeFromContent(path: string, content: string | Buffer): MimeInfo {\n return detectMimeType(\n path,\n Buffer.isBuffer(content)\n ? content.subarray(0, MIME_SAMPLE_SIZE)\n : Buffer.from(content.slice(0, MIME_SAMPLE_SIZE)),\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-completer.d.ts","sourceRoot":"","sources":["../../src/core/path-completer.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAyK3C;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"path-completer.d.ts","sourceRoot":"","sources":["../../src/core/path-completer.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAyK3C;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA6BzF"}
|
|
@@ -147,18 +147,21 @@ function getSearchContext(currentValue, allowed) {
|
|
|
147
147
|
* every match, sorted; the SDK truncates to 100 and reports the total.
|
|
148
148
|
*/
|
|
149
149
|
export async function suggestPaths(pathGuard, value) {
|
|
150
|
+
// Roots to pick, name and list; the containment set also holds each root's
|
|
151
|
+
// realpath alias, so it only gates what a directory listing may enter.
|
|
152
|
+
const roots = pathGuard.getRoots();
|
|
150
153
|
const allowed = pathGuard.getAllowedDirectories();
|
|
151
154
|
try {
|
|
152
155
|
if (!value) {
|
|
153
|
-
return
|
|
156
|
+
return roots;
|
|
154
157
|
}
|
|
155
|
-
const context = getSearchContext(value,
|
|
158
|
+
const context = getSearchContext(value, roots);
|
|
156
159
|
if (!context) {
|
|
157
|
-
return findRootPrefixMatches(value,
|
|
160
|
+
return findRootPrefixMatches(value, roots);
|
|
158
161
|
}
|
|
159
162
|
const { searchDir, prefix } = context;
|
|
160
163
|
const dirMatches = await findMatchesInDirectory(searchDir, prefix, allowed, (p) => pathGuard.isSensitive(p));
|
|
161
|
-
const rootMatches = findMatchingRoots(searchDir, prefix,
|
|
164
|
+
const rootMatches = findMatchingRoots(searchDir, prefix, roots);
|
|
162
165
|
return mergeCompletionMatches(dirMatches, rootMatches);
|
|
163
166
|
}
|
|
164
167
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-completer.js","sourceRoot":"","sources":["../../src/core/path-completer.ts"],"names":[],"mappings":"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","sourcesContent":["import { opendir, stat } from 'node:fs/promises';\nimport { basename, dirname, isAbsolute, join, parse, resolve, sep } from 'node:path';\n\nimport { isSkippableErrno, rethrowIfAborted } from './errors.ts';\nimport { Logger } from './observability.ts';\nimport {\n isPathWithinDirectories,\n isSamePath,\n isSlash,\n normalizePath,\n toPosixPath,\n} from './path-utils.ts';\nimport type { PathGuard } from './path.ts';\nimport { resolveRealPath } from './path.ts';\n\n// ─── pure path-completion helpers ───────────────────────────────────────────\n\nfunction hasTrailingSeparator(value: string): boolean {\n return value.length > 0 && isSlash(value.charCodeAt(value.length - 1));\n}\n\nfunction resolveFromBase(\n base: string,\n rawValue: string,\n trailingSeparator: boolean,\n): { searchDir: string; prefix: string } {\n const normalizedValue = normalizePath(resolve(base, rawValue));\n if (trailingSeparator) return { searchDir: normalizedValue, prefix: '' };\n return {\n searchDir: dirname(normalizedValue),\n prefix: basename(normalizedValue),\n };\n}\n\nfunction parseNamedRootInput(value: string): { rootName: string; remainder: string } | undefined {\n const normalizedInput = toPosixPath(value);\n if (!normalizedInput) return undefined;\n const slashIndex = normalizedInput.indexOf('/');\n if (slashIndex === -1) return { rootName: normalizedInput, remainder: '' };\n const rootName = normalizedInput.slice(0, slashIndex);\n if (!rootName) return undefined;\n return { rootName, remainder: normalizedInput.slice(slashIndex + 1) };\n}\n\nfunction findAllowedRootByName(rootName: string, allowed: readonly string[]): string | undefined {\n const normalizedRootName = rootName.toLowerCase();\n return allowed.find((candidate) => basename(candidate).toLowerCase() === normalizedRootName);\n}\n\nfunction resolveNamedRootContext(\n currentValue: string,\n allowed: readonly string[],\n): { searchDir: string; prefix: string } | undefined {\n const parsed = parseNamedRootInput(currentValue);\n if (!parsed) return undefined;\n const root = findAllowedRootByName(parsed.rootName, allowed);\n if (!root) return undefined;\n const trailingSeparator = hasTrailingSeparator(currentValue);\n return resolveFromBase(root, parsed.remainder, trailingSeparator);\n}\n\nasync function isAllowedCompletionDirectory(\n path: string,\n allowed: readonly string[],\n): Promise<boolean> {\n if (!isPathWithinDirectories(path, allowed)) return false;\n try {\n const stats = await stat(path);\n if (!stats.isDirectory()) return false;\n const real = await resolveRealPath(path);\n return real !== null && isPathWithinDirectories(real, allowed);\n } catch (err) {\n if (!isSkippableErrno(err)) {\n Logger.debug('isAllowedCompletionDirectory: unexpected probe error', {\n path,\n error: String(err),\n });\n }\n return false;\n }\n}\n\nfunction withDirectorySeparator(value: string): string {\n return value.endsWith(sep) ? value : `${value}${sep}`;\n}\n\nfunction collectAllowedRoots(\n allowed: readonly string[],\n predicate: (root: string) => boolean,\n): string[] {\n return allowed.filter(predicate).map(withDirectorySeparator);\n}\n\nfunction findRootPrefixMatches(currentValue: string, allowed: readonly string[]): string[] {\n const rootPrefix = parseNamedRootInput(currentValue)?.rootName.toLowerCase() ?? '';\n return collectAllowedRoots(allowed, (root) =>\n basename(root).toLowerCase().startsWith(rootPrefix),\n );\n}\n\nfunction findMatchingRoots(\n searchDir: string,\n prefix: string,\n allowed: readonly string[],\n): string[] {\n const lowerPrefix = prefix.toLowerCase();\n const normalizedSearchDir = normalizePath(searchDir);\n return collectAllowedRoots(allowed, (root) => {\n const rootDir = dirname(root);\n if (!isSamePath(rootDir, normalizedSearchDir)) return false;\n return basename(root).toLowerCase().startsWith(lowerPrefix);\n });\n}\n\nfunction mergeCompletionMatches(\n dirMatches: readonly string[],\n rootMatches: readonly string[],\n): string[] {\n const isDir = (v: string): boolean => v.endsWith(sep);\n return [...new Set([...dirMatches, ...rootMatches])].sort(\n (left, right) => Number(isDir(right)) - Number(isDir(left)) || left.localeCompare(right),\n );\n}\n\nasync function findMatchesInDirectory(\n searchDir: string,\n prefix: string,\n allowed: readonly string[],\n isSensitive?: (path: string) => boolean,\n): Promise<string[]> {\n const matches: string[] = [];\n if (!(await isAllowedCompletionDirectory(searchDir, allowed))) return matches;\n try {\n // Stream via opendir and collect every match. Do not cap here: the\n // SDK's completion result keeps the first 100 values and reports the\n // full length as `total` / `hasMore`, so the list must reach it whole\n // and already sorted (mergeCompletionMatches). Capping here would\n // keep the opendir-first 100, not the alphabetically-first 100, and\n // hide the true count from the client.\n const dir = await opendir(searchDir);\n try {\n const lowerPrefix = prefix.toLowerCase();\n for await (const entry of dir) {\n if (prefix !== '' && !entry.name.toLowerCase().startsWith(lowerPrefix)) continue;\n const fullPath = join(searchDir, entry.name);\n if (isSensitive?.(fullPath)) continue;\n matches.push(entry.isDirectory() ? `${fullPath}${sep}` : fullPath);\n }\n } finally {\n await dir.close().catch(() => {\n /* dir already closed or opendir never resolved */\n });\n }\n } catch (err) {\n if (!isSkippableErrno(err)) {\n Logger.warn('PathCompleter.findMatchesInDirectory: readdir failed', {\n searchDir,\n error: String(err),\n });\n }\n }\n return matches;\n}\n\nfunction getSearchContext(\n currentValue: string,\n allowed: readonly string[],\n): { searchDir: string; prefix: string } | undefined {\n const trailingSeparator = hasTrailingSeparator(currentValue);\n if (isAbsolute(currentValue)) {\n return resolveFromBase(parse(currentValue).root || sep, currentValue, trailingSeparator);\n }\n const namedRootContext = resolveNamedRootContext(currentValue, allowed);\n if (namedRootContext) return namedRootContext;\n if (allowed.length === 1) {\n const base = allowed[0];\n if (base) return resolveFromBase(base, currentValue, trailingSeparator);\n }\n return undefined;\n}\n\n/**\n * Path suggestions for `completion/complete` on the file template. One\n * `opendir` per call; clients debounce, so nothing is cached here. Returns\n * every match, sorted; the SDK truncates to 100 and reports the total.\n */\nexport async function suggestPaths(pathGuard: PathGuard, value: string): Promise<string[]> {\n const allowed = pathGuard.getAllowedDirectories();\n\n try {\n if (!value) {\n return allowed;\n }\n\n const context = getSearchContext(value, allowed);\n if (!context) {\n return findRootPrefixMatches(value, allowed);\n }\n\n const { searchDir, prefix } = context;\n const dirMatches = await findMatchesInDirectory(searchDir, prefix, allowed, (p) =>\n pathGuard.isSensitive(p),\n );\n const rootMatches = findMatchingRoots(searchDir, prefix, allowed);\n return mergeCompletionMatches(dirMatches, rootMatches);\n } catch (error) {\n rethrowIfAborted(error);\n Logger.warn('suggestPaths: completion failed, returning empty list', {\n error: error instanceof Error ? (error.stack ?? error.message) : String(error),\n });\n return [];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"path-completer.js","sourceRoot":"","sources":["../../src/core/path-completer.ts"],"names":[],"mappings":"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","sourcesContent":["import { opendir, stat } from 'node:fs/promises';\nimport { basename, dirname, isAbsolute, join, parse, resolve, sep } from 'node:path';\n\nimport { isSkippableErrno, rethrowIfAborted } from './errors.ts';\nimport { Logger } from './observability.ts';\nimport {\n isPathWithinDirectories,\n isSamePath,\n isSlash,\n normalizePath,\n toPosixPath,\n} from './path-utils.ts';\nimport type { PathGuard } from './path.ts';\nimport { resolveRealPath } from './path.ts';\n\n// ─── pure path-completion helpers ───────────────────────────────────────────\n\nfunction hasTrailingSeparator(value: string): boolean {\n return value.length > 0 && isSlash(value.charCodeAt(value.length - 1));\n}\n\nfunction resolveFromBase(\n base: string,\n rawValue: string,\n trailingSeparator: boolean,\n): { searchDir: string; prefix: string } {\n const normalizedValue = normalizePath(resolve(base, rawValue));\n if (trailingSeparator) return { searchDir: normalizedValue, prefix: '' };\n return {\n searchDir: dirname(normalizedValue),\n prefix: basename(normalizedValue),\n };\n}\n\nfunction parseNamedRootInput(value: string): { rootName: string; remainder: string } | undefined {\n const normalizedInput = toPosixPath(value);\n if (!normalizedInput) return undefined;\n const slashIndex = normalizedInput.indexOf('/');\n if (slashIndex === -1) return { rootName: normalizedInput, remainder: '' };\n const rootName = normalizedInput.slice(0, slashIndex);\n if (!rootName) return undefined;\n return { rootName, remainder: normalizedInput.slice(slashIndex + 1) };\n}\n\nfunction findAllowedRootByName(rootName: string, allowed: readonly string[]): string | undefined {\n const normalizedRootName = rootName.toLowerCase();\n return allowed.find((candidate) => basename(candidate).toLowerCase() === normalizedRootName);\n}\n\nfunction resolveNamedRootContext(\n currentValue: string,\n allowed: readonly string[],\n): { searchDir: string; prefix: string } | undefined {\n const parsed = parseNamedRootInput(currentValue);\n if (!parsed) return undefined;\n const root = findAllowedRootByName(parsed.rootName, allowed);\n if (!root) return undefined;\n const trailingSeparator = hasTrailingSeparator(currentValue);\n return resolveFromBase(root, parsed.remainder, trailingSeparator);\n}\n\nasync function isAllowedCompletionDirectory(\n path: string,\n allowed: readonly string[],\n): Promise<boolean> {\n if (!isPathWithinDirectories(path, allowed)) return false;\n try {\n const stats = await stat(path);\n if (!stats.isDirectory()) return false;\n const real = await resolveRealPath(path);\n return real !== null && isPathWithinDirectories(real, allowed);\n } catch (err) {\n if (!isSkippableErrno(err)) {\n Logger.debug('isAllowedCompletionDirectory: unexpected probe error', {\n path,\n error: String(err),\n });\n }\n return false;\n }\n}\n\nfunction withDirectorySeparator(value: string): string {\n return value.endsWith(sep) ? value : `${value}${sep}`;\n}\n\nfunction collectAllowedRoots(\n allowed: readonly string[],\n predicate: (root: string) => boolean,\n): string[] {\n return allowed.filter(predicate).map(withDirectorySeparator);\n}\n\nfunction findRootPrefixMatches(currentValue: string, allowed: readonly string[]): string[] {\n const rootPrefix = parseNamedRootInput(currentValue)?.rootName.toLowerCase() ?? '';\n return collectAllowedRoots(allowed, (root) =>\n basename(root).toLowerCase().startsWith(rootPrefix),\n );\n}\n\nfunction findMatchingRoots(\n searchDir: string,\n prefix: string,\n allowed: readonly string[],\n): string[] {\n const lowerPrefix = prefix.toLowerCase();\n const normalizedSearchDir = normalizePath(searchDir);\n return collectAllowedRoots(allowed, (root) => {\n const rootDir = dirname(root);\n if (!isSamePath(rootDir, normalizedSearchDir)) return false;\n return basename(root).toLowerCase().startsWith(lowerPrefix);\n });\n}\n\nfunction mergeCompletionMatches(\n dirMatches: readonly string[],\n rootMatches: readonly string[],\n): string[] {\n const isDir = (v: string): boolean => v.endsWith(sep);\n return [...new Set([...dirMatches, ...rootMatches])].sort(\n (left, right) => Number(isDir(right)) - Number(isDir(left)) || left.localeCompare(right),\n );\n}\n\nasync function findMatchesInDirectory(\n searchDir: string,\n prefix: string,\n allowed: readonly string[],\n isSensitive?: (path: string) => boolean,\n): Promise<string[]> {\n const matches: string[] = [];\n if (!(await isAllowedCompletionDirectory(searchDir, allowed))) return matches;\n try {\n // Stream via opendir and collect every match. Do not cap here: the\n // SDK's completion result keeps the first 100 values and reports the\n // full length as `total` / `hasMore`, so the list must reach it whole\n // and already sorted (mergeCompletionMatches). Capping here would\n // keep the opendir-first 100, not the alphabetically-first 100, and\n // hide the true count from the client.\n const dir = await opendir(searchDir);\n try {\n const lowerPrefix = prefix.toLowerCase();\n for await (const entry of dir) {\n if (prefix !== '' && !entry.name.toLowerCase().startsWith(lowerPrefix)) continue;\n const fullPath = join(searchDir, entry.name);\n if (isSensitive?.(fullPath)) continue;\n matches.push(entry.isDirectory() ? `${fullPath}${sep}` : fullPath);\n }\n } finally {\n await dir.close().catch(() => {\n /* dir already closed or opendir never resolved */\n });\n }\n } catch (err) {\n if (!isSkippableErrno(err)) {\n Logger.warn('PathCompleter.findMatchesInDirectory: readdir failed', {\n searchDir,\n error: String(err),\n });\n }\n }\n return matches;\n}\n\nfunction getSearchContext(\n currentValue: string,\n allowed: readonly string[],\n): { searchDir: string; prefix: string } | undefined {\n const trailingSeparator = hasTrailingSeparator(currentValue);\n if (isAbsolute(currentValue)) {\n return resolveFromBase(parse(currentValue).root || sep, currentValue, trailingSeparator);\n }\n const namedRootContext = resolveNamedRootContext(currentValue, allowed);\n if (namedRootContext) return namedRootContext;\n if (allowed.length === 1) {\n const base = allowed[0];\n if (base) return resolveFromBase(base, currentValue, trailingSeparator);\n }\n return undefined;\n}\n\n/**\n * Path suggestions for `completion/complete` on the file template. One\n * `opendir` per call; clients debounce, so nothing is cached here. Returns\n * every match, sorted; the SDK truncates to 100 and reports the total.\n */\nexport async function suggestPaths(pathGuard: PathGuard, value: string): Promise<string[]> {\n // Roots to pick, name and list; the containment set also holds each root's\n // realpath alias, so it only gates what a directory listing may enter.\n const roots = pathGuard.getRoots();\n const allowed = pathGuard.getAllowedDirectories();\n\n try {\n if (!value) {\n return roots;\n }\n\n const context = getSearchContext(value, roots);\n if (!context) {\n return findRootPrefixMatches(value, roots);\n }\n\n const { searchDir, prefix } = context;\n const dirMatches = await findMatchesInDirectory(searchDir, prefix, allowed, (p) =>\n pathGuard.isSensitive(p),\n );\n const rootMatches = findMatchingRoots(searchDir, prefix, roots);\n return mergeCompletionMatches(dirMatches, rootMatches);\n } catch (error) {\n rethrowIfAborted(error);\n Logger.warn('suggestPaths: completion failed, returning empty list', {\n error: error instanceof Error ? (error.stack ?? error.message) : String(error),\n });\n return [];\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-discovery.d.ts","sourceRoot":"","sources":["../../src/core/path-discovery.ts"],"names":[],"mappings":"AAuBA,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9E,OAAO,CAAC,MAAM,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"path-discovery.d.ts","sourceRoot":"","sources":["../../src/core/path-discovery.ts"],"names":[],"mappings":"AAuBA,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9E,OAAO,CAAC,MAAM,EAAE,CAAC,CAuBnB;AAiBD,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAkB9D;AAID,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAwC1F"}
|
|
@@ -23,7 +23,7 @@ export async function resolveConfiguredDirs(envVar, opts = {}) {
|
|
|
23
23
|
result.push(opts.resolveReal ? normalizePath(await realpath(normalized)) : normalized);
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
26
|
-
Logger.
|
|
26
|
+
Logger.warn(`Path configured in ${envVar} is not a directory: ${rawPath}`);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
catch (error) {
|
|
@@ -31,7 +31,7 @@ export async function resolveConfiguredDirs(envVar, opts = {}) {
|
|
|
31
31
|
result.push(normalized);
|
|
32
32
|
}
|
|
33
33
|
else {
|
|
34
|
-
Logger.
|
|
34
|
+
Logger.warn(`Path configured in ${envVar} is invalid or does not exist: ${rawPath} (${formatUnknownErrorMessage(error)})`);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-discovery.js","sourceRoot":"","sources":["../../src/core/path-discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,uBAAuB,EACvB,UAAU,EACV,aAAa,EACb,YAAY,GACb,MAAM,iBAAiB,CAAC;AAEzB,mFAAmF;AACnF,+EAA+E;AAC/E,+EAA+E;AAC/E,wEAAwE;AACxE,4EAA4E;AAC5E,gFAAgF;AAChF,4EAA4E;AAC5E,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAc,EACd,OAA6E,EAAE;IAE/E,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,OAAO,IAAI,GAAG,EAAE,CAAC;QAC1B,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,CAAC;YACjC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;gBACpB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YACzF,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"path-discovery.js","sourceRoot":"","sources":["../../src/core/path-discovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join, parse } from 'node:path';\n\nimport { formatUnknownErrorMessage } from './errors.ts';\nimport { Logger } from './observability.ts';\nimport {\n IS_WINDOWS,\n isPathInsideDirectory,\n isPathWithinDirectories,\n isSamePath,\n normalizePath,\n splitDirList,\n} from './path-utils.ts';\n\n// Resolve a configured env-var directory list (FS_ALLOWED_DIRS / FS_ROOT_BOUNDARY)\n// into normalized, verified directories. Each entry is stat'd; a non-directory\n// warns and is dropped, a missing entry warns unless `allowMissing` is set (in\n// which case the normalized path is kept). When `resolveReal` is set, a\n// directory entry is pushed as its realpath (normalized) instead of the raw\n// path — FS_ROOT_BOUNDARY uses this so a symlinked root resolves to its target.\n// A CLI override supplies `rawValue`, which beats the environment entirely.\n// Both warning messages are templated on `envVar` so operator output is stable.\nexport async function resolveConfiguredDirs(\n envVar: string,\n opts: { allowMissing?: boolean; resolveReal?: boolean; rawValue?: string } = {},\n): Promise<string[]> {\n const raw = splitDirList(opts.rawValue ?? process.env[envVar]);\n const result: string[] = [];\n for (const rawPath of raw) {\n const normalized = normalizePath(rawPath);\n try {\n const s = await stat(normalized);\n if (s.isDirectory()) {\n result.push(opts.resolveReal ? normalizePath(await realpath(normalized)) : normalized);\n } else {\n Logger.warn(`Path configured in ${envVar} is not a directory: ${rawPath}`);\n }\n } catch (error) {\n if (opts.allowMissing) {\n result.push(normalized);\n } else {\n Logger.warn(\n `Path configured in ${envVar} is invalid or does not exist: ${rawPath} (${formatUnknownErrorMessage(error)})`,\n );\n }\n }\n }\n return result;\n}\n\n// Directories a grant (or --allow-cwd) must never admit, as subtrees: a target\n// inside one of these is unsafe, and so is a target that *contains* one — the\n// nearest existing ancestor of a made-up path under C:\\Users is C:\\Users.\n//\n// Per platform on purpose: on POSIX `normalizePath('C:\\\\Windows')` resolves\n// against the cwd, so a cross-platform list would make the cwd itself\n// \"contain a system dir\" and refuse --allow-cwd.\nconst UNSAFE_SYSTEM_DIRS = IS_WINDOWS\n ? ['C:\\\\Windows', 'C:\\\\Program Files', 'C:\\\\Program Files (x86)', 'C:\\\\ProgramData']\n : ['/usr', '/etc', '/bin', '/sbin', '/System'];\n\n// Subtrees of home that hold credentials. Home itself and its other\n// subdirectories stay grantable: projects live there.\nconst UNSAFE_HOME_SUBDIRS = ['.ssh', '.aws', '.gnupg', '.kube', '.docker', '.config/gcloud'];\n\nexport function isUnsafeCwdPath(normalizedCwd: string): boolean {\n const candidate = normalizePath(normalizedCwd);\n\n // 1. A filesystem root, or anything that contains home or a system dir.\n const home = normalizePath(homedir());\n if (isSamePath(candidate, parse(candidate).root)) return true;\n if (isPathInsideDirectory(candidate, home)) return true;\n for (const dir of UNSAFE_SYSTEM_DIRS) {\n const system = normalizePath(dir);\n if (isPathInsideDirectory(candidate, system)) return true;\n if (isPathInsideDirectory(system, candidate)) return true;\n }\n\n // 2. Credential subtrees of home.\n for (const sub of UNSAFE_HOME_SUBDIRS) {\n if (isPathInsideDirectory(normalizePath(join(home, sub)), candidate)) return true;\n }\n return false;\n}\n\nconst MAX_PROJECT_ROOT_WALK_DEPTH = 32;\n\nexport async function findProjectRoot(startDir: string, ceiling: string[]): Promise<string> {\n const normCeiling = ceiling.map(normalizePath);\n let current = normalizePath(startDir);\n let depth = 0;\n\n for (;;) {\n if (depth++ >= MAX_PROJECT_ROOT_WALK_DEPTH) {\n break;\n }\n // Check if the current directory contains any markers\n const markers = ['.git', 'package.json', 'pyproject.toml'];\n for (const marker of markers) {\n const markerPath = join(current, marker);\n try {\n const s = await stat(markerPath);\n if (s.isDirectory() || s.isFile()) {\n return current;\n }\n } catch (_error) {\n // Skip and try next marker\n }\n }\n\n // Move to parent directory\n const parent = normalizePath(dirname(current));\n\n // Check if we hit the filesystem root\n if (parent === current) {\n break;\n }\n\n // Check if the parent is still within the ceiling\n if (!isPathWithinDirectories(parent, normCeiling)) {\n break;\n }\n\n current = parent;\n }\n\n return normalizePath(startDir);\n}\n"]}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const ENTRY_TYPES: readonly ["file", "directory", "symlink", "other"];
|
|
3
|
-
export type EntryType = (typeof ENTRY_TYPES)[number];
|
|
1
|
+
export type EntryType = 'file' | 'directory' | 'symlink' | 'other';
|
|
4
2
|
/** The three predicates both `Dirent` and `Stats` expose - all `resolveEntryType` needs. */
|
|
5
3
|
export interface DirentLike {
|
|
6
4
|
isDirectory(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/core/path-utils.ts"],"names":[],"mappings":"AAKA,
|
|
1
|
+
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../src/core/path-utils.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnE,4FAA4F;AAC5F,MAAM,WAAW,UAAU;IACzB,WAAW,IAAI,OAAO,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC;IAClB,cAAc,IAAI,OAAO,CAAC;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAK9D;AAID;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAClC,IAAI,CAON;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAQlF;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAM9D;AAID,eAAO,MAAM,UAAU,SAA+B,CAAC;AAEvD,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OAAqC,CAAC;AAE7E,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,KAAG,OACoB,CAAC;AAE5D,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MAAqC,CAAC;AAuClF,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAStF;AAED,wBAAgB,0BAA0B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAOzE;AAUD,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAa/C;AAID,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAO/D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,CAK3F;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAa7D;AAMD,wBAAgB,qBAAqB,CACnC,mBAAmB,EAAE,MAAM,EAC3B,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAST;AAED,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAET"}
|
package/dist/core/path-utils.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { homedir, platform } from 'node:os';
|
|
2
2
|
import { basename, delimiter, dirname, join, parse, resolve, sep, win32 } from 'node:path';
|
|
3
|
-
// ─── Shared primitives (no intra-package imports; every core module may import this) ──
|
|
4
|
-
/** The four filesystem entry types; published as the `FileType` schema. */
|
|
5
|
-
export const ENTRY_TYPES = ['file', 'directory', 'symlink', 'other'];
|
|
6
3
|
export function resolveEntryType(dirent) {
|
|
7
4
|
if (dirent.isDirectory())
|
|
8
5
|
return 'directory';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../src/core/path-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir, platform } from 'node:os';\nimport { basename, delimiter, dirname, join, parse, resolve, sep, win32 } from 'node:path';\n\n// ─── Shared primitives (no intra-package imports; every core module may import this) ──\n\n/** The four filesystem entry types; published as the `FileType` schema. */\nexport const ENTRY_TYPES = ['file', 'directory', 'symlink', 'other'] as const;\nexport type EntryType = (typeof ENTRY_TYPES)[number];\n\n/** The three predicates both `Dirent` and `Stats` expose - all `resolveEntryType` needs. */\nexport interface DirentLike {\n isDirectory(): boolean;\n isFile(): boolean;\n isSymbolicLink(): boolean;\n}\n\nexport function resolveEntryType(dirent: DirentLike): EntryType {\n if (dirent.isDirectory()) return 'directory';\n if (dirent.isFile()) return 'file';\n if (dirent.isSymbolicLink()) return 'symlink';\n return 'other';\n}\n\nconst warnedSettings = new Set<string>();\n\n/**\n * Warn once per (setting, value) that a configured value was rejected, and say\n * what was used instead. console.error, not Logger: this module stays\n * dependency-free to avoid import cycles, and a startup misconfiguration is\n * worth printing even when the operator asked for errors only.\n */\nexport function warnInvalidSetting(\n name: string,\n value: string,\n expected: string,\n fallback: string | number | boolean,\n): void {\n const key = `${name}:${value}`;\n if (warnedSettings.has(key)) return;\n warnedSettings.add(key);\n console.error(\n `[warning] Invalid ${name} value: ${value} (must be ${expected}). Using default: ${String(fallback)}`,\n );\n}\n\n/**\n * Parse a boolean env flag: `true`/`1` enable, `false`/`0`/empty disable.\n * Any other value warns once per var (when `name` is given) and reads as\n * false, so a typo like `FS_ALLOW_SENSITIVE=yes` is never silent.\n */\nexport function parseTrueEnvFlag(value: string | undefined, name?: string): boolean {\n if (value === undefined) return false;\n const trimmed = value.trim().toLowerCase();\n if (trimmed === 'true' || trimmed === '1') return true;\n if (name && trimmed !== '' && trimmed !== 'false' && trimmed !== '0') {\n warnInvalidSetting(name, value, '\"true\" or \"1\"', false);\n }\n return false;\n}\n\nexport function splitDirList(val: string | undefined): string[] {\n if (!val) return [];\n return val\n .split(delimiter)\n .map((p) => p.trim())\n .filter((p) => p.length > 0);\n}\n\n// ─── Character-level path primitives ─────────────────────────────────────────\n\nexport const IS_WINDOWS = process.platform === 'win32';\n\nexport const isSlash = (code: number): boolean => code === 47 || code === 92;\n\nexport const isAlpha = (code: number): boolean =>\n (code >= 65 && code <= 90) || (code >= 97 && code <= 122);\n\nexport const toPosixPath = (value: string): string => value.replaceAll('\\\\', '/');\n\n// ─── Path normalization and containment ──────────────────────────────────────\n\nconst CHAR_COLON = 58;\nconst HOMEDIR = homedir();\n\nconst RESERVED_DEVICE_NAMES = new Set<string>([\n 'CON',\n 'PRN',\n 'AUX',\n 'NUL',\n 'COM1',\n 'COM2',\n 'COM3',\n 'COM4',\n 'COM5',\n 'COM6',\n 'COM7',\n 'COM8',\n 'COM9',\n 'LPT1',\n 'LPT2',\n 'LPT3',\n 'LPT4',\n 'LPT5',\n 'LPT6',\n 'LPT7',\n 'LPT8',\n 'LPT9',\n]);\n\nfunction getReservedDeviceName(segment: string): string | undefined {\n const trimmed = segment.replace(/[. ]+$/, '');\n const withoutStream = trimmed.split(':')[0] ?? '';\n const baseName = (withoutStream.split('.')[0] ?? '').toUpperCase();\n return RESERVED_DEVICE_NAMES.has(baseName) ? baseName : undefined;\n}\n\nexport function getReservedDeviceNameForPath(requestedPath: string): string | undefined {\n const segments = requestedPath.split(/[\\\\/]/u);\n for (const segment of segments) {\n const reserved = getReservedDeviceName(segment);\n if (reserved) {\n return reserved;\n }\n }\n return undefined;\n}\n\nexport function isWindowsDriveRelativePath(requestedPath: string): boolean {\n // Check on all platforms so cross-platform clients cannot smuggle drive-relative\n // inputs (e.g. C:relative) to a POSIX-hosted server where path.resolve would\n // silently expand them relative to CWD. win32.parse is the platform-independent\n // binding, so this reads the same on a POSIX host.\n const { root } = win32.parse(requestedPath);\n return root.length === 2;\n}\n\nfunction expandHome(filepath: string): string {\n if (filepath === '~' || filepath.startsWith('~/') || filepath.startsWith('~\\\\')) {\n const rest = filepath.slice(1).replace(/^[/\\\\]+/, '');\n return rest ? join(HOMEDIR, rest) : HOMEDIR;\n }\n return filepath;\n}\n\nexport function normalizePath(p: string): string {\n const resolved = resolve(expandHome(p));\n\n // On Windows only the drive letter is lowercased (e.g. \"C:\\Foo\\Bar\").\n // The rest of the path retains its original casing.\n // IMPORTANT: callers must use isSamePath / isPathInsideDirectory for all\n // equality and containment checks — never raw string equality — because\n // those helpers apply full case-folding before comparing.\n if (IS_WINDOWS && resolved.length >= 2 && resolved.charCodeAt(1) === CHAR_COLON) {\n return resolved.charAt(0).toLowerCase() + resolved.slice(1);\n }\n\n return resolved;\n}\n\nconst IS_CASE_INSENSITIVE_FS = IS_WINDOWS || platform() === 'darwin';\n\nexport function normalizeCaseForComparison(value: string): string {\n return IS_CASE_INSENSITIVE_FS ? value.toLowerCase() : value;\n}\n\nexport function isSamePath(left: string, right: string): boolean {\n if (left === right) {\n return true;\n }\n const leftResolved = normalizeCaseForComparison(resolve(left));\n const rightResolved = normalizeCaseForComparison(resolve(right));\n return leftResolved === rightResolved;\n}\n\n/**\n * On a case-insensitive filesystem a write target that already exists is\n * realpathed to its on-disk spelling, so a case-only rename (`foo.txt` ->\n * `Foo.txt`) resolves to the source itself. Returns the resolved target\n * re-spelled with the requested basename when the two differ only by case;\n * otherwise the resolved target unchanged. Parent segments are not re-spelled.\n */\nexport function respellCaseOnlyTarget(requestedPath: string, resolvedTarget: string): string {\n const requestedName = basename(requestedPath);\n if (requestedName === basename(resolvedTarget)) return resolvedTarget;\n const respelled = join(dirname(resolvedTarget), requestedName);\n return isSamePath(respelled, resolvedTarget) ? respelled : resolvedTarget;\n}\n\nexport function normalizeAllowedDirectory(dir: string): string {\n const trimmed = dir.trim();\n if (trimmed.length === 0) return '';\n\n const normalized = normalizePath(trimmed);\n const { root } = parse(normalized);\n\n // Keep filesystem roots as-is (\"/\", \"c:\\\\\", \"\\\\\\\\server\\\\share\\\\\").\n if (isSamePath(normalized, root)) {\n return root;\n }\n\n return normalized.length > 1 && normalized.endsWith(sep) ? normalized.slice(0, -1) : normalized;\n}\n\n// `\\` separates path segments only on Windows. On POSIX it is a filename\n// character, so `/root\\x` names a sibling of `/root`, not a child.\nconst isPathSeparator = (code: number): boolean => code === 47 || (IS_WINDOWS && code === 92);\n\nexport function isPathInsideDirectory(\n normalizedDirectory: string,\n normalizedCandidate: string,\n): boolean {\n const root = normalizeCaseForComparison(normalizedDirectory);\n const candidate = normalizeCaseForComparison(normalizedCandidate);\n\n if (root === candidate) return true;\n if (!candidate.startsWith(root)) return false;\n\n if (isPathSeparator(root.charCodeAt(root.length - 1))) return true;\n return isPathSeparator(candidate.charCodeAt(root.length));\n}\n\nexport function isPathWithinDirectories(\n normalizedPath: string,\n allowedDirs: readonly string[],\n): boolean {\n return allowedDirs.some((dir) => isPathInsideDirectory(dir, normalizedPath));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../src/core/path-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import { homedir, platform } from 'node:os';\nimport { basename, delimiter, dirname, join, parse, resolve, sep, win32 } from 'node:path';\n\n// ─── Shared primitives (no intra-package imports; every core module may import this) ──\n\nexport type EntryType = 'file' | 'directory' | 'symlink' | 'other';\n\n/** The three predicates both `Dirent` and `Stats` expose - all `resolveEntryType` needs. */\nexport interface DirentLike {\n isDirectory(): boolean;\n isFile(): boolean;\n isSymbolicLink(): boolean;\n}\n\nexport function resolveEntryType(dirent: DirentLike): EntryType {\n if (dirent.isDirectory()) return 'directory';\n if (dirent.isFile()) return 'file';\n if (dirent.isSymbolicLink()) return 'symlink';\n return 'other';\n}\n\nconst warnedSettings = new Set<string>();\n\n/**\n * Warn once per (setting, value) that a configured value was rejected, and say\n * what was used instead. console.error, not Logger: this module stays\n * dependency-free to avoid import cycles, and a startup misconfiguration is\n * worth printing even when the operator asked for errors only.\n */\nexport function warnInvalidSetting(\n name: string,\n value: string,\n expected: string,\n fallback: string | number | boolean,\n): void {\n const key = `${name}:${value}`;\n if (warnedSettings.has(key)) return;\n warnedSettings.add(key);\n console.error(\n `[warning] Invalid ${name} value: ${value} (must be ${expected}). Using default: ${String(fallback)}`,\n );\n}\n\n/**\n * Parse a boolean env flag: `true`/`1` enable, `false`/`0`/empty disable.\n * Any other value warns once per var (when `name` is given) and reads as\n * false, so a typo like `FS_ALLOW_SENSITIVE=yes` is never silent.\n */\nexport function parseTrueEnvFlag(value: string | undefined, name?: string): boolean {\n if (value === undefined) return false;\n const trimmed = value.trim().toLowerCase();\n if (trimmed === 'true' || trimmed === '1') return true;\n if (name && trimmed !== '' && trimmed !== 'false' && trimmed !== '0') {\n warnInvalidSetting(name, value, '\"true\" or \"1\"', false);\n }\n return false;\n}\n\nexport function splitDirList(val: string | undefined): string[] {\n if (!val) return [];\n return val\n .split(delimiter)\n .map((p) => p.trim())\n .filter((p) => p.length > 0);\n}\n\n// ─── Character-level path primitives ─────────────────────────────────────────\n\nexport const IS_WINDOWS = process.platform === 'win32';\n\nexport const isSlash = (code: number): boolean => code === 47 || code === 92;\n\nexport const isAlpha = (code: number): boolean =>\n (code >= 65 && code <= 90) || (code >= 97 && code <= 122);\n\nexport const toPosixPath = (value: string): string => value.replaceAll('\\\\', '/');\n\n// ─── Path normalization and containment ──────────────────────────────────────\n\nconst CHAR_COLON = 58;\nconst HOMEDIR = homedir();\n\nconst RESERVED_DEVICE_NAMES = new Set<string>([\n 'CON',\n 'PRN',\n 'AUX',\n 'NUL',\n 'COM1',\n 'COM2',\n 'COM3',\n 'COM4',\n 'COM5',\n 'COM6',\n 'COM7',\n 'COM8',\n 'COM9',\n 'LPT1',\n 'LPT2',\n 'LPT3',\n 'LPT4',\n 'LPT5',\n 'LPT6',\n 'LPT7',\n 'LPT8',\n 'LPT9',\n]);\n\nfunction getReservedDeviceName(segment: string): string | undefined {\n const trimmed = segment.replace(/[. ]+$/, '');\n const withoutStream = trimmed.split(':')[0] ?? '';\n const baseName = (withoutStream.split('.')[0] ?? '').toUpperCase();\n return RESERVED_DEVICE_NAMES.has(baseName) ? baseName : undefined;\n}\n\nexport function getReservedDeviceNameForPath(requestedPath: string): string | undefined {\n const segments = requestedPath.split(/[\\\\/]/u);\n for (const segment of segments) {\n const reserved = getReservedDeviceName(segment);\n if (reserved) {\n return reserved;\n }\n }\n return undefined;\n}\n\nexport function isWindowsDriveRelativePath(requestedPath: string): boolean {\n // Check on all platforms so cross-platform clients cannot smuggle drive-relative\n // inputs (e.g. C:relative) to a POSIX-hosted server where path.resolve would\n // silently expand them relative to CWD. win32.parse is the platform-independent\n // binding, so this reads the same on a POSIX host.\n const { root } = win32.parse(requestedPath);\n return root.length === 2;\n}\n\nfunction expandHome(filepath: string): string {\n if (filepath === '~' || filepath.startsWith('~/') || filepath.startsWith('~\\\\')) {\n const rest = filepath.slice(1).replace(/^[/\\\\]+/, '');\n return rest ? join(HOMEDIR, rest) : HOMEDIR;\n }\n return filepath;\n}\n\nexport function normalizePath(p: string): string {\n const resolved = resolve(expandHome(p));\n\n // On Windows only the drive letter is lowercased (e.g. \"C:\\Foo\\Bar\").\n // The rest of the path retains its original casing.\n // IMPORTANT: callers must use isSamePath / isPathInsideDirectory for all\n // equality and containment checks — never raw string equality — because\n // those helpers apply full case-folding before comparing.\n if (IS_WINDOWS && resolved.length >= 2 && resolved.charCodeAt(1) === CHAR_COLON) {\n return resolved.charAt(0).toLowerCase() + resolved.slice(1);\n }\n\n return resolved;\n}\n\nconst IS_CASE_INSENSITIVE_FS = IS_WINDOWS || platform() === 'darwin';\n\nexport function normalizeCaseForComparison(value: string): string {\n return IS_CASE_INSENSITIVE_FS ? value.toLowerCase() : value;\n}\n\nexport function isSamePath(left: string, right: string): boolean {\n if (left === right) {\n return true;\n }\n const leftResolved = normalizeCaseForComparison(resolve(left));\n const rightResolved = normalizeCaseForComparison(resolve(right));\n return leftResolved === rightResolved;\n}\n\n/**\n * On a case-insensitive filesystem a write target that already exists is\n * realpathed to its on-disk spelling, so a case-only rename (`foo.txt` ->\n * `Foo.txt`) resolves to the source itself. Returns the resolved target\n * re-spelled with the requested basename when the two differ only by case;\n * otherwise the resolved target unchanged. Parent segments are not re-spelled.\n */\nexport function respellCaseOnlyTarget(requestedPath: string, resolvedTarget: string): string {\n const requestedName = basename(requestedPath);\n if (requestedName === basename(resolvedTarget)) return resolvedTarget;\n const respelled = join(dirname(resolvedTarget), requestedName);\n return isSamePath(respelled, resolvedTarget) ? respelled : resolvedTarget;\n}\n\nexport function normalizeAllowedDirectory(dir: string): string {\n const trimmed = dir.trim();\n if (trimmed.length === 0) return '';\n\n const normalized = normalizePath(trimmed);\n const { root } = parse(normalized);\n\n // Keep filesystem roots as-is (\"/\", \"c:\\\\\", \"\\\\\\\\server\\\\share\\\\\").\n if (isSamePath(normalized, root)) {\n return root;\n }\n\n return normalized.length > 1 && normalized.endsWith(sep) ? normalized.slice(0, -1) : normalized;\n}\n\n// `\\` separates path segments only on Windows. On POSIX it is a filename\n// character, so `/root\\x` names a sibling of `/root`, not a child.\nconst isPathSeparator = (code: number): boolean => code === 47 || (IS_WINDOWS && code === 92);\n\nexport function isPathInsideDirectory(\n normalizedDirectory: string,\n normalizedCandidate: string,\n): boolean {\n const root = normalizeCaseForComparison(normalizedDirectory);\n const candidate = normalizeCaseForComparison(normalizedCandidate);\n\n if (root === candidate) return true;\n if (!candidate.startsWith(root)) return false;\n\n if (isPathSeparator(root.charCodeAt(root.length - 1))) return true;\n return isPathSeparator(candidate.charCodeAt(root.length));\n}\n\nexport function isPathWithinDirectories(\n normalizedPath: string,\n allowedDirs: readonly string[],\n): boolean {\n return allowedDirs.some((dir) => isPathInsideDirectory(dir, normalizedPath));\n}\n"]}
|
package/dist/core/path.d.ts
CHANGED
|
@@ -61,6 +61,13 @@ export declare class PathGuard {
|
|
|
61
61
|
*/
|
|
62
62
|
private runExclusive;
|
|
63
63
|
getAllowedDirectories(): string[];
|
|
64
|
+
/**
|
|
65
|
+
* The allowed directory that contains `resolvedPath` — the longest match,
|
|
66
|
+
* so a nested root wins over its parent — or `undefined` when none does.
|
|
67
|
+
* Walks use it as the `.gitignore` ceiling: rules above an allowed root are
|
|
68
|
+
* never read.
|
|
69
|
+
*/
|
|
70
|
+
allowedRootContaining(resolvedPath: string): string | undefined;
|
|
64
71
|
/**
|
|
65
72
|
* The roots as configured or granted, one per root. getAllowedDirectories()
|
|
66
73
|
* also holds each root's realpath alias for containment, so an aliased root
|
package/dist/core/path.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/core/path.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../src/core/path.ts"],"names":[],"mappings":"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"}
|
package/dist/core/path.js
CHANGED
|
@@ -6,33 +6,52 @@ import { cli } from "./config.js";
|
|
|
6
6
|
import { classifyCauseChain, ErrorCode, FsError, isFsError, isNotFoundErrno, isSkippableErrno, rethrowIfAborted, SKIPPABLE_FS_CODES, } from "./errors.js";
|
|
7
7
|
import { Logger } from "./observability.js";
|
|
8
8
|
import { findProjectRoot, isUnsafeCwdPath, resolveConfiguredDirs } from "./path-discovery.js";
|
|
9
|
-
import { getReservedDeviceNameForPath, IS_WINDOWS, isPathWithinDirectories, isSamePath, isWindowsDriveRelativePath, normalizeAllowedDirectory, normalizePath, parseTrueEnvFlag, toPosixPath, } from "./path-utils.js";
|
|
9
|
+
import { getReservedDeviceNameForPath, IS_WINDOWS, isPathInsideDirectory, isPathWithinDirectories, isSamePath, isWindowsDriveRelativePath, normalizeAllowedDirectory, normalizePath, parseTrueEnvFlag, toPosixPath, } from "./path-utils.js";
|
|
10
10
|
import { SensitiveMatcher } from "./sensitive.js";
|
|
11
11
|
import { ROOTS_TIMEOUT_MS } from "./util.js";
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The real path of `normalizedPath`, or the one it would have if it does not
|
|
14
|
+
* exist yet: realpath of its nearest existing ancestor plus the missing suffix.
|
|
15
|
+
* Lets a missing root be checked against a realpath-canonicalized boundary even
|
|
16
|
+
* when the boundary itself is reached through a symlink. `undefined` when an
|
|
17
|
+
* ancestor exists but cannot be resolved (EACCES and friends).
|
|
18
|
+
*/
|
|
19
|
+
async function projectRealPath(normalizedPath, signal) {
|
|
20
|
+
const missing = [];
|
|
21
|
+
let current = normalizedPath;
|
|
22
|
+
for (;;) {
|
|
23
|
+
try {
|
|
24
|
+
signal?.throwIfAborted();
|
|
25
|
+
const real = await withAbort(realpath(current), signal);
|
|
26
|
+
return normalizePath(missing.length === 0 ? real : join(real, ...missing.reverse()));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
rethrowIfAborted(error);
|
|
30
|
+
if (!isNotFoundErrno(error))
|
|
31
|
+
return undefined;
|
|
32
|
+
const parent = dirname(current);
|
|
33
|
+
if (parent === current)
|
|
34
|
+
return undefined;
|
|
35
|
+
missing.push(basename(current));
|
|
36
|
+
current = parent;
|
|
23
37
|
}
|
|
24
|
-
Logger.warn('grantBoundary: realpath failed unexpectedly', {
|
|
25
|
-
root: normalizedRoot,
|
|
26
|
-
error: String(error),
|
|
27
|
-
});
|
|
28
|
-
return false;
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Every root — configured or granted — must fall under FS_ROOT_BOUNDARY. An
|
|
42
|
+
* existing root must *resolve* inside the boundary; a missing root is projected
|
|
43
|
+
* through its nearest existing ancestor before comparison so symlinked boundary
|
|
44
|
+
* spellings still compare against realpath-canonicalized bounds.
|
|
45
|
+
*/
|
|
46
|
+
async function partitionRootsWithin(roots, bounds, signal) {
|
|
32
47
|
const normalizedBounds = normalizeAllowedDirectories(bounds);
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
48
|
+
const kept = [];
|
|
49
|
+
const dropped = [];
|
|
50
|
+
for (const root of roots.map(normalizePath)) {
|
|
51
|
+
const real = await projectRealPath(root, signal);
|
|
52
|
+
(real !== undefined && isPathWithinDirectories(real, normalizedBounds) ? kept : dropped).push(root);
|
|
53
|
+
}
|
|
54
|
+
return { kept, dropped };
|
|
36
55
|
}
|
|
37
56
|
/** `path.relative` with forward slashes, so displayed paths match across platforms. */
|
|
38
57
|
export function toPosixRelative(from, to) {
|
|
@@ -141,6 +160,23 @@ export class PathGuard {
|
|
|
141
160
|
}
|
|
142
161
|
return [...this.allowedDirectoriesState];
|
|
143
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* The allowed directory that contains `resolvedPath` — the longest match,
|
|
165
|
+
* so a nested root wins over its parent — or `undefined` when none does.
|
|
166
|
+
* Walks use it as the `.gitignore` ceiling: rules above an allowed root are
|
|
167
|
+
* never read.
|
|
168
|
+
*/
|
|
169
|
+
allowedRootContaining(resolvedPath) {
|
|
170
|
+
const normalized = normalizePath(resolvedPath);
|
|
171
|
+
let best;
|
|
172
|
+
for (const dir of this.getAllowedDirectories()) {
|
|
173
|
+
if (isPathInsideDirectory(dir, normalized) &&
|
|
174
|
+
(best === undefined || dir.length > best.length)) {
|
|
175
|
+
best = dir;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return best;
|
|
179
|
+
}
|
|
144
180
|
/**
|
|
145
181
|
* The roots as configured or granted, one per root. getAllowedDirectories()
|
|
146
182
|
* also holds each root's realpath alias for containment, so an aliased root
|
|
@@ -601,19 +637,26 @@ export class PathGuard {
|
|
|
601
637
|
cwd = await findProjectRoot(cwd, [...boundaries, homedir()]);
|
|
602
638
|
}
|
|
603
639
|
if (isUnsafeCwdPath(cwd)) {
|
|
604
|
-
Logger.
|
|
640
|
+
Logger.warn(`Skipped adding unsafe current working directory to allowed list: ${cwd}`);
|
|
605
641
|
}
|
|
606
642
|
else {
|
|
607
643
|
allowCwdDirs.push(cwd);
|
|
608
644
|
}
|
|
609
645
|
}
|
|
610
|
-
const baseline = [...cliAllowedDirs, ...envAllowedDirs, ...allowCwdDirs];
|
|
611
646
|
const signal = AbortSignal.timeout(ROOTS_TIMEOUT_MS);
|
|
647
|
+
let baseline = [...cliAllowedDirs, ...envAllowedDirs, ...allowCwdDirs];
|
|
612
648
|
// FS_ROOT_BOUNDARY is the only filter grants answer to (see
|
|
613
649
|
// `grantedDirectories`); without one they pass through as accepted.
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
650
|
+
let grantsToInclude = this.grantedDirectories;
|
|
651
|
+
if (boundaries.length > 0) {
|
|
652
|
+
const { kept, dropped } = await partitionRootsWithin(baseline, boundaries, signal);
|
|
653
|
+
for (const root of dropped) {
|
|
654
|
+
Logger.warn(`Skipped allowed root outside FS_ROOT_BOUNDARY (${boundaries.join(', ')}): ${root}`);
|
|
655
|
+
}
|
|
656
|
+
baseline = kept;
|
|
657
|
+
grantsToInclude = (await partitionRootsWithin(this.grantedDirectories, boundaries, signal))
|
|
658
|
+
.kept;
|
|
659
|
+
}
|
|
617
660
|
const combined = [...baseline, ...grantsToInclude];
|
|
618
661
|
const nextState = await resolveAllowedDirectoriesState(combined, signal);
|
|
619
662
|
// Commit boundaries and the allowed set together, after every await has
|