All notable changes to Dual are documented in this file.
v3.0 architecture rewrite — devcontainer.json as primary config, interactive init wizard, and shell interception pane propagation.
- devcontainer.json support — New
src/devcontainer.rsparser readsimage,build.dockerfile,forwardPorts,containerEnv,postCreateCommand, andmountsfrom.devcontainer/devcontainer.json(or rootdevcontainer.json). Repos with existing devcontainer configs work with zero Dual-specific setup - Dockerfile build support —
build.dockerfilein devcontainer.json triggersdocker buildinstead ofdocker pull, with support for build args, target stages, and custom context paths - Interactive init wizard —
dual initlaunches a 5-step interactive wizard (image selection, ports, setup command, review/confirm) powered bydialoguer. Skip prompts withdual init --yes - Shell interception pane propagation — New tmux panes and windows auto-load command interception.
tmux set-environmentsetsDUAL_ACTIVE,DUAL_RC_PATH,DUAL_CONTAINERat the session level; a shell RC snippet in~/.zshrc/~/.bashrcauto-sources the interception file - Shell hook auto-install — Dual automatically installs a guarded snippet in the user's shell RC file on first run (idempotent, no-op outside Dual tmux sessions)
.dual/settings.json— New per-repo config file for Dual-specific orchestration (extra_commands,anonymous_volumes,shared,devcontainerpath override)
dual addrenamed todual init— The command for registering a workspace is nowdual init(breaking)- Config file format — Per-repo config moved from
.dual.toml(TOML) to.dual/settings.json(JSON) with a new schema separating container config (devcontainer.json) from orchestration config (settings.json) - Two-file config architecture — Container settings (
image,ports,env,setup) now live indevcontainer.json; Dual-specific settings (extra_commands,anonymous_volumes,shared) live in.dual/settings.json - Config load order —
load_hints()merges.dual/settings.json+devcontainer.jsonintoRepoHints. devcontainer.json is the primary source for container config - Proxy error messages — Now reference
devcontainer.jsonfor port configuration instead of.dual.toml
.dual.toml— Replaced by.dual/settings.json+devcontainer.jsondual addsubcommand — Replaced bydual init- Container fields in Dual config —
image,ports,setup,[env]removed from Dual config (now in devcontainer.json)
Fix macOS shell RC source path quoting.
- Shell RC source path — Quoted the RC file path in
source_file_command()to handle paths with spaces - RC file location — Moved RC files to
~/.dual/rc/directory
TUI workspace browser and multiplexer abstraction layer.
- TUI workspace browser —
dual(no args) launches a ratatui-based terminal UI with repo/branch tree view, status colors (green=running, yellow=stopped, gray=lazy), and keyboard navigation (j/k, Enter, q) - MultiplexerBackend trait — Abstract interface over terminal multiplexers (
is_available,create_session,attach,detach,destroy,is_alive,list_sessions,send_keys,is_inside), withTmuxBackendas the concrete implementation - TUI suspend/resume loop — Selecting a workspace suspends the TUI, launches/attaches tmux, and resumes the TUI on detach (Ctrl+b d)
- Panic hook — Terminal state (alternate screen, raw mode) restored on crash
- Inside-tmux TUI handling — When launched from inside tmux, uses
switch-clientand exits cleanly instead of looping
dual(no args) — Now launches TUI instead of printing usage.dual listremains as the non-interactive fallback- Tmux module —
src/tmux.rsreplaced bysrc/backend.rs(trait) +src/tmux_backend.rs(implementation) session_name()moved to config — Session naming is now insrc/config.rsalongsidecontainer_name()
Bug fixes, feature completions, and context-aware CLI improvements.
- Context-aware
dual create— Repo auto-detected from cwd when--repois omitted. New syntax:dual create <branch> [--repo NAME] - Context-aware
dual launch/dual destroy— Workspace arg now optional, auto-detected from cwd - Tmux nested session detection — Detects
$TMUXenv var and usesswitch-clientinstead ofattach-sessionto avoid nesting - Grouped
dual listoutput — Workspaces displayed grouped by repo with detailed container/tmux status clone_from_local()— New fast clone strategy usinggit clone --localfrom main workspace +git checkout -bfor new branches- Commented
.dual.tomltemplate —dual addcreates a.dual.tomlwith helpful inline documentation - Environment variable support —
[env]section in.dual.tomlpassed as-e KEY=VALUEtodocker create - Setup command support —
setup = "pnpm install"in.dual.tomlruns viadocker execafter first container creation - Configurable container commands —
extra_commandsfield in.dual.tomlmerges with default command routing list - Configurable anonymous volumes —
anonymous_volumesfield in.dual.tomlreplaces hardcodednode_modulesvolume
dual createCLI — Args changed from<repo> <branch>to<branch> [--repo NAME](breaking)dual launchCLI — Workspace arg now optional (backward compatible)dual destroyCLI — Workspace arg now optional (backward compatible)container::create()signature — Now acceptsenvandanonymous_volumesparametersshell::generate_rc()/write_rc_file()— Now acceptextra_commandsparameter
Complete rewrite from TypeScript to Rust. Dual is now a compiled binary with Docker-based workspace isolation, transparent command routing, and a reverse proxy for browser access.
- Rust rewrite — Full rewrite in Rust (Edition 2024) replacing the TypeScript/npm implementation
- Docker container isolation — Each workspace runs in its own Docker container with bind-mounted source
- Transparent command routing — Shell RC intercepts runtime commands (
pnpm,node,curl localhost) and routes them to containers viadocker exec - Reverse proxy — HTTP reverse proxy (hyper + tokio) with
{repo}-{branch}.localhost:{port}subdomain routing for browser access - tmux session management — Automatic tmux sessions per workspace with attach/detach lifecycle
- Two-file config system — Split architecture:
.dual.toml(per-repo hints, committed to git) +~/.dual/workspaces.toml(global state, managed by Dual) dual addcommand — Register an existing git repo as a Dual workspacedual createcommand — Create a new branch workspace for an existing repodual synccommand — Propagate shared config files (.vercel,.env.local, etc.) across branch workspaces using symlinks (Unix) or copies (Windows)dual shell-rccommand — Generate shell RC for transparent command routing (internal use)- Shared config propagation — Declare shared files in
.dual.toml[shared]section, synced across all branches of a repo via~/.dual/shared/{repo}/ - Atomic state writes — File locking (
fs2) + temp file + atomic rename for crash-safe state persistence - Structured logging —
tracing+tracing-subscriberwithDUAL_LOGenvironment variable filter - Typed errors —
thiserror-based error types replacing string errors - curl-based installer — Shell and PowerShell install scripts via cargo-dist
- Cross-platform builds — Pre-built binaries for macOS (Apple Silicon + Intel), Linux (ARM64 + x64), and Windows (x64)
- CI/CD pipeline — GitHub Actions with check/lint, unit tests, E2E tests (Docker + tmux), and automated releases
- Architecture — Worktree-based workflows replaced with full git clone per workspace
- Config format — Single
dual.tomlreplaced with two-file system (.dual.toml+workspaces.toml) - Installation — npm package replaced with compiled binary (
cargo installor curl installer) - Language — TypeScript → Rust (Edition 2024)
- npm package distribution
- Git worktree-based workspace management
- dotenv multiplexer functionality
- Environment variable file loading
The following releases are from the original TypeScript implementation and are no longer supported. They have been marked as pre-release on GitHub.
- 1.2.2 (2025-10-16) — Final TypeScript release
- 1.2.1 (2025-10-16) — Patch release
- 1.2.0 (2025-10-16) — Dotenv compatibility improvements
- 1.1.0 (2025-10-16) — Environment variable loading
- 1.0.0 (2025-10-15) — First stable TypeScript release
- 0.3.0 (2025-10-15) — Worktree lifecycle management with hooks
- 0.2.2 (2025-10-15) — Patch release
- 0.2.1 (2025-10-15) — Patch release
- 0.2.0 (2025-10-15) — Feature release
- 0.1.0 (2025-10-14) — Initial release