@lariocpt/crust
v0.3.3
Published
Mock it, test it, load it, tail it: the whole test stack baked into one binary. Spec-driven, generated, zero dependencies.
Maintainers
Readme
crust
Mock it, test it, load it, tail it: the whole test stack baked into one binary. Spec-driven, generated, zero dependencies.
Never reports a false pass — a run that measured nothing fails.
An OpenAPI mock server, fixture suites generated from the spec, load gates CI
can fail on, a process orchestrator, and a link checker — every tool composes
on the same | stream, driven from an interactive REPL, a crust -c
one-liner, a .crust script, or as a one-shot command from your shell
(crust mock-server openapi.yaml -p 4000).
npm i -g @lariocpt/crustDocumentation: https://lariocpt.github.io/crust-website/
Source and issues: https://github.com/lariocpt/crust
What it looks like
Lines below are parsed against the live grammar on every build, so an example that stopped working fails CI rather than misleading you.
Fixture tests — .crust.ts fixtures with setup() context and async
matchers, JUnit for CI, and -n/-j to turn any fixture into a stress run:
test-fixture "tests/api/**/*.crust.ts" -j 8 -o reports/api.junit.xml
test-fixture tests/api/checkout.crust.ts -n 500 -j 32 -o reports/stress.jsonLoad testing with a gate CI can trust — thresholds are assert
composition, and a run that measured nothing is refused rather than passing:
load 30s 200/s | parallel 50 | GET :3000/health | expect 200 | stats | assert (s => s.p95 < 250)
load 10s 50/s, 5m 200/s | parallel 200 | GET $BASE/api/search | stats --every 15 | assert (s => !s.window || s.p99 < 800)A persistent mock server from a Swagger/OpenAPI spec — stateful CRUD persisted to sqlite so it survives a restart, seeded from a file, with spec-violating requests rejected as 422:
mock-server ./openapi.yaml -p 4010 --state ./.crust/mock.sqlite --seed seeds/dev.json --validate
{"thing": {"name": "x"}} | POST :4010/api/things | expect 201 | (r => r.json()) | capture TID (t => t.thing.id)Point it at a real API instead and it audits that API's spec conformance:
mock-server ./swagger.json -p 4747 --proxy http://localhost:3001 --report violations.ndjsonSQL assertions, inline — rows stream one item each, and an empty result
fails instead of passing quietly. The bind placeholder is ? for every
dialect (crust rewrites it to $n for Postgres URLs):
sql "SELECT count(*)::int AS c FROM buildings WHERE name = 'Court'" | assert (r => r.c === 1)
sql "SELECT status FROM orders WHERE id = ?" $OID | assert (r => r.status === "paid")Pretty logs from every service at once — procs merges stdout and stderr
into one stream of {proc, stream, line} items, so you filter them as objects
and hand the text to pino-pretty (local node_modules/.bin is on PATH):
procs({api: "bun run dev", worker: "bun run worker", db: "docker compose logs -f postgres"}) | (l => l.line) | pino-pretty --colorize
procs({api: "bun run dev", worker: "bun run worker"}) | filter (l => l.stream === "stdout") | (l => l.line) | pino-pretty --colorize --minimumLevel warnInteractive log querying — logs holds one live stream and lets you iterate
on filters against its buffered past and live future:
logs procs({api: "bun run dev", worker: "bun run worker"})
logs docker logs -f my-appAt its prompt each line runs over the buffer, then live until Ctrl-C:
logs> filter (l => l.proc === "api") | (l => l.line) | grep -i timeout
logs> json on
logs> filter (e => e.level >= 50) | stats --every 5
logs> search "connection reset"Interactive shell
The REPL continues a line — a trailing \, an unbalanced quote, or an unclosed
( holds the line instead of erroring, so pasted multi-line JSON survives the
Enter. Ctrl-R searches history; &, jobs, fg, bg, wait %N,
kill %N and Ctrl-Z are job control. Scripts take positional arguments
($0, $1…, $#, $@), a bare FOO=x line persists for the session, and
source runs .ts files, .crust scripts, and shell scripts alike.
MCP server
crust mcp speaks the Model Context Protocol over stdio, so an MCP-capable
agent (OpenCode, Claude Code, Codex, Cursor) drives crust directly — every
capability is a first-class tool, not a shell to fumble through. One-shot tools
(crust_run_line, crust_test_fixture, crust_load_run, …) run a fresh crust
process per call; lifecycle tools keep a mock server, a supervised procs
runner, or a buffered logs session open across calls and tear them down
honestly on exit. A nonzero run is always an isError result with the verbatim
output — never a false pass.
{ "mcpServers": { "crust": { "command": "crust", "args": ["mcp"] } } }The crust-mcp skill teaches an agent which crust_* tool fits which task.
Agent skills
Crust ships the SKILL.md guides that teach a coding agent how to drive it —
pipelines, fixture suites, load gates, the mock server, procs, the log
sessions, the link checker, the MCP server — embedded in the binary, so they
always match the version you run.
They go into .agents/skills/, the cross-client directory Codex, Cursor and
OpenCode read, plus a link per skill in .claude/skills/ for Claude Code, the
one mainstream host that doesn't read the standard path:
bunx crust skills install # on a terminal: pick which ones
bunx crust skills install --all # everything, into ./.agents/skills
bunx crust skills install --select crust-cli,crust-logs
bunx crust skills install --global # into ~/.agents/skillsThe menu lists each skill with its status in the target it would write to
(current, not installed, EDITED locally) and g switches between the
project and ~/.agents/skills; declining exits 0. A .claude/skills/<name>
directory crust did not write is left alone and reported — --force consents
to replacing it with a link.
npm install asks once whether to install them. It never asks on CI, with
piped output, for a global install, or once crust skills are already in the
project under either path, and it can never fail the install. Answering no
writes nothing; CRUST_NO_SKILLS=1 stops the question permanently.
What this package installs
This package is a ~4 KB launcher, not the binary. On first use it downloads the
bun build --compile executable for your platform from the matching GitHub
Release, verifies its published sha256 before the file is ever made executable,
and caches it under ~/.cache/crust. Every install route resolves that same
artifact.
What it installed is recorded in install-manifest.json next to package.json
(plane, binary version, the package version that asked for it, the cached path
and its sha256). Every run re-checks that the manifest was written for THIS
package version on THIS plane: a version bump or a plane switch is a cache
miss, so the launcher re-resolves, re-verifies the sha256 and rewrites the
manifest — a stale install never shadows a new release, and no file has to be
deleted by hand.
Supported platforms: linux x64/arm64, macOS x64/arm64. On anything else, build from source — see the repository README.
Environment variables
| Variable | Effect |
|---|---|
| CRUST_SOURCE | github (default for this package) or apps — which artifact plane to resolve from. |
| CRUST_GITHUB_REPO | Override the owner/repo the release is fetched from. |
| CRUST_APPS_URL | Base URL of a self-hosted artifact plane, used when CRUST_SOURCE=apps. |
| CRUST_NO_SKILLS | Set to any value to stop the postinstall question about installing agent skills. crust skills install still works. |
MIT © Lario Borges
