A mesmerizing terminal visualization that brings your file system to life. Watch files heat decay as they change, see git status at a glance, and experience your codebase breathing in real-time.
watching: /Users/dev/myproject โ3
+ myproject/
โโโ + src/
โ โโโ โ App.tsx MODIFIED 2s โโโโโโโ
โ โโโ โ components/
โ โ โโโ โ Button.tsx CREATED 8s โโโโโโโ
โ โ โฎ +2 more
โ โโโ utils/...
โ โโโ index.ts
โโโ โฆ package.json
โโโ README.md
โ2 โ3 โฆ1
- ๐ก๏ธ Heat Visualization โ 7-color thermal gradient (redโblue) shows recency at a glance
- ๐จ Git Status Integration โ Instantly see staged, unstaged, untracked, and conflicted files with colored symbols
- ๐ฌ๏ธ Breathing Mode โ The tree auto-refreshes, showing heat decay in real-time like a living organism
- ๐ Space-Aware Layout โ Adapts to terminal size, collapsing cold branches when space is limited
- ๐ Priority Bubbling โ Hot files and git-status items bubble to the top, always visible
- ๐ Git-First Sorting โ Files with git status appear first within directories
- ๐ป Smart Ghosts โ Tracked deleted files stay visible until committed; untracked fade naturally
- โฎ Partial Collapse โ Shows
โฎ +N morewhen some directory contents are hidden - ๐๏ธ Additive Weights โ Granular, composable priority system for fine-tuned control
- ๐ Directory Folding โ Ctrl+Up/Down or click
[โผ]/[โถ]to fold/unfold directories; Shift+click to hide - โจ๏ธ Cursor Navigation โ Use arrow keys or j/k to navigate, Enter to open files
- ๐ Filter Mode โ Press
/to search (vim-style), matches highlighted with yellow background - ๐ Clickable Links โ Filenames are clickable in iTerm and compatible terminals (OSC 8)
- โก Debounced Updates โ Smooth rendering even during rapid file changes
- ๐ Symlink Support โ Follows symlinked directories, showing their contents live; detects and prevents loops
# Clone the repository
git clone https://github.com/yourusername/fstop.git
cd fstop
# Install dependencies
npm install# Link globally to use from anywhere
npm link
# Now you can use it in any directory
cd ~/any-project
fstop .To unlink: npm unlink -g fstop
# Watch current directory
node bin/watch.mjs .
# Watch a specific directory
node bin/watch.mjs ./src
# Watch with more history slots
node bin/watch.mjs . --history 8
# Faster breathing (500ms refresh)
node bin/watch.mjs . --breathe 500| Option | Short | Default | Description |
|---|---|---|---|
--history |
-n |
4 |
Number of recent changes to keep hot |
--breathe |
-b |
2000 |
Auto-refresh interval in ms |
--interval |
100 |
Debounce interval for file changes | |
--ignore |
-i |
Add glob patterns to ignore | |
--ghost-steps |
3 |
Fade steps for deleted items | |
--no-git |
Disable git status indicators | ||
--quick |
-q |
Render once and exit (no watching) | |
--markdown-preview |
Preview .md files with a command (e.g. markserv) |
||
--loopcheck |
Enable symlink loop detection (slower startup, safer) |
fstop loads defaults from config files so you don't have to repeat CLI flags:
| File | Scope |
|---|---|
~/.config/fstop/config.json |
Global defaults |
.fstop.json |
Per-project overrides (in project root) |
CLI flags override config. Config keys use camelCase versions of flag names: history, ignore, interval, breathe, ghostSteps, git, loopcheck, markdownPreview.
The markdownPreview key accepts either a plain command string or a structured object with extra flags. Works great with markserv:
{
"markdownPreview": {
"command": "markserv",
"flags": ["--theme", "solarized"]
}
}The flags array is passed to the command before fstop's own arguments.
fstop is fully interactive:
| Key | Action |
|---|---|
โ / k |
Move cursor up |
โ / j |
Move cursor down |
Enter |
Open selected file/directory |
Ctrl+โ |
Fold directory at cursor |
Ctrl+โ |
Unfold directory at cursor |
| Any character | Start/continue filtering |
Backspace |
Remove last filter character |
Esc |
Clear filter, folds, and hidden dirs |
Ctrl+C |
Quit |
| Action | Effect |
|---|---|
| Click filename | Open file/directory |
Click [โผ]/[โถ] |
Toggle fold on directory |
| Shift+click directory | Hide directory from tree |
Just start typing to filter and highlight matching files:
โโโ โ [lay]out.mjs <- "lay" highlighted
โโโ renderer.mjs
โโโ terminal.mjs
/lay_
- Matching files get a weight boost (bubble to top)
- Matched substring shown with yellow background
- Cursor navigation works during filter
Enteropens selectedBackspaceremoves last filter characterEscclears filter, folds, and hidden dirs
| Symbol | Color | Meaning |
|---|---|---|
โ |
๐ด Red | Merge conflicts |
โ |
๐ก Yellow | Unstaged changes |
โ |
๐ข Green | Staged for commit |
โฆ |
โช Gray | Untracked (new to project) |
โ
|
๐ฃ Magenta | Ahead and behind remote |
โ |
๐ต Cyan | Ahead of remote |
โ |
๐ด Red | Behind remote |
Files pulse with a 7-segment thermal gradient from hot to cold:
โโโโโโโ brightRed โ Just changed (< 1s)
โโโโโโโ red โ Hot
โโโโโโโ magenta โ Warm
โโโโโโโ brightMagenta โ Cooling
โโโโโโโ cyan โ Cool
โโโโโโโ brightCyan โ Cold
โโโโโโโ blue โ Coldest
Heat decays exponentially with a ~10 second half-life. The breathing timer keeps the visualization alive, smoothly transitioning colors as files cool down.
Note: The heat bar color is always based on temperature, independent of git status. Filenames use git colors, bars use thermal colors.
fstop/
โโโ bin/
โ โโโ watch.mjs # CLI entry point
โโโ lib/
โ โโโ file-watcher.mjs # Chokidar wrapper
โ โโโ git-status.mjs # Git status parsing
โ โโโ heat.mjs # Heat scoring system
โ โโโ layout.mjs # Space-aware tree layout
โ โโโ renderer.mjs # ANSI terminal rendering
โ โโโ terminal.mjs # Terminal utilities
โ โโโ tree-state.mjs # File tree state management
โ โโโ config.mjs # Config file loading
โโโ package.json
- File Watcher โ Chokidar monitors the directory for changes (follows symlinked directories)
- Symlink Loop Detection โ Preflight check detects and prevents directory symlink cycles
- Tree State โ Maintains a virtual file tree with event history
- Heat Scoring โ Calculates priority based on recency and event type
- Git Integration โ Fetches status via
git status --porcelain - Layout Engine โ Weight-based priority system adapts tree to terminal height
- Renderer โ Outputs ANSI-styled tree with in-place updates
When space is limited, fstop uses an additive weight system to decide what to show. Each line gets a score from multiple categories:
| Category | Options | Weights |
|---|---|---|
| Git | conflict, unstaged, staged, untracked | 800, 700, 600, 500 |
| Heat | hot, cold | 350, 0 |
| Type | dir, file | 100, 50 |
| Event | deleted, created, modified | 150, 75, 50 |
| Context | hasChildren, inHistory, ghost | 200, 100, 50 |
Example: A hot unstaged file that was just modified:
- type.file (50) + git.unstaged (700) + heat.hot (350) + event.change (50) = 1150
Edit lib/layout.mjs to tune priorities:
// Prioritize deletions above everything
WEIGHT.event.unlink = 900
// Heat-first workflow (over git)
WEIGHT.heat.hot = 800
WEIGHT.git.unstaged = 300- Resize your terminal to see the adaptive layout in action
- Use
--breathe 500for faster, more responsive heat decay - Use
--history 8to track more simultaneous changes - Press
Ctrl+Cto exit cleanly - Symlinked directories are followed automatically; if you have symlink loops, fstop will exit with an error before starting
By default, fstop ignores:
**/node_modules/****/.git/****/localdata/**,**/.postgres/**,**/.mysql/**(database data)**/.cache/**
Note: dist/ directories are not ignored by default. Use --ignore "**/dist/**" if you want to exclude them.
Symlinked directories are followed, including those pointing outside the watched root. If a symlink loop is detected, fstop will fail with a clear error message before starting the watcher.
- Node.js 18+
- A terminal with ANSI color support
- Git (optional, for git status features)
MIT
Watch your code breathe.
