git.delta.rocks / fleet / refs/commits / 7bc4be6bcef6

difftreelog

feat remowt integration

kyumtlkpYaroslav Bolyukin2026-06-05parent: #7bbada6.patch.diff

46 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3,15 +3,6 @@
 version = 4
 
 [[package]]
-name = "abort-on-drop"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dd6d700ad9af641490c1f7d67980d2de4d1433016e5b12f819448d3c832142a"
-dependencies = [
- "tokio",
-]
-
-[[package]]
 name = "adler2"
 version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -23,54 +14,91 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
 dependencies = [
- "crypto-common",
- "generic-array",
+ "crypto-common 0.1.7",
+ "generic-array 0.14.7",
 ]
 
 [[package]]
+name = "aead"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef60ac202874e574ce7a7158cc8bca7313dd344322482e4fadee288bf4a306b8"
+dependencies = [
+ "crypto-common 0.2.2",
+ "inout 0.2.2",
+]
+
+[[package]]
 name = "aes"
 version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
 dependencies = [
  "cfg-if",
- "cipher",
+ "cipher 0.4.4",
  "cpufeatures 0.2.17",
 ]
 
 [[package]]
+name = "aes"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138"
+dependencies = [
+ "cipher 0.5.2",
+ "cpubits",
+ "cpufeatures 0.3.0",
+ "zeroize",
+]
+
+[[package]]
 name = "aes-gcm"
 version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
 dependencies = [
- "aead",
- "aes",
- "cipher",
- "ctr",
- "ghash",
+ "aead 0.5.2",
+ "aes 0.8.4",
+ "cipher 0.4.4",
+ "ctr 0.9.2",
+ "ghash 0.5.1",
  "subtle",
 ]
 
 [[package]]
+name = "aes-gcm"
+version = "0.11.0-rc.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da8c919c118108f144adecad74b425b804ad075580d605d9b33c2d6d1c62a2f8"
+dependencies = [
+ "aead 0.6.0",
+ "aes 0.9.1",
+ "cipher 0.5.2",
+ "ctr 0.10.1",
+ "ghash 0.6.0",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
 name = "age"
-version = "0.11.2"
+version = "0.11.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf640be7658959746f1f0f2faab798f6098a9436a8e18e148d18bc9875e13c4b"
+checksum = "a07d86e4272c093c88caf7864a2d09af52a5159180848ca4832a3cdbd7d014d5"
 dependencies = [
- "aes",
- "aes-gcm",
+ "aes 0.8.4",
+ "aes-gcm 0.10.3",
  "age-core",
  "base64 0.21.7",
- "bcrypt-pbkdf",
+ "bcrypt-pbkdf 0.10.0",
  "bech32",
- "cbc",
+ "cbc 0.1.2",
  "chacha20poly1305",
- "cipher",
+ "cipher 0.4.4",
  "cookie-factory",
- "ctr",
- "curve25519-dalek",
- "hmac",
+ "ctr 0.9.2",
+ "curve25519-dalek 4.1.3",
+ "hmac 0.12.1",
  "i18n-embed",
  "i18n-embed-fl",
  "lazy_static",
@@ -78,10 +106,10 @@
  "num-traits",
  "pin-project",
  "rand 0.8.6",
- "rsa",
+ "rsa 0.9.10",
  "rust-embed",
- "scrypt",
- "sha2",
+ "scrypt 0.11.0",
+ "sha2 0.10.9",
  "subtle",
  "which",
  "wsl",
@@ -98,12 +126,12 @@
  "base64 0.21.7",
  "chacha20poly1305",
  "cookie-factory",
- "hkdf",
+ "hkdf 0.12.4",
  "io_tee",
  "nom 7.1.3",
  "rand 0.8.6",
  "secrecy",
- "sha2",
+ "sha2 0.10.9",
  "tempfile",
 ]
 
@@ -191,34 +219,144 @@
 ]
 
 [[package]]
+name = "argon2"
+version = "0.6.0-rc.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7af50940b73bf4e16c15c448a2b121c63f2d68e3e54b6a8731673cb4aa0cdff5"
+dependencies = [
+ "base64ct",
+ "blake2",
+ "cpufeatures 0.3.0",
+ "password-hash",
+]
+
+[[package]]
 name = "arrayvec"
 version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
 
 [[package]]
-name = "async-stream"
-version = "0.3.6"
+name = "async-broadcast"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
 dependencies = [
- "async-stream-impl",
+ "concurrent-queue",
+ "event-listener-strategy",
  "futures-core",
  "pin-project-lite",
 ]
 
 [[package]]
-name = "async-stream-impl"
-version = "0.3.6"
+name = "async-executor"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-io"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix 1.1.4",
+ "slab",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
+dependencies = [
+ "async-channel",
+ "async-io",
+ "async-lock",
+ "async-signal",
+ "async-task",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix 1.1.4",
+]
+
+[[package]]
+name = "async-recursion"
+version = "1.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
+checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 1.1.4",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.61.2",
 ]
 
 [[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
 name = "async-trait"
 version = "0.1.89"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -226,10 +364,19 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
+name = "async_fn_traits"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc58d489c5f2d2c5be31b9004cec7af25a70d23df4d8111715eee736234cf217"
+dependencies = [
+ "paste",
+]
+
+[[package]]
 name = "atomic-waker"
 version = "1.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -237,56 +384,15 @@
 
 [[package]]
 name = "autocfg"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
-
-[[package]]
-name = "axum"
-version = "0.7.9"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
-dependencies = [
- "async-trait",
- "axum-core",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "itoa",
- "matchit",
- "memchr",
- "mime",
- "percent-encoding",
- "pin-project-lite",
- "rustversion",
- "serde",
- "sync_wrapper",
- "tower 0.5.3",
- "tower-layer",
- "tower-service",
-]
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
 
 [[package]]
-name = "axum-core"
-version = "0.4.5"
+name = "base16ct"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
-dependencies = [
- "async-trait",
- "bytes",
- "futures-util",
- "http",
- "http-body",
- "http-body-util",
- "mime",
- "pin-project-lite",
- "rustversion",
- "sync_wrapper",
- "tower-layer",
- "tower-service",
-]
+checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6"
 
 [[package]]
 name = "base64"
@@ -321,9 +427,20 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6aeac2e1fe888769f34f05ac343bbef98b14d1ffb292ab69d4608b3abc86f2a2"
 dependencies = [
- "blowfish",
- "pbkdf2",
- "sha2",
+ "blowfish 0.9.1",
+ "pbkdf2 0.12.2",
+ "sha2 0.10.9",
+]
+
+[[package]]
+name = "bcrypt-pbkdf"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "144e573728da132683b9488acd528274c790e07fc06ff81ee29f9d8f8b1041e0"
+dependencies = [
+ "blowfish 0.10.0",
+ "pbkdf2 0.13.0",
+ "sha2 0.11.0",
 ]
 
 [[package]]
@@ -333,14 +450,45 @@
 checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"
 
 [[package]]
-name = "better-command"
-version = "0.1.0"
+name = "bifrostlink"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fb01af731c11dd31b23783a83a36a29f644cc1972481f6fa4f4fabc709079eb"
 dependencies = [
- "regex",
+ "async-trait",
+ "async_fn_traits",
+ "bifrostlink-macros",
+ "bytes",
+ "derivative",
+ "futures",
  "serde",
  "serde_json",
+ "tokio",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
+name = "bifrostlink-macros"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c4b7a5fb38b36bd81910c17ebf369f9296e508d92b1277a768a63c8a2254fdb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "bifrostlink-ports"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "977acfcb8ed3c24ab7c2f76fb3eeebff1533c72708733ce6020f2501980b7cf2"
+dependencies = [
+ "bifrostlink",
+ "bytes",
+ "tokio",
  "tracing",
- "tracing-indicatif",
 ]
 
 [[package]]
@@ -359,15 +507,24 @@
  "quote",
  "regex",
  "rustc-hash 2.1.2",
- "shlex",
- "syn",
+ "shlex 1.3.0",
+ "syn 2.0.117",
 ]
 
 [[package]]
 name = "bitflags"
-version = "2.11.1"
+version = "2.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
+
+[[package]]
+name = "blake2"
+version = "0.11.0-rc.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "061f1a09225e328e1ffbb378d2d49923c0ca5fee19fb5ac1cc9c1e9d52b93690"
+dependencies = [
+ "digest 0.11.3",
+]
 
 [[package]]
 name = "block-buffer"
@@ -375,33 +532,85 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.7",
 ]
 
 [[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+ "zeroize",
+]
+
+[[package]]
 name = "block-padding"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.7",
 ]
 
 [[package]]
+name = "block-padding"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "blocking"
+version = "1.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite",
+ "piper",
+]
+
+[[package]]
 name = "blowfish"
 version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
 dependencies = [
  "byteorder",
- "cipher",
+ "cipher 0.4.4",
+]
+
+[[package]]
+name = "blowfish"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62ce3946557b35e71d1bbe07ec385073ce9eda05043f95de134eb578fcf1a298"
+dependencies = [
+ "byteorder",
+ "cipher 0.5.2",
+]
+
+[[package]]
+name = "bstr"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
+dependencies = [
+ "memchr",
+ "serde",
 ]
 
 [[package]]
 name = "bumpalo"
-version = "3.20.2"
+version = "3.20.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
 
 [[package]]
 name = "bytecount"
@@ -422,22 +631,40 @@
 checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
 
 [[package]]
+name = "camino"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
 name = "cbc"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
 dependencies = [
- "cipher",
+ "cipher 0.4.4",
 ]
 
 [[package]]
+name = "cbc"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
+dependencies = [
+ "cipher 0.5.2",
+]
+
+[[package]]
 name = "cc"
-version = "1.2.60"
+version = "1.2.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
+checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
 dependencies = [
  "find-msvc-tools",
- "shlex",
+ "shlex 2.0.1",
 ]
 
 [[package]]
@@ -468,7 +695,7 @@
 checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
 dependencies = [
  "cfg-if",
- "cipher",
+ "cipher 0.4.4",
  "cpufeatures 0.2.17",
 ]
 
@@ -479,8 +706,10 @@
 checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
 dependencies = [
  "cfg-if",
+ "cipher 0.5.2",
  "cpufeatures 0.3.0",
  "rand_core 0.10.1",
+ "zeroize",
 ]
 
 [[package]]
@@ -489,18 +718,18 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
 dependencies = [
- "aead",
+ "aead 0.5.2",
  "chacha20 0.9.1",
- "cipher",
- "poly1305",
+ "cipher 0.4.4",
+ "poly1305 0.8.0",
  "zeroize",
 ]
 
 [[package]]
 name = "chrono"
