tokenlean 0.1.0 ā 0.3.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 +44 -1
- package/bin/tl-cache.mjs +168 -0
- package/bin/tl-component.mjs +109 -83
- package/bin/tl-context.mjs +147 -98
- package/bin/tl-diff.mjs +95 -62
- package/bin/tl-entry.mjs +7 -1
- package/bin/tl-impact.mjs +10 -2
- package/bin/tl-related.mjs +122 -75
- package/bin/tl-search.mjs +148 -27
- package/bin/tl-structure.mjs +152 -83
- package/bin/tl-todo.mjs +8 -1
- package/bin/tl-unused.mjs +29 -19
- package/package.json +2 -1
- package/src/cache.mjs +493 -0
- package/src/config.mjs +6 -0
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ the API surface.
|
|
|
18
18
|
|
|
19
19
|
## The Solution
|
|
20
20
|
|
|
21
|
-
tokenlean provides **
|
|
21
|
+
tokenlean provides **26 specialized CLI tools** that give you (or your AI agent) exactly the information needed - no
|
|
22
22
|
more, no less. Each tool is designed to answer a specific question about your codebase with minimal token overhead.
|
|
23
23
|
|
|
24
24
|
Instead of reading a 500-line file to understand its exports, run `tl-exports` (~50 tokens). Instead of reading all your
|
|
@@ -128,6 +128,7 @@ Search and discover code patterns.
|
|
|
128
128
|
|
|
129
129
|
| Tool | Description | Example |
|
|
130
130
|
|-------------|--------------------------------|-----------------------|
|
|
131
|
+
| `tl-cache` | Manage ripgrep result cache | `tl-cache stats` |
|
|
131
132
|
| `tl-config` | Show/manage configuration | `tl-config --init` |
|
|
132
133
|
| `tl-prompt` | Generate AI agent instructions | `tl-prompt --minimal` |
|
|
133
134
|
|
|
@@ -190,6 +191,48 @@ Project config overrides global config. Both are optional.
|
|
|
190
191
|
|
|
191
192
|
Config values extend built-in defaults (they don't replace them).
|
|
192
193
|
|
|
194
|
+
## Caching
|
|
195
|
+
|
|
196
|
+
tokenlean caches expensive ripgrep operations to speed up repeated searches. The cache uses **git-based invalidation** - it automatically invalidates when you make commits or modify files.
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
tl-cache stats # View cache statistics
|
|
200
|
+
tl-cache clear # Clear cache for current project
|
|
201
|
+
tl-cache clear-all # Clear all cached data
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Cache is stored in `~/.tokenlean/cache/` and is enabled by default.
|
|
205
|
+
|
|
206
|
+
### Cache Configuration
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"cache": {
|
|
211
|
+
"enabled": true,
|
|
212
|
+
"ttl": 300,
|
|
213
|
+
"maxSize": "100MB",
|
|
214
|
+
"location": null
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Option | Default | Description |
|
|
220
|
+
|------------|--------------------|--------------------------------------------|
|
|
221
|
+
| `enabled` | `true` | Enable/disable caching |
|
|
222
|
+
| `ttl` | `300` | Max age in seconds (fallback for non-git) |
|
|
223
|
+
| `maxSize` | `"100MB"` | Max cache size per project |
|
|
224
|
+
| `location` | `null` | Override default `~/.tokenlean/cache` |
|
|
225
|
+
|
|
226
|
+
### Disable Caching
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Disable for a single command
|
|
230
|
+
TOKENLEAN_CACHE=0 tl-search hooks
|
|
231
|
+
|
|
232
|
+
# Disable in config
|
|
233
|
+
echo '{"cache":{"enabled":false}}' > .tokenleanrc.json
|
|
234
|
+
```
|
|
235
|
+
|
|
193
236
|
## Example Workflows
|
|
194
237
|
|
|
195
238
|
### Starting work on an unfamiliar codebase
|
package/bin/tl-cache.mjs
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* tl-cache - Manage tokenlean cache
|
|
5
|
+
*
|
|
6
|
+
* View cache statistics and clear cached data.
|
|
7
|
+
*
|
|
8
|
+
* Usage: tl-cache [command]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Prompt info for tl-prompt
|
|
12
|
+
if (process.argv.includes('--prompt')) {
|
|
13
|
+
console.log(JSON.stringify({
|
|
14
|
+
name: 'tl-cache',
|
|
15
|
+
desc: 'Manage tokenlean cache (stats, clear)',
|
|
16
|
+
when: 'maintenance',
|
|
17
|
+
example: 'tl-cache stats'
|
|
18
|
+
}));
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
createOutput,
|
|
24
|
+
parseCommonArgs,
|
|
25
|
+
COMMON_OPTIONS_HELP
|
|
26
|
+
} from '../src/output.mjs';
|
|
27
|
+
import { findProjectRoot } from '../src/project.mjs';
|
|
28
|
+
import {
|
|
29
|
+
getCacheConfig,
|
|
30
|
+
getCacheStats,
|
|
31
|
+
clearCache,
|
|
32
|
+
getCacheDir
|
|
33
|
+
} from '../src/cache.mjs';
|
|
34
|
+
|
|
35
|
+
const HELP = `
|
|
36
|
+
tl-cache - Manage tokenlean cache
|
|
37
|
+
|
|
38
|
+
Usage: tl-cache <command> [options]
|
|
39
|
+
|
|
40
|
+
Commands:
|
|
41
|
+
stats Show cache statistics (default)
|
|
42
|
+
clear Clear cache for current project
|
|
43
|
+
clear-all Clear cache for all projects
|
|
44
|
+
|
|
45
|
+
${COMMON_OPTIONS_HELP}
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
tl-cache # Show stats for current project
|
|
49
|
+
tl-cache stats # Same as above
|
|
50
|
+
tl-cache clear # Clear cache for this project
|
|
51
|
+
tl-cache clear-all # Clear all cached data
|
|
52
|
+
|
|
53
|
+
Configuration:
|
|
54
|
+
Cache can be configured in .tokenleanrc.json:
|
|
55
|
+
{
|
|
56
|
+
"cache": {
|
|
57
|
+
"enabled": true, // Enable/disable caching
|
|
58
|
+
"ttl": 300, // Max age in seconds (for non-git repos)
|
|
59
|
+
"maxSize": "100MB", // Max cache size per project
|
|
60
|
+
"location": null // Override ~/.tokenlean/cache
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Environment:
|
|
65
|
+
TOKENLEAN_CACHE=0 Disable caching for this run
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
69
|
+
// Commands
|
|
70
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
71
|
+
|
|
72
|
+
function showStats(out, projectRoot) {
|
|
73
|
+
const config = getCacheConfig();
|
|
74
|
+
const projectStats = getCacheStats(projectRoot);
|
|
75
|
+
const globalStats = getCacheStats(null);
|
|
76
|
+
|
|
77
|
+
out.header('Cache Configuration:');
|
|
78
|
+
out.add(` Enabled: ${config.enabled ? 'yes' : 'no'}`);
|
|
79
|
+
out.add(` Location: ${config.location}`);
|
|
80
|
+
out.add(` Max size: ${projectStats.maxSizeFormatted}`);
|
|
81
|
+
out.add(` TTL: ${config.ttl}s (fallback for non-git repos)`);
|
|
82
|
+
out.blank();
|
|
83
|
+
|
|
84
|
+
out.header('Current Project Cache:');
|
|
85
|
+
out.add(` Directory: ${projectStats.location}`);
|
|
86
|
+
out.add(` Entries: ${projectStats.entries}`);
|
|
87
|
+
out.add(` Size: ${projectStats.sizeFormatted}`);
|
|
88
|
+
out.blank();
|
|
89
|
+
|
|
90
|
+
out.header('Global Cache:');
|
|
91
|
+
out.add(` Projects: ${globalStats.projects}`);
|
|
92
|
+
out.add(` Entries: ${globalStats.totalEntries}`);
|
|
93
|
+
out.add(` Total size: ${globalStats.totalSizeFormatted}`);
|
|
94
|
+
out.blank();
|
|
95
|
+
|
|
96
|
+
// Set JSON data
|
|
97
|
+
out.setData('config', config);
|
|
98
|
+
out.setData('project', projectStats);
|
|
99
|
+
out.setData('global', globalStats);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function doClear(out, projectRoot) {
|
|
103
|
+
const before = getCacheStats(projectRoot);
|
|
104
|
+
clearCache(projectRoot);
|
|
105
|
+
const after = getCacheStats(projectRoot);
|
|
106
|
+
|
|
107
|
+
out.add(`Cleared ${before.entries} cache entries (${before.sizeFormatted})`);
|
|
108
|
+
out.blank();
|
|
109
|
+
|
|
110
|
+
out.setData('cleared', {
|
|
111
|
+
entries: before.entries,
|
|
112
|
+
size: before.size,
|
|
113
|
+
sizeFormatted: before.sizeFormatted
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function doClearAll(out) {
|
|
118
|
+
const before = getCacheStats(null);
|
|
119
|
+
clearCache(null);
|
|
120
|
+
const after = getCacheStats(null);
|
|
121
|
+
|
|
122
|
+
out.add(`Cleared ${before.totalEntries} cache entries across ${before.projects} projects (${before.totalSizeFormatted})`);
|
|
123
|
+
out.blank();
|
|
124
|
+
|
|
125
|
+
out.setData('cleared', {
|
|
126
|
+
projects: before.projects,
|
|
127
|
+
entries: before.totalEntries,
|
|
128
|
+
size: before.totalSize,
|
|
129
|
+
sizeFormatted: before.totalSizeFormatted
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
134
|
+
// Main
|
|
135
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
136
|
+
|
|
137
|
+
const args = process.argv.slice(2);
|
|
138
|
+
const options = parseCommonArgs(args);
|
|
139
|
+
|
|
140
|
+
if (options.help) {
|
|
141
|
+
console.log(HELP);
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const command = options.remaining.find(a => !a.startsWith('-')) || 'stats';
|
|
146
|
+
const projectRoot = findProjectRoot();
|
|
147
|
+
const out = createOutput(options);
|
|
148
|
+
|
|
149
|
+
switch (command) {
|
|
150
|
+
case 'stats':
|
|
151
|
+
showStats(out, projectRoot);
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
case 'clear':
|
|
155
|
+
doClear(out, projectRoot);
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case 'clear-all':
|
|
159
|
+
doClearAll(out);
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
default:
|
|
163
|
+
console.error(`Unknown command: ${command}`);
|
|
164
|
+
console.log('Use: stats, clear, or clear-all');
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
out.print();
|
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();
|