tri-cli 0.0.2 → 0.0.3

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,51 @@
1
+ # tri-cli
2
+
3
+ A terminal-based interactive directory visualizer, like `tree` but with interactivity and treemaps:
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g tri-cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ tri [directory] [options]
15
+ ```
16
+
17
+ ### Examples
18
+
19
+ ```bash
20
+ tri . # visualize current directory
21
+ tri ../bionemo -L 2 # show 2 levels deep
22
+ tri --dir src # specify directory with flag
23
+ ```
24
+
25
+ ### Interactive Controls
26
+
27
+ | Key | Action |
28
+ | ----- | ------------------------------ |
29
+ | ↑ / ↓ | Navigate files and folders |
30
+ | Enter | Expand or collapse a directory |
31
+ | t | Toggle Treemap view |
32
+ | Esc | Clear typed shortcut |
33
+ | q | Quit |
34
+
35
+ ### Options
36
+
37
+ | Option | Description |
38
+ | -------------- | ----------------------------------------------- |
39
+ | `--dir <path>` | Directory to visualize (optional if positional) |
40
+ | `-L <level>` | Depth level to expand initially (default: 1) |
41
+ | `-h, --help` | Show help message |
42
+
43
+ ---
44
+
45
+ **Example:**
46
+
47
+ ```bash
48
+ tri ../bionemo -L 2
49
+ ```
50
+
51
+ Type `t` to open (and close) the treemap view, navigate with arrow keys, and press `q` to quit.
package/dist/cli.js CHANGED
@@ -8,9 +8,10 @@ import { TreeMapView } from './Treemap.js';
8
8
  const oneHunter = {
9
9
  bg: '#282c34',
10
10
  bgAlt: '#21252b',
11
- text: '#abb2bf',
11
+ text: 'rgb(206, 205, 195)',
12
+ text2: 'rgb(135, 133, 128)',
12
13
  textAlt: 'white',
13
- red: '#e06c75',
14
+ red: 'rgb(209, 77, 65)',
14
15
  orange: 'rgb(218, 112, 44)',
15
16
  yellow: '#e5c07b',
16
17
  green: '#98c379',
@@ -20,7 +21,7 @@ const oneHunter = {
20
21
  magenta: 'rgb(206, 93, 151)'
21
22
  };
22
23
 
23
- // Function to format bytes to human readable
24
+ // --- Helpers ---
24
25
  const formatBytes = bytes => {
25
26
  if (bytes === 0) return '0 B';
26
27
  const k = 1024;
@@ -28,8 +29,6 @@ const formatBytes = bytes => {
28
29
  const i = Math.floor(Math.log(bytes) / Math.log(k));
29
30
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
30
31
  };
31
-
32
- // Function to get directory size recursively
33
32
  const getDirSize = dirPath => {
34
33
  let size = 0;
35
34
  try {
@@ -49,13 +48,13 @@ const getDirSize = dirPath => {
49
48
  return size;
50
49
  };
51
50
 
52
- // Function to read directory structure (unbounded depth)
53
- const readDirTree = dirPath => {
51
+ // Updated: allow skipping size computation
52
+ const readDirTree = (dirPath, noSize = false) => {
54
53
  try {
55
54
  const stats = fs.statSync(dirPath);
56
55
  const name = path.basename(dirPath) || dirPath;
57
56
  if (!stats.isDirectory()) {
58
- const size = stats.size;
57
+ const size = noSize ? 0 : stats.size;
59
58
  return {
60
59
  name,
61
60
  path: dirPath,
@@ -64,21 +63,21 @@ const readDirTree = dirPath => {
64
63
  value: size > 0 ? size : 100
65
64
  };
66
65
  }
67
- const children = fs.readdirSync(dirPath).filter(file => !file.startsWith('.')).map(file => readDirTree(path.join(dirPath, file))).filter(Boolean);
66
+ const children = fs.readdirSync(dirPath).filter(file => !file.startsWith('.')).map(file => readDirTree(path.join(dirPath, file), noSize)).filter(Boolean);
68
67
  return {
69
68
  name,
70
69
  path: dirPath,
71
70
  isFile: false,
72
- size: getDirSize(dirPath),
71
+ size: noSize ? 0 : getDirSize(dirPath),
73
72
  children: children.length > 0 ? children : undefined
74
73
  };
75
74
  } catch {
76
75
  return null;
77
76
  }
78
77
  };
79
-
80
- // Get color based on file extension
81
78
  const getFileColor = filename => {
79
+ if (filename === 'VERSION') return oneHunter.purple;
80
+ if (filename === 'Dockerfile') return oneHunter.blue;
82
81
  const ext = path.extname(filename).toLowerCase();
83
82
  const colorMap = {
84
83
  '.js': oneHunter.yellow,
@@ -90,7 +89,7 @@ const getFileColor = filename => {
90
89
  '.json': oneHunter.purple,
91
90
  '.html': oneHunter.cyan,
92
91
  '.css': oneHunter.cyan,
93
- '.md': oneHunter.text,
92
+ '.md': oneHunter.orange,
94
93
  '.txt': oneHunter.textAlt,
95
94
  '.sh': oneHunter.green,
96
95
  '.yml': oneHunter.purple,
@@ -100,12 +99,11 @@ const getFileColor = filename => {
100
99
  '.png': oneHunter.blue,
101
100
  '.jpg': oneHunter.blue,
102
101
  '.gif': oneHunter.blue,
103
- '.pdf': oneHunter.red
102
+ '.pdf': oneHunter.red,
103
+ '.toml': oneHunter.text2
104
104
  };
105
105
  return colorMap[ext] || oneHunter.text;
106
106
  };
107
-
108
- // Generate shortcut label
109
107
  const generateShortcut = (index, isFile, parentShortcut = '') => {
110
108
  const letters = 'abcdefghijklmnoprsuvwxyz'; // omitting q, t
111
109
  const base = letters.length;
@@ -119,12 +117,13 @@ const generateShortcut = (index, isFile, parentShortcut = '') => {
119
117
  return parentShortcut + label;
120
118
  };
121
119
 
122
- // TreeRow
120
+ // --- Components ---
123
121
  const TreeRow = /*#__PURE__*/React.memo(({
124
122
  node,
125
123
  isSelected,
126
124
  collapsed,
127
- shortcutInput
125
+ shortcutInput,
126
+ noSize
128
127
  }) => {
129
128
  const isCollapsed = collapsed.has(node.data.path);
130
129
  const hasChildren = node.children && node.children.length > 0;
@@ -175,13 +174,14 @@ const TreeRow = /*#__PURE__*/React.memo(({
175
174
  backgroundColor: isSelected ? oneHunter.green : undefined
176
175
  }, node.data.name), hasChildren && !isCollapsed && /*#__PURE__*/React.createElement(Text, {
177
176
  color: oneHunter.textAlt
178
- }, " (", node.children.length, ")"), node.data.size > 0 && /*#__PURE__*/React.createElement(Text, {
179
- color: oneHunter.green
177
+ }, " (", node.children.length, ")"), !noSize && node.data.size > 0 && /*#__PURE__*/React.createElement(Text, {
178
+ color: oneHunter.cyan
180
179
  }, " ", formatBytes(node.data.size)), renderShortcut()));
181
- }, (p, n) => p.isSelected === n.isSelected && p.collapsed === n.collapsed && p.shortcutInput === n.shortcutInput);
180
+ }, (p, n) => p.isSelected === n.isSelected && p.collapsed === n.collapsed && p.shortcutInput === n.shortcutInput && p.noSize === n.noSize);
182
181
  const TreeVisualization = ({
183
182
  dirPath = '.',
184
- maxLevel = 1
183
+ maxLevel = 1,
184
+ noSize = false
185
185
  }) => {
186
186
  const [selectedIndex, setSelectedIndex] = useState(0);
187
187
  const [collapsed, setCollapsed] = useState(new Set());
@@ -193,13 +193,11 @@ const TreeVisualization = ({
193
193
  } = useStdout();
194
194
  const terminalHeight = stdout?.rows || 24;
195
195
  const viewportHeight = Math.max(5, terminalHeight - 7);
196
- const treeData = useMemo(() => readDirTree(dirPath), [dirPath]);
196
+ const treeData = useMemo(() => readDirTree(dirPath, noSize), [dirPath, noSize]);
197
197
  if (!treeData) return /*#__PURE__*/React.createElement(Text, {
198
198
  color: "red"
199
199
  }, "Error: Could not read directory");
200
200
  const root = useMemo(() => hierarchy(treeData), [treeData]);
201
-
202
- // --- Initial collapse based on -L ---
203
201
  useEffect(() => {
204
202
  const newCollapsed = new Set();
205
203
  const applyLevel = (node, level = 0) => {
@@ -212,8 +210,6 @@ const TreeVisualization = ({
212
210
  setCollapsed(newCollapsed);
213
211
  setInitialized(true);
214
212
  }, [maxLevel, root]);
215
-
216
- // Assign shortcuts
217
213
  const rootWithShortcuts = useMemo(() => {
218
214
  const assignShortcuts = (node, parentShortcut = '', siblingIndex = 0) => {
219
215
  const isFile = node.data.isFile;
@@ -226,8 +222,6 @@ const TreeVisualization = ({
226
222
  assignShortcuts(cloned);
227
223
  return cloned;
228
224
  }, [root]);
229
-
230
- // Flatten visible nodes
231
225
  const visibleNodes = useMemo(() => {
232
226
  const nodes = [];
233
227
  const traverse = node => {
@@ -247,7 +241,7 @@ const TreeVisualization = ({
247
241
  useInput((input, key) => {
248
242
  if (input === 'q') process.exit(0);
249
243
  if (input === 't') {
250
- setShowTreeMap(p => !p);
244
+ if (!noSize) setShowTreeMap(p => !p);
251
245
  return;
252
246
  }
253
247
  if (showTreeMap) return;
@@ -260,14 +254,10 @@ const TreeVisualization = ({
260
254
  setShortcutInput('');
261
255
  return;
262
256
  }
263
-
264
- // ✅ ENTER behavior
265
257
  if (key.return) {
266
258
  if (shortcutInput && shortcutMap.has(shortcutInput)) {
267
259
  const shortcutIndex = shortcutMap.get(shortcutInput);
268
260
  const shortcutNode = visibleNodes[shortcutIndex];
269
-
270
- // Jump and toggle collapse (in one go)
271
261
  setSelectedIndex(shortcutIndex);
272
262
  if (shortcutNode.children) {
273
263
  setCollapsed(prev => {
@@ -279,8 +269,6 @@ const TreeVisualization = ({
279
269
  setShortcutInput('');
280
270
  return;
281
271
  }
282
-
283
- // Otherwise toggle current node
284
272
  const node = visibleNodes[selectedIndex];
285
273
  if (node.children) {
286
274
  setCollapsed(prev => {
@@ -296,13 +284,9 @@ const TreeVisualization = ({
296
284
  setShortcutInput('');
297
285
  return;
298
286
  }
299
-
300
- // Typing a shortcut
301
287
  if (input && /^[a-z0-9]$/.test(input)) {
302
288
  const newInput = shortcutInput + input;
303
289
  setShortcutInput(newInput);
304
-
305
- // Move highlight immediately
306
290
  if (shortcutMap.has(newInput)) {
307
291
  setSelectedIndex(shortcutMap.get(newInput));
308
292
  }
@@ -318,8 +302,6 @@ const TreeVisualization = ({
318
302
  stdout: stdout
319
303
  });
320
304
  }
321
-
322
- // --- Viewport logic ---
323
305
  const getViewportWindow = () => {
324
306
  let start = Math.max(0, selectedIndex - Math.floor(viewportHeight / 2));
325
307
  let end = start + viewportHeight;
@@ -340,17 +322,21 @@ const TreeVisualization = ({
340
322
  if (!initialized) return null;
341
323
  return /*#__PURE__*/React.createElement(Box, {
342
324
  flexDirection: "column"
343
- }, /*#__PURE__*/React.createElement(Text, {
325
+ }, !noSize ? /*#__PURE__*/React.createElement(Text, {
344
326
  color: oneHunter.textAlt,
345
327
  dimColor: true
346
- }, "\u2191/\u2193: Navigate | Enter: Toggle | t: TreeMap | Esc: Clear | q: Quit"), start > 0 && /*#__PURE__*/React.createElement(Text, {
328
+ }, "\u2191/\u2193: Navigate | Enter: Toggle | t: TreeMap | Esc: Clear | q: Quit") : /*#__PURE__*/React.createElement(Text, {
329
+ color: oneHunter.textAlt,
330
+ dimColor: true
331
+ }, "\u2191/\u2193: Navigate | Enter: Toggle | Esc: Clear | q: Quit"), start > 0 && /*#__PURE__*/React.createElement(Text, {
347
332
  color: oneHunter.yellow
348
333
  }, "\u22EE (", start, " more above)"), viewportNodes.map((node, i) => /*#__PURE__*/React.createElement(TreeRow, {
349
334
  key: start + i,
350
335
  node: node,
351
336
  isSelected: start + i === selectedIndex,
352
337
  collapsed: collapsed,
353
- shortcutInput: shortcutInput
338
+ shortcutInput: shortcutInput,
339
+ noSize: noSize
354
340
  })), end < visibleNodes.length && /*#__PURE__*/React.createElement(Text, {
355
341
  color: oneHunter.yellow
356
342
  }, "\u22EE (", visibleNodes.length - end, " more below)"));
@@ -359,22 +345,33 @@ const TreeVisualization = ({
359
345
  // --- CLI args ---
360
346
  const args = process.argv.slice(2);
361
347
 
362
- // Dir flag
348
+ // Help
349
+ if (args.includes('--help') || args.includes('-h')) {
350
+ console.log(`
351
+ Usage:
352
+ tri [directory] [options]
353
+
354
+ Examples:
355
+ tri . # visualize current directory
356
+ tri ../bionemo -L 2 # show 2 levels deep
357
+ tri --dir src # specify directory with flag
358
+
359
+ Options:
360
+ --dir <path> Directory to visualize (optional if positional)
361
+ -L <level> Depth level to expand initially (default: 1)
362
+ --no-size, -ns Skip file size calculation (faster, disables treemap)
363
+ -h, --help Show this help message
364
+ `);
365
+ process.exit(0);
366
+ }
363
367
  const dirIndex = args.indexOf('--dir');
364
- let dirPath;
365
368
  const firstNonFlagArg = args.find(arg => !arg.startsWith('-'));
366
- if (dirIndex !== -1 && args[dirIndex + 1]) {
367
- dirPath = args[dirIndex + 1];
368
- } else if (firstNonFlagArg) {
369
- dirPath = firstNonFlagArg;
370
- } else {
371
- dirPath = '.';
372
- }
373
-
374
- // Handle -L flag
369
+ const dirPath = dirIndex !== -1 && args[dirIndex + 1] ? args[dirIndex + 1] : firstNonFlagArg || '.';
375
370
  const levelIndex = args.indexOf('-L');
376
371
  const maxLevel = levelIndex !== -1 && args[levelIndex + 1] ? parseInt(args[levelIndex + 1], 10) : 1;
372
+ const noSize = args.includes('--no-size') || args.includes('-ns');
377
373
  render(/*#__PURE__*/React.createElement(TreeVisualization, {
378
374
  dirPath: dirPath,
379
- maxLevel: maxLevel
375
+ maxLevel: maxLevel,
376
+ noSize: noSize
380
377
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tri-cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Interactive CLI directory tree visualizer with treemap view",
5
5
  "author": "Jared Wilber",
6
6
  "license": "MIT",
package/readme.md DELETED
@@ -1,25 +0,0 @@
1
- # tri
2
-
3
- > This readme is automatically generated by [create-ink-app](https://github.com/vadimdemedes/create-ink-app)
4
-
5
- ## Install
6
-
7
- ```bash
8
- $ npm install --global tri
9
- ```
10
-
11
- ## CLI
12
-
13
- ```
14
- $ tri --help
15
-
16
- Usage
17
- $ tri
18
-
19
- Options
20
- --name Your name
21
-
22
- Examples
23
- $ tri --name=Jane
24
- Hello, Jane
25
- ```