-version = "0.4.44"
+version = "0.4.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
 dependencies = [
  "iana-time-zone",
  "js-sys",
@@ -516,8 +745,20 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
 dependencies = [
- "crypto-common",
- "inout",
+ "crypto-common 0.1.7",
+ "inout 0.1.4",
+ "zeroize",
+]
+
+[[package]]
+name = "cipher"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
+dependencies = [
+ "block-buffer 0.12.1",
+ "crypto-common 0.2.2",
+ "inout 0.2.2",
  "zeroize",
 ]
 
@@ -559,9 +800,9 @@
 
 [[package]]
 name = "clap_complete"
-version = "4.6.2"
+version = "4.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ff7a1dccbdd8b078c2bdebff47e404615151534d5043da397ec50286816f9cb"
+checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772"
 dependencies = [
  "clap",
 ]
@@ -575,7 +816,7 @@
  "heck",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -585,6 +826,12 @@
 checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
 
 [[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
+[[package]]
 name = "codespan-reporting"
 version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -602,6 +849,15 @@
 checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
 
 [[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
 name = "console"
 version = "0.16.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -615,9 +871,9 @@
 
 [[package]]
 name = "const-hex"
-version = "1.18.1"
+version = "1.19.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "531185e432bb31db1ecda541e9e7ab21468d4d844ad7505e0546a49b4945d49b"
+checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558"
 dependencies = [
  "cfg-if",
  "cpufeatures 0.2.17",
@@ -632,6 +888,12 @@
 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
 
 [[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
 name = "cookie-factory"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -657,6 +919,12 @@
 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
 [[package]]
+name = "cpubits"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
+
+[[package]]
 name = "cpufeatures"
 version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -684,23 +952,86 @@
 ]
 
 [[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-bigint"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3"
+dependencies = [
+ "cpubits",
+ "ctutils",
+ "getrandom 0.4.2",
+ "hybrid-array",
+ "num-traits",
+ "rand_core 0.10.1",
+ "serdect",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
 name = "crypto-common"
 version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
 dependencies = [
- "generic-array",
+ "generic-array 0.14.7",
  "rand_core 0.6.4",
  "typenum",
 ]
 
 [[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "getrandom 0.4.2",
+ "hybrid-array",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "crypto-primes"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3633a51a39c69ebbaa4feaa694bd83d241e4093901c84a0963b19d9bb3f0cf8f"
+dependencies = [
+ "crypto-bigint",
+ "rand_core 0.10.1",
+]
+
+[[package]]
 name = "ctr"
 version = "0.9.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
 dependencies = [
- "cipher",
+ "cipher 0.4.4",
+]
+
+[[package]]
+name = "ctr"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21"
+dependencies = [
+ "cipher 0.5.2",
+]
+
+[[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+ "subtle",
 ]
 
 [[package]]
@@ -718,14 +1049,30 @@
  "cfg-if",
  "cpufeatures 0.2.17",
  "curve25519-dalek-derive",
- "digest",
- "fiat-crypto",
+ "digest 0.10.7",
+ "fiat-crypto 0.2.9",
  "rustc_version",
  "subtle",
  "zeroize",
 ]
 
 [[package]]
+name = "curve25519-dalek"
+version = "5.0.0-rc.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f359e08ca85e7bd759e1fd933ff2bccd81864c60a8fba0e259c7f822b0924bf"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "curve25519-dalek-derive",
+ "digest 0.11.3",
+ "fiat-crypto 0.3.0",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
 name = "curve25519-dalek-derive"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -733,7 +1080,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -759,11 +1106,11 @@
 dependencies = [
  "cc",
  "codespan-reporting",
- "indexmap 2.14.0",
+ "indexmap",
  "proc-macro2",
  "quote",
  "scratch",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -774,10 +1121,10 @@
 dependencies = [
  "clap",
  "codespan-reporting",
- "indexmap 2.14.0",
+ "indexmap",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -792,10 +1139,27 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf"
 dependencies = [
- "indexmap 2.14.0",
+ "indexmap",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
+
+[[package]]
+name = "delegate"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370"
+dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -804,7 +1168,18 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
 dependencies = [
- "const-oid",
+ "const-oid 0.9.6",
+ "zeroize",
+]
+
+[[package]]
+name = "der"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b"
+dependencies = [
+ "const-oid 0.10.2",
+ "pem-rfc7468",
  "zeroize",
 ]
 
@@ -813,9 +1188,25 @@
 version = "0.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "des"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "916a94e407b54f9034d71dd748234cd1e516ced6284009906ae246f177eafe5a"
 dependencies = [
- "powerfmt",
- "serde_core",
+ "cipher 0.5.2",
 ]
 
 [[package]]
@@ -824,21 +1215,48 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
 dependencies = [
- "block-buffer",
- "const-oid",
- "crypto-common",
+ "block-buffer 0.10.4",
+ "const-oid 0.9.6",
+ "crypto-common 0.1.7",
  "subtle",
 ]
 
 [[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "const-oid 0.10.2",
+ "crypto-common 0.2.2",
+ "ctutils",
+]
+
+[[package]]
 name = "displaydoc"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "ecdsa"
+version = "0.17.0-rc.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54fb064faabbee66e1fc8e5c5a9458d4269dc2d8b638fe86a425adb2510d1a96"
+dependencies = [
+ "der 0.8.0",
+ "digest 0.11.3",
+ "elliptic-curve",
+ "rfc6979",
+ "signature 3.0.0",
+ "spki 0.8.0",
+ "zeroize",
 ]
 
 [[package]]
@@ -847,29 +1265,78 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
 dependencies = [
- "pkcs8",
- "signature",
+ "pkcs8 0.10.2",
+ "signature 2.2.0",
 ]
 
 [[package]]
+name = "ed25519"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
+dependencies = [
+ "pkcs8 0.11.0",
+ "signature 3.0.0",
+]
+
+[[package]]
 name = "ed25519-dalek"
 version = "2.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
 dependencies = [
- "curve25519-dalek",
- "ed25519",
+ "curve25519-dalek 4.1.3",
+ "ed25519 2.2.3",
  "serde",
- "sha2",
+ "sha2 0.10.9",
  "subtle",
  "zeroize",
 ]
 
 [[package]]
+name = "ed25519-dalek"
+version = "3.0.0-rc.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b011170fe4f04665565b4110afef66774fe9ffff278f3eb5b81cc73d26e27d60"
+dependencies = [
+ "curve25519-dalek 5.0.0-rc.0",
+ "ed25519 3.0.0",
+ "rand_core 0.10.1",
+ "serde",
+ "sha2 0.11.0",
+ "signature 3.0.0",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
 name = "either"
-version = "1.15.0"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
+
+[[package]]
+name = "elliptic-curve"
+version = "0.14.0-rc.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+checksum = "102d3643d30dd8b559613c5cced68317199597fffb278cdc88daa2ef7fafc935"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "crypto-common 0.2.2",
+ "digest 0.11.3",
+ "ff",
+ "group",
+ "hkdf 0.13.0",
+ "hybrid-array",
+ "once_cell",
+ "pem-rfc7468",
+ "pkcs8 0.11.0",
+ "rand_core 0.10.1",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
 
 [[package]]
 name = "encode_unicode"
@@ -878,6 +1345,45 @@
 checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
 
 [[package]]
+name = "endi"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
+
+[[package]]
+name = "enum_dispatch"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
+dependencies = [
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "enumflags2"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
 name = "env_filter"
 version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -915,18 +1421,55 @@
 ]
 
 [[package]]
+name = "event-listener"
+version = "5.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
+[[package]]
 name = "fastrand"
 version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
 
 [[package]]
+name = "ff"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f686ab92a9fb0eaf188f6c6c87b89490baa6fdb0db4544ba4dc47f7942489f"
+dependencies = [
+ "rand_core 0.10.1",
+ "subtle",
+]
+
+[[package]]
 name = "fiat-crypto"
 version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
 
 [[package]]
+name = "fiat-crypto"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
+
+[[package]]
 name = "find-crate"
 version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -940,12 +1483,6 @@
 version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
-
-[[package]]
-name = "fixedbitset"
-version = "0.5.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
 
 [[package]]
 name = "flate2"
@@ -961,42 +1498,27 @@
 name = "fleet"
 version = "0.2.0"
 dependencies = [
- "abort-on-drop",
- "age",
- "age-core",
  "anyhow",
- "async-trait",
- "base64 0.22.1",
- "better-command",
- "chrono",
+ "camino",
  "clap",
  "clap_complete",
  "fleet-base",
- "fleet-shared",
  "futures",
- "hostname",
  "human-repr",
  "indicatif",
  "itertools 0.14.0",
  "nix-eval",
- "nixlike",
- "nom 8.0.0",
- "openssh",
  "opentelemetry",
  "opentelemetry-appender-tracing",
  "opentelemetry-exporter-env",
  "opentelemetry_sdk",
- "peg",
- "regex",
+ "remowt-fleet",
  "serde",
  "serde_json",
- "shlex",
+ "shlex 1.3.0",
  "tabled",
  "tempfile",
- "thiserror 2.0.18",
- "time",
  "tokio",
- "tokio-util",
  "tracing",
  "tracing-indicatif",
  "tracing-opentelemetry",
@@ -1009,7 +1531,7 @@
 dependencies = [
  "age",
  "anyhow",
- "better-command",
+ "camino",
  "chrono",
  "clap",
  "fleet-shared",
@@ -1020,17 +1542,18 @@
  "nix-eval",
  "nixlike",
  "nom 8.0.0",
- "openssh",
  "rand 0.10.1",
+ "remowt-client",
+ "remowt-endpoints",
+ "remowt-fleet",
+ "remowt-link-shared",
+ "remowt-ui-prompt",
  "serde",
  "serde_json",
  "tabled",
  "tempfile",
- "thiserror 2.0.18",
  "time",
  "tokio",
- "tokio-util",
- "toml_edit",
  "tracing",
 ]
 
@@ -1042,13 +1565,13 @@
  "anyhow",
  "base64 0.22.1",
  "clap",
- "ed25519-dalek",
+ "ed25519-dalek 2.2.0",
  "fleet-shared",
  "hex",
- "hmac",
- "pbkdf2",
+ "hmac 0.12.1",
+ "pbkdf2 0.12.2",
  "rand 0.10.1",
- "sha2",
+ "sha2 0.10.9",
  "x25519-dalek",
 ]
 
@@ -1197,6 +1720,19 @@
 checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
 
 [[package]]
+name = "futures-lite"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
 name = "futures-macro"
 version = "0.3.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1204,7 +1740,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -1247,6 +1783,17 @@
 ]
 
 [[package]]
+name = "generic-array"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2e55f16dcf0e9c00efbe2e655ffe45fc98e7066b52bc92f8a79e64060a79351"
+dependencies = [
+ "generic-array 0.14.7",
+ "rustversion",
+ "typenum",
+]
+
+[[package]]
 name = "getrandom"
 version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1280,11 +1827,13 @@
 checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
 dependencies = [
  "cfg-if",
+ "js-sys",
  "libc",
  "r-efi 6.0.0",
  "rand_core 0.10.1",
  "wasip2",
  "wasip3",
+ "wasm-bindgen",
 ]
 
 [[package]]
@@ -1294,20 +1843,53 @@
 checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
 dependencies = [
  "opaque-debug",
- "polyval",
+ "polyval 0.6.2",
 ]
 
 [[package]]
+name = "ghash"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5"
+dependencies = [
+ "polyval 0.7.1",
+]
+
+[[package]]
 name = "glob"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
 
 [[package]]
+name = "globset"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "group"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fd1a1c7a5206c5b7a3f5a0d7ccd3ff85d0c8f5133d62a02680255b0004af5f4"
+dependencies = [
+ "ff",
+ "rand_core 0.10.1",
+ "subtle",
+]
+
+[[package]]
 name = "h2"
-version = "0.4.13"
+version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
+checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733"
 dependencies = [
  "atomic-waker",
  "bytes",
@@ -1315,7 +1897,7 @@
  "futures-core",
  "futures-sink",
  "http",
- "indexmap 2.14.0",
+ "indexmap",
  "slab",
  "tokio",
  "tokio-util",
@@ -1324,12 +1906,6 @@
 
 [[package]]
 name = "hashbrown"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-
-[[package]]
-name = "hashbrown"
 version = "0.15.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
@@ -1339,9 +1915,9 @@
 
 [[package]]
 name = "hashbrown"
-version = "0.17.0"
+version = "0.17.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
 
 [[package]]
 name = "heck"
@@ -1350,27 +1926,57 @@
 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
 
 [[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
 name = "hex"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
 
 [[package]]
+name = "hex-literal"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1"
+
+[[package]]
 name = "hkdf"
 version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
 dependencies = [
- "hmac",
+ "hmac 0.12.1",
 ]
 
 [[package]]
+name = "hkdf"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
+dependencies = [
+ "hmac 0.13.0",
+]
+
+[[package]]
 name = "hmac"
 version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
 dependencies = [
- "digest",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "hmac"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
+dependencies = [
+ "digest 0.11.3",
 ]
 
 [[package]]
@@ -1395,9 +2001,9 @@
 
 [[package]]
 name = "http"
-version = "1.4.0"
+version = "1.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
 dependencies = [
  "bytes",
  "itoa",
@@ -1433,22 +2039,28 @@
 checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
 
 [[package]]
-name = "httpdate"
-version = "1.0.3"
+name = "human-repr"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1"
 
 [[package]]
-name = "human-repr"
-version = "1.1.0"
+name = "hybrid-array"
+version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1"
+checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da"
+dependencies = [
+ "ctutils",
+ "subtle",
+ "typenum",
+ "zeroize",
+]
 
 [[package]]
 name = "hyper"
-version = "1.9.0"
+version = "1.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
+checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
 dependencies = [
  "atomic-waker",
  "bytes",
@@ -1458,7 +2070,6 @@
  "http",
  "http-body",
  "httparse",
- "httpdate",
  "itoa",
  "pin-project-lite",
  "smallvec",
@@ -1512,7 +2123,7 @@
  "libc",
  "percent-encoding",
  "pin-project-lite",
- "socket2 0.6.3",
+ "socket2",
  "tokio",
  "tower-service",
  "tracing",
@@ -1567,7 +2178,7 @@
  "proc-macro2",
  "quote",
  "strsim",
- "syn",
+ "syn 2.0.117",
  "unic-langid",
 ]
 
@@ -1581,7 +2192,7 @@
  "i18n-config",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -1709,9 +2320,9 @@
 
 [[package]]
 name = "idna_adapter"
-version = "1.2.1"
+version = "1.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
 dependencies = [
  "icu_normalizer",
  "icu_properties",
@@ -1719,22 +2330,12 @@
 
 [[package]]
 name = "indexmap"
-version = "1.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
-dependencies = [
- "autocfg",
- "hashbrown 0.12.3",
-]
-
-[[package]]
-name = "indexmap"
 version = "2.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
 dependencies = [
  "equivalent",
- "hashbrown 0.17.0",
+ "hashbrown 0.17.1",
  "serde",
  "serde_core",
 ]
@@ -1768,11 +2369,33 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
 dependencies = [
- "block-padding",
- "generic-array",
+ "block-padding 0.3.3",
+ "generic-array 0.14.7",
 ]
 
 [[package]]
+name = "inout"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
+dependencies = [
+ "block-padding 0.4.2",
+ "hybrid-array",
+]
+
+[[package]]
+name = "internal-russh-num-bigint"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae8e22120c32fb4d19ec55fba35015f57095cd95a2e3b732e44457f5915b2ee8"
+dependencies = [
+ "num-integer",
+ "num-traits",
+ "rand 0.10.1",
+ "rand_core 0.10.1",
+]
+
+[[package]]
 name = "intl-memoizer"
 version = "0.5.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1802,16 +2425,6 @@
 version = "2.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
-
-[[package]]
-name = "iri-string"
-version = "0.7.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
-dependencies = [
- "memchr",
- "serde",
-]
 
 [[package]]
 name = "is_terminal_polyfill"
@@ -1845,17 +2458,36 @@
 
 [[package]]
 name = "js-sys"
-version = "0.3.95"
+version = "0.3.102"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
+checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31"
 dependencies = [
  "cfg-if",
  "futures-util",
- "once_cell",
  "wasm-bindgen",
 ]
 
 [[package]]
+name = "keccak"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+]
+
+[[package]]
+name = "kem"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01737161ba802849cfd486b5bd209d38ba4943494c249a8126005170c7621edd"
+dependencies = [
+ "crypto-common 0.2.2",
+ "rand_core 0.10.1",
+]
+
+[[package]]
 name = "lazy_static"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1872,9 +2504,9 @@
 
 [[package]]
 name = "libc"
-version = "0.2.185"
+version = "0.2.186"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
+checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
 
 [[package]]
 name = "libloading"
@@ -1893,6 +2525,18 @@
 checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
 
 [[package]]
+name = "libredox"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
+dependencies = [
+ "bitflags",
+ "libc",
+ "plain",
+ "redox_syscall 0.8.1",
+]
+
+[[package]]
 name = "link-cplusplus"
 version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1936,9 +2580,9 @@
 
 [[package]]
 name = "log"
-version = "0.4.29"
+version = "0.4.32"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
 
 [[package]]
 name = "lru-slab"
@@ -1956,22 +2600,25 @@
 ]
 
 [[package]]
-name = "matchit"
-version = "0.7.3"
+name = "md5"
+version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
 
 [[package]]
 name = "memchr"
-version = "2.8.0"
+version = "2.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
 
 [[package]]
-name = "mime"
-version = "0.3.17"
+name = "memoffset"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
 
 [[package]]
 name = "minimal-lexical"
@@ -1991,9 +2638,9 @@
 
 [[package]]
 name = "mio"
-version = "1.2.0"
+version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
+checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
 dependencies = [
  "libc",
  "wasi",
@@ -2001,16 +2648,35 @@
 ]
 
 [[package]]
-name = "multimap"
-version = "0.10.1"
+name = "ml-kem"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e15f3e5b957493873e396a66914e83e616b6afe335cdef7efe5c6e1216aba66"
+dependencies = [
+ "hybrid-array",
+ "kem",
+ "module-lattice",
+ "pkcs8 0.11.0",
+ "rand_core 0.10.1",
+ "sha3",
+]
+
+[[package]]
+name = "module-lattice"
+version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
+checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe"
+dependencies = [
+ "ctutils",
+ "hybrid-array",
+ "num-traits",
+]
 
 [[package]]
 name = "nix"
-version = "0.31.2"
+version = "0.31.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
+checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
 dependencies = [
  "bitflags",
  "cfg-if",
@@ -2024,15 +2690,16 @@
 dependencies = [
  "anyhow",
  "bindgen",
+ "camino",
  "cxx",
  "cxx-build",
+ "futures",
  "itertools 0.14.0",
  "nixlike",
  "pkg-config",
  "serde",
  "serde_json",
  "test-log",
- "thiserror 2.0.18",
  "tokio",
  "tracing",
  "tracing-indicatif",
@@ -2040,6 +2707,13 @@
 ]
 
 [[package]]
+name = "nix-plugin-fleet"
+version = "0.1.0"
+dependencies = [
+ "fleet-base",
+]
+
+[[package]]
 name = "nixlike"
 version = "0.1.0"
 dependencies = [
@@ -2047,8 +2721,6 @@
  "linked-hash-map",
  "peg",
  "serde",
- "serde-transcode",
- "serde_json",
  "thiserror 2.0.18",
 ]
 
@@ -2081,6 +2753,16 @@
 ]
 
 [[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
 name = "num-bigint-dig"
 version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2098,9 +2780,9 @@
 
 [[package]]
 name = "num-conv"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
 
 [[package]]
 name = "num-integer"
@@ -2151,20 +2833,6 @@
 checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
 
 [[package]]
-name = "openssh"
-version = "0.11.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d534c4bfecb0ed71dea4db444a5922a294d15cf40e700548f27295e1feb0ef18"
-dependencies = [
- "libc",
- "once_cell",
- "shell-escape",
- "tempfile",
- "thiserror 2.0.18",
- "tokio",
-]
-
-[[package]]
 name = "openssl-probe"
 version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2229,12 +2897,12 @@
  "opentelemetry-http",
  "opentelemetry-proto",
  "opentelemetry_sdk",
- "prost 0.14.3",
+ "prost",
  "reqwest",
  "serde_json",
  "thiserror 2.0.18",
  "tokio",
- "tonic 0.14.5",
+ "tonic",
  "tracing",
 ]
 
@@ -2248,10 +2916,10 @@
  "const-hex",
  "opentelemetry",
  "opentelemetry_sdk",
- "prost 0.14.3",
+ "prost",
  "serde",
  "serde_json",
- "tonic 0.14.5",
+ "tonic",
  "tonic-prost",
 ]
 
@@ -2271,6 +2939,77 @@
 ]
 
 [[package]]
+name = "ordered-stream"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "p256"
+version = "0.14.0-rc.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41adc63effe99d48837a8cc0e6d7a77e32ae6a07f6000df466178dbc2193093e"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primefield",
+ "primeorder",
+ "sha2 0.11.0",
+]
+
+[[package]]
+name = "p384"
+version = "0.14.0-rc.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bd5333afa5ae0347f39e6a0f2c9c155da431583fd71fe5555bd0521b4ccaf02"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "fiat-crypto 0.3.0",
+ "primefield",
+ "primeorder",
+ "sha2 0.11.0",
+]
+
+[[package]]
+name = "p521"
+version = "0.14.0-rc.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3a5297f53dc16d35909060ba3032cff7867e8809f01e273ff325579d5f0ceae"
+dependencies = [
+ "base16ct",
+ "ecdsa",
+ "elliptic-curve",
+ "primefield",
+ "primeorder",
+ "sha2 0.11.0",
+]
+
+[[package]]
+name = "pageant"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f3a5ae18f65a85c67a77d18d42d3606c07948e3c17c1e5f74852b26589e88a5"
+dependencies = [
+ "base16ct",
+ "byteorder",
+ "bytes",
+ "delegate",
+ "futures",
+ "log",
+ "rand 0.10.1",
+ "sha2 0.11.0",
+ "thiserror 2.0.18",
+ "tokio",
+ "windows",
+ "windows-strings",
+]
+
+[[package]]
 name = "papergrid"
 version = "0.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2282,6 +3021,12 @@
 ]
 
 [[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
 name = "parking_lot"
 version = "0.12.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2299,26 +3044,51 @@
 dependencies = [
  "cfg-if",
  "libc",
- "redox_syscall",
+ "redox_syscall 0.5.18",
  "smallvec",
  "windows-link",
 ]
 
 [[package]]
+name = "password-hash"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aab41826031698d6ffcd9cff78ef56ef998e39dc7e5067cdfebe373842d4723b"
+dependencies = [
+ "phc",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
 name = "pbkdf2"
 version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
 dependencies = [
- "digest",
- "hmac",
+ "digest 0.10.7",
+ "hmac 0.12.1",
+]
+
+[[package]]
+name = "pbkdf2"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
+dependencies = [
+ "digest 0.11.3",
+ "hmac 0.13.0",
 ]
 
 [[package]]
 name = "peg"
-version = "0.8.5"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9928cfca101b36ec5163e70049ee5368a8a1c3c6efc9ca9c5f9cc2f816152477"
+checksum = "0aad070be5b63aa72103f2fcdd70a83adbd5e90112ce5b574171ff1c65501773"
 dependencies = [
  "peg-macros",
  "peg-runtime",
@@ -2326,9 +3096,9 @@
 
 [[package]]
 name = "peg-macros"
-version = "0.8.5"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6298ab04c202fa5b5d52ba03269fb7b74550b150323038878fe6c372d8280f71"
+checksum = "ddd8ef6825cae95355031ae26a99b616a2a21f22ba2de0197c43dfb05acbe7ee"
 dependencies = [
  "peg-runtime",
  "proc-macro2",
@@ -2337,18 +3107,17 @@
 
 [[package]]
 name = "peg-runtime"
-version = "0.8.5"
+version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca"
+checksum = "7011d97b484a5ebdc4b1fdb3b12d5e4bbbea56e9d22b688f2e79e04b65a7d8a6"
 
 [[package]]
-name = "pem"
-version = "3.0.6"
+name = "pem-rfc7468"
+version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
+checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
 dependencies = [
- "base64 0.22.1",
- "serde_core",
+ "base64ct",
 ]
 
 [[package]]
@@ -2358,33 +3127,33 @@
 checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
 
 [[package]]
-name = "petgraph"
-version = "0.7.1"
+name = "phc"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
+checksum = "44dc769b75f93afdddd8c7fa12d685292ddeff1e66f7f0f3a234cf1818afe892"
 dependencies = [
- "fixedbitset",
- "indexmap 2.14.0",
+ "base64ct",
+ "ctutils",
 ]
 
 [[package]]
 name = "pin-project"
-version = "1.1.11"
+version = "1.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
 dependencies = [
  "pin-project-internal",
 ]
 
 [[package]]
 name = "pin-project-internal"
-version = "1.1.11"
+version = "1.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -2394,33 +3163,102 @@
 checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
 
 [[package]]
+name = "piper"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
 name = "pkcs1"
 version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
 dependencies = [
- "der",
- "pkcs8",
- "spki",
+ "der 0.7.10",
+ "pkcs8 0.10.2",
+ "spki 0.7.3",
 ]
 
 [[package]]
+name = "pkcs1"
+version = "0.8.0-rc.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e"
+dependencies = [
+ "der 0.8.0",
+ "spki 0.8.0",
+]
+
+[[package]]
+name = "pkcs5"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "279a91971a1d8eb1260a30938eae3be9cb67b472dffecb222fbbbe2fd2dc1453"
+dependencies = [
+ "aes 0.9.1",
+ "cbc 0.2.1",
+ "der 0.8.0",
+ "pbkdf2 0.13.0",
+ "rand_core 0.10.1",
+ "scrypt 0.12.0",
+ "sha2 0.11.0",
+ "spki 0.8.0",
+]
+
+[[package]]
 name = "pkcs8"
 version = "0.10.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
 dependencies = [
- "der",
- "spki",
+ "der 0.7.10",
+ "spki 0.7.3",
 ]
 
 [[package]]
+name = "pkcs8"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
+dependencies = [
+ "der 0.8.0",
+ "pkcs5",
+ "rand_core 0.10.1",
+ "spki 0.8.0",
+]
+
+[[package]]
 name = "pkg-config"
 version = "0.3.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
 
 [[package]]
+name = "plain"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix 1.1.4",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
 name = "poly1305"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2428,7 +3266,18 @@
 dependencies = [
  "cpufeatures 0.2.17",
  "opaque-debug",
- "universal-hash",
+ "universal-hash 0.5.1",
+]
+
+[[package]]
+name = "poly1305"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00baa632505d05512f48a963e16051c54fda9a95cc9acea1a4e3c90991c4a2e"
+dependencies = [
+ "cpufeatures 0.3.0",
+ "universal-hash 0.6.1",
+ "zeroize",
 ]
 
 [[package]]
@@ -2440,7 +3289,18 @@
  "cfg-if",
  "cpufeatures 0.2.17",
  "opaque-debug",
- "universal-hash",
+ "universal-hash 0.5.1",
+]
+
+[[package]]
+name = "polyval"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede"
+dependencies = [
+ "cpubits",
+ "cpufeatures 0.3.0",
+ "universal-hash 0.6.1",
 ]
 
 [[package]]
@@ -2480,10 +3340,42 @@
 checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
 dependencies = [
  "proc-macro2",
- "syn",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "primefield"
+version = "0.14.0-rc.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d7e42f46a29abc16fb621a3466ee453358ebaae48a9e515f287e0af052ed8f"
+dependencies = [
+ "crypto-bigint",
+ "crypto-common 0.2.2",
+ "ff",
+ "rand_core 0.10.1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "primeorder"
+version = "0.14.0-rc.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d2793f22b9b6fd11ef3ac1d59bf003c2573593e4968702341605c2748fd90bf"
+dependencies = [
+ "elliptic-curve",
 ]
 
 [[package]]
+name = "proc-macro-crate"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
 name = "proc-macro-error-attr2"
 version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2502,7 +3394,7 @@
  "proc-macro-error-attr2",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -2531,77 +3423,25 @@
 
 [[package]]
 name = "prost"
-version = "0.13.5"
+version = "0.14.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
+checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1"
 dependencies = [
  "bytes",
- "prost-derive 0.13.5",
-]
-
-[[package]]
-name = "prost"
-version = "0.14.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568"
-dependencies = [
- "bytes",
- "prost-derive 0.14.3",
+ "prost-derive",
 ]
 
 [[package]]
-name = "prost-build"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
-dependencies = [
- "heck",
- "itertools 0.14.0",
- "log",
- "multimap",
- "once_cell",
- "petgraph",
- "prettyplease",
- "prost 0.13.5",
- "prost-types",
- "regex",
- "syn",
- "tempfile",
-]
-
-[[package]]
 name = "prost-derive"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
-dependencies = [
- "anyhow",
- "itertools 0.14.0",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "prost-derive"
-version = "0.14.3"
+version = "0.14.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b"
+checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
 dependencies = [
  "anyhow",
  "itertools 0.14.0",
  "proc-macro2",
  "quote",
- "syn",
-]
-
-[[package]]
-name = "prost-types"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
-dependencies = [
- "prost 0.13.5",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -2617,7 +3457,7 @@
  "quinn-udp",
  "rustc-hash 2.1.2",
  "rustls",
- "socket2 0.6.3",
+ "socket2",
  "thiserror 2.0.18",
  "tokio",
  "tracing",
@@ -2654,7 +3494,7 @@
  "cfg_aliases",
  "libc",
  "once_cell",
- "socket2 0.6.3",
+ "socket2",
  "tracing",
  "windows-sys 0.60.2",
 ]
@@ -2766,32 +3606,28 @@
 ]
 
 [[package]]
-name = "rcgen"
-version = "0.13.2"
+name = "redox_syscall"
+version = "0.5.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
 dependencies = [
- "pem",
- "ring",
- "rustls-pki-types",
- "time",
- "yasna",
+ "bitflags",
 ]
 
 [[package]]
 name = "redox_syscall"
-version = "0.5.18"
+version = "0.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7"
 dependencies = [
  "bitflags",
 ]
 
 [[package]]
 name = "regex"
-version = "1.12.3"
+version = "1.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
 dependencies = [
  "aho-corasick",
  "memchr",
@@ -2812,18 +3648,131 @@
 
 [[package]]
 name = "regex-syntax"
-version = "0.8.10"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+
+[[package]]
+name = "remowt-client"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34d59c0ce311b4b3c094b0b196a1004aa438b9947c3eace2ca4d2a0ac08fac59"
+dependencies = [
+ "anyhow",
+ "bifrostlink",
+ "bifrostlink-ports",
+ "bytes",
+ "camino",
+ "futures",
+ "remowt-endpoints",
+ "remowt-link-shared",
+ "russh",
+ "russh-config",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "tokio",
+ "tokio-util",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
+name = "remowt-endpoints"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb203856cd5cff27504bd2023c59195b1f1ce1cf5d8ea836598993894eb1b9c0"
+dependencies = [
+ "anyhow",
+ "bifrostlink",
+ "bifrostlink-macros",
+ "camino",
+ "nix",
+ "serde",
+ "tempfile",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "uuid",
+ "zbus",
+]
+
+[[package]]
+name = "remowt-fleet"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "bifrostlink",
+ "camino",
+ "nix-eval",
+ "remowt-client",
+ "remowt-endpoints",
+ "serde",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
+name = "remowt-link-shared"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98ebba5be028a2dfb7f1858bb0ff74037262753eaae4dc6e5d9ae241c4567668"
+dependencies = [
+ "bifrostlink",
+ "bytes",
+ "remowt-ui-prompt",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "remowt-plugin"
+version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
+checksum = "ef73ef3eea5bb6152d01019b5062e51ef258a1ac1c7a6f8cdcdf6fe1eb192cdd"
+dependencies = [
+ "anyhow",
+ "bifrostlink",
+ "bifrostlink-ports",
+ "remowt-link-shared",
+ "serde_json",
+ "tokio",
+ "tracing",
+ "tracing-subscriber",
+]
 
 [[package]]
-name = "repl-plugin-unstable"
+name = "remowt-plugin-fleet"
 version = "0.1.0"
 dependencies = [
- "fleet-base",
+ "anyhow",
+ "remowt-fleet",
+ "remowt-plugin",
 ]
 
 [[package]]
+name = "remowt-ui-prompt"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acf28ba68e6f8087657157695e9ab42b82a21130e77f6dffd27781062109325d"
+dependencies = [
+ "anyhow",
+ "bifrostlink",
+ "bifrostlink-macros",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "zbus",
+]
+
+[[package]]
 name = "reqwest"
 version = "0.12.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2854,8 +3803,8 @@
  "sync_wrapper",
  "tokio",
  "tokio-rustls",
- "tower 0.5.3",
- "tower-http 0.6.8",
+ "tower",
+ "tower-http",
  "tower-service",
  "url",
  "wasm-bindgen",
@@ -2864,6 +3813,16 @@
 ]
 
 [[package]]
+name = "rfc6979"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5236ce872cac07e0fb3969b0cbf468c7d2f37d432f1b627dcb7b8d34563fb0c3"
+dependencies = [
+ "hmac 0.13.0",
+ "subtle",
+]
+
+[[package]]
 name = "ring"
 version = "0.17.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2878,45 +3837,155 @@
 ]
 
 [[package]]
-name = "rmp"
-version = "0.8.15"
+name = "rsa"
+version = "0.9.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
+checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
 dependencies = [
+ "const-oid 0.9.6",
+ "digest 0.10.7",
+ "num-bigint-dig",
+ "num-integer",
  "num-traits",
+ "pkcs1 0.7.5",
+ "pkcs8 0.10.2",
+ "rand_core 0.6.4",
+ "signature 2.2.0",
+ "spki 0.7.3",
+ "subtle",
+ "zeroize",
 ]
 
 [[package]]
-name = "rmp-serde"
-version = "1.3.1"
+name = "rsa"
+version = "0.10.0-rc.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
+checksum = "30b2aa4ba0d89f73d1e332df05be0eeab8840351c36ca5654341dfdb57bb3caf"
 dependencies = [
- "rmp",
- "serde",
+ "const-oid 0.10.2",
+ "crypto-bigint",
+ "crypto-primes",
+ "digest 0.11.3",
+ "pkcs1 0.8.0-rc.4",
+ "pkcs8 0.11.0",
+ "rand_core 0.10.1",
+ "sha2 0.11.0",
+ "signature 3.0.0",
+ "spki 0.8.0",
+ "zeroize",
 ]
 
 [[package]]
-name = "rsa"
-version = "0.9.10"
+name = "russh"
+version = "0.61.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
+checksum = "bbf893f64684e58da8a68d56a5e84d1cf0440226274c515770fe267707a7d0b0"
 dependencies = [
- "const-oid",
- "digest",
- "num-bigint-dig",
- "num-integer",
- "num-traits",
- "pkcs1",
- "pkcs8",
- "rand_core 0.6.4",
- "signature",
- "spki",
+ "aes 0.9.1",
+ "bitflags",
+ "block-padding 0.4.2",
+ "byteorder",
+ "bytes",
+ "cbc 0.2.1",
+ "cipher 0.5.2",
+ "crypto-bigint",
+ "ctr 0.10.1",
+ "curve25519-dalek 5.0.0-rc.0",
+ "data-encoding",
+ "delegate",
+ "der 0.8.0",
+ "digest 0.11.3",
+ "ecdsa",
+ "ed25519-dalek 3.0.0-rc.0",
+ "elliptic-curve",
+ "enum_dispatch",
+ "flate2",
+ "futures",
+ "generic-array 1.4.3",
+ "getrandom 0.4.2",
+ "ghash 0.6.0",
+ "hex-literal",
+ "hmac 0.13.0",
+ "inout 0.2.2",
+ "internal-russh-num-bigint",
+ "keccak",
+ "log",
+ "md5",
+ "ml-kem",
+ "module-lattice",
+ "num-bigint",
+ "p256",
+ "p384",
+ "p521",
+ "pageant",
+ "pbkdf2 0.13.0",
+ "pkcs1 0.8.0-rc.4",
+ "pkcs5",
+ "pkcs8 0.11.0",
+ "polyval 0.7.1",
+ "rand 0.10.1",
+ "rand_core 0.10.1",
+ "ring",
+ "rsa 0.10.0-rc.18",
+ "russh-cryptovec",
+ "russh-util",
+ "salsa20 0.11.0",
+ "scrypt 0.12.0",
+ "sec1",
+ "sha1",
+ "sha2 0.11.0",
+ "sha3",
+ "signature 3.0.0",
+ "spki 0.8.0",
+ "ssh-encoding",
+ "ssh-key",
  "subtle",
+ "thiserror 2.0.18",
+ "tokio",
+ "typenum",
+ "universal-hash 0.6.1",
  "zeroize",
 ]
 
 [[package]]
+name = "russh-config"
+version = "0.58.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "993346101d4741a59c82bafc6b645c52ce3e7b0820f13e4b5d48cdb591e3d955"
+dependencies = [
+ "futures",
+ "globset",
+ "log",
+ "thiserror 2.0.18",
+ "tokio",
+ "whoami",
+]
+
+[[package]]
+name = "russh-cryptovec"
+version = "0.61.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "443f6bbcfacb34a1aab2b12b99bf08e0c63abdc5a0db261901365df9d57fff51"
+dependencies = [
+ "log",
+ "nix",
+ "ssh-encoding",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "russh-util"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668424a5dde0bcb45b55ba7de8476b93831b4aa2fa6947e145f3b053e22c60b6"
+dependencies = [
+ "chrono",
+ "tokio",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
 name = "rust-embed"
 version = "8.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2936,7 +4005,7 @@
  "proc-macro2",
  "quote",
  "rust-embed-utils",
- "syn",
+ "syn 2.0.117",
  "walkdir",
 ]
 
@@ -2946,7 +4015,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1"
 dependencies = [
- "sha2",
+ "sha2 0.10.9",
  "walkdir",
 ]
 
@@ -2999,11 +4068,10 @@
 
 [[package]]
 name = "rustls"
-version = "0.23.38"
+version = "0.23.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21"
+checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
 dependencies = [
- "log",
  "once_cell",
  "ring",
  "rustls-pki-types",
@@ -3014,30 +4082,21 @@
 
 [[package]]
 name = "rustls-native-certs"
-version = "0.8.3"
+version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
+checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
 dependencies = [
  "openssl-probe",
  "rustls-pki-types",
  "schannel",
  "security-framework",
-]
-
-[[package]]
-name = "rustls-pemfile"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
-dependencies = [
- "rustls-pki-types",
 ]
 
 [[package]]
 name = "rustls-pki-types"
-version = "1.14.0"
+version = "1.14.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
+checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
 dependencies = [
  "web-time",
  "zeroize",
@@ -3072,7 +4131,17 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
 dependencies = [
- "cipher",
+ "cipher 0.4.4",
+]
+
+[[package]]
+name = "salsa20"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f874456e72520ff1375a06c588eaf074b0f01f9e9e1aada45bd9b7954a6e42c"
+dependencies = [
+ "cfg-if",
+ "cipher 0.5.2",
 ]
 
 [[package]]
@@ -3111,12 +4180,38 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
 dependencies = [
- "pbkdf2",
- "salsa20",
- "sha2",
+ "pbkdf2 0.12.2",
+ "salsa20 0.10.2",
+ "sha2 0.10.9",
 ]
 
 [[package]]
+name = "scrypt"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87af57419b594aa23fa95f09f0e06d80d84ba01c26148c43844cad6ff4485f0"
+dependencies = [
+ "cfg-if",
+ "pbkdf2 0.13.0",
+ "salsa20 0.11.0",
+ "sha2 0.11.0",
+]
+
+[[package]]
+name = "sec1"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56d437c2f19203ce5f7122e507831de96f3d2d4d3be5af44a0b0a09d8a80e4d"
+dependencies = [
+ "base16ct",
+ "ctutils",
+ "der 0.8.0",
+ "hybrid-array",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
 name = "secrecy"
 version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3177,28 +4272,9 @@
 dependencies = [
  "serde_core",
  "serde_derive",
-]
-
-[[package]]
-name = "serde-transcode"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2"
-dependencies = [
- "serde",
 ]
 
 [[package]]
-name = "serde_bytes"
-version = "0.11.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
-dependencies = [
- "serde",
- "serde_core",
-]
-
-[[package]]
 name = "serde_core"
 version = "1.0.228"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3215,14 +4291,14 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
 name = "serde_json"
-version = "1.0.149"
+version = "1.0.150"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
 dependencies = [
  "itoa",
  "memchr",
@@ -3232,6 +4308,17 @@
 ]
 
 [[package]]
+name = "serde_repr"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
 name = "serde_urlencoded"
 version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3244,6 +4331,27 @@
 ]
 
 [[package]]
+name = "serdect"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e"
+dependencies = [
+ "base16ct",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
+]
+
+[[package]]
 name = "sha2"
 version = "0.10.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3251,10 +4359,31 @@
 dependencies = [
  "cfg-if",
  "cpufeatures 0.2.17",
- "digest",
+ "digest 0.10.7",
 ]
 
 [[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
+]
+
+[[package]]
+name = "sha3"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1"
+dependencies = [
+ "digest 0.11.3",
+ "keccak",
+]
+
+[[package]]
 name = "sharded-slab"
 version = "0.1.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3264,16 +4393,16 @@
 ]
 
 [[package]]
-name = "shell-escape"
-version = "0.1.5"
+name = "shlex"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
 
 [[package]]
 name = "shlex"
-version = "1.3.0"
+version = "2.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
 
 [[package]]
 name = "signal-hook-registry"
@@ -3291,11 +4420,21 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
 dependencies = [
- "digest",
+ "digest 0.10.7",
  "rand_core 0.6.4",
 ]
 
 [[package]]
+name = "signature"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5"
+dependencies = [
+ "digest 0.11.3",
+ "rand_core 0.10.1",
+]
+
+[[package]]
 name = "simd-adler32"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3309,25 +4448,15 @@
 
 [[package]]
 name = "smallvec"
-version = "1.15.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-
-[[package]]
-name = "socket2"
-version = "0.5.10"
+version = "1.15.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
-dependencies = [
- "libc",
- "windows-sys 0.52.0",
-]
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
 
 [[package]]
 name = "socket2"
-version = "0.6.3"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
+checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
 dependencies = [
  "libc",
  "windows-sys 0.61.2",
@@ -3346,10 +4475,81 @@
 checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
 dependencies = [
  "base64ct",
- "der",
+ "der 0.7.10",
+]
+
+[[package]]
+name = "spki"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
+dependencies = [
+ "base64ct",
+ "der 0.8.0",
+]
+
+[[package]]
+name = "ssh-cipher"
+version = "0.3.0-rc.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10db6f219196a8528f9ec904d9d45cdad692d65b0e57e72be4dedd1c5fddce36"
+dependencies = [
+ "aead 0.6.0",
+ "aes 0.9.1",
+ "aes-gcm 0.11.0-rc.4",
+ "cbc 0.2.1",
+ "chacha20 0.10.0",
+ "cipher 0.5.2",
+ "ctr 0.10.1",
+ "ctutils",
+ "des",
+ "poly1305 0.9.0",
+ "ssh-encoding",
+ "zeroize",
 ]
 
 [[package]]
+name = "ssh-encoding"
+version = "0.3.0-rc.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abf34aa716da5d5b4c496936d042ea282ab392092cd68a72ef6a8863ff8c96a"
+dependencies = [
+ "base64ct",
+ "bytes",
+ "crypto-bigint",
+ "ctutils",
+ "digest 0.11.3",
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
+name = "ssh-key"
+version = "0.7.0-rc.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45735ce3dea95690e4a9e414c4cfde7f79835063c3dcd35881df85a84118e74b"
+dependencies = [
+ "argon2",
+ "bcrypt-pbkdf 0.11.0",
+ "ctutils",
+ "ed25519-dalek 3.0.0-rc.0",
+ "hex",
+ "hmac 0.13.0",
+ "p256",
+ "p384",
+ "p521",
+ "rand_core 0.10.1",
+ "rsa 0.10.0-rc.18",
+ "sec1",
+ "sha1",
+ "sha2 0.11.0",
+ "signature 3.0.0",
+ "ssh-cipher",
+ "ssh-encoding",
+ "zeroize",
+]
+
+[[package]]
 name = "stable_deref_trait"
 version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3369,6 +4569,17 @@
 
 [[package]]
 name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
 version = "2.0.117"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
@@ -3395,7 +4606,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -3419,7 +4630,7 @@
  "proc-macro-error2",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -3452,24 +4663,13 @@
 dependencies = [
  "rustix 1.1.4",
  "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "terraform-provider-fleet"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "async-trait",
- "serde",
- "tf-provider",
- "tokio",
 ]
 
 [[package]]
 name = "test-log"
-version = "0.2.20"
+version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f46bf474f0a4afebf92f076d54fd5e63423d9438b8c278a3d2ccb0f47f7cdb3"
+checksum = "9b9c218384242b5c89b68303ab6f6fc53a312d923f0c14dc6bb860c6aeee40f1"
 dependencies = [
  "env_logger",
  "test-log-macros",
@@ -3478,22 +4678,22 @@
 
 [[package]]
 name = "test-log-core"
-version = "0.2.20"
+version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37d4d41320b48bc4a211a9021678fcc0c99569b594ea31c93735b8e517102b4c"
+checksum = "c26ef8b00e4d382e59f6a8ddb3cd790b3a5bb29f21a358a9a69ea2f29f13f27b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
 name = "test-log-macros"
-version = "0.2.20"
+version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9beb9249a81e430dffd42400a49019bcf548444f1968ff23080a625de0d4d320"
+checksum = "944ad38adcbb71eaa682c56bceeb079e4ca82b4b3edc2a0fde5cb297b77dac8d"
 dependencies = [
- "syn",
+ "syn 2.0.117",
  "test-log-core",
 ]
 
@@ -3504,34 +4704,6 @@
 checksum = "0f8daae29995a24f65619e19d8d31dea5b389f3d853d8bf297bbf607cd0014cc"
 dependencies = [
  "unicode-width",
-]
-
-[[package]]
-name = "tf-provider"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d80ea2e5f9f54717952d199888aab7e607dc99275ec5221f1259ce7a5f55f5a6"
-dependencies = [
- "anyhow",
- "async-stream",
- "async-trait",
- "base64 0.22.1",
- "futures",
- "prost 0.13.5",
- "rcgen",
- "rmp-serde",
- "serde",
- "serde_bytes",
- "serde_json",
- "time",
- "tokio",
- "tokio-stream",
- "tokio-util",
- "tonic 0.12.3",
- "tonic-build",
- "tower-http 0.5.2",
- "tracing",
- "tracing-subscriber",
 ]
 
 [[package]]
@@ -3560,7 +4732,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -3571,7 +4743,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -3585,9 +4757,9 @@
 
 [[package]]
 name = "time"
-version = "0.3.47"
+version = "0.3.49"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
+checksum = "711a53c2d47bbd818258c498c8dbfe186a2526c631495cfe7e078567f86b8469"
 dependencies = [
  "deranged",
  "num-conv",
@@ -3599,15 +4771,15 @@
 
 [[package]]
 name = "time-core"
-version = "0.1.8"
+version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
 
 [[package]]
 name = "time-macros"
-version = "0.2.27"
+version = "0.2.29"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
+checksum = "71c652a3727a9cbb9a02f707f530b618ce00d0ccd762009c8c23bd191df3c17d"
 dependencies = [
  "num-conv",
  "time-core",
@@ -3641,16 +4813,16 @@
 
 [[package]]
 name = "tokio"
-version = "1.52.1"
+version = "1.52.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
+checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
 dependencies = [
  "bytes",
  "libc",
  "mio",
  "pin-project-lite",
  "signal-hook-registry",
- "socket2 0.6.3",
+ "socket2",
  "tokio-macros",
  "windows-sys 0.61.2",
 ]
@@ -3663,7 +4835,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -3685,7 +4857,6 @@
  "futures-core",
  "pin-project-lite",
  "tokio",
- "tokio-util",
 ]
 
 [[package]]
@@ -3712,24 +4883,23 @@
 
 [[package]]
 name = "toml_datetime"
-version = "0.7.5+spec-1.1.0"
+version = "1.1.1+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
 dependencies = [
  "serde_core",
 ]
 
 [[package]]
 name = "toml_edit"
-version = "0.23.10+spec-1.0.0"
+version = "0.25.12+spec-1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
+checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7"
 dependencies = [
- "indexmap 2.14.0",
+ "indexmap",
  "toml_datetime",
  "toml_parser",
- "toml_writer",
- "winnow 0.7.15",
+ "winnow",
 ]
 
 [[package]]
@@ -3738,52 +4908,14 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
 dependencies = [
- "winnow 1.0.2",
-]
-
-[[package]]
-name = "toml_writer"
-version = "1.1.1+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
-
-[[package]]
-name = "tonic"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
-dependencies = [
- "async-stream",
- "async-trait",
- "axum",
- "base64 0.22.1",
- "bytes",
- "h2",
- "http",
- "http-body",
- "http-body-util",
- "hyper",
- "hyper-timeout",
- "hyper-util",
- "percent-encoding",
- "pin-project",
- "prost 0.13.5",
- "rustls-pemfile",
- "socket2 0.5.10",
- "tokio",
- "tokio-rustls",
- "tokio-stream",
- "tower 0.4.13",
- "tower-layer",
- "tower-service",
- "tracing",
+ "winnow",
 ]
 
 [[package]]
 name = "tonic"
-version = "0.14.5"
+version = "0.14.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec"
+checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef"
 dependencies = [
  "async-trait",
  "base64 0.22.1",
@@ -3800,55 +4932,21 @@
  "sync_wrapper",
  "tokio",
  "tokio-stream",
- "tower 0.5.3",
+ "tower",
  "tower-layer",
  "tower-service",
  "tracing",
 ]
 
 [[package]]
-name = "tonic-build"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11"
-dependencies = [
- "prettyplease",
- "proc-macro2",
- "prost-build",
- "prost-types",
- "quote",
- "syn",
-]
-
-[[package]]
 name = "tonic-prost"
-version = "0.14.5"
+version = "0.14.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309"
+checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0"
 dependencies = [
  "bytes",
- "prost 0.14.3",
- "tonic 0.14.5",
-]
-
-[[package]]
-name = "tower"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
-dependencies = [
- "futures-core",
- "futures-util",
- "indexmap 1.9.3",
- "pin-project",
- "pin-project-lite",
- "rand 0.8.6",
- "slab",
- "tokio",
- "tokio-util",
- "tower-layer",
- "tower-service",
- "tracing",
+ "prost",
+ "tonic",
 ]
 
 [[package]]
@@ -3859,7 +4957,7 @@
 dependencies = [
  "futures-core",
  "futures-util",
- "indexmap 2.14.0",
+ "indexmap",
  "pin-project-lite",
  "slab",
  "sync_wrapper",
@@ -3872,37 +4970,20 @@
 
 [[package]]
 name = "tower-http"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
-dependencies = [
- "bitflags",
- "bytes",
- "http",
- "http-body",
- "http-body-util",
- "pin-project-lite",
- "tower-layer",
- "tower-service",
- "tracing",
-]
-
-[[package]]
-name = "tower-http"
-version = "0.6.8"
+version = "0.6.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
 dependencies = [
  "bitflags",
  "bytes",
  "futures-util",
  "http",
  "http-body",
- "iri-string",
  "pin-project-lite",
- "tower 0.5.3",
+ "tower",
  "tower-layer",
  "tower-service",
+ "url",
 ]
 
 [[package]]
@@ -3936,7 +5017,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -3989,16 +5070,6 @@
 ]
 
 [[package]]
-name = "tracing-serde"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
-dependencies = [
- "serde",
- "tracing-core",
-]
-
-[[package]]
 name = "tracing-subscriber"
 version = "0.3.23"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4008,15 +5079,12 @@
  "nu-ansi-term",
  "once_cell",
  "regex-automata",
- "serde",
- "serde_json",
  "sharded-slab",
  "smallvec",
  "thread_local",
  "tracing",
  "tracing-core",
  "tracing-log",
- "tracing-serde",
 ]
 
 [[package]]
@@ -4036,11 +5104,22 @@
 
 [[package]]
 name = "typenum"
-version = "1.20.0"
+version = "1.20.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
 
 [[package]]
+name = "uds_windows"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
+dependencies = [
+ "memoffset",
+ "tempfile",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
 name = "unarray"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4107,11 +5186,21 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
 dependencies = [
- "crypto-common",
+ "crypto-common 0.1.7",
  "subtle",
 ]
 
 [[package]]
+name = "universal-hash"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96"
+dependencies = [
+ "crypto-common 0.2.2",
+ "ctutils",
+]
+
+[[package]]
 name = "untrusted"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4142,6 +5231,18 @@
 checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
 
 [[package]]
+name = "uuid"
+version = "1.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
+dependencies = [
+ "getrandom 0.4.2",
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
 name = "valuable"
 version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4204,9 +5305,9 @@
 
 [[package]]
 name = "wasip2"
-version = "1.0.3+wasi-0.2.9"
+version = "1.0.4+wasi-0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
 dependencies = [
  "wit-bindgen 0.57.1",
 ]
@@ -4221,10 +5322,16 @@
 ]
 
 [[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
+[[package]]
 name = "wasm-bindgen"
-version = "0.2.118"
+version = "0.2.125"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
+checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -4235,9 +5342,9 @@
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.68"
+version = "0.4.75"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8"
+checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -4245,9 +5352,9 @@
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.118"
+version = "0.2.125"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
+checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -4255,22 +5362,22 @@
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.118"
+version = "0.2.125"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
+checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
 dependencies = [
  "bumpalo",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
  "wasm-bindgen-shared",
 ]
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.118"
+version = "0.2.125"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
+checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
 dependencies = [
  "unicode-ident",
 ]
@@ -4292,7 +5399,7 @@
 checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
 dependencies = [
  "anyhow",
- "indexmap 2.14.0",
+ "indexmap",
  "wasm-encoder",
  "wasmparser",
 ]
@@ -4305,15 +5412,15 @@
 dependencies = [
  "bitflags",
  "hashbrown 0.15.5",
- "indexmap 2.14.0",
+ "indexmap",
  "semver",
 ]
 
 [[package]]
 name = "web-sys"
-version = "0.3.95"
+version = "0.3.102"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d"
+checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
@@ -4342,6 +5449,17 @@
 ]
 
 [[package]]
+name = "whoami"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
+dependencies = [
+ "libredox",
+ "wasite",
+ "web-sys",
+]
+
+[[package]]
 name = "winapi-util"
 version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4351,6 +5469,27 @@
 ]
 
 [[package]]
+name = "windows"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
+dependencies = [
+ "windows-collections",
+ "windows-core",
+ "windows-future",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
+dependencies = [
+ "windows-core",
+]
+
+[[package]]
 name = "windows-core"
 version = "0.62.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4364,6 +5503,17 @@
 ]
 
 [[package]]
+name = "windows-future"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
+dependencies = [
+ "windows-core",
+ "windows-link",
+ "windows-threading",
+]
+
+[[package]]
 name = "windows-implement"
 version = "0.60.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4371,7 +5521,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -4382,7 +5532,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -4392,6 +5542,16 @@
 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
 
 [[package]]
+name = "windows-numerics"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
+dependencies = [
+ "windows-core",
+ "windows-link",
+]
+
+[[package]]
 name = "windows-result"
 version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4479,6 +5639,15 @@
 ]
 
 [[package]]
+name = "windows-threading"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
 name = "windows_aarch64_gnullvm"
 version = "0.52.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4576,20 +5745,14 @@
 
 [[package]]
 name = "winnow"
-version = "0.7.15"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
 dependencies = [
  "memchr",
 ]
 
 [[package]]
-name = "winnow"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
-
-[[package]]
 name = "wit-bindgen"
 version = "0.51.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4623,9 +5786,9 @@
 dependencies = [
  "anyhow",
  "heck",
- "indexmap 2.14.0",
+ "indexmap",
  "prettyplease",
- "syn",
+ "syn 2.0.117",
  "wasm-metadata",
  "wit-bindgen-core",
  "wit-component",
@@ -4641,7 +5804,7 @@
  "prettyplease",
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
  "wit-bindgen-core",
  "wit-bindgen-rust",
 ]
@@ -4654,7 +5817,7 @@
 dependencies = [
  "anyhow",
  "bitflags",
- "indexmap 2.14.0",
+ "indexmap",
  "log",
  "serde",
  "serde_derive",
@@ -4673,7 +5836,7 @@
 dependencies = [
  "anyhow",
  "id-arena",
- "indexmap 2.14.0",
+ "indexmap",
  "log",
  "semver",
  "serde",
@@ -4701,26 +5864,17 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
 dependencies = [
- "curve25519-dalek",
+ "curve25519-dalek 4.1.3",
  "rand_core 0.6.4",
  "serde",
  "zeroize",
 ]
 
 [[package]]
-name = "yasna"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd"
-dependencies = [
- "time",
-]
-
-[[package]]
 name = "yoke"
-version = "0.8.2"
+version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
 dependencies = [
  "stable_deref_trait",
  "yoke-derive",
@@ -4735,35 +5889,96 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
  "synstructure",
 ]
 
 [[package]]
+name = "zbus"
+version = "5.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "enumflags2",
+ "event-listener",
+ "futures-core",
+ "futures-lite",
+ "hex",
+ "libc",
+ "ordered-stream",
+ "rustix 1.1.4",
+ "serde",
+ "serde_repr",
+ "tracing",
+ "uds_windows",
+ "uuid",
+ "windows-sys 0.61.2",
+ "winnow",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "5.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "zbus_names",
+ "zvariant",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "4.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
+dependencies = [
+ "serde",
+ "winnow",
+ "zvariant",
+]
+
+[[package]]
 name = "zerocopy"
-version = "0.8.48"
+version = "0.8.52"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
+checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.8.48"
+version = "0.8.52"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
+checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
 name = "zerofrom"
-version = "0.1.7"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
 dependencies = [
  "zerofrom-derive",
 ]
@@ -4776,28 +5991,28 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
  "synstructure",
 ]
 
 [[package]]
 name = "zeroize"
-version = "1.8.2"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
 dependencies = [
  "zeroize_derive",
 ]
 
 [[package]]
 name = "zeroize_derive"
-version = "1.4.3"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e"
+checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -4831,7 +6046,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn",
+ "syn 2.0.117",
 ]
 
 [[package]]
@@ -4839,3 +6054,43 @@
 version = "1.0.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+
+[[package]]
+name = "zvariant"
+version = "5.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "winnow",
+ "zvariant_derive",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "5.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn 2.0.117",
+ "winnow",
+]
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,22 +6,28 @@
 package.rust-version = "1.89.0"
 
 [workspace.dependencies]
-better-command = { path = "./crates/better-command" }
 fleet-base = { path = "./crates/fleet-base" }
 fleet-shared = { path = "./crates/fleet-shared" }
 nix-eval = { path = "./crates/nix-eval" }
 nixlike = { path = "./crates/nixlike" }
 opentelemetry-exporter-env = { path = "./crates/opentelemetry-exporter-env" }
+remowt-fleet = { path = "./crates/remowt-fleet" }
 
-abort-on-drop = "0.2"
+remowt-client = "0.1.3"
+remowt-endpoints = "0.1.3"
+remowt-link-shared = "0.1.3"
+remowt-plugin = "0.1.3"
+remowt-ui-prompt = "0.1.3"
+
+bifrostlink = "0.2.3"
+
+uuid = { version = "1", features = ["v4"] }
+
 age = { version = "0.11", features = ["plugin", "ssh"] }
-age-core = "0.11"
 anyhow = "1.0"
-async-trait = "0.1.88"
-axum = { version = "0.8.7", features = ["http2", "macros", "ws"] }
-axum-extra = { version = "0.12.2", features = ["typed-header"] }
 base64 = "0.22.1"
 bindgen = "0.72.0"
+camino = "1.2.2"
 chrono = { version = "0.4.41", features = ["serde"] }
 clap = { version = "4.5", features = ["derive", "env", "unicode", "wrap_help"] }
 clap_complete = "4.5"
@@ -29,19 +35,16 @@
 cxx-build = "1.0.168"
 ed25519-dalek = "2.1"
 futures = "0.3.31"
-futures-util = { version = "0.3.31", features = ["sink"] }
 hex = "0.4.3"
 hmac = "0.12"
 hostname = "0.4.1"
 human-repr = "1.1"
-hyper = "1.8.1"
 indicatif = "0.18"
 indoc = "2.0.6"
 itertools = "0.14.0"
 linked-hash-map = "0.5.6"
 nix = { version = "0.31.2", features = ["fs", "user"] }
 nom = "8.0.0"
-openssh = "0.11.5"
 opentelemetry = "0.31.0"
 opentelemetry-appender-tracing = "0.31.1"
 opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "gzip-tonic", "http-json", "reqwest-rustls"] }
@@ -50,21 +53,16 @@
 peg = "0.8.5"
 pkg-config = "0.3.30"
 rand = "0.10.0"
-regex = "1.11"
 serde = { version = "1.0", features = ["derive"] }
-serde-transcode = "1.1.1"
 serde_json = "1.0"
 sha2 = "0.10"
 shlex = "1.3"
 tabled = "0.20.0"
 tempfile = "3.20"
 test-log = { version = "0.2.19", features = ["trace"] }
-tf-provider = "0.2.2"
 thiserror = "2.0.12"
 time = "0.3.41"
 tokio = { version = "1.45.1", features = ["fs", "macros", "rt", "rt-multi-thread", "sync", "time"] }
-tokio-util = "0.7.17"
-toml_edit = "0.23.10"
 tracing = "0.1"
 tracing-indicatif = "0.3.13"
 tracing-opentelemetry = "0.32.1"
modifiedcmds/fleet/Cargo.tomldiffbeforeafterboth
--- a/cmds/fleet/Cargo.toml
+++ b/cmds/fleet/Cargo.toml
@@ -8,15 +8,13 @@
 default-run = "fleet"
 
 [dependencies]
-age = { workspace = true, features = ["armor"] }
 anyhow.workspace = true
-better-command.workspace = true
+camino.workspace = true
 clap.workspace = true
 clap_complete.workspace = true
 fleet-base.workspace = true
-fleet-shared.workspace = true
 nix-eval.workspace = true
-nixlike.workspace = true
+remowt-fleet.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 tempfile.workspace = true
@@ -24,30 +22,17 @@
 tracing.workspace = true
 tracing-subscriber.workspace = true
 
-abort-on-drop.workspace = true
-age-core.workspace = true
-async-trait.workspace = true
-base64.workspace = true
-chrono.workspace = true
 futures.workspace = true
-hostname.workspace = true
 itertools.workspace = true
-openssh.workspace = true
-peg.workspace = true
-regex.workspace = true
 shlex.workspace = true
 tabled.workspace = true
-time = { workspace = true, features = ["serde"] }
-tokio-util = { workspace = true, features = ["codec"] }
 
 human-repr = { workspace = true, optional = true }
 indicatif = { workspace = true, optional = true }
-nom.workspace = true
 opentelemetry.workspace = true
 opentelemetry-appender-tracing.workspace = true
 opentelemetry-exporter-env.workspace = true
 opentelemetry_sdk.workspace = true
-thiserror.workspace = true
 tracing-indicatif = { workspace = true, optional = true }
 tracing-opentelemetry.workspace = true
 
@@ -58,6 +43,5 @@
 	"dep:tracing-indicatif",
 	"dep:indicatif",
 	"dep:human-repr",
-	"better-command/indicatif",
 	"nix-eval/indicatif",
 ]
modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/build_systems.rs
+++ b/cmds/fleet/src/cmds/build_systems.rs
@@ -1,6 +1,7 @@
 use std::{env::current_dir, os::unix::fs::symlink, path::PathBuf};
 
 use anyhow::Result;
+use camino::Utf8PathBuf;
 use clap::Parser;
 use fleet_base::{
 	deploy::{DeployAction, deploy_task, upload_task},
@@ -29,7 +30,7 @@
 	build_attr: String,
 }
 
-async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<PathBuf> {
+async fn build_task(config: Config, hostname: String, build_attr: &str) -> Result<Utf8PathBuf> {
 	info!("building");
 	let host = config.host(&hostname)?;
 	// let action = Action::from(self.subcommand.clone());
@@ -42,17 +43,20 @@
 	// We already have system profiles for backups.
 	if !host.local {
 		info!("adding gc root");
-		let mut cmd = config.local_host().cmd("nix").await?;
-		cmd.arg("build")
-			.comparg(
-				"--profile",
-				format!(
-					"/nix/var/nix/profiles/{}-{hostname}",
-					config.data.gc_root_prefix
-				),
-			)
-			.arg(&out_output);
-		cmd.sudo().run_nix().await?;
+		let local = config.local_host();
+		let plugin_id = local.ensure_nix_plugin().await?;
+		let nix = local
+			.remowt()
+			.await?
+			.plugin_endpoints::<remowt_fleet::NixClient<_>>(plugin_id);
+		let profile = format!(
+			"/nix/var/nix/profiles/{}-{hostname}",
+			config.data.gc_root_prefix
+		);
+		nix.switch_profile(profile, out_output.clone())
+			.await
+			.map_err(|e| anyhow::anyhow!("{e:?}"))?
+			.map_err(|e| anyhow::anyhow!("{e}"))?;
 	}
 
 	Ok(out_output)
modifiedcmds/fleet/src/cmds/tf.rsdiffbeforeafterboth
--- a/cmds/fleet/src/cmds/tf.rs
+++ b/cmds/fleet/src/cmds/tf.rs
@@ -1,6 +1,7 @@
-use std::{collections::BTreeMap, ffi::OsString, path::PathBuf};
+use std::{collections::BTreeMap, ffi::OsString};
 
 use anyhow::{Context, Result};
+use camino::Utf8PathBuf;
 use clap::Parser;
 use fleet_base::host::Config;
 use nix_eval::nix_go;
@@ -40,7 +41,7 @@
 			let system = &config.local_system;
 			let config = &config.flake_outputs;
 			let data = nix_go!(config.tf({ system }));
-			let data: PathBuf = spawn_blocking(move || data.build("out"))
+			let data: Utf8PathBuf = spawn_blocking(move || data.build("out"))
 				.await
 				.expect("tf.json derivation should not fail")?;
 			let data = fs::read(&data).await?;
modifiedcmds/fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/fleet/src/main.rs
+++ b/cmds/fleet/src/main.rs
@@ -4,9 +4,10 @@
 // pub(crate) mod command;
 pub(crate) mod extra_args;
 
-use std::{env, ffi::OsString, process::ExitCode, sync::Arc};
+use std::{process::ExitCode, sync::Arc};
 
-use anyhow::{Result, bail};
+use anyhow::{Context as _, Result, bail};
+use camino::Utf8PathBuf;
 use clap::{CommandFactory, Parser};
 use cmds::{
 	build_systems::{BuildSystems, Deploy},
@@ -23,7 +24,8 @@
 #[cfg(feature = "indicatif")]
 use indicatif::{ProgressState, ProgressStyle};
 use nix_eval::{
-	gc_register_my_thread, gc_unregister_my_thread, init_libraries, init_tokio_for_nix,
+	add_file_to_store, gc_register_my_thread, gc_unregister_my_thread, init_libraries,
+	init_tokio_for_nix,
 };
 use opentelemetry::trace::TracerProvider;
 use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
@@ -48,24 +50,21 @@
 		}
 		let tasks = FuturesUnordered::new();
 		for entry in std::fs::read_dir(&prefetch_dir)? {
-			tasks.push(async {
-				let entry = entry?;
-				if !entry.metadata()?.is_file() {
-					bail!("only files should exist in prefetch directory");
-				}
-				let span = info_span!(
-					"prefetching",
-					name = entry.file_name().to_string_lossy().as_ref()
-				);
-				let mut path = OsString::new();
-				path.push("file://");
-				path.push(entry.path());
-
-				let mut status = config.local_host().cmd("nix").await?;
-				status.args(&config.nix_args);
-				status.arg("store").arg("prefetch-file").arg(path);
-				status.run_nix_string().instrument(span).await?;
-				Ok(())
+			let entry = entry?;
+			if !entry.metadata()?.is_file() {
+				bail!("only files should exist in prefetch directory");
+			}
+			let name = entry.file_name().to_string_lossy().into_owned();
+			let path =
+				Utf8PathBuf::try_from(entry.path()).context("prefetch path should be utf8")?;
+			let span = info_span!("prefetching", name = %name);
+			tasks.push(async move {
+				let added = tokio::task::spawn_blocking(move || add_file_to_store(&name, &path))
+					.instrument(span.clone())
+					.await??;
+				let _g = span.enter();
+				info!("{} -> {}", added.hash, added.store_path);
+				anyhow::Ok(())
 			});
 		}
 		tasks.try_collect::<Vec<()>>().await?;
@@ -179,9 +178,7 @@
 	let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
 
 	let reg = tracing_subscriber::registry().with({
-		let sub = tracing_subscriber::fmt::layer()
-			.without_time()
-			.with_target(false);
+		let sub = tracing_subscriber::fmt::layer().without_time();
 		#[cfg(feature = "indicatif")]
 		let sub = sub.with_writer(indicatif_layer.get_stderr_writer());
 		sub.with_filter(filter) // .without,
@@ -262,14 +259,10 @@
 }
 
 async fn main_real(opts: RootOpts) -> Result<()> {
-	let nix_args = std::env::var_os("NIX_ARGS")
-		.map(|a| extra_args::parse_os(&a))
-		.transpose()?
-		.unwrap_or_default();
-	let config = opts.fleet_opts.build(
-		nix_args,
-		matches!(opts.command, Opts::Deploy(_) | Opts::BuildSystems(_)),
-	)?;
+	let config = opts.fleet_opts.build(matches!(
+		opts.command,
+		Opts::Deploy(_) | Opts::BuildSystems(_)
+	))?;
 
 	match run_command(&config, opts.fleet_opts, opts.command).await {
 		Ok(()) => {
modifiedcmds/install-secrets/src/main.rsdiffbeforeafterboth
--- a/cmds/install-secrets/src/main.rs
+++ b/cmds/install-secrets/src/main.rs
@@ -227,7 +227,6 @@
 				.from_env_lossy(),
 		)
 		.without_time()
-		.with_target(false)
 		.init();
 
 	let opts = Opts::parse();
addedcmds/nix-plugin-fleet/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/cmds/nix-plugin-fleet/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "nix-plugin-fleet"
+version.workspace = true
+edition.workspace = true
+rust-version.workspace = true
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+fleet-base.workspace = true
addedcmds/nix-plugin-fleet/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/nix-plugin-fleet/src/lib.rs
@@ -0,0 +1,7 @@
+use fleet_base::primops::init_primops;
+
+/// SAFETY: expected plugin dynamic library entry point
+#[unsafe(no_mangle)]
+fn nix_plugin_entry() {
+	init_primops();
+}
addedcmds/remowt-plugin-fleet/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/cmds/remowt-plugin-fleet/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "remowt-plugin-fleet"
+description = "Remowt plugin exposing fleet's Nix endpoint to a running agent"
+version.workspace = true
+edition = "2021"
+
+[dependencies]
+remowt-fleet.workspace = true
+remowt-plugin.workspace = true
+
+anyhow.workspace = true
addedcmds/remowt-plugin-fleet/src/main.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/remowt-plugin-fleet/src/main.rs
@@ -0,0 +1,8 @@
+use remowt_fleet::{Nix, init_libraries};
+
+fn main() -> anyhow::Result<()> {
+	remowt_plugin::run(|rpc| {
+		init_libraries();
+		Nix.register_endpoints(rpc);
+	})
+}
deletedcmds/repl-plugin-unstable/Cargo.tomldiffbeforeafterboth
--- a/cmds/repl-plugin-unstable/Cargo.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-[package]
-name = "repl-plugin-unstable"
-version.workspace = true
-edition.workspace = true
-rust-version.workspace = true
-
-[lib]
-crate-type = ["cdylib"]
-
-[dependencies]
-fleet-base.workspace = true
deletedcmds/repl-plugin-unstable/src/lib.rsdiffbeforeafterboth
--- a/cmds/repl-plugin-unstable/src/lib.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use fleet_base::primops::init_primops;
-
-/// SAFETY: expected plugin dynamic library entry point
-#[unsafe(no_mangle)]
-fn nix_plugin_entry() {
-	init_primops();
-}
deletedcmds/terraform-provider-fleet/Cargo.tomldiffbeforeafterboth
--- a/cmds/terraform-provider-fleet/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "terraform-provider-fleet"
-version.workspace = true
-edition.workspace = true
-rust-version.workspace = true
-
-[dependencies]
-anyhow.workspace = true
-serde = { workspace = true, features = ["derive"] }
-tokio.workspace = true
-
-async-trait.workspace = true
-tf-provider.workspace = true
deletedcmds/terraform-provider-fleet/src/main.rsdiffbeforeafterboth
--- a/cmds/terraform-provider-fleet/src/main.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-fn main() {
-	panic!(
-		"this is a stub, real provider is in development, I just don't want to keep it in different branch."
-	)
-}
deletedcrates/better-command/Cargo.tomldiffbeforeafterboth
--- a/crates/better-command/Cargo.toml
+++ /dev/null
@@ -1,16 +0,0 @@
-[package]
-name = "better-command"
-version.workspace = true
-edition.workspace = true
-rust-version.workspace = true
-
-[dependencies]
-regex.workspace = true
-serde.workspace = true
-serde_json.workspace = true
-tracing.workspace = true
-
-tracing-indicatif = { workspace = true, optional = true }
-
-[features]
-indicatif = ["tracing-indicatif"]
deletedcrates/better-command/src/handler.rsdiffbeforeafterboth
--- a/crates/better-command/src/handler.rs
+++ /dev/null
@@ -1,335 +0,0 @@
-//! Collection of handlers, which transform program-specific stdout format to tracing
-
-use std::{
-	collections::HashMap,
-	sync::{Arc, LazyLock, Mutex},
-};
-
-use regex::Regex;
-use serde::Deserialize;
-use tracing::{Span, info, info_span, warn};
-#[cfg(feature = "indicatif")]
-use tracing_indicatif::span_ext::IndicatifSpanExt as _;
-
-pub trait Handler: Send {
-	fn handle_line(&mut self, e: &str);
-}
-
-/// Handler wrapper, which can be cloned.
-pub struct ClonableHandler<H>(Arc<Mutex<H>>);
-impl<H> Clone for ClonableHandler<H> {
-	fn clone(&self) -> Self {
-		Self(self.0.clone())
-	}
-}
-impl<H> ClonableHandler<H> {
-	pub fn new(inner: H) -> Self {
-		Self(Arc::new(Mutex::new(inner)))
-	}
-}
-impl<H: Handler> Handler for ClonableHandler<H> {
-	fn handle_line(&mut self, e: &str) {
-		self.0.lock().unwrap().handle_line(e)
-	}
-}
-
-/// Converts command output to tracing lines
-pub struct PlainHandler;
-impl Handler for PlainHandler {
-	fn handle_line(&mut self, e: &str) {
-		info!(target: "log", "{e}");
-	}
-}
-
-/// Ignores output
-pub struct NoopHandler;
-impl Handler for NoopHandler {
-	fn handle_line(&mut self, _e: &str) {}
-}
-
-/// Transform nix internal-json logs to tracing spans.
-#[derive(Default)]
-pub struct NixHandler {
-	spans: HashMap<u64, Span>,
-}
-#[derive(Deserialize, Debug)]
-#[serde(untagged)]
-enum LogField {
-	String(String),
-	Num(u64),
-}
-
-/// Nix internal-json log line type
-#[derive(Deserialize, Debug)]
-#[serde(rename_all = "camelCase", tag = "action")]
-#[allow(dead_code)]
-enum NixLog {
-	Msg {
-		level: u32,
-		msg: String,
-		raw_msg: Option<String>,
-	},
-	Start {
-		id: u64,
-		level: u32,
-		#[serde(default)]
-		fields: Vec<LogField>,
-		text: String,
-		#[serde(rename = "type")]
-		typ: u32,
-	},
-	Stop {
-		id: u64,
-	},
-	Result {
-		id: u64,
-		#[serde(rename = "type")]
-		typ: u32,
-		#[serde(default)]
-		fields: Vec<LogField>,
-	},
-}
-fn process_message(m: &str) -> String {
-	// Supposed to remove formatting characters except colors, as some programs try to reset cursor position etc.
-	static OSC_CLEANER: LazyLock<Regex> =
-		LazyLock::new(|| Regex::new(r"\x1B\]([^\x07\x1C]*[\x07\x1C])?|\r").unwrap());
-	static DETABBER: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\t").unwrap());
-	let m = OSC_CLEANER.replace_all(m, "");
-	// Indicatif can't format tabs. This is not the correct tab formatting, as correct one should be aligned,
-	// and not just be replaced with the constant number of spaces, but it's ok for now, as statuses are single-line.
-	DETABBER.replace_all(m.as_ref(), "  ").to_string()
-}
-impl Handler for NixHandler {
-	fn handle_line(&mut self, e: &str) {
-		if let Some(e) = e.strip_prefix("@nix ") {
-			let log: NixLog = match serde_json::from_str(e) {
-				Ok(l) => l,
-				Err(err) => {
-					warn!("failed to parse nix log line {:?}: {}", e, err);
-					return;
-				}
-			};
-			match log {
-				NixLog::Msg { msg, raw_msg, .. } => {
-					#[allow(clippy::nonminimal_bool)]
-					if !(msg.starts_with("\u{1b}[35;1mwarning:\u{1b}[0m Git tree '")
-						&& msg.ends_with("' is dirty"))
-						&& !msg.starts_with(
-							"\u{1b}[35;1mwarning:\u{1b}[0m not writing modified lock file of flake",
-						) && msg
-						!= "\u{1b}[35;1mwarning:\u{1b}[0m \u{1b}[31;1merror:\u{1b}[0m SQLite database '\u{1b}[35;1m/nix/var/nix/db/db.sqlite\u{1b}[0m' is busy"
-					{
-						if let Some(raw_msg) = raw_msg {
-							if !msg.is_empty() {
-								info!(target: "nix", "{}\n{}", raw_msg.trim_end(), msg.trim_end())
-							} else {
-								info!(target: "nix", "{}", raw_msg.trim_end())
-							}
-						} else {
-							info!(target: "nix", "{}", msg.trim_end())
-						}
-					}
-				}
-				NixLog::Start {
-					ref fields,
-					typ,
-					id,
-					..
-				} if typ == 105 && !fields.is_empty() => {
-					if let [LogField::String(drv), ..] = &fields[..] {
-						let mut drv = drv.as_str();
-						if let Some(pkg) = drv.strip_prefix("/nix/store/") {
-							let mut it = pkg.splitn(2, '-');
-							it.next();
-							if let Some(pkg) = it.next() {
-								drv = pkg;
-							}
-						}
-						info!(target: "nix","building {}", drv);
-						let span = info_span!("build", drv);
-						#[cfg(feature = "indicatif")]
-						span.pb_start();
-						self.spans.insert(id, span);
-					} else {
-						warn!("bad build log: {:?}", log)
-					}
-				}
-				NixLog::Start {
-					ref fields,
-					typ,
-					id,
-					..
-				} if typ == 100 && fields.len() >= 3 => {
-					if let [
-						LogField::String(drv),
-						LogField::String(from),
-						LogField::String(to),
-						..,
-					] = &fields[..]
-					{
-						let mut drv = drv.as_str();
-
-						if let Some(pkg) = drv.strip_prefix("/nix/store/") {
-							let mut it = pkg.splitn(2, '-');
-							it.next();
-							if let Some(pkg) = it.next() {
-								drv = pkg;
-							}
-						}
-						info!(target: "nix","copying {} {} -> {}", drv, from, to);
-						let span = info_span!("copy", from, to, drv);
-						#[cfg(feature = "indicatif")]
-						span.pb_start();
-						self.spans.insert(id, span);
-					} else {
-						warn!("bad copy log: {:?}", log)
-					}
-				}
-				NixLog::Start { text, typ, id, .. }
-					if typ == 0 || typ == 102 || typ == 103 || typ == 104 =>
-				{
-					if !text.is_empty()
-						&& text != "querying info about missing paths"
-						&& text != "copying 0 paths"
-						// Too much spam on lazy-trees branch
-						&& !(text.starts_with("copying '") && text.ends_with("' to the store"))
-					{
-						let span = info_span!("job");
-						#[cfg(feature = "indicatif")]
-						{
-							span.pb_start();
-							span.pb_set_message(&process_message(text.trim()));
-						}
-						self.spans.insert(id, span);
-						info!(target: "nix", "{}", text);
-					}
-				}
-				NixLog::Start {
-					text,
-					level: 0,
-					typ: 108,
-					..
-				} if text.is_empty() => {
-					// Cache lookup? Coupled with copy log
-				}
-				NixLog::Start {
-					text,
-					level: 4,
-					typ: 109,
-					..
-				} if text.starts_with("querying info about ") => {
-					// Cache lookup
-				}
-				NixLog::Start {
-					text,
-					level: 4,
-					typ: 101,
-					..
-				} if text.starts_with("downloading ") => {
-					// NAR downloading, coupled with copy log
-				}
-				NixLog::Start {
-					text,
-					level: 1,
-					typ: 111,
-					..
-				} if text.starts_with("waiting for a machine to build ") => {
-					// Useless repeating notification about build
-				}
-				NixLog::Start {
-					text,
-					level: 3,
-					typ: 111,
-					..
-				} if text.starts_with("resolved derivation: ") => {
-					// CA resolved
-				}
-				NixLog::Start {
-					text,
-					level: 1,
-					typ: 111,
-					id,
-					..
-				} if text.starts_with("waiting for lock on ") => {
-					let mut drv = text.strip_prefix("waiting for lock on ").unwrap();
-					if let Some(txt) = drv.strip_prefix("\u{1b}[35;1m'") {
-						drv = txt;
-					}
-					if let Some(txt) = drv.strip_suffix("'\u{1b}[0m") {
-						drv = txt;
-					}
-					if let Some(txt) = drv.split("', '").next() {
-						drv = txt;
-					}
-					if let Some(pkg) = drv.strip_prefix("/nix/store/") {
-						let mut it = pkg.splitn(2, '-');
-						it.next();
-						if let Some(pkg) = it.next() {
-							drv = pkg;
-						}
-					}
-					let span = info_span!("waiting on drv", drv);
-					#[cfg(feature = "indicatif")]
-					span.pb_start();
-					self.spans.insert(id, span);
-					// Concurrent build of the same message
-				}
-				NixLog::Stop { id, .. } => {
-					self.spans.remove(&id);
-				}
-				NixLog::Result { fields, id, typ } if typ == 101 && !fields.is_empty() => {
-					if let Some(span) = self.spans.get(&id) {
-						if let LogField::String(s) = &fields[0] {
-							#[cfg(feature = "indicatif")]
-							span.pb_set_message(&process_message(s.trim()));
-							#[cfg(not(feature = "indicatif"))]
-							{
-								let _span = span.enter();
-								info!("{}", process_message(s));
-							}
-						} else {
-							warn!("bad fields: {fields:?}");
-						}
-					} else {
-						warn!("unknown result id: {id} {typ} {fields:?}");
-					}
-					// dbg!(fields, id, typ);
-				}
-				NixLog::Result { fields, id, typ } if typ == 105 && fields.len() >= 4 => {
-					if let Some(span) = self.spans.get(&id) {
-						if let [
-							LogField::Num(done),
-							LogField::Num(expected),
-							LogField::Num(_running),
-							LogField::Num(_failed),
-						] = &fields[..4]
-						{
-							#[cfg(feature = "indicatif")]
-							{
-								span.pb_set_length(*expected);
-								span.pb_set_position(*done);
-							}
-							let _ = (span, done, expected);
-						} else {
-							warn!("bad fields: {fields:?}");
-						}
-					} else {
-						// warn!("unknown result id: {id} {typ} {fields:?}");
-						// Unaccounted progress.
-					}
-					// dbg!(fields, id, typ);
-				}
-				NixLog::Result { typ, .. } if typ == 104 || typ == 106 => {
-					// Set phase, expected
-				}
-				_ => warn!("unknown log: {:?}", log),
-			};
-		} else {
-			let e = e.trim();
-			if e.starts_with("Failed tcsetattr(TCSADRAIN): ") {
-				return;
-			}
-			info!("{e}")
-		}
-	}
-}
deletedcrates/better-command/src/lib.rsdiffbeforeafterboth
--- a/crates/better-command/src/lib.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-mod handler;
-pub use handler::{ClonableHandler, Handler, NixHandler, NoopHandler, PlainHandler};
modifiedcrates/fleet-base/Cargo.tomldiffbeforeafterboth
--- a/crates/fleet-base/Cargo.toml
+++ b/crates/fleet-base/Cargo.toml
@@ -5,9 +5,15 @@
 rust-version.workspace = true
 
 [dependencies]
+remowt-client.workspace = true
+remowt-endpoints.workspace = true
+remowt-fleet.workspace = true
+remowt-link-shared.workspace = true
+remowt-ui-prompt.workspace = true
+
 age.workspace = true
 anyhow.workspace = true
-better-command.workspace = true
+camino.workspace = true
 chrono.workspace = true
 clap = { workspace = true, features = ["derive"] }
 fleet-shared.workspace = true
@@ -18,15 +24,11 @@
 nix-eval.workspace = true
 nixlike.workspace = true
 nom.workspace = true
-openssh.workspace = true
 rand.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 tabled.workspace = true
 tempfile.workspace = true
-thiserror.workspace = true
 time = { workspace = true, features = ["parsing"] }
 tokio.workspace = true
-tokio-util = { workspace = true, features = ["codec"] }
-toml_edit.workspace = true
 tracing.workspace = true
deletedcrates/fleet-base/src/command.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/command.rs
+++ /dev/null
@@ -1,442 +0,0 @@
-use std::{ffi::OsStr, pin, process::Stdio, sync::Arc, task::Poll};
-
-use anyhow::{Result, anyhow};
-use better_command::{Handler, NixHandler, PlainHandler};
-use futures::StreamExt;
-use itertools::Either;
-use openssh::{OverSsh, OwningCommand, Session};
-use serde::de::DeserializeOwned;
-use tokio::{io::AsyncRead, process::Command, select};
-use tokio_util::codec::{BytesCodec, FramedRead, LinesCodec};
-use tracing::debug;
-
-use crate::host::EscalationStrategy;
-
-fn escape_bash(input: &str, out: &mut String) {
-	const TO_ESCAPE: &str = "$ !\"#&'()*,;<>?[\\]^`{|}";
-	if input.chars().all(|c| !TO_ESCAPE.contains(c)) {
-		out.push_str(input);
-		return;
-	}
-	out.push('\'');
-	for (i, v) in input.split('\'').enumerate() {
-		if i != 0 {
-			out.push_str("'\"'\"'");
-		}
-		out.push_str(v);
-	}
-	out.push('\'');
-}
-fn ostoutf8(os: impl AsRef<OsStr>) -> String {
-	os.as_ref().to_str().expect("non-utf8 data").to_owned()
-}
-
-#[derive(Clone, Debug)]
-pub struct MyCommand {
-	command: String,
-	args: Vec<String>,
-	env: Vec<(String, String)>,
-	ssh_session: Option<Arc<Session>>,
-	escalation: EscalationStrategy,
-	escalate: bool,
-}
-impl MyCommand {
-	pub fn new_on(
-		escalation: EscalationStrategy,
-		cmd: impl AsRef<OsStr>,
-		session: Arc<Session>,
-	) -> Self {
-		assert!(!cmd.as_ref().is_empty());
-		Self {
-			command: ostoutf8(cmd),
-			args: vec![],
-			env: vec![],
-			ssh_session: Some(session),
-			escalation,
-			escalate: false,
-		}
-	}
-	pub fn new(escalation: EscalationStrategy, cmd: impl AsRef<OsStr>) -> Self {
-		assert!(!cmd.as_ref().is_empty());
-		Self {
-			command: ostoutf8(cmd),
-			args: vec![],
-			env: vec![],
-			ssh_session: None,
-			escalation,
-			escalate: false,
-		}
-	}
-	fn new_here(&self, cmd: impl AsRef<OsStr>) -> Self {
-		match self.ssh_session.clone() {
-			Some(ssh_session) => Self::new_on(self.escalation, cmd, ssh_session),
-			_ => Self::new(self.escalation, cmd),
-		}
-	}
-
-	fn into_args(self) -> Vec<String> {
-		let mut out = Vec::new();
-		if !self.env.is_empty() {
-			out.push("env".to_owned());
-			for (k, v) in self.env {
-				assert!(!k.contains('='));
-				out.push(format!("{k}={v}"));
-			}
-		}
-		out.push(self.command);
-		out.extend(self.args);
-		out
-	}
-
-	/// Translates environment variables into env command execution.
-	/// Required for ssh, as ssh don't allow to send environment variables (at least by default).
-	///
-	/// FIXME: Insecure, as arguments might be seen by other users on the same machine.
-	/// Figure out some way to transfer environment using stdio?
-	fn translate_env_into_env(self) -> Self {
-		if self.env.is_empty() {
-			return self;
-		}
-		let mut out = self.new_here("env");
-		for (k, v) in self.env {
-			assert!(!k.contains('='));
-			out.arg(format!("{k}={v}"));
-		}
-		out.arg(self.command);
-		out.args(self.args);
-
-		out
-	}
-	fn into_string(self) -> String {
-		let mut out = String::new();
-		if !self.env.is_empty() {
-			out.push_str("env");
-			for (k, v) in self.env {
-				out.push(' ');
-				assert!(!k.contains('='));
-				escape_bash(&k, &mut out);
-				out.push('=');
-				escape_bash(&v, &mut out);
-			}
-		}
-		if !out.is_empty() {
-			out.push(' ');
-		}
-		escape_bash(&self.command, &mut out);
-		for arg in self.args {
-			out.push(' ');
-			escape_bash(&arg, &mut out);
-		}
-		out
-	}
-	fn into_command_unchecked_local(self) -> Command {
-		let mut out = Command::new(self.command);
-		out.args(self.args);
-		for (k, v) in self.env {
-			out.env(k, v);
-		}
-		out
-	}
-	fn into_command(self) -> Result<Either<Command, openssh::OwningCommand<Arc<Session>>>> {
-		Ok(match self.ssh_session.clone() {
-			Some(session) => {
-				let cmd = self.translate_env_into_env().into_command_unchecked_local();
-				Either::Right(
-					cmd.over_ssh(session)
-						.map_err(|e| anyhow!("ssh error: {e}"))?,
-				)
-			}
-			_ => {
-				let cmd = self.into_command_unchecked_local();
-				Either::Left(cmd)
-			}
-		})
-	}
-	pub fn arg(&mut self, arg: impl AsRef<OsStr>) -> &mut Self {
-		let arg = arg.as_ref();
-		self.args.push(ostoutf8(arg));
-		self
-	}
-	pub fn eqarg(&mut self, arg: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> &mut Self {
-		let arg = arg.as_ref();
-		let value = value.as_ref();
-		let arg = ostoutf8(arg);
-		let value = ostoutf8(value);
-		self.arg(format!("{arg}={value}"));
-		self
-	}
-	pub fn comparg(&mut self, arg: impl AsRef<OsStr>, value: impl AsRef<OsStr>) -> &mut Self {
-		self.arg(arg);
-		self.arg(value);
-		self
-	}
-	pub fn env(&mut self, name: impl AsRef<str>, value: impl AsRef<str>) -> &mut Self {
-		self.env
-			.push((name.as_ref().to_owned(), value.as_ref().to_owned()));
-		self
-	}
-	pub fn args<V: AsRef<OsStr>>(&mut self, args: impl IntoIterator<Item = V>) -> &mut Self {
-		for arg in args.into_iter() {
-			let arg = arg.as_ref();
-			self.args.push(ostoutf8(arg));
-		}
-		self
-	}
-	pub fn sudo(mut self) -> Self {
-		self.escalate = true;
-		self
-	}
-	fn wrap_sudo_if_needed(self) -> Self {
-		if !self.escalate {
-			return self;
-		}
-		match self.escalation {
-			EscalationStrategy::Su => {
-				let mut out = self.new_here("su");
-				out.arg("-c").arg(self.into_string());
-				out
-			}
-			EscalationStrategy::Sudo => {
-				let mut out = self.new_here("sudo");
-				out.args(self.into_args());
-				out
-			}
-			EscalationStrategy::Run0 => {
-				// run0 wants interactive authentication by default.
-				let mut run0 = self.new_here("run0");
-				let mut out = self.new_here("script");
-
-				// Red backgrounds messes with fleet formatting
-				run0.arg("--background=");
-				run0.args(self.into_args());
-
-				out.arg("-q");
-				out.arg("/dev/null");
-				out.arg("-c");
-				out.arg(run0.into_string());
-				dbg!(&out);
-				out
-			}
-		}
-	}
-
-	pub async fn run(self) -> Result<()> {
-		let str = self.clone().into_string();
-		let cmd = self.wrap_sudo_if_needed().into_command()?;
-		match cmd {
-			Either::Left(cmd) => run_nix_inner(str, cmd, &mut PlainHandler).await?,
-			Either::Right(cmd) => run_nix_inner_ssh(str, cmd, &mut PlainHandler).await?,
-		};
-		Ok(())
-	}
-	pub async fn run_string(self) -> Result<String> {
-		let bytes = self.run_bytes().await?;
-		Ok(String::from_utf8(bytes)?)
-	}
-	pub async fn run_value<T: DeserializeOwned>(self) -> Result<T> {
-		let v = self.run_string().await?;
-		Ok(serde_json::from_str(&v)?)
-	}
-	pub async fn run_bytes(self) -> Result<Vec<u8>> {
-		let str = self.clone().into_string();
-		let cmd = self.wrap_sudo_if_needed().into_command()?;
-		let v = match cmd {
-			Either::Left(cmd) => run_nix_inner_stdout(str, cmd, &mut PlainHandler).await?,
-			Either::Right(cmd) => run_nix_inner_stdout_ssh(str, cmd, &mut PlainHandler).await?,
-		};
-		Ok(v)
-	}
-
-	pub async fn run_nix_string(mut self) -> Result<String> {
-		let str = self.clone().into_string();
-		self.arg("--log-format").arg("internal-json");
-		let cmd = self.wrap_sudo_if_needed().into_command()?;
-		let bytes = match cmd {
-			Either::Left(cmd) => run_nix_inner_stdout(str, cmd, &mut NixHandler::default()).await?,
-			Either::Right(cmd) => {
-				run_nix_inner_stdout_ssh(str, cmd, &mut NixHandler::default()).await?
-			}
-		};
-		Ok(String::from_utf8(bytes)?)
-	}
-	pub async fn run_nix(mut self) -> Result<()> {
-		let str = self.clone().into_string();
-		self.arg("--log-format").arg("internal-json");
-		let cmd = self.wrap_sudo_if_needed().into_command()?;
-		match cmd {
-			Either::Left(mut cmd) => {
-				cmd.stdout(Stdio::inherit());
-				run_nix_inner(str, cmd, &mut NixHandler::default()).await
-			}
-			Either::Right(mut cmd) => {
-				cmd.stdout(openssh::Stdio::inherit());
-				run_nix_inner_ssh(str, cmd, &mut NixHandler::default()).await
-			}
-		}
-	}
-}
-
-struct EmptyAsyncRead;
-impl AsyncRead for EmptyAsyncRead {
-	fn poll_read(
-		self: std::pin::Pin<&mut Self>,
-		_cx: &mut std::task::Context<'_>,
-		_buf: &mut tokio::io::ReadBuf<'_>,
-	) -> Poll<std::io::Result<()>> {
-		Poll::Pending
-	}
-}
-
-async fn run_nix_inner_stdout(
-	str: String,
-	cmd: Command,
-	handler: &mut dyn Handler,
-) -> Result<Vec<u8>> {
-	Ok(run_nix_inner_raw(str, cmd, true, handler, None)
-		.await?
-		.expect("has out"))
-}
-async fn run_nix_inner(str: String, cmd: Command, handler: &mut dyn Handler) -> Result<()> {
-	let v = run_nix_inner_raw(str, cmd, false, handler, None).await?;
-	assert!(v.is_none());
-	Ok(())
-}
-async fn run_nix_inner_stdout_ssh(
-	str: String,
-	cmd: OwningCommand<Arc<Session>>,
-	handler: &mut dyn Handler,
-) -> Result<Vec<u8>> {
-	Ok(run_nix_inner_raw_ssh(str, cmd, true, handler, None)
-		.await?
-		.expect("has out"))
-}
-async fn run_nix_inner_ssh(
-	str: String,
-	cmd: OwningCommand<Arc<Session>>,
-	handler: &mut dyn Handler,
-) -> Result<()> {
-	let v = run_nix_inner_raw_ssh(str, cmd, false, handler, None).await?;
-	assert!(v.is_none());
-	Ok(())
-}
-
-async fn run_nix_inner_raw(
-	str: String,
-	mut cmd: Command,
-	want_stdout: bool,
-	err_handler: &mut dyn Handler,
-	mut out_handler: Option<&mut dyn Handler>,
-) -> Result<Option<Vec<u8>>> {
-	cmd.stderr(Stdio::piped());
-	cmd.stdout(Stdio::piped());
-	debug!("running command {str:?} on local");
-	let mut child = cmd.spawn()?;
-	let mut stderr = child.stderr.take().unwrap();
-	let stdout = child.stdout.take().unwrap();
-	let mut err = FramedRead::new(&mut stderr, LinesCodec::new());
-	let mut out: Option<Box<dyn AsyncRead + Unpin + Send>> = Some(Box::new(stdout));
-	let mut ob = want_stdout
-		.then(|| out.take().unwrap())
-		.unwrap_or_else(|| Box::new(EmptyAsyncRead));
-	let mut ol = (!want_stdout)
-		.then(|| out.take().unwrap())
-		.unwrap_or_else(|| Box::new(EmptyAsyncRead));
-	let mut ob = FramedRead::new(&mut ob, BytesCodec::new());
-	let mut ol = FramedRead::new(&mut ol, LinesCodec::new());
-
-	// while let Some(line) = read.next().await? {}
-
-	let mut out_buf = if want_stdout { Some(vec![]) } else { None };
-	loop {
-		select! {
-			biased;
-
-			Some(e) = err.next() => {
-				let e = e?;
-				err_handler.handle_line(&e);
-			},
-			Some(o) = ob.next() => {
-				out_buf.as_mut().expect("stdout == wants_stdout").extend_from_slice(&o?);
-			},
-			Some(o) = ol.next() => {
-				let o = o?;
-				if let Some(out) = out_handler.as_mut() {
-					out.handle_line(&o)
-				} else {
-					err_handler.handle_line(&o)
-				}
-				// out_handler.handle_info(&o);
-			},
-			code = child.wait() => {
-				let code = code?;
-				if !code.success() {
-					anyhow::bail!("command '{str}' failed with status {}", code);
-				}
-				break;
-			}
-		}
-	}
-
-	Ok(out_buf)
-}
-async fn run_nix_inner_raw_ssh(
-	str: String,
-	mut cmd: OwningCommand<Arc<Session>>,
-	want_stdout: bool,
-	err_handler: &mut dyn Handler,
-	mut out_handler: Option<&mut dyn Handler>,
-) -> Result<Option<Vec<u8>>> {
-	debug!("running command {str:?} over ssh");
-	cmd.stderr(openssh::Stdio::piped());
-	cmd.stdout(openssh::Stdio::piped());
-	let mut child = cmd.spawn().await?;
-	let mut stderr = child.stderr().take().unwrap();
-	let stdout = child.stdout().take().unwrap();
-	let mut err = FramedRead::new(&mut stderr, LinesCodec::new());
-	let mut out: Option<Box<dyn AsyncRead + Unpin + Send>> = Some(Box::new(stdout));
-	let mut ob = want_stdout
-		.then(|| out.take().unwrap())
-		.unwrap_or_else(|| Box::new(EmptyAsyncRead));
-	let mut ol = (!want_stdout)
-		.then(|| out.take().unwrap())
-		.unwrap_or_else(|| Box::new(EmptyAsyncRead));
-	let mut ob = FramedRead::new(&mut ob, BytesCodec::new());
-	let mut ol = FramedRead::new(&mut ol, LinesCodec::new());
-
-	// while let Some(line) = read.next().await? {}
-
-	let mut out_buf = if want_stdout { Some(vec![]) } else { None };
-
-	let mut wait_future = pin::pin!(child.wait());
-	loop {
-		select! {
-			biased;
-
-			Some(e) = err.next() => {
-				let e = e?;
-				err_handler.handle_line(&e);
-			},
-			Some(o) = ob.next() => {
-				out_buf.as_mut().expect("stdout == wants_stdout").extend_from_slice(&o?);
-			},
-			Some(o) = ol.next() => {
-				let o = o?;
-				if let Some(out) = out_handler.as_mut() {
-					out.handle_line(&o)
-				} else {
-					err_handler.handle_line(&o)
-				}
-				// out_handler.handle_info(&o);
-			},
-			code = &mut wait_future => {
-				let code = code?;
-				if !code.success() {
-					anyhow::bail!("command '{str}' failed with status {}", code);
-				}
-				break;
-			}
-		}
-	}
-
-	Ok(out_buf)
-}
modifiedcrates/fleet-base/src/deploy.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/deploy.rs
+++ b/crates/fleet-base/src/deploy.rs
@@ -1,8 +1,13 @@
-use std::{path::PathBuf, time::Duration};
+use std::time::Duration;
 
 use anyhow::{Context as _, Result, anyhow, bail};
+use camino::Utf8PathBuf;
 use clap::ValueEnum;
 use itertools::Itertools;
+use remowt_endpoints::fs::FsClient;
+use remowt_endpoints::systemd::SystemdClient;
+use remowt_fleet::NixClient;
+use remowt_link_shared::BifConfig;
 use tokio::time::sleep;
 use tracing::{Instrument as _, error, info, info_span, warn};
 
@@ -59,10 +64,11 @@
 pub async fn deploy_task(
 	action: DeployAction,
 	host: &ConfigHost,
-	built: PathBuf,
+	built: Utf8PathBuf,
 	specialisation: Option<String>,
 	disable_rollback: bool,
 ) -> Result<()> {
+	let remowt = host.remowt().await?;
 	let deploy_kind = host.deploy_kind().await?;
 	if (deploy_kind == DeployKind::NixosInstall || deploy_kind == DeployKind::NixosLustrate)
 		&& !matches!(action, DeployAction::Boot | DeployAction::Upload)
@@ -86,7 +92,7 @@
 			generation.id, generation.datetime
 		);
 		{
-			let mut cmd = host.cmd("sh").await?;
+			let mut cmd = remowt.cmd("sh");
 			cmd.arg("-c").arg(format!("mark=$(mktemp -p /etc -t fleet_rollback_marker.XXXXX) && echo -n {} > $mark && mv --no-clobber $mark /etc/fleet_rollback_marker", generation.id));
 			if let Err(e) = cmd.sudo().run().await {
 				error!("failed to set rollback marker: {e}");
@@ -104,7 +110,7 @@
 		// if we fail to perform generation switch in time, then we will still call the activation script, and this may break something.
 		// Anyway, reboot will still help in this case.
 		if action.should_schedule_rollback_run() {
-			let mut cmd = host.cmd("systemd-run").await?;
+			let mut cmd = remowt.cmd("systemd-run");
 			cmd.comparg("--on-active", "3min")
 				.comparg("--unit", "rollback-watchdog-run")
 				.arg("systemctl")
@@ -116,14 +122,20 @@
 			}
 		}
 	}
+
+	let remowt = host.remowt().await?;
+	let fs = remowt.endpoints::<FsClient<_>>();
 	if deploy_kind == DeployKind::NixosLustrate {
 		// Fleet could also create this file, but as this operation is potentially disruptive,
 		// make user do it themself.
-		if !host.file_exists("/etc/NIXOS_LUSTRATE").await? {
+		if !fs
+			.file_exists(Utf8PathBuf::from("/etc/NIXOS_LUSTRATE"))
+			.await?
+		{
 			bail!("/etc/NIXOS_LUSTRATE should be created on remote host");
 		}
 		// Wanted by NixOS to recognize the system as NixOS.
-		let mut cmd = host.cmd("touch").await?;
+		let mut cmd = remowt.cmd("touch");
 		cmd.arg("/etc/NIXOS");
 		cmd.sudo().run().await.context("creating /etc/NIXOS")?;
 	}
@@ -131,7 +143,7 @@
 		info!(
 			"running nixos-install to switch profile, install bootloader, and perform activation"
 		);
-		let mut cmd = host.cmd("nixos-install").await?;
+		let mut cmd = remowt.cmd("nixos-install");
 		cmd.arg("--system").arg(&built).args([
 			// Channels here aren't fleet host system channels, but channels embedded in installation cd, which might be old.
 			// It is possible to copy host channels, but I would prefer non-flake nix just to be unsupported.
@@ -147,32 +159,38 @@
 		if action.should_switch_profile() && !failed {
 			info!("switching system profile generation");
 
-			// To avoid even more problems, using nixos-install for now.
-			// // nix build is unable to work with --store argument for some reason, and nix until 2.26 didn't support copy with --profile argument,
-			// // falling back to using nix-env command
-			// // After stable NixOS starts using 2.26 - use `nix --store /mnt copy --from /mnt --profile ...` here, and instead of nix build below.
-			// let mut cmd = host.cmd("nix-env").await?;
-			// cmd.args([
-			// 	"--store",
-			// 	"/mnt",
-			// 	"--profile",
-			// 	"/mnt/nix/var/nix/profiles/system",
-			// 	"--set",
-			// ])
-			// .arg(&built);
-			// if let Err(e) = cmd.sudo().run_nix().await {
-			// 	error!("failed to switch system profile generation: {e}");
-			// 	failed = true;
-			// }
-			// It would also be possible to update profile atomically during copy:
-			// https://github.com/NixOS/nix/pull/11657
-			let mut cmd = host.nix_cmd().await?;
-			cmd.arg("build");
-			cmd.comparg("--profile", "/nix/var/nix/profiles/system");
-			cmd.arg(&built);
-			if let Err(e) = cmd.sudo().run_nix().await {
-				error!("failed to switch system profile generation: {e}");
-				failed = true;
+			match host.ensure_nix_plugin().await {
+				Ok(plugin_id) => {
+					let nix_elevated = remowt.plugin_endpoints::<NixClient<_>>(plugin_id);
+					// To avoid even more problems, using nixos-install for now.
+					// // nix build is unable to work with --store argument for some reason, and nix until 2.26 didn't support copy with --profile argument,
+					// // falling back to using nix-env command
+					// // After stable NixOS starts using 2.26 - use `nix --store /mnt copy --from /mnt --profile ...` here, and instead of nix build below.
+					// let mut cmd = host.cmd("nix-env").await?;
+					// cmd.args([
+					// 	"--store",
+					// 	"/mnt",
+					// 	"--profile",
+					// 	"/mnt/nix/var/nix/profiles/system",
+					// 	"--set",
+					// ])
+					// .arg(&built);
+					// if let Err(e) = cmd.sudo().run_nix().await {
+					// 	error!("failed to switch system profile generation: {e}");
+					// 	failed = true;
+					// }
+					if let Err(e) = nix_elevated
+						.switch_profile("/nix/var/nix/profiles/system".to_owned(), built.clone())
+						.await
+					{
+						error!("failed to switch system profile generation: {e}");
+						failed = true;
+					}
+				}
+				Err(e) => {
+					failed = true;
+					error!("failed to enable nix plugin: {e:?}");
+				}
 			}
 		}
 
@@ -189,7 +207,7 @@
 				built.clone()
 			};
 			let switch_script = specialised.join("bin/switch-to-configuration");
-			let mut cmd = host.cmd("systemd-run").in_current_span().await?;
+			let mut cmd = remowt.cmd("systemd-run");
 			if deploy_kind == DeployKind::NixosLustrate {
 				cmd.arg("--setenv=NIXOS_INSTALL_BOOTLOADER=1");
 			}
@@ -209,12 +227,14 @@
 		}
 	}
 	if action.should_create_rollback_marker() {
+		let elevated_systemd = remowt.run0_endpoints::<SystemdClient<BifConfig>>().await?;
+		let elevated_fs = remowt.run0_endpoints::<FsClient<BifConfig>>().await?;
 		if !disable_rollback {
 			if failed {
 				if action.should_schedule_rollback_run() {
 					info!("executing rollback");
-					if let Err(e) = host
-						.systemctl_start("rollback-watchdog.service")
+					if let Err(e) = elevated_systemd
+						.start("rollback-watchdog.service".to_owned())
 						.instrument(info_span!("rollback"))
 						.await
 					{
@@ -223,8 +243,8 @@
 				}
 			} else {
 				info!("trying to mark upgrade as successful");
-				if let Err(e) = host
-					.rm_file("/etc/fleet_rollback_marker", true)
+				if let Err(e) = elevated_fs
+					.rm_file(Utf8PathBuf::from("/etc/fleet_rollback_marker"))
 					.in_current_span()
 					.await
 				{
@@ -234,16 +254,22 @@
 				}
 			}
 			info!("disarming watchdog, just in case");
-			if let Err(_e) = host.systemctl_stop("rollback-watchdog.timer").await {
+			if let Err(_e) = elevated_systemd
+				.stop("rollback-watchdog.timer".to_owned())
+				.await
+			{
 				// It is ok, if there was no reboot - then timer might not be running.
 			}
 			if action.should_schedule_rollback_run() {
-				if let Err(e) = host.systemctl_stop("rollback-watchdog-run.timer").await {
+				if let Err(e) = elevated_systemd
+					.stop("rollback-watchdog-run.timer".to_owned())
+					.await
+				{
 					error!("failed to disarm rollback run: {e}");
 				}
 			}
-		} else if let Err(_e) = host
-			.rm_file("/etc/fleet_rollback_marker", true)
+		} else if let Err(_e) = elevated_fs
+			.rm_file(Utf8PathBuf::from("/etc/fleet_rollback_marker"))
 			.in_current_span()
 			.await
 		{
@@ -257,8 +283,8 @@
 	config: &Config,
 	host: &ConfigHost,
 	location: GenerationStorage,
-	generation: PathBuf,
-) -> Result<PathBuf> {
+	generation: Utf8PathBuf,
+) -> Result<Utf8PathBuf> {
 	if matches!(location, GenerationStorage::Pusher) {
 		bail!("pusher is not enabled in this version of fleet");
 	}
@@ -273,11 +299,11 @@
 				}
 				Err(e) if tries < 3 => {
 					tries += 1;
-					warn!("copy failure ({}/3): {}", tries, e);
+					warn!("copy failure ({}/3): {:#}", tries, e);
 					sleep(Duration::from_millis(5000)).await;
 				}
 				Err(e) => {
-					bail!("upload failed: {e}");
+					bail!("upload failed: {e:#}");
 				}
 			}
 		}
modifiedcrates/fleet-base/src/fleetdata.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/fleetdata.rs
+++ b/crates/fleet-base/src/fleetdata.rs
@@ -144,10 +144,6 @@
 	pub generation_data: Value,
 }
 
-fn is_false(b: &bool) -> bool {
-	*b == false
-}
-
 #[derive(Serialize, Deserialize, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
 #[repr(transparent)]
 pub struct SecretOwner(String);
@@ -291,7 +287,7 @@
 	}
 
 	// Which one is easier to access
-	return a.owners.len().cmp(&b.owners.len());
+	a.owners.len().cmp(&b.owners.len())
 }
 
 impl FleetSecretDistributions {
modifiedcrates/fleet-base/src/host.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/host.rs
+++ b/crates/fleet-base/src/host.rs
@@ -1,30 +1,33 @@
 use std::{
 	collections::{BTreeMap, BTreeSet, HashSet},
-	ffi::{OsStr, OsString},
-	fmt::Display,
+	future::Future,
 	io::Write,
 	ops::Deref,
 	path::PathBuf,
+	pin::Pin,
 	str::FromStr,
-	sync::{Arc, Mutex, MutexGuard, OnceLock},
+	sync::{Arc, OnceLock},
 };
 
 use anyhow::{Context, Result, anyhow, bail, ensure};
+use camino::{Utf8Path, Utf8PathBuf};
 use chrono::{DateTime, Utc};
 use fleet_shared::SecretData;
-use nix_eval::{Value, nix_go, nix_go_json, util::assert_warn};
-use openssh::{ControlPersist, SessionBuilder};
-use serde::de::DeserializeOwned;
+use nix_eval::{Store, Value, nix_go, nix_go_json, util::assert_warn};
+use remowt_client::{AgentBundle, Remowt};
+use remowt_endpoints::fs::FsClient;
+use remowt_link_shared::Address;
+use remowt_ui_prompt::auto::AutoPrompter;
+use remowt_ui_prompt::bifrost::PromptEndpoints;
+use remowt_ui_prompt::{PrependSourcePrompter, Source};
 use tabled::Tabled;
 use tempfile::NamedTempFile;
-use time::{UtcDateTime, format_description};
-use tracing::warn;
+use time::UtcDateTime;
+use tokio::task::spawn_blocking;
+use tracing::{info, warn};
 
-use crate::{
-	command::MyCommand,
-	fleetdata::{
-		FleetData, FleetSecretData, FleetSecretDistribution, FleetSecretPart, SecretOwner,
-	},
+use crate::fleetdata::{
+	FleetData, FleetSecretData, FleetSecretDistribution, FleetSecretPart, SecretOwner,
 };
 
 pub struct FleetConfigInternals {
@@ -36,7 +39,6 @@
 	/// builtins.currentSystem
 	pub local_system: String,
 	pub data: Arc<FleetData>,
-	pub nix_args: Vec<OsString>,
 	/// fleet_config.config
 	pub config_field: Value,
 	/// flake.output
@@ -48,6 +50,8 @@
 	pub default_pkgs: Value,
 	/// inputs.nixpkgs
 	pub nixpkgs: Value,
+
+	pub local_host: OnceLock<Arc<ConfigHost>>,
 }
 
 // TODO: Make field not pub
@@ -62,13 +66,6 @@
 	}
 }
 
-#[derive(Clone, Copy, Debug)]
-pub enum EscalationStrategy {
-	Sudo,
-	Run0,
-	Su,
-}
-
 #[derive(Clone, PartialEq, Copy, Debug)]
 pub enum DeployKind {
 	/// NixOS => NixOS managed by fleet
@@ -115,9 +112,26 @@
 
 	// TODO: Move command helpers away with connectivity refactor
 	pub local: bool,
-	pub session: OnceLock<Arc<openssh::Session>>,
+	pub remowt: OnceLock<Remowt>,
+	nix_store: OnceLock<Arc<Store>>,
+	nix_plugin: tokio::sync::OnceCell<()>,
 }
 
+const NIX_PLUGIN_ID: u16 = 2;
+
+fn agents_dir() -> Result<PathBuf> {
+	std::env::var_os("REMOWT_AGENTS_DIR")
+		.map(PathBuf::from)
+		.or_else(|| option_env!("REMOWT_AGENTS_DIR").map(PathBuf::from))
+		.ok_or_else(|| {
+			anyhow!("no remowt-agents bundle; set REMOWT_AGENTS_DIR to a remowt-agents output")
+		})
+}
+
+fn agent_bundle() -> Result<AgentBundle> {
+	AgentBundle::from_dir(agents_dir()?)
+}
+
 #[derive(Debug, Clone, Copy)]
 pub enum GenerationStorage {
 	Deployer,
@@ -143,7 +157,7 @@
 	#[tabled(rename = "Created at")]
 	pub datetime: UtcDateTime,
 	#[tabled(format = "{:?}")]
-	pub store_path: PathBuf,
+	pub store_path: Utf8PathBuf,
 	#[tabled(skip)]
 	pub location: GenerationStorage,
 }
@@ -153,68 +167,36 @@
 	}
 }
 
-fn parse_generation_line(g: &str) -> Option<Generation> {
-	let mut parts = g.split_whitespace();
-	let id = parts.next()?;
-	let id: u32 = id.parse().ok()?;
-	let date = parts.next()?;
-	let time = parts.next()?;
-	let current = if let Some(current) = parts.next() {
-		if current == "(current)" {
-			Some(true)
-		} else {
-			None
-		}
-	} else {
-		Some(false)
-	};
-	let current = current?;
-	if parts.next().is_some() {
-		warn!("unexpected text after generation: {g}");
-	}
-
-	let format = format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second]")
-		.expect("valid format");
-	let datetime = UtcDateTime::parse(&format!("{date} {time}"), &format).ok()?;
-
-	Some(Generation {
-		id,
-		current,
-		datetime,
-		store_path: PathBuf::new(),
-		location: GenerationStorage::Machine,
-	})
-}
-// TODO: Move command helpers away with connectivity refactor
 impl ConfigHost {
 	pub async fn list_generations(&self, profile: &str) -> Result<Vec<Generation>> {
-		let mut cmd = self.cmd("nix-env").await?;
-		cmd.comparg("--profile", format!("/nix/var/nix/profiles/{profile}"))
-			.arg("--list-generations")
-			.env("TZ", "UTC");
-		// Sudo is required because --list-generations tries to acquire profile lock
-		let data = cmd.sudo().run_string().await?;
-		let mut generations = data
-			.split('\n')
-			.map(|e| e.trim())
-			.filter(|&l| !l.is_empty())
-			.filter_map(|g| {
-				let generation = parse_generation_line(g);
-				if generation.is_none() {
-					warn!("bad generation: {g}");
-				};
-				generation
+		let plugin_id = self.ensure_nix_plugin().await?;
+		let nix = self
+			.remowt()
+			.await?
+			.plugin_endpoints::<remowt_fleet::NixClient<_>>(plugin_id);
+		let raw = nix
+			.list_generations(profile.to_owned())
+			.await
+			.map_err(|e| anyhow!("{e:?}"))?
+			.map_err(|e| anyhow!("{e}"))?;
+		raw.into_iter()
+			.map(|g| {
+				let id: u32 =
+					g.id.try_into()
+						.with_context(|| format!("generation id {} doesn't fit in u32", g.id))?;
+				let datetime = UtcDateTime::from_unix_timestamp(g.creation_time_unix)
+					.with_context(|| {
+						format!("invalid generation timestamp {}", g.creation_time_unix)
+					})?;
+				Ok(Generation {
+					id,
+					current: g.current,
+					datetime,
+					store_path: g.store_path,
+					location: GenerationStorage::Machine,
+				})
 			})
-			.collect::<Vec<_>>();
-		for ele in generations.iter_mut() {
-			let mut cmd = self.cmd("readlink").await?;
-			cmd.arg("--")
-				.arg(format!("/nix/var/nix/profiles/{profile}-{}-link", ele.id));
-			let path = cmd.run_string().await?;
-			ele.store_path = PathBuf::from(path.trim_end_matches("\n"));
-		}
-
-		Ok(generations)
+			.collect()
 	}
 
 	pub fn set_session_destination(&self, dest: String) {
@@ -236,7 +218,9 @@
 		if let Some(kind) = self.deploy_kind.get() {
 			return Ok(*kind);
 		}
-		let is_fleet_managed = match self.file_exists("/etc/FLEET_HOST").await {
+		let remowt = self.remowt().await?;
+		let fs = remowt.endpoints::<FsClient<_>>();
+		let is_fleet_managed = match fs.file_exists(Utf8PathBuf::from("/etc/FLEET_HOST")).await {
 			Ok(v) => v,
 			Err(e) => {
 				bail!("failed to query remote system kind: {e}");
@@ -252,6 +236,7 @@
 					1. manually create /etc/FLEET_HOST file on the target host,
 					2. use ?deploy_kind=fleet host argument if you're upgrading from older version of fleet
 					3. use ?deploy_kind=upgrade_to_fleet if you're upgrading from plain nixos to fleet-managed nixos
+				for installation use ?deploy_kind=nixos_install / ?deploy_kind=nixos_lustrate 
 			"}
 			);
 		}
@@ -259,139 +244,101 @@
 		let _ = self.deploy_kind.set(DeployKind::Fleet);
 		Ok(*self.deploy_kind.get().expect("deploy kind is just set"))
 	}
-	pub async fn escalation_strategy(&self) -> Result<EscalationStrategy> {
-		// Prefer sudo, as run0 has some gotchas with polkit
-		// and too many repeating prompts.
-		if (self.find_in_path("sudo").await).is_ok() {
-			return Ok(EscalationStrategy::Sudo);
-		}
-		if (self.find_in_path("run0").await).is_ok() {
-			return Ok(EscalationStrategy::Run0);
+	async fn connection(&self) -> Result<Remowt> {
+		if let Some(conn) = self.remowt.get() {
+			return Ok(conn.clone());
 		}
-		Ok(EscalationStrategy::Su)
-	}
-	async fn open_session(&self) -> Result<Arc<openssh::Session>> {
-		assert!(!self.local, "do not open ssh connection to local session");
-		// FIXME: TOCTOU
-		if let Some(session) = &self.session.get() {
-			return Ok((*session).clone());
+		let bundle = agent_bundle()?;
+		let conn = if self.local {
+			Remowt::connect_local(&bundle)
+				.await
+				.context("starting local remowt agent")?
+		} else {
+			let dest = self
+				.session_destination
+				.get()
+				.cloned()
+				.unwrap_or_else(|| self.name.clone());
+			Remowt::connect(&dest, &bundle)
+				.await
+				.map_err(|e| anyhow!("remowt error while connecting to {}: {e:#?}", self.name))?
 		};
-		let mut session = SessionBuilder::default();
-		session.control_persist(ControlPersist::ClosedAfterInitialConnection);
-
-		let dest = self.session_destination.get().unwrap_or(&self.name);
-		let session = session
-			.connect(&dest)
-			.await
-			.map_err(|e| anyhow!("ssh error while connecting to {}: {e:#?}", self.name))?;
-		let session = Arc::new(session);
-		self.session.set(session.clone()).expect("TOCTOU happened");
-		Ok(session)
-	}
-	pub async fn mktemp_dir(&self) -> Result<String> {
-		let mut cmd = self.cmd("mktemp").await?;
-		cmd.arg("-d");
-		let path = cmd.run_string().await?;
-		Ok(path.trim_end().to_owned())
-	}
-	pub async fn file_exists(&self, path: impl AsRef<OsStr>) -> Result<bool> {
-		let mut cmd = self.cmd("sh").await?;
-		cmd.arg("-c")
-			.arg("test -e \"$1\" && echo true || echo false")
-			.arg("_")
-			.arg(path);
-		cmd.run_value().await
-	}
-	pub async fn read_file_bin(&self, path: impl AsRef<OsStr>) -> Result<Vec<u8>> {
-		let mut cmd = self.cmd("cat").await?;
-		cmd.arg(path);
-		cmd.run_bytes().await
-	}
-	pub async fn read_file_text(&self, path: impl AsRef<OsStr>) -> Result<String> {
-		let mut cmd = self.cmd("cat").await?;
-		cmd.arg(path);
-		cmd.run_string().await
-	}
-	pub async fn read_dir(&self, path: impl AsRef<OsStr>) -> Result<Vec<String>> {
-		let mut cmd = self.cmd("ls").await?;
-		cmd.arg(path);
-		let out = cmd.run_string().await?;
-		let mut lines = out.split('\n');
-		if let Some(last) = lines.next_back() {
-			ensure!(last.is_empty(), "output of ls should end with newline");
-		}
-		Ok(lines.map(ToOwned::to_owned).collect())
-	}
-	#[allow(dead_code)]
-	pub async fn read_file_json<D: DeserializeOwned>(&self, path: impl AsRef<OsStr>) -> Result<D> {
-		let text = self.read_file_text(path).await?;
-		Ok(serde_json::from_str(&text)?)
-	}
-	pub async fn read_env(&self, env: &str) -> Result<String> {
-		let mut cmd = self.cmd("printenv").await?;
-		cmd.arg(env);
-		cmd.run_string().await
-	}
-	pub async fn find_in_path(&self, command: &str) -> Result<String> {
-		// // `which` is not a part of coreutils, and it might not exist on machine.
-		// let path = self.read_env("PATH").await?;
-		// // Assuming delimiter is :, we don't work with windows host, this check will be much
-		// // more sophisticated in remowt backend (and quicker, since actual PATH search will be done on remote machine)
-		// for ele in path.split(':') {
-		// 	let test_path = format!("{ele}/{cmd}");
-		// 	test -x etc
-		// }
-		// let mut cmd = self.cmd("printenv").await?;
-		// cmd.arg(env);
-		// Ok(cmd.run_string().await?)
-		// Assuming this is an environment issue if which doesn't exist, will be fixed with remowt.
-		let mut cmd = self
-			.cmd_escalation(
-				// Not used
-				EscalationStrategy::Su,
-				"which",
-			)
-			.await?;
-		cmd.arg(command);
-		cmd.run_string().await
+		PromptEndpoints(PrependSourcePrompter {
+			prompter: AutoPrompter::new().await,
+			source: if self.local {
+				vec![]
+			} else {
+				vec![Source(std::borrow::Cow::Owned(format!(
+					"ssh host: {}",
+					self.name
+				)))]
+			},
+			description: "".to_owned(),
+		})
+		.register_endpoints(&mut conn.rpc());
+		let _ = self.remowt.set(conn);
+		Ok(self.remowt.get().expect("just set").clone())
 	}
-	pub async fn read_file_value<D: FromStr>(&self, path: impl AsRef<OsStr>) -> Result<D>
-	where
-		<D as FromStr>::Err: Display,
-	{
-		let text = self.read_file_text(path).await?;
-		D::from_str(&text).map_err(|e| anyhow!("failed to parse value: {e}"))
+
+	/// Client for this host's unprivileged agent.
+	pub async fn remowt(&self) -> Result<Remowt> {
+		Ok(self.connection().await?)
 	}
-	pub async fn cmd(&self, cmd: impl AsRef<OsStr>) -> Result<MyCommand> {
-		self.cmd_escalation(self.escalation_strategy().await?, cmd)
-			.await
+
+	pub fn ensure_nix_plugin(&self) -> Pin<Box<dyn Future<Output = Result<u16>> + Send + '_>> {
+		Box::pin(async {
+			self.nix_plugin
+				.get_or_try_init(|| async {
+					let pkgs = self.pkgs()?;
+					let name = "remowt-plugin-fleet";
+					let plugin = nix_go!(pkgs[{ name }]);
+					let built = plugin
+						.build("out")
+						.context("failed to build the fleet nix plugin")?;
+					let copied = self
+						.remote_derivation(&built)
+						.await
+						.context("failed to copy the fleet nix plugin to the host store")?;
+					let bin = copied.join("bin/remowt-plugin-fleet");
+					self.remowt()
+						.await?
+						.run0_load_plugin_path(NIX_PLUGIN_ID, bin.as_str())
+						.await
+						.context("failed to load the fleet nix plugin")?;
+					self.remowt()
+						.await?
+						.rpc()
+						.wait_for_connection_to(Address::Plugin(NIX_PLUGIN_ID))
+						.await
+						.map_err(|e| anyhow!("failed to wait for plugin"))?;
+					anyhow::Ok(())
+				})
+				.await?;
+			Ok(NIX_PLUGIN_ID)
+		})
 	}
-	pub async fn cmd_escalation(
-		&self,
-		escalation: EscalationStrategy,
-		cmd: impl AsRef<OsStr>,
-	) -> Result<MyCommand> {
-		if self.local {
-			Ok(MyCommand::new(escalation, cmd))
-		} else {
-			let session = self.open_session().await?;
-			Ok(MyCommand::new_on(escalation, cmd, session))
+
+	async fn nix_store(&self) -> Result<Arc<Store>> {
+		if let Some(store) = self.nix_store.get() {
+			return Ok(store.clone());
 		}
-	}
-	pub async fn nix_cmd(&self) -> Result<MyCommand> {
-		let mut nix = self.cmd("nix").await?;
-		nix.args([
-			"--extra-experimental-features",
-			"nix-command",
-			"--extra-experimental-features",
-			"flakes",
-		]);
-		Ok(nix)
+		let conn = self.connection().await?;
+		let socket = match self.deploy_kind().await? {
+			DeployKind::NixosInstall => {
+				remowt_fleet::nix_store_socket(conn, "/mnt?require-sigs=false").await?
+			}
+			_ => remowt_fleet::nix_store_socket(conn, "auto").await?,
+		};
+		let uri = format!("unix://{}", socket.display());
+		let store = Arc::new(Store::open(&uri)?);
+		let _ = self.nix_store.set(store);
+		Ok(self.nix_store.get().expect("just set").clone())
 	}
 
 	pub async fn decrypt(&self, data: SecretData) -> Result<Vec<u8>> {
 		ensure!(data.encrypted, "secret is not encrypted");
-		let mut cmd = self.cmd("fleet-install-secrets").await?;
+		let remowt = self.remowt().await?;
+		let mut cmd = remowt.cmd("fleet-install-secrets");
 		cmd.arg("decrypt").eqarg("--secret", data.to_string());
 		let encoded = cmd
 			.sudo()
@@ -434,8 +381,9 @@
 		data: SecretData,
 		targets: BTreeSet<SecretOwner>,
 	) -> Result<SecretData> {
+		let remowt = self.remowt().await?;
 		ensure!(data.encrypted, "secret is not encrypted");
-		let mut cmd = self.cmd("fleet-install-secrets").await?;
+		let mut cmd = remowt.cmd("fleet-install-secrets");
 		cmd.arg("reencrypt").eqarg("--secret", data.to_string());
 		for target in targets {
 			let key = self.config.key(&target).await?;
@@ -451,74 +399,39 @@
 		Ok(data)
 	}
 	/// Returns path for futureproofing, as path might change i.e on conversion to CA
-	pub async fn remote_derivation(&self, path: &PathBuf) -> Result<PathBuf> {
+	pub async fn remote_derivation(&self, path: impl AsRef<Utf8Path>) -> Result<Utf8PathBuf> {
+		let path = path.as_ref().to_owned();
 		if self.local {
 			// Path is located locally, thus already trusted.
-			return Ok(path.to_owned());
-		}
-		let mut sign = MyCommand::new(
-			// TODO: Look at the current escalation strategy.
-			// ... or switch to run0 right after polkit update
-			EscalationStrategy::Sudo,
-			"nix",
-		);
-		sign.arg("store")
-			.arg("sign")
-			.comparg("--key-file", "/etc/nix/private-key")
-			.arg("-r")
-			.arg(&path);
-		if let Err(e) = sign.sudo().run_nix().await {
-			warn!("failed to sign store paths: {e}");
+			return Ok(path);
 		}
-		let mut nix = MyCommand::new(
-			// Not used
-			EscalationStrategy::Su,
-			"nix",
-		);
-		nix.arg("copy").arg("--substitute-on-destination");
-
-		let proto = if self.legacy_ssh_store.get().cloned().unwrap_or(false) {
-			"ssh"
-		} else {
-			"ssh-ng"
+		let sign: Pin<Box<dyn Future<Output = Result<()>> + Send>> = {
+			let path = path.clone();
+			Box::pin(async move {
+				let local = self.config.local_host();
+				let plugin_id = local.ensure_nix_plugin().await?;
+				let nix = local
+					.remowt()
+					.await?
+					.plugin_endpoints::<remowt_fleet::NixClient<_>>(plugin_id);
+				nix.sign_closure(path, Utf8PathBuf::from("/etc/nix/private-key"))
+					.await
+					.map_err(|e| anyhow!("{e:?}"))?
+					.map_err(|e| anyhow!("{e}"))?;
+				Ok(())
+			})
 		};
-
-		match self.deploy_kind().await? {
-			DeployKind::Fleet | DeployKind::UpgradeToFleet | DeployKind::NixosLustrate => {
-				nix.comparg("--to", format!("{proto}://{}", self.name));
-			}
-			DeployKind::NixosInstall => {
-				nix
-					// Signature checking makes no sense with remote-store store argument set, as we're not even interacting with remote nix daemon
-					.arg("--no-check-sigs")
-					.comparg(
-						"--to",
-						format!("{proto}://root@{}?remote-store=/mnt", self.name),
-					);
-			}
+		if let Err(e) = sign.await {
+			warn!("failed to sign store paths: {e}");
 		}
-		nix.arg(path);
-		nix.run_nix().await.context("nix copy")?;
-		Ok(path.to_owned())
-	}
-	pub async fn systemctl_stop(&self, name: &str) -> Result<()> {
-		let mut cmd = self.cmd("systemctl").await?;
-		cmd.arg("stop").arg(name);
-		cmd.sudo().run().await
-	}
-	pub async fn systemctl_start(&self, name: &str) -> Result<()> {
-		let mut cmd = self.cmd("systemctl").await?;
-		cmd.arg("start").arg(name);
-		cmd.sudo().run().await
-	}
-
-	pub async fn rm_file(&self, path: impl AsRef<OsStr>, sudo: bool) -> Result<()> {
-		let mut cmd = self.cmd("rm").await?;
-		cmd.arg("-f").arg(path);
-		if sudo {
-			cmd = cmd.sudo()
+		let store = self.nix_store().await?;
+		{
+			let path = path.clone();
+			spawn_blocking(move || nix_eval::copy_closure_to(&store, path.as_ref()))
+				.await?
+				.context("copying closure to remote store")?;
 		}
-		cmd.run().await
+		Ok(path)
 	}
 }
 
@@ -630,26 +543,32 @@
 		}
 		Ok(out)
 	}
-	pub fn local_host(&self) -> ConfigHost {
-		ConfigHost {
-			config: self.clone(),
-			name: "<virtual localhost>".to_owned(),
-			host_config: None,
-			nixos_config: OnceLock::new(),
-			nixos_unchecked_config: OnceLock::new(),
-			groups: {
-				let cell = OnceLock::new();
-				let _ = cell.set(vec![]);
-				cell
-			},
-			pkgs_override: Some(self.default_pkgs.clone()),
+	pub fn local_host(&self) -> Arc<ConfigHost> {
+		self.local_host
+			.get_or_init(|| {
+				Arc::new(ConfigHost {
+					config: self.clone(),
+					name: "<virtual localhost>".to_owned(),
+					host_config: None,
+					nixos_config: OnceLock::new(),
+					nixos_unchecked_config: OnceLock::new(),
+					groups: {
+						let cell = OnceLock::new();
+						let _ = cell.set(vec![]);
+						cell
+					},
+					pkgs_override: Some(self.default_pkgs.clone()),
 
-			local: true,
-			session: OnceLock::new(),
-			deploy_kind: OnceLock::new(),
-			session_destination: OnceLock::new(),
-			legacy_ssh_store: OnceLock::new(),
-		}
+					local: true,
+					remowt: OnceLock::new(),
+					nix_store: OnceLock::new(),
+					nix_plugin: tokio::sync::OnceCell::new(),
+					deploy_kind: OnceLock::new(),
+					session_destination: OnceLock::new(),
+					legacy_ssh_store: OnceLock::new(),
+				})
+			})
+			.clone()
 	}
 
 	pub fn preferred_hosts(
@@ -684,7 +603,9 @@
 
 			// TODO: Remove with connectivit refactor
 			local: self.localhost == name,
-			session: OnceLock::new(),
+			remowt: OnceLock::new(),
+			nix_store: OnceLock::new(),
+			nix_plugin: tokio::sync::OnceCell::new(),
 			deploy_kind: OnceLock::new(),
 			session_destination: OnceLock::new(),
 			legacy_ssh_store: OnceLock::new(),
modifiedcrates/fleet-base/src/keys.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/keys.rs
+++ b/crates/fleet-base/src/keys.rs
@@ -31,7 +31,8 @@
 		} else {
 			warn!("Loading key for {}", host);
 			let host = self.host(host)?;
-			let mut cmd = host.cmd("cat").await?;
+			let remowt = host.remowt().await?;
+			let mut cmd = remowt.cmd("cat");
 			cmd.arg("/etc/ssh/ssh_host_ed25519_key.pub");
 			let key = cmd.run_string().await?;
 			self.update_key(&host.name, key.clone());
modifiedcrates/fleet-base/src/lib.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/lib.rs
+++ b/crates/fleet-base/src/lib.rs
@@ -1,8 +1,6 @@
-pub mod command;
 pub mod deploy;
 pub mod fleetdata;
 pub mod host;
 mod keys;
 pub mod opts;
 pub mod primops;
-pub mod secret_storage;
modifiedcrates/fleet-base/src/opts.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/opts.rs
+++ b/crates/fleet-base/src/opts.rs
@@ -1,9 +1,8 @@
 use std::{
 	collections::{BTreeMap, BTreeSet},
 	env::current_dir,
-	ffi::OsString,
 	str::FromStr,
-	sync::{Arc, Mutex},
+	sync::{Arc, OnceLock},
 };
 
 use anyhow::{Context, Result, bail};
@@ -196,7 +195,7 @@
 	}
 
 	// TODO: Config should be detached from opts.
-	pub fn build(&self, nix_args: Vec<OsString>, assert: bool) -> Result<Config> {
+	pub fn build(&self, assert: bool) -> Result<Config> {
 		let cwd = current_dir()?;
 		let mut directory = cwd.clone();
 		let mut fleet_data_path = directory.join("fleet.nix");
@@ -274,11 +273,11 @@
 			data,
 			flake_outputs: flake,
 			local_system: self.local_system.clone(),
-			nix_args,
 			config_field,
 			default_pkgs,
 			nixpkgs,
 			localhost: self.localhost.to_owned(),
+			local_host: OnceLock::new(),
 		}));
 
 		PRIMOPS_DATA
modifiedcrates/fleet-base/src/primops.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/primops.rs
+++ b/crates/fleet-base/src/primops.rs
@@ -1,10 +1,12 @@
 use std::collections::{BTreeMap, BTreeSet, HashMap};
-use std::sync::OnceLock;
+use std::sync::{Arc, OnceLock};
 
 use anyhow::{Context, bail, ensure};
 use fleet_shared::SecretData;
 use itertools::Itertools;
 use nix_eval::{NativeFn, Value, await_in_nix, nix_go, nix_go_json};
+use remowt_endpoints::fs::FsClient;
+use remowt_link_shared::BifConfig;
 use serde::Deserialize;
 use tracing::{info, warn};
 
@@ -28,7 +30,7 @@
 	let pkgs = host_on.pkgs()?;
 	let default_mk_secret_generators = nix_go!(pkgs.mkSecretGenerators);
 	let generators = nix_go!(default_mk_secret_generators(Obj { recipients }));
-	Ok(pkgs.clone().attrs_update(generators)?)
+	pkgs.clone().attrs_update(generators)
 }
 pub fn get_default_pkgs_and_generators(config: &Config) -> Result<Value> {
 	let host_on = config.local_host();
@@ -91,12 +93,18 @@
 			let impure_on: Option<String> = nix_go_json!(default_generator_drv.impureOn);
 
 			let host_on = if let Some(on) = &impure_on {
-				config
-					.host(on)
-					.context("failed to get secret generation target host")?
+				Arc::new(
+					config
+						.host(on)
+						.context("failed to get secret generation target host")?,
+				)
 			} else {
 				config.local_host()
 			};
+
+			let remowt = host_on.remowt().await?;
+			let fs = remowt.endpoints::<FsClient<BifConfig>>();
+
 			let mut recipients = Vec::new();
 			for owner in &expectations.owners {
 				recipients.push(config.key(owner).await?);
@@ -116,12 +124,13 @@
 				.context("failed to copy generator to target host")?;
 
 			// TODO: Remove destdir after everything is done
-			let out_parent = host_on
+			let out_parent = fs
 				.mktemp_dir()
 				.await
+				.map_err(|e| anyhow!("{e:?}"))
 				.context("failed to prepare generator output dir on target host")?;
-			let out = format!("{out_parent}/out");
-			let mut generator_cmd = host_on.cmd(generator).await?;
+			let out = out_parent.join("out");
+			let mut generator_cmd = remowt.cmd(generator);
 			generator_cmd.env("out", &out);
 			if impure_on.is_none() {
 				let project_path: String = config
@@ -138,7 +147,7 @@
 				.context("failed to run impure generator")?;
 
 			{
-				let marker = host_on.read_file_text(format!("{out}/marker")).await?;
+				let marker = fs.read_file_text(out.join("marker")).await?;
 				ensure!(
 					marker == "SUCCESS",
 					"impure generator ended prematurely, secret generation failed"
@@ -147,15 +156,16 @@
 
 			let mut missing_parts = expectations.parts.clone();
 			let mut parts = BTreeMap::new();
-			for part in host_on.read_dir(&out).await? {
+			for part in fs.read_dir(&out).await? {
+				let part = part.into_string();
 				if part == "created_at" || part == "expires_at" || part == "marker" {
 					continue;
 				}
 				let Some(part_def) = missing_parts.remove(&part) else {
 					bail!("secret generator has produced an unexpected part: {part}");
 				};
-				let contents: SecretData = host_on
-					.read_file_text(format!("{out}/{part}"))
+				let contents: SecretData = fs
+					.read_file_text(out.join("part"))
 					.await?
 					.parse()
 					.map_err(|e| anyhow!("failed to decode secret {out:?} part {part:?}: {e}"))?;
@@ -178,11 +188,12 @@
 				);
 			}
 
-			let created_at = host_on.read_file_value(format!("{out}/created_at")).await?;
-			let expires_at = host_on
-				.read_file_value(format!("{out}/expires_at"))
-				.await
-				.ok();
+			let created_at = fs.read_file_value(out.join("created_at")).await??;
+			let expires_at = match fs.read_file_value(out.join("expires_at")).await {
+				Ok(v) => Some(v?),
+				Err(remowt_endpoints::fs::Error::NotFound) => None,
+				Err(e) => return Err(e.into()),
+			};
 
 			let new_data = FleetSecretData {
 				created_at,
deletedcrates/fleet-base/src/secret_storage.rsdiffbeforeafterboth
--- a/crates/fleet-base/src/secret_storage.rs
+++ /dev/null
@@ -1,278 +0,0 @@
-use anyhow::{Result, bail, ensure};
-use itertools::Itertools;
-use std::fs::{File, metadata};
-use std::io::{self, ErrorKind, Read, Write};
-use std::path::PathBuf;
-use std::str::FromStr;
-use std::{env, fs};
-
-use tempfile::{TempPath, tempfile_in};
-use toml_edit::{Document, DocumentMut, Formatted, Item, Value};
-
-struct Name(String);
-
-fn encode_name(name: &str) -> Name {
-	assert!(
-		!name.starts_with(['_', '.']),
-		"groups should not start with _ or ."
-	);
-	assert!(
-		!name.chars().any(|c| c == '/'),
-		"group name should not contain internal slash"
-	);
-	Name(name.to_owned())
-}
-
-enum RewriteError {
-	ConcurrentCreate,
-	ConcurrentDelete,
-	ConcurrentModify,
-	ConcurrentWrite,
-	Io(io::Error),
-	Persist(tempfile::PersistError),
-}
-
-fn safe_rewrite(
-	path: &PathBuf,
-	old_content: Option<Vec<u8>>,
-	new_content: Option<Vec<u8>>,
-) -> Result<(), RewriteError> {
-	let mut f = match (old_content.is_some(), new_content.is_some()) {
-		(false, true) => match File::create_new(path) {
-			Ok(v) => v,
-			Err(e) if e.kind() == ErrorKind::AlreadyExists => {
-				return Err(RewriteError::ConcurrentCreate);
-			}
-			Err(e) => return Err(RewriteError::Io(e)),
-		},
-		(true, _) => match File::open(&path) {
-			Ok(v) => v,
-			Err(_e) => return Err(RewriteError::ConcurrentDelete),
-		},
-		(false, false) => match metadata(&path) {
-			Err(e) if e.kind() == ErrorKind::NotFound => {
-				return Ok(());
-			}
-			Ok(_) => return Err(RewriteError::ConcurrentCreate),
-			Err(e) => return Err(RewriteError::Io(e)),
-		},
-	};
-	f.lock().map_err(RewriteError::Io)?;
-	let mut check_content = vec![];
-	f.read_to_end(&mut check_content)
-		.map_err(RewriteError::Io)?;
-	match &old_content {
-		Some(old) => {
-			if old != &check_content {
-				return Err(RewriteError::ConcurrentModify);
-			}
-		}
-		None => {
-			if !check_content.is_empty() {
-				return Err(RewriteError::ConcurrentDelete);
-			}
-		}
-	}
-	if let Some(new_content) = new_content {
-		if Some(&new_content) == old_content.as_ref() {
-			return Ok(());
-		}
-		let dir = path.parent().expect("file is in directory, thus not root");
-		let mut tempfile = tempfile::Builder::new()
-			.prefix(".rewrite-")
-			.tempfile_in(dir)
-			.map_err(RewriteError::Io)?;
-		tempfile.write_all(&new_content).map_err(RewriteError::Io)?;
-		tempfile.flush().map_err(RewriteError::Io)?;
-		tempfile.persist(path).map_err(RewriteError::Persist)?;
-	} else {
-		fs::remove_file(path).map_err(RewriteError::Io)?;
-	}
-	let _ = f.unlock();
-	Ok(())
-}
-fn update_string(path: PathBuf, modify: impl Fn(&mut Option<String>) -> Result<()>) -> Result<()> {
-	loop {
-		let orig = match fs::read_to_string(&path) {
-			Ok(v) => Some(v),
-			Err(e) if e.kind() == ErrorKind::NotFound => None,
-			Err(e) => return Err(e.into()),
-		};
-		let mut edit = orig.clone();
-		modify(&mut edit);
-
-		match safe_rewrite(&path, orig.map(String::into), edit.map(String::into)) {
-			Ok(()) => return Ok(()),
-			Err(
-				RewriteError::ConcurrentCreate
-				| RewriteError::ConcurrentModify
-				| RewriteError::ConcurrentWrite
-				| RewriteError::ConcurrentDelete,
-			) => {
-				continue;
-			}
-			Err(RewriteError::Io(io)) => return Err(io.into()),
-			Err(RewriteError::Persist(io)) => return Err(io.into()),
-		}
-	}
-}
-fn update_toml(path: PathBuf, modify: impl Fn(&mut DocumentMut) -> Result<()>) -> Result<()> {
-	update_string(path, |str| {
-		let mut doc = match str {
-			None => DocumentMut::new(),
-			Some(v) => DocumentMut::from_str(v)?,
-		};
-		modify(&mut doc)?;
-		if doc.is_empty() {
-			*str = None
-		} else {
-			*str = Some(doc.to_string())
-		}
-		Ok(())
-	})
-}
-fn update_lines(path: PathBuf, modify: impl Fn(&mut Vec<String>) -> Result<()>) -> Result<()> {
-	update_string(path, |str| {
-		let mut list = if let Some(str) = str {
-			str.split('\n').map(|s| s.to_owned()).collect_vec()
-		} else {
-			vec![]
-		};
-		let had_end_newline = if list.last().map(|v| v.as_str()) == Some("") {
-			list.pop();
-			true
-		} else {
-			false
-		};
-		modify(&mut list)?;
-		if list.is_empty() {
-			*str = None
-		} else {
-			if had_end_newline {
-				list.push("".to_owned())
-			}
-			*str = Some(list.join("\n"));
-		}
-		Ok(())
-	})
-}
-fn update_section(
-	data: &mut Vec<String>,
-	start: &str,
-	end: &str,
-	modify: impl Fn(&mut Vec<String>) -> Result<()>,
-) -> Result<()> {
-	let first = data
-		.iter()
-		.enumerate()
-		.filter(|(_, v)| *v == start)
-		.at_most_one()
-		.map_err(|_| anyhow::anyhow!("there should be at most one section start"))?
-		.map(|(v, _)| v);
-	let last = data
-		.iter()
-		.enumerate()
-		.filter(|(_, v)| *v == end)
-		.at_most_one()
-		.map_err(|_| anyhow::anyhow!("there should be at most one section end"))?
-		.map(|(v, _)| v);
-
-	match (first, last) {
-		(None, None) => {
-			let mut out = Vec::new();
-			modify(&mut out)?;
-			if out.is_empty() {
-				return Ok(());
-			}
-			data.push(start.to_owned());
-			data.extend(out);
-			data.push(end.to_owned());
-			Ok(())
-		}
-		(None, Some(_)) | (Some(_), None) => {
-			bail!("mismatched section start/end")
-		}
-		(Some(first), Some(last)) => {
-			ensure!(first < last, "section end should come after start");
-			let mut out = data[first + 1..last]
-				.iter()
-				.map(|v| v.to_owned())
-				.collect_vec();
-			modify(&mut out)?;
-			if out.is_empty() {
-				data.drain(first..=last);
-			} else {
-				data.splice(first + 1..last, out);
-			}
-			Ok(())
-		}
-	}
-}
-
-struct Group {
-	path: PathBuf,
-}
-impl Group {
-	fn new(path: PathBuf) -> Self {
-		Self { path }
-	}
-	fn manage(&self, manager: &str) {}
-	fn ensure_managing(&self, manager: &str) {
-		if !self.has_stored() {
-			return;
-		}
-		let managed = match fs::read_to_string(self.path.join(".managed_by")) {
-			Ok(found_manager) => found_manager.lines().any(|line| line == manager),
-			Err(e) if e.kind() == ErrorKind::NotFound => true,
-			Err(e) => panic!("{e}"),
-		};
-		assert!(managed);
-	}
-	fn has_stored(&self) -> bool {
-		match fs::metadata(&self.path) {
-			Ok(d) => d.is_dir(),
-			Err(e) if e.kind() == ErrorKind::NotFound => false,
-			Err(e) => panic!("{e}"),
-		}
-	}
-}
-
-struct Root {
-	path: PathBuf,
-}
-impl Root {
-	fn new(path: PathBuf) -> Self {
-		Self { path }
-	}
-	fn subgroup(&self, name: &str) -> Group {
-		Group::new(self.path.join(name))
-	}
-}
-
-#[test]
-fn test() {
-	let mut data = vec![
-		"a".to_owned(),
-		"b".to_owned(),
-		"start".to_owned(),
-		"c".to_owned(),
-		"d".to_owned(),
-		"end".to_owned(),
-		"e".to_owned(),
-		"f".to_owned(),
-	];
-	update_section(&mut data, "start", "end", |a| {
-		a.push("vv".to_owned());
-		Ok(())
-	})
-	.unwrap();
-	dbg!(&data);
-	// for v in 0..1000 {
-	// 	update_toml(PathBuf::from("./test.toml"), |e| {
-	// 		e.as_table_mut()
-	// 			.insert("hello", Item::Value(Value::Integer(Formatted::new(v))));
-	// 	})
-	// 	.expect("update")
-	// }
-	// v.subgroup(name)
-}
modifiedcrates/nix-eval/Cargo.tomldiffbeforeafterboth
before · crates/nix-eval/Cargo.toml
1[package]2name = "nix-eval"3version.workspace = true4build = "build.rs"5edition.workspace = true6rust-version.workspace = true78[dependencies]9anyhow.workspace = true10nixlike.workspace = true11serde = { workspace = true, features = ["derive"] }12serde_json.workspace = true13thiserror.workspace = true14tracing.workspace = true1516cxx.workspace = true17itertools.workspace = true18test-log.workspace = true19tokio.workspace = true20tracing-indicatif = { workspace = true, optional = true }21vte.workspace = true2223[build-dependencies]24bindgen.workspace = true25cxx-build.workspace = true26pkg-config.workspace = true2728[features]29indicatif = ["dep:tracing-indicatif"]
modifiedcrates/nix-eval/src/drv.rsdiffbeforeafterboth
--- a/crates/nix-eval/src/drv.rs
+++ b/crates/nix-eval/src/drv.rs
@@ -66,7 +66,13 @@
 #[derive(Debug, Deserialize)]
 pub struct DrvParsed {
 	pub inputs: DrvInputs,
-	pub outputs: HashMap<String, serde_json::Value>,
+	pub outputs: HashMap<String, DrvParsedOutput>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct DrvParsedOutput {
+	#[serde(default)]
+	pub path: Option<String>,
 }
 
 #[derive(Debug, Deserialize)]
@@ -82,18 +88,19 @@
 	pub outputs: Vec<String>,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct DrvGraph {
 	pub root: String,
 	pub nodes: HashMap<String, DrvNode>,
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct DrvNode {
 	pub name: String,
 	pub input_drvs: HashMap<String, Vec<String>>,
 	pub input_srcs: Vec<String>,
-	pub outputs: Vec<String>,
+	// TODO: CA outputs without a known paths are skipped
+	pub outputs: HashMap<String, String>,
 }
 
 impl DrvGraph {
@@ -124,19 +131,59 @@
 				}
 			}
 
+			let outputs: HashMap<String, String> = parsed
+				.outputs
+				.into_iter()
+				.filter_map(|(name, out)| out.path.map(|p| (name, to_absolute_store_path(&sd, &p))))
+				.collect();
+
 			nodes.insert(
 				path.clone(),
 				DrvNode {
 					name: extract_drv_name(&path),
 					input_drvs,
 					input_srcs: parsed.inputs.srcs,
-					outputs: parsed.outputs.into_keys().collect(),
+					outputs,
 				},
 			);
 		}
 
 		Ok(Self { root, nodes })
 	}
+
+	pub fn wanted_outputs(&self, root_outputs: &[String]) -> HashMap<String, Vec<String>> {
+		let mut wanted: HashMap<String, HashSet<String>> = HashMap::new();
+		wanted.insert(self.root.clone(), root_outputs.iter().cloned().collect());
+
+		let mut queue: VecDeque<String> = VecDeque::new();
+		queue.push_back(self.root.clone());
+		while let Some(path) = queue.pop_front() {
+			let Some(node) = self.nodes.get(&path) else {
+				continue;
+			};
+			for (dep_path, dep_outputs) in &node.input_drvs {
+				let entry = wanted.entry(dep_path.clone()).or_default();
+				let mut changed = false;
+				for o in dep_outputs {
+					if entry.insert(o.clone()) {
+						changed = true;
+					}
+				}
+				if changed {
+					queue.push_back(dep_path.clone());
+				}
+			}
+		}
+
+		wanted
+			.into_iter()
+			.map(|(k, v)| {
+				let mut v: Vec<_> = v.into_iter().collect();
+				v.sort();
+				(k, v)
+			})
+			.collect()
+	}
 }
 
 fn extract_drv_name(drv_path: &str) -> String {
modifiedcrates/nix-eval/src/lib.ccdiffbeforeafterboth
--- a/crates/nix-eval/src/lib.cc
+++ b/crates/nix-eval/src/lib.cc
@@ -1,8 +1,25 @@
 #include "nix-eval/src/lib.rs"
 #include "lib.hh"
 #include <nix/fetchers/fetch-settings.hh>
+#include <nix/store/build-result.hh>
+#include <nix/store/content-address.hh>
+#include <nix/store/derived-path.hh>
+#include <nix/store/local-fs-store.hh>
+#include <nix/store/outputs-spec.hh>
+#include <nix/store/path-info.hh>
+#include <nix/store/profiles.hh>
+#include <nix/store/realisation.hh>
+#include <nix/store/store-api.hh>
+#include <nix/util/file-system.hh>
+#include <nix/util/hash.hh>
+#include <nix/util/posix-source-accessor.hh>
 #include <nix/util/ref.hh>
+#include <nix/util/signature/local-keys.hh>
+#include <nix/util/signature/signer.hh>
+#include <nix/util/source-path.hh>
 #include <nix_api_fetchers.h>
+#include <nix_api_store_internal.h>
+#include <sstream>
 
 struct nix_fetchers_settings {
   nix::ref<nix::fetchers::Settings> settings;
@@ -15,3 +32,175 @@
   bool result = settings_ref->set(setting, value);
 }
 }
+
+rust::String switch_profile(Store *store, rust::Str profile,
+                            rust::Str store_path) {
+  try {
+    auto nixStore = store->ptr;
+    auto *lfs = dynamic_cast<nix::LocalFSStore *>(&*nixStore);
+    if (!lfs)
+      return rust::String("destination is not a local-fs store");
+    auto path = nixStore->parseStorePath(std::string(store_path));
+    std::filesystem::path prof = std::string(profile);
+    auto gen = nix::createGeneration(*lfs, prof, path);
+    nix::switchLink(prof, gen);
+    return rust::String();
+  } catch (const std::exception &e) {
+    return rust::String(e.what());
+  }
+}
+
+rust::String sign_closure(Store *store, rust::Str store_path,
+                          rust::Str key_file) {
+  try {
+    auto nixStore = store->ptr;
+    nix::LocalSigner signer(
+        nix::SecretKey::parse(nix::readFile(std::string(key_file))));
+    auto root = nixStore->parseStorePath(std::string(store_path));
+    nix::StorePathSet closure;
+    nixStore->computeFSClosure(root, closure);
+    for (auto &p : closure) {
+      auto info = nixStore->queryPathInfo(p);
+      nix::ValidPathInfo info2(*info);
+      info2.sign(*nixStore, signer);
+      nixStore->addSignatures(p, info2.sigs);
+    }
+    return rust::String();
+  } catch (const std::exception &e) {
+    return rust::String(e.what());
+  }
+}
+
+CxxListGenerationsResult list_generations(rust::Str profile_path) {
+  CxxListGenerationsResult out{rust::String(), {}};
+  try {
+    auto [gens, current] =
+        nix::findGenerations(std::filesystem::path(std::string(profile_path)));
+    for (auto &g : gens) {
+      CxxProfileGeneration cg{};
+      cg.id = g.number;
+      cg.store_path = rust::String(g.path.string());
+      cg.creation_time_unix = static_cast<int64_t>(g.creationTime);
+      cg.current = current.has_value() && *current == g.number;
+      out.generations.push_back(cg);
+    }
+  } catch (const std::exception &e) {
+    out.error = rust::String(e.what());
+  }
+  return out;
+}
+
+static std::vector<std::string> split_lines(rust::Str joined) {
+  std::vector<std::string> out;
+  std::string buf;
+  std::istringstream iss((std::string(joined)));
+  while (std::getline(iss, buf)) {
+    if (!buf.empty())
+      out.push_back(buf);
+  }
+  return out;
+}
+
+CxxBuildResult build_drv_outputs(Store *store, rust::Str drv_path,
+                                 // TODO: Vec<str>
+                                 rust::Str output_names_joined) {
+  CxxBuildResult res{rust::String(), {}};
+  try {
+    auto nixStore = store->ptr;
+    auto sp = nixStore->parseStorePath(std::string(drv_path));
+
+    nix::DerivedPath::Built built{
+        .drvPath = nix::makeConstantStorePathRef(sp),
+        .outputs = nix::OutputsSpec{nix::OutputsSpec::All{}},
+    };
+    auto names = split_lines(output_names_joined);
+    if (!names.empty()) {
+      std::set<nix::OutputName, std::less<>> nameSet;
+      for (auto &n : names)
+        nameSet.insert(n);
+      built.outputs =
+          nix::OutputsSpec{nix::OutputsSpec::Names{std::move(nameSet)}};
+    }
+
+    std::vector<nix::DerivedPath> reqs;
+    reqs.push_back(built);
+
+    auto results = nixStore->buildPathsWithResults(reqs);
+    for (auto &r : results) {
+      if (auto *failure = r.tryGetFailure()) {
+        res.error = rust::String(failure->what());
+        return res;
+      }
+    }
+    for (auto &r : results) {
+      if (auto *success = r.tryGetSuccess()) {
+        for (auto &[name, real] : success->builtOutputs) {
+          res.outputs.push_back(
+              rust::String(nixStore->printStorePath(real.outPath)));
+        }
+      }
+    }
+  } catch (const std::exception &e) {
+    res.error = rust::String(e.what());
+  }
+  return res;
+}
+
+CxxBuildResult substitute_paths(Store *store, rust::Str paths_joined) {
+  CxxBuildResult res{rust::String(), {}};
+  try {
+    auto nixStore = store->ptr;
+    auto paths = split_lines(paths_joined);
+
+    std::vector<nix::DerivedPath> reqs;
+    reqs.reserve(paths.size());
+    for (auto &p : paths) {
+      reqs.push_back(nix::DerivedPath::Opaque{nixStore->parseStorePath(p)});
+    }
+
+    // Substituter miss will cause scheduler to trigger build
+    auto results = nixStore->buildPathsWithResults(reqs);
+    for (auto &r : results) {
+      if (r.tryGetSuccess() == nullptr)
+        continue;
+      if (auto *opaque = std::get_if<nix::DerivedPath::Opaque>(&r.path.raw())) {
+        res.outputs.push_back(
+            rust::String(nixStore->printStorePath(opaque->path)));
+      }
+    }
+  } catch (const std::exception &e) {
+    res.error = rust::String(e.what());
+  }
+  return res;
+}
+
+bool is_valid_path(Store *store, rust::Str path) {
+  try {
+    auto nixStore = store->ptr;
+    auto sp = nixStore->parseStorePath(std::string(path));
+    return nixStore->isValidPath(sp);
+  } catch (const std::exception &) {
+    return false;
+  }
+}
+
+AddFileToStoreResult add_file_to_store(Store *store, rust::Str name,
+                                       rust::Str path) {
+  AddFileToStoreResult out{rust::String(), rust::String(), rust::String()};
+  try {
+    auto nixStore = store->ptr;
+    auto src = nix::PosixSourceAccessor::createAtRoot(
+        std::filesystem::path(std::string(path)));
+    auto info = nixStore->addToStoreSlow(std::string(name), src,
+                                         nix::ContentAddressMethod::Raw::Flat,
+                                         nix::HashAlgorithm::SHA256);
+    out.store_path = rust::String(nixStore->printStorePath(info.path));
+    if (info.ca.has_value()) {
+      out.hash =
+          rust::String(info.ca->hash.to_string(nix::HashFormat::SRI, true));
+    }
+  } catch (const std::exception &e) {
+    out.error = rust::String(e.what());
+  }
+  return out;
+}
modifiedcrates/nix-eval/src/lib.hhdiffbeforeafterboth
--- a/crates/nix-eval/src/lib.hh
+++ b/crates/nix-eval/src/lib.hh
@@ -1,7 +1,32 @@
 #pragma once
+#include "rust/cxx.h"
 #include <nix_api_fetchers.h>
 
+struct Store;
+
+struct AddFileToStoreResult;
+struct CxxBuildResult;
+
 extern "C" {
 void set_fetcher_setting(nix_fetchers_settings *settings, const char *setting,
                          const char *value);
 }
+
+rust::String switch_profile(Store *store, rust::Str profile,
+                            rust::Str store_path);
+
+rust::String sign_closure(Store *store, rust::Str store_path,
+                          rust::Str key_file);
+
+struct CxxListGenerationsResult;
+CxxListGenerationsResult list_generations(rust::Str profile_path);
+
+AddFileToStoreResult add_file_to_store(Store *store, rust::Str name,
+                                       rust::Str path);
+
+CxxBuildResult build_drv_outputs(Store *store, rust::Str drv_path,
+                                 rust::Str output_names_joined);
+
+CxxBuildResult substitute_paths(Store *store, rust::Str paths_joined);
+
+bool is_valid_path(Store *store, rust::Str path);
modifiedcrates/nix-eval/src/lib.rsdiffbeforeafterboth
--- a/crates/nix-eval/src/lib.rs
+++ b/crates/nix-eval/src/lib.rs
@@ -1,12 +1,13 @@
 use std::borrow::Cow;
 use std::cell::RefCell;
+use std::collections::HashMap;
 use std::ffi::{CStr, CString, c_char, c_int, c_uint, c_void};
 use std::ptr::{null, null_mut};
 use std::sync::{Arc, LazyLock, OnceLock};
 use std::{array, fmt, slice};
-use std::{collections::HashMap, path::PathBuf};
 
 use anyhow::{Context, anyhow, bail};
+use camino::{Utf8Path, Utf8PathBuf};
 use itertools::Itertools;
 use serde::Serialize;
 use serde::de::DeserializeOwned;
@@ -38,8 +39,9 @@
 	make_attrs, make_bindings_builder, make_list, make_list_builder, realised_string,
 	realised_string_free, realised_string_get_buffer_size, realised_string_get_buffer_start,
 	realised_string_get_store_path, realised_string_get_store_path_count, register_primop,
-	set_err_msg, setting_set, state_free, store_open, store_parse_path, store_path_free,
-	store_path_name, string_realise, value, value_call, value_decref, value_force, value_incref,
+	set_err_msg, setting_set, state_free, store_copy_closure, store_free, store_open,
+	store_parse_path, store_path_free, store_path_name, string_realise, value, value_call,
+	value_decref, value_force, value_incref,
 };
 
 // Contains macros helpers
@@ -47,6 +49,7 @@
 pub mod logging;
 #[doc(hidden)]
 pub mod macros;
+pub mod scheduler;
 
 #[doc(hidden)]
 pub mod __macro_support {
@@ -68,8 +71,28 @@
 }
 #[cxx::bridge]
 pub mod nix_cxx {
+	struct AddFileToStoreResult {
+		error: String,
+		store_path: String,
+		hash: String,
+	}
+	struct CxxProfileGeneration {
+		id: u64,
+		store_path: String,
+		creation_time_unix: i64,
+		current: bool,
+	}
+	struct CxxListGenerationsResult {
+		error: String,
+		generations: Vec<CxxProfileGeneration>,
+	}
+	struct CxxBuildResult {
+		error: String,
+		outputs: Vec<String>,
+	}
 	unsafe extern "C++" {
 		type nix_fetchers_settings;
+		type Store;
 		include!("nix-eval/src/lib.hh");
 
 		#[allow(clippy::missing_safety_doc)]
@@ -78,6 +101,34 @@
 			setting: *const c_char,
 			value: *const c_char,
 		);
+
+		#[allow(clippy::missing_safety_doc)]
+		unsafe fn switch_profile(store: *mut Store, profile: &str, store_path: &str) -> String;
+
+		#[allow(clippy::missing_safety_doc)]
+		unsafe fn sign_closure(store: *mut Store, store_path: &str, key_file: &str) -> String;
+
+		#[allow(clippy::missing_safety_doc)]
+		unsafe fn add_file_to_store(
+			store: *mut Store,
+			name: &str,
+			path: &str,
+		) -> AddFileToStoreResult;
+
+		fn list_generations(profile_path: &str) -> CxxListGenerationsResult;
+
+		#[allow(clippy::missing_safety_doc)]
+		unsafe fn build_drv_outputs(
+			store: *mut Store,
+			drv_path: &str,
+			output_names_joined: &str,
+		) -> CxxBuildResult;
+
+		#[allow(clippy::missing_safety_doc)]
+		unsafe fn substitute_paths(store: *mut Store, paths_joined: &str) -> CxxBuildResult;
+
+		#[allow(clippy::missing_safety_doc)]
+		unsafe fn is_valid_path(store: *mut Store, path: &str) -> bool;
 	}
 }
 
@@ -186,7 +237,7 @@
 		unsafe { set_err_msg(self.0, err as c_int, msg.as_ptr()) };
 	}
 	pub fn set_err(&mut self, err: anyhow::Error) {
-		let mut fmt = format!("{err:?}").replace("\0", "\\0");
+		let fmt = format!("{err:?}").replace("\0", "\\0");
 		self.set_err_raw(
 			NixErrorKind::Generic,
 			&CString::new(fmt).expect("NUL bytes were just replaced"),
@@ -352,6 +403,125 @@
 	with_default_context(|c, _| unsafe { setting_set(c, s.as_ptr(), v.as_ptr()) }).map(|_| ())
 }
 
+#[instrument(skip(dst))]
+pub fn copy_closure_to(dst: &Store, path: &Utf8Path) -> Result<()> {
+	let path_c = CString::new(path.as_str())?;
+	with_store_context(|c, src_store, _state| -> Result<()> {
+		let sp = unsafe { store_parse_path(c, src_store, path_c.as_ptr()) };
+		if sp.is_null() {
+			bail!("failed to parse store path {path}");
+		}
+		let rc = unsafe { store_copy_closure(c, src_store, dst.0, sp) };
+		unsafe { store_path_free(sp) };
+		if rc != nix_raw::err_NIX_OK {
+			bail!("store_copy_closure failed (code {rc})");
+		}
+		Ok(())
+	})?
+}
+
+#[instrument]
+pub fn switch_profile(profile: &str, store_path: &Utf8Path) -> Result<()> {
+	let msg = with_store_context(|_c, store, _state| unsafe {
+		nix_cxx::switch_profile(store.cast(), profile, store_path.as_str())
+	})?
+	.to_string();
+	if msg.is_empty() {
+		Ok(())
+	} else {
+		bail!("failed to switch profile {profile}: {msg}");
+	}
+}
+
+// TODO: fleet operator-managed key file
+#[instrument]
+pub fn sign_closure(store_path: &str, key_file: &str) -> Result<()> {
+	let msg = with_store_context(|_c, store, _state| unsafe {
+		nix_cxx::sign_closure(store.cast(), store_path, key_file)
+	})?
+	.to_string();
+	if msg.is_empty() {
+		Ok(())
+	} else {
+		bail!("failed to sign {store_path}: {msg}");
+	}
+}
+
+#[derive(Debug)]
+pub struct AddedFile {
+	pub store_path: Utf8PathBuf,
+	pub hash: String,
+}
+
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
+pub struct ProfileGeneration {
+	pub id: u64,
+	pub store_path: Utf8PathBuf,
+	pub creation_time_unix: i64,
+	pub current: bool,
+}
+
+#[instrument]
+pub fn list_generations(profile_path: &str) -> Result<Vec<ProfileGeneration>> {
+	let res = nix_cxx::list_generations(profile_path);
+	if !res.error.is_empty() {
+		bail!(
+			"failed to list generations at {profile_path}: {}",
+			res.error
+		);
+	}
+	Ok(res
+		.generations
+		.into_iter()
+		.map(|g| ProfileGeneration {
+			id: g.id,
+			store_path: Utf8PathBuf::from(g.store_path),
+			creation_time_unix: g.creation_time_unix,
+			current: g.current,
+		})
+		.collect())
+}
+
+#[instrument]
+pub fn add_file_to_store(name: &str, path: &Utf8Path) -> Result<AddedFile> {
+	let res = with_store_context(|_c, store, _state| unsafe {
+		nix_cxx::add_file_to_store(store.cast(), name, path.as_str())
+	})?;
+	if !res.error.is_empty() {
+		bail!("failed to add {path} to store: {}", res.error);
+	}
+	Ok(AddedFile {
+		store_path: Utf8PathBuf::from(res.store_path),
+		hash: res.hash,
+	})
+}
+
+pub fn build_drv_outputs(drv_path: &str, output_names: &[String]) -> Result<Vec<String>> {
+	let joined = output_names.join("\n");
+	let res = with_store_context(|_c, store, _state| unsafe {
+		nix_cxx::build_drv_outputs(store.cast(), drv_path, &joined)
+	})?;
+	if !res.error.is_empty() {
+		bail!("build of {drv_path} failed: {}", res.error);
+	}
+	Ok(res.outputs)
+}
+
+pub fn substitute_paths(paths: &[String]) -> Result<Vec<String>> {
+	let joined = paths.join("\n");
+	let res = with_store_context(|_c, store, _state| unsafe {
+		nix_cxx::substitute_paths(store.cast(), &joined)
+	})?;
+	if !res.error.is_empty() {
+		warn!("substitute_paths reported: {}", res.error);
+	}
+	Ok(res.outputs)
+}
+
+pub fn is_valid_path(path: &str) -> Result<bool> {
+	with_store_context(|_c, store, _state| unsafe { nix_cxx::is_valid_path(store.cast(), path) })
+}
+
 pub struct FetchSettings(*mut fetchers_settings);
 impl FetchSettings {
 	pub fn new() -> Self {
@@ -450,17 +620,31 @@
 	unsafe { *user_data.cast::<String>() = s.to_owned() };
 }
 
-struct Store(*mut c_store);
+pub struct Store(*mut c_store);
 unsafe impl Send for Store {}
 unsafe impl Sync for Store {}
 
 impl Store {
+	pub fn open(uri: &str) -> Result<Self> {
+		let uri = CString::new(uri)?;
+		let ptr = with_default_context(|c, _| unsafe { store_open(c, uri.as_ptr(), null_mut()) })?;
+		if ptr.is_null() {
+			bail!("failed to open store");
+		}
+		Ok(Store(ptr))
+	}
+
 	fn parse_path(&self, path: &CStr) -> Result<StorePath> {
 		with_default_context(|c, _| {
 			StorePath(unsafe { store_parse_path(c, self.0, path.as_ptr()) })
 		})
 	}
 }
+impl Drop for Store {
+	fn drop(&mut self) {
+		unsafe { store_free(self.0) }
+	}
+}
 
 #[repr(transparent)]
 pub struct EvalState(*mut c_eval_state);
@@ -858,7 +1042,7 @@
 		Ok(out)
 	}
 	#[instrument(name = "build", skip(self), fields(output))]
-	pub fn build(&self, output: &str) -> Result<PathBuf> {
+	pub fn build(&self, output: &str) -> Result<Utf8PathBuf> {
 		if !self.is_derivation() {
 			bail!("expected derivation to build")
 		}
@@ -880,14 +1064,15 @@
 			.get_field("drvPath")
 			.context("getting drvPath")?
 			.to_string()?;
-		let graph = drv::DrvGraph::resolve(&drv_path)?;
+		let graph = Arc::new(drv::DrvGraph::resolve(&drv_path)?);
 		let _guard = logging::register_build_graph(&Span::current(), &graph);
 
-		// to_string here blocks until the path is built
+		scheduler::build_graph_sync(graph.clone(), vec![output.to_owned()])?;
+
 		let s = v.builtin_to_string()?;
 		let rs = s.to_realised_string()?;
 		let out_path = rs.as_str().to_owned();
-		Ok(PathBuf::from(out_path))
+		Ok(Utf8PathBuf::from(out_path))
 	}
 	pub fn as_json<T: DeserializeOwned>(&self) -> Result<T> {
 		let to_json = Self::eval("builtins.toJSON")?;
modifiedcrates/nix-eval/src/logging.ccdiffbeforeafterboth
--- a/crates/nix-eval/src/logging.cc
+++ b/crates/nix-eval/src/logging.cc
@@ -96,7 +96,9 @@
 
 extern "C" {
 void apply_tracing_logger() {
-  logger = std::make_unique<TracingLogger>();
+  auto old = logger;
+  logger = new TracingLogger();
+  delete old;
   // verbosity = lvlVomit;
 }
 rust::Box<ErrorInfoBuilder>
modifiedcrates/nix-eval/src/logging.rsdiffbeforeafterboth
--- a/crates/nix-eval/src/logging.rs
+++ b/crates/nix-eval/src/logging.rs
@@ -499,7 +499,9 @@
 			} else if level == Level::INFO {
 				info!(target: "nix", "{}", s)
 			} else if level == Level::DEBUG {
-				debug!(target: "nix", "{}", s)
+				if s != "querying info about missing paths" {
+					debug!(target: "nix", "{}", s)
+				}
 			} else {
 				trace!(target: "nix", "{}", s)
 			}
@@ -583,7 +585,9 @@
 	} else if level == Level::INFO {
 		info!(target: "nix", "{v}")
 	} else if level == Level::DEBUG {
-		debug!(target: "nix", "{v}")
+		if v != "querying info about missing paths" {
+			debug!(target: "nix", "{v}")
+		}
 	} else {
 		trace!(target: "nix", "{v}")
 	}
addedcrates/nix-eval/src/scheduler.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/nix-eval/src/scheduler.rs
@@ -0,0 +1,372 @@
+use std::collections::{HashMap, HashSet};
+use std::sync::Arc;
+
+use anyhow::{Context, Result, bail};
+use futures::stream::{FuturesUnordered, StreamExt};
+use tokio::sync::{Semaphore, broadcast};
+use tracing::{debug, info, instrument, warn};
+
+use crate::drv::DrvGraph;
+
+#[derive(Clone, Debug)]
+pub enum BuildEvent {
+	SubstitutePrepassStarted {
+		paths: usize,
+	},
+	SubstitutePrepassFinished {
+		satisfied: usize,
+	},
+	DrvStarted {
+		drv_path: String,
+		name: String,
+		wanted: Vec<String>,
+	},
+	DrvSkipped {
+		drv_path: String,
+		name: String,
+	},
+	DrvFinished {
+		drv_path: String,
+		name: String,
+	},
+	DrvFailed {
+		drv_path: String,
+		name: String,
+		error: String,
+	},
+	DrvCancelled {
+		drv_path: String,
+		name: String,
+		failed_dep: String,
+	},
+}
+
+pub struct Scheduler {
+	parallelism: usize,
+	events: broadcast::Sender<BuildEvent>,
+}
+
+impl Scheduler {
+	pub fn new(parallelism: usize) -> Self {
+		let parallelism = parallelism.max(1);
+		let (events, _) = broadcast::channel(1024);
+		Self {
+			parallelism,
+			events,
+		}
+	}
+
+	pub fn subscribe(&self) -> broadcast::Receiver<BuildEvent> {
+		self.events.subscribe()
+	}
+
+	#[instrument(name = "scheduler", skip(self, graph), fields(root = %graph.root, nodes = graph.nodes.len()))]
+	pub async fn run(&self, graph: Arc<DrvGraph>, root_outputs: Vec<String>) -> Result<()> {
+		let wanted = graph.wanted_outputs(&root_outputs);
+
+		self.substitute_prepass(&graph, &wanted).await?;
+		self.build_topo(&graph, wanted).await
+	}
+
+	async fn substitute_prepass(
+		&self,
+		graph: &DrvGraph,
+		wanted: &HashMap<String, Vec<String>>,
+	) -> Result<()> {
+		let paths = collect_substitute_paths(graph, wanted);
+		if paths.is_empty() {
+			return Ok(());
+		}
+		let _ = self
+			.events
+			.send(BuildEvent::SubstitutePrepassStarted { paths: paths.len() });
+		debug!("substitute pre-pass: {} paths", paths.len());
+
+		let satisfied = tokio::task::spawn_blocking(move || crate::substitute_paths(&paths))
+			.await
+			.expect("substitute pre-pass task should not panic")?;
+
+		let _ = self.events.send(BuildEvent::SubstitutePrepassFinished {
+			satisfied: satisfied.len(),
+		});
+		Ok(())
+	}
+
+	async fn build_topo(
+		&self,
+		graph: &Arc<DrvGraph>,
+		wanted: HashMap<String, Vec<String>>,
+	) -> Result<()> {
+		let mut indeg: HashMap<String, usize> = graph
+			.nodes
+			.iter()
+			.map(|(k, n)| (k.clone(), n.input_drvs.len()))
+			.collect();
+		let mut dependents: HashMap<String, Vec<String>> = HashMap::new();
+		for (path, node) in &graph.nodes {
+			for dep in node.input_drvs.keys() {
+				dependents
+					.entry(dep.clone())
+					.or_default()
+					.push(path.clone());
+			}
+		}
+
+		let sem = Arc::new(Semaphore::new(self.parallelism));
+		let mut ready: Vec<String> = indeg
+			.iter()
+			.filter(|(_, d)| **d == 0)
+			.map(|(k, _)| k.clone())
+			.collect();
+		let mut in_flight = FuturesUnordered::new();
+		let mut failed: HashMap<String, String> = HashMap::new();
+		// Tainted = transitively depends on a failed drv
+		let mut tainted: HashMap<String, String> = HashMap::new();
+
+		loop {
+			let batch: Vec<String> = std::mem::take(&mut ready);
+			for path in batch {
+				if let Some(failed_dep) = tainted.get(&path) {
+					let name = graph
+						.nodes
+						.get(&path)
+						.map(|n| n.name.clone())
+						.unwrap_or_default();
+					let _ = self.events.send(BuildEvent::DrvCancelled {
+						drv_path: path.clone(),
+						name,
+						failed_dep: failed_dep.clone(),
+					});
+					propagate_done(&dependents, &mut indeg, &mut ready, &path);
+					continue;
+				}
+
+				let sem = sem.clone();
+				let events = self.events.clone();
+				let graph = graph.clone();
+				let wanted_here = wanted.get(&path).cloned().unwrap_or_default();
+				in_flight.push(tokio::spawn(async move {
+					let _permit = sem.acquire_owned().await.expect("semaphore not closed");
+					let node = graph
+						.nodes
+						.get(&path)
+						.expect("ready node must be in graph")
+						.clone();
+					let name = node.name.clone();
+
+					let all_valid = !wanted_here.is_empty()
+						&& wanted_here.iter().all(|o| {
+							node.outputs
+								.get(o)
+								.map(|p| crate::is_valid_path(p).unwrap_or(false))
+								.unwrap_or(false)
+						});
+					if all_valid {
+						let _ = events.send(BuildEvent::DrvSkipped {
+							drv_path: path.clone(),
+							name: name.clone(),
+						});
+						return (path, name, Ok::<(), anyhow::Error>(()));
+					}
+
+					let _ = events.send(BuildEvent::DrvStarted {
+						drv_path: path.clone(),
+						name: name.clone(),
+						wanted: wanted_here.clone(),
+					});
+
+					let path_for_build = path.clone();
+					let res = tokio::task::spawn_blocking(move || {
+						crate::build_drv_outputs(&path_for_build, &wanted_here)
+					})
+					.await
+					.expect("build task should not panic");
+
+					match res {
+						Ok(_) => {
+							let _ = events.send(BuildEvent::DrvFinished {
+								drv_path: path.clone(),
+								name: name.clone(),
+							});
+							(path, name, Ok(()))
+						}
+						Err(e) => {
+							let msg = format!("{e:#}");
+							let _ = events.send(BuildEvent::DrvFailed {
+								drv_path: path.clone(),
+								name: name.clone(),
+								error: msg,
+							});
+							(path, name, Err(e))
+						}
+					}
+				}));
+			}
+
+			let Some(joined) = in_flight.next().await else {
+				break;
+			};
+			let (finished, _name, res) = match joined {
+				Ok(t) => t,
+				Err(e) => bail!("scheduler task panicked: {e}"),
+			};
+			match res {
+				Ok(()) => {
+					propagate_done(&dependents, &mut indeg, &mut ready, &finished);
+				}
+				Err(e) => {
+					failed.insert(finished.clone(), format!("{e:#}"));
+					mark_tainted(&dependents, &finished, &mut tainted);
+					propagate_done(&dependents, &mut indeg, &mut ready, &finished);
+				}
+			}
+		}
+
+		let stuck: Vec<_> = indeg
+			.iter()
+			.filter(|(_, d)| **d != 0)
+			.map(|(k, _)| k.as_str())
+			.collect();
+		if !stuck.is_empty() {
+			warn!(
+				"scheduler finished with {} nodes still pending (loop?)",
+				stuck.len()
+			);
+		}
+
+		if failed.is_empty() {
+			info!("scheduler completed");
+			Ok(())
+		} else {
+			let mut report = format!("{} drv(s) failed to build:", failed.len());
+			let mut sorted: Vec<_> = failed.iter().collect();
+			sorted.sort_by(|a, b| a.0.cmp(b.0));
+			for (path, err) in sorted {
+				let name = graph
+					.nodes
+					.get(path)
+					.map(|n| n.name.as_str())
+					.unwrap_or("?");
+				let chain = path_to_root(graph, path);
+				report.push_str(&format!(
+					"\n\n  {name} ({path}):\n    {err}\n    needed by: {}",
+					chain.join(" => "),
+				));
+			}
+			Err(anyhow::anyhow!(report))
+		}
+	}
+}
+
+fn propagate_done(
+	dependents: &HashMap<String, Vec<String>>,
+	indeg: &mut HashMap<String, usize>,
+	ready: &mut Vec<String>,
+	finished: &str,
+) {
+	if let Some(deps) = dependents.get(finished) {
+		for d in deps {
+			let entry = indeg.get_mut(d).expect("dependent must have indeg");
+			*entry = entry.saturating_sub(1);
+			if *entry == 0 {
+				ready.push(d.clone());
+			}
+		}
+	}
+}
+
+fn mark_tainted(
+	dependents: &HashMap<String, Vec<String>>,
+	failed: &str,
+	tainted: &mut HashMap<String, String>,
+) {
+	let mut queue: Vec<String> = dependents.get(failed).cloned().unwrap_or_default();
+	while let Some(node) = queue.pop() {
+		if tainted
+			.entry(node.clone())
+			.or_insert_with(|| failed.to_owned())
+			== failed
+		{
+			if let Some(deps) = dependents.get(&node) {
+				for d in deps {
+					if !tainted.contains_key(d) {
+						queue.push(d.clone());
+					}
+				}
+			}
+		}
+	}
+}
+
+fn path_to_root(graph: &DrvGraph, from: &str) -> Vec<String> {
+	let mut dependents: HashMap<&str, Vec<&str>> = HashMap::new();
+	for (path, node) in &graph.nodes {
+		for dep in node.input_drvs.keys() {
+			dependents
+				.entry(dep.as_str())
+				.or_default()
+				.push(path.as_str());
+		}
+	}
+
+	let mut chain: Vec<String> = vec![node_name(graph, from)];
+	let mut cur = from;
+	let mut seen: HashSet<&str> = HashSet::new();
+	seen.insert(cur);
+	while cur != graph.root.as_str() {
+		let Some(next) = dependents.get(cur).and_then(|v| v.first().copied()) else {
+			break;
+		};
+		if !seen.insert(next) {
+			break;
+		}
+		chain.push(node_name(graph, next));
+		cur = next;
+	}
+	chain
+}
+
+fn node_name(graph: &DrvGraph, path: &str) -> String {
+	graph
+		.nodes
+		.get(path)
+		.map(|n| n.name.clone())
+		.unwrap_or_else(|| path.to_owned())
+}
+
+fn collect_substitute_paths(
+	graph: &DrvGraph,
+	wanted: &HashMap<String, Vec<String>>,
+) -> Vec<String> {
+	let mut paths: HashSet<String> = HashSet::new();
+	for node in graph.nodes.values() {
+		for src in &node.input_srcs {
+			paths.insert(src.clone());
+		}
+	}
+	for (path, outs) in wanted {
+		let Some(node) = graph.nodes.get(path) else {
+			continue;
+		};
+		for o in outs {
+			if let Some(p) = node.outputs.get(o) {
+				paths.insert(p.clone());
+			}
+		}
+	}
+	let mut v: Vec<_> = paths.into_iter().collect();
+	v.sort();
+	v
+}
+
+// TODO: Parallelism as a metric works poorly with multiple machines, but I haven't thought about bringing
+// hercy here yet. In case of remote machines - they will handle parallelism on their own, and this one
+// will work as a hard cap.
+pub fn build_graph_sync(graph: Arc<DrvGraph>, root_outputs: Vec<String>) -> Result<()> {
+	let parallelism = std::thread::available_parallelism()
+		.map(|p| p.get())
+		.unwrap_or(4);
+	let scheduler = Scheduler::new(parallelism);
+	crate::await_in_nix(async move { scheduler.run(graph, root_outputs).await })
+		.context("scheduler run")
+}
modifiedcrates/nixlike/Cargo.tomldiffbeforeafterboth
--- a/crates/nixlike/Cargo.toml
+++ b/crates/nixlike/Cargo.toml
@@ -11,5 +11,3 @@
 linked-hash-map.workspace = true
 peg.workspace = true
 serde.workspace = true
-serde-transcode.workspace = true
-serde_json.workspace = true
addedcrates/remowt-fleet/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/remowt-fleet/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "remowt-fleet"
+description = "Remowt endpoints for fleet"
+version.workspace = true
+edition = "2021"
+
+[dependencies]
+bifrostlink.workspace = true
+
+remowt-client.workspace = true
+remowt-endpoints.workspace = true
+
+anyhow.workspace = true
+camino.workspace = true
+nix-eval.workspace = true
+serde = { workspace = true }
+thiserror.workspace = true
+tokio = { workspace = true, features = ["io-util", "net", "process", "rt"] }
+tracing.workspace = true
+uuid.workspace = true
addedcrates/remowt-fleet/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/remowt-fleet/src/lib.rs
@@ -0,0 +1,120 @@
+use std::path::PathBuf;
+
+use anyhow::{Context as _, Result};
+use bifrostlink::Config;
+use bifrostlink::declarative::endpoints;
+use camino::Utf8PathBuf;
+use remowt_client::Remowt;
+use remowt_endpoints::nix_daemon::NixDaemonClient;
+use serde::{Deserialize, Serialize};
+use tokio::net::UnixListener;
+use tracing::error;
+
+pub struct Nix;
+pub use nix_eval::{init_libraries, init_tokio_for_nix};
+
+#[derive(Serialize, Deserialize, Debug, thiserror::Error)]
+pub enum NixError {
+	#[error("nix daemon unavailable: {0}")]
+	DaemonUnavailable(String),
+	#[error("tunnel socket unavailable: {0}")]
+	Tunnel(String),
+	#[error("profile switch failed: {0}")]
+	Profile(String),
+	#[error("signing failed: {0}")]
+	Sign(String),
+	#[error("listing generations failed: {0}")]
+	ListGenerations(String),
+}
+
+#[endpoints(ns = 91)]
+impl Nix {
+	#[endpoints(id = 3)]
+	async fn switch_profile(
+		&self,
+		profile: String,
+		store_path: Utf8PathBuf,
+	) -> Result<(), NixError> {
+		tokio::task::spawn_blocking(move || nix_eval::switch_profile(&profile, &store_path))
+			.await
+			.map_err(|e| NixError::Profile(e.to_string()))?
+			.map_err(|e| NixError::Profile(e.to_string()))
+	}
+
+	#[endpoints(id = 4)]
+	async fn sign_closure(
+		&self,
+		store_path: Utf8PathBuf,
+		key_file: Utf8PathBuf,
+	) -> Result<(), NixError> {
+		tokio::task::spawn_blocking(move || {
+			nix_eval::sign_closure(store_path.as_str(), key_file.as_str())
+		})
+		.await
+		.map_err(|e| NixError::Sign(e.to_string()))?
+		.map_err(|e| NixError::Sign(e.to_string()))
+	}
+
+	#[endpoints(id = 5)]
+	async fn list_generations(
+		&self,
+		profile: String,
+	) -> Result<Vec<nix_eval::ProfileGeneration>, NixError> {
+		tokio::task::spawn_blocking(move || {
+			nix_eval::list_generations(&format!("/nix/var/nix/profiles/{profile}"))
+		})
+		.await
+		.map_err(|e| NixError::ListGenerations(e.to_string()))?
+		.map_err(|e| NixError::ListGenerations(e.to_string()))
+	}
+}
+
+pub async fn nix_store_socket(conn: Remowt, store: &str) -> Result<PathBuf> {
+	let store = store.to_owned();
+	let path = std::env::temp_dir().join(format!("fleet-nix-{}.sock", uuid::Uuid::new_v4()));
+	let _ = std::fs::remove_file(&path);
+	let listener = UnixListener::bind(&path)?;
+	tokio::spawn(async move {
+		if let Err(e) = serve(conn, listener, store).await {
+			error!("nix daemon proxy failed: {e}");
+		}
+	});
+	Ok(path)
+}
+
+async fn serve(conn: Remowt, listener: UnixListener, store: String) -> Result<()> {
+	let nix = conn.endpoints::<NixDaemonClient<_>>();
+	loop {
+		let (mut local, _) = listener.accept().await?;
+
+		let (rx, remote_sock) = match conn.bind_runtime_unix("nix-daemon").await {
+			Ok(rx) => rx,
+			Err(e) => {
+				error!("streamlocal_forward failed: {e}");
+				continue;
+			}
+		};
+		let sock_str = remote_sock.as_str().to_owned();
+		match nix.serve_store(store.clone(), sock_str).await {
+			Ok(Ok(())) => {}
+			Ok(Err(e)) => {
+				error!("nix bridge: {e}");
+				continue;
+			}
+			Err(e) => {
+				error!("nix bridge rpc failed: {e}");
+				continue;
+			}
+		}
+
+		let mut channel = rx
+			.accept()
+			.await
+			.context("failed to accept remote nix connection")?;
+		tokio::spawn(async move {
+			if let Err(e) = tokio::io::copy_bidirectional(&mut local, &mut channel).await {
+				tracing::debug!("nix tunnel ended: {e}");
+			}
+		});
+	}
+}
modifiedflake.lockdiffbeforeafterboth
--- a/flake.lock
+++ b/flake.lock
@@ -2,10 +2,10 @@
   "nodes": {
     "crane": {
       "locked": {
-        "lastModified": 1773189535,
+        "lastModified": 1780532242,
         "owner": "ipetkov",
         "repo": "crane",
-        "rev": "6fa2fb4cf4a89ba49fc9dd5a3eb6cde99d388269",
+        "rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37",
         "type": "github"
       },
       "original": {
@@ -37,10 +37,10 @@
         ]
       },
       "locked": {
-        "lastModified": 1772408722,
+        "lastModified": 1778716662,
         "owner": "hercules-ci",
         "repo": "flake-parts",
-        "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
+        "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
         "type": "github"
       },
       "original": {
@@ -62,10 +62,10 @@
         ]
       },
       "locked": {
-        "lastModified": 1759080490,
+        "lastModified": 1780160585,
         "owner": "CertainLach",
         "repo": "fleet-tf",
-        "rev": "878bd8c23933d628bf750378bbe527b841901c3d",
+        "rev": "aa2200a3f8f93a8c0d3b87bb29f11d9d9b0a23e6",
         "type": "github"
       },
       "original": {
@@ -111,10 +111,10 @@
         "nixpkgs-regression": "nixpkgs-regression"
       },
       "locked": {
-        "lastModified": 1768702010,
+        "lastModified": 1781448983,
         "owner": "deltarocks",
         "repo": "nix",
-        "rev": "b05b52670b9c7affff5b9be3edb539a1603c39e6",
+        "rev": "bb8784f285658e77642437409824619f5c34a854",
         "type": "github"
       },
       "original": {
@@ -126,10 +126,10 @@
     },
     "nixpkgs": {
       "locked": {
-        "lastModified": 1773268153,
+        "lastModified": 1781402740,
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "63ca4f465b79edbaa9ff113ea7ff48fb49ea0db0",
+        "rev": "49145c5aba740948d6da1265f584826bd27619ad",
         "type": "github"
       },
       "original": {
@@ -171,6 +171,34 @@
         "type": "github"
       }
     },
+    "remowt-agents": {
+      "inputs": {
+        "crane": [
+          "crane"
+        ],
+        "flake-parts": [
+          "flake-parts"
+        ],
+        "nixpkgs": [
+          "nixpkgs"
+        ],
+        "rust-overlay": "rust-overlay",
+        "shelly": [
+          "shelly"
+        ]
+      },
+      "locked": {
+        "lastModified": 1781471358,
+        "rev": "c69dddc33f8e2081ef685ae20a7278f92cfcd32e",
+        "revCount": 37,
+        "type": "git",
+        "url": "file:/home/lach/build/remowt-agents"
+      },
+      "original": {
+        "type": "git",
+        "url": "file:/home/lach/build/remowt-agents"
+      }
+    },
     "root": {
       "inputs": {
         "crane": "crane",
@@ -178,7 +206,8 @@
         "fleet-tf": "fleet-tf",
         "nix": "nix",
         "nixpkgs": "nixpkgs",
-        "rust-overlay": "rust-overlay",
+        "remowt-agents": "remowt-agents",
+        "rust-overlay": "rust-overlay_2",
         "shelly": "shelly",
         "treefmt-nix": "treefmt-nix"
       }
@@ -186,14 +215,15 @@
     "rust-overlay": {
       "inputs": {
         "nixpkgs": [
+          "remowt-agents",
           "nixpkgs"
         ]
       },
       "locked": {
-        "lastModified": 1773216618,
+        "lastModified": 1769309768,
         "owner": "oxalica",
         "repo": "rust-overlay",
-        "rev": "07d7dc6fcc5eae76b4fb0e19d4afd939437bec97",
+        "rev": "140c9dc582cb73ada2d63a2180524fcaa744fad5",
         "type": "github"
       },
       "original": {
@@ -202,6 +232,25 @@
         "type": "github"
       }
     },
+    "rust-overlay_2": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1781320681,
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "rev": "5b929d8c854149d926d05ea0cd6469bf4e54db27",
+        "type": "github"
+      },
+      "original": {
+        "owner": "oxalica",
+        "repo": "rust-overlay",
+        "type": "github"
+      }
+    },
     "shelly": {
       "locked": {
         "lastModified": 1756323923,
@@ -223,10 +272,10 @@
         ]
       },
       "locked": {
-        "lastModified": 1772660329,
+        "lastModified": 1780220602,
         "owner": "numtide",
         "repo": "treefmt-nix",
-        "rev": "3710e0e1218041bbad640352a0440114b1e10428",
+        "rev": "db947814a175b7ca6ded66e21383d938df01c227",
         "type": "github"
       },
       "original": {
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -23,6 +23,13 @@
       url = "github:numtide/treefmt-nix";
       inputs.nixpkgs.follows = "nixpkgs";
     };
+    remowt-agents = {
+      url = "git+file:/home/lach/build/remowt-agents";
+      inputs.nixpkgs.follows = "nixpkgs";
+      inputs.flake-parts.follows = "flake-parts";
+      inputs.crane.follows = "crane";
+      inputs.shelly.follows = "shelly";
+    };
     # DeterminateSystem's nix fork is controversial, but I don't mind it,
     # and it has lazy-trees support which is useful for fleet.
     nix = {
@@ -147,7 +154,7 @@
             packages = lib.mkIf deployerSystem (
               let
                 packages = pkgs.callPackages ./pkgs {
-                  inherit craneLib inputs';
+                  inherit craneLib inputs;
                 };
               in
               packages // { default = packages.fleet; }
modifiedlib/flakePart.nixdiffbeforeafterboth
--- a/lib/flakePart.nix
+++ b/lib/flakePart.nix
@@ -57,7 +57,7 @@
             modules = (import ../modules/module-list.nix) ++ [
               module
               (
-                { inputs', ... }:
+                { inputs, ... }:
                 {
                   config = {
                     data = if isPath data then import data else data;
@@ -66,12 +66,13 @@
                       (final: prev: {
                         inherit
                           (import ../pkgs {
-                            inherit (prev) callPackage;
-                            inherit inputs';
+                            inherit (final) callPackage;
+                            inherit inputs;
                             craneLib = crane.mkLib prev;
                           })
                           fleet-install-secrets
                           fleet-generator-helper
+                          remowt-plugin-fleet
                           ;
                       })
                     ];
modifiedmodules/nixpkgs.nixdiffbeforeafterboth
--- a/modules/nixpkgs.nix
+++ b/modules/nixpkgs.nix
@@ -42,10 +42,12 @@
         default = config.nixpkgs.buildUsing;
         defaultText = literalExpression "config.nixpkgs.buildUsing";
       };
-      config.nixos = {
-        inherit _file;
-        nixpkgs.overlays = config.nixpkgs.overlays;
-      };
+      config.nixos =
+        { inputs', ... }:
+        {
+          inherit _file;
+          nixpkgs.overlays = config.nixpkgs.overlays;
+        };
     };
   };
 }
modifiedpkgs/default.nixdiffbeforeafterboth
--- a/pkgs/default.nix
+++ b/pkgs/default.nix
@@ -1,10 +1,11 @@
 {
   callPackage,
   craneLib,
-  inputs',
+  inputs,
 }:
 {
-  fleet = callPackage ./fleet.nix { inherit craneLib inputs'; };
+  fleet = callPackage ./fleet.nix { inherit craneLib inputs; };
+  remowt-plugin-fleet = callPackage ./remowt-plugin-fleet.nix { inherit craneLib inputs; };
   fleet-install-secrets = callPackage ./fleet-install-secrets.nix { inherit craneLib; };
   fleet-generator-helper = callPackage ./fleet-generator-helper.nix { inherit craneLib; };
 }
modifiedpkgs/fleet.nixdiffbeforeafterboth
--- a/pkgs/fleet.nix
+++ b/pkgs/fleet.nix
@@ -2,10 +2,16 @@
   lib,
   craneLib,
   installShellFiles,
-  inputs',
+  inputs,
+
+  stdenv,
   pkg-config,
   rustPlatform,
+  rofi,
 }:
+let
+  system = stdenv.hostPlatform.system;
+in
 craneLib.buildPackage rec {
   pname = "fleet";
   src = lib.cleanSourceWith {
@@ -20,10 +26,15 @@
 
   cargoExtraArgs = "--locked -p ${pname}";
 
+  REMOWT_AGENTS_DIR = "${inputs.remowt-agents.packages.${system}.remowt-agents}";
+  # TODO: built-in fleet prompter should be a prodash widget, or it should require
+  # tty remowt prompter running on host machine idk.
+  ROFI = "${rofi}/bin/rofi";
+
   buildInputs = [
-    inputs'.nix.packages.nix-expr-c
-    inputs'.nix.packages.nix-flake-c
-    inputs'.nix.packages.nix-fetchers-c
+    inputs.nix.packages.${system}.nix-expr-c
+    inputs.nix.packages.${system}.nix-flake-c
+    inputs.nix.packages.${system}.nix-fetchers-c
   ];
   nativeBuildInputs = [
     installShellFiles
addedpkgs/remowt-plugin-fleet.nixdiffbeforeafterboth
--- /dev/null
+++ b/pkgs/remowt-plugin-fleet.nix
@@ -0,0 +1,36 @@
+{
+  lib,
+  craneLib,
+  inputs,
+
+  stdenv,
+  pkg-config,
+  rustPlatform,
+}:
+let
+  system = stdenv.hostPlatform.system;
+in
+craneLib.buildPackage rec {
+  pname = "remowt-plugin-fleet";
+  src = lib.cleanSourceWith {
+    src = ../.;
+    filter =
+      path: type:
+      (lib.hasSuffix "\.cc" path)
+      || (lib.hasSuffix "\.hh" path)
+      || (craneLib.filterCargoSources path type);
+  };
+  strictDeps = true;
+
+  cargoExtraArgs = "--locked -p ${pname}";
+
+  buildInputs = [
+    inputs.nix.packages.${system}.nix-expr-c
+    inputs.nix.packages.${system}.nix-flake-c
+    inputs.nix.packages.${system}.nix-fetchers-c
+  ];
+  nativeBuildInputs = [
+    pkg-config
+    rustPlatform.bindgenHook
+  ];
+}