tri-cli 0.0.51 → 0.0.53

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/dist/Treemap.js CHANGED
@@ -20,6 +20,7 @@ const oneHunter = {
20
20
 
21
21
  // Expanded color palette for directories
22
22
  const dirColors = [oneHunter.blue, oneHunter.cyan, oneHunter.green, oneHunter.yellow, oneHunter.orange, oneHunter.red, oneHunter.purple, oneHunter.magenta, '#4a9eff', '#7ec699', '#e8b339', '#ff7eb6'];
23
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
23
24
 
24
25
  // Function to format bytes to human readable
25
26
  const formatBytes = bytes => {
@@ -30,9 +31,18 @@ const formatBytes = bytes => {
30
31
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
31
32
  };
32
33
 
34
+ // Function to format counts
35
+ const formatCount = count => {
36
+ if (count === 0) return '0';
37
+ if (count < 1000) return count.toString();
38
+ if (count < 1000000) return (count / 1000).toFixed(1) + 'K';
39
+ return (count / 1000000).toFixed(1) + 'M';
40
+ };
41
+
33
42
  // Get color based on file extension
34
43
  const getFileColor = filename => {
35
- const ext = filename.match(/\.[^.]+$/)?.[0]?.toLowerCase();
44
+ var _filename$match;
45
+ const ext = (_filename$match = filename.match(/\.[^.]+$/)) === null || _filename$match === void 0 || (_filename$match = _filename$match[0]) === null || _filename$match === void 0 ? void 0 : _filename$match.toLowerCase();
36
46
  const colorMap = {
37
47
  '.js': oneHunter.yellow,
38
48
  '.jsx': oneHunter.yellow,
@@ -98,8 +108,19 @@ const assignGroupColors = root => {
98
108
  };
99
109
  export const TreeMapView = ({
100
110
  selectedNode,
101
- stdout
111
+ stdout,
112
+ mode = 'size',
113
+ loadingPaths = new Set(),
114
+ calculatedCounts = new Map(),
115
+ calculatedSizes = new Map()
102
116
  }) => {
117
+ const [spinnerFrame, setSpinnerFrame] = React.useState(0);
118
+
119
+ // Spinner animation loop
120
+ React.useEffect(() => {
121
+ const interval = setInterval(() => setSpinnerFrame(f => (f + 1) % SPINNER_FRAMES.length), 80);
122
+ return () => clearInterval(interval);
123
+ }, []);
103
124
  if (!selectedNode || !selectedNode.data) {
104
125
  return /*#__PURE__*/React.createElement(Box, {
105
126
  flexDirection: "column"
@@ -109,16 +130,20 @@ export const TreeMapView = ({
109
130
  }, "Error: Invalid node selected"), /*#__PURE__*/React.createElement(Text, {
110
131
  dimColor: true,
111
132
  color: oneHunter.textAlt
112
- }, "Press 't' to go back to tree view"));
133
+ }, "Press any key to go back to tree view"));
113
134
  }
114
- const width = Math.min(stdout?.columns || 80, 120) - 4;
115
- const height = Math.min(stdout?.rows || 24, 40) - 8;
135
+ const width = Math.min((stdout === null || stdout === void 0 ? void 0 : stdout.columns) || 80, 120) - 4;
136
+ const height = Math.min((stdout === null || stdout === void 0 ? void 0 : stdout.rows) || 24, 40) - 8;
137
+
138
+ // Choose formatter based on mode
139
+ const formatValue = mode === 'count' ? formatCount : formatBytes;
140
+ const modeLabel = mode === 'count' ? 'Count' : 'Size';
116
141
 
117
142
  // If it's a file, show just that file as a big rectangle
118
143
  if (selectedNode.data.isFile) {
119
144
  const color = getFileColor(selectedNode.data.name);
120
145
  const name = selectedNode.data.name;
121
- const sizeStr = selectedNode.data.size ? formatBytes(selectedNode.data.size) : '';
146
+ const valueStr = mode === 'count' ? '1 file' : selectedNode.data.size ? formatBytes(selectedNode.data.size) : '';
122
147
  const grid = Array(height).fill(null).map(() => Array(width).fill(null).map(() => ({
123
148
  char: ' ',
124
149
  color
@@ -148,12 +173,12 @@ export const TreeMapView = ({
148
173
  };
149
174
  }
150
175
  }
151
- if (sizeStr && midY + 2 < height - 2) {
152
- const sizeX = Math.floor((width - sizeStr.length) / 2);
153
- for (let i = 0; i < sizeStr.length && sizeX + i < width - 2; i++) {
176
+ if (valueStr && midY + 2 < height - 2) {
177
+ const sizeX = Math.floor((width - valueStr.length) / 2);
178
+ for (let i = 0; i < valueStr.length && sizeX + i < width - 2; i++) {
154
179
  if (sizeX + i > 0) {
155
180
  grid[midY + 2][sizeX + i] = {
156
- char: sizeStr[i],
181
+ char: valueStr[i],
157
182
  color
158
183
  };
159
184
  }
@@ -193,10 +218,10 @@ export const TreeMapView = ({
193
218
  }, /*#__PURE__*/React.createElement(Text, {
194
219
  bold: true,
195
220
  color: oneHunter.cyan
196
- }, "TreeMap View: ", name, " (File)"), /*#__PURE__*/React.createElement(Text, {
221
+ }, "TreeMap View (", modeLabel, "): ", name, " (File)"), /*#__PURE__*/React.createElement(Text, {
197
222
  dimColor: true,
198
223
  color: oneHunter.textAlt
199
- }, "Press 't' to toggle back to tree view | Ctrl+C: Exit"), /*#__PURE__*/React.createElement(Box, {
224
+ }, "Press any key to go back to tree view | Ctrl+C: Exit"), /*#__PURE__*/React.createElement(Box, {
200
225
  borderStyle: "single",
201
226
  borderColor: oneHunter.textAlt,
202
227
  flexDirection: "column",
@@ -205,7 +230,7 @@ export const TreeMapView = ({
205
230
  dimColor: true,
206
231
  color: oneHunter.textAlt,
207
232
  marginTop: 1
208
- }, "File: ", name, " | Size: ", sizeStr || 'Unknown'));
233
+ }, "File: ", name, " | ", modeLabel, ": ", valueStr || 'Unknown'));
209
234
  }
210
235
 
211
236
  // It's a directory - show treemap of its children
@@ -215,10 +240,10 @@ export const TreeMapView = ({
215
240
  }, /*#__PURE__*/React.createElement(Text, {
216
241
  bold: true,
217
242
  color: oneHunter.cyan
218
- }, "TreeMap View: ", selectedNode.data.name, " (Empty Directory)"), /*#__PURE__*/React.createElement(Text, {
243
+ }, "TreeMap View (", modeLabel, "): ", selectedNode.data.name, " (Empty Directory)"), /*#__PURE__*/React.createElement(Text, {
219
244
  dimColor: true,
220
245
  color: oneHunter.textAlt
221
- }, "Press 't' to toggle back to tree view | Ctrl+C: Exit"), /*#__PURE__*/React.createElement(Box, {
246
+ }, "Press any key to go back to tree view | Ctrl+C: Exit"), /*#__PURE__*/React.createElement(Box, {
222
247
  borderStyle: "single",
223
248
  borderColor: oneHunter.textAlt,
224
249
  marginTop: 1,
@@ -247,12 +272,18 @@ export const TreeMapView = ({
247
272
  const x1 = Math.ceil(node.x1);
248
273
  const y1 = Math.ceil(node.y1);
249
274
  const isFile = node.data.isFile;
275
+ const isNodeLoading = loadingPaths.has(node.data.path);
250
276
  const boxWidth = x1 - x0;
251
277
  const boxHeight = y1 - y0;
252
278
  if (boxWidth < 1 || boxHeight < 1) return;
253
279
 
254
- // Color based on type
255
- const color = isFile ? getFileColor(node.data.name) : node.groupColor || oneHunter.blue;
280
+ // Color based on type (dimmed if loading)
281
+ let color = isFile ? getFileColor(node.data.name) : node.groupColor || oneHunter.blue;
282
+
283
+ // Dim the color if loading
284
+ if (isNodeLoading) {
285
+ color = oneHunter.textAlt;
286
+ }
256
287
 
257
288
  // Draw border
258
289
  for (let y = y0; y < y1 && y < height; y++) {
@@ -276,14 +307,14 @@ export const TreeMapView = ({
276
307
 
277
308
  // Draw label if there's room
278
309
  const name = node.data.name;
279
- const sizeStr = node.value ? ` ${formatBytes(node.value)}` : '';
310
+ const valueStr = isNodeLoading ? ` ${SPINNER_FRAMES[spinnerFrame]}` : node.value ? ` ${formatValue(node.value)}${mode === 'count' ? ' files' : ''}` : '';
280
311
  if (boxWidth >= 8 && boxHeight >= 2) {
281
312
  const labelY = y0 + 1;
282
313
  const labelX = x0 + 1;
283
314
  const maxLength = boxWidth - 2;
284
315
 
285
- // Combine name and size
286
- let label = name + sizeStr;
316
+ // Combine name and value
317
+ let label = name + valueStr;
287
318
  if (label.length > maxLength) {
288
319
  // Try just name
289
320
  if (name.length <= maxLength) {
@@ -307,8 +338,9 @@ export const TreeMapView = ({
307
338
  const maxLength = boxWidth - 2;
308
339
  let label;
309
340
  if (isFile) {
341
+ var _name$match;
310
342
  // Show extension for files
311
- const ext = name.match(/\.([^.]{1,3})$/)?.[1];
343
+ const ext = (_name$match = name.match(/\.([^.]{1,3})$/)) === null || _name$match === void 0 ? void 0 : _name$match[1];
312
344
  label = ext || name.slice(0, maxLength);
313
345
  } else {
314
346
  // Show first few chars for directories
@@ -372,15 +404,21 @@ export const TreeMapView = ({
372
404
  key: y
373
405
  }, segments);
374
406
  });
407
+ const totalLabel = mode === 'count' ? `${formatCount(root.value || 0)} files` : formatBytes(root.value || 0);
408
+ const isLoading = loadingPaths.size > 0;
375
409
  return /*#__PURE__*/React.createElement(Box, {
376
410
  flexDirection: "column"
377
411
  }, /*#__PURE__*/React.createElement(Text, {
378
412
  bold: true,
379
413
  color: oneHunter.cyan
380
- }, "TreeMap View: ", selectedNode.data.name), /*#__PURE__*/React.createElement(Text, {
414
+ }, "TreeMap View (", modeLabel, "): ", selectedNode.data.name), /*#__PURE__*/React.createElement(Box, {
415
+ justifyContent: "space-between"
416
+ }, /*#__PURE__*/React.createElement(Text, {
381
417
  dimColor: true,
382
418
  color: oneHunter.textAlt
383
- }, "Press 't' to toggle back to tree view | \u2191/\u2193: Navigate in tree | Ctrl+C: Exit"), /*#__PURE__*/React.createElement(Box, {
419
+ }, "Press any key to go back to tree view | \u2191/\u2193: Navigate in tree | Ctrl+C: Exit"), isLoading && /*#__PURE__*/React.createElement(Text, {
420
+ color: oneHunter.yellow
421
+ }, ' ', SPINNER_FRAMES[spinnerFrame], " Loading ", loadingPaths.size, "...")), /*#__PURE__*/React.createElement(Box, {
384
422
  borderStyle: "single",
385
423
  borderColor: oneHunter.textAlt,
386
424
  flexDirection: "column",
@@ -389,5 +427,5 @@ export const TreeMapView = ({
389
427
  dimColor: true,
390
428
  color: oneHunter.textAlt,
391
429
  marginTop: 1
392
- }, "Showing ", firstLevelNodes.length, " items | Total:", ' ', formatBytes(root.value || 0)));
430
+ }, "Showing ", firstLevelNodes.length, " items | Total: ", totalLabel));
393
431
  };
package/dist/cli.js CHANGED
@@ -30,6 +30,12 @@ const formatBytes = bytes => {
30
30
  const i = Math.floor(Math.log(bytes) / Math.log(k));
31
31
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
32
32
  };
33
+ const formatCount = count => {
34
+ if (count === 0) return '0';
35
+ if (count < 1000) return count.toString();
36
+ if (count < 1000000) return (count / 1000).toFixed(1) + 'K';
37
+ return (count / 1000000).toFixed(1) + 'M';
38
+ };
33
39
  const formatDate = timestamp => {
34
40
  const date = new Date(timestamp);
35
41
  const now = new Date();
@@ -66,6 +72,38 @@ const getDirSizeAsync = async dirPath => {
66
72
  return 0;
67
73
  }
68
74
  };
75
+
76
+ // Fast file counting - much faster than size calculation
77
+ const countFilesAsync = async dirPath => {
78
+ try {
79
+ const stats = fs.statSync(dirPath);
80
+ if (!stats.isDirectory()) return 1;
81
+ let count = 0;
82
+ const countRecursive = async dir => {
83
+ try {
84
+ const entries = fs.readdirSync(dir, {
85
+ withFileTypes: true
86
+ });
87
+ for (const entry of entries) {
88
+ // Skip hidden files
89
+ if (entry.name.startsWith('.')) continue;
90
+ const fullPath = path.join(dir, entry.name);
91
+ if (entry.isFile()) {
92
+ count++;
93
+ } else if (entry.isDirectory()) {
94
+ await countRecursive(fullPath);
95
+ }
96
+ }
97
+ } catch {
98
+ // Skip directories we can't read
99
+ }
100
+ };
101
+ await countRecursive(dirPath);
102
+ return count;
103
+ } catch {
104
+ return 0;
105
+ }
106
+ };
69
107
  const readDirTree = (dirPath, noSize = true, currentLevel = 0, maxLevel = Infinity) => {
70
108
  try {
71
109
  const stats = fs.statSync(dirPath);
@@ -182,6 +220,7 @@ const TreeRow = /*#__PURE__*/React.memo(({
182
220
  shortcutInput,
183
221
  noSize,
184
222
  calculatedSizes,
223
+ calculatedCounts,
185
224
  loadingPaths,
186
225
  spinnerFrame,
187
226
  lastModifiedDates
@@ -199,6 +238,7 @@ const TreeRow = /*#__PURE__*/React.memo(({
199
238
  } else {
200
239
  displaySize = node.data.size;
201
240
  }
241
+ const fileCount = calculatedCounts === null || calculatedCounts === void 0 ? void 0 : calculatedCounts.get(node.data.path);
202
242
  const lastModified = lastModifiedDates.get(node.data.path);
203
243
  const treeLines = getTreeLines(node);
204
244
 
@@ -235,7 +275,11 @@ const TreeRow = /*#__PURE__*/React.memo(({
235
275
  backgroundColor: isSelected ? oneHunter.green : undefined
236
276
  }, node.data.name, !node.data.isFile && !isRoot ? '/' : ''), hasChildren && !isCollapsed && /*#__PURE__*/React.createElement(Text, {
237
277
  color: oneHunter.textAlt
238
- }, " (", node.children.length, ")"), isLoading ? /*#__PURE__*/React.createElement(Text, {
278
+ }, " (", node.children.length, ")"), fileCount !== undefined && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, {
279
+ color: oneHunter.purple
280
+ }, " [", formatCount(fileCount), " files]"), isLoading && /*#__PURE__*/React.createElement(Text, {
281
+ color: oneHunter.yellow
282
+ }, " ", SPINNER_FRAMES[spinnerFrame])), isLoading ? /*#__PURE__*/React.createElement(Text, {
239
283
  color: oneHunter.yellow
240
284
  }, " ", SPINNER_FRAMES[spinnerFrame]) : displaySize > 0 ? /*#__PURE__*/React.createElement(Text, {
241
285
  color: oneHunter.cyan
@@ -253,8 +297,10 @@ const TreeVisualization = ({
253
297
  const [collapsed, setCollapsed] = useState(new Set());
254
298
  const [shortcutInput, setShortcutInput] = useState('');
255
299
  const [showTreeMap, setShowTreeMap] = useState(false);
300
+ const [treeMapMode, setTreeMapMode] = useState('size'); // 'size' or 'count'
256
301
  const [initialized, setInitialized] = useState(false);
257
302
  const [calculatedSizes, setCalculatedSizes] = useState(new Map());
303
+ const [calculatedCounts, setCalculatedCounts] = useState(new Map());
258
304
  const [loadingPaths, setLoadingPaths] = useState(new Set());
259
305
  const [spinnerFrame, setSpinnerFrame] = useState(0);
260
306
  const [rootTree, setRootTree] = useState(() => hierarchy(readDirTree(dirPath, noSize, 0, maxLevel)));
@@ -262,7 +308,7 @@ const TreeVisualization = ({
262
308
  const {
263
309
  stdout
264
310
  } = useStdout();
265
- const terminalHeight = stdout?.rows || 24;
311
+ const terminalHeight = (stdout === null || stdout === void 0 ? void 0 : stdout.rows) || 24;
266
312
  const viewportHeight = Math.max(5, terminalHeight - 7);
267
313
 
268
314
  // Spinner animation loop
@@ -385,6 +431,85 @@ const TreeVisualization = ({
385
431
  await new Promise(r => setTimeout(r, 0));
386
432
  }
387
433
  };
434
+ const countFilesInTreeAsync = async node => {
435
+ // Collect node and all its descendants
436
+ const nodesToCount = [];
437
+ const collectNodes = n => {
438
+ // Skip if already counted
439
+ if (calculatedCounts.has(n.data.path)) {
440
+ // Still recurse to children in case they need counting
441
+ if (n.children) {
442
+ n.children.forEach(collectNodes);
443
+ }
444
+ return;
445
+ }
446
+ nodesToCount.push(n);
447
+ if (n.children) {
448
+ n.children.forEach(collectNodes);
449
+ }
450
+ };
451
+ collectNodes(node);
452
+ if (nodesToCount.length === 0) return; // Nothing to count
453
+
454
+ // Mark all nodes as loading
455
+ const pathsToLoad = nodesToCount.map(n => n.data.path);
456
+ setLoadingPaths(prev => new Set([...prev, ...pathsToLoad]));
457
+ const newMap = new Map(calculatedCounts);
458
+
459
+ // Count and stream updates for each node individually
460
+ for (const targetNode of nodesToCount) {
461
+ // For directories, stream partial counts as we count
462
+ if (!targetNode.data.isFile) {
463
+ let runningCount = 0;
464
+ let updateCounter = 0;
465
+ const countRecursive = async dir => {
466
+ try {
467
+ const entries = fs.readdirSync(dir, {
468
+ withFileTypes: true
469
+ });
470
+ for (const entry of entries) {
471
+ // Skip hidden files
472
+ if (entry.name.startsWith('.')) continue;
473
+ const fullPath = path.join(dir, entry.name);
474
+ if (entry.isFile()) {
475
+ runningCount++;
476
+ updateCounter++;
477
+ // Stream update every 100 files for better performance
478
+ if (updateCounter >= 100) {
479
+ updateCounter = 0;
480
+ newMap.set(targetNode.data.path, runningCount);
481
+ React.startTransition(() => {
482
+ setCalculatedCounts(new Map(newMap));
483
+ });
484
+ await new Promise(r => setTimeout(r, 0));
485
+ }
486
+ } else if (entry.isDirectory()) {
487
+ await countRecursive(fullPath);
488
+ }
489
+ }
490
+ } catch {
491
+ // Skip directories we can't read
492
+ }
493
+ };
494
+ await countRecursive(targetNode.data.path);
495
+ newMap.set(targetNode.data.path, runningCount);
496
+ } else {
497
+ // Files are always 1
498
+ newMap.set(targetNode.data.path, 1);
499
+ }
500
+
501
+ // Stream the final update and remove from loading
502
+ React.startTransition(() => {
503
+ setCalculatedCounts(new Map(newMap));
504
+ setLoadingPaths(prev => {
505
+ const next = new Set(prev);
506
+ next.delete(targetNode.data.path);
507
+ return next;
508
+ });
509
+ });
510
+ await new Promise(r => setTimeout(r, 0));
511
+ }
512
+ };
388
513
  const getLastModifiedAsync = async node => {
389
514
  // Collect node and all its descendants
390
515
  const nodesToProcess = [];
@@ -460,8 +585,8 @@ const TreeVisualization = ({
460
585
  return;
461
586
  }
462
587
  if (input === 'q') process.exit(0);
463
- if (input === 'C') {
464
- // Toggle collapse/expand all children of the selected node's PARENT
588
+ if (input === 'C' && !key.shift) {
589
+ // Toggle collapse/expand all children of the selected node's PARENT (lowercase handling moved)
465
590
  const selectedNode = visibleNodes[selectedIndex];
466
591
  if (!selectedNode || !selectedNode.parent) return;
467
592
  const parentNode = selectedNode.parent;
@@ -492,6 +617,40 @@ const TreeVisualization = ({
492
617
  });
493
618
  return;
494
619
  }
620
+ if (key.shift && input === 'C') {
621
+ // Shift+C: Show count treemap
622
+ const node = visibleNodes[selectedIndex];
623
+ if (!node) return;
624
+
625
+ // Collect all nodes that need counting
626
+ const nodesToCount = [];
627
+ const collectNodes = n => {
628
+ if (calculatedCounts.has(n.data.path)) {
629
+ if (n.children) {
630
+ n.children.forEach(collectNodes);
631
+ }
632
+ return;
633
+ }
634
+ nodesToCount.push(n);
635
+ if (n.children) {
636
+ n.children.forEach(collectNodes);
637
+ }
638
+ };
639
+ collectNodes(node);
640
+
641
+ // Mark nodes as loading before starting
642
+ if (nodesToCount.length > 0) {
643
+ const pathsToLoad = nodesToCount.map(n => n.data.path);
644
+ setLoadingPaths(prev => new Set([...prev, ...pathsToLoad]));
645
+ }
646
+
647
+ // Start calculating counts in the background
648
+ countFilesInTreeAsync(node);
649
+ // Show count treemap immediately
650
+ setTreeMapMode('count');
651
+ setShowTreeMap(true);
652
+ return;
653
+ }
495
654
  if (input === 'O') {
496
655
  // Toggle collapse/expand ALL nodes in the entire tree
497
656
  const allNodesWithChildren = [];
@@ -524,7 +683,8 @@ const TreeVisualization = ({
524
683
  if (!node) return;
525
684
  // Always start calculating sizes in the background (calculateSizeAsync skips already-calculated nodes)
526
685
  calculateSizeAsync(node);
527
- // Show treemap immediately
686
+ // Show size treemap immediately
687
+ setTreeMapMode('size');
528
688
  setShowTreeMap(true);
529
689
  return;
530
690
  }
@@ -533,6 +693,11 @@ const TreeVisualization = ({
533
693
  if (node) await calculateSizeAsync(node);
534
694
  return;
535
695
  }
696
+ if (input === 'c') {
697
+ const node = visibleNodes[selectedIndex];
698
+ if (node) await countFilesInTreeAsync(node);
699
+ return;
700
+ }
536
701
  if (input === 'l') {
537
702
  const node = visibleNodes[selectedIndex];
538
703
  if (node) await getLastModifiedAsync(node);
@@ -559,8 +724,9 @@ const TreeVisualization = ({
559
724
  // If it's a directory with no children loaded yet, traverse into it
560
725
  if (node.data.children === undefined) {
561
726
  await traverseDir(node);
562
- } else if (node.children) {
563
- // Children already loaded, toggle collapse
727
+ } else {
728
+ // Changed from `else if (node.children)`
729
+ // Children already loaded, toggle collapse (even if empty)
564
730
  setCollapsed(prev => {
565
731
  const next = new Set(prev);
566
732
  if (next.has(node.data.path)) next.delete(node.data.path);else next.add(node.data.path);
@@ -596,31 +762,52 @@ const TreeVisualization = ({
596
762
 
597
763
  // Set value on the DATA object
598
764
  const data = hierarchyNode.data;
599
- if (data.isFile) {
600
- // Files: use calculatedSize or read from disk
601
- const calcSize = calculatedSizes.get(data.path);
602
- if (calcSize !== undefined) {
603
- data.value = calcSize;
604
- } else {
605
- try {
606
- data.value = fs.statSync(data.path).size;
607
- } catch {
765
+ if (treeMapMode === 'count') {
766
+ // COUNT MODE: Use file counts
767
+ if (data.isFile) {
768
+ // Files always count as 1
769
+ data.value = 1;
770
+ } else if (data.children === undefined || !hierarchyNode.children || hierarchyNode.children.length === 0) {
771
+ // Directory with no children loaded OR empty directory
772
+ // Use calculatedCount if available
773
+ const calcCount = calculatedCounts.get(data.path);
774
+ if (calcCount !== undefined) {
775
+ data.value = calcCount;
776
+ } else {
608
777
  data.value = 0;
609
778
  }
610
- }
611
- } else if (data.children === undefined || !hierarchyNode.children || hierarchyNode.children.length === 0) {
612
- // Directory with no children loaded OR empty directory
613
- // Use calculatedSize if available
614
- const calcSize = calculatedSizes.get(data.path);
615
- if (calcSize !== undefined) {
616
- data.value = calcSize;
617
779
  } else {
618
- // For unloaded directories without calculated size, will show as 0
619
- data.value = data.size || 0;
780
+ // Directory with loaded children - let d3's .sum() aggregate from children
781
+ data.value = undefined;
620
782
  }
621
783
  } else {
622
- // Directory with loaded children - let d3's .sum() aggregate from children
623
- data.value = undefined;
784
+ // SIZE MODE: Use file sizes
785
+ if (data.isFile) {
786
+ // Files: use calculatedSize or read from disk
787
+ const calcSize = calculatedSizes.get(data.path);
788
+ if (calcSize !== undefined) {
789
+ data.value = calcSize;
790
+ } else {
791
+ try {
792
+ data.value = fs.statSync(data.path).size;
793
+ } catch {
794
+ data.value = 0;
795
+ }
796
+ }
797
+ } else if (data.children === undefined || !hierarchyNode.children || hierarchyNode.children.length === 0) {
798
+ // Directory with no children loaded OR empty directory
799
+ // Use calculatedSize if available
800
+ const calcSize = calculatedSizes.get(data.path);
801
+ if (calcSize !== undefined) {
802
+ data.value = calcSize;
803
+ } else {
804
+ // For unloaded directories without calculated size, will show as 0
805
+ data.value = data.size || 0;
806
+ }
807
+ } else {
808
+ // Directory with loaded children - let d3's .sum() aggregate from children
809
+ data.value = undefined;
810
+ }
624
811
  }
625
812
  };
626
813
  populateValues(node);
@@ -628,7 +815,9 @@ const TreeVisualization = ({
628
815
  selectedNode: node,
629
816
  stdout: stdout,
630
817
  calculatedSizes: calculatedSizes,
631
- loadingPaths: loadingPaths
818
+ calculatedCounts: calculatedCounts,
819
+ loadingPaths: loadingPaths,
820
+ mode: treeMapMode
632
821
  });
633
822
  }
634
823
  const start = Math.max(0, selectedIndex - Math.floor(viewportHeight / 2));
@@ -642,7 +831,7 @@ const TreeVisualization = ({
642
831
  }, /*#__PURE__*/React.createElement(Text, {
643
832
  color: oneHunter.textAlt,
644
833
  dimColor: true
645
- }, "\u2191/\u2193: Navigate | Enter: Expand/Traverse | d: Size | l: Date | m: Map | q: Quit"), loadingPaths.size > 0 && /*#__PURE__*/React.createElement(Text, {
834
+ }, "\u2191/\u2193: Navigate | Enter: Expand/Traverse | c: Count | d: Size | l: Date | m: Map | Shift+C: CountMap | q: Quit"), loadingPaths.size > 0 && /*#__PURE__*/React.createElement(Text, {
646
835
  color: oneHunter.yellow
647
836
  }, ' ', SPINNER_FRAMES[spinnerFrame], " Loading ", loadingPaths.size, "...")), viewportNodes.map((node, i) => /*#__PURE__*/React.createElement(TreeRow, {
648
837
  key: start + i,
@@ -652,6 +841,7 @@ const TreeVisualization = ({
652
841
  shortcutInput: shortcutInput,
653
842
  noSize: noSize,
654
843
  calculatedSizes: calculatedSizes,
844
+ calculatedCounts: calculatedCounts,
655
845
  loadingPaths: loadingPaths,
656
846
  spinnerFrame: spinnerFrame,
657
847
  lastModifiedDates: lastModifiedDates
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "tri-cli",
3
- "version": "0.0.51",
3
+ "version": "0.0.53",
4
4
  "description": "Interactive CLI directory tree visualizer with treemap view",
5
5
  "author": "Jared Wilber",
6
6
  "license": "MIT",
7
+ "type": "module",
7
8
  "keywords": [
8
9
  "cli",
9
10
  "tree",
@@ -19,7 +20,6 @@
19
20
  "bin": {
20
21
  "tri": "dist/cli.js"
21
22
  },
22
- "type": "module",
23
23
  "engines": {
24
24
  "node": ">=16"
25
25
  },
@@ -35,12 +35,13 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "d3-hierarchy": "^3.1.2",
38
- "ink": "^4.1.0",
39
- "meow": "^11.0.0",
38
+ "ink": "^3.2.0",
39
+ "meow": "^9.0.0",
40
40
  "react": "^18.2.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@babel/cli": "^7.21.0",
44
+ "@babel/preset-env": "^7.22.0",
44
45
  "@babel/preset-react": "^7.28.5",
45
46
  "@vdemedes/prettier-config": "^2.0.1",
46
47
  "ava": "^5.2.0",
@@ -72,7 +73,16 @@
72
73
  "prettier": "@vdemedes/prettier-config",
73
74
  "babel": {
74
75
  "presets": [
76
+ [
77
+ "@babel/preset-env",
78
+ {
79
+ "targets": {
80
+ "node": "16"
81
+ },
82
+ "modules": false
83
+ }
84
+ ],
75
85
  "@babel/preset-react"
76
86
  ]
77
87
  }
78
- }
88
+ }
package/readme.tape CHANGED
@@ -1,4 +1,4 @@
1
- Output assets/readme.gif
1
+ Output readme2.gif
2
2
  Set FontSize 18
3
3
  Set Width 1000
4
4
  Set Height 600
@@ -10,40 +10,36 @@ Sleep 500ms
10
10
 
11
11
  Type "tri ."
12
12
  Enter
13
- Sleep 2s
13
+ Sleep 1s
14
14
 
15
- Down
16
- Sleep 250ms
17
- Down
18
- Sleep 250ms
19
- Enter
20
- Sleep 500ms
21
- Type "d"
22
- Enter
23
- Sleep 500ms
24
15
  Down
25
16
  Sleep 250ms
26
17
  Down
27
18
  Sleep 250ms
28
19
  Down
29
20
  Sleep 250ms
30
- Type "l"
31
- Sleep 1.5s
32
-
33
21
  Enter
22
+ Sleep 1s
23
+ Type "d"
34
24
  Sleep 1.5s
35
- Type "m"
36
- Sleep 1.5s
37
- Type "m"
38
- Sleep 500ms
39
- Down
40
- Sleep 250ms
41
- Down
25
+
26
+ Type "a"
42
27
  Sleep 250ms
28
+ Type "e"
29
+ Sleep 500ms
43
30
  Enter
44
- Sleep 1.5s
31
+ Sleep 1s
45
32
 
46
33
 
34
+ Type "a"
35
+ Sleep 250ms
36
+ Type "e"
37
+ Sleep 500ms
38
+ Type "b"
39
+ Sleep 500ms
40
+ Enter
41
+ Sleep 1s
42
+
47
43
  # Toggle treemap view
48
44
  Type "m"
49
45
  Sleep 2s
@@ -54,20 +50,11 @@ Sleep 2s
54
50
 
55
51
  Type "a"
56
52
  Sleep 250ms
57
- Type "c"
58
- Sleep 500ms
59
- Enter
60
- Sleep 1s
61
-
62
- Type "a"
63
- Sleep 250ms
64
- Type "h"
53
+ Type "e"
65
54
  Sleep 500ms
66
55
  Enter
67
56
  Sleep 1s
68
57
 
69
- Enter
70
- Sleep 1s
71
58
 
72
59
  # Quit
73
60
  Type "q"