~core/sage

f11e86509b6263d2d92c20f8aefdc2472a1285bf — core 21 days ago
initial
A  => .gitignore +1 -0
@@ 1,1 @@
/target

A  => .idea/.gitignore +8 -0
@@ 1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

A  => .idea/modules.xml +8 -0
@@ 1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/sage.iml" filepath="$PROJECT_DIR$/.idea/sage.iml" />
    </modules>
  </component>
</project>
\ No newline at end of file

A  => .idea/sage.iml +11 -0
@@ 1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
  <component name="NewModuleRootManager">
    <content url="file://$MODULE_DIR$">
      <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
      <excludeFolder url="file://$MODULE_DIR$/target" />
    </content>
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>
\ No newline at end of file

A  => .idea/vcs.xml +6 -0
@@ 1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="" vcs="Git" />
  </component>
</project>
\ No newline at end of file

A  => Cargo.lock +1502 -0
@@ 1,1502 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
 "crypto-common",
 "generic-array",
]

[[package]]
name = "age"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc171f4874fa10887e47088f81a55fcf030cd421aa31ec2b370cafebcc608a"
dependencies = [
 "age-core",
 "base64",
 "bech32",
 "chacha20poly1305",
 "cookie-factory",
 "hmac",
 "i18n-embed",
 "i18n-embed-fl",
 "lazy_static",
 "nom",
 "pin-project",
 "rand",
 "rust-embed",
 "scrypt",
 "sha2",
 "subtle",
 "x25519-dalek",
 "zeroize",
]

[[package]]
name = "age-core"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2bf6a89c984ca9d850913ece2da39e1d200563b0a94b002b253beee4c5acf99"
dependencies = [
 "base64",
 "chacha20poly1305",
 "cookie-factory",
 "hkdf",
 "io_tee",
 "nom",
 "rand",
 "secrecy",
 "sha2",
]

[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
 "memchr",
]

