9 ms·
Show HN: Crust – A CLI framework for TypeScript and Bun
We've been building Crust (https://crustjs.com/ https://crustjs.com/), a TypeScript-first, Bun-native CLI framework with zero dependencies. It's been powering our core product internally for a while, and we're now open-sourcing it.
The problem we kept running into: existing CLI frameworks in the JS ecosystem are either minimal arg parsers where you wire everything yourself, or heavyweight frameworks with large dependency trees and Node-era assumptions. We wanted something in between.
What Crust does differently:
- Full type inference from definitions — args and flags are inferred automatically. No manual type annotations, no generics to wrangle. You define a flag as type: "string" and it flows through to your handler.
- Compile-time validation — catches flag alias collisions and variadic arg mistakes before your code runs, not at runtime.
- Zero runtime dependencies — @crustjs/core is ~3.6kB gzipped (21kB install). For comparison: yargs is 509kB, oclif is 411kB.
- Composable modules — core, plugins, prompts, styling, validation, and build tooling are all separate packages. Install only what you need.
- Plugin system — middleware-based with lifecycle hooks (preRun/postRun). Official plugins for help, version, and shell autocompletion.
- Built for Bun — no Node compatibility layers, no legacy baggage.
Quick example:
import { Crust } from "@crustjs/core";
import { helpPlugin, versionPlugin } from "@crustjs/plugins";
const main = new Crust("greet")
.args([{ name: "name", type: "string", default: "world" }])
.flags({ shout: { type: "boolean", short: "s" } })
.use(helpPlugin())
.use(versionPlugin("1.0.0"))
.run(({ args, flags }) => {
const msg = `Hello, ${args.name}!`;
console.log(flags.shout ? msg.toUpperCase() : msg);
});
await main.execute();
Scaffold a new project:
bun create crust my-cli
Site: https://crustjs.com https://crustjs.com
GitHub: https://github.com/chenxin-yan/crust https://github.com/chenxin-yan/crust
Happy to answer any questions about the design decisions or internals.
- landl0rd 7mo agoIs there an examples section? Would be helpful to see a demo
- jellyotsiro 7mo agoone of the examples would be trynia.ai (search and index api for ai agents) here is github: github.com/nozomio-labs/nia-cli
- matt_kantor 7mo ago> Versions before 1.0 do not strictly follow semantic versioning. Sorry for being nitpicky, but yes they do. Semantic versioning[0] allows arbitrary changes while the major version is 0: > Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable. [0]: https://semver.org/ https://semver.org/
- jellyotsiro 7mo agothanks for the catch, what we meant is that we’re not committing to strict stability guarantees yet, so APIs may still change as we iterate toward 1.0.
- matt_kantor 7mo agoI understand, but that's already implied by a 0.y.z version number.
- olivia-banks 7mo agoI will say that this doesn't seem to be how semver is used in the wild, which I would argue is more important. I personally didn't know about this rule. Tons of Rust projects follow semver don't follow it either, and just stay on 0.x.y forever.
- matt_kantor 7mo agoHow is it used in the wild, in your experience? Have you observed projects following some alternate set of rules? I thought projects that stay on 0.x.y forever mostly do it because it means they're allowed to break things. Also, since 0.x.y means "anything goes", projects can introduce their own conventions within that range without violating the spec. I know that some package managers (including Cargo and npm) confusingly treat 0.1.0 → 0.1.1 like a "minor" update, despite the spec. Is this what you're referring to?
- 7mo ago
- bennettpompi1 7mo agothis is cool! i'd recommend fleshing out the README. Clicked on the link before the discussion and was a tad confused.
- jellyotsiro 7mo agowill fix in the next hour!
- chenxin-yan 7mo agoHi, I will update the README.md to make it more informative. The reason I left it kinda empty is because curst has a website that have more details about this project at crustjs.com. I just had the link in README.md for now
- dnlzro 7mo agoPsst, the GitHub link in your post is broken (it should be https://github.com/chenxin-yan/crust https://github.com/chenxin-yan/crust).
- deleted 7mo ago[deleted]
- jellyotsiro 7mo agothanks for flagging! the post itself works, just the link at the bottom
- dang 7mo agoFixed above. Thanks for the heads-up!
- camkego 7mo agoThis looks useful. But, it's interesting how the backend-world and front-end world keep diverging. I must admit, I had no idea what this was from the title. "CLI framework"? But in backend-land, these would typically be called "argument parsers" or "command line argument parsers". But maybe I am missing some of the functionality.
- jellyotsiro 7mo agogood point. we’re using “framework” intentionally because it goes beyond argument parsing. crust handles parsing, but also: type inference across args + flags end to end compile-time validation (so mistakes fail before runtime) plugin system with lifecycle hooks (help, version, autocomplete, etc.) composable modules (prompts, styling, validation, build tooling) auto-generates agent skills and modules from the CLI definitions so it sits a layer above a traditional arg parser like yargs or commander, closer to something like oclif, but much lighter and bun-native.
- embedding-shape 7mo agoBoth in the frontend and the backend, I've usually used "If it calls your code, it's a framework, if you call its code, it's a library", and would seem to fit here too. An argument parser you'd call from your main method, then do stuff with what it returns. In Crust, it seems you instead setup the command + what will happen when it's called, then let the framework call your code.
- codybontecou 7mo agoThat’s… actually a great definition. I’m going to try to retain that.
- jazzypants 7mo agoThis is classically called "Inversion of Control"[0] or the "Hollywood Principle"[1] as in "Don't call us-- we'll call you". [0] - https://martinfowler.com/bliki/InversionOfControl.html https://martinfowler.com/bliki/InversionOfControl.html [1] - https://wiki.c2.com/?HollywoodPrinciple https://wiki.c2.com/?HollywoodPrinciple
- rgbrgb 7mo agonice, congrats on launch. To get an idea... what's the size of a standalone hello world cli binary?
- jellyotsiro 7mo agotens of KBs (v small)
- rgbrgb 7mo agoIsn't a standalone Bun binary like 50MB because it has to bundle the runtime? How could this get smaller?
- chenxin-yan 7mo agoHi, the creator of crust here, the binary size varies between platforms. With the hello world cli, the smallest, on darwin-arm64 it is 58.1M, the largest on windows-x64 is 109M. hope this helps!
- leontloveless 7mo ago[dead]
- nullstyle 7mo agoI’ve been using the jsr:@cliffy/* packages from deno to solve the same problem.
- bpev 7mo agoyup yup same. That one's worked well for me. Between that and the deno std, it's nice to have it feel like mostly everything you need is available with very little searching.
- qyron 7mo agoAny plans to support Node.js? Also some comparison (at least design choices) with existing frameworks would be nice.
- chenxin-yan 7mo agoTechnically most modules would work with node.js as they are not using any bun specific APIs. The reason for crust to be all in bun is because bun can compile your cli into binaries for distribution which powers curst build. The idea is that since you will have bun bundled with your CLI to end user. the developer does have to worry about the end users don't have bun installed and use Bun api freely when building.
- lozf 7mo agoalso: no "C", and no "rust", despite being a portmanteau of both these other languages names :)
- optivly 7mo ago[dead]
- GutenYe 7mo agoLooks cool. If anyone is interested in a simple option with autocomplete working out of the box and no extra bells and whistles, feel free to check out my project: https://github.com/gutenye/script.js https://github.com/gutenye/script.js
- hdjrudni 7mo agoI was looking for something like this. Does the help plugin not support color? Looks like the spacing is messed up too. I just converted my app to use it and its coming out like ``` COMMANDS: export-schemaExport table definitions from existing database to YAML export-dataExport table data in CSV format import-dataImport table data from CSV file schema-sqlConvert YAML schema back to MySQL export-usersExport users in YAML format users-sql Convert users.yaml back into SQL export-allExport all data from host export-all-tgzExport all data from host databases-sqlConvert databases.yaml back into SQL export-typescriptExport TypeScript interfaces ``` i.e. there's no space after `export-schema` it just goes immediately to the description.
- chenxin-yan 7mo agoI will add color support for help plugin later. open a issue if you found bugs or improvement
- kaihong_deng 7mo ago[dead]
- todteera 7mo ago[flagged]
- derodero24 7mo ago[flagged]
- babarot 7mo agoThe binary size is my main hesitation with Bun-based CLIs too. For comparison, a typical Go CLI with cobra compiles to ~10MB statically linked, and you get cross-compilation for free. The plugin system and compile-time validation here are genuinely nice though — Go's CLI ecosystem doesn't have anything equivalent to that.
- derodero24 7mo ago[flagged]
- deleted 7mo ago[deleted]
- rrojas-nexus 7mo ago[dead]
- iktakahiro 7mo agoAs a Bun user, this caught my eye. Gonna give it a try!
- matheuspoleza 6mo agonice work. TS + Bun for CLI is the right bet — the DX is so much better than the traditional node + commander setup. curious about your approach to type-safe argument parsing. we've been exploring similar patterns and found that deriving types from the schema definition (instead of manually typing args) eliminates a whole class of bugs.