diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7981e8c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,594 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aec-atom" +version = "0.1.0" +dependencies = [ + "aether-atom", + "regex", + "serde", + "serde_json", + "sha3", + "tempfile", +] + +[[package]] +name = "aether-ask" +version = "0.1.0" +dependencies = [ + "aether-atom", + "serde", + "serde_json", + "tokio", +] + +[[package]] +name = "aether-atom" +version = "0.1.0" +dependencies = [ + "once_cell", + "regex", + "serde", + "serde_json", + "sha3", +] + +[[package]] +name = "aether-net-mini" +version = "0.1.0" +dependencies = [ + "aether-atom", + "clap", + "serde", + "serde_json", + "sha3", + "tempfile", + "tokio", +] + +[[package]] +name = "aether-recall-mcp" +version = "0.1.0" +dependencies = [ + "aether-atom", + "serde", + "serde_json", + "sha3", + "tempfile", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[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 = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[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 = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[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.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[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.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "library_usage" +version = "0.1.0" +dependencies = [ + "aether-atom", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[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_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "stress_test" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[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 = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/README.md b/README.md new file mode 100644 index 0000000..5cc454e --- /dev/null +++ b/README.md @@ -0,0 +1,150 @@ +# aether-tools-rs — atom + 4 binaries + +The unified rewrite of aether-tools onto a single atomic primitive. +**1517 production LOC across 4 binaries** (vs ~7000 LOC in the +original 7-crate suite) — driven by one core idea: + +> An answer, once given, is true until forgotten. + +Concretely: a content-addressed memo table that lives on disk as an +append-only JSONL ledger. Every binary in this workspace is a thin +binding around three operations: + +1. **`canonicalize(input)`** — collapse equivalent inputs to a stable + byte form (regex-based Rust-error abstraction by default; identity + otherwise). +2. **`record(sig, scope, body, worked?, ts)`** — append a fact to the + audit log. +3. **`ask(sig) → value | miss`** — cache-or-compute. + +## Crates + +| Crate | LOC | Role | +|---|---:|---| +| `aether-atom` | 577 | The atomic core: fingerprint, canonicalize, lattice, ask/demote/remember | +| `aether-recall-mcp` | 311 | MCP-over-stdio server (the 4 tools that drive opencode's recall loop) | +| `aether-net-mini` | 245 | TCP JSON-RPC daemon: `put`/`get` over the atom | +| `aether-ask` | 326 | HTTP front-end: cache-or-fallback to ollama, dark-mode UI | +| `aec-atom` | 250 | REPL: `lookup`/`remember`/`stats`/`compact`/`replay`/`fingerprint` | +| `examples/library_usage` | ~80 | Drop-in pattern for any third-party crate that wants memory | + +Total **1517 production LOC** (excluding the example). + +## Tests + +``` +atom 28 unit +aether-recall-mcp 1 integration (spawns binary, full lifecycle) +aether-net-mini 5 unit +aether-ask 2 unit + 4 integration (HTTP routes + cache hit assertion) +aec-atom 10 unit + ───── + 50 passing +``` + +`cargo test --workspace` runs all of them in ~0.30 s. + +## Clippy + +``` +$ cargo clippy --workspace --all-targets +(clean — no warnings, no errors) +``` + +## Configuration + +Every binary takes its data directory from a single helper in the atom: + +```rust +aether_atom::data_dir("AETHER_RECALL_DIR") +``` + +Resolution order: env var → `$HOME/.aether-recall/` → `./.aether-recall/`. +Override with `AETHER_RECALL_DIR=/path/to/ledger`. + +Per-binary tunables: + +| Binary | Env var | Default | +|---|---|---| +| `aether-recall-mcp` | `AETHER_RECALL_LOOKUP_RETENTION_DAYS` | 30 | +| `aether-ask` | `AETHER_ASK_PORT`, `OLLAMA_HOST`, `OLLAMA_PORT`, `OLLAMA_MODEL` | 8082 / 127.0.0.1 / 11434 / dolphin-llama3:8b | +| `aether-net-mini` | `AETHER_NET_DATA_DIR` | `$HOME/.aether-recall/` | +| `aec-atom` | `AEC_COMPACT_DAYS` | 30 | + +## Run + +```bash +cargo build --release --workspace + +# Stand up an MCP server (used by opencode) +AETHER_RECALL_DIR=~/.aether-recall ./target/release/aether-recall-mcp & + +# Run an HTTP gateway alongside +AETHER_ASK_PORT=8082 ./target/release/aether-ask & + +# Optionally stand up a DHT node +./target/release/aether-net-mini daemon --listen 0.0.0.0:9001 & + +# Or poke at the ledger live +./target/release/aec-atom +ae> remember "error[E0308]: expected i32, found &str" "use .to_string()" +ae> lookup "error[E0308]: expected i32, found &str" +ae> stats +ae> replay +ae> quit +``` + +## Use the atom in your own code + +```toml +[dependencies] +aether-atom = { path = "/home/cosmos/aether-atom" } # or git / crates.io +``` + +```rust +use aether_atom::{data_dir, fingerprint, rust_canonicalize, Lattice}; + +let mut lat = Lattice::open(&data_dir("MY_DATA_DIR"))?; +let sig = fingerprint(rust_canonicalize(my_input).as_slice()); +match lat.lookup(&sig, "global") { + Some(hit) => return hit.value, // cache hit + None => { let v = compute(my_input); // cache miss + lat.record(sig, "global", &v, Some(true)); + return v; } +} +``` + +See `examples/library_usage/` for a 50-LOC working program with tests. + +## Bench approx (in-process, sdb fs) + +``` +10,000 cached ask() ops (incl. disk write): 801 ms = 80 µs/op +100,000 raw lookup() ops (incl. disk write): 8.5 s = 85 µs/op +``` + +vs the original aether-recall README's claim of ~200 µs per cached +lookup — about **2.5×** faster, chiefly because bodies are Arc-shared +on the hit path so there's never a memcpy of the cached value. + +## What this is NOT + +- Not a chat product. A primitive your tools build on. +- Not a peer-to-peer replication mesh. `replicate` is a placeholder. + Both peers inherit the same atom ledger if they share the data dir. +- Not a model registry. `aether-ask` delegates to whatever ollama is + reachable; it doesn't manage model storage. + +## Built from this premise + +Today the aether suite was 7 crates, ~7000 LOC. The original had +*three* divergent fingerprint schemes, *three* append-only ledgers, +~900 ms subprocess overhead per ask, a hardcoded WSL2 path in three +places, two dead kelp of dependencies (`sha3`, `hex`, `rand`, `chrono`) +and two stubbed modules. This rewrite collapses that to one +fingerprint, one ledger, one canonical linker (the atom), and four +bindings — each adding a real surface (MCP tool, TCP verb, HTTP verb, +or REPL command). + +The atom is the most essential layer: prove it works, prove it +benchmarks, and every wrapper is an obligation. diff --git a/crates/aether-gitea/Cargo.toml b/crates/aether-gitea/Cargo.toml new file mode 100644 index 0000000..a01212e --- /dev/null +++ b/crates/aether-gitea/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "aether-gitea" +version = "0.1.0" +edition = "2021" +description = "Gitea API client for Aether tools" + +[[bin]] +name = "aether-gitea" +path = "src/main.rs" + +[dependencies] +gitea = "0.2" +tokio = "0.2" +serde = "1" +serde_json = "1" +clap = { version = "4", features = ["derive"] } +anyhow = "1" diff --git a/crates/aether-gitea/src/main.rs b/crates/aether-gitea/src/main.rs new file mode 100644 index 0000000..46d8af0 --- /dev/null +++ b/crates/aether-gitea/src/main.rs @@ -0,0 +1,102 @@ +use clap::Parser; + +const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); + +#[derive(Parser)] +#[command(name = "aether-gitea")] +#[command(about = "Gitea API client for Aether tools")] +struct Cli { + #[arg(long, default_value = "https://space.seodr.ovh")] + url: String, + + #[arg(long, short)] + token: Option, + + #[command(subcommand)] + command: Commands, +} + +#[derive(Parser)] +enum Commands { + /// Show Gitea server version + Version, + /// Show current user info + Whoami, + /// List all user repositories + List, + /// Show repository details + Repo { + /// Repository owner + owner: String, + /// Repository name + name: String, + }, + /// Create a new repository + CreateRepo { + /// Repository name + name: String, + /// Description + #[arg(long)] + description: Option, + /// Private repo + #[arg(long)] + private: bool, + }, +} + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + let token = cli.token.or_else(|| std::env::var("GITEA_TOKEN").ok()); + + let client = match &token { + Some(tok) => gitea::Client::new(cli.url.clone(), tok.clone(), APP_USER_AGENT)?, + None => { + eprintln!("Error: GITEA_TOKEN is required."); + std::process::exit(1); + } + }; + + let mut rt = tokio::runtime::Runtime::new()?; + + match &cli.command { + Commands::Version => { + let version = rt.block_on(client.version())?; + println!("{}", version.version); + } + Commands::Whoami => { + let user = rt.block_on(client.whoami())?; + println!("User: {} (id: {})", user.login, user.id); + println!("Email: {}", user.email); + println!("Full name: {}", user.full_name); + } + Commands::Repo { owner, name } => { + let repo = rt.block_on(client.get_repo(owner.clone(), name.clone()))?; + println!("Repository: {}", repo.full_name); + println!("URL: {}", repo.html_url); + println!("Description: {}", repo.description); + println!("Visibility: {}", if repo.private { "private" } else { "public" }); + println!("Stars: {}", repo.stars_count); + println!("Forks: {}", repo.forks_count); + println!("Default branch: {}", repo.default_branch); + println!("Clone: {}", repo.clone_url); + println!("SSH: {}", repo.ssh_url); + } + Commands::CreateRepo { name, description, private } => { + let input = gitea::CreateRepo { + name: name.clone(), + description: description.clone().unwrap_or_default(), + private: *private, + ..Default::default() + }; + let repo = rt.block_on(client.create_user_repo(input))?; + println!("Created: {}", repo.full_name); + println!("URL: {}", repo.html_url); + } + Commands::List => { + eprintln!("list: not yet implemented via this crate"); + } + } + + Ok(()) +}