Help us shape the future of Go! Take the 2026 Go Developer Survey

drift

command module
v0.1.5-alpha Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 1 Imported by: 0

README

drift

A terminal TUI for browsing, diffing, and syncing files with remote hosts — think PHPStorm's "Browse Remote Host" and "Sync with Deployed To", but in your terminal.

Supports SFTP/SSH, FTP, and FTPS targets. Runs on Linux and macOS.

drift screenshot

[!WARNING] Alpha: drift is in an early public stage. Expect rough edges, incomplete polish, and breaking changes between releases.


Features

  • Side-by-side local/remote file browser with multi-select (Space) and recursive directory marking
  • Safe local/remote text preview in the opposite pane (p), with line numbers and wrapping
  • Project-wide fuzzy file finder (f) for marking files
  • Unified diff view with the file list on the left for local vs. remote files, unchanged stretches folded away behind @@ hunk headers
  • Per-file sync direction control: upload ↑, download ↓, delete local ✗, delete remote ✗, or skip —
  • Bulk sync direction toggle (A key cycles all files at once)
  • Auto pre-selection of sync direction based on file modification time
  • Sync current file (s) or all marked files (S) in one keystroke
  • Per-host path mappings (like PHPStorm's Deployment Mappings tab)
  • Host manager: create, edit, delete, and test connections
  • Global config (~/.config/drift/config.toml) + project-level config (.drift/config.toml)
  • Skips .git, node_modules, .idea, and other irrelevant directories automatically

Installation

From source (requires Go 1.25+)
git clone https://github.com/WariKoda/drift.git
cd drift
make install

This builds the binary and installs it to ~/.local/bin/drift.

Directly with Go
go install github.com/WariKoda/drift@latest
Update after code changes
make update

Usage

# Start in the current directory, last project, or dashboard (see below)
drift

# Open the project dashboard explicitly
drift dash

# Open a registered project by name or slug
drift open kunde-a

# Manage projects
drift projects list
drift projects add "KUNDE A" ~/work/kunde-a
drift projects edit kunde-a --name "KUNDE A GmbH" --path ~/work/kunde-a
drift projects archive kunde-a
drift projects remove kunde-a

# Show version
drift version

Navigate to any file or directory, press Space to mark it, then s to open the sync target picker.

Project dashboard

drift can keep a registry of your projects (one per customer, say) and show them in a dashboard on startup. From the dashboard you select a project and drift re-roots into it: it loads that project's config and opens the file browser in its directory — the same as cd <path> && drift, but without leaving drift.

The dashboard appears automatically when you run drift outside any project (no .drift/ found) and nothing has been opened yet, as long as at least one project is registered. If you have opened a project before, drift restores that one instead (its path must still exist). Inside a project directory, drift opens the browser as before. --dashboard (or drift dash) always opens the list; --no-dashboard stays in the current directory.

From the file browser, P opens a filterable switcher and leaves the current session alone. Esc goes back. Enter on another project re-roots. m (with an empty filter) opens the full dashboard to add, edit, archive, or remove entries.

drift open accepts a display name or slug: exact slug, then exact name (case-insensitive), then a unique prefix or substring of either.

Each project stores only a slug, display name, local path and timestamps in ~/.config/drift/projects.toml. Hosts and mappings continue to live in the project's own <path>/.drift/config.toml — nothing is duplicated.

When you start drift inside a .drift project that isn't registered yet, it offers to add it to the registry (name defaults to the directory name). Press y to register or any other key to skip — you can always register later with drift projects add ..

Typical workflow
  1. Run drift in your project directory
  2. Mark one or more files/directories with Space
  3. Press s and choose a host
  4. Review diffs and suggested sync directions
  5. Sync the current file with s or all files with S

Key Bindings

Project Dashboard
Key Action
j / k or ↑ / ↓ Navigate
Enter Open project (re-root drift into it)
1–9 Jump to / open the n-th project
n New project
e Edit project
d Remove project (with confirmation)
a Archive / unarchive project
. Show / hide archived projects
Click / wheel Move the cursor; double click opens
Esc Back to the browser when opened from P then m; otherwise quit
q Quit drift
Project switcher (P in the file browser)
Key Action
type Filter by name, slug, or path
↑ / ↓ or Ctrl+n / Ctrl+p Navigate
Enter Open the selected project
m Open the dashboard to manage projects (empty filter only)
Esc Back to the browser (session kept)
File Browser
Key Action
j / k or ↑ / ↓ Navigate in the active pane
h / ←, l / → / Enter Collapse / open directory
g / G Jump to top / bottom
Tab Switch local / remote pane
p Toggle a text preview in the opposite pane
PgUp / PgDn, Home / End Scroll / jump within an active preview
@ Choose or change the remote host
Space Mark / unmark file or directory in the active pane
V / * Mark the current level / invert the active pane's selection
f Fuzzy-find files across the project and mark them
s Sync marked local and remote files
r / / / ? Refresh active pane / filter / help
H Open host manager
P Switch project (filterable picker; m opens the dashboard)
Esc Clear filter and selections
q / Ctrl+C Quit
Diff View
Key Action
j / k or ↑ / ↓ Scroll diff content by line
PgUp / PgDn Scroll diff content by page
Ctrl+u / Ctrl+d Scroll diff content by half page
Home / g, End / G Jump to start / end of diff content
[ / ] Jump to the previous / next hunk
Enter / l Expand or collapse the first fold in view
h Collapse the fold around the top of the viewport
c Expand or collapse every fold in the file
Tab / Shift+Tab Select next / previous file
Space Cycle sync direction for current file
A Cycle sync direction for all files
s Sync current file
S Sync all files
u / d Quick upload / download current file
e Toggle the last bulk-sync error list (when errors occurred)
r Refresh diffs
q / Esc Back to browser
Host Manager
Key Action
n New host
e / Enter Edit host
d Delete host
t Test connection
q / Esc Back
Mouse

The mouse works in the file browser, the diff view and the host manager.

Action Effect
Wheel Scroll the pane under the pointer
Click Move the cursor there, and focus that pane
Click on a pane label Focus that pane
Click on a fold marker Expand or collapse that fold (diff view)
Double click Expand a directory (local), open one (remote), cycle a file's sync direction (diff view), edit a host (host manager)
Shift+Click Select text with the terminal's own selection

Mouse reporting takes the terminal's native text selection away, so it can be turned off:

drift --no-mouse              # this run only
DRIFT_NO_MOUSE=1 drift        # via environment
# ~/.config/drift/config.toml — permanently
[ui]
mouse = false

The flag beats the environment variable, which beats the config file. Mouse support is on unless one of them turns it off.


Theming

Drift uses the terminal palette by default and auto-detects Omarchy themes from $XDG_STATE_HOME/omarchy/current/theme/colors.toml (normally ~/.local/state/omarchy/current/theme/colors.toml). The legacy location under $XDG_CONFIG_HOME is supported as a fallback. You can override this with:

DRIFT_THEME=auto|ansi|omarchy|default
DRIFT_THEME_FILE=/path/to/colors.toml

auto loads Omarchy colors first and falls back to ANSI terminal colors. default uses Drift's built-in fallback palette.


Logging

Logging is off by default. When enabled, drift writes diagnostics (connection lifecycle and every connect/sync/diff error, with full paths) to a file — never to the terminal, which the TUI owns. Enable it per run with a flag or environment variable; the flag wins:

drift --debug                 # debug level → ~/.config/drift/drift.log
DRIFT_DEBUG=1 drift           # same, via environment
drift --log /tmp/drift.log    # info level → explicit path
DRIFT_LOG=/tmp/drift.log drift

--debug (or DRIFT_DEBUG=1) raises the level to debug, adding a line per synced file; without it the log stays at info level. The log file is appended to, not rotated. This complements the in-app [e] error list in the diff view, which only holds the most recent bulk sync.


Configuration

Global config: ~/.config/drift/config.toml
[defaults]
user = "deploy"

[[hosts]]
name       = "prod"
hostname   = "example.com"
port       = 22
user       = "deploy"
root_path  = "/var/www/html"
protocol   = "sftp"

  [hosts.auth]
  type     = "keyfile"
  key_file = "~/.ssh/id_ed25519"
Project config: .drift/config.toml

Place this file in your project root. drift walks up from the working directory to find it.

[[hosts]]
name      = "staging"
hostname  = "shopdev.example.com"
port      = 21
user      = "webuser"
root_path = "/var/www"
protocol  = "ftp"

# For ftps with a self-signed / mismatched certificate (skips TLS verification):
# insecure_tls = true

  [hosts.auth]
  type     = "password"
  password = "$DEPLOY_PASSWORD"

  [[hosts.mappings]]
  local  = "plugins/plugin1"
  remote = "html/custom/plugins/plugin1"

  [[hosts.mappings]]
  local  = "plugins/plugin2"
  remote = "html/custom/plugins/plugin2"
Path Mappings

local paths are relative to the project root. remote paths are relative to the host's root_path.

When effective mappings are configured, only files that fall under a mapping rule can be synced. Files outside all mappings are excluded. Without mappings, all files sync relative to root_path.

Auth types
Type Fields
keyfile key_file, passphrase (optional)
password password (supports $ENV_VAR)
agent none — uses SSH agent
Keep credentials out of version control

.drift/config.toml lives inside your project, so it is easy to commit by accident. It holds hostnames, usernames and — for password auth — whatever you typed into the host form, stored verbatim.

drift guards against that in two ways. When it writes the file it creates .drift/.gitignore containing config.toml (an existing .gitignore is left untouched), and it restricts .drift/ to mode 700 and the config to 600. It also asks git whether the file is reachable — on startup and after every save that stores a literal password or passphrase — and warns in the status line when it is:

⚠ .drift/config.toml holds credentials and is tracked by git — untrack it: git rm --cached .drift/config.toml

Esc dismisses the warning. If you keep other files under .drift/, they stay shareable: only config.toml is ignored.

Prefer agent or keyfile auth, or write password = "$DEPLOY_PASSWORD" and export the variable in your shell: drift expands $ENV_VAR at connect time, so the secret itself never reaches the file. The same applies to passphrase.

If a config with a real password was already committed, treat that password as compromised and rotate it — removing the file in a later commit does not remove it from the history.


Project Structure

internal/
  config/       config types, loader, writer
  project/      project registry model + store (projects.toml)
  diff/         diff engine, result types, renderer
  ftp/          FTP/FTPS client (jlaffaye/ftp)
  fs/           local file walker, directory reader
  log/          optional file-based diagnostics
  pathmap/      local ↔ remote path resolution with mapping rules
  remote/       protocol-agnostic Client interface and connection factory
  sftp/         SFTP client
  ssh/          SSH auth and known_hosts verification
  styles/       shared palettes and lipgloss styles
  sync/         sync plan types and direction policy
  tui/
    app.go      root Bubble Tea model, screen routing
    browser/    file browser screen
    dashboard/  project dashboard screen
    projectform/ project create/edit form
    projectselector/ project switcher modal
    diffview/   diff + sync screen
    hostform/   host create/edit form (incl. mapping manager)
    hostmanager/ host list screen
    hostselector/sync target picker
    statusbar/  reusable one-line status renderer
    textfield/  shared single-line text input widget
    styles.go   TUI-facing style aliases

Development

go test ./...
go vet ./...
go build ./...
Local install
make install
Versioned build
make release-build VERSION=vX.Y.Z

Contributing

Issues and pull requests are welcome.

For development notes and contribution workflow, see CONTRIBUTING.md.


License

MIT — see LICENSE.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
config
Package config defines drift's configuration types and loading logic.
Package config defines drift's configuration types and loading logic.
diff
Package diff compares local and remote files and produces structured diff results.
Package diff compares local and remote files and produces structured diff results.
fs
Package fs defines the shared FileEntry type used for both local and remote files.
Package fs defines the shared FileEntry type used for both local and remote files.
ftp
Package ftp provides an FTP client that implements remote.Client.
Package ftp provides an FTP client that implements remote.Client.
log
Package log provides drift's opt-in file logging.
Package log provides drift's opt-in file logging.
pathmap
Package pathmap resolves local filesystem paths to remote paths and vice versa using the Mapping rules from the project config.
Package pathmap resolves local filesystem paths to remote paths and vice versa using the Mapping rules from the project config.
project
Package project manages the global registry of drift projects.
Package project manages the global registry of drift projects.
remote
Package remote defines the protocol-agnostic interface for remote file operations and provides a factory that returns the right implementation (SFTP or FTP) based on host.Protocol.
Package remote defines the protocol-agnostic interface for remote file operations and provides a factory that returns the right implementation (SFTP or FTP) based on host.Protocol.
sftp
Package sftp provides an SFTP client that wraps SSH connection + SFTP session.
Package sftp provides an SFTP client that wraps SSH connection + SFTP session.
ssh
Package ssh provides SSH authentication helpers.
Package ssh provides SSH authentication helpers.
styles
Package styles defines all lipgloss colors and styles used across drift's TUI.
Package styles defines all lipgloss colors and styles used across drift's TUI.
sync
Package sync handles building and executing file sync plans.
Package sync handles building and executing file sync plans.
tui
Package tui re-exports styles for convenience within the tui package itself.
Package tui re-exports styles for convenience within the tui package itself.
tui/browser
Package browser implements the yazi-like local file browser TUI component.
Package browser implements the yazi-like local file browser TUI component.
tui/dashboard
Package dashboard implements the project dashboard — the optional landing screen that lists registered drift projects and lets the user open one.
Package dashboard implements the project dashboard — the optional landing screen that lists registered drift projects and lets the user open one.
tui/diffview
Package diffview implements the file-list + unified-diff screen.
Package diffview implements the file-list + unified-diff screen.
tui/hostform
Package hostform implements the create/edit host form screen.
Package hostform implements the create/edit host form screen.
tui/hostmanager
Package hostmanager implements the host list / CRUD screen.
Package hostmanager implements the host list / CRUD screen.
tui/hostselector
Package hostselector implements the host picker modal shown when the user presses [s].
Package hostselector implements the host picker modal shown when the user presses [s].
tui/loading
Package loading provides the global network activity indicator used by TUI screens.
Package loading provides the global network activity indicator used by TUI screens.
tui/mouse
Package mouse holds the mouse helpers shared by the TUI screens.
Package mouse holds the mouse helpers shared by the TUI screens.
tui/projectform
Package projectform implements the create/edit project form screen.
Package projectform implements the create/edit project form screen.
tui/projectselector
Package projectselector implements the project switcher modal shown when the user presses [P].
Package projectselector implements the project switcher modal shown when the user presses [P].
tui/statusbar
Package statusbar renders the persistent one-line status bar at the bottom.
Package statusbar renders the persistent one-line status bar at the bottom.
tui/textfield
Package textfield provides a single-line editable text input widget shared by drift's TUI forms (host form, project form).
Package textfield provides a single-line editable text input widget shared by drift's TUI forms (host form, project form).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL