@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 +115 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +209 -0
- package/dist/tools/code-counter.d.ts +22 -0
- package/dist/tools/code-counter.js +187 -0
- package/dist/tools/dir-tree.d.ts +12 -0
- package/dist/tools/dir-tree.js +107 -0
- package/dist/tools/duplicate-finder.d.ts +22 -0
- package/dist/tools/duplicate-finder.js +143 -0
- package/dist/tools/file-search.d.ts +36 -0
- package/dist/tools/file-search.js +146 -0
- package/dist/tools/file-stats.d.ts +25 -0
- package/dist/tools/file-stats.js +128 -0
- package/package.json +37 -0
- package/src/index.ts +230 -0
- package/src/tools/code-counter.ts +208 -0
- package/src/tools/dir-tree.ts +108 -0
- package/src/tools/duplicate-finder.ts +145 -0
- package/src/tools/file-search.ts +157 -0
- package/src/tools/file-stats.ts +128 -0
- package/tsconfig.json +14 -0
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
|
package/dist/index.d.ts
ADDED
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 {};
|