pālana

The plan is the product

Every file manager acts. pālana shows its work first—and the work it shows is, to the letter, the work it does.

What other file managers hide#

Drag a file from server A to server B in a network-capable Mac file manager and the bytes travel A → your laptop → B. The interface presents a direct operation while executing an indirect one. For a 100 GB archive moving between ZFS pools, that indirection is slow, fragile, and baffling when it fails—because you didn't know your machine was in the middle.

ZFS adds another hidden distinction. When services live in separate datasets, a "rename" across datasets is a full copy followed by a delete. Tools that flatten this into a rename can conceal substantial work until after it has started.

pālana's answer is to change what happens, not how it looks. Between your keystroke and any mutation stands a plan: a readable statement of what will run, composed from probed facts, shown whole. Nothing mutates while a plan is on screen. Enter enacts it. Esc dismisses it, and nothing has happened. Dry-run is not a mode you remember to turn on. It is the default state of every operation.

What a plan says#

move · the plan ⏎ enter runs
⏎ press enter to run this plan · esc dismisses it
move · move to another machine
1 entry · 41.3 GB
will merge into archive-2025
jodo:/tank/media/camera → koan:/rpool/cold
how: rsync, run host-to-host · using the forwarded ssh key

$ rsync -a -s --partial --info=progress2 /tank/media/camera/archive-2025 koan:/rpool/cold/
  runs only after the copy above is verified: $ rm -rf /tank/media/camera/archive-2025
A move, planned. Six facts, top to bottom—each one read from the hosts, none of them decoration.

What you asked, and what it actually is. The first line pairs your verb with the operation's real shape: move · move to another machine. You said move; the plan says what a move is here.

Size. 1 entry · 41.3 GB is measured recursively on the host, not inferred from the directory inode. When a folder cannot be measured, the plan marks the result as a lower bound: this is a floor—a folder couldn't be measured.

What's in the way. Before a plan is offered, the destination is read. Anything standing where your files will land is named — will merge into archive-2025, or will replace photos.db (1.2 MB)—in rust, before Enter, not in an error after. When the destination can't be checked, the plan says that instead: couldn't check the destination—this may overwrite files there.

The route. Source and destination as host:path, the same names your ssh config uses.

Transport. The plan names the tool, execution host, and authentication path. More on this below.

The commands. Every step that will run, verbatim, with its gate stated inline. The delete—the dangerous half of a move—is declared gated: it does not run until the copy is verified. The gate isn't a comforting sentence in documentation. It's a property of the plan you're reading.

One key, three operations#

Press m on the same 41.3 GB folder in three different worlds, and read what comes back.

The destination is in the same dataset:

move · the plan ⏎ enter runs
move · move on the same disk (instant)
1 entry · 41.3 GB
jodo:/tank/media/camera → jodo:/tank/media/film
how: runs locally on the one host involved

$ mv /tank/media/camera/archive-2025 /tank/media/film/
A true rename. One command, no data moved, done before your finger lifts.

The destination is another dataset on the same host:

move · the plan ⏎ enter runs
move · copy then delete the original
1 entry · 41.3 GB
jodo:/tank/media/camera → jodo:/rust/archive
how: runs locally on the one host involved

$ rsync -a -s --partial --info=progress2 /tank/media/camera/archive-2025 /rust/archive/
  runs only after the copy above is verified: $ rm -rf /tank/media/camera/archive-2025
Same host, same keystroke, different operation. A dataset boundary sits between the panes, so this move copies 41.3 GB and deletes the source only after verification.

The destination is another machine—the plan at the top of this page: a cross-host transfer, rsync running host-to-host, the delete gated on verification.

Same keystroke, three shapes. The classification isn't guessed from paths — it's computed from dataset boundaries the Field actually probed. Different facts in, different plan out: the moment an operation stops being what it looks like is exactly the moment you most need to be told.

The transport is named#

A plan doesn't just say what. It says how—which tool moves the bytes, which machine runs it, and who authenticates to whom.

jodo koan your Mac the bytes · 41.3 GB the order: rsync, run on jodo

The fast path forwards your SSH agent: jodo authenticates to koan directly, your key never leaves your Mac, and the bytes go host to host while your machine only orchestrates. When forwarding isn't available—or one end has no rsync—pālana falls back, and the plan says so in the same plain line:

how: runs locally on the one host involved
how: rsync, run host-to-host · using the forwarded ssh key
how: rsync, run from this Mac · using this Mac's ssh keys
how: tar stream, proxied through this Mac · no rsync at one end
how: tar stream, run from this Mac · using this Mac's ssh keys
how: zfs send/receive, run host-to-host · using the forwarded ssh key
how: zfs send/receive, proxied through this Mac

The engine selects among seven routes from probed facts: host reachability, available rsync versions, and whether both ends are whole ZFS datasets. The plan makes that selection visible. If it says "proxied through this Mac," your laptop is in the byte path and the route states why.

Run and verify#

The plan panel is a real terminal surface: when Enter fires, the enactment echoes into it live—the same commands the plan showed, now with their actual output streaming under them.

move · done
$ rsync -a -s --partial --info=progress2 /tank/media/camera/archive-2025 koan:/rpool/cold/
 41,334,982,656 100%  112.43MB/s    0:05:51 (xfr#3412, to-chk=0/3413)
step 1 finished (exit 0)
check on jodo: $ find /tank/media/camera/archive-2025 | wc -l
check on koan: $ find /rpool/cold/archive-2025 | wc -l
counted 3413 at source, 3413 at destination—match
$ rm -rf /tank/media/camera/archive-2025
step 2 finished (exit 0)
done—every step ran and checked out
The run, live. The delete was gated—and here is the gate opening: two count commands, one on each host, shown like everything else.

The transfer finishes. Then the verification runs — not a spinner, two more commands you can read, counting what landed against what left. Only when the counts match does the gated delete run. If they didn't match, it wouldn't. (Mid-run, ⌃c cancels; a plan that hasn't been enacted is dismissed with esc and costs nothing.)

That is the whole trust story: the interface claims "these are the commands," and the claim is checkable by watching them run.

The paste test#

Every command in every plan passes one test: you could select it, paste it into your own terminal, and get the same result. That falls out of two architectural commitments.

pālana wraps your own tools. There is no embedded SSH library, no parallel transport stack—the system ssh, the hosts' own rsync and zfs. A command that runs through pālana and a command you type by hand travel the same road with the same identity, so the plan can quote reality instead of approximating it.

The plan engine is a pure function. Facts in, plan out; it performs no I/O itself. That makes its classifications and commands directly testable.

Read the plan, then press Enter. Every step is shown before it runs and its output appears in the same panel.

Try the beta →