tokenlean 0.1.0 ā 0.2.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/bin/tl-component.mjs +109 -83
- package/bin/tl-context.mjs +147 -98
- package/bin/tl-diff.mjs +95 -62
- package/bin/tl-related.mjs +112 -71
- package/bin/tl-search.mjs +142 -27
- package/bin/tl-structure.mjs +152 -83
- package/package.json +1 -1
package/bin/tl-component.mjs
CHANGED
|
@@ -21,16 +21,27 @@ if (process.argv.includes('--prompt')) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
import { readFileSync, existsSync } from 'fs';
|
|
24
|
-
import { join, relative
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
import { join, relative } from 'path';
|
|
25
|
+
import {
|
|
26
|
+
createOutput,
|
|
27
|
+
parseCommonArgs,
|
|
28
|
+
estimateTokens,
|
|
29
|
+
formatTokens,
|
|
30
|
+
COMMON_OPTIONS_HELP
|
|
31
|
+
} from '../src/output.mjs';
|
|
32
|
+
import { findProjectRoot } from '../src/project.mjs';
|
|
33
|
+
|
|
34
|
+
const HELP = `
|
|
35
|
+
tl-component - React component analyzer
|
|
36
|
+
|
|
37
|
+
Usage: tl-component <file.tsx> [options]
|
|
38
|
+
${COMMON_OPTIONS_HELP}
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
tl-component src/Button.tsx # Analyze component
|
|
42
|
+
tl-component src/App.tsx -j # JSON output
|
|
43
|
+
tl-component src/Modal.tsx -q # Quiet (minimal)
|
|
44
|
+
`;
|
|
34
45
|
|
|
35
46
|
function extractImports(content) {
|
|
36
47
|
const imports = {
|
|
@@ -175,79 +186,14 @@ function extractRedux(content) {
|
|
|
175
186
|
return redux;
|
|
176
187
|
}
|
|
177
188
|
|
|
178
|
-
function printAnalysis(analysis) {
|
|
179
|
-
const { file, lines, tokens, imports, hooks, propsInfo, components, styles, redux } = analysis;
|
|
180
|
-
|
|
181
|
-
console.log(`\nš§© Component Analysis: ${file}`);
|
|
182
|
-
console.log(` ${lines} lines, ~${tokens} tokens\n`);
|
|
183
|
-
|
|
184
|
-
// Components
|
|
185
|
-
if (components.length > 0) {
|
|
186
|
-
console.log(`š¦ Components: ${components.join(', ')}`);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Props
|
|
190
|
-
if (propsInfo) {
|
|
191
|
-
console.log(`\nš ${propsInfo.name}:`);
|
|
192
|
-
for (const p of propsInfo.props) {
|
|
193
|
-
const opt = p.optional ? '?' : '';
|
|
194
|
-
console.log(` ${p.name}${opt}: ${p.type}`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Hooks
|
|
199
|
-
if (hooks.length > 0) {
|
|
200
|
-
console.log(`\nšŖ Hooks: ${hooks.join(', ')}`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Redux
|
|
204
|
-
if (redux.dispatch || redux.selectors.length > 0) {
|
|
205
|
-
console.log(`\nš¦ Redux:`);
|
|
206
|
-
if (redux.selectors.length > 0) {
|
|
207
|
-
console.log(` Selectors: ${redux.selectors.join(', ')}`);
|
|
208
|
-
}
|
|
209
|
-
if (redux.actions.length > 0) {
|
|
210
|
-
console.log(` Actions: ${redux.actions.join(', ')}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Imports summary
|
|
215
|
-
console.log(`\nš„ Imports:`);
|
|
216
|
-
if (imports.react.length > 0) {
|
|
217
|
-
console.log(` React: ${imports.react.join(', ')}`);
|
|
218
|
-
}
|
|
219
|
-
if (imports.reactNative.length > 0) {
|
|
220
|
-
console.log(` React Native: ${imports.reactNative.join(', ')}`);
|
|
221
|
-
}
|
|
222
|
-
if (imports.internal.length > 0) {
|
|
223
|
-
console.log(` Internal: ${imports.internal.length} modules`);
|
|
224
|
-
for (const i of imports.internal.slice(0, 5)) {
|
|
225
|
-
console.log(` ${i.source}`);
|
|
226
|
-
}
|
|
227
|
-
if (imports.internal.length > 5) {
|
|
228
|
-
console.log(` ... and ${imports.internal.length - 5} more`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
if (imports.external.length > 0) {
|
|
232
|
-
console.log(` External: ${imports.external.map(i => i.source).join(', ')}`);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Styles
|
|
236
|
-
if (styles.length > 0) {
|
|
237
|
-
console.log(`\nšØ Styling: ${styles.join(', ')}`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
console.log();
|
|
241
|
-
}
|
|
242
|
-
|
|
243
189
|
// Main
|
|
244
190
|
const args = process.argv.slice(2);
|
|
245
|
-
const
|
|
191
|
+
const options = parseCommonArgs(args);
|
|
192
|
+
const targetFile = options.remaining.find(a => !a.startsWith('-'));
|
|
246
193
|
|
|
247
|
-
if (!targetFile) {
|
|
248
|
-
console.log(
|
|
249
|
-
|
|
250
|
-
process.exit(1);
|
|
194
|
+
if (options.help || !targetFile) {
|
|
195
|
+
console.log(HELP);
|
|
196
|
+
process.exit(options.help ? 0 : 1);
|
|
251
197
|
}
|
|
252
198
|
|
|
253
199
|
const fullPath = targetFile.startsWith('/') ? targetFile : join(process.cwd(), targetFile);
|
|
@@ -258,11 +204,12 @@ if (!existsSync(fullPath)) {
|
|
|
258
204
|
|
|
259
205
|
const content = readFileSync(fullPath, 'utf-8');
|
|
260
206
|
const projectRoot = findProjectRoot();
|
|
207
|
+
const relPath = relative(projectRoot, fullPath);
|
|
261
208
|
|
|
262
209
|
const analysis = {
|
|
263
|
-
file:
|
|
210
|
+
file: relPath,
|
|
264
211
|
lines: content.split('\n').length,
|
|
265
|
-
tokens:
|
|
212
|
+
tokens: estimateTokens(content),
|
|
266
213
|
imports: extractImports(content),
|
|
267
214
|
hooks: extractHooks(content),
|
|
268
215
|
propsInfo: extractProps(content),
|
|
@@ -271,4 +218,83 @@ const analysis = {
|
|
|
271
218
|
redux: extractRedux(content)
|
|
272
219
|
};
|
|
273
220
|
|
|
274
|
-
|
|
221
|
+
const out = createOutput(options);
|
|
222
|
+
|
|
223
|
+
// Set JSON data
|
|
224
|
+
out.setData('file', analysis.file);
|
|
225
|
+
out.setData('lines', analysis.lines);
|
|
226
|
+
out.setData('tokens', analysis.tokens);
|
|
227
|
+
out.setData('components', analysis.components);
|
|
228
|
+
out.setData('props', analysis.propsInfo);
|
|
229
|
+
out.setData('hooks', analysis.hooks);
|
|
230
|
+
out.setData('imports', analysis.imports);
|
|
231
|
+
out.setData('styles', analysis.styles);
|
|
232
|
+
out.setData('redux', analysis.redux);
|
|
233
|
+
|
|
234
|
+
// Headers
|
|
235
|
+
out.header(`Component Analysis: ${analysis.file}`);
|
|
236
|
+
out.header(`${analysis.lines} lines, ~${formatTokens(analysis.tokens)} tokens`);
|
|
237
|
+
out.blank();
|
|
238
|
+
|
|
239
|
+
// Components
|
|
240
|
+
if (analysis.components.length > 0) {
|
|
241
|
+
out.add(`Components: ${analysis.components.join(', ')}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Props
|
|
245
|
+
if (analysis.propsInfo) {
|
|
246
|
+
out.blank();
|
|
247
|
+
out.add(`${analysis.propsInfo.name}:`);
|
|
248
|
+
for (const p of analysis.propsInfo.props) {
|
|
249
|
+
const opt = p.optional ? '?' : '';
|
|
250
|
+
out.add(` ${p.name}${opt}: ${p.type}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Hooks
|
|
255
|
+
if (analysis.hooks.length > 0) {
|
|
256
|
+
out.blank();
|
|
257
|
+
out.add(`Hooks: ${analysis.hooks.join(', ')}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Redux
|
|
261
|
+
if (analysis.redux.dispatch || analysis.redux.selectors.length > 0) {
|
|
262
|
+
out.blank();
|
|
263
|
+
out.add('Redux:');
|
|
264
|
+
if (analysis.redux.selectors.length > 0) {
|
|
265
|
+
out.add(` Selectors: ${analysis.redux.selectors.join(', ')}`);
|
|
266
|
+
}
|
|
267
|
+
if (analysis.redux.actions.length > 0) {
|
|
268
|
+
out.add(` Actions: ${analysis.redux.actions.join(', ')}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Imports summary
|
|
273
|
+
out.blank();
|
|
274
|
+
out.add('Imports:');
|
|
275
|
+
if (analysis.imports.react.length > 0) {
|
|
276
|
+
out.add(` React: ${analysis.imports.react.join(', ')}`);
|
|
277
|
+
}
|
|
278
|
+
if (analysis.imports.reactNative.length > 0) {
|
|
279
|
+
out.add(` React Native: ${analysis.imports.reactNative.join(', ')}`);
|
|
280
|
+
}
|
|
281
|
+
if (analysis.imports.internal.length > 0) {
|
|
282
|
+
out.add(` Internal: ${analysis.imports.internal.length} modules`);
|
|
283
|
+
for (const i of analysis.imports.internal.slice(0, 5)) {
|
|
284
|
+
out.add(` ${i.source}`);
|
|
285
|
+
}
|
|
286
|
+
if (analysis.imports.internal.length > 5) {
|
|
287
|
+
out.add(` ... and ${analysis.imports.internal.length - 5} more`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (analysis.imports.external.length > 0) {
|
|
291
|
+
out.add(` External: ${analysis.imports.external.map(i => i.source).join(', ')}`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Styles
|
|
295
|
+
if (analysis.styles.length > 0) {
|
|
296
|
+
out.blank();
|
|
297
|
+
out.add(`Styling: ${analysis.styles.join(', ')}`);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
out.print();
|
package/bin/tl-context.mjs
CHANGED
|
@@ -20,137 +20,186 @@ if (process.argv.includes('--prompt')) {
|
|
|
20
20
|
|
|
21
21
|
import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
22
22
|
import { join, relative } from 'path';
|
|
23
|
+
import {
|
|
24
|
+
createOutput,
|
|
25
|
+
parseCommonArgs,
|
|
26
|
+
estimateTokens,
|
|
27
|
+
formatTokens,
|
|
28
|
+
formatTable,
|
|
29
|
+
COMMON_OPTIONS_HELP
|
|
30
|
+
} from '../src/output.mjs';
|
|
31
|
+
import {
|
|
32
|
+
findProjectRoot,
|
|
33
|
+
shouldSkip,
|
|
34
|
+
getSkipDirs,
|
|
35
|
+
getImportantDirs
|
|
36
|
+
} from '../src/project.mjs';
|
|
37
|
+
|
|
38
|
+
const HELP = `
|
|
39
|
+
tl-context - Estimate context token usage for files/directories
|
|
40
|
+
|
|
41
|
+
Usage: tl-context [path] [options]
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
--top N, -n N Show top N files (default: 20, use --all for all)
|
|
45
|
+
--all Show all files
|
|
46
|
+
${COMMON_OPTIONS_HELP}
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
tl-context src/ # Estimate tokens for src directory
|
|
50
|
+
tl-context src/ --top 10 # Show top 10 largest files
|
|
51
|
+
tl-context src/ --all # Show all files
|
|
52
|
+
tl-context package.json # Single file estimate
|
|
53
|
+
tl-context -j # JSON output for scripting
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
function analyzeDir(dirPath, results = [], skipDirs, importantDirs) {
|
|
57
|
+
try {
|
|
58
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
59
|
+
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (entry.name.startsWith('.') && !importantDirs.has(entry.name)) continue;
|
|
62
|
+
if (shouldSkip(entry.name, entry.isDirectory())) continue;
|
|
63
|
+
|
|
64
|
+
const fullPath = join(dirPath, entry.name);
|
|
65
|
+
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
analyzeDir(fullPath, results, skipDirs, importantDirs);
|
|
68
|
+
} else {
|
|
69
|
+
try {
|
|
70
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
71
|
+
const tokens = estimateTokens(content);
|
|
72
|
+
results.push({ path: fullPath, tokens, lines: content.split('\n').length });
|
|
73
|
+
} catch {
|
|
74
|
+
// Skip binary or unreadable files
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// Permission error
|
|
80
|
+
}
|
|
23
81
|
|
|
24
|
-
|
|
25
|
-
'node_modules', '.git', 'android', 'ios', 'dist', 'build',
|
|
26
|
-
'.expo', '.next', 'coverage', '__pycache__', '.cache'
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
const SKIP_EXTENSIONS = new Set([
|
|
30
|
-
'.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.webp',
|
|
31
|
-
'.woff', '.woff2', '.ttf', '.eot',
|
|
32
|
-
'.mp3', '.mp4', '.wav', '.ogg',
|
|
33
|
-
'.zip', '.tar', '.gz',
|
|
34
|
-
'.lock', '.log'
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
// Rough token estimate: ~4 chars per token for code
|
|
38
|
-
function estimateTokens(content) {
|
|
39
|
-
return Math.ceil(content.length / 4);
|
|
82
|
+
return results;
|
|
40
83
|
}
|
|
41
84
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return String(tokens);
|
|
46
|
-
}
|
|
85
|
+
// Main
|
|
86
|
+
const args = process.argv.slice(2);
|
|
87
|
+
const options = parseCommonArgs(args);
|
|
47
88
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
89
|
+
// Parse tool-specific options
|
|
90
|
+
let topN = 20;
|
|
91
|
+
let targetPath = '.';
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < options.remaining.length; i++) {
|
|
94
|
+
const arg = options.remaining[i];
|
|
95
|
+
if ((arg === '--top' || arg === '-n') && options.remaining[i + 1]) {
|
|
96
|
+
topN = parseInt(options.remaining[i + 1], 10);
|
|
97
|
+
i++;
|
|
98
|
+
} else if (arg === '--all') {
|
|
99
|
+
topN = null;
|
|
100
|
+
} else if (!arg.startsWith('-')) {
|
|
101
|
+
targetPath = arg;
|
|
53
102
|
}
|
|
54
|
-
return false;
|
|
55
103
|
}
|
|
56
104
|
|
|
57
|
-
|
|
58
|
-
|
|
105
|
+
if (options.help) {
|
|
106
|
+
console.log(HELP);
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
59
109
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
if (!existsSync(targetPath)) {
|
|
111
|
+
console.error(`Path not found: ${targetPath}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
63
114
|
|
|
64
|
-
|
|
115
|
+
const projectRoot = findProjectRoot();
|
|
116
|
+
const skipDirs = getSkipDirs();
|
|
117
|
+
const importantDirs = getImportantDirs();
|
|
118
|
+
const out = createOutput(options);
|
|
65
119
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
results.push({ path: fullPath, tokens, lines: content.split('\n').length });
|
|
73
|
-
} catch (e) {
|
|
74
|
-
// Skip binary or unreadable files
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
120
|
+
const stat = statSync(targetPath);
|
|
121
|
+
if (stat.isFile()) {
|
|
122
|
+
// Single file
|
|
123
|
+
const content = readFileSync(targetPath, 'utf-8');
|
|
124
|
+
const tokens = estimateTokens(content);
|
|
125
|
+
const lines = content.split('\n').length;
|
|
78
126
|
|
|
79
|
-
|
|
80
|
-
|
|
127
|
+
out.setData('file', targetPath);
|
|
128
|
+
out.setData('tokens', tokens);
|
|
129
|
+
out.setData('lines', lines);
|
|
130
|
+
|
|
131
|
+
out.header(`${targetPath}: ~${formatTokens(tokens)} tokens (${lines} lines)`);
|
|
132
|
+
out.print();
|
|
133
|
+
} else {
|
|
134
|
+
// Directory
|
|
135
|
+
const results = analyzeDir(targetPath, [], skipDirs, importantDirs);
|
|
81
136
|
|
|
82
|
-
function printResults(results, rootPath, topN) {
|
|
83
137
|
// Sort by tokens descending
|
|
84
138
|
results.sort((a, b) => b.tokens - a.tokens);
|
|
85
139
|
|
|
86
140
|
const total = results.reduce((sum, r) => sum + r.tokens, 0);
|
|
141
|
+
const totalLines = results.reduce((sum, r) => sum + r.lines, 0);
|
|
87
142
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
results = results.slice(0, topN);
|
|
94
|
-
}
|
|
143
|
+
// Set JSON data
|
|
144
|
+
out.setData('path', targetPath);
|
|
145
|
+
out.setData('totalTokens', total);
|
|
146
|
+
out.setData('totalLines', totalLines);
|
|
147
|
+
out.setData('fileCount', results.length);
|
|
95
148
|
|
|
96
|
-
|
|
149
|
+
// Header
|
|
150
|
+
out.header(`Context Estimate: ${targetPath}`);
|
|
151
|
+
out.header(`Total: ~${formatTokens(total)} tokens across ${results.length} files`);
|
|
152
|
+
out.blank();
|
|
97
153
|
|
|
98
|
-
|
|
99
|
-
|
|
154
|
+
// File list
|
|
155
|
+
const displayResults = topN ? results.slice(0, topN) : results;
|
|
100
156
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
157
|
+
if (displayResults.length > 0) {
|
|
158
|
+
if (topN) {
|
|
159
|
+
out.header(`Top ${Math.min(topN, results.length)} largest files:`);
|
|
160
|
+
}
|
|
161
|
+
out.blank();
|
|
162
|
+
|
|
163
|
+
// Format as table
|
|
164
|
+
const rows = displayResults.map(r => {
|
|
165
|
+
const relPath = relative(targetPath, r.path);
|
|
166
|
+
const truncPath = relPath.length > 60 ? '...' + relPath.slice(-57) : relPath;
|
|
167
|
+
return [formatTokens(r.tokens), r.lines, truncPath];
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const tableLines = formatTable(rows, { indent: ' ', separator: ' ' });
|
|
171
|
+
out.add(' Tokens Lines Path');
|
|
172
|
+
out.add(' ' + '-'.repeat(70));
|
|
173
|
+
out.addLines(tableLines);
|
|
105
174
|
}
|
|
106
175
|
|
|
107
|
-
console.log();
|
|
108
|
-
|
|
109
176
|
// Group by directory
|
|
110
177
|
const byDir = {};
|
|
111
178
|
for (const r of results) {
|
|
112
|
-
const rel = relative(
|
|
179
|
+
const rel = relative(targetPath, r.path);
|
|
113
180
|
const dir = rel.includes('/') ? rel.split('/')[0] : '.';
|
|
114
181
|
byDir[dir] = (byDir[dir] || 0) + r.tokens;
|
|
115
182
|
}
|
|
116
183
|
|
|
117
184
|
const sortedDirs = Object.entries(byDir).sort((a, b) => b[1] - a[1]);
|
|
118
185
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
console.log(` ${formatTokens(tokens).padStart(6)} ${pct.padStart(5)}% ${dir}/`);
|
|
123
|
-
}
|
|
124
|
-
console.log();
|
|
125
|
-
}
|
|
186
|
+
out.blank();
|
|
187
|
+
out.header('By top-level directory:');
|
|
188
|
+
out.blank();
|
|
126
189
|
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
190
|
+
const dirRows = sortedDirs.slice(0, 10).map(([dir, tokens]) => {
|
|
191
|
+
const pct = ((tokens / total) * 100).toFixed(1) + '%';
|
|
192
|
+
return [formatTokens(tokens), pct, dir + '/'];
|
|
193
|
+
});
|
|
131
194
|
|
|
132
|
-
|
|
133
|
-
if (args[i] === '--top' && args[i + 1]) {
|
|
134
|
-
topN = parseInt(args[i + 1], 10);
|
|
135
|
-
i++;
|
|
136
|
-
} else if (args[i] === '--all') {
|
|
137
|
-
topN = null;
|
|
138
|
-
} else if (!args[i].startsWith('-')) {
|
|
139
|
-
targetPath = args[i];
|
|
140
|
-
}
|
|
141
|
-
}
|
|
195
|
+
out.addLines(formatTable(dirRows, { indent: ' ', separator: ' ' }));
|
|
142
196
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
197
|
+
out.setData('byDirectory', Object.fromEntries(sortedDirs));
|
|
198
|
+
out.setData('files', results.slice(0, 100).map(r => ({
|
|
199
|
+
path: relative(targetPath, r.path),
|
|
200
|
+
tokens: r.tokens,
|
|
201
|
+
lines: r.lines
|
|
202
|
+
})));
|
|
147
203
|
|
|
148
|
-
|
|
149
|
-
if (stat.isFile()) {
|
|
150
|
-
const content = readFileSync(targetPath, 'utf-8');
|
|
151
|
-
const tokens = estimateTokens(content);
|
|
152
|
-
console.log(`\n${targetPath}: ~${formatTokens(tokens)} tokens (${content.split('\n').length} lines)\n`);
|
|
153
|
-
} else {
|
|
154
|
-
const results = analyzeDir(targetPath);
|
|
155
|
-
printResults(results, targetPath, topN);
|
|
204
|
+
out.print();
|
|
156
205
|
}
|