Radish alpha
r
rad:z39mP9rQAaGmERfUMPULfPUi473tY
Radicle terminal user interface
Radicle
Git
Use `radicle-cli`
Merged did:key:z6MkswQE...2C1V opened 2 years ago

This adds radicle-cli and removes the duplicated terminal module.

9 files changed +426 -370 d54f20f1 e76c69b5
modified Cargo.lock
@@ -65,6 +65,15 @@ dependencies = [
]

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

+
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -130,6 +139,35 @@ dependencies = [
]

[[package]]
+
name = "anstream"
+
version = "0.6.13"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
+
dependencies = [
+
 "anstyle",
+
 "anstyle-parse",
+
 "anstyle-query",
+
 "anstyle-wincon",
+
 "colorchoice",
+
 "utf8parse",
+
]
+

+
[[package]]
+
name = "anstyle"
+
version = "1.0.6"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
+

+
[[package]]
+
name = "anstyle-parse"
+
version = "0.2.3"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
+
dependencies = [
+
 "utf8parse",
+
]
+

+
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -139,6 +177,16 @@ dependencies = [
]

[[package]]
+
name = "anstyle-wincon"
+
version = "3.0.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
+
dependencies = [
+
 "anstyle",
+
 "windows-sys 0.52.0",
+
]
+

