Documentation and a modular generator for modern web proxies: Scramjet, Wisp, Bare, and their transport layers.
Two things live here:
docs/. An explanation of the whole stack, written to be read. Plain markdown, so it renders fine on GitHub, and there is a site that renders it with navigation and search-friendly structure.builder/. A generator that composes a working proxy from parts. Tick the features you want; the generated README gives the commands for your selected package manager.
Ask development questions or show your project in the Night Network Discord:
https://discord.gg/algebra. For more direct support, message me on discord:
@crllect
bun install
bun startThe documentation is served at /, and the interactive builder is served at
/build, on the configured port.
The site renders the Markdown in docs/, so you can
read the guides on GitHub.
The site also builds to static files with the builder kept working, so it can be hosted for people who would rather read it in a browser than on GitHub.
bun run build:siteThat writes dist/: every page prerendered to <slug>.html, the search index,
and site/public copied to /static. It also writes
functions/_generated/parts.js, a frozen copy of builder/parts/ that the
Cloudflare Functions import, since a Worker cannot read them off disk.
To preview the built site with its Functions exactly as Cloudflare runs them:
bun run preview:siteThat fetches wrangler through bunx on first use rather than carrying it as a
dependency, since deploying from GitHub does not need it locally.
Create a Pages project pointed at this repository and set:
| Setting | Value |
|---|---|
| Build command | bun run build:site |
| Build output | dist |
| Compatibility flags | nodejs_compat |
wrangler.toml already carries the output directory and the compatibility flag,
so a wrangler pages deploy picks them up without further arguments. The flag
is not optional: /api/preview and /api/download bundle typescript to emit
JavaScript builds, and it reaches for node builtins.
Pushes to main redeploy. Docs are served as static files; only the two builder
endpoints run as Functions, so reading the documentation costs no invocations.
bun builder/cli.js --out ./my-proxy --preset minimal
cd my-proxy && bun install && bun start| Preset | Frontend | Toolchain | Server | Transports | Purpose |
|---|---|---|---|---|---|
minimal |
Vanilla | JavaScript, no build step | Express | libcurl | Smallest readable build |
standard |
Vanilla | Bun, TypeScript, Vite, Tailwind | Fastify | libcurl, epoxy | The recommended setup |
everything |
Vanilla | Bun, TypeScript, Vite, Tailwind | Express | libcurl, epoxy, bare | Every optional feature |
serverless |
Vanilla | JavaScript, no build step | Express | bare | No WebSocket needed |
react |
React | TypeScript, Vite | Express | libcurl | Hydrated React shell |
astroPreact |
Astro + Preact | TypeScript, Astro | Express | libcurl | Static page with a Preact island |
Selecting more than one transport turns on runtime switching automatically.
Every preset uses Scramjet with manual wiring.
Or answer the questions yourself:
bun builder/cli.js --out ./my-proxy \
--language ts --runtime bun --server express \
--frontend react --bundler vite --styling tailwind \
--transport libcurl,epoxy \
--features browserControls,tabs,settings,transportSwitch,history,bookmarks| Question | Options |
|---|---|
--language |
ts, js |
--package-manager |
npm, pnpm, yarn, bun |
--runtime |
node, bun |
--server |
express, fastify |
--frontend |
vanilla, react, astro |
--bundler |
vite, none |
--styling |
plain, scss, tailwind |
--transport |
libcurl, epoxy, bare (comma separated) |
--features |
Comma-separated feature identifiers below. |
Two more flags exist but are not part of the normal path. --wiring manual
(default) or bootstrap picks how Scramjet's browser files are served; only
manual is offered in the web builder, and bootstrap cannot use the Bare
transport. See wiring. --vercel adds vercel.json
and the serverless function export, which forces the Bare transport. Neither is
offered in the web builder. Serverless deployment itself is just the Bare
transport, which --preset serverless selects on its own; see
Serverless deployment.
| Feature identifier | Adds |
|---|---|
browserControls |
Back, forward, and reload controls |
tabs |
Multiple isolated proxy sessions |
settings |
Persisted proxy and search settings |
transportSwitch |
Runtime transport selection |
history |
Persisted browsing history |
bookmarks |
Persisted bookmarks |
cloak |
Title, icon, and about:blank cloaking |
quietServiceWorker |
Silence log/info/debug inside the service worker |
aboutPages |
Navigable custom-protocol pages inside the proxy tab |
Combinations that cannot work are corrected and explained rather than generated
broken. Framework frontends need a build step, and TypeScript needs one unless
you are happy shipping it uncompiled. Tailwind without a build step loads from
the Tailwind CDN. Fastify on Bun falls back to Express because @fastify/static
serves empty bodies there.
Serverless hosting forces the Bare transport, because functions cannot hold Wisp's persistent WebSocket open. It works, and it is a reasonable choice if you have no server and no budget. It costs you WebSocket sites and puts target TLS on your server, and a proxy is almost pure egress while serverless bills egress per GB, so the economics stop working as traffic grows. A static host can serve the client instead, with Wisp on a cheap VPS.
Run bun builder/cli.js --help for the full list.
Run bun run examples to write every preset into examples/ if you would
rather just read the output. That directory is generated and untracked.
Concepts: how a proxy works, proxy engines, wisp vs bare, transports, bare-mux and proxy-transports, cross-origin isolation, inside Scramjet.
Guides: quickstart, multiple tabs, settings, URL parsing and history, custom protocols, search engines, bootstrap or manual wiring, serverless deployment, other frameworks, deployment, practices worth knowing.
Reference. config and flags, plugins and hooks, Controller and Frame API, core API and types, known bugs, site compatibility, version matrix, breaking changes, troubleshooting, official docs and licensing, glossary.
Every part under builder/parts/ is a real, readable source file. The generator
strips the blocks you did not ask for and substitutes a few names, it does not
assemble code out of strings:
//#if transportSwitch
const transportModules = {
libcurl: "/libcurl/index.mjs",
epoxy: "/epoxy/index.mjs"
};
//#else
const transportModules = { libcurl: "/libcurl/index.mjs" };
//#endifSo you can read builder/parts/engine/scramjet.ts directly and copy it by hand
if you never touch the builder.
The parts are written in TypeScript. Choosing JavaScript transpiles those parts rather than maintaining a second implementation.
The generated client feature modules use a small interface implemented by
engine.ts:
await engine.init();
const session = await engine.createSession(iframeElement, handlers);
await engine.setTransport({ kind, wisp });
session.go(url);
session.back();
session.forward();
session.reload();
session.destroy();Tabs, settings, history, and bookmarks are written against that interface, so
they never touch the engine directly. Changing transports also changes
dependencies and server mounts; regenerate instead of editing only engine.ts.
docs/ markdown documentation, readable on GitHub
site/ the local docs site + builder UI (Node, no build step)
builder/
options.js what can be configured, and which combinations are legal
versions.js package constraints, with the date they were verified
template.js the //#if directive processor
parts/ real source files that get composed
cli.js Node entry point for generated projects
examples/ generated presets, untracked (bun run examples)
scripts/check.js validates docs links and that every combination compiles
bun run checkVerifies documentation files, links, Markdown rendering, URL classification, generated JSON and JavaScript, client TypeScript semantics, template directives, and all 57 option combinations.
bun run examplesThat command writes examples/ from the presets. It is generated output, not
tracked in git, so there is nothing to keep in sync.
Package constraints live in builder/versions.js,
verified against npm on 2026-08-04. The two combinations that work:
scramjet 2.0.67-alpha.2 + controller 0.0.14 + utils 0.0.3
libcurl ^2 epoxy ^3 bare-transport ^1 (proxy-transports generation)
There is an older bare-mux generation (libcurl ^1, epoxy ^2, bare-as-module3) that Ultraviolet used. Mixing the two generations causes your proxy to shit itself. See the version matrix.
Note that installing @mercuryworkshop/scramjet without a version gives you
1.1.0; 2.x is published under the alpha tag, not latest.
Night Network, where I personally help in building really cool stuff. https://discord.gg/algebra
Mercury Workshop for Scramjet, wisp, epoxy and the transport layer.
TitaniumNetwork for Ultraviolet, which I would argue is the catalyst for the entire proxy community.
Corrections are the most valuable contribution. If something here is wrong, out
of date, or describes a fork's behavior as if it were upstream, open an issue or
a PR. Run bun run check before submitting, and read
CONTRIBUTING.md for what a documentation change has to prove.
This repository and its generated projects are AGPL-3.0-only. Several upstream
components are AGPL as well. Review LICENSE before distributing or deploying a
modified project; this is not legal advice.