A local-first remote development platform. Work with remote files and commands as if they were local.
Note: This project is still in alpha development. Breaking changes may be made. Issues and contributions are welcome!
| Local (client) | Remote (target) | |
|---|---|---|
| macOS | yes | |
| Linux | yes | yes |
curl --proto '=https' --tlsv1.2 -sSf https://graft.run/install.sh | shOptions: --install-dir <dir>, --version <tag>
Nix Flakes
# In your flake inputs add graft:
inputs = {
# ...
graft.url = "github:edaniels/graft";
# ...
}
# In your configuration add graft to your systemPackages:
# ${system} should be defined as your current platform. Ex: "x86_64-linux" or "aarch64-darwin".
environment.systemPackages = [
# ...
graft.packages.${system}.default
# ...
]1. Activate shell integration (add to your shell rc file):
# bash: ~/.bashrc, zsh: ~/.zshrc
eval "$(graft activate zsh)"This lets graft track your working directory so commands like run, shell, sync, and forward can automatically detect which connection to use.
2. Start the daemon:
graft daemon service install # auto-start on loginThe daemon runs in the background and manages your remote connections.
3. Connect to a remote machine:
graft connect . user@host:~/project --syncThis connects your current directory (.) to ~/project on the remote host, with --sync to enable bidirectional file synchronization. You can also use graft init to save connection settings to a graft.yaml file for repeated use.
4. Use it from within the connected directory:
graft run make build # run a command remotely
graft shell # open a remote shell
graft sync # sync files to the remote
graft forward go make # forward commands to the remoteAll of these commands detect the connection from your current directory. You can also specify a connection explicitly with --to <connection>, or pin a connection to your shell session with graft use <connection>.
| Command | Description |
|---|---|
connect |
Connect to a remote machine (SSH or Docker) |
disconnect |
Disconnect from a remote connection |
run |
Run a command on the remote |
shell |
Open a remote shell |
sync |
Sync files to the remote |
forward |
Forward local commands to the remote |
use |
Pin a connection to the current shell session |
status |
Show connection status |
doctor |
Check environment setup and diagnose issues |
init |
Generate a graft.yaml configuration file for future graft connects |
When you run a command like graft run or graft shell, graft needs to know which connection to use. It follows this hierarchy:
- Explicit -
--to <connection>on the command line - Session pin - set with
graft use <connection>, applies to the current shell - CWD-based - automatically detected from your working directory based on each connection's local root
graft use is useful when you have multiple connections and want to lock your shell to a specific one:
graft use labos # pin this shell to the "labos" connection
graft shell # opens a shell on labos, regardless of cwd
graft use --clear # resume CWD-based auto-selectionInstead of passing flags to graft connect every time, you can save connection settings in a graft.yaml file.
A project config lives in a project directory and defines how to connect:
graft init . ubuntu@myhost:~/mydir --name myconn --sync --forward makeThis creates a graft.yaml:
version: v1
forward:
- make
destinations:
myconn:
host: myhost
user: ubuntu
syncTo: ~/mydir
sync: trueThen graft connect with no arguments from that directory reads the config automatically.
A workspace groups multiple projects under a shared root. Create one with:
cd ~/work
graft init --workspaceThis creates a graft.yaml with workspace: true. When you run graft connect from a project directory inside the workspace, graft walks up the directory tree looking for the workspace root. If found and syncWorkspace is enabled, the entire workspace directory is synced rather than just the project subdirectory.
~/work/ <- workspace root (graft.yaml with workspace: true)
infra/
projectA/ <- project (graft.yaml with destinations)
projectB/ <- project (graft.yaml with destinations)
Connections created with --background are excluded from CWD-based auto-selection. This is useful for auxiliary connections (e.g. a shared build server) that you only want to use explicitly via --to or graft use:
graft connect . user@build-server --background --name build
graft use build # explicitly switch to it- Transparent SSH agent forwarding -- use local SSH keys on the remote without manual setup (written, not yet tested)
- LSP support -- run language servers remotely with local editor integration (written, not yet tested)
See docs/architecture.md for how graft works internally.
See CONTRIBUTING.md for guidelines, including our AI usage policy. This project uses AI tools responsibly - all code is human-reviewed before merging, and all contributions must disclose AI usage.
All build/test/lint commands use just:
just graft-dev # build and install for local dev
just test # run tests
just lint # run all lintersSee the justfile for the full list of recipes.