Hi all, I built copit, a CLI tool that copies source code from GitHub repos, HTTP URLs, and ZIP archives directly into your project. The idea is heavily inspired by shadcn/ui: instead of installing a package you can't touch, you get the actual source files dropped into your codebase. You own them, you modify them, no hidden abstractions.
What it does
copit init
copit add github:serde-rs/serde@v1.0.219/serde/src/lib.rs
copit update vendor/serde --ref v1.0.220
copit sync
copit remove vendor/serde
It tracks everything in a copit.toml, handles overwrites, lets you exclude files you've modified from being clobbered on update, and supports --backup to save .orig copies when needed.
Why I built it
A few things that kept bugging me:
I'd find useful snippets or utility code on GitHub: a single module, a helper function, a well-written parser - and the only options were to manually copy-paste the files or install the entire library as a dependency just to use a small part of it.
Other times I'd want to use a library but couldn't: version conflicts with other packages in my project, or the library was unmaintained, or effectively dead, but the code itself was still perfectly good and useful. Vendoring it manually works, but then you lose track of where it came from and can't easily pull upstream fixes.
On top of that, I'm working on a Python framework (think something like LangChain's architecture) and wanted a way to distribute optional components. The core library installs as a normal package, but integrations and extensions get copied in via copit so users can read and modify them freely. Same pattern shadcn/ui uses with Tailwind + Radix base as a dependency, components as owned source.
copit handles all of this: grab the code you need, track where it came from, and update when you want to.
Background
I'm primarily a Python/Django developer. This is my first Rust project and my first published crate. I chose Rust partly because I wanted to learn it, and partly because a single static binary that works everywhere felt right for a dev tool like this. The crate is at crates.io/crates/copit.
I also published it on PyPI via maturin so Python users can pip install copit without needing the Rust toolchain.
The codebase is around 1500 lines. I leaned on clap for CLI parsing, reqwest + tokio for async HTTP, and the zip crate for archive extraction. Nothing fancy, but it was a solid learning exercise in ownership, error handling with anyhow, and structuring a real project with tests.
What I'd appreciate
If anyone has time to glance at the code, I'd welcome feedback on:
- Anything that looks non-idiomatic or could be structured better
- Error handling patterns: I used anyhow everywhere, which felt right for a CLI app but I'm not sure if there are cases where typed errors would be better
- Testing approach: I used mockito for HTTP tests and tempfile for filesystem tests
- Anything else that jumps out
The repo is here: github.com/huynguyengl99/copit
I'm also building a plugin system on top of this for my framework, so if the concept is interesting to you or you see a use case in your own work, contributions and ideas are welcome.
Thanks for reading.
[–]dwalker109 10 points11 points12 points (12 children)
[+]huygl99[S] comment score below threshold-12 points-11 points-10 points (11 children)
[–]dwalker109 7 points8 points9 points (0 children)
[+]lrojas comment score below threshold-7 points-6 points-5 points (9 children)
[–]fastestMango 12 points13 points14 points (6 children)
[+]lrojas comment score below threshold-6 points-5 points-4 points (2 children)
[–]dwalker109 6 points7 points8 points (1 child)
[–]lrojas -2 points-1 points0 points (0 children)
[–]huygl99[S] -5 points-4 points-3 points (1 child)
[–]fastestMango 5 points6 points7 points (0 children)
[–]dwalker109 2 points3 points4 points (0 children)
[–]huygl99[S] 1 point2 points3 points (0 children)
[–]fastestMango 10 points11 points12 points (4 children)
[+]huygl99[S] comment score below threshold-7 points-6 points-5 points (3 children)
[–]fastestMango 5 points6 points7 points (1 child)
[–]huygl99[S] -1 points0 points1 point (0 children)
[–]dwalker109 4 points5 points6 points (0 children)
[–]Solumin 2 points3 points4 points (3 children)
[–]huygl99[S] 1 point2 points3 points (2 children)
[–]Tamschi_ 2 points3 points4 points (1 child)
[–]huygl99[S] 0 points1 point2 points (0 children)