nu-plugin-tramp#
A TRAMP-inspired remote filesystem plugin for Nushell, written in Rust.
The key design principle — inherited from Emacs TRAMP — is that the shell
and all its tools stay local. Only file I/O crosses the transport boundary.
Nushell's structured data model makes this a natural fit: tramp open returns
a typed Nushell value; pipes operate locally; tramp save writes back remotely.
Features#
- TRAMP-style URIs —
/ssh:user@host#port:/remote/path - Multiple backends — SSH, Docker, Kubernetes, and Sudo
- Path chaining — reach nested environments:
/ssh:myvm|docker:ctr:/app/config.toml - Full
~/.ssh/configsupport — host aliases, keys, jump hosts all work - SSH agent forwarding — no extra configuration needed
- ControlMaster multiplexing — fast subsequent operations on the same host
- Connection pooling with health-checks — sessions are reused across commands; stale connections are detected and transparently reconnected
- SFTP fast-path — file read/write/delete uses the SFTP subsystem for efficient binary-safe transfer without base64 overhead or shell argument limits; falls back to exec transparently if SFTP is unavailable
- Rich metadata —
tramp lsshows owner, group, nlinks, inode, and symlink targets (all gathered in a single remote command via batch-stat) - Stat & directory listing cache — metadata is cached with a configurable TTL (default 5s) to avoid redundant remote round-trips
- Configurable cache TTL — set
$env.TRAMP_CACHE_TTLto tune caching (supports durations like10sec,500ms, or0to disable) - Glob/wildcard filtering —
tramp ls --glob '*.log'andtramp cp --glob '*.conf'for pattern-based operations - Remote working directory —
tramp cdlets you navigate remote hosts with relative paths - Cross-host copy —
tramp cptransfers files between any combination of local and remote paths - Push execution —
tramp execruns arbitrary commands on the remote - Tab completion — dynamic completions for TRAMP paths: backend prefixes, host names (from
~/.ssh/config,docker ps,kubectl get pods, active connections), and remote file/directory listings - Streaming large files — files over 1 MB are read/written in chunks via the RPC agent's
file.read_range/file.write_rangemethods, enablingtramp open /ssh:host:/big.bin | save local.binwithout loading the entire file into memory - PTY support — the RPC agent can allocate pseudo-terminals for interactive commands via
process.start_ptyandprocess.resize, enabling proper TTY-aware behaviour (colour output, line editing, signal handling) - Interactive remote shell —
tramp shellopens a full interactive terminal session on a remote host via the RPC agent's PTY support, with automatic terminal resize detection, raw mode forwarding, and CWD support - Socket transport — the agent supports
--listen tcp:<addr>:<port>and--listen unix:<path>modes for direct TCP/Unix socket connections, bypassingdocker execoverhead for local containers - Cross-compilation — Nix-based build matrix (
cross.nix) produces statically-linked musl agent binaries for x86_64 and aarch64 Linux, plus native Darwin builds for Intel and Apple Silicon Macs - Home Manager module — auto-register the plugin with Nushell via
programs.nu-plugin-tramp.enable
Installation#
From source#
cargo install --path .
Register with Nushell#
plugin add ~/.cargo/bin/nu_plugin_tramp
plugin use tramp
With Nix#
The package is available as nu-plugin-tramp from the monorepo flake:
nix build .#nu-plugin-tramp
With Home Manager#
Import the Home Manager module for automatic registration:
# home.nix
{ inputs, ... }:
{
imports = [ inputs.nu-plugin-tramp.homeManagerModules.default ];
programs.nu-plugin-tramp = {
enable = true;
# cacheTTL = "10sec"; # optional: override the default 5s cache TTL
};
}
Usage#
Read a remote file#
# Read a text file
tramp open /ssh:myvm:/etc/hostname
# Read and parse a remote JSON file
tramp open /ssh:myvm:/app/config.json | from json
# Read a remote TOML config
tramp open /ssh:admin@myvm:/app/config.toml | from toml
List a remote directory#
# List files in a remote directory (includes owner, group, nlinks, inode, symlink targets)
tramp ls /ssh:myvm:/var/log
# Filter to only directories
tramp ls /ssh:myvm:/ | where type == dir
# Sort by size
tramp ls /ssh:myvm:/var/log | sort-by size --reverse
# Filter by owner
tramp ls /ssh:myvm:/var/log | where owner == root
# Show only symlinks and their targets
tramp ls /ssh:myvm:/usr/lib | where type == symlink | select name target
# Glob/wildcard filtering — list only .log files
tramp ls /ssh:myvm:/var/log --glob '*.log'
# Match entries starting with "config"
tramp ls /ssh:myvm:/etc -g 'config*'
Write to a remote file#
# Write a string
"hello world" | tramp save /ssh:myvm:/tmp/hello.txt
# Pipe structured data through a serialiser
open local-config.toml | to toml | tramp save /ssh:myvm:/app/config.toml
# Copy a local file to remote
open local-file.bin | tramp save /ssh:myvm:/tmp/remote-file.bin
Delete a remote file#
tramp rm /ssh:myvm:/tmp/stale.lock
Copy files#
# Remote → Local
tramp cp /ssh:myvm:/etc/hostname ./hostname
# Local → Remote
tramp cp ./config.toml /ssh:myvm:/app/config.toml
# Remote → Remote (even across different hosts)
tramp cp /ssh:vm1:/etc/config /ssh:vm2:/etc/config
# Glob copy — copy all .log files from remote to a local directory
tramp cp /ssh:myvm:/var/log ./logs --glob '*.log'
# Glob copy — copy matching files between remotes
tramp cp /ssh:vm1:/etc /ssh:vm2:/etc/backup --glob '*.conf'
Execute remote commands#
Run arbitrary commands on a remote target using push execution:
# Run a command on a remote SSH host
tramp exec /ssh:myvm:/ -- ls -la /tmp
# Run inside a Docker container on a remote host
tramp exec /ssh:myvm|docker:ctr:/ -- hostname
# Run inside a local Docker container
tramp exec /docker:mycontainer:/ -- cat /etc/os-release
# Run as root via sudo
tramp exec /sudo:root:/ -- cat /etc/shadow
Docker backend#
Access files inside Docker containers, locally or through SSH:
# Local Docker container
tramp open /docker:mycontainer:/app/config.toml
tramp ls /docker:mycontainer:/var/log
# Docker container on a remote host (chained)
tramp open /ssh:myvm|docker:webapp:/app/config.toml
tramp ls /ssh:myvm|docker:webapp:/var/log
# With a specific user inside the container
tramp open /docker:admin@mycontainer:/app/config.toml
Kubernetes backend#
Access files inside Kubernetes pods:
# Access a pod (uses current kubectl context)
tramp open /k8s:mypod:/app/config.toml
tramp ls /k8s:mypod:/var/log
# Specify a container in a multi-container pod
tramp open /k8s:sidecar@mypod:/app/config.toml
# Pod on a remote host (chained through SSH)
tramp open /ssh:myvm|k8s:mypod:/app/config.toml
Sudo backend#
Access files as another user via sudo:
# Read a root-only file
tramp open /sudo:root:/etc/shadow
# Sudo through SSH
tramp open /ssh:myvm|sudo:root:/etc/shadow
# List as another user
tramp ls /sudo:www-data:/var/www
Path chaining#
Chain multiple hops to reach nested environments. Each hop is separated by |:
# SSH → Docker
tramp open /ssh:myvm|docker:webapp:/app/config.toml
# SSH → Sudo
tramp open /ssh:myvm|sudo:root:/etc/shadow
# SSH → Docker → Sudo (triple chain)
tramp ls /ssh:myvm|docker:webapp|sudo:root:/etc
# Use cd for repeated access
tramp cd /ssh:myvm|docker:webapp:/app
tramp open config.toml
tramp ls
Remote working directory#
Set a remote CWD so that subsequent commands can use relative paths:
# Set the remote working directory
tramp cd /ssh:myvm:/app
# Now use relative paths — resolves to /ssh:myvm:/app/config.toml
tramp open config.toml
# List the current remote directory
tramp ls
# Navigate relatively
tramp cd subdir
tramp cd ..
# Show the current remote CWD
tramp pwd
# Clear the remote CWD
tramp cd --reset
Non-TRAMP commands (e.g. git) receive $env.PWD as-is and are unaffected —
only tramp subcommands participate in remote CWD resolution.
System information#
Gather remote system info in a single round-trip (inspired by emacs-tramp-rpc's
system.info RPC method):
# Show OS, arch, hostname, user, kernel, CPU count, and disk usage
tramp info /ssh:myvm:/
# Works with any backend
tramp info /docker:mycontainer:/
# Works with chained paths
tramp info /ssh:myvm|docker:webapp:/
Example output:
╭────────────────┬───────────────────────╮
│ os │ Linux │
│ arch │ x86_64 │
│ hostname │ myvm │
│ user │ admin │
│ kernel │ 6.1.0-18-amd64 │
│ cpus │ 4 │
│ disk_total │ 50.0 GiB │
│ disk_used │ 12.3 GiB │
│ disk_available │ 35.2 GiB │
│ disk_use_pct │ 26% │
│ connection │ /ssh:myvm:/ │
╰────────────────┴───────────────────────╯
Connection management#
# Test connectivity to a remote host (reports timing)
tramp ping /ssh:myvm:/
# List all active pooled connections
tramp connections
# Disconnect a specific host
tramp disconnect myvm
# Disconnect everything
tramp disconnect --all
Filesystem watching#
Watch a remote path for filesystem changes using the RPC agent's inotify/kqueue integration. Requires the RPC agent to be deployed (automatic for SSH and standalone Docker/K8s backends).
# Watch /app for 5 seconds and collect change events
> tramp watch /ssh:myvm:/app --duration 5000
╭───┬──────────────────────┬────────┬─────────────────────────╮
│ # │ paths │ kind │ timestamp │
├───┼──────────────────────┼────────┼─────────────────────────┤
│ 0 │ [/app/config.toml] │ modify │ 2025-01-15 10:23:45.123 │
│ 1 │ [/app/data/cache.db] │ create │ 2025-01-15 10:23:46.456 │
╰───┴──────────────────────┴────────┴─────────────────────────╯
# Watch recursively (include subdirectories)
> tramp watch /ssh:myvm:/app --recursive --duration 10000
# List currently active watches on the remote
> tramp watch /ssh:myvm:/ --list
╭───┬──────┬───────────╮
│ # │ path │ recursive │
├───┼──────┼───────────┤
│ 0 │ /app │ true │
╰───┴──────┴───────────╯
# Stop watching a path
> tramp watch /ssh:myvm:/app --remove
Interactive remote shell#
Open a full interactive terminal session on a remote host. This uses the RPC agent's PTY support to provide a seamless shell-in-shell experience without leaving Nushell. Requires the RPC agent (automatic for SSH and standalone Docker/K8s backends). Unix only.
# Open a shell on a remote host (auto-detects remote $SHELL)
> tramp shell /ssh:myvm:/
# Open a shell with a specific working directory
> tramp shell /ssh:myvm:/app
# Use a specific shell program
> tramp shell /ssh:myvm:/ --shell /bin/bash
# Shell inside a Docker container
> tramp shell /docker:mycontainer:/
# Shell inside a container on a remote host (chained path)
> tramp shell /ssh:myvm|docker:ctr:/
The terminal is put in raw mode for the duration of the session — control
characters like Ctrl-C are forwarded to the remote shell rather than
interpreted locally. The session ends when the remote shell exits (e.g.
type exit or press Ctrl-D). Terminal resize events are detected
automatically and forwarded to the remote PTY.
Cache configuration#
The stat and directory listing caches default to a 5-second TTL. Override
this at runtime via the $env.TRAMP_CACHE_TTL environment variable:
# Set cache TTL to 10 seconds
$env.TRAMP_CACHE_TTL = 10sec
# Use a shorter TTL for rapid iteration
$env.TRAMP_CACHE_TTL = 1sec
# Disable caching entirely (every command hits the remote)
$env.TRAMP_CACHE_TTL = 0sec
# String values also work: "5", "2.5", "500ms", "10s", "3sec"
$env.TRAMP_CACHE_TTL = "500ms"
The TTL is read from the environment on each command invocation, so you can change it on-the-fly without restarting the plugin.
Tab completion#
All tramp commands that accept path arguments provide dynamic tab
completion. Just press Tab at any point while typing a TRAMP path:
# Complete backend prefixes
tramp open /<Tab>
# → /ssh: /docker: /k8s: /sudo:
# Complete host names (from ~/.ssh/config, active connections, docker ps, kubectl get pods)
tramp open /ssh:<Tab>
# → /ssh:myvm: /ssh:webserver: /ssh:jumpbox:
tramp open /docker:<Tab>
# → /docker:webapp: /docker:postgres:
# Start remote path after host
tramp open /ssh:myvm:<Tab>
# → /ssh:myvm:/
# Complete remote directories and files
tramp open /ssh:myvm:/<Tab>
# → /ssh:myvm:/etc/ /ssh:myvm:/home/ /ssh:myvm:/var/ ...
tramp open /ssh:myvm:/etc/ho<Tab>
# → /ssh:myvm:/etc/hostname /ssh:myvm:/etc/hosts
# Relative path completion works when a remote CWD is set
tramp cd /ssh:myvm:/app
tramp open conf<Tab>
# → config.toml config.json
Both positional arguments of tramp cp support completions, so you get
suggestions for source and destination paths independently.
Path Format#
/<backend>:<user>@<host>#<port>:<remote-path>
| Segment | Required | Example |
|---|---|---|
backend |
✅ | ssh, docker, k8s, sudo |
user |
❌ | admin |
host |
✅ | myvm, 10.0.0.1, container/pod name, target user |
port |
❌ | 2222 |
remote-path |
✅ | /etc/config |
Backend reference#
| Backend | Host field | User field | Example |
|---|---|---|---|
ssh |
Hostname or IP | SSH user | /ssh:admin@myvm#2222:/path |
docker |
Container name/ID | --user inside container |
/docker:root@webapp:/path |
k8s / kubernetes |
Pod name | -c container name |
/k8s:sidecar@mypod:/path |
sudo |
Target user | (unused) | /sudo:root:/path |
Single-hop examples#
/ssh:myvm:/etc/config
/ssh:admin@myvm#2222:/etc/config
/docker:mycontainer:/app/config.toml
/k8s:mypod:/tmp/data
/k8s:sidecar@mypod:/app/logs
/sudo:root:/etc/shadow
Chained path examples#
/ssh:myvm|docker:webapp:/app/config.toml
/ssh:myvm|sudo:root:/etc/shadow
/ssh:jumpbox|docker:webapp|sudo:root:/etc/shadow
Note: SSH-through-SSH chaining (
/ssh:jump|ssh:target:/path) is not supported directly. UseProxyJumpin~/.ssh/configinstead — it's more efficient and fully supported by the SSH backend.
Commands#
| Command | Description |
|---|---|
tramp |
Show help and usage information |
tramp open |
Read a remote file and return as Nushell value |
tramp ls |
List a remote directory as a table (with rich metadata) |
tramp save |
Write piped data to a remote file |
tramp rm |
Delete a remote file |
tramp cp |
Copy files between local/remote locations |
tramp cd |
Set the remote working directory |
tramp pwd |
Show the current remote working directory |
tramp exec |
Execute a command on the remote (push execution) |
tramp info |
Show remote system info (OS, arch, hostname, disk) |
tramp ping |
Test connectivity to a remote host |
tramp connections |
List active pooled connections |
tramp disconnect |
Close connections (by host or --all) |
tramp watch |
Watch a remote path for filesystem changes (requires RPC agent) |
tramp shell |
Interactive remote terminal session (requires RPC agent, Unix only) |
Requirements#
- Nushell ≥ 0.110
- OpenSSH client installed and in
$PATH(ssh,ssh-agent) — for SSH backend - Docker CLI installed and in
$PATH— for Docker backend - kubectl installed and configured — for Kubernetes backend
- sudo configured for non-interactive use (
NOPASSWD) — for Sudo backend - GNU coreutils on the remote/target (
statfor listings;cat,rm,base64as fallback when SFTP is unavailable)
When the tramp-agent binary is deployed on the remote host, GNU coreutils are no longer required — all operations use native syscalls.
Architecture#
The project is organised as a Cargo workspace with two crates:
nu-plugin-tramp/
├── crates/
│ ├── plugin/ # nu-plugin-tramp — Nushell plugin
│ └── agent/ # tramp-agent — lightweight RPC agent
├── Cargo.toml # workspace root
└── ...
Plugin architecture#
Nushell command
│
▼
┌─────────────────────────────────────────────┐
│ nu-plugin-tramp plugin │
│ │
│ ┌─────────────┐ ┌───────────────────┐ │
│ │ Path Parser │──▶│ Chain Resolver │ │
│ └─────────────┘ └───────┬───────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ VFS Layer │ │
│ │ ┌───────────┐ │ │
│ │ │ Stat/List │ │ │
│ │ │ Cache │ │ │
│ │ └───────────┘ │ │
│ └────────┬────────┘ │
│ │ │
│ ┌──────────────────┼──────────┐ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────┐ ┌────────┐ ┌─────┐ ┌──────┐ │
│ │ SSH │ │ Docker │ │ K8s │ │ Sudo │ │
│ └─────┘ └────────┘ └─────┘ └──────┘ │
│ ▲ ▲ ▲ │
│ └────────┴────────┘ │
│ CommandRunner abstraction │
│ (LocalRunner / RemoteRunner) │
└─────────────────────────────────────────────┘
RPC Agent architecture#
The tramp-agent binary runs on the remote host and replaces shell-command
parsing with native syscalls over a MsgPack-RPC connection. The agent
supports three transport modes:
- stdin/stdout (default) — piped through an SSH session or
docker exec -i - TCP listener (
--listen tcp:0.0.0.0:9547) — direct socket connection - Unix socket (
--listen unix:/tmp/tramp-agent.sock) — local IPC
┌──────────────────┐ SSH / TCP / Unix ┌───────────────────────────┐
│ nu-plugin-tramp │ ◄───────────────► │ tramp-agent │
│ (local Nushell) │ MsgPack-RPC │ (remote Rust) │
└──────────────────┘ 4-byte len + │ │
MessagePack │ ┌──────┐ ┌─────────┐ │
│ │ file │ │ dir │ │
│ └──────┘ └─────────┘ │
│ ┌──────┐ ┌─────────┐ │
│ │ proc │ │ system │ │
│ └──────┘ └─────────┘ │
│ ┌──────┐ ┌─────────┐ │
│ │batch │ │ watch │ │
│ └──────┘ └─────────┘ │
│ ┌──────┐ │
│ │ pty │ │
│ └──────┘ │
└───────────────────────────┘
| Category | Methods |
|---|---|
| File | file.stat, file.stat_batch, file.truename, file.size, |
file.read, file.read_range, file.write, |
|
file.write_range, file.copy, file.rename, |
|
file.delete, file.set_modes |
|
| Directory | dir.list, dir.create, dir.remove |
| Process | process.run, process.start, process.read, |
process.write, process.kill |
|
| PTY | process.start_pty, process.resize |
| System | system.info, system.getenv, system.statvfs |
| Batch | batch (N ops in 1 round-trip, sequential or parallel) |
| Watch | watch.add, watch.remove, watch.list → fs.changed |
Key advantages over shell parsing:
| Aspect | Current (shell parsing) | With tramp-agent |
|---|---|---|
| Communication | Individual SSH exec commands | MsgPack-RPC over single pipe |
| Latency | N operations = N round-trips | N operations = 1 round-trip |
| Binary data | base64 encode/decode | Native binary (MsgPack bin) |
| Stat + list | Separate commands, text parse | Native lstat() syscalls |
| Shell dependency | Requires sh, stat, cat, etc. | None (self-contained binary) |
| Cache invalidation | TTL-based (5s default) | inotify/kqueue push events |
Automatic agent deployment (SSH)#
When the plugin connects to a remote host via SSH, it attempts to deploy
and start the tramp-agent binary automatically:
Connect via SSH
│
▼
Agent already deployed? ──yes──► Start agent, use RPC backend
│ no
▼
Detect remote arch (uname -sm)
│
▼
Check local cache (~/.cache/nu-plugin-tramp/<version>/<triple>/tramp-agent)
│
├─ Found ──────────────► Upload via SFTP, chmod, start
│
▼
No cached binary available
│
▼
Fall back to shell-parsing mode (SshBackend)
The deployment is completely transparent — if anything fails at any step,
the plugin silently falls back to the existing shell-parsing SSH backend.
When the agent is available, an RpcBackend replaces SshBackend and all
operations go through the MsgPack-RPC pipe instead of spawning individual
shell commands.
To pre-cache an agent binary for a target, place it at:
~/.cache/nu-plugin-tramp/<version>/<target-triple>/tramp-agent
Supported target triples: x86_64-unknown-linux-musl, aarch64-unknown-linux-musl,
x86_64-apple-darwin, aarch64-apple-darwin, x86_64-unknown-freebsd.
Container agent deployment#
The same agent deployment concept extends to Docker and Kubernetes backends.
For standalone containers (not behind an SSH hop), the plugin attempts to
copy the tramp-agent binary into the container and start it as an
interactive process with piped stdin/stdout:
Standalone Docker/K8s:
┌──────────────────┐ docker exec -i ┌───────────────────┐
│ nu-plugin-tramp │ ◄──────────────► │ tramp-agent │
│ (local Nushell) │ MsgPack-RPC │ (in container) │
└──────────────────┘ └───────────────────┘
Chained (SSH → Docker):
┌──────────────────┐ SSH pipe ┌───────────┐ docker exec ┌───────────┐
│ nu-plugin-tramp │ ◄───────► │ agent │ ◄─────────► │ container │
│ (local Nushell) │ MsgPack │ (remote) │ shell cmds │ │
└──────────────────┘ └───────────┘ └───────────┘
The deployment flow for containers (crates/plugin/src/backend/deploy_exec.rs):
- Detect container arch via
docker exec <ctr> uname -sm/kubectl exec <pod> -- uname -sm - Check if agent is already deployed inside the container
- Find cached binary locally → copy into container:
- Docker:
docker cp <local_tmp> <container>:/tmp/tramp-agent-dir/tramp-agent - Kubernetes:
kubectl cpwith base64 exec fallback (for minimal images lackingtar)
- Docker:
- Start agent:
docker exec -i <ctr> /tmp/tramp-agent-dir/tramp-agent(orkubectl exec -i) - Ping to verify, then switch to
RpcBackend
For chained paths (e.g. /ssh:host|docker:ctr:/path), the parent SSH hop
already uses the RPC agent, so Docker/K8s commands routed through it benefit
from the SSH agent's performance. The shell-parsing ExecBackend is used
for the container hop in this case.
Layers#
- Path Parser (
crates/plugin/src/protocol.rs) — Parses TRAMP URIs into structured types with round-trip fidelity; supports multi-hop chained paths - Backend Trait (
crates/plugin/src/backend/mod.rs) — Async trait definingread,write,list,stat,exec,delete, andcheck(health-check) - CommandRunner (
crates/plugin/src/backend/runner.rs) — Abstraction for executing commands locally (LocalRunner) or through a parent backend (RemoteRunner), enabling path chaining - ExecBackend (
crates/plugin/src/backend/exec.rs) — Generic exec-based backend used by Docker, Kubernetes, and Sudo; wraps commands with a configurable prefix - SSH Backend (
crates/plugin/src/backend/ssh.rs) — Uses SFTP for file read/write/delete (fast-path) with automatic fallback to remote command execution; listing and stat use batch-stat (single remote command for all metadata including owner, group, nlinks, inode, symlink targets) - RPC Backend (
crates/plugin/src/backend/rpc.rs) — ImplementsBackendby sending MsgPack-RPC calls to a runningtramp-agent; used automatically when the agent is deployed - RPC Client (
crates/plugin/src/backend/rpc_client.rs) — Client-side MsgPack-RPC framing (length-prefixed messages, request/response matching, notification buffering) - SSH Agent Deployment (
crates/plugin/src/backend/deploy.rs) — Detects remote arch, manages local binary cache, uploads agent via SFTP or exec fallback, starts the agent process - Container Agent Deployment (
crates/plugin/src/backend/deploy_exec.rs) — Deploys agent into Docker/K8s containers viadocker cp/kubectl cp(with base64 fallback), starts agent via interactivedocker exec -i/kubectl exec -i - VFS (
crates/plugin/src/vfs.rs) — Resolves paths to backends, builds multi-hop chains, manages connection pooling with health-checks, provides stat/list caching with TTL, bridges async↔sync; automatically attempts RPC backend for SSH, Docker, and Kubernetes hops - RPC Protocol (
crates/agent/src/rpc.rs) — Length-prefixed MsgPack framing with Request/Response/Notification message types - Agent Operations (
crates/agent/src/ops/) — Native implementations of file, directory, process, system, batch, and watch operations
Chaining internals#
When resolving a chained path like /ssh:myvm|docker:ctr:/path, the VFS:
- Creates an
SshBackendfor the first hop (ssh:myvm) - Creates a
RemoteRunnerwrapping the SSH backend - Creates an
ExecBackend::docker(remote_runner, "ctr")for the second hop - The Docker backend's commands (e.g.
docker exec ctr cat /path) execute through the SSH session
This composable design means any combination of backends can be chained (except SSH-through-SSH, which should use ProxyJump).
Roadmap#
Phase 1 — MVP ✅#
- TRAMP URI parser (single-hop SSH only)
- SSH backend via
opensshcrate -
tramp open,tramp ls,tramp save,tramp rm - Nushell plugin compiles and registers correctly
- README with install + usage
- Nix package derivation
Phase 2 — Daily Driver ✅#
- Connection pooling with health-checks + automatic reconnection
-
tramp cd/tramp pwdwith relative path resolution - Stat + directory listing cache (with 5s TTL)
-
tramp cpbetween local/remote/remote -
tramp ping,tramp connections,tramp disconnectcommands - SFTP fast-path for file read/write/delete with automatic exec fallback
Phase 3 — Power Features ✅#
- Path chaining (SSH → Docker, SSH → Sudo, triple chains, etc.)
- Docker backend (
docker exec) - Kubernetes backend (
kubectl exec) - Sudo backend
- Push execution model (
tramp exec)
Phase 4 — Polish & Usability ✅#
-
tramp infocommand — remote system info (OS, arch, hostname, user, disk) in one round-trip - Richer
tramp lsmetadata — owner, group, nlinks, inode, symlink targets - Batch stat in listings — all metadata gathered in a single remote command
- Configurable cache TTL via
$env.TRAMP_CACHE_TTL - Home Manager module for auto-registration (
hm-module.nix) - Glob/wildcard support for
tramp ls --globandtramp cp --glob - Tab completion for remote paths
Phase 5 — RPC Agent ✅ (inspired by emacs-tramp-rpc)#
-
tramp-agentbinary — lightweight Rust RPC server (MsgPack-RPC over stdin/stdout) - Protocol design — 4-byte length-prefixed MessagePack framing with JSON-RPC 2.0-style messages
- File operations —
file.stat,file.stat_batch,file.truename,file.read,file.write,file.copy,file.rename,file.delete,file.set_modes - Directory operations —
dir.list(with full lstat metadata),dir.create,dir.remove - Process operations —
process.run,process.start,process.read,process.write,process.kill - System operations —
system.info,system.getenv,system.statvfs - Batch operations —
batch(N ops in 1 round-trip, sequential or parallel) - Filesystem watching —
watch.add,watch.remove,watch.listvia inotify/kqueue withfs.changedpush notifications - Cargo workspace restructure — plugin and agent as separate crates
- Automatic agent deployment (detect arch, upload, fallback to shell-parsing)
- Plugin RPC backend (switch SSH backend to use agent when available)
- Agent for exec backends (deploy inside Docker/K8s containers)
Phase 6 — Future ✅#
- Streaming for very large files (chunked RPC reads/writes)
- PTY support via agent (remote terminal emulation)
-
tramp watchcommand — subscribe to filesystem change notifications - Agent version management and auto-upgrade
- TCP/Unix socket transport for agent (local Docker without SSH)
- Cross-compilation matrix for agent binaries (x86_64/aarch64 × Linux/macOS)
License#
MIT