@kitsunekode/file-ops-cli
v1.0.0
Published
Interactive Bun-native file move, copy, and remove CLIs with shared safety-first workflows.
Maintainers
Readme
mvi / cpi / rmi
Interactive terminal file move, copy, and remove tools. Browse files visually, multi-select with fuzzy search, pick a destination folder when needed, and execute without typing paths.
mvi moves files. cpi copies files. rmi removes files by moving them to trash by default, with optional hard delete. The commands share the same source-selection interface and keybindings.
You can select both files and directories. Selections persist while you navigate, so you can collect items from multiple directories before choosing the destination.
Features
- Visual file browser with directory navigation
- Fuzzy search to filter files as you type
- Multi-select with checkboxes
- Nerd Font icons for 30+ file types
- Destination folder picker (directories only)
- Direct path jump for destination folders (for example
~/dotfiles) - Optional
fzf-powered destination search from configurable roots - Bookmark and recent-destination support for faster targeting
- Pre-flight validation (permissions, conflicts, circular moves)
- Explicit confirmation before any operation (
Y/n) - No-clobber by default — never overwrites without asking
- Interactive remove command with trash-by-default safety
- Verified copy pipeline for copy and cross-device move operations
- Recovery journal for interrupted overwrite replacement
- Lazy startup path for help/version and non-interactive failures
- Bounded-concurrency directory metadata scanning for faster large-folder browsing
- Clean terminal restore on exit, Ctrl+C, or crash
- Zero runtime dependencies — just Bun
Requirements
Install
bun add -g @kitsunekode/file-ops-cliYou can also install with npm if Bun is already available on your PATH at runtime:
npm install -g @kitsunekode/file-ops-cliThis installs mvi, cpi, and rmi globally.
Install From Source
git clone https://github.com/KitsuneKode/interactive-move-copy-cli.git
cd interactive-move-copy-cli
bun install
bun run link:globalUseful Scripts
bun run build # bundle CLI entrypoints into dist/
bun run build:compile # build standalone executables into dist/
bun run config:init # create or normalize the shared config file
bun run config:edit # open the shared config in $VISUAL / $EDITOR
bun run test # run the full test suite
bun run check # alias for the current verification suite
bun run pkg:check # dry-run the published npm package contents
bun run changeset # create a changeset entry for the next release
bun run version:packages # consume changesets and update release versions/changelogs
bun run link:global # build and link mvi/cpi/rmi globally
bun run unlink:global # remove the global link
bun run relink:global # refresh the global link after changes
bun run clean # remove dist/If you use the globally linked commands while developing, rebuild and relink after behavior changes:
bun run build
bun run relink:globalRelease Workflow
- Add a changeset with
bun run changesetfor user-facing or release-worthy changes. - Work from short-lived dev branches and merge to
main. .github/workflows/version-packages.ymlopens or updates aVersion PackagesPR from merged changesets.- Merging the version PR updates
package.jsonand changelog entries for the next release. CIruns on pull requests and on pushes tomain, and its workflow summary records the exact package version it validated.Releaseruns on pushes tomainand manual dispatch. It publishes the exactpackage.jsonversion of@kitsunekode/file-ops-clithrough npm trusted publishing and creates a GitHub release taggedvX.Y.Z.- For a brand-new package, do one manual bootstrap publish first so the npm package settings page exists and you can attach the trusted publisher to
release.yml.
Usage
mvi # browse current directory, move selected files
mvi ~/Downloads # start in ~/Downloads
cpi . # copy files from current directory
rmi # move selected files to trash
rmi --hard-delete . # permanently delete selected files from current directoryFlags
-h, --help Show help
-v, --version Show versionrmi Delete Mode Flags
--trash Move items to trash (default)
--hard-delete Permanently delete itemsrmi Config
All commands share one config file:
${XDG_CONFIG_HOME:-~/.config}/interactive-move-copy-cli/config.jsonbun run link:global runs config:init first, so the file is created automatically if it does not exist yet. You can also run bun run config:init manually to create or normalize it, and bun run config:edit to open it in $VISUAL, then $EDITOR, then nano.
The repository ships an example at config.example.json.
Default config:
{
"mvi": {},
"cpi": {},
"destinationSearch": {
"roots": [
"~"
],
"bookmarks": {
"dotfiles": "~/dotfiles",
"projects": "~/Projects"
},
"rememberRecent": true,
"recentLimit": 8
},
"rmi": {
"mode": "trash"
}
}Current keys:
mvi: reserved for future move-specific settingscpi: reserved for future copy-specific settingsdestinationSearch.roots: directories searched byCtrl+F, default["~"]destinationSearch.bookmarks: named directory aliases used bygdestinationSearch.rememberRecent: whether successful destinations are remembered automaticallydestinationSearch.recentLimit: max number of recent destinations to keeprmi.mode: default delete behavior, either"trash"or"hard-delete"
Recent destinations are stored separately in:
${XDG_CONFIG_HOME:-~/.config}/interactive-move-copy-cli/state.jsonHow It Works
Flow
mvi [dir]
|
v
File Browser (alt screen)
- Browse files/dirs in current directory
- Type to fuzzy-search, Space to select, Right to open dirs
- Press Enter with selections to confirm source files/directories
|
v
Destination-Selection Loop:
|
v
Folder Picker (alt screen)
- Browse directories only
- Navigate into subdirs with Right
- Press `g` to jump directly to a path or bookmark like `~/dotfiles` or `dotfiles`
- Press `Ctrl+F` to jump to a destination with `fzf` across configured roots plus recent destinations
- Embedded `fzf` uses a clean local config so user preview or bind overrides do not break selection
- Cancelling `fzf` returns to the folder picker with a notice
- Press Enter or 'c' to confirm current directory as destination
|
v
Validation (pre-flight checks)
- Destination exists and is writable?
- Source != destination?
- Not moving a parent into its own child?
- Name conflicts at destination? -> prompt overwrite/skip/abort
- Esc at conflict prompt goes back to Folder Picker
|
v
Confirmation Prompt
- Lists all files and destination
- Y or Enter confirms, n/N/Ctrl+C aborts
- Esc goes back to Folder Picker to reselect the destination
|
v
Execution
- Uses atomic rename for same-device moves when possible
- Uses hidden staging paths plus verification for copies and fallback moves
- Shows per-file progress with checkmarks/crosses
- Prints summary: N moved/copied, M failedrmi [dir]
|
v
File Browser (alt screen)
- Browse files/dirs in current directory
- Type to fuzzy-search, Space to select, Right to open dirs
- Press Enter with selections to confirm source files/directories
|
v
Validation (pre-flight checks)
- Selected sources still exist?
- Not deleting a parent and child in the same run?
|
v
Confirmation Prompt
- Trash by default, hard delete only when explicitly requested
- Y or Enter confirms, n/N/Ctrl+C/Esc aborts
|
v
Execution
- Same-device trash uses rename into the trash store when possible
- Cross-device trash uses verified copy-then-delete
- Hard delete permanently removes the selected pathsKeybindings — Source Selection
| Key | Action | | --- | --- | | Up/Down | Navigate file list | | Left | Go to parent / delete search char | | Right | Open directory | | Space | Toggle selection on current file | | Enter | Confirm selection | | Backspace | Delete search char / go to parent | | Ctrl+A | Select all visible files | | Ctrl+D | Deselect all | | Ctrl+R | Reset to the starting directory/state | | Tab | Show selected files summary | | Esc | Clear search / quit | | Any letter | Fuzzy search |
Keybindings — Destination Picker
| Key | Action |
| --- | --- |
| Up/Down | Navigate directories |
| Left | Go to parent |
| Right | Open directory |
| g | Jump to a destination by path or bookmark |
| Ctrl+F | Jump to a destination with fzf |
| Enter | Confirm current directory |
| Backspace | Go to parent |
| c | Confirm current directory |
| Ctrl+R | Reset to the starting directory |
| Esc | Cancel and abort |
Data Safety
This tool is designed to avoid silent data loss:
No-clobber default — If a path with the same name exists at the destination, the operation is skipped unless you explicitly choose overwrite.
Explicit confirmation — A final
[Y/n]prompt shows exactly what will happen before any operation runs.Ctrl+Calways aborts. Formvi/cpi,Escgoes back to the destination picker so you can reselect without losing your file selection.Verified staging — Copies and cross-device moves write into a hidden staging path, verify the staged result against the source, and only then promote it to the final destination.
Safe replacement — Overwrite mode replaces the existing destination path instead of merging or nesting directories.
Conflict detection — Before execution, the validator checks destination conflicts, duplicate destination names within the selection, and parent/child overlaps that would cause partial or ambiguous results.
Recovery journal — Interrupted overwrite replacements leave a journal so the next run can restore the previous destination if the final path was never promoted.
Verified delete on move fallback — When a move cannot use an atomic same-device rename, the source is deleted only after the destination copy has been verified.
Trash by default —
rmimoves items to trash unless you explicitly opt into--hard-deleteor set that mode in config.Clean exit — Terminal state (raw mode, alternate screen, cursor visibility) is restored on normal exit, Ctrl+C, SIGINT, and SIGTERM.
Performance Notes
- Cold paths stay cheap.
--help,--version, and non-TTY exits avoid loading the full TUI/execution stack. - Directory scans use bounded concurrency instead of serial
lstat()calls. - The file browser reuses cached directory listings and memoized search results instead of recomputing on every cursor move.
- Safety-critical copy verification is intentionally not optimized away.
Destination Search Notes
gis the exact jump path: use it for absolute paths, relative paths,~/..., or configured bookmark names.Ctrl+Fis the fuzzy destination path: it searches configured roots plus recent destinations withfzf.- Embedded
fzfreceives plain absolute directory paths only. - Embedded
fzfintentionally ignores global preview and bind overrides so the destination picker stays stable across different user setups. - After
gorCtrl+F, the picker jumps to the chosen directory and stays open. Press Enter orcto confirm it.
Shell Completions
The commands ship completion files, but they are not installed automatically. The examples below assume you are working from a cloned checkout or unpacked package contents.
Bash
Add to ~/.bashrc:
source /path/to/interactive-move-copy-cli/completions/mvi.bashZsh
Source the helper file directly from ~/.zshrc:
[[ ! -f ~/.config/zsh/mvi.zsh ]] || source ~/.config/zsh/mvi.zshOne-time setup:
mkdir -p ~/.config/zsh
cp /path/to/interactive-move-copy-cli/completions/mvi.zsh ~/.config/zsh/mvi.zshThe TUI requires an interactive terminal. --help and --version work in non-interactive shells, but browsing mode does not.
Project Structure
src/
bin/mvi.ts, cpi.ts, rmi.ts Entry points (shebang scripts)
config.ts Shared config loading and normalization
cli.ts Arg parsing, orchestration
core/types.ts TypeScript interfaces
core/constants.ts ANSI codes, key maps, colors
tui/terminal.ts Raw mode, keypress parsing, cleanup
tui/renderer.ts Diff-based ANSI screen rendering
tui/fuzzy.ts Fuzzy match with scoring
tui/file-browser.ts Source file selection component
tui/folder-picker.ts Destination directory picker
fs/file-info.ts Directory listing with caching
fs/icons.ts Nerd Font icon mapping
fs/format.ts Size/date formatting
ops/validator.ts Pre-flight validation
ops/executor.ts Verified execution with progress
ops/safe-fs.ts Staging, verification, and recovery journal logic
scripts/config-init.ts Create or normalize the default config
scripts/config-edit.ts Open the config in the default editor
tests/
cli.test.ts Non-interactive runtime checks
fuzzy.test.ts Fuzzy matching tests
format.test.ts Formatting tests
icons.test.ts Icon resolution tests
recovery.test.ts Journal recovery behavior
config.test.ts Config loading tests
validator.test.ts Validation logic testsTesting
bun testLicense
MIT
