@rog0x/mcp-file-tools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # @rog0x/mcp-file-tools
2
+
3
+ MCP server providing file and directory analysis tools for AI agents. Gives Claude (and other MCP-compatible AI) the ability to explore, analyze, and search file systems.
4
+
5
+ ## Tools
6
+
7
+ | Tool | Description |
8
+ |------|-------------|
9
+ | `dir_tree` | Generate a visual directory tree structure with configurable depth and ignore patterns |
10
+ | `file_stats` | Analyze a directory: total files, size breakdown by extension, largest/newest/oldest files |
11
+ | `duplicate_finder` | Find duplicate files by size + MD5 hash, report wasted space |
12
+ | `code_counter` | Count lines of code by language: code, comments, and blank lines (like cloc/scc) |
13
+ | `file_search` | Search files by name glob, content regex, size range, and date range |
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ git clone https://github.com/rog0x/mcp-file-tools.git
19
+ cd mcp-file-tools
20
+ npm install
21
+ npm run build
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ ### Claude Code
27
+
28
+ ```bash
29
+ claude mcp add mcp-file-tools node /absolute/path/to/mcp-file-tools/dist/index.js
30
+ ```
31
+
32
+ ### Claude Desktop
33
+
34
+ Add to your `claude_desktop_config.json`:
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "mcp-file-tools": {
40
+ "command": "node",
41
+ "args": ["/absolute/path/to/mcp-file-tools/dist/index.js"]
42
+ }
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Tool Details
48
+
49
+ ### dir_tree
50
+
51
+ Generate a visual directory tree like the `tree` command.
52
+
53
+ ```
54
+ Parameters:
55
+ dir_path (required) - Absolute path to the directory
56
+ max_depth (optional) - Max depth to traverse (default: 5, 0 = unlimited)
57
+ ignore_patterns (optional) - Patterns to ignore (default: node_modules, .git, dist, etc.)
58
+ ```
59
+
60
+ ### file_stats
61
+
62
+ Get a comprehensive analysis of a directory's contents.
63
+
64
+ ```
65
+ Parameters:
66
+ dir_path (required) - Absolute path to the directory
67
+ max_depth (optional) - Max depth to traverse (default: 10)
68
+ ```
69
+
70
+ Returns: total files, total size, file counts by extension, top 10 largest files, top 10 newest/oldest files.
71
+
72
+ ### duplicate_finder
73
+
74
+ Find duplicate files using size pre-filtering and MD5 hashing.
75
+
76
+ ```
77
+ Parameters:
78
+ dir_path (required) - Absolute path to the directory
79
+ min_size (optional) - Minimum file size in bytes (default: 1)
80
+ max_depth (optional) - Max depth to traverse (default: 10)
81
+ ```
82
+
83
+ Returns: duplicate groups with file paths, sizes, and total wasted space.
84
+
85
+ ### code_counter
86
+
87
+ Count lines of code across 20+ languages with comment detection.
88
+
89
+ ```
90
+ Parameters:
91
+ dir_path (required) - Absolute path to the directory
92
+ max_depth (optional) - Max depth to traverse (default: 10)
93
+ ```
94
+
95
+ Supported languages: TypeScript, JavaScript, Python, Java, C/C++, C#, Go, Rust, Ruby, PHP, Swift, Kotlin, HTML, CSS, JSON, YAML, Markdown, Shell, SQL, Lua.
96
+
97
+ ### file_search
98
+
99
+ Search for files using multiple criteria simultaneously.
100
+
101
+ ```
102
+ Parameters:
103
+ dir_path (required) - Absolute path to the directory
104
+ name_pattern (optional) - Glob pattern (e.g. "**/*.ts")
105
+ content_pattern (optional) - Regex to search file contents
106
+ min_size (optional) - Minimum file size in bytes
107
+ max_size (optional) - Maximum file size in bytes
108
+ modified_after (optional) - ISO date string
109
+ modified_before (optional) - ISO date string
110
+ max_results (optional) - Max results (default: 100)
111
+ ```
112
+
113
+ ## License
114
+
115
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const dir_tree_js_1 = require("./tools/dir-tree.js");
8
+ const file_stats_js_1 = require("./tools/file-stats.js");
9
+ const duplicate_finder_js_1 = require("./tools/duplicate-finder.js");
10
+ const code_counter_js_1 = require("./tools/code-counter.js");
11
+ const file_search_js_1 = require("./tools/file-search.js");
12
+ const server = new index_js_1.Server({
13
+ name: "mcp-file-tools",
14
+ version: "1.0.0",
15
+ }, {
16
+ capabilities: {
17
+ tools: {},
18
+ },
19
+ });
20
+ // List available tools
21
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
22
+ tools: [
23
+ {
24
+ name: "dir_tree",
25
+ description: "Generate a visual directory tree structure (like the `tree` command). Shows files and folders with configurable depth and ignore patterns.",
26
+ inputSchema: {
27
+ type: "object",
28
+ properties: {
29
+ dir_path: { type: "string", description: "Absolute path to the directory" },
30
+ max_depth: {
31
+ type: "number",
32
+ description: "Maximum depth to traverse (default: 5, 0 = unlimited)",
33
+ },
34
+ ignore_patterns: {
35
+ type: "array",
36
+ items: { type: "string" },
37
+ description: "Patterns to ignore (default: node_modules, .git, dist, __pycache__, .next, .cache, coverage)",
38
+ },
39
+ },
40
+ required: ["dir_path"],
41
+ },
42
+ },
43
+ {
44
+ name: "file_stats",
45
+ description: "Analyze a directory: total files, total size, file count by extension, largest files, newest/oldest files.",
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ dir_path: { type: "string", description: "Absolute path to the directory" },
50
+ max_depth: {
51
+ type: "number",
52
+ description: "Maximum depth to traverse (default: 10, 0 = unlimited)",
53
+ },
54
+ },
55
+ required: ["dir_path"],
56
+ },
57
+ },
58
+ {
59
+ name: "duplicate_finder",
60
+ description: "Find duplicate files by comparing size and MD5 hash. Returns groups of duplicates with paths and wasted space.",
61
+ inputSchema: {
62
+ type: "object",
63
+ properties: {
64
+ dir_path: { type: "string", description: "Absolute path to the directory" },
65
+ min_size: {
66
+ type: "number",
67
+ description: "Minimum file size in bytes to consider (default: 1)",
68
+ },
69
+ max_depth: {
70
+ type: "number",
71
+ description: "Maximum depth to traverse (default: 10)",
72
+ },
73
+ },
74
+ required: ["dir_path"],
75
+ },
76
+ },
77
+ {
78
+ name: "code_counter",
79
+ description: "Count lines of code by programming language: total lines, code lines, comment lines, blank lines (like cloc/scc).",
80
+ inputSchema: {
81
+ type: "object",
82
+ properties: {
83
+ dir_path: { type: "string", description: "Absolute path to the directory" },
84
+ max_depth: {
85
+ type: "number",
86
+ description: "Maximum depth to traverse (default: 10)",
87
+ },
88
+ },
89
+ required: ["dir_path"],
90
+ },
91
+ },
92
+ {
93
+ name: "file_search",
94
+ description: "Search for files by name pattern (glob), content (regex), size range, and/or date range.",
95
+ inputSchema: {
96
+ type: "object",
97
+ properties: {
98
+ dir_path: { type: "string", description: "Absolute path to the directory to search" },
99
+ name_pattern: {
100
+ type: "string",
101
+ description: 'Glob pattern for file names (e.g. "**/*.ts", "*.json")',
102
+ },
103
+ content_pattern: {
104
+ type: "string",
105
+ description: "Regex pattern to search within file contents",
106
+ },
107
+ min_size: {
108
+ type: "number",
109
+ description: "Minimum file size in bytes",
110
+ },
111
+ max_size: {
112
+ type: "number",
113
+ description: "Maximum file size in bytes",
114
+ },
115
+ modified_after: {
116
+ type: "string",
117
+ description: "ISO date string - only files modified after this date",
118
+ },
119
+ modified_before: {
120
+ type: "string",
121
+ description: "ISO date string - only files modified before this date",
122
+ },
123
+ max_results: {
124
+ type: "number",
125
+ description: "Maximum number of results to return (default: 100)",
126
+ },
127
+ },
128
+ required: ["dir_path"],
129
+ },
130
+ },
131
+ ],
132
+ }));
133
+ // Handle tool calls
134
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
135
+ const { name, arguments: args } = request.params;
136
+ try {
137
+ switch (name) {
138
+ case "dir_tree": {
139
+ const result = await (0, dir_tree_js_1.dirTree)({
140
+ dirPath: args?.dir_path,
141
+ maxDepth: args?.max_depth,
142
+ ignorePatterns: args?.ignore_patterns,
143
+ });
144
+ return {
145
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
146
+ };
147
+ }
148
+ case "file_stats": {
149
+ const result = await (0, file_stats_js_1.fileStats)({
150
+ dirPath: args?.dir_path,
151
+ maxDepth: args?.max_depth,
152
+ });
153
+ return {
154
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
155
+ };
156
+ }
157
+ case "duplicate_finder": {
158
+ const result = await (0, duplicate_finder_js_1.duplicateFinder)({
159
+ dirPath: args?.dir_path,
160
+ minSize: args?.min_size,
161
+ maxDepth: args?.max_depth,
162
+ });
163
+ return {
164
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
165
+ };
166
+ }
167
+ case "code_counter": {
168
+ const result = await (0, code_counter_js_1.codeCounter)({
169
+ dirPath: args?.dir_path,
170
+ maxDepth: args?.max_depth,
171
+ });
172
+ return {
173
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
174
+ };
175
+ }
176
+ case "file_search": {
177
+ const result = await (0, file_search_js_1.fileSearch)({
178
+ dirPath: args?.dir_path,
179
+ namePattern: args?.name_pattern,
180
+ contentPattern: args?.content_pattern,
181
+ minSize: args?.min_size,
182
+ maxSize: args?.max_size,
183
+ modifiedAfter: args?.modified_after,
184
+ modifiedBefore: args?.modified_before,
185
+ maxResults: args?.max_results,
186
+ });
187
+ return {
188
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
189
+ };
190
+ }
191
+ default:
192
+ throw new Error(`Unknown tool: ${name}`);
193
+ }
194
+ }
195
+ catch (error) {
196
+ const message = error instanceof Error ? error.message : String(error);
197
+ return {
198
+ content: [{ type: "text", text: `Error: ${message}` }],
199
+ isError: true,
200
+ };
201
+ }
202
+ });
203
+ // Start server
204
+ async function main() {
205
+ const transport = new stdio_js_1.StdioServerTransport();
206
+ await server.connect(transport);
207
+ console.error("MCP File Tools server running on stdio");
208
+ }
209
+ main().catch(console.error);
@@ -0,0 +1,22 @@
1
+ interface CodeCounterOptions {
2
+ dirPath: string;
3
+ maxDepth?: number;
4
+ }
5
+ interface LanguageStats {
6
+ files: number;
7
+ totalLines: number;
8
+ codeLines: number;
9
+ commentLines: number;
10
+ blankLines: number;
11
+ }
12
+ interface CodeCounterResult {
13
+ directory: string;
14
+ totalFiles: number;
15
+ totalLines: number;
16
+ totalCodeLines: number;
17
+ totalCommentLines: number;
18
+ totalBlankLines: number;
19
+ languages: Record<string, LanguageStats>;
20
+ }
21
+ export declare function codeCounter(options: CodeCounterOptions): Promise<CodeCounterResult>;
22
+ export {};
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.codeCounter = codeCounter;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const LANGUAGES = [
40
+ { name: "TypeScript", extensions: [".ts", ".tsx"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
41
+ { name: "JavaScript", extensions: [".js", ".jsx", ".mjs", ".cjs"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
42
+ { name: "Python", extensions: [".py", ".pyw"], singleLineComment: ["#"], blockCommentStart: '"""', blockCommentEnd: '"""' },
43
+ { name: "Java", extensions: [".java"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
44
+ { name: "C/C++", extensions: [".c", ".cpp", ".cc", ".h", ".hpp"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
45
+ { name: "C#", extensions: [".cs"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
46
+ { name: "Go", extensions: [".go"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
47
+ { name: "Rust", extensions: [".rs"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
48
+ { name: "Ruby", extensions: [".rb"], singleLineComment: ["#"], blockCommentStart: "=begin", blockCommentEnd: "=end" },
49
+ { name: "PHP", extensions: [".php"], singleLineComment: ["//", "#"], blockCommentStart: "/*", blockCommentEnd: "*/" },
50
+ { name: "Swift", extensions: [".swift"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
51
+ { name: "Kotlin", extensions: [".kt", ".kts"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
52
+ { name: "HTML", extensions: [".html", ".htm"], singleLineComment: [], blockCommentStart: "<!--", blockCommentEnd: "-->" },
53
+ { name: "CSS", extensions: [".css", ".scss", ".sass", ".less"], singleLineComment: ["//"], blockCommentStart: "/*", blockCommentEnd: "*/" },
54
+ { name: "JSON", extensions: [".json"], singleLineComment: [], },
55
+ { name: "YAML", extensions: [".yml", ".yaml"], singleLineComment: ["#"] },
56
+ { name: "Markdown", extensions: [".md", ".mdx"], singleLineComment: [] },
57
+ { name: "Shell", extensions: [".sh", ".bash", ".zsh"], singleLineComment: ["#"] },
58
+ { name: "SQL", extensions: [".sql"], singleLineComment: ["--"], blockCommentStart: "/*", blockCommentEnd: "*/" },
59
+ { name: "Lua", extensions: [".lua"], singleLineComment: ["--"], blockCommentStart: "--[[", blockCommentEnd: "]]" },
60
+ ];
61
+ const EXT_TO_LANG = {};
62
+ for (const lang of LANGUAGES) {
63
+ for (const ext of lang.extensions) {
64
+ EXT_TO_LANG[ext] = lang;
65
+ }
66
+ }
67
+ function countLines(filePath, lang) {
68
+ let content;
69
+ try {
70
+ content = fs.readFileSync(filePath, "utf-8");
71
+ }
72
+ catch {
73
+ return { totalLines: 0, codeLines: 0, commentLines: 0, blankLines: 0 };
74
+ }
75
+ const lines = content.split(/\r?\n/);
76
+ let blankLines = 0;
77
+ let commentLines = 0;
78
+ let codeLines = 0;
79
+ let inBlockComment = false;
80
+ for (const line of lines) {
81
+ const trimmed = line.trim();
82
+ if (trimmed === "") {
83
+ blankLines++;
84
+ continue;
85
+ }
86
+ if (inBlockComment) {
87
+ commentLines++;
88
+ if (lang.blockCommentEnd && trimmed.includes(lang.blockCommentEnd)) {
89
+ inBlockComment = false;
90
+ }
91
+ continue;
92
+ }
93
+ if (lang.blockCommentStart && trimmed.startsWith(lang.blockCommentStart)) {
94
+ commentLines++;
95
+ if (!lang.blockCommentEnd || !trimmed.includes(lang.blockCommentEnd, lang.blockCommentStart.length)) {
96
+ inBlockComment = true;
97
+ }
98
+ continue;
99
+ }
100
+ const isSingleComment = lang.singleLineComment.some((prefix) => trimmed.startsWith(prefix));
101
+ if (isSingleComment) {
102
+ commentLines++;
103
+ }
104
+ else {
105
+ codeLines++;
106
+ }
107
+ }
108
+ return {
109
+ totalLines: lines.length,
110
+ codeLines,
111
+ commentLines,
112
+ blankLines,
113
+ };
114
+ }
115
+ function collectFiles(dirPath, depth, maxDepth, files) {
116
+ if (maxDepth > 0 && depth >= maxDepth)
117
+ return;
118
+ let entries;
119
+ try {
120
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
121
+ }
122
+ catch {
123
+ return;
124
+ }
125
+ for (const entry of entries) {
126
+ const fullPath = path.join(dirPath, entry.name);
127
+ if (entry.isDirectory()) {
128
+ if (["node_modules", ".git", "dist", "__pycache__", ".next", "coverage", "build"].includes(entry.name))
129
+ continue;
130
+ collectFiles(fullPath, depth + 1, maxDepth, files);
131
+ }
132
+ else if (entry.isFile()) {
133
+ const ext = path.extname(entry.name).toLowerCase();
134
+ if (EXT_TO_LANG[ext]) {
135
+ files.push(fullPath);
136
+ }
137
+ }
138
+ }
139
+ }
140
+ async function codeCounter(options) {
141
+ const { dirPath, maxDepth = 10 } = options;
142
+ const resolvedPath = path.resolve(dirPath);
143
+ if (!fs.existsSync(resolvedPath)) {
144
+ throw new Error(`Directory not found: ${resolvedPath}`);
145
+ }
146
+ const files = [];
147
+ collectFiles(resolvedPath, 0, maxDepth, files);
148
+ const langStats = {};
149
+ let totalLines = 0;
150
+ let totalCodeLines = 0;
151
+ let totalCommentLines = 0;
152
+ let totalBlankLines = 0;
153
+ for (const filePath of files) {
154
+ const ext = path.extname(filePath).toLowerCase();
155
+ const lang = EXT_TO_LANG[ext];
156
+ if (!lang)
157
+ continue;
158
+ const counts = countLines(filePath, lang);
159
+ if (!langStats[lang.name]) {
160
+ langStats[lang.name] = { files: 0, totalLines: 0, codeLines: 0, commentLines: 0, blankLines: 0 };
161
+ }
162
+ langStats[lang.name].files++;
163
+ langStats[lang.name].totalLines += counts.totalLines;
164
+ langStats[lang.name].codeLines += counts.codeLines;
165
+ langStats[lang.name].commentLines += counts.commentLines;
166
+ langStats[lang.name].blankLines += counts.blankLines;
167
+ totalLines += counts.totalLines;
168
+ totalCodeLines += counts.codeLines;
169
+ totalCommentLines += counts.commentLines;
170
+ totalBlankLines += counts.blankLines;
171
+ }
172
+ // Sort languages by code lines descending
173
+ const sortedLangs = {};
174
+ const sorted = Object.entries(langStats).sort((a, b) => b[1].codeLines - a[1].codeLines);
175
+ for (const [name, stats] of sorted) {
176
+ sortedLangs[name] = stats;
177
+ }
178
+ return {
179
+ directory: resolvedPath,
180
+ totalFiles: files.length,
181
+ totalLines,
182
+ totalCodeLines,
183
+ totalCommentLines,
184
+ totalBlankLines,
185
+ languages: sortedLangs,
186
+ };
187
+ }
@@ -0,0 +1,12 @@
1
+ interface DirTreeOptions {
2
+ dirPath: string;
3
+ maxDepth?: number;
4
+ ignorePatterns?: string[];
5
+ }
6
+ interface TreeResult {
7
+ tree: string;
8
+ totalDirs: number;
9
+ totalFiles: number;
10
+ }
11
+ export declare function dirTree(options: DirTreeOptions): Promise<TreeResult>;
12
+ export {};
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.dirTree = dirTree;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const DEFAULT_IGNORE = ["node_modules", ".git", "dist", "__pycache__", ".next", ".cache", "coverage"];
40
+ function shouldIgnore(name, ignorePatterns) {
41
+ return ignorePatterns.some((pattern) => {
42
+ if (pattern.includes("*")) {
43
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
44
+ return regex.test(name);
45
+ }
46
+ return name === pattern;
47
+ });
48
+ }
49
+ function buildTree(dirPath, prefix, depth, maxDepth, ignorePatterns, stats) {
50
+ if (maxDepth > 0 && depth >= maxDepth) {
51
+ return [];
52
+ }
53
+ let entries;
54
+ try {
55
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
56
+ }
57
+ catch {
58
+ return [`${prefix}[permission denied]`];
59
+ }
60
+ entries = entries.filter((e) => !shouldIgnore(e.name, ignorePatterns));
61
+ entries.sort((a, b) => {
62
+ if (a.isDirectory() && !b.isDirectory())
63
+ return -1;
64
+ if (!a.isDirectory() && b.isDirectory())
65
+ return 1;
66
+ return a.name.localeCompare(b.name);
67
+ });
68
+ const lines = [];
69
+ for (let i = 0; i < entries.length; i++) {
70
+ const entry = entries[i];
71
+ const isLast = i === entries.length - 1;
72
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
73
+ const childPrefix = isLast ? " " : "\u2502 ";
74
+ if (entry.isDirectory()) {
75
+ stats.dirs++;
76
+ lines.push(`${prefix}${connector}${entry.name}/`);
77
+ const childLines = buildTree(path.join(dirPath, entry.name), prefix + childPrefix, depth + 1, maxDepth, ignorePatterns, stats);
78
+ lines.push(...childLines);
79
+ }
80
+ else {
81
+ stats.files++;
82
+ lines.push(`${prefix}${connector}${entry.name}`);
83
+ }
84
+ }
85
+ return lines;
86
+ }
87
+ async function dirTree(options) {
88
+ const { dirPath, maxDepth = 5, ignorePatterns = DEFAULT_IGNORE } = options;
89
+ const resolvedPath = path.resolve(dirPath);
90
+ if (!fs.existsSync(resolvedPath)) {
91
+ throw new Error(`Directory not found: ${resolvedPath}`);
92
+ }
93
+ const stat = fs.statSync(resolvedPath);
94
+ if (!stat.isDirectory()) {
95
+ throw new Error(`Not a directory: ${resolvedPath}`);
96
+ }
97
+ const stats = { dirs: 0, files: 0 };
98
+ const rootName = path.basename(resolvedPath);
99
+ const lines = [`${rootName}/`];
100
+ const childLines = buildTree(resolvedPath, "", 0, maxDepth, ignorePatterns, stats);
101
+ lines.push(...childLines);
102
+ return {
103
+ tree: lines.join("\n"),
104
+ totalDirs: stats.dirs,
105
+ totalFiles: stats.files,
106
+ };
107
+ }
@@ -0,0 +1,22 @@
1
+ interface DuplicateFinderOptions {
2
+ dirPath: string;
3
+ minSize?: number;
4
+ maxDepth?: number;
5
+ }
6
+ interface DuplicateGroup {
7
+ hash: string;
8
+ size: string;
9
+ sizeBytes: number;
10
+ files: string[];
11
+ }
12
+ interface DuplicateResult {
13
+ directory: string;
14
+ totalFilesScanned: number;
15
+ duplicateGroups: number;
16
+ totalDuplicateFiles: number;
17
+ wastedSpace: string;
18
+ wastedSpaceBytes: number;
19
+ groups: DuplicateGroup[];
20
+ }
21
+ export declare function duplicateFinder(options: DuplicateFinderOptions): Promise<DuplicateResult>;
22
+ export {};