+
[[package]]
name = "anyhow"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -302,9 +350,9 @@ dependencies = [

[[package]]
name = "cc"
-
version = "1.0.81"
+
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "6c6b2562119bf28c3439f7f02db99faf0aa1a8cdfe5772a2ee155d32227239f0"
+
checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7"
dependencies = [
 "jobserver",
 "libc",
@@ -353,6 +401,22 @@ dependencies = [
]

[[package]]
+
name = "colorchoice"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
+

+
[[package]]
+
name = "colored"
+
version = "2.1.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
+
dependencies = [
+
 "lazy_static",
+
 "windows-sys 0.48.0",
+
]
+

+
[[package]]
name = "compact_str"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -530,6 +594,12 @@ dependencies = [
]

[[package]]
+
name = "diff"
+
version = "0.1.13"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
+

+
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -634,6 +704,18 @@ dependencies = [
]

[[package]]
+
name = "escargot"
+
version = "0.5.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4f474c6844cbd04e783d0f25757583db4f491770ca618bedf2fb01815fc79939"
+
dependencies = [
+
 "log",
+
 "once_cell",
+
 "serde",
+
 "serde_json",
+
]
+

+
[[package]]
name = "fastrand"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -673,9 +755,9 @@ dependencies = [

[[package]]
name = "form_urlencoded"
-
version = "1.2.0"
+
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
+
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
 "percent-encoding",
]
@@ -845,9 +927,9 @@ dependencies = [

[[package]]
name = "idna"
-
version = "0.4.0"
+
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
 "unicode-bidi",
 "unicode-normalization",
@@ -1127,6 +1209,12 @@ dependencies = [
]

[[package]]
+
name = "normalize-line-endings"
+
version = "0.3.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
+

+
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1313,9 +1401,9 @@ dependencies = [

[[package]]
name = "percent-encoding"
-
version = "2.3.0"
+
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
+
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"

[[package]]
name = "phf"
@@ -1404,6 +1492,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"

[[package]]
+
name = "pretty_assertions"
+
version = "1.4.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
+
dependencies = [
+
 "diff",
+
 "yansi",
+
]
+

+
[[package]]
name = "primeorder"
version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1446,6 +1544,15 @@ dependencies = [
]

[[package]]
+
name = "qcheck"
+
version = "1.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b439bd4242da51d62d18c95e6a6add749346756b0d1a587dfd0cc22fa6b5f3f0"
+
dependencies = [
+
 "rand",
+
]
+

+
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1462,6 +1569,8 @@ checksum = "030f82578c59d0f1d4c5ceaede44d5f8b9c8193628e55d28025d7b540057c50d"
dependencies = [
 "amplify",
 "base64 0.21.7",
+
 "chrono",
+
 "colored",
 "crossbeam-channel",
 "cyphernet",
 "fastrand",
@@ -1472,6 +1581,7 @@ dependencies = [
 "multibase",
 "nonempty 0.9.0",
 "once_cell",
+
 "qcheck",
 "radicle-cob",
 "radicle-crypto",
 "radicle-git-ext",
@@ -1486,6 +1596,63 @@ dependencies = [
]

[[package]]
+
name = "radicle-cli"
+
version = "0.9.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "89fbe33c5d79e748716bcce09df5220622ee205209a3dd81fe840a631df7a59b"
+
dependencies = [
+
 "anyhow",
+
 "chrono",
+
 "git-ref-format",
+
 "lexopt",
+
 "localtime",
+
 "log",
+
 "nonempty 0.9.0",
+
 "radicle",
+
 "radicle-cli-test",
+
 "radicle-cob",
+
 "radicle-crypto",
+
 "radicle-git-ext",
+
 "radicle-surf 0.19.0",
+
 "radicle-term",
+
 "serde",
+
 "serde_json",
+
 "shlex",
+
 "tempfile",
+
 "thiserror",
+
 "timeago",
+
 "tree-sitter",
+
 "tree-sitter-bash",
+
 "tree-sitter-c",
+
 "tree-sitter-css",
+
 "tree-sitter-go",
+
 "tree-sitter-highlight",
+
 "tree-sitter-html",
+
 "tree-sitter-json",
+
 "tree-sitter-md",
+
 "tree-sitter-python",
+
 "tree-sitter-rust",
+
 "tree-sitter-toml",
+
 "tree-sitter-typescript",
+
 "zeroize",
+
]
+

+
[[package]]
+
name = "radicle-cli-test"
+
version = "0.9.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ed1637aa46c99b5296103edf1ef30aead1b6281bd9206aee0bb0e9cc0c0f7707"
+
dependencies = [
+
 "escargot",
+
 "log",
+
 "pretty_assertions",
+
 "radicle",
+
 "shlex",
+
 "snapbox",
+
 "thiserror",
+
]
+

+
[[package]]
name = "radicle-cob"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1513,7 +1680,9 @@ dependencies = [
 "amplify",
 "cyphernet",
 "ec25519",
+
 "fastrand",
 "multibase",
+
 "qcheck",
 "radicle-git-ext",
 "radicle-ssh",
 "serde",
@@ -1583,6 +1752,25 @@ dependencies = [
]

[[package]]
+
name = "radicle-surf"
+
version = "0.19.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fade014fab67a7bdb816e4cf821611af5f0bc55c6eb1018561027ba6ba3b6b28"
+
dependencies = [
+
 "anyhow",
+
 "base64 0.13.1",
+
 "flate2",
+
 "git2",
+
 "log",
+
 "nonempty 0.5.0",
+
 "radicle-git-ext",
+
 "radicle-std-ext",
+
 "tar",
+
 "thiserror",
+
 "url",
+
]
+

+
[[package]]
name = "radicle-term"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1612,7 +1800,8 @@ dependencies = [
 "log",
 "nom",
 "radicle",
-
 "radicle-surf",
+
 "radicle-cli",
+
 "radicle-surf 0.18.0",
 "radicle-term",
 "ratatui",
 "serde",
@@ -1717,6 +1906,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"

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

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

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

+
[[package]]
name = "rfc6979"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1852,6 +2070,12 @@ dependencies = [
]

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

+
[[package]]
name = "signal-hook"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1887,6 +2111,12 @@ dependencies = [
]

[[package]]
+
name = "similar"
+
version = "2.5.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640"
+

+
[[package]]
name = "simple-logging"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1922,6 +2152,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"

[[package]]
+
name = "snapbox"
+
version = "0.4.17"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4b831b6e80fbcd2889efa75b185d24005f85981431495f995292b25836519d84"
+
dependencies = [
+
 "anstream",
+
 "anstyle",
+
 "normalize-line-endings",
+
 "similar",
+
 "snapbox-macros",
+
]
+

+
[[package]]
+
name = "snapbox-macros"
+
version = "0.3.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e1c4b838b05d15ab22754068cb73500b2f3b07bf09d310e15b27f88160f1de40"
+
dependencies = [
+
 "anstream",
+
]
+

+
[[package]]
name = "socket2"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2236,9 +2488,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"

[[package]]
name = "timeago"
-
version = "0.4.1"
+
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "5082dc942361cdfb74eab98bf995762d6015e5bb3a20bf7c5c71213778b4fcb4"
+
checksum = "a1710e589de0a76aaf295cd47a6699f6405737dbfd3cf2b75c92d000b548d0e6"
dependencies = [
 "chrono",
 "isolang",
@@ -2301,6 +2553,137 @@ dependencies = [
]

[[package]]
+
name = "tree-sitter"
+
version = "0.20.10"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e747b1f9b7b931ed39a548c1fae149101497de3c1fc8d9e18c62c1a66c683d3d"
+
dependencies = [
+
 "cc",
+
 "regex",
+
]
+

+
[[package]]
+
name = "tree-sitter-bash"
+
version = "0.20.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "57da2032c37eb2ce29fd18df7d3b94355fec8d6d854d8f80934955df542b5906"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-c"
+
version = "0.20.8"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "4bbd5f3d8658c08581f8f2adac6c391c2e9fa00fe9246bf6c5f52213b9cc6b72"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-css"
+
version = "0.20.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c3306ddefa1d2681adda2613d11974ffabfbeb215e23235da6c862f3493a04fd"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-go"
+
version = "0.20.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "1ad6d11f19441b961af2fda7f12f5d0dac325f6d6de83836a1d3750018cc5114"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-highlight"
+
version = "0.20.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "042342584c5a7a0b833d9fc4e2bdab3f9868ddc6c4b339a1e01451c6720868bc"
+
dependencies = [
+
 "regex",
+
 "thiserror",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-html"
+
version = "0.20.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "017822b6bd42843c4bd67fabb834f61ce23254e866282dd93871350fd6b7fa1d"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-json"
+
version = "0.20.2"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "5a9a38a9c679b55cc8d17350381ec08d69fa1a17a53fcf197f344516e485ed4d"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-md"
+
version = "0.1.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "3c20d3ef8d202430b644a307e6299d84bf8ed87fa1b796e4638f8805a595060c"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-python"
+
version = "0.20.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "e6c93b1b1fbd0d399db3445f51fd3058e43d0b4dcff62ddbdb46e66550978aa5"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-rust"
+
version = "0.20.4"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b0832309b0b2b6d33760ce5c0e818cb47e1d72b468516bfe4134408926fa7594"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-toml"
+
version = "0.20.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "ca517f578a98b23d20780247cc2688407fa81effad5b627a5a364ec3339b53e8"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
+
name = "tree-sitter-typescript"
+
version = "0.20.5"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "c8bc1d2c24276a48ef097a71b56888ac9db63717e8f8d0b324668a27fd619670"
+
dependencies = [
+
 "cc",
+
 "tree-sitter",
+
]
+

+
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2366,9 +2749,9 @@ dependencies = [

[[package]]
name = "url"
-
version = "2.4.0"
+
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
+
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
 "form_urlencoded",
 "idna",
@@ -2376,6 +2759,12 @@ dependencies = [
]

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

+
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2626,6 +3015,12 @@ dependencies = [
]

[[package]]
+
name = "yansi"
+
version = "0.5.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
+

+
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified Cargo.toml
@@ -19,7 +19,8 @@ libc = { version = "^0.2" }
log = { version = "0.4.19" }
nom = { version = "^7.1.0" }
radicle = { version = "0.9.0" }
-
radicle-term = { version = "0.9.0", package = "radicle-term" }
+
radicle-term = { version = "0.9.0" }
+
radicle-cli = { version = "0.9.0" }
radicle-surf = { version = "0.18.0" }
ratatui = { version = "0.26.1", default-features = false, features = ["all-widgets", "termion"] }
simple-logging = { version = "2.0.2" }
modified bin/commands/inbox.rs
@@ -11,8 +11,8 @@ use radicle_tui as tui;

use tui::cob::inbox::{self};

-
use crate::terminal;
-
use crate::terminal::args::{Args, Error, Help};
+
use radicle_cli::terminal;
+
use radicle_cli::terminal::{Args, Error, Help};

use self::common::{Mode, RepositoryMode, SelectionMode};

@@ -139,7 +139,7 @@ impl Args for Options {
}

#[tokio::main]
-
pub async fn run(options: Options, _ctx: impl terminal::Context) -> anyhow::Result<()> {
+
pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Result<()> {
    use radicle::storage::ReadStorage;
    use tui::log;

@@ -148,7 +148,7 @@ pub async fn run(options: Options, _ctx: impl terminal::Context) -> anyhow::Resu

    match options.op {
        Operation::Select { opts } => {
-
            let profile = terminal::profile()?;
+
            let profile = ctx.profile()?;
            let repository = profile.storage.repository(rid).unwrap();

            log::enable(&profile, "inbox", "select")?;
modified bin/commands/issue.rs
@@ -7,17 +7,17 @@ use std::ffi::OsString;

use anyhow::anyhow;

+
use radicle::identity::RepoId;
use radicle::issue;

-
use radicle::identity::RepoId;
+
use radicle_cli::terminal;
+
use radicle_cli::terminal::{Args, Error, Help};
+

use radicle_tui as tui;

use tui::cob;
use tui::log;

-
use crate::terminal;
-
use crate::terminal::args::{Args, Error, Help};
-

pub const HELP: Help = Help {
    name: "issue",
    description: "Terminal interfaces for issues",
@@ -136,7 +136,7 @@ impl Args for Options {
}

#[tokio::main]
-
pub async fn run(options: Options, _ctx: impl terminal::Context) -> anyhow::Result<()> {
+
pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Result<()> {
    use radicle::storage::ReadStorage;

    let (_, rid) = radicle::rad::cwd()
@@ -144,7 +144,7 @@ pub async fn run(options: Options, _ctx: impl terminal::Context) -> anyhow::Resu

    match options.op {
        Operation::Select { opts } => {
-
            let profile = terminal::profile()?;
+
            let profile = ctx.profile()?;
            let rid = options.repo.unwrap_or(rid);
            let repository = profile.storage.repository(rid).unwrap();

modified bin/commands/patch.rs
@@ -14,8 +14,8 @@ use radicle_tui as tui;
use tui::cob::patch::{self, Filter};
use tui::log;

-
use crate::terminal;
-
use crate::terminal::args::{Args, Error, Help};
+
use radicle_cli::terminal;
+
use radicle_cli::terminal::args::{Args, Error, Help};

pub const HELP: Help = Help {
    name: "patch",
@@ -145,7 +145,7 @@ impl Args for Options {
}

#[tokio::main]
-
pub async fn run(options: Options, _ctx: impl terminal::Context) -> anyhow::Result<()> {
+
pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Result<()> {
    use radicle::storage::ReadStorage;

    let (_, rid) = radicle::rad::cwd()
@@ -153,7 +153,7 @@ pub async fn run(options: Options, _ctx: impl terminal::Context) -> anyhow::Resu

    match options.op {
        Operation::Select { opts } => {
-
            let profile = terminal::profile()?;
+
            let profile = ctx.profile()?;
            let rid = options.repo.unwrap_or(rid);
            let repository = profile.storage.repository(rid).unwrap();

modified bin/main.rs
@@ -1,5 +1,4 @@
mod commands;
-
mod terminal;

use std::ffi::OsString;
use std::io;
@@ -9,6 +8,8 @@ use std::{iter, process};
use anyhow::anyhow;

use radicle::version::Version;
+

+
use radicle_cli::terminal;
use radicle_term as term;

use commands::*;
deleted bin/terminal.rs
@@ -1,143 +0,0 @@
-
pub mod args;
-
pub mod io;
-

-
use std::ffi::OsString;
-
use std::process;
-

-
pub use args::{Args, Error, Help};
-

-
use radicle_term as term;
-

-
use radicle::profile::{Home, Profile};
-

-
/// Context passed to all commands.
-
pub trait Context {
-
    /// Return the currently active profile, or an error if no profile is active.
-
    fn profile(&self) -> Result<Profile, anyhow::Error>;
-
    /// Return the Radicle home.
-
    fn home(&self) -> Result<Home, std::io::Error>;
-
}
-

-
impl Context for Profile {
-
    fn profile(&self) -> Result<Profile, anyhow::Error> {
-
        Ok(self.clone())
-
    }
-

-
    fn home(&self) -> Result<Home, std::io::Error> {
-
        Ok(self.home.clone())
-
    }
-
}
-

-
pub struct DefaultContext;
-

-
impl Context for DefaultContext {
-
    fn home(&self) -> Result<Home, std::io::Error> {
-
        radicle::profile::home()
-
    }
-

-
    fn profile(&self) -> Result<Profile, anyhow::Error> {
-
        match Profile::load() {
-
            Ok(profile) => Ok(profile),
-
            Err(radicle::profile::Error::NotFound(path)) => Err(args::Error::WithHint {
-
                err: anyhow::anyhow!("Radicle profile not found in '{}'.", path.display()),
-
                hint: "To setup your radicle profile, run `rad auth`.",
-
            }
-
            .into()),
-
            Err(radicle::profile::Error::Config(e)) => Err(e.into()),
-
            Err(e) => Err(anyhow::anyhow!("Could not load radicle profile: {e}")),
-
        }
-
    }
-
}
-

-
/// A command that can be run.
-
pub trait Command<A: Args, C: Context> {
-
    /// Run the command, given arguments and a context.
-
    fn run(self, args: A, context: C) -> anyhow::Result<()>;
-
}
-

-
impl<F, A: Args, C: Context> Command<A, C> for F
-
where
-
    F: FnOnce(A, C) -> anyhow::Result<()>,
-
{
-
    fn run(self, args: A, context: C) -> anyhow::Result<()> {
-
        self(args, context)
-
    }
-
}
-

-
pub fn run_command_args<A, C>(help: Help, cmd: C, args: Vec<OsString>) -> !
-
where
-
    A: Args,
-
    C: Command<A, DefaultContext>,
-
{
-
    let options = match A::from_args(args) {
-
        Ok((opts, unparsed)) => {
-
            if let Err(err) = args::finish(unparsed) {
-
                term::error(err);
-
                process::exit(1);
-
            }
-
            opts
-
        }
-
        Err(err) => {
-
            let hint = match err.downcast_ref::<Error>() {
-
                Some(Error::Help) => {
-
                    term::help(help.name, help.version, help.description, help.usage);
-
                    process::exit(0);
-
                }
-
                Some(Error::HelpManual { name }) => {
-
                    let Ok(status) = term::manual(name) else {
-
                        term::error(format!("rad-tui {}: failed to load manual page", help.name));
-
                        process::exit(1);
-
                    };
-
                    process::exit(status.code().unwrap_or(0));
-
                }
-
                Some(Error::Usage) => {
-
                    term::usage(help.name, help.usage);
-
                    process::exit(1);
-
                }
-
                Some(Error::WithHint { hint, .. }) => Some(hint),
-
                None => None,
-
            };
-
            term::error(format!("rad-tui {}: {err}", help.name));
-

-
            if let Some(hint) = hint {
-
                term::hint(hint);
-
            }
-
            process::exit(1);
-
        }
-
    };
-

-
    match cmd.run(options, DefaultContext) {
-
        Ok(()) => process::exit(0),
-
        Err(err) => {
-
            fail(help.name, &err);
-
            process::exit(1);
-
        }
-
    }
-
}
-

-
/// Get the default profile. Fails if there is no profile.
-
pub fn profile() -> Result<Profile, anyhow::Error> {
-
    match Profile::load() {
-
        Ok(profile) => Ok(profile),
-
        Err(radicle::profile::Error::NotFound(path)) => Err(args::Error::WithHint {
-
            err: anyhow::anyhow!("Radicle profile not found in '{}'.", path.display()),
-
            hint: "To setup your radicle profile, run `rad auth`.",
-
        }
-
        .into()),
-
        Err(radicle::profile::Error::Config(e)) => Err(e.into()),
-
        Err(e) => Err(anyhow::anyhow!("Could not load radicle profile: {e}")),
-
    }
-
}
-

-
pub fn fail(_name: &str, error: &anyhow::Error) {
-
    let err = error.to_string();
-
    let err = err.trim_end();
-

-
    for line in err.lines() {
-
        term::error(line);
-
    }
-

-
    if let Some(Error::WithHint { hint, .. }) = error.downcast_ref::<Error>() {
-
        term::hint(hint);
-
    }
-
}
deleted bin/terminal/args.rs
@@ -1,134 +0,0 @@
-
use std::ffi::OsString;
-
use std::str::FromStr;
-

-
use anyhow::anyhow;
-

-
use radicle::cob::{issue, patch};
-
use radicle::crypto;
-
use radicle::identity::{Did, RepoId};
-

-
/// Git revision parameter. Supports     extended SHA-1 syntax.
-
#[derive(Debug, Clone, PartialEq, Eq)]
-
pub struct Rev(String);
-

-
impl From<String> for Rev {
-
    fn from(value: String) -> Self {
-
        Rev(value)
-
    }
-
}
-

-
#[derive(thiserror::Error, Debug)]
-
#[allow(dead_code)]
-
pub enum Error {
-
    /// If this error is returned from argument parsing, help is displayed.
-
    #[error("help invoked")]
-
    Help,
-
    /// If this error is returned from argument parsing, the manual page is displayed.
-
    #[error("help manual invoked")]
-
    HelpManual { name: &'static str },
-
    /// If this error is returned from argument parsing, usage is displayed.
-
    #[error("usage invoked")]
-
    Usage,
-
    /// An error with a hint.
-
    #[error("{err}")]
-
    WithHint {
-
        err: anyhow::Error,
-
        hint: &'static str,
-
    },
-
}
-

-
pub struct Help {
-
    pub name: &'static str,
-
    pub description: &'static str,
-
    pub version: &'static str,
-
    pub usage: &'static str,
-
}
-

-
pub trait Args: Sized {
-
    fn from_env() -> anyhow::Result<Self> {
-
        let args: Vec<_> = std::env::args_os().skip(1).collect();
-

-
        match Self::from_args(args) {
-
            Ok((opts, unparsed)) => {
-
                self::finish(unparsed)?;
-

-
                Ok(opts)
-
            }
-
            Err(err) => Err(err),
-
        }
-
    }
-

-
    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)>;
-
}
-

-
#[allow(dead_code)]
-
pub fn parse_value<T: FromStr>(flag: &str, value: OsString) -> anyhow::Result<T>
-
where
-
    <T as FromStr>::Err: std::error::Error,
-
{
-
    value
-
        .into_string()
-
        .map_err(|_| anyhow!("the value specified for '--{}' is not valid UTF-8", flag))?
-
        .parse()
-
        .map_err(|e| anyhow!("invalid value specified for '--{}' ({})", flag, e))
-
}
-

-
#[allow(dead_code)]
-
pub fn format(arg: lexopt::Arg) -> OsString {
-
    match arg {
-
        lexopt::Arg::Long(flag) => format!("--{flag}").into(),
-
        lexopt::Arg::Short(flag) => format!("-{flag}").into(),
-
        lexopt::Arg::Value(val) => val,
-
    }
-
}
-

-
#[allow(dead_code)]
-
pub fn finish(unparsed: Vec<OsString>) -> anyhow::Result<()> {
-
    if let Some(arg) = unparsed.first() {
-
        return Err(anyhow::anyhow!(
-
            "unexpected argument `{}`",
-
            arg.to_string_lossy()
-
        ));
-
    }
-
    Ok(())
-
}
-

-
pub fn string(val: &OsString) -> String {
-
    val.to_string_lossy().to_string()
-
}
-

-
#[allow(dead_code)]
-
pub fn rev(val: &OsString) -> anyhow::Result<Rev> {
-
    let s = val.to_str().ok_or(anyhow!("invalid git rev {val:?}"))?;
-
    Ok(Rev::from(s.to_owned()))
-
}
-

-
#[allow(dead_code)]
-
pub fn did(val: &OsString) -> anyhow::Result<Did> {
-
    let val = val.to_string_lossy();
-
    let Ok(peer) = Did::from_str(&val) else {
-
        if crypto::PublicKey::from_str(&val).is_ok() {
-
            return Err(anyhow!("expected DID, did you mean 'did:key:{val}'?"));
-
        } else {
-
            return Err(anyhow!("invalid DID '{}', expected 'did:key'", val));
-
        }
-
    };
-
    Ok(peer)
-
}
-

-
pub fn rid(val: &OsString) -> anyhow::Result<RepoId> {
-
    let val = val.to_string_lossy();
-
    RepoId::from_str(&val).map_err(|_| anyhow!("invalid Repository ID '{}'", val))
-
}
-

-
#[allow(dead_code)]
-
pub fn issue(val: &OsString) -> anyhow::Result<issue::IssueId> {
-
    let val = val.to_string_lossy();
-
    issue::IssueId::from_str(&val).map_err(|_| anyhow!("invalid Issue ID '{}'", val))
-
}
-

-
#[allow(dead_code)]
-
pub fn patch(val: &OsString) -> anyhow::Result<patch::PatchId> {
-
    let val = val.to_string_lossy();
-
    patch::PatchId::from_str(&val).map_err(|_| anyhow!("invalid Patch ID '{}'", val))
-
}
deleted bin/terminal/io.rs
@@ -1,64 +0,0 @@
-
use radicle::crypto::ssh::keystore::MemorySigner;
-
use radicle::crypto::{ssh::Keystore, Signer};
-
use radicle::profile::env::RAD_PASSPHRASE;
-
use radicle::profile::Profile;
-

-
use radicle_term::io::*;
-
use radicle_term::spinner;
-

-
use inquire::validator;
-

-
/// Validates secret key passphrases.
-
#[derive(Clone)]
-
pub struct PassphraseValidator {
-
    keystore: Keystore,
-
}
-

-
#[allow(dead_code)]
-
impl PassphraseValidator {
-
    /// Create a new validator.
-
    pub fn new(keystore: Keystore) -> Self {
-
        Self { keystore }
-
    }
-
}
-

-
impl inquire::validator::StringValidator for PassphraseValidator {
-
    fn validate(
-
        &self,
-
        input: &str,
-
    ) -> Result<validator::Validation, inquire::error::CustomUserError> {
-
        let passphrase = Passphrase::from(input.to_owned());
-
        if self.keystore.is_valid_passphrase(&passphrase)? {
-
            Ok(validator::Validation::Valid)
-
        } else {
-
            Ok(validator::Validation::Invalid(
-
                validator::ErrorMessage::from("Invalid passphrase, please try again"),
-
            ))
-
        }
-
    }
-
}
-

-
/// Get the signer. First we try getting it from ssh-agent, otherwise we prompt the user,
-
/// if we're connected to a TTY.
-
#[allow(dead_code)]
-
pub fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> {
-
    if let Ok(signer) = profile.signer() {
-
        return Ok(signer);
-
    }
-
    let validator = PassphraseValidator::new(profile.keystore.clone());
-
    let passphrase = match passphrase(validator) {
-
        Ok(p) => p,
-
        Err(inquire::InquireError::NotTTY) => {
-
            return Err(anyhow::anyhow!(
-
                "running in non-interactive mode, please set `{RAD_PASSPHRASE}` to unseal your key",
-
            ));
-
        }
-
        Err(e) => return Err(e.into()),
-
    };
-
    let spinner = spinner("Unsealing key...");
-
    let signer = MemorySigner::load(&profile.keystore, Some(passphrase))?;
-

-
    spinner.finish();
-

-
    Ok(signer.boxed())
-
}