[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"

[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"

[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"

[[package]]
name = "basic-toml"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
dependencies = [
 "serde",
]

[[package]]
name = "bech32"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445"

[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"

[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
 "generic-array",
]

[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"

[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
 "cfg-if",
 "cipher",
 "cpufeatures",
]

[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
 "aead",
 "chacha20",
 "cipher",
 "poly1305",
 "zeroize",
]

[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
 "crypto-common",
 "inout",
 "zeroize",
]

[[package]]
name = "cookie-factory"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2"
dependencies = [
 "futures",
]

[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
 "libc",
]

[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
 "crossbeam-epoch",
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
 "crossbeam-utils",
]

[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"

[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
 "generic-array",
 "typenum",
]

[[package]]
name = "curve25519-dalek"
version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
 "cfg-if",
 "cpufeatures",
 "curve25519-dalek-derive",
 "fiat-crypto",
 "rustc_version",
 "subtle",
 "zeroize",
]

[[package]]
name = "curve25519-dalek-derive"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
 "block-buffer",
 "crypto-common",
 "subtle",
]

[[package]]
name = "dirs"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
dependencies = [
 "dirs-sys",
]

[[package]]
name = "dirs-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
dependencies = [
 "libc",
 "option-ext",
 "redox_users",
 "windows-sys",
]

[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"

[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"

[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
 "libc",
 "windows-sys",
]

[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"

[[package]]
name = "fiat-crypto"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"

[[package]]
name = "find-crate"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2"
dependencies = [
 "toml 0.5.11",
]

[[package]]
name = "fluent"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb74634707bebd0ce645a981148e8fb8c7bccd4c33c652aeffd28bf2f96d555a"
dependencies = [
 "fluent-bundle",
 "unic-langid",
]

[[package]]
name = "fluent-bundle"
version = "0.15.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe0a21ee80050c678013f82edf4b705fe2f26f1f9877593d13198612503f493"
dependencies = [
 "fluent-langneg",
 "fluent-syntax",
 "intl-memoizer",
 "intl_pluralrules",
 "rustc-hash 1.1.0",
 "self_cell 0.10.3",
 "smallvec",
 "unic-langid",
]

[[package]]
name = "fluent-langneg"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0"
dependencies = [
 "unic-langid",
]

[[package]]
name = "fluent-syntax"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a530c4694a6a8d528794ee9bbd8ba0122e779629ac908d15ad5a7ae7763a33d"
dependencies = [
 "thiserror 1.0.69",
]

[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
 "futures-channel",
 "futures-core",
 "futures-executor",
 "futures-io",
 "futures-sink",
 "futures-task",
 "futures-util",
]

[[package]]
name = "futures-channel"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
 "futures-core",
 "futures-sink",
]

[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"

[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
 "futures-core",
 "futures-task",
 "futures-util",
]

[[package]]
name = "futures-io"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"

[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"

[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"

[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
 "futures-channel",
 "futures-core",
 "futures-io",
 "futures-macro",
 "futures-sink",
 "futures-task",
 "memchr",
 "pin-project-lite",
 "pin-utils",
 "slab",
]

[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
 "typenum",
 "version_check",
]

[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
 "cfg-if",
 "libc",
 "wasi",
]

[[package]]
name = "getrandom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
 "cfg-if",
 "libc",
 "r-efi",
 "wasip2",
]

[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"

[[package]]
name = "hkdf"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [
 "hmac",
]

[[package]]
name = "hmac"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
 "digest",
]

[[package]]
name = "i18n-config"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef"
dependencies = [
 "basic-toml",
 "log",
 "serde",
 "serde_derive",
 "thiserror 1.0.69",
 "unic-langid",
]

[[package]]
name = "i18n-embed"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "669ffc2c93f97e6ddf06ddbe999fcd6782e3342978bb85f7d3c087c7978404c4"
dependencies = [
 "arc-swap",
 "fluent",
 "fluent-langneg",
 "fluent-syntax",
 "i18n-embed-impl",
 "intl-memoizer",
 "log",
 "parking_lot",
 "rust-embed",
 "thiserror 1.0.69",
 "unic-langid",
 "walkdir",
]

[[package]]
name = "i18n-embed-fl"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04b2969d0b3fc6143776c535184c19722032b43e6a642d710fa3f88faec53c2d"
dependencies = [
 "find-crate",
 "fluent",
 "fluent-syntax",
 "i18n-config",
 "i18n-embed",
 "proc-macro-error2",
 "proc-macro2",
 "quote",
 "strsim",
 "syn",
 "unic-langid",
]

[[package]]
name = "i18n-embed-impl"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2"
dependencies = [
 "find-crate",
 "i18n-config",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "indexmap"
version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
dependencies = [
 "equivalent",
 "hashbrown",
]

[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
 "generic-array",
]

[[package]]
name = "intl-memoizer"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f"
dependencies = [
 "type-map",
 "unic-langid",
]

[[package]]
name = "intl_pluralrules"
version = "7.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
dependencies = [
 "unic-langid",
]

[[package]]
name = "io_tee"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304"

[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"

[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"

[[package]]
name = "libredox"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
 "bitflags",
 "libc",
]

[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"

[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
 "scopeguard",
]

[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"

[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"

[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"

[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
 "memchr",
 "minimal-lexical",
]

[[package]]
name = "nucleo"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5262af4c94921c2646c5ac6ff7900c2af9cbb08dc26a797e18130a7019c039d4"
dependencies = [
 "nucleo-matcher",
 "parking_lot",
 "rayon",
]

[[package]]
name = "nucleo-matcher"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf33f538733d1a5a3494b836ba913207f14d9d4a1d3cd67030c5061bdd2cac85"
dependencies = [
 "memchr",
 "unicode-segmentation",
]

[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"

[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"

[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"

[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
 "lock_api",
 "parking_lot_core",
]

[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
 "cfg-if",
 "libc",
 "redox_syscall",
 "smallvec",
 "windows-link",
]

[[package]]
name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
 "digest",
 "hmac",
]

[[package]]
name = "pico-args"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"

[[package]]
name = "pin-project"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
dependencies = [
 "pin-project-internal",
]

[[package]]
name = "pin-project-internal"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"

[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"

[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
 "cpufeatures",
 "opaque-debug",
 "universal-hash",
]

[[package]]
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
 "zerocopy",
]

[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
 "proc-macro2",
 "quote",
]

[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
 "proc-macro-error-attr2",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"

[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
 "libc",
 "rand_chacha",
 "rand_core",
]

[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
 "ppv-lite86",
 "rand_core",
]

[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
 "getrandom 0.2.16",
]

[[package]]
name = "rayon"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
 "either",
 "rayon-core",
]

[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
 "crossbeam-deque",
 "crossbeam-utils",
]

[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
 "bitflags",
]

[[package]]
name = "redox_users"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
 "getrandom 0.2.16",
 "libredox",
 "thiserror 2.0.17",
]

[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-automata",
 "regex-syntax",
]

[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-syntax",
]

[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"

[[package]]
name = "rust-embed"
version = "8.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "947d7f3fad52b283d261c4c99a084937e2fe492248cb9a68a8435a861b8798ca"
dependencies = [
 "rust-embed-impl",
 "rust-embed-utils",
 "walkdir",
]

[[package]]
name = "rust-embed-impl"
version = "8.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fa2c8c9e8711e10f9c4fd2d64317ef13feaab820a4c51541f1a8c8e2e851ab2"
dependencies = [
 "proc-macro2",
 "quote",
 "rust-embed-utils",
 "syn",
 "walkdir",
]

[[package]]
name = "rust-embed-utils"
version = "8.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b161f275cb337fe0a44d924a5f4df0ed69c2c39519858f931ce61c779d3475"
dependencies = [
 "sha2",
 "walkdir",
]

[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"

[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"

[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
 "semver",
]

[[package]]
name = "rustix"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [
 "bitflags",
 "errno",
 "libc",
 "linux-raw-sys",
 "windows-sys",
]

[[package]]
name = "sage"
version = "0.1.0"
dependencies = [
 "age",
 "anyhow",
 "dirs",
 "nucleo",
 "pico-args",
 "regex",
 "serde",
 "tempfile",
 "toml 0.9.8",
 "urlencoding",
]

[[package]]
name = "salsa20"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
 "cipher",
]

[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
 "winapi-util",
]

[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"

[[package]]
name = "scrypt"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
dependencies = [
 "pbkdf2",
 "salsa20",
 "sha2",
]

[[package]]
name = "secrecy"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
dependencies = [
 "zeroize",
]

[[package]]
name = "self_cell"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d"
dependencies = [
 "self_cell 1.2.1",
]

[[package]]
name = "self_cell"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16c2f82143577edb4921b71ede051dac62ca3c16084e918bf7b40c96ae10eb33"

[[package]]
name = "semver"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"

[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
 "serde_core",
 "serde_derive",
]

[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "serde_spanned"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
dependencies = [
 "serde_core",
]

[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
 "cfg-if",
 "cpufeatures",
 "digest",
]

[[package]]
name = "slab"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"

[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"

[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"

[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"

[[package]]
name = "syn"
version = "2.0.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "tempfile"
version = "3.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
dependencies = [
 "fastrand",
 "getrandom 0.3.4",
 "once_cell",
 "rustix",
 "windows-sys",
]

[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
 "thiserror-impl 1.0.69",
]

[[package]]
name = "thiserror"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
dependencies = [
 "thiserror-impl 2.0.17",
]

[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "thiserror-impl"
version = "2.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "tinystr"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
 "displaydoc",
 "serde_core",
 "zerovec",
]

[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
 "serde",
]

[[package]]
name = "toml"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
dependencies = [
 "indexmap",
 "serde_core",
 "serde_spanned",
 "toml_datetime",
 "toml_parser",
 "toml_writer",
 "winnow",
]

[[package]]
name = "toml_datetime"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
dependencies = [
 "serde_core",
]

[[package]]
name = "toml_parser"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
dependencies = [
 "winnow",
]

[[package]]
name = "toml_writer"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"

[[package]]
name = "type-map"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
dependencies = [
 "rustc-hash 2.1.1",
]

[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"

[[package]]
name = "unic-langid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05"
dependencies = [
 "unic-langid-impl",
]

[[package]]
name = "unic-langid-impl"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658"
dependencies = [
 "serde",
 "tinystr",
]

[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"

[[package]]
name = "unicode-segmentation"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"

[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
 "crypto-common",
 "subtle",
]

[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"

[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"

[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
 "same-file",
 "winapi-util",
]

[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"

[[package]]
name = "wasip2"
version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
 "wit-bindgen",
]

[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
 "windows-sys",
]

[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"

[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
 "windows-link",
]

[[package]]
name = "winnow"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"

[[package]]
name = "wit-bindgen"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"

[[package]]
name = "x25519-dalek"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
dependencies = [
 "curve25519-dalek",
 "rand_core",
 "serde",
 "zeroize",
]

[[package]]
name = "zerocopy"
version = "0.8.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c"
dependencies = [
 "zerocopy-derive",
]

[[package]]
name = "zerocopy-derive"
version = "0.8.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"

[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
dependencies = [
 "zeroize_derive",
]

[[package]]
name = "zeroize_derive"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "zerovec"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
 "serde",
 "zerofrom",
]

A  => Cargo.toml +23 -0
@@ 1,23 @@
[package]
name = "sage"
version = "0.1.0"
edition = "2024"

[dependencies]
serde = { version = "1", features = ["derive"] }
toml = "0.9"

age = "0.11"

pico-args = { version = "0.5", features = ["combined-flags"] }

anyhow = "1"

dirs = "6"

regex = "1.12"

nucleo = "0.5"
urlencoding = "2"

tempfile = "3"
\ No newline at end of file

A  => src/cmds/createid.rs +50 -0
@@ 1,50 @@
use std::path::Path;
use std::process::exit;
use age::secrecy::ExposeSecret;
use regex::Regex;
use crate::db::Database;
use crate::identity::{IdKeyData, Identity};

pub fn create_id(db_path: &Path, name: String, email: String) -> anyhow::Result<()> {
    let mut db = Database::load_or_create(db_path)?;

    // sanity check to ensure this id does not already exist
    for key in &db.keys {
        if key.name == name && key.email == email {
            eprintln!("key '{:?}' (keyid {}) already exists  in database", key, key.keys.keyid()?);
            eprintln!("if you want to recreate it, remove it first");
            exit(1);
        }
    }

    // Email is a fucking nightmare
    // RFC5322, I hate you
    let email_re = Regex::new(r#"(?x)
            ^(?P<login>[^@\s]+)@
            ([[:word:]]+\.)*
            [[:word:]]+$
            "#).unwrap();

    if !email_re.is_match(&email) {
        eprintln!("`{email}` is not a valid RFC5322 email address");
        exit(1);
    }

    let key = age::x25519::Identity::generate();
    let ss = key.to_string();
    let ss = ss.expose_secret();

    let identity = Identity {
        name,
        email,
        keys: IdKeyData::Local(ss.to_string())
    };

    db.keys.push(identity.clone());
    // save database
    db.write(db_path)?;

    println!("wrote new key {:?} (keyid {}) to database", identity, identity.keys.keyid()?);

    Ok(())
}
\ No newline at end of file

A  => src/cmds/import.rs +62 -0
@@ 1,62 @@
use std::convert::identity;
use std::path::Path;
use std::process::exit;
use crate::db::Database;
use crate::identity::{unescape, IdKeyData, Identity};

pub fn import_key(db_path: &Path, key: String, insert_anyway: bool) -> anyhow::Result<()> {
    // decode the key
    let components = key.split("/").collect::<Vec<_>>();
    if components.len() != 4 || components[0] != "sage" {
        eprintln!("not a sage key (looks like sage/key/name/email");
        eprintln!("to import a raw age key without an identity, add it to the database manually");
        exit(1);
    }

    let [_sentinel, key, name, email] = components.as_slice() else { unreachable!() };

    let name = unescape(name.to_string())?;
    let email = unescape(email.to_string())?;

    let keys = match &key[0..3] {
        "age" => {
            IdKeyData::Peer(key.to_string())
        },
        "AGE" => {
            IdKeyData::Local(key.to_string())
        },
        _ => {
            eprintln!("unsupported age key type; is this a sage key?");
            exit(1);
        }
    };

    let identity = Identity {
        name,
        email,
        keys
    };

    let mut db = Database::load_or_create(db_path)?;

    for key in &db.keys {
        if key.keys.keyid()? == identity.keys.keyid()? {
            println!("key {} already exists in database, skipping", key.keys.keyid()?);
            return Ok(());
        }

        if key.name == identity.name && key.email == identity.email && !insert_anyway {
            eprintln!("database already has a key for {:?}", identity);
            eprintln!("to import anyway, run again with --import-anyway");
            exit(1);
        }
    }

    db.keys.push(identity.clone());

    db.write(db_path)?;

    println!("imported {:?} (keyid {}) successfully", identity, identity.keys.keyid()?);

    Ok(())
}
\ No newline at end of file

A  => src/cmds/ls.rs +45 -0
@@ 1,45 @@
use std::path::Path;
use std::process::exit;
use urlencoding::encode;
use crate::db::Database;
use crate::identity::{escape, IdKeyData};

pub fn list_keys(db_path: &Path, local_only: bool, peer_only: bool, full_pks: bool) -> anyhow::Result<()> {
    if local_only && peer_only {
        eprintln!("cannot show only local and only peer keys, pick one");
        exit(1);
    }
    let show_local = !peer_only;
    let show_peer = !full_pks;

    let db = Database::load_or_create(db_path)?;

    let mut displayed = 0;

    for key in &db.keys {
        match key.keys {
            IdKeyData::Local(_) => {
                if !show_local { continue };
                displayed += 1;
                println!("local: {:?}", key);
            }
            IdKeyData::Peer(_) => {
                if !show_peer { continue };
                displayed += 1;
                println!("peer: {:?}", key);
            }
        }

        if full_pks {
            println!("   pk: sage/{}/{}/{}", key.keys.pk()?, escape(key.name.clone()), escape(key.email.clone()));
        } else {
            println!("   id: {}", key.keys.keyid()?);
        }
    }

    if displayed == 0 {
        println!("no keys found");
    }

    Ok(())
}
\ No newline at end of file

A  => src/cmds/mod.rs +4 -0
@@ 1,4 @@
pub mod createid;
pub mod ls;
pub mod show;
pub mod import;
\ No newline at end of file

A  => src/cmds/show.rs +32 -0
@@ 1,32 @@
use std::path::Path;
use std::process::exit;
use crate::db::Database;
use crate::identity::{escape, IdKeyData};

pub fn show_key(db_path: &Path, search: String, expose_secret: bool) -> anyhow::Result<()> {
    let db = Database::load_or_create(db_path)?;

    let matches = db.fuzzy_search(search);

    if matches.len() > 1 {
        println!("there were multiple best matches, showing all");
    } else if matches.is_empty() {
        println!("no matches found");
        exit(0);
    }

    for each_match in &matches {
        println!("match: {:?}", each_match);

        let show_sk = expose_secret && matches!(each_match.keys, IdKeyData::Local(..));

        if !show_sk {
            println!("   pk: sage/{}/{}/{}", each_match.keys.pk()?, escape(each_match.name.clone()), escape(each_match.email.clone()));
        } else {
            let IdKeyData::Local(k) = &each_match.keys else { continue };
            println!("   sk: sage/{}/{}/{}", k, escape(each_match.name.clone()), escape(each_match.email.clone()));
        }
    }

    Ok(())
}
\ No newline at end of file

A  => src/db.rs +74 -0
@@ 1,74 @@
use std::path::{Path, PathBuf};
use dirs::config_dir;
use nucleo::{Config, Matcher, Utf32Str};
use serde::{Deserialize, Serialize};
use crate::identity::Identity;

#[derive(Serialize, Deserialize)]
pub struct Database {
    pub keys: Vec<Identity>
}
impl Database {
    pub fn load_or_create(path: &Path) -> anyhow::Result<Self> {
        if !database_exists(path) {
            let d = Database {
                keys: vec![]
            };
            write_database(d, path)?;
        }
        load_database(path)
    }
    pub fn write(self, path: &Path) -> anyhow::Result<()> {
        write_database(self, path)
    }
    pub fn fuzzy_search(&self, term: String) -> Vec<Identity> {
        if self.keys.is_empty() { return vec![] }
        let mut m = Matcher::new(Config::DEFAULT);

        let haystacks = self.keys.iter().map(|u| {
            (u.clone(), format!("{:?} {}", u, u.keys.pk().unwrap_or("<pk_unknown>".to_string())))
        }).collect::<Vec<_>>();

        let mut scores = haystacks.iter().map(|h| {
            (
                m.fuzzy_match(Utf32Str::Ascii(h.1.as_bytes()), Utf32Str::Ascii(term.as_bytes())).unwrap_or(0),
                h.0.clone()
            )
        }).collect::<Vec<_>>();

        scores.sort_by_key(|u| u.0);
        scores.reverse();

        let mut best_matches = vec![scores[0].clone()];
        for score in &scores[1..] {
            if score.0 == best_matches[0].0 {
                best_matches.push(score.clone());
            }
        }

        best_matches.iter().map(|u| u.1.clone()).collect()
    }
}

fn database_exists(path: &Path) -> bool {
    path.exists()
}
fn load_database(path: &Path) -> anyhow::Result<Database> {
    let f = std::fs::read_to_string(path)?;
    let data: Database = toml::from_str(&f)?;
    Ok(data)
}
fn write_database(d: Database, path: &Path) -> anyhow::Result<()> {
    let s = toml::to_string(&d)?;
    let s_with_warning = format!(
        "# This file is autogenerated; do not edit! sage v{}\n{}",
        env!("CARGO_PKG_VERSION"),
        s
    );
    std::fs::write(path, s_with_warning)?;
    Ok(())
}

pub fn db_default() -> PathBuf {
    config_dir().unwrap().join("sage.toml")
}
\ No newline at end of file

A  => src/identity.rs +52 -0
@@ 1,52 @@
use std::fmt::{Debug, Formatter};
use std::str::FromStr;
use anyhow::bail;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone)]
pub struct Identity {
    pub name: String,
    pub email: String,
    pub keys: IdKeyData
}
#[derive(Serialize, Deserialize, Clone)]
pub enum IdKeyData {
    Local(String),
    Peer(String)
}

impl Debug for Identity {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{} <{}>", self.name, self.email)
    }
}
impl IdKeyData {
    pub fn pk(&self) -> anyhow::Result<String> {
        match self {
            IdKeyData::Local(id) => {
                let k = match age::x25519::Identity::from_str(id) {
                    Ok(k) => k,
                    Err(e) => {
                        bail!(e);
                    }
                };
                Ok(k.to_public().to_string())
            },
            IdKeyData::Peer(pk) => {
                Ok(pk.clone())
            }
        }
    }
    pub fn keyid(&self) -> anyhow::Result<String> {
        let pk = self.pk()?;
        let s0 = &pk[4..12];
        Ok(format!("{s0}"))
    }
}

pub fn escape(i: String) -> String {
    urlencoding::encode(&i).to_string()
}
pub fn unescape(i: String) -> anyhow::Result<String> {
    Ok(urlencoding::decode(&i)?.to_string())
}
\ No newline at end of file

A  => src/main.rs +161 -0
@@ 1,161 @@
use std::path::PathBuf;
use std::process::exit;
use dirs::config_dir;
use crate::cmds::createid::create_id;
use crate::db::db_default;
use crate::wrapper::wrapper;

mod identity;
mod db;
mod cmds;
mod wrapper;

fn main() -> anyhow::Result<()> {
    let mut pargs = pico_args::Arguments::from_env();

    if pargs.contains(["-h", "--help"]) {
        print_help();
        exit(0);
    }

    let database = pargs.opt_value_from_str(["-D", "--database"])?.unwrap_or(db_default());

    let Some(subcommand) = pargs.subcommand()? else {
        // run the wrapper
        return wrapper(pargs, &database);
    };

    match subcommand.as_str() {
        "create-id" => {
            let Ok(name) = pargs.free_from_str() else {
                eprintln!("name is required, --help for help");
                exit(1);
            };
            let Ok(email) = pargs.free_from_str() else {
                eprintln!("email is required, --help for help");
                exit(1);
            };

            create_id(&database, name, email)?;
        },
        "ls" => {
            cmds::ls::list_keys(
                &database,
                pargs.contains(["-l", "--local"]),
                pargs.contains(["-p", "--peer"]),
                pargs.contains(["-f", "--full"]),
            )?;
        },
        "show" => {
            let Ok(search) = pargs.free_from_str() else {
                eprintln!("search term is required, --help for help");
                exit(1);
            };
            cmds::show::show_key(
                &database,
                search,
                pargs.contains(["-e", "--expose-secret"])
            )?;
        },
        "import" => {
            let Ok(key) = pargs.free_from_str() else {
                eprintln!("key is required, --help for help");
                exit(1);
            };
            cmds::import::import_key(
                &database,
                key,
                pargs.contains(["-i", "--import-anyway"])
            )?;
        },
        unknown => {
            eprintln!("Unknown subcommand: {}", unknown);
            print_help();
            exit(1);
        }
    }

    Ok(())
}

fn print_help() {
    let cf = db_default().display().to_string();
    println!("{}", HELP.to_string().replace("{CONFIGFILE}", cf.as_str()));
}
const HELP: &str = "\
sage - simple age wrapper for named identities

USAGE:
    sage [OPTIONS] [SUBCOMMAND]
    sage [--encrypt] (-r RECIPIENT | -R PATH)... [--armor] [-o OUTPUT] [INPUT]
    sage [--encrypt] --passphrase [--armor] [-o OUTPUT] [INPUT]
    sage --decrypt [-i PATH]... [-o OUTPUT] [INPUT]

OPTIONS:
    -h, --help                  Prints help information
    -D, --database <FILE>       Set the database file (default {CONFIGFILE})

    -v, --verbose               Enable debug logging
    -e, --encrypt               Encrypt the input to the output. Default if omitted.
    -d, --decrypt               Decrypt the input to the output.
    -o, --output OUTPUT         Write the result to the file at path OUTPUT.
    -a, --armor                 Encrypt to a PEM encoded format.
    -p, --passphrase            Encrypt with a passphrase.
    -r, --recipient RECIPIENT   Encrypt to the specified RECIPIENT. Can be repeated.
    -R, --recipients-file PATH  Encrypt to the recipients listed at PATH. Can be repeated.
    -i, --identity IDENTITY     Use the specified identity. Can be repeated.

INPUT defaults to standard input, and OUTPUT defaults to standard output.
If OUTPUT exists, it will be overwritten.

RECIPIENT can be an age public key generate by age-keygen (\"age1...\")
an SSH public key (\"ssh-ed25519 AAAA...\", \"ssh-rsa AAAA...\"), OR
a fuzzy search term to use the database to locate a key.

Recipient files contain one or more recipients, one per line. Empty lines
and lines starting with \"#\" are ignored as comments. \"-\" may be uised to
read recipients from standard input.

An IDENTITY may either be a fuzzy search term to search the database, or
an identity file.

Identity files contain one or more secret keys (\"AGE-SECRET-KEY-1...\"),
one per line, or an SSH key. Empty lines and lines starting with \"#\" are
ignored as comments. Passphrase encrypted age files can be used as
identity files. Multiple key files can be provided, and any unused ones
will be ignored. \"-\" may be used to read identities from standard input.

When --encrypt is specified explicitly, -i can also be used to encrypt to an
identity file symmetrically, instead or in addition to normal recipients.

If an identity is omitted and only one local identity exists in the database,
it will be chosen automatically.

SUBCOMMANDS:

create-id <name> <email>
    Create a new local identity in the database.
    <name>      Name for the new identity
    <email>     Email of the new identity

ls [OPTIONS]
    List keys in the database
    OPTIONS:

    -l, --local     Show only local (your) keys
    -p, --peer      Show only peer (other's) keys
    -f, --full      List full public keys instead of key IDs

show [OPTIONS] <search term>
    Search the database (fuzzy) and show a specific key

    OPTIONS:
    -e, --expose-secret     Output the secret key, if it is a local key

import [OPTIONS] <key>
    Imports the provided key into the database.
    If the keyid already exists, it will do nothing.

    OPTIONS:
    -i, --insert-anyway     Insert even if a matching identity is already present
";
\ No newline at end of file

A  => src/wrapper.rs +189 -0
@@ 1,189 @@
use std::os::unix::prelude::CommandExt;
use std::path::{Path, PathBuf};
use std::process::{exit, Command, Stdio};
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;
use anyhow::bail;
use pico_args::Arguments;
use tempfile::NamedTempFile;
use crate::db::Database;
use crate::identity::IdKeyData;

#[derive(Debug)]
enum Mode {
    Encrypt,
    Decrypt
}

pub fn wrapper(mut pargs: Arguments, db_path: &Path) -> anyhow::Result<()> {
    let verbose = pargs.contains(["-v", "--verbose"]);
    let contains_encrypt = pargs.contains(["-e", "--encrypt"]);
    let contains_decrypt = pargs.contains(["-d", "--decrypt"]);

    if contains_encrypt && contains_decrypt {
        bail!("cannot specify both --encrypt and --decrypt, pick one");
    }

    let mode = if contains_decrypt { Mode::Decrypt } else { Mode::Encrypt };
    let output: Option<PathBuf> = pargs.opt_value_from_str(["-o", "--output"])?;
    let armor = pargs.contains(["-a", "--armor"]);
    let passphrase = pargs.contains(["-p", "--passphrase"]);

    let mut recipients = vec![];
    loop {
        let maybe_r: Option<String> = pargs.opt_value_from_str(["-r", "--recipient"])?;

        if let Some(r) = maybe_r {
            recipients.push(r);
        } else {
            break
        }
    }

    if let Ok(Some(f)) = pargs.opt_value_from_str::<[&str; 2], PathBuf>(["-R", "--recipients-file"]) {
        recipients.extend(load_keyfile(&f)?);
    }

    let mut identities = vec![];
    loop {
        let maybe_i: Option<String> = pargs.opt_value_from_str(["-i", "--identity"])?;

        if let Some(i) = maybe_i {
            if let Ok(path) = PathBuf::from_str(&i) && path.exists() {
                identities.extend(load_keyfile(&path)?);
            } else {
                identities.push(i);
            }
        } else {
            break
        }
    }

    let input: Option<PathBuf> = pargs.opt_free_from_str()?;

    if verbose {
        println!("resolved args: {:?} output={:?} armor?{:?} passphrase?{:?} recipients=>{:?} identities=>{:?} input={:?}", mode, output, armor, passphrase, recipients, identities, input);
    }

    // Resolve recipients and identities to the database
    let db = Database::load_or_create(db_path)?;

    // resolve recipients
    let resolved_recipients = recipients.iter()
        .map(|u| {
            // is this already a valid key?
            if u.starts_with("age1") {
                // regular ol' pubkey
                return u.clone()
            }
            // otherwise, resolve it in the database
            let results = db.fuzzy_search(u.clone());
            if results.is_empty() {
                eprintln!("no results for recipient {u}");
                exit(1);
            }
            if results.len() > 1 {
                eprintln!("recipient {u} is ambiguous, please narrow it down");
                exit(1);
            }
            let r = &results[0];

            if verbose {
                eprintln!("resolved rcpt {u} => {:?} (keyid {})", r, r.keys.keyid().unwrap());
            }

            r.keys.pk().unwrap()
        })
        .collect::<Vec<_>>();
    // resolve identities
    let resolved_identities = identities.iter()
        .map(|u| {
            // is this already a valid key?
            if u.starts_with("AGE-SECRET-KEY") {
                // regular ol' sk
                return u.clone()
            }
            // otherwise, resolve it in the database
            let results = db.fuzzy_search(u.clone());
            if results.is_empty() {
                eprintln!("no results for identity {u}");
                exit(1);
            }
            if results.len() > 1 {
                eprintln!("identity {u} is ambiguous, please narrow it down");
                exit(1);
            }
            let r = &results[0];

            if verbose {
                eprintln!("resolved id {u} => {:?} (keyid {})", r, r.keys.keyid().unwrap());
            }

            match &r.keys {
                IdKeyData::Local(sk) => sk.clone(),
                IdKeyData::Peer(_) => {
                    eprintln!("resolved identity is a peer key, not a local key. perhaps revise your search terms?");
                    exit(1);
                }
            }
        })
        .collect::<Vec<_>>();

    if verbose {
        eprintln!("resolved recipients: {:#?}", resolved_recipients);
    }

    // write recipients to a temp file
    let r_tmpfile = NamedTempFile::new()?;
    std::fs::write(r_tmpfile.path(), resolved_recipients.join("\n"))?;
    let i_tmpfile = NamedTempFile::new()?;
    std::fs::write(i_tmpfile.path(), resolved_identities.join("\n"))?;

    // execute age
    let mut args = vec![];
    args.push(match mode {
        Mode::Encrypt => "--encrypt",
        Mode::Decrypt => "--decrypt"
    }.to_string());
    if let Some(o) = output {
        args.push("--output".to_string());
        args.push(o.display().to_string());
    }
    if armor {
        args.push("--armor".to_string());
    }
    if passphrase {
        args.push("--passphrase".to_string());
    }
    if !resolved_recipients.is_empty() {
        args.push("--recipients-file".to_string());
        args.push(r_tmpfile.path().display().to_string());
    }
    if !resolved_identities.is_empty() {
        args.push("--identity".to_string());
        args.push(i_tmpfile.path().display().to_string());
    }
    if let Some(i) = input {
        args.push(i.display().to_string());
    }

    if verbose {
        eprintln!("exec age {:?}", args);
    }

    Err(Command::new("age")
        .args(args)
        .exec())?;
    Ok(())
}

fn load_keyfile(path: &Path) -> anyhow::Result<Vec<String>> {
    let contents = std::fs::read_to_string(path)?;
    Ok(contents
        .lines()
        .filter(|u| !u.is_empty())
        .filter(|u| !u.starts_with("#"))
        .map(|u| u.to_string())
        .collect())
}
\ No newline at end of file