Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ jobs:
- run: bun run test
- run: bun run docs:commands:check
- run: bun run generate:skills:check
- run: bun run sync:launchers:check
- run: bun run build
- name: Bin smoke
run: |
node dist/bin.js --version
node dist/bin.js --help > /dev/null
node dist/bin.js docs --agent > /dev/null
- name: Launcher pack smoke
run: bun run smoke:launchers
# SIGINT/SIGTERM against the built binary: the in-process suite cannot
# prove the process dies by the signal after printing its envelope.
- name: Signal smoke
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,35 @@ jobs:
npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# `brew.new` and `create-brew`: thin launchers pinned to this CLI
# version, published after it. This step never fails the release (the
# CLI is already out): a version npm already has is skipped, and a
# failed check or publish, such as a name the token does not own yet,
# leaves a warning.
- name: Publish launchers (brew.new, create-brew)
continue-on-error: true
run: |
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "::warning::NPM_TOKEN not set; launchers not published."
exit 0
fi
VERSION=$(node -p "require('./package.json').version")
if ! bun run sync:launchers:check || ! bun run smoke:launchers \
|| ! bun run scripts/smoke-launchers.ts --registry; then
echo "::warning::Launcher checks failed; brew.new and create-brew $VERSION not published. The CLI release is unaffected."
exit 0
fi
for pkg in brew.new create-brew; do
if npm view "$pkg@$VERSION" version >/dev/null 2>&1; then
echo "$pkg@$VERSION is already on npm; skipping."
continue
fi
if ! (cd "packages/launchers/$pkg" && npm publish --provenance --access public); then
echo "::warning::$pkg@$VERSION was not published (is the npm name reserved for this token?). The CLI release is unaffected."
fi
done
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Bump Homebrew tap
run: bun run scripts/update-tap.ts
env:
Expand Down
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ bun run docs:commands:check
- `openapi/public-api-v1.yaml` and `docs/commands/README.md` are
generated — regenerate, never hand-edit.

## Launchers

`packages/launchers/{brew.new,create-brew}` are thin `npx`/`npm create`
launchers for `init`. Edit `packages/launchers/launcher.mjs`, then
`bun run sync:launchers` (copies it, pins the CLI version);
`sync:launchers:check` and `smoke:launchers` gate CI.

## Releasing

See [`RELEASING.md`](./RELEASING.md) — tag-driven npm publish with
Expand Down
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,66 @@
# Changelog

## 0.20.0

Needs the same SDK release as 0.19.0 (`appEmails.setUpKit` and the auth
adapter subpaths from GetBrew/typescript-sdk#64), and the API from
GetBrew/brew-v2#2169 (its DX delta @ `6f8ef50544`).

### Added

- **`brew-cli init`** sets up Brew app email in a project: detect
(package manager, monorepo, framework: Next.js app/pages, Vite + React,
React Router, Remix, SvelteKit, Astro, Express, Hono, Supabase Edge
Functions; auth: Better Auth, Auth.js v4/v5, Supabase, Clerk, Firebase,
WorkOS, Stytch; Stripe; existing email providers; editors; cloud
shells), sign in through the browser for a CLI key and an app key (the
app key only ever lands in `.env.local`, 0600, git-ignored), set up the
starter kit for the detected stack, install `@brew.new/sdk`, write
`lib/brew.ts`, `lib/brew-auth.ts`, `lib/brew-contracts.ts`, a Supabase
Send Email Hook function, `.env.example` and `brew.json` (create-only,
hashes kept in `brew.json`), print patches for the auth config
(`--apply-patches <id>@<hash>,…` from the report applies each only while
its file still matches the reviewed hash), configure
MCP for detected editors, write an `AGENTS.md` block and (in Claude Code
projects) the skills, and send a sandbox test email. Destructive class:
`--dry-run --json` plans with no writes and no mutating requests,
`--yes --json` runs; `--no-wait` / `--resume` for agents (exit 3 with
the sign-in link and code). Flags: `--app`, `--domain`, `--sets`,
`--stack`, `--test-to`, `--apply-patches`, `--app-url` (shown on the
approval page so the right brand is picked), `--no-install`, `--no-mcp`,
`--no-agent-files`, `--no-test-email`, `--no-open`. The JSON report
follows `schemas/init-report.json` (`schemaVersion: 1`). Re-runs reuse
the idempotency keys `init:<projectId>:<step>:<body hash>`. After a
browser sign-in the run acts as the new app key, so the kit lands in the
brand that key is bound to. `lib/brew.ts` reads the key through
`$env/dynamic/private` in SvelteKit and `import.meta.env` in Astro;
Express, Hono and plain Node get a blocking step to load `.env.local`.
A test email that does not go out (say `SANDBOX_DAILY_LIMIT`) is a
non-blocking retry step, not a failed run. A domain `--domain` adds that
already publishes another DKIM key (`DKIM_SELECTOR_IN_USE`) gets a
`dkim-selector-in-use` step naming the suggested subdomain instead of
the DNS records step, and a kit key refused with
`APP_EMAIL_TRIGGER_IN_USE` gets a human `trigger-in-use:<key>` review
step ahead of the re-run. The generated code and the
install command use `@brew.new/sdk@^13.6.0`, the planned release with
`appEmails` and the auth adapters (on top of 13.5.0's `send`).
- Not in this release (they need platform work first): the Supabase and
Clerk provider switch-over (Supabase Management API hook, Clerk
`toggle_delivery`; init prints them as human steps), `integrations
connect` (GetBrew/brew-v2#2169 and SDK `integrations.connect`), and brand
creation from `--app-url` (sign-in keys are bound to a brand picked on
the approval page).
- **Launchers** `brew.new` and `create-brew` (`packages/launchers/`):
each runs `@brew.new/cli` in-process, pinned to the same version,
inserts `init` when no subcommand is given, and sets
`BREW_CLI_INVOKED_AS` (`npx -y brew.new@<version>`, `npm create
brew@latest --`). `bun run sync:launchers[:check]` keeps them in
lockstep; `bun run smoke:launchers` packs and runs them. The release
workflow publishes them after the CLI and skips them, with a warning,
while the npm names are not ours yet; they are not on npm today.
- The `brew-app-emails` agent skill (embedded; `init` installs it).
- `types` exports its contracts builder and trigger pager for `init`.

## 0.19.0

Needs `@brew.new/sdk` 13.6.0: `brew.send` and
Expand Down
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,56 @@ data on stdout and progress on stderr, and documents itself:
brew-cli emails send --help
```

## Set up Brew in your app: `init`

```bash
npx @brew.new/cli init # or, installed: brew-cli init
```

Short launchers (`npx brew.new init`, `npm create brew`) live in
`packages/launchers/` and are not on npm yet.

`init` reads the project (framework, auth library, package manager,
monorepo, editors), signs you in through the browser, and then:

1. sets up and publishes the auth and lifecycle starter kit for your auth
library (`app-emails kit`);
2. installs `@brew.new/sdk` with your package manager;
3. writes `lib/brew.ts` (a lazy `getBrew()`), `lib/brew-auth.ts` (Better
Auth callbacks, the Auth.js provider, or welcome/auth helpers),
`lib/brew-contracts.ts` (payload types and the `brew.send` key
registry), a Supabase Send Email Hook function when you use Supabase
Auth, `.env.example` and `brew.json`;
4. puts the app key in `.env.local` (mode 0600, git-ignored, never under a
`NEXT_PUBLIC_`/`VITE_`/`PUBLIC_` name);
5. prints patches for your auth config; after review, the report's
`--apply-patches <id>@<hash>` command applies each one only while its
file still matches the hash it was reviewed against (an Auth.js config
without a database adapter gets a manual step instead, since Auth.js
email sign-in needs one);
6. adds the Brew MCP server for the editors the project uses, an
`AGENTS.md` block, and (in Claude Code projects) the agent skills;
7. sends you a test email from the team sandbox.

Every file is create-only: a file you changed is reported
`kept-user-version` and left alone, and a re-run with nothing new changes
nothing. Until a sending domain verifies, app emails reach members of
your Brew organization only; `--domain mail.example.com` adds one and
prints its DNS records without waiting.

Agents run it in two steps and read one JSON report
(`schemas/init-report.json`: `status`, `steps[]`, `files[]`, `patches[]`,
`nextSteps[]` with `actor: agent | human`, `agentInstructions`):

```bash
npx @brew.new/cli init --dry-run --json # the plan; no writes, no sign-in, no mutating requests
npx @brew.new/cli init --yes --json # exit 3: show the person nextSteps' link + code, then run its resume command
```

Exit codes: 0 `complete` or `action_required` (blocking `nextSteps` left),
1 `failed`, 2 usage or a monorepo root with several apps (pick one with
`--app apps/web`), 3 sign-in required, 4 confirmation required.

## Send app email by key

An app email is a trigger with a stable key (`auth.password-reset`,
Expand Down
20 changes: 18 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ which is the #2169 @ `6f8ef50544` DX delta on main's spec (brew-v2
```bash
# on a clean main with CI green
npm version <patch|minor|major> --no-git-tag-version
git add package.json && git commit -m "Release vX.Y.Z"
bun run sync:launchers # brew.new + create-brew follow the CLI version
git add package.json packages/launchers && git commit -m "Release vX.Y.Z"
git push origin main
git tag vX.Y.Z
git push origin vX.Y.Z # <-- triggers .github/workflows/release.yml
Expand All @@ -69,7 +70,17 @@ The workflow then:
darwin-arm64/x64, linux-x64/arm64, windows-x64
(`scripts/build-binaries.ts`) and attaches them + `checksums.txt` to an
auto-generated GitHub Release.
5. Rewrites `Formula/brew-cli.rb` in `GetBrew/homebrew-tap`
5. Publishes the launchers `brew.new` (`npx brew.new init`) and
`create-brew` (`npm create brew`) from `packages/launchers/`, after the
sync check, a pack smoke and a registry smoke install
(`scripts/smoke-launchers.ts --registry`). Each is the CLI's version and
depends on exactly that `@brew.new/cli`. This step never fails the
release: the CLI is already out, so a version npm already has is skipped,
and a failed check or publish (for example a launcher name the token
does not own yet) leaves a warning on the run. Neither name is on npm
today; until they are reserved, docs and generated files point at
`npx @brew.new/cli`.
6. Rewrites `Formula/brew-cli.rb` in `GetBrew/homebrew-tap`
(`scripts/update-tap.ts`; skipped with a notice when
`TAP_GITHUB_TOKEN` is absent).

Expand All @@ -90,6 +101,11 @@ brew install getbrew/tap/brew-cli

## One-time setup

- **The launcher names**: `brew.new` and `create-brew` must be owned on
npm by the account behind `NPM_TOKEN` (reserve them with a `0.0.x`
placeholder first), and the token must be allowed to publish unscoped
packages. Until then the launcher step warns and skips them.

- **`NPM_TOKEN`** (repo secret): npm Automation token with publish access
to the `@brew.new` org — same recipe as
[typescript-sdk/RELEASING.md](https://github.com/GetBrew/typescript-sdk/blob/main/RELEASING.md).
Expand Down
35 changes: 34 additions & 1 deletion docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<!-- GENERATED FILE — do not edit. Regenerate with `bun run docs:commands`. -->

155 commands. Classes: read (always safe), write
156 commands. Classes: read (always safe), write
(mutating, retry-safe), destructive (irreversible — the confirmation
protocol applies: interactive y/N on a TTY, exit 4 + JSON envelope with
a `confirmCommand` otherwise, `--yes` to proceed).
Expand Down Expand Up @@ -176,6 +176,7 @@ a `confirmCommand` otherwise, `--yes` to proceed).
| `brew-cli docs` | read | — | Documentation pointers; --agent prints the command manifest |
| `brew-cli docs api` | read | `GET /v1/help` | Fetch the live machine-readable API catalog (GET /v1/help) |
| `brew-cli mcp install` | write | — | Add the Brew MCP server to your AI editors' config (Cursor, VS Code, Claude Code, Gemini CLI, Devin Desktop/Windsurf, Codex) |
| `brew-cli init` | destructive | — | Set up Brew app email in this project: sign in, starter kit, SDK, lib/brew.ts, .env.local, MCP, a sandbox test email |
| `brew-cli api` | destructive | — | Raw authenticated request against the Brew public API |

## Details
Expand Down Expand Up @@ -2819,6 +2820,38 @@ brew-cli mcp install --client all --dry-run --json
brew-cli mcp install --client vscode --print
```

### brew-cli init

Set up Brew app email in this project: sign in, starter kit, SDK, lib/brew.ts, .env.local, MCP, a sandbox test email

- Class: destructive
- Derived from `brew.brand.get(...)`, `brew.domains.list(...)`, `brew.domains.add(...)`, `brew.appEmails.setUpKit(...)`, `brew.automations.triggers.list(...)`, `brew.automations.triggers.getContract(...)`, `brew.send(...)`
- `--dry-run` — Plan only: detect, read, and report every change (--json for the plan); no writes, no sign-in, no mutating requests
- `--app <dir>` — The app to set up inside a monorepo (relative to the cwd)
- `--domain <host>` — Add this sending domain and print its DNS records (init does not wait for verification)
- `--sets <sets>` — Kit sets to set up: auth, lifecycle (default both)
- `--stack <provider>` — Auth stack for the kit (default: detected): better-auth | authjs | supabase | clerk | firebase | custom
- `--test-to <email>` — Send the test email here (default: the person who signed in; the sandbox reaches your Brew team only)
- `--apply-patches <patches>` — Apply reviewed patches to your auth config: comma-separated <id>@<hash> from the report (each applies only while its file is unchanged since that report)
- `--app-url <url>` — Your app's URL, shown on the brew.new approval page so the right brand is picked
- `--no-install` — Do not install @brew.new/sdk
- `--no-mcp` — Do not configure MCP for detected editors
- `--no-agent-files` — Do not write the AGENTS.md block or the skill files
- `--no-test-email` — Do not send the sandbox test email
- `--no-wait` — If a sign-in is needed, exit 3 at once with its link and code (agents), then run the resume command
- `--resume` — Collect the sign-in started with --no-wait, then continue
- `--no-open` — Print the sign-in link without opening a browser

```bash
brew-cli init
brew-cli init --dry-run --json
brew-cli init --yes --json
brew-cli init --yes --no-wait --json
brew-cli init --resume --yes --json
brew-cli init --app apps/web --domain mail.example.com --yes
brew-cli init --yes --apply-patches better-auth-config@3f9c2a7d1b04
```

### brew-cli api

Raw authenticated request against the Brew public API
Expand Down
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@brew.new/cli",
"version": "0.19.0",
"version": "0.20.0",
"description": "Official agent-first command-line interface for the Brew public API.",
"license": "MIT",
"author": "Brew",
Expand Down Expand Up @@ -55,14 +55,17 @@
"test": "vitest run",
"test:watch": "vitest",
"parity": "vitest run tests/parity-sdk.test.ts tests/parity-spec.test.ts",
"check": "bun run tsc && bun run lint && bun run test && bun run docs:commands:check && bun run generate:skills:check",
"check": "bun run tsc && bun run lint && bun run test && bun run docs:commands:check && bun run generate:skills:check && bun run sync:launchers:check",
"docs:commands": "bun run scripts/generate-command-docs.ts",
"docs:commands:check": "bun run scripts/generate-command-docs.ts --check",
"generate:types": "node scripts/generate-types.mjs",
"generate:skills": "bun run scripts/generate-skills.ts",
"generate:skills:check": "bun run scripts/generate-skills.ts --check",
"prepublishOnly": "bun run clean && bun run tsc && bun run lint && bun run test && bun run generate:skills:check && bun run build",
"smoke:signals": "bun run scripts/signal-smoke.ts"
"smoke:signals": "bun run scripts/signal-smoke.ts",
"sync:launchers": "bun run scripts/sync-launchers.ts",
"sync:launchers:check": "bun run scripts/sync-launchers.ts --check",
"smoke:launchers": "bun run scripts/smoke-launchers.ts"
},
"dependencies": {
"@brew.new/sdk": "^13.3.0",
Expand Down
21 changes: 21 additions & 0 deletions packages/launchers/brew.new/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Brew

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
24 changes: 24 additions & 0 deletions packages/launchers/brew.new/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# brew.new

Set up [Brew](https://brew.new) email in your app with one command:

```bash
npx brew.new init
```

`init` finds your framework and auth library, signs you in through the
browser, sets up the auth and lifecycle starter emails, installs
`@brew.new/sdk`, writes `lib/brew.ts` (and the helpers for Better Auth,
Auth.js or Supabase), puts the app key in `.env.local`, connects your AI
editor to the Brew MCP server, and sends you a test email. It never
overwrites a file you changed.

```bash
npx brew.new init --dry-run --json # the plan, nothing changed
npx brew.new init --yes --json # for agents: exit 3 means approve the sign-in, then run the resume command
```

Every other command works too: `npx brew.new send lifecycle.welcome --to you@example.com --yes`.

This package is a launcher for [`@brew.new/cli`](https://www.npmjs.com/package/@brew.new/cli)
(pinned to the same version). Docs: https://docs.brew.new
8 changes: 8 additions & 0 deletions packages/launchers/brew.new/bin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env node
import { readFileSync } from 'node:fs'
import { launch } from './launcher.mjs'

const { version } = JSON.parse(
readFileSync(new URL('./package.json', import.meta.url), 'utf8')
)
await launch({ packageName: 'brew.new', version })
Loading