mirror of
https://github.com/NotAShelf/stash.git
synced 2026-06-23 21:23:27 +00:00
Compare commits
13 commits
notashelf/
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
6413449c45 |
|||
|
8c339240c7 |
|||
|
65be1f8e08 |
|||
|
|
2f5cf1dd2e | ||
|
4479cdb1bc |
|||
|
|
a50298b118 |
||
|
9120e57926 |
|||
|
3f2e34b8ea |
|||
|
fef407ec86 |
|||
|
f5789aa43d |
|||
|
ad70e65125 |
|||
|
384ac708eb |
|||
|
656709bd19 |
10 changed files with 356 additions and 137 deletions
205
Cargo.lock
generated
205
Cargo.lock
generated
|
|
@ -141,6 +141,15 @@ version = "1.0.102"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "approx"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
|
|
@ -389,9 +398,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.11.0"
|
version = "2.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
|
|
@ -430,6 +439,12 @@ version = "3.20.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "by_address"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.25.0"
|
version = "1.25.0"
|
||||||
|
|
@ -504,9 +519,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.6.0"
|
version = "4.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
|
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
|
@ -536,9 +551,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.6.0"
|
version = "4.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
|
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -635,6 +650,12 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "critical-section"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.21"
|
version = "0.8.21"
|
||||||
|
|
@ -647,7 +668,7 @@ version = "0.29.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"document-features",
|
"document-features",
|
||||||
|
|
@ -834,7 +855,7 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2",
|
||||||
|
|
@ -1006,6 +1027,12 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fast-srgb8"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
|
|
@ -1311,14 +1338,19 @@ name = "hashbrown"
|
||||||
version = "0.17.0"
|
version = "0.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
version = "0.11.0"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230"
|
checksum = "a5081f264ed7adee96ea4b4778b6bb9da0a7228b084587aa3bd3ff05da7c5a3b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1592,7 +1624,7 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6654738b8024300cf062d04a1c13c10c8e2cea598ec1c47dc9b6641159429756"
|
checksum = "6654738b8024300cf062d04a1c13c10c8e2cea598ec1c47dc9b6641159429756"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
|
@ -1727,6 +1759,12 @@ version = "0.2.186"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
|
|
@ -1738,9 +1776,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.37.0"
|
version = "0.38.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
|
checksum = "f6c19a05435c21ac299d71b6a9c13db3e3f47c520517d58990a462a1397a61db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
|
|
@ -1753,7 +1791,7 @@ version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8"
|
checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1785,29 +1823,31 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.29"
|
version = "0.4.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
version = "0.16.3"
|
version = "0.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
|
checksum = "8a860605968fce16869fd239cf4237a82f3ac470723415db603b0e8b6c8d4fb9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mac-notification-sys"
|
name = "mac-notification-sys"
|
||||||
version = "0.6.12"
|
version = "0.6.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29a16783dd1a47849b8c8133c9cd3eb2112cfbc6901670af3dba47c8bbfb07d3"
|
checksum = "fd604973958ddcc11b561193c0fb96ba146506ef2f231ef2e7c35fd2cbc9beca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
|
"log",
|
||||||
"objc2",
|
"objc2",
|
||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"time",
|
"time",
|
||||||
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1890,7 +1930,7 @@ version = "0.29.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -1903,7 +1943,7 @@ version = "0.31.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
|
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -1930,9 +1970,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notify-rust"
|
name = "notify-rust"
|
||||||
version = "4.17.0"
|
version = "4.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "50ff2e74231b72c832d82982193b417f230945be6bdb5575b251d941d31adb00"
|
checksum = "c5b4c1b4f2aa9f25f63a7a49d3dd0ed567b3670da15330a66b29434be899b891"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -1992,7 +2032,7 @@ version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"dispatch2",
|
"dispatch2",
|
||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
@ -2009,7 +2049,7 @@ version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"block2",
|
"block2",
|
||||||
"libc",
|
"libc",
|
||||||
"objc2",
|
"objc2",
|
||||||
|
|
@ -2084,6 +2124,30 @@ version = "4.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "palette"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6"
|
||||||
|
dependencies = [
|
||||||
|
"approx",
|
||||||
|
"fast-srgb8",
|
||||||
|
"libm",
|
||||||
|
"palette_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "palette_derive"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30"
|
||||||
|
dependencies = [
|
||||||
|
"by_address",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
|
|
@ -2463,9 +2527,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui"
|
name = "ratatui"
|
||||||
version = "0.30.0"
|
version = "0.30.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc"
|
checksum = "1695748e3a735b34968c887ceea5a380b43545903868ae8f5b666593100f6b68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"instability",
|
"instability",
|
||||||
"ratatui-core",
|
"ratatui-core",
|
||||||
|
|
@ -2473,21 +2537,25 @@ dependencies = [
|
||||||
"ratatui-macros",
|
"ratatui-macros",
|
||||||
"ratatui-termwiz",
|
"ratatui-termwiz",
|
||||||
"ratatui-widgets",
|
"ratatui-widgets",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui-core"
|
name = "ratatui-core"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293"
|
checksum = "42d3603f354bba8c595fa47860e60142d7372b7210c27044c6a7d0e1a4336b44"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"hashbrown 0.16.1",
|
"critical-section",
|
||||||
|
"hashbrown 0.17.0",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools",
|
"itertools",
|
||||||
"kasuari",
|
"kasuari",
|
||||||
"lru",
|
"lru",
|
||||||
|
"palette",
|
||||||
|
"serde",
|
||||||
"strum",
|
"strum",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
|
@ -2497,9 +2565,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui-crossterm"
|
name = "ratatui-crossterm"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3"
|
checksum = "2b2867bedcbd6a690ca4f8672a687b730ec07660c79844517b084311b529980c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
|
|
@ -2509,9 +2577,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui-macros"
|
name = "ratatui-macros"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4"
|
checksum = "80fac59720679490d89d200df411faa249be728681adcabed3d047ae72c48f1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ratatui-core",
|
"ratatui-core",
|
||||||
"ratatui-widgets",
|
"ratatui-widgets",
|
||||||
|
|
@ -2519,9 +2587,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui-termwiz"
|
name = "ratatui-termwiz"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c"
|
checksum = "386b8ff8f74ed749509391c56d549761a2fcdb408e1f42e467286bcb7dac8967"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ratatui-core",
|
"ratatui-core",
|
||||||
"termwiz",
|
"termwiz",
|
||||||
|
|
@ -2529,17 +2597,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ratatui-widgets"
|
name = "ratatui-widgets"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db"
|
checksum = "7ef4f17dd7ac3abf5adc2b920a03c61eee4bfe6a88fa5191936895525371d79c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.17.0",
|
||||||
"indoc",
|
"indoc",
|
||||||
"instability",
|
"instability",
|
||||||
"itertools",
|
"itertools",
|
||||||
"line-clipping",
|
"line-clipping",
|
||||||
"ratatui-core",
|
"ratatui-core",
|
||||||
|
"serde",
|
||||||
"strum",
|
"strum",
|
||||||
"time",
|
"time",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
|
@ -2552,7 +2621,7 @@ version = "0.5.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2568,9 +2637,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.3"
|
version = "1.12.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -2591,9 +2660,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.8.10"
|
version = "0.8.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsqlite-vfs"
|
name = "rsqlite-vfs"
|
||||||
|
|
@ -2607,11 +2676,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.39.0"
|
version = "0.40.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e"
|
checksum = "11438310b19e3109b6446c33d1ed5e889428cf2e278407bc7896bc4aaea43323"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"fallible-streaming-iterator",
|
"fallible-streaming-iterator",
|
||||||
"hashlink",
|
"hashlink",
|
||||||
|
|
@ -2687,7 +2756,7 @@ version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
|
|
@ -2803,9 +2872,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.149"
|
version = "1.0.150"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -2937,7 +3006,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stash-clipboard"
|
name = "stash-clipboard"
|
||||||
version = "0.3.6"
|
version = "0.4.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"age",
|
"age",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
|
|
@ -2987,18 +3056,18 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.27.2"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum_macros"
|
name = "strum_macros"
|
||||||
version = "0.27.2"
|
version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
|
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -3099,7 +3168,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"filedescriptor",
|
"filedescriptor",
|
||||||
"finl_unicode",
|
"finl_unicode",
|
||||||
|
|
@ -3376,9 +3445,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.13.2"
|
version = "1.13.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
|
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-truncate"
|
name = "unicode-truncate"
|
||||||
|
|
@ -3584,7 +3653,7 @@ version = "0.244.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"hashbrown 0.15.5",
|
"hashbrown 0.15.5",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"semver",
|
"semver",
|
||||||
|
|
@ -3609,7 +3678,7 @@ version = "0.31.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"log",
|
"log",
|
||||||
"rustix",
|
"rustix",
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
|
|
@ -3622,7 +3691,7 @@ version = "0.32.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
|
checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-scanner",
|
"wayland-scanner",
|
||||||
|
|
@ -3634,7 +3703,7 @@ version = "0.3.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
|
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"wayland-backend",
|
"wayland-backend",
|
||||||
"wayland-client",
|
"wayland-client",
|
||||||
"wayland-protocols",
|
"wayland-protocols",
|
||||||
|
|
@ -3975,7 +4044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.13.0",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
||||||
20
Cargo.toml
20
Cargo.toml
|
|
@ -1,13 +1,13 @@
|
||||||
[package]
|
[package]
|
||||||
name = "stash-clipboard"
|
name = "stash-clipboard"
|
||||||
description = "Wayland clipboard manager with fast persistent history and multi-media support"
|
description = "Wayland clipboard manager with fast persistent history and multi-media support"
|
||||||
version = "0.3.6"
|
version = "0.4.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = [ "NotAShelf <raf@notashelf.dev>" ]
|
authors = [ "NotAShelf <raf@notashelf.dev>" ]
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
readme = true
|
readme = true
|
||||||
repository = "https://github.com/notashelf/stash"
|
repository = "https://github.com/notashelf/stash"
|
||||||
rust-version = "1.91.0"
|
rust-version = "1.95.0"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "stash" # actual binary name for Nix, Cargo, etc.
|
name = "stash" # actual binary name for Nix, Cargo, etc.
|
||||||
|
|
@ -18,7 +18,7 @@ age = { version = "0.11.3", optional = true }
|
||||||
arc-swap = { version = "1.9.1", optional = true }
|
arc-swap = { version = "1.9.1", optional = true }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
blocking = "1.6.2"
|
blocking = "1.6.2"
|
||||||
clap = { version = "4.6.0", features = [ "derive", "env" ] }
|
clap = { version = "4.6.1", features = [ "derive", "env" ] }
|
||||||
clap-verbosity-flag = "3.0.4"
|
clap-verbosity-flag = "3.0.4"
|
||||||
color-eyre = "0.6.5"
|
color-eyre = "0.6.5"
|
||||||
crossterm = "0.29.0"
|
crossterm = "0.29.0"
|
||||||
|
|
@ -29,17 +29,17 @@ humantime = "2.3.0"
|
||||||
imagesize = "0.14.0"
|
imagesize = "0.14.0"
|
||||||
inquire = { version = "0.9.4", default-features = false, features = [ "crossterm" ] }
|
inquire = { version = "0.9.4", default-features = false, features = [ "crossterm" ] }
|
||||||
libc = "0.2.186"
|
libc = "0.2.186"
|
||||||
log = "0.4.29"
|
log = "0.4.32"
|
||||||
mime-sniffer = "0.1.3"
|
mime-sniffer = "0.1.3"
|
||||||
notify-rust = { version = "4.17.0", optional = true }
|
notify-rust = { version = "4.18.0", optional = true }
|
||||||
ratatui = "0.30.0"
|
ratatui = "0.30.1"
|
||||||
regex = "1.12.3"
|
regex = "1.12.4"
|
||||||
rusqlite = { version = "0.39.0", features = [ "bundled" ] }
|
rusqlite = { version = "0.40.1", features = [ "bundled" ] }
|
||||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.150"
|
||||||
smol = "2.0.2"
|
smol = "2.0.2"
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
unicode-segmentation = "1.13.2"
|
unicode-segmentation = "1.13.3"
|
||||||
unicode-width = "0.2.2"
|
unicode-width = "0.2.2"
|
||||||
wayland-client = { version = "0.31.14", features = [ "log" ], optional = true }
|
wayland-client = { version = "0.31.14", features = [ "log" ], optional = true }
|
||||||
wayland-protocols-wlr = { version = "0.3.12", default-features = false, optional = true }
|
wayland-protocols-wlr = { version = "0.3.12", default-features = false, optional = true }
|
||||||
|
|
|
||||||
120
README.md
120
README.md
|
|
@ -20,9 +20,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
Lightweight & feature-rich Wayland clipboard "manager" with fast persistent history and
|
Lightweight & feature-rich Wayland clipboard "manager" with fast persistent
|
||||||
robust multi-media support. Stores and previews clipboard entries (text, images)
|
history and robust multi-media support. Stores and previews clipboard
|
||||||
on the clipboard with a neat TUI and advanced scripting capabilities.
|
entries (text, images) on the clipboard with a neat TUI and advanced
|
||||||
|
scripting capabilities.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
@ -52,6 +53,8 @@ with many features such as but not necessarily limited to:
|
||||||
- Drop-in replacement for `wl-clipboard` tools (`wl-copy` and `wl-paste`)
|
- Drop-in replacement for `wl-clipboard` tools (`wl-copy` and `wl-paste`)
|
||||||
- Sensitive clipboard filtering via regex (see below)
|
- Sensitive clipboard filtering via regex (see below)
|
||||||
- Sensitive clipboard filtering by application (see below)
|
- Sensitive clipboard filtering by application (see below)
|
||||||
|
- Password manager hint filtering (`x-kde-passwordManagerHint`)
|
||||||
|
- Optional at-rest encryption for database entries using age
|
||||||
|
|
||||||
on top of the existing features of Cliphist, which are as follows:
|
on top of the existing features of Cliphist, which are as follows:
|
||||||
|
|
||||||
|
|
@ -270,8 +273,8 @@ stash db stats
|
||||||
- `stash db vacuum`: Optimize the database using SQLite's VACUUM command,
|
- `stash db vacuum`: Optimize the database using SQLite's VACUUM command,
|
||||||
reclaiming space and improving performance.
|
reclaiming space and improving performance.
|
||||||
- `stash db stats`: Display database statistics including total/active/expired
|
- `stash db stats`: Display database statistics including total/active/expired
|
||||||
entry counts, storage size, and page information. This is provided purely for
|
entry counts, encrypted/undecryptable entry counts, storage size, and page
|
||||||
convenience and the rule of the cool.
|
information.
|
||||||
|
|
||||||
### Watch clipboard for changes and store automatically
|
### Watch clipboard for changes and store automatically
|
||||||
|
|
||||||
|
|
@ -357,21 +360,36 @@ sensitive pattern, using a regular expression. This is useful for preventing
|
||||||
accidental storage of secrets, passwords, or other sensitive data. You don't
|
accidental storage of secrets, passwords, or other sensitive data. You don't
|
||||||
want sensitive data ending up in your persistent clipboard, right?
|
want sensitive data ending up in your persistent clipboard, right?
|
||||||
|
|
||||||
The filter can be configured in one of three ways, as part of two separate
|
The filter can be configured in several ways, as part of three separate
|
||||||
features.
|
features.
|
||||||
|
|
||||||
#### Clipboard Filtering by Entry Regex
|
#### Clipboard Filtering by Entry Regex
|
||||||
|
|
||||||
This can be configured in one of two ways. You can use the **environment
|
This can be configured in several ways. The simplest is the **environment
|
||||||
variable** `STASTH_SENSITIVE_REGEX` to a valid regex pattern, and if the
|
variable** `STASH_SENSITIVE_REGEX` set to a valid regex pattern; if the
|
||||||
clipboard text matches the regex it will not be stored. This can be used for
|
clipboard text matches, it will not be stored. Useful for trivial secrets such
|
||||||
trivial secrets such as but not limited to GitHub tokens or secrets that follow
|
as GitHub tokens or secrets that follow a rule. You would typically set this in
|
||||||
a rule, e.g. a prefix. You would typically set this in your `~/.bashrc` or
|
your `~/.bashrc` or similar, but in some cases this might be a security flaw.
|
||||||
similar but in some cases this might be a security flaw.
|
|
||||||
|
|
||||||
The safer alternative to this is using **Systemd LoadCrediental**. If Stash is
|
The less-insecure alternatives are:
|
||||||
running as a Systemd service, you can provide a regex pattern using a crediental
|
|
||||||
file. For example, add to your `stash.service`:
|
- `STASH_SENSITIVE_REGEX_FILE`: read the regex from a file path. Useful with
|
||||||
|
NixOS secrets managers like agenix or sops-nix.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export STASH_SENSITIVE_REGEX_FILE=/run/secrets/stash/clipboard_filter
|
||||||
|
```
|
||||||
|
|
||||||
|
- `STASH_SENSITIVE_REGEX_COMMAND`: execute a shell command whose stdout is the
|
||||||
|
regex pattern. Works well with password managers.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export STASH_SENSITIVE_REGEX_COMMAND="pass show stash/clipboard-filter"
|
||||||
|
```
|
||||||
|
|
||||||
|
The safest option is **Systemd LoadCredential**. If Stash is running as a
|
||||||
|
Systemd service, you can provide a regex pattern using a credential file. For
|
||||||
|
example, add to your `stash.service`:
|
||||||
|
|
||||||
```dosini
|
```dosini
|
||||||
LoadCredential=clipboard_filter:/etc/stash/clipboard_filter
|
LoadCredential=clipboard_filter:/etc/stash/clipboard_filter
|
||||||
|
|
@ -382,9 +400,9 @@ quotes). This is done automatically in the
|
||||||
[vendored Systemd service](./contrib/stash.service). Remember to set the
|
[vendored Systemd service](./contrib/stash.service). Remember to set the
|
||||||
appropriate file permissions if using this option.
|
appropriate file permissions if using this option.
|
||||||
|
|
||||||
The service will check the credential file first, then the environment variable.
|
The service will check the credential file first, then the command, then the
|
||||||
If a clipboard entry matches the regex, it will be skipped and a warning will be
|
file path, then the environment variable. If a clipboard entry matches the
|
||||||
logged.
|
regex, it will be skipped and a warning will be logged.
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> **Example regex to block common password patterns**:
|
> **Example regex to block common password patterns**:
|
||||||
|
|
@ -415,6 +433,72 @@ be only copied to the clipboard.
|
||||||
>
|
>
|
||||||
> `stash --excluded-apps Bitwarden watch`
|
> `stash --excluded-apps Bitwarden watch`
|
||||||
|
|
||||||
|
#### Clipboard Filtering by Password Manager Hint
|
||||||
|
|
||||||
|
Stash automatically skips entries whose clipboard offer includes the
|
||||||
|
`x-kde-passwordManagerHint` MIME type. This is the convention used by KeePassXC
|
||||||
|
and compatible password managers to signal that clipboard content is sensitive
|
||||||
|
and should not be persisted.
|
||||||
|
|
||||||
|
No configuration is required. If the hint is present in the clipboard offer, the
|
||||||
|
entry is dropped before storage. The entry is still available in your clipboard
|
||||||
|
— it is only excluded from the persistent database.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> This filter only applies via the watch daemon (`stash watch`), where MIME type
|
||||||
|
> metadata is available from the Wayland clipboard protocol. Manual
|
||||||
|
> `stash store` invocations do not have this context and are not filtered.
|
||||||
|
|
||||||
|
### Database Encryption
|
||||||
|
|
||||||
|
Stash supports encrypting clipboard entries at rest using the
|
||||||
|
[age](https://age-encryption.org/) encryption format.
|
||||||
|
|
||||||
|
Encryption is **opt-in** and only activates when a passphrase is configured.
|
||||||
|
When one is configured, all new entries are encrypted before storage and
|
||||||
|
decrypted transparently on retrieval. Entries stored without encryption remain
|
||||||
|
as plaintext. Only new entries written after configuring encryption are
|
||||||
|
encrypted.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Removing the passphrase after encrypted entries have been stored leaves those
|
||||||
|
> entries permanently unreadable. There is no migration path short of wiping the
|
||||||
|
> database. `stash db stats` reports affected entries as Undecryptable.
|
||||||
|
>
|
||||||
|
> Full-text search (`stash delete --type query`, TUI search) operates on raw
|
||||||
|
> database contents. Encrypted entries will not match any search query.
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
|
||||||
|
Provide a passphrase in one of these ways (checked in order):
|
||||||
|
|
||||||
|
1. **Systemd LoadCredential** (safest): add to `stash.service`:
|
||||||
|
|
||||||
|
```dosini
|
||||||
|
LoadCredential=stash_encryption_passphrase:/etc/stash/encryption_passphrase
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Command** — stdout of a shell command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export STASH_ENCRYPTION_PASSPHRASE_COMMAND="pass show stash/encryption-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **File** — path to a file containing the passphrase:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export STASH_ENCRYPTION_PASSPHRASE_FILE=/run/secrets/stash/encryption_passphrase
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Environment variable** (least secure):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export STASH_ENCRYPTION_PASSPHRASE="your-secure-passphrase"
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Back up your passphrase. Encrypted entries cannot be recovered without it.
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
I've been a long-time user of Cliphist. You can probably tell by the number of
|
I've been a long-time user of Cliphist. You can probably tell by the number of
|
||||||
|
|
|
||||||
12
flake.lock
generated
12
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"crane": {
|
"crane": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1778106249,
|
"lastModified": 1780532242,
|
||||||
"narHash": "sha256-cM/AuKy5tMhwOOQIbha8ZRRMHVfNf7cv2aljIw+qoCg=",
|
"narHash": "sha256-D+BsdpxmtUwtqGoY0IXPhHgTlmqgcZKCEo1oMyn7ep0=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "6d015ea29630b7ad2402841386da2cb617a470a7",
|
"rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -17,11 +17,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1778869304,
|
"lastModified": 1781577229,
|
||||||
"narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=",
|
"narHash": "sha256-lrp67w8AulE9Ks53n27I45ADSzbOCn4H+CNW1Ck8B+8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "d233902339c02a9c334e7e593de68855ad26c4cb",
|
"rev": "567a49d1913ce81ac6e9582e3553dd90a955875f",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ self: {
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit (lib.modules) mkIf;
|
inherit (lib.modules) mkIf;
|
||||||
inherit (lib.options) mkOption mkEnableOption mkPackageOption literalMD;
|
inherit (lib.options) mkOption mkEnableOption mkPackageOption;
|
||||||
inherit (lib.types) listOf str;
|
inherit (lib.types) listOf str;
|
||||||
inherit (lib.strings) concatStringsSep;
|
inherit (lib.strings) concatStringsSep;
|
||||||
inherit (lib.meta) getExe;
|
inherit (lib.meta) getExe;
|
||||||
|
|
@ -15,7 +15,9 @@ in {
|
||||||
options.services.stash-clipboard = {
|
options.services.stash-clipboard = {
|
||||||
enable = mkEnableOption "stash, a Wayland clipboard manager";
|
enable = mkEnableOption "stash, a Wayland clipboard manager";
|
||||||
|
|
||||||
package = mkPackageOption self.packages.${pkgs.system} ["stash"] {};
|
package = mkPackageOption self.packages.${pkgs.stdenv.hostPlatform.system} ["stash"] {
|
||||||
|
pkgsText = "self.packages.\${pkgs.stdenv.hostPlatform.system}";
|
||||||
|
};
|
||||||
|
|
||||||
flags = mkOption {
|
flags = mkOption {
|
||||||
type = listOf str;
|
type = listOf str;
|
||||||
|
|
@ -28,7 +30,7 @@ in {
|
||||||
type = str;
|
type = str;
|
||||||
default = "";
|
default = "";
|
||||||
example = "{file}`/etc/stash/clipboard_filter`";
|
example = "{file}`/etc/stash/clipboard_filter`";
|
||||||
description = literalMD ''
|
description = ''
|
||||||
File containing a regular expression to catch sensitive patterns. The file
|
File containing a regular expression to catch sensitive patterns. The file
|
||||||
passed to this option must contain your regex pattern with no quotes.
|
passed to this option must contain your regex pattern with no quotes.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,25 @@ static SERVING_PID: AtomicI32 = AtomicI32::new(0);
|
||||||
|
|
||||||
/// Get the current serving PID if any. Used by the watch loop to avoid
|
/// Get the current serving PID if any. Used by the watch loop to avoid
|
||||||
/// duplicate persistence processes.
|
/// duplicate persistence processes.
|
||||||
|
///
|
||||||
|
/// Probes the stored PID with `kill(pid, 0)` to detect children that have
|
||||||
|
/// already exited (SIGCHLD is ignored so we never get reaped notifications).
|
||||||
|
/// A stale PID is cleared and `None` is returned.
|
||||||
pub fn get_serving_pid() -> Option<i32> {
|
pub fn get_serving_pid() -> Option<i32> {
|
||||||
let pid = SERVING_PID.load(Ordering::SeqCst);
|
let pid = SERVING_PID.load(Ordering::SeqCst);
|
||||||
if pid != 0 { Some(pid) } else { None }
|
if pid == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal 0 = existence check, no signal sent. Returns 0 if alive,
|
||||||
|
// -1 (ESRCH) if the PID is gone.
|
||||||
|
if unsafe { libc::kill(pid, 0) } == 0 {
|
||||||
|
Some(pid)
|
||||||
|
} else {
|
||||||
|
let _ =
|
||||||
|
SERVING_PID.compare_exchange(pid, 0, Ordering::SeqCst, Ordering::SeqCst);
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result type for persistence operations.
|
/// Result type for persistence operations.
|
||||||
|
|
@ -157,6 +173,18 @@ unsafe fn fork_and_serve(prepared: PreparedCopy) -> PersistenceResult<()> {
|
||||||
libc::signal(libc::SIGCHLD, libc::SIG_IGN);
|
libc::signal(libc::SIGCHLD, libc::SIG_IGN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace any prior serving child: a new clipboard entry supersedes the
|
||||||
|
// old offer (the compositor will invalidate it anyway the moment the new
|
||||||
|
// selection is taken). Without this, the old child lingers serving stale
|
||||||
|
// data until MAX_SERVE_REQUESTS or invalidation.
|
||||||
|
let prior = SERVING_PID.swap(0, Ordering::SeqCst);
|
||||||
|
if prior > 0 && unsafe { libc::kill(prior, 0) } == 0 {
|
||||||
|
unsafe {
|
||||||
|
libc::kill(prior, libc::SIGTERM);
|
||||||
|
}
|
||||||
|
log::debug!("terminated prior persistence child (pid: {prior})");
|
||||||
|
}
|
||||||
|
|
||||||
match unsafe { libc::fork() } {
|
match unsafe { libc::fork() } {
|
||||||
0 => {
|
0 => {
|
||||||
// Child process - clear serving PID
|
// Child process - clear serving PID
|
||||||
|
|
|
||||||
|
|
@ -396,17 +396,15 @@ impl SqliteClipboardDb {
|
||||||
// Normal mode navigation commands
|
// Normal mode navigation commands
|
||||||
match (key.code, key.modifiers) {
|
match (key.code, key.modifiers) {
|
||||||
(KeyCode::Char('q') | KeyCode::Esc, _) => actions.quit = true,
|
(KeyCode::Char('q') | KeyCode::Esc, _) => actions.quit = true,
|
||||||
(KeyCode::Down | KeyCode::Char('j'), _) => {
|
(KeyCode::Down | KeyCode::Char('j'), _)
|
||||||
// Cap at +1 per frame for smooth scrolling
|
// Cap at +1 per frame for smooth scrolling
|
||||||
if actions.net_down < 1 {
|
if actions.net_down < 1 => {
|
||||||
actions.net_down += 1;
|
actions.net_down += 1;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(KeyCode::Up | KeyCode::Char('k'), _) => {
|
(KeyCode::Up | KeyCode::Char('k'), _)
|
||||||
// Cap at -1 per frame for smooth scrolling
|
// Cap at -1 per frame for smooth scrolling
|
||||||
if actions.net_down > -1 {
|
if actions.net_down > -1 => {
|
||||||
actions.net_down -= 1;
|
actions.net_down -= 1;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(KeyCode::Enter, _) => actions.copy = true,
|
(KeyCode::Enter, _) => actions.copy = true,
|
||||||
(KeyCode::Char('D'), KeyModifiers::SHIFT) => {
|
(KeyCode::Char('D'), KeyModifiers::SHIFT) => {
|
||||||
|
|
|
||||||
|
|
@ -1024,6 +1024,17 @@ impl SqliteClipboardDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clean up all expired entries. Returns count deleted.
|
/// Clean up all expired entries. Returns count deleted.
|
||||||
|
pub fn expire_ttl_entries(&self) -> Result<usize, StashError> {
|
||||||
|
self
|
||||||
|
.conn
|
||||||
|
.execute(
|
||||||
|
"UPDATE clipboard SET is_expired = 1 WHERE expires_at IS NOT NULL AND \
|
||||||
|
(is_expired IS NULL OR is_expired = 0)",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.map_err(|e| StashError::Trim(e.to_string().into()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cleanup_expired(&self) -> Result<usize, StashError> {
|
pub fn cleanup_expired(&self) -> Result<usize, StashError> {
|
||||||
let now = Self::now();
|
let now = Self::now();
|
||||||
self
|
self
|
||||||
|
|
@ -1141,13 +1152,15 @@ impl SqliteClipboardDb {
|
||||||
#[cfg(not(feature = "encryption"))]
|
#[cfg(not(feature = "encryption"))]
|
||||||
let undecryptable: i64 = encrypted;
|
let undecryptable: i64 = encrypted;
|
||||||
|
|
||||||
|
let db_path = self.db_path.display();
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"Database Statistics:\n\nEntries:\nTotal: \
|
"Database Statistics:\n\nEntries:\nTotal: \
|
||||||
{total}\nActive: {active}\nExpired: \
|
{total}\nActive: {active}\nExpired: \
|
||||||
{expired}\nWith TTL: \
|
{expired}\nWith TTL: \
|
||||||
{with_expiration}\nEncrypted: \
|
{with_expiration}\nEncrypted: \
|
||||||
{encrypted}\nUndecryptable: \
|
{encrypted}\nUndecryptable: \
|
||||||
{undecryptable}\n\nStorage:\nSize: {size_mb:.2} MB \
|
{undecryptable}\n\nStorage:\nPath: \
|
||||||
|
{db_path}\nSize: {size_mb:.2} MB \
|
||||||
({size_bytes} bytes)\nPages: {page_count}\nPage size: \
|
({size_bytes} bytes)\nPages: {page_count}\nPage size: \
|
||||||
{page_size} bytes"
|
{page_size} bytes"
|
||||||
))
|
))
|
||||||
|
|
@ -1218,6 +1231,11 @@ fn load_sensitive_regex() -> Option<Regex> {
|
||||||
/// previously encrypted entries permanently undecryptable, so the permanent
|
/// previously encrypted entries permanently undecryptable, so the permanent
|
||||||
/// cache prevents accidental passphrase changes from corrupting the
|
/// cache prevents accidental passphrase changes from corrupting the
|
||||||
/// clipboard history.
|
/// clipboard history.
|
||||||
|
///
|
||||||
|
/// Removing the passphrase entirely (disabling encryption) after entries have
|
||||||
|
/// been stored encrypted also renders those entries permanently unreadable.
|
||||||
|
/// There is no migration path short of wiping the database. `stash stats`
|
||||||
|
/// reports affected entries as Undecryptable.
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
fn load_encryption_passphrase() -> Option<age::secrecy::SecretString> {
|
fn load_encryption_passphrase() -> Option<age::secrecy::SecretString> {
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
@ -1250,25 +1268,18 @@ fn load_encryption_passphrase() -> Option<age::secrecy::SecretString> {
|
||||||
Some(secret)
|
Some(secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypt age-encrypted data using a cached scrypt identity.
|
/// Decrypt age-encrypted data.
|
||||||
|
///
|
||||||
|
/// `age::scrypt::Identity::new` is cheap since it stores the passphrase only.
|
||||||
|
/// The scrypt KDF runs inside `age::decrypt` per call, on the per-file salt
|
||||||
|
/// embedded in the ciphertext header. Caching the Identity would not avoid
|
||||||
|
/// it. The passphrase itself is cached by [`load_encryption_passphrase`].
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
fn decrypt_cached(ciphertext: &[u8]) -> Result<Vec<u8>, StashError> {
|
fn decrypt_cached(ciphertext: &[u8]) -> Result<Vec<u8>, StashError> {
|
||||||
static CACHE: OnceLock<Mutex<Option<age::scrypt::Identity>>> =
|
let passphrase = load_encryption_passphrase()
|
||||||
OnceLock::new();
|
.ok_or_else(|| StashError::Decryption("no passphrase configured".into()))?;
|
||||||
let cache = CACHE.get_or_init(|| Mutex::new(None));
|
let identity = age::scrypt::Identity::new(passphrase);
|
||||||
let mut guard = cache.lock().map_err(|e| {
|
age::decrypt(&identity, ciphertext)
|
||||||
StashError::Decryption(format!("identity cache lock poisoned: {e}").into())
|
|
||||||
})?;
|
|
||||||
if guard.is_none() {
|
|
||||||
let passphrase = load_encryption_passphrase().ok_or_else(|| {
|
|
||||||
StashError::Decryption("no passphrase configured".into())
|
|
||||||
})?;
|
|
||||||
*guard = Some(age::scrypt::Identity::new(passphrase));
|
|
||||||
}
|
|
||||||
let identity = guard
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| StashError::Decryption("identity not available".into()))?;
|
|
||||||
age::decrypt(identity, ciphertext)
|
|
||||||
.map_err(|e| StashError::Decryption(e.to_string().into()))
|
.map_err(|e| StashError::Decryption(e.to_string().into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
29
src/main.rs
29
src/main.rs
|
|
@ -169,6 +169,13 @@ enum DbAction {
|
||||||
ask: bool,
|
ask: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Immediately expire all entries with a TTL
|
||||||
|
Expire {
|
||||||
|
/// Ask for confirmation before expiring
|
||||||
|
#[arg(long)]
|
||||||
|
ask: bool,
|
||||||
|
},
|
||||||
|
|
||||||
/// Optimize database using VACUUM
|
/// Optimize database using VACUUM
|
||||||
Vacuum,
|
Vacuum,
|
||||||
|
|
||||||
|
|
@ -406,6 +413,28 @@ fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
DbAction::Expire { ask } => {
|
||||||
|
let should_proceed = !ask
|
||||||
|
|| confirm(
|
||||||
|
"Are you sure you want to immediately expire all entries with \
|
||||||
|
a TTL?",
|
||||||
|
);
|
||||||
|
if should_proceed {
|
||||||
|
match db.expire_ttl_entries() {
|
||||||
|
Ok(0) => {
|
||||||
|
println!("no entries with a TTL to expire");
|
||||||
|
},
|
||||||
|
Ok(count) => {
|
||||||
|
println!("marked {count} entries as expired");
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("failed to expire entries: {e}");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::info!("db expire command aborted by user.");
|
||||||
|
}
|
||||||
|
},
|
||||||
DbAction::Vacuum => {
|
DbAction::Vacuum => {
|
||||||
match db.vacuum() {
|
match db.vacuum() {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
|
|
|
||||||
|
|
@ -474,13 +474,11 @@ fn handle_regular_paste(
|
||||||
|| types == "application/x-sh"
|
|| types == "application/x-sh"
|
||||||
};
|
};
|
||||||
|
|
||||||
if !args.no_newline && is_text_content && !buf.ends_with(b"\n") {
|
if !args.no_newline && is_text_content && !buf.ends_with(b"\n")
|
||||||
if let Err(e) = out.write_all(b"\n") {
|
&& let Err(e) = out.write_all(b"\n")
|
||||||
if e.kind() != io::ErrorKind::BrokenPipe {
|
&& e.kind() != io::ErrorKind::BrokenPipe {
|
||||||
bail!("failed to write newline to stdout: {e}");
|
bail!("failed to write newline to stdout: {e}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Err(PasteError::NoSeats) => {
|
Err(PasteError::NoSeats) => {
|
||||||
bail!("no seats available (is a Wayland compositor running?)");
|
bail!("no seats available (is a Wayland compositor running?)");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue