Skip to content
These docs describe @green-tea/core 26.9.0-beta.2 — what changed. Install with npm i @green-tea/core@beta.

The matcha CLI

matcha scaffolds green-tea projects, runs them in watch mode, and generates pieces already wired into your graph. It is a native binary — installing the CLI needs no JS runtime, and it only reaches for yours when it runs or type-checks your project.

That is deliberate: green-tea runs on Node, Deno, Bun, and the edge, so its tool shouldn’t be bound to one of them.

What it scaffolds is an ordinary green-tea application — nothing here is required to use the framework, and nothing it generates is CLI-specific. If you would rather write the files yourself, Getting started builds the same thing by hand.

Terminal window
curl -fsSL https://raw.githubusercontent.com/Expressive-Tea/matcha/main/install.sh | sh

Downloads a checksum-verified prebuilt binary into ~/.local/bin. Environment variables go on the sh side of the pipe:

Terminal window
curl -fsSL …/install.sh | MATCHA_VERSION=v26.7.0 MATCHA_INSTALL_DIR=/usr/local/bin sh

From source, if you have Rust:

Terminal window
cargo install --git https://github.com/Expressive-Tea/matcha
Terminal window
matcha new my-api # Node (the default)
matcha new my-api --runtime deno # or deno | bun
matcha new my-api --template-url gh:owner/repo # any git template

The starter is alive on the first run: it serves an index.html and streams a rotating zen message over @Sse('/zen'). Open the browser and something is already moving — you are editing a working app, not assembling one.

Only the entry point and the config differ between runtimes. The src/ tree is identical, which is the same portability the framework promises.

Terminal window
matcha run

Detects the runtime and starts a watch loop. First match wins:

  1. matcha.toml — an explicit runtime = "node" | "deno" | "bun"
  2. deno.json / deno.jsonc → deno
  3. bun.lockb / bun.lock → bun
  4. package.json → node

Edge is a deploy target, not a matcha run target.

Terminal window
matcha create controller Users
matcha create step Authenticate
matcha create provider Database
matcha create module Billing
matcha create controller Users --check # type-check with your runtime afterward

Writes the file and wires it in — into the right @Module array, or into createApp({ modules }) for a module.

Terminal window
matcha create plugin "Plugin Algo" # plugins/plugin-algo/index.ts, registered in createApp
matcha create plugin --package=./algo --scope acme # a package of its own, for JSR

An in-app plugin gets its own folder, so it can become a package later without being pulled out of the app. --package writes a package into the current directory, or --package=DIR into DIR; the = is required, and the directory has to be empty. At a terminal, matcha asks for whatever the flags leave out. Without one (CI, a pipe) it uses the defaults, and for anything that has no default it stops and names the flag to pass. See Plugins.

Terminal window
matcha add sse # or: stream | buffer

Inserts a handler of that shape into your controller.

create and add edit your TypeScript with tree-sitter, not string splicing or regex. Edits are idempotent — running the same command twice doesn’t duplicate anything — and they revert themselves if the result wouldn’t parse. A generator that corrupts your source is worse than no generator.

One case it deliberately refuses: if modules is a variable (modules: MODULES) rather than an array literal, create module won’t add a second modules key. Duplicate keys parse cleanly, so nothing would fail — it would just silently shadow your value. It tells you instead.