Skip to content

init + the brew.new / create-brew launchers (C4, 0.20.0) - #55

Open
thedogwiththedataonit wants to merge 10 commits into
claude/send-app-emailsfrom
claude/init
Open

thedogwiththedataonit wants to merge 10 commits into
claude/send-app-emailsfrom
claude/init

Conversation

@thedogwiththedataonit

@thedogwiththedataonit thedogwiththedataonit commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Update 2026-10-25: restacked on #54. Conflict: CHANGELOG.md. SDK_RANGE is ^13.6.0; a declared ^13.3.0 (published, no adapters) or ^13.5.0 now installs it (test cases added). Checks against the local typescript-sdk#64 + SDK main build: tsc, lint, test 1051, generated types fresh, docs:commands:check, generate:skills:check, sync:launchers:check, build, bin smoke, launcher pack smoke, signal smoke ✓.

Renumbered 10-25: main published 0.18.0 (--lang/--clear-lang, contact imports, SDK ^13.3.0), and SDK main published 13.1.0, 13.2.0 and 13.3.0 without the DX features (13.4.0 is reserved for its pending email lang release). So this chain moves up one: #54 0.19.0, #55 0.20.0, #61 0.21.0, #63 0.22.0, and the DX SDK releases are send-by-key 13.5.0, adapters/appEmails 13.6.0 (SDK_RANGE ^13.6.0, so installs can no longer resolve to the published 13.3.0), integrations.connect 13.7.0. This PR is 0.20.0; older numbers below are pre-rebase.

Spec sync 2026-10-21: restacked on #54 (brew-v2 #2169 @ 6f8ef50544 spec). Adapted init: a --domain that already publishes another DKIM key (DKIM_SELECTOR_IN_USE on POST /v1/domains) gets a dkim-selector-in-use step naming the suggested subdomain instead of the DNS records step; a kit key refused with APP_EMAIL_TRIGGER_IN_USE gets a human trigger-in-use:<key> step ahead of the re-run (init passes no domainId, so the purpose refusal cannot happen). Checks: tsc, lint, test 1019, docs/skills/launchers freshness, build, launcher + signal smokes ✓.

Renumbered 10-21: main published 0.17.0 (canvas commands, SDK ^13.0.0), so this chain moved up one: #54 0.18.0, #55 0.19.0, #61 0.20.0, #63 0.21.0. This PR is 0.19.0; older numbers below are pre-rebase.

Stacked on #54 (→ #53 → #44 → main); #61 (migrate resend) stacks on this. CLI 0.18.0 (C4): init and the brew.new / create-brew launchers.

Depends on unpublished work (the same SDK release as #54: appEmails.setUpKit and the adapter subpaths from typescript-sdk#64; brew-v2#2169 deployed). Generated code installs @brew.new/sdk@^13.2.0 (SDK_RANGE), the planned release; an app that declares an older range gets it installed over (reachesSdkRange, Greptile issue 10); npm's 12.3.0 has neither send nor appEmails.

The npm names brew.new and create-brew are not Brew's yet (both 404). Nothing user-facing tells people to run them: generated files, the AGENTS.md block, the skill, README and the report schema say npx @brew.new/cli. release.yml publishes the launchers only after the CLI, in a step that never fails the release (sync check, pack smoke and registry smoke inside it; a version already on npm is skipped; a failed publish, such as a name the token does not own, leaves a warning). CI still hard-gates sync:launchers:check and smoke:launchers.

Publish order (nothing here is published, merged or tagged)

  1. brew-v2: #2169 (the whole DX stack collapsed into one PR: browser sign-in, trigger keys, fire by key, team sandbox, app emails, import modes, hosted auth hooks) merged and deployed.
  2. typescript-sdk: init + the brew.new / create-brew launchers (C4, 0.20.0) #55 (send, fireByKey) and Release CLI 0.17.0 with registry SDK 13 #64 (appEmails, adapter subpaths) rebased onto SDK main (which already has the canvas revocation fix this CLI's main needs) and published. Numbers are final: init + the brew.new / create-brew launchers (C4, 0.20.0) #55 ships as 13.1.0 and Release CLI 0.17.0 with registry SDK 13 #64 as 13.2.0, after the canvas fix's 13.0.0; this CLI takes ^13.2.0. Both SDK PRs are behind SDK main (init + the brew.new / create-brew launchers (C4, 0.20.0) #55 conflicts).
  3. This chain: bun add @brew.new/sdk@^<that version> (regenerates bun.lock from the registry, replacing main's git pin), re-vendor openapi/public-api-v1.yaml from brew-v2 main, frozen install + full checks, then CLI 0.17.0 → 0.18.0 → 0.19.0.
  4. Launchers: reserve brew.new and create-brew on npm (both still 404) for the token's account; until then the release step warns and skips them.

brew-cli init

Destructive class; --dry-run --json plans (no writes, no sign-in, no mutating requests), --yes --json runs; exit 0 complete / action_required / planned, 1 failed, 2 usage or ambiguous monorepo, 3 auth_required (with --no-wait / --resume), 4 confirm. Pipeline: detect (framework, auth, billing, existing email, editors, cloud shells) → .gitignore check → browser sign-in (CLI key to config, app key only to .env.local, 0600) → brand → domain (sandbox until verified; --domain adds one and prints DNS) → appEmails.setUpKit → install the SDK → create-only managed files (lib/brew.ts, lib/brew-auth.ts, lib/brew-contracts.ts, the Supabase hook function, .env.example, brew.json) → auth-config patches → MCP → AGENTS.md + skills (Claude Code projects) → sandbox test email → report (schemas/init-report.json) with nextSteps[] (actor: agent|human).

Launchers

packages/launchers/{launcher.mjs, brew.new/, create-brew/}: exact @brew.new/cli pin = launcher version = CLI version (sync:launchers[:check]), init inserted when no subcommand is given, BREW_CLI_INVOKED_AS = npx -y brew.new@<v> / npm create brew@<v> -- (pinned, so resume and confirm commands run the same CLI).

Review round 2026-10-16

  • Rebased onto the new send by key, app-emails, trigger keys (C2, 0.19.0) #54: types conflict (init now writes lib/brew-contracts.ts from main's contract artifacts), CHANGELOG gets its own ## 0.18.0, version 0.18.0 + launchers synced. Init tests serve GET /v1/automations/triggers/{id}/contract; the agent sign-in tests pin the fake clock (they had started failing on the real date).
  • --apply-patches <id>@<hash>,…: the report's nextStep carries the hash of the reviewed file; a file edited since is reported stale and left alone (new test). Re-run commands drop a flag's value with the flag.
  • The Auth.js patch is manual without a database adapter (Auth.js refuses an email provider without one). After a browser sign-in the run acts as the new app key, so the kit lands in that key's brand. lib/brew.ts reads the key via $env/dynamic/private (SvelteKit) and import.meta.env (Astro); Express, Hono and plain Node get a blocking load-.env.local step; SvelteKit and Astro joined the typecheck matrix. A failed test email is a non-blocking retry step. --app-url is shown on the approval page. The launcher smoke runs under Node with an init --dry-run --json argv check.
  • Not in this PR (need platform work first): the Supabase Management API hook switch-over and Clerk toggle_delivery (brew-v2 PR-K/PR-L not started; init prints them as human steps), integrations connect (no /v1/integrations/{provider}/connection in any spec; SDK S4), brand creation from --app-url (sign-in keys are bound to a brand picked on the approval page), the Entri link, doctor --project.

Checks (scratch SDK = SDK main + #64)

tsc ✓ · lint ✓ · test 64 files / 906 tests ✓ (secret canary on every init run; generated files type-checked for 9 stacks + a negative control) · docs:commands:check, generate:skills:check, sync:launchers:check ✓ · build + bin smoke ✓ · smoke:launchers (Node) ✓ · smoke:signals ✓.

Needs live validation (after publish + deploy)

npx -y @brew.new/cli@0.18.0 init --dry-run --json then --yes --json on each wave-1 example app (sign-in minting both keys, kit per stack, sandbox test email in a team inbox, DOMAIN_REQUIRED for a non-member --test-to), the Better Auth patch on a real app, the Supabase hook deploy, the Workers secret path, and the launcher publish once the names are reserved.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

RetriggerView in GreptileConfidence Score: 0/5

Do not merge yet: setup still has unresolved auth, file-write, recovery, and launcher failures.

Fix All in Claude CodeFindings

  1. P1 Reruns forget DKIM conflicts ▶
  2. P1 Recovery replays failed setup ▶
  3. P1 Password reset stays unwired ▶
  4. P1 Disabled hook looks ready ▶
  5. P1 Supabase config becomes invalid ▶
  6. P1 Missing files look written ▶
  7. P1 Windows SDK install fails ▶
  8. P1 Lifecycle setup breaks auth email ▶
  9. P1 Launcher skips setup ▶
  10. P1 Auth patch breaks startup ▶
  11. P1 CommonJS helpers cannot load ▶
  12. P1 Setup changes the wrong app ▶
  13. P1 Worker auth emails fail ▶
  14. P1 Server setup gets skipped ▶
  15. P1 Selected auth library stays unwired ▶
  16. P1 Welcome emails stay unwired ▶
  17. P2 Setup misses existing domains ▶
  18. P2 Setup has no default deadline ▶
  19. P2 Test retry loses its account ▶
Fix with agent prompt
### Issue 1
src/lib/init/pipeline.ts:559-572
A rerun with the same `--domain` loses the `DKIM_SELECTOR_IN_USE` warning. `selectorInUse` is set only after `domains.add`, so finding the existing domain skips that check and produces an `add-dns-records` step. Following that step can replace another sender's DKIM key and stop its emails from passing authentication.

Keep the warning across reruns and withhold conflicting records from both report formats until they are safe to publish.

### Issue 2
src/lib/init/pipeline.ts:670-672
After a partial kit failure, the suggested rerun sends the same body with the same saved `projectId`, so it reuses the same `Idempotency-Key`. The API returns the original response for 24 hours, including the failed entries. The rerun therefore cannot finish those keys during that window. Keep the key for uncertain request retries, but use a new attempt key after receiving a completed response with failed entries.

### Issue 3
src/lib/init/patches.ts:206-210
A valid Better Auth config with `emailAndPassword: { enabled: true },` on one line passes the automatic-patch checks, but this regex never finds its password block. The patch still reports success without adding `sendResetPassword`, leaving password-reset email unwired. Handle the inline form or return a manual patch when the callback cannot be placed.

### Issue 4
src/lib/init/patches.ts:333-335
`supabaseConfigPatch` reports `already-applied` whenever the file contains the hook header and URL text. It does not check whether the section is commented out or has `enabled = false`. A disabled hook pointing at `send-email` therefore receives no patch or blocking next step. Check the active section and its enabled value before declaring it applied.

### Issue 5
src/lib/init/patches.ts:345-349
If `supabase/config.toml` already defines `[functions.send-email]` but has no send-email hook section, this patch appends another `[functions.send-email]` table. Repeating a TOML table makes the config invalid, so `--apply-patches` can break `supabase start`. Merge the existing function section or leave that part as a manual edit.

### Issue 6
src/lib/init/pipeline.ts:851-858
Required file writes can return `action: 'refused'`, but the pipeline still marks the `files` and `state` steps as done. A helper directory linked outside the project can leave a generated helper missing; a refused `brew.json` write loses the saved project ID and hashes. Mark refused required writes as failed and provide a recovery step before applying patches that depend on them.

### Issue 7
src/lib/init/pipeline.ts:1220-1223
The new install step passes bare `npm`, `pnpm` and `yarn` commands to `defaultSpawn`, which always uses `shell: false`. On Windows these commands normally resolve to `.cmd` shims, which cannot be launched this way. Setup therefore fails to install the SDK and exits with a failed install step. Add a Windows-safe way to launch these shims while preserving argument quoting.

### Issue 8
src/lib/init/pipeline.ts:867-871
On a fresh Better Auth or Auth.js project, `--sets lifecycle` creates only lifecycle emails, but the pipeline still generates and offers patches for auth callbacks. Applying those patches routes auth emails to keys the kit did not create. Track whether the auth set was requested, and skip auth adapters and patches when it was not.

### Issue 9
packages/launchers/launcher.mjs:13-18
`VALUE_FLAGS` omits `--app-url` and `--apply-patches`. For example, `npm create brew -- --app-url https://example.com --yes` treats the URL as a subcommand and does not insert `init`. The CLI then rejects the init-only flag instead of starting setup. Add both flags to the shared launcher and sync its copies.

### Issue 10
src/lib/init/patches.ts:305-308
`authjsPatch` checks the whole line for `[]` when deciding whether to add a comma. A valid line such as `providers: [GitHub({ checks: [] })],` takes the empty-list branch and becomes `providers: [brewEmailProviderGitHub({ checks: [] })],`. Applying that patch breaks the auth config and can stop the app from building or starting. Check whether the providers list itself is empty, not whether any empty array appears on the line.

### Issue 11
src/lib/init/templates.ts:167-169
In a JavaScript server with `"type": "commonjs"`, `init` writes `brew.js` and `brew-auth.js` with `import` and `export` statements. Node cannot load these files, so the generated email helpers fail before they can send anything. Generate CommonJS code for these projects, or use `.mjs` files with matching imports.

### Issue 12
src/lib/init/detect.ts:380-387
A missing or mistyped `--app` path can silently select a parent project. `detectProject` passes the path to `resolveProjectRoot`, which walks upward; supplying `--app` also skips the monorepo choice check. With `--yes`, setup can then install the SDK and write files in the parent app or monorepo root instead of the requested app. Reject an invalid explicit app path before falling back to a parent.

### Issue 13
src/lib/init/templates.ts:undefined-342
On a Hono Worker using Better Auth or Auth.js, `authHelperFile` passes `getBrew` directly to the email adapter. The generated Worker version requires the request's `env`, but neither adapter setup supplies it. TypeScript can reject the callback, and calling it without `env` fails at `env.BREW_API_KEY`, so auth emails cannot send.

Generate an adapter factory that takes the request's `env` and passes a zero-argument client callback. Both the Better Auth and Auth.js templates need this.

### Issue 14
src/lib/init/detect.ts:192-198
If a TanStack Start app declares `vite` and `react`, this branch returns before the later `@tanstack/react-start` check. `detectProject` calls it a browser-only app, so `init` skips installing the SDK and writes no server client or auth helper, despite the app having a server.

Check known full-stack frameworks before the generic Vite/React branch.

### Issue 15
src/lib/init/pipeline.ts:undefined-241
`--stack` changes the kit request, but generated helpers and patches still follow `project.auth`. For example, `--stack better-auth` in an app detected as `none` creates the Better Auth kit but generates `sendAuthEmail` instead of `brewEmails` and offers no Better Auth patch. The user must wire the selected library by hand. Use the selected stack consistently for email helpers and patches, or reject conflicting selections.

### Issue 16
src/lib/init/patches.ts:165-167
`betterAuthPatch` treats any mention of `brewEmails` as proof that all requested callbacks are wired. Run setup with `--sets auth` and apply its patch, then rerun with both sets: the kit adds lifecycle emails, but this check skips adding `databaseHooks.user.create.after`. The welcome email never runs, and setup leaves no blocking step to add it. Check the callbacks needed for the current sets and offer a patch or manual step for missing ones.

### Issue 17
src/lib/init/pipeline.ts:546
`init` reads only the first 100 domains before deciding whether `--domain` already exists. If the requested domain is on a later page, setup calls `domains.add` again. The API returns `DOMAIN_ALREADY_EXISTS`, so a valid rerun gets a failed domain step instead of the existing domain's records. Setup can also miss a verified sending domain on a later page.

Follow the remaining pages before choosing or adding a domain.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

(2 more issues omitted due to length limits.)

Summary

Adds init to detect an app, sign in, create starter emails, install the SDK, write helpers, and report remaining work.

  • Init sets up Brew app email and project files from the app you choose.
  • The brew.new and create-brew launchers start the matching CLI version.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Detect app] --> B{Dry run?}
  B -->|Yes| C[Report planned changes]
  B -->|No| D[Read app key or browser sign-in]
  D --> E[Check brand and domains]
  E --> F[Set up starter emails]
  F --> G[Install SDK and write helpers]
  G --> H[Offer reviewed auth patches]
  H --> I[Configure editors and save project state]
  I --> J[Send test email]
  J --> K[Report remaining steps]
Loading

Reviews (7) · Last reviewed commit: "chore(release): init is 0.20.0; generate..." · Reviewed by Greptile

Comment thread src/lib/init/pipeline.ts Outdated
Comment thread src/lib/init/pipeline.ts
Comment on lines +633 to +635
kit = await client.appEmails.setUpKit(body, {
idempotencyKey: idempotencyKey('kit', body),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Recovery replays failed setup

After a partial kit failure, the suggested rerun sends the same body with the same saved projectId, so it reuses the same Idempotency-Key. The API returns the original response for 24 hours, including the failed entries. The rerun therefore cannot finish those keys during that window. Keep the key for uncertain request retries, but use a new attempt key after receiving a completed response with failed entries.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 633-635

Comment:
**Recovery replays failed setup**

After a partial kit failure, the suggested rerun sends the same body with the same saved `projectId`, so it reuses the same `Idempotency-Key`. The API returns the original response for 24 hours, including the failed entries. The rerun therefore cannot finish those keys during that window. Keep the key for uncertain request retries, but use a new attempt key after receiving a completed response with failed entries.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/patches.ts Outdated
Comment on lines +206 to +210
const passwordLines = next.flatMap((line, index) =>
/^\s*emailAndPassword\s*:\s*\{\s*$/.test(line) ? [index] : []
)
const passwordLine = passwordLines[0]
if (passwordLines.length === 1 && passwordLine !== undefined) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Password reset stays unwired

A valid Better Auth config with emailAndPassword: { enabled: true }, on one line passes the automatic-patch checks, but this regex never finds its password block. The patch still reports success without adding sendResetPassword, leaving password-reset email unwired. Handle the inline form or return a manual patch when the callback cannot be placed.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 206-210

Comment:
**Password reset stays unwired**

A valid Better Auth config with `emailAndPassword: { enabled: true },` on one line passes the automatic-patch checks, but this regex never finds its password block. The patch still reports success without adding `sendResetPassword`, leaving password-reset email unwired. Handle the inline form or return a manual patch when the callback cannot be placed.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/patches.ts
Comment on lines +311 to +313
if (normalized.includes('[auth.hook.send_email]')) {
return normalized.includes('/functions/v1/send-email')
? { patch: { ...meta, path, status: 'already-applied' } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Disabled hook looks ready

supabaseConfigPatch reports already-applied whenever the file contains the hook header and URL text. It does not check whether the section is commented out or has enabled = false. A disabled hook pointing at send-email therefore receives no patch or blocking next step. Check the active section and its enabled value before declaring it applied.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 311-313

Comment:
**Disabled hook looks ready**

`supabaseConfigPatch` reports `already-applied` whenever the file contains the hook header and URL text. It does not check whether the section is commented out or has `enabled = false`. A disabled hook pointing at `send-email` therefore receives no patch or blocking next step. Check the active section and its enabled value before declaring it applied.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/patches.ts
Comment on lines +323 to +327
const separator = normalized.endsWith('\n') ? '\n' : '\n\n'
return computed(
path,
text,
`${normalized}${separator}${SUPABASE_CONFIG_BLOCK}\n`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Supabase config becomes invalid

If supabase/config.toml already defines [functions.send-email] but has no send-email hook section, this patch appends another [functions.send-email] table. Repeating a TOML table makes the config invalid, so --apply-patches can break supabase start. Merge the existing function section or leave that part as a manual edit.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 323-327

Comment:
**Supabase config becomes invalid**

If `supabase/config.toml` already defines `[functions.send-email]` but has no send-email hook section, this patch appends another `[functions.send-email]` table. Repeating a TOML table makes the config invalid, so `--apply-patches` can break `supabase start`. Merge the existing function section or leave that part as a manual edit.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/pipeline.ts
Comment on lines +796 to +803
const kept = files.filter((file) => file.action === 'kept-user-version')
steps.push({
id: 'files',
status: dryRun ? 'planned' : 'done',
detail:
kept.length > 0
? `kept your version of ${kept.map((file) => file.path).join(', ')}`
: `${files.filter((file) => file.action === 'created' || file.action === 'updated').length} ${dryRun ? 'to write' : 'written'}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing files look written

Required file writes can return action: 'refused', but the pipeline still marks the files and state steps as done. A helper directory linked outside the project can leave a generated helper missing; a refused brew.json write loses the saved project ID and hashes. Mark refused required writes as failed and provide a recovery step before applying patches that depend on them.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 796-803

Comment:
**Missing files look written**

Required file writes can return `action: 'refused'`, but the pipeline still marks the `files` and `state` steps as done. A helper directory linked outside the project can leave a generated helper missing; a refused `brew.json` write loses the saved project ID and hashes. Mark refused required writes as failed and provide a recovery step before applying patches that depend on them.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/pipeline.ts
Comment on lines +1158 to +1161
const result = await sink.spawn(install.line.command, install.line.args, {
cwd: install.cwd,
signal: ctx.signal,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Windows SDK install fails

The new install step passes bare npm, pnpm and yarn commands to defaultSpawn, which always uses shell: false. On Windows these commands normally resolve to .cmd shims, which cannot be launched this way. Setup therefore fails to install the SDK and exits with a failed install step. Add a Windows-safe way to launch these shims while preserving argument quoting.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 1158-1161

Comment:
**Windows SDK install fails**

The new install step passes bare `npm`, `pnpm` and `yarn` commands to `defaultSpawn`, which always uses `shell: false`. On Windows these commands normally resolve to `.cmd` shims, which cannot be launched this way. Setup therefore fails to install the SDK and exits with a failed install step. Add a Windows-safe way to launch these shims while preserving argument quoting.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/pipeline.ts
Comment on lines +812 to +816
if (paths.auth !== null && project.auth === 'better-auth') {
computedPatches.push(betterAuthPatch(project, paths.auth, { lifecycle }))
}
if (paths.auth !== null && project.auth === 'authjs') {
computedPatches.push(authjsPatch(project, paths.auth))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Lifecycle setup breaks auth email

On a fresh Better Auth or Auth.js project, --sets lifecycle creates only lifecycle emails, but the pipeline still generates and offers patches for auth callbacks. Applying those patches routes auth emails to keys the kit did not create. Track whether the auth set was requested, and skip auth adapters and patches when it was not.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 812-816

Comment:
**Lifecycle setup breaks auth email**

On a fresh Better Auth or Auth.js project, `--sets lifecycle` creates only lifecycle emails, but the pipeline still generates and offers patches for auth callbacks. Applying those patches routes auth emails to keys the kit did not create. Track whether the auth set was requested, and skip auth adapters and patches when it was not.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/pipeline.ts Outdated
Comment on lines +13 to +18
'--app',
'--domain',
'--sets',
'--stack',
'--test-to',
])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Launcher skips setup

VALUE_FLAGS omits --app-url and --apply-patches. For example, npm create brew -- --app-url https://example.com --yes treats the URL as a subcommand and does not insert init. The CLI then rejects the init-only flag instead of starting setup. Add both flags to the shared launcher and sync its copies.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/launchers/launcher.mjs
Line: 13-18

Comment:
**Launcher skips setup**

`VALUE_FLAGS` omits `--app-url` and `--apply-patches`. For example, `npm create brew -- --app-url https://example.com --yes` treats the URL as a subcommand and does not insert `init`. The CLI then rejects the init-only flag instead of starting setup. Add both flags to the shared launcher and sync its copies.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/patches.ts
Comment on lines +305 to +308
next[at] = line.replace(/\bproviders\s*:\s*\[\s*/, (match) =>
/\[\s*\]/.test(line)
? `${match}brewEmailProvider`
: `${match}brewEmailProvider, `

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Auth patch breaks startup

authjsPatch checks the whole line for [] when deciding whether to add a comma. A valid line such as providers: [GitHub({ checks: [] })], takes the empty-list branch and becomes providers: [brewEmailProviderGitHub({ checks: [] })],. Applying that patch breaks the auth config and can stop the app from building or starting. Check whether the providers list itself is empty, not whether any empty array appears on the line.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 305-308

Comment:
**Auth patch breaks startup**

`authjsPatch` checks the whole line for `[]` when deciding whether to add a comma. A valid line such as `providers: [GitHub({ checks: [] })],` takes the empty-list branch and becomes `providers: [brewEmailProviderGitHub({ checks: [] })],`. Applying that patch breaks the auth config and can stop the app from building or starting. Check whether the providers list itself is empty, not whether any empty array appears on the line.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/commands/init.ts
'automations.triggers.getContract',
'send',
],
commandClass: 'destructive',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Setup has no default deadline

initCommand calls appEmails.setUpKit without declaring defaultTimeoutMs. The repository guide requires commands with long-running server work to declare the SDK's per-call timeout through lib/long-running.ts, which already lists this kit operation. Add the required timeout before merging so the whole setup has a default deadline.

Context Used: AGENTS.md (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/commands/init.ts
Line: 109

Comment:
**Setup has no default deadline**

`initCommand` calls `appEmails.setUpKit` without declaring `defaultTimeoutMs`. The repository guide requires commands with long-running server work to declare the SDK's per-call timeout through `lib/long-running.ts`, which already lists this kit operation. Add the required timeout before merging so the whole setup has a default deadline.

**Context Used:** AGENTS.md ([source](https://github.com/getbrew/brew-cli/blob/main/AGENTS.md))

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex Fix in Cursor

@thedogwiththedataonit thedogwiththedataonit changed the title init + the brew.new / create-brew launchers (C4) init + the brew.new / create-brew launchers (C4, 0.18.0) Oct 16, 2026
Comment thread src/lib/init/templates.ts
Comment on lines +150 to +152
ts
? "import { type BrewClient, createBrewClient } from '@brew.new/sdk'"
: "import { createBrewClient } from '@brew.new/sdk'",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 CommonJS helpers cannot load

In a JavaScript server with "type": "commonjs", init writes brew.js and brew-auth.js with import and export statements. Node cannot load these files, so the generated email helpers fail before they can send anything. Generate CommonJS code for these projects, or use .mjs files with matching imports.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/templates.ts
Line: 150-152

Comment:
**CommonJS helpers cannot load**

In a JavaScript server with `"type": "commonjs"`, `init` writes `brew.js` and `brew-auth.js` with `import` and `export` statements. Node cannot load these files, so the generated email helpers fail before they can send anything. Generate CommonJS code for these projects, or use `.mjs` files with matching imports.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/detect.ts
Comment on lines +380 to +387
const start = options.app === undefined ? cwd : resolve(cwd, options.app)
const resolved = resolveProjectRoot(start)
let root = resolved.root
let workspace = resolved.monorepo?.workspace ?? null
if (
resolved.monorepo !== null &&
workspace === null &&
options.app === undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Setup changes the wrong app

A missing or mistyped --app path can silently select a parent project. detectProject passes the path to resolveProjectRoot, which walks upward; supplying --app also skips the monorepo choice check. With --yes, setup can then install the SDK and write files in the parent app or monorepo root instead of the requested app. Reject an invalid explicit app path before falling back to a parent.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/detect.ts
Line: 380-387

Comment:
**Setup changes the wrong app**

A missing or mistyped `--app` path can silently select a parent project. `detectProject` passes the path to `resolveProjectRoot`, which walks upward; supplying `--app` also skips the monorepo choice check. With `--yes`, setup can then install the SDK and write files in the parent app or monorepo root instead of the requested app. Reject an invalid explicit app path before falling back to a parent.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

thedogwiththedataonit added a commit that referenced this pull request Oct 16, 2026
…ed send summary

- RELEASING app email SDK gate and the 0.17.0 changelog name the SDK
  versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's
  13.0.0; the CLI takes `^13.2.0`.
- `app-emails upsert` passes `--input` payloadSchema fields through as
  given, so `secret: true` on a one-time code or sign-in link reaches the
  API; a test pins it (typed against the vendored spec) and a new example
  shows it.
- `send`'s human summary reads the fire response through the vendored
  spec's TriggerFireAccepted: `notStarted[].code` (DOMAIN_REQUIRED, from
  #2059) is in the CLI's spec but not in SDK 13.2.0's types, which leave
  #2059 out. A test pins the not-started line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 16, 2026
…^13.2.0

The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64
(`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's
generated code imports the adapters (Better Auth, Auth.js, the Supabase
hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for
`@brew.new/sdk/resend`.

The test email step reads the fire response through the vendored spec's
TriggerFireAccepted: `notStarted[].code` (DOMAIN_REQUIRED, brew-v2#2059)
is not in SDK 13.2.0's types, which leave #2059 out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 16, 2026
The install step skipped any app that already declared @brew.new/sdk
12.3 or later, but the generated code needs ^13.2.0 (the adapters and
`appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the
files init writes (Greptile on #55). The threshold now comes from
SDK_RANGE itself; tests cover 12.3, 13.1, 13.2 and 14.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 16, 2026
The install step skipped any app that already declared @brew.new/sdk
12.3 or later, but the generated code needs ^13.2.0 (the adapters and
`appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the
files init writes (Greptile on #55). `reachesSdkRange` (templates.ts,
next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover
12.3, 13.1, 13.2 and 14.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 16, 2026
…ted code

- RELEASING app email SDK gate and the 0.17.0 changelog name the SDK
  versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's
  13.0.0; the CLI takes `^13.2.0`.
- `app-emails upsert` passes `--input` payloadSchema fields through as
  given, so `secret: true` on a one-time code or sign-in link reaches the
  API; a test pins it (typed against the vendored spec) and a new example
  shows it.
- A test pins `send`'s not-started line naming `DOMAIN_REQUIRED` (#2059),
  which SDK 13.2.0 types now that its spec includes #2059.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 16, 2026
…^13.2.0

The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64
(`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's
generated code imports the adapters (Better Auth, Auth.js, the Supabase
hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for
`@brew.new/sdk/resend`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 16, 2026
The install step skipped any app that already declared @brew.new/sdk
12.3 or later, but the generated code needs ^13.2.0 (the adapters and
`appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the
files init writes (Greptile on #55). `reachesSdkRange` (templates.ts,
next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover
12.3, 13.1, 13.2 and 14.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/lib/init/templates.ts
: []),
' */',
'export const brewEmails = brewAuthEmails({',
' client: getBrew,',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Worker auth emails fail

On a Hono Worker using Better Auth or Auth.js, authHelperFile passes getBrew directly to the email adapter. The generated Worker version requires the request's env, but neither adapter setup supplies it. TypeScript can reject the callback, and calling it without env fails at env.BREW_API_KEY, so auth emails cannot send.

Generate an adapter factory that takes the request's env and passes a zero-argument client callback. Both the Better Auth and Auth.js templates need this.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/templates.ts
Line: 342

Comment:
**Worker auth emails fail**

On a Hono Worker using Better Auth or Auth.js, `authHelperFile` passes `getBrew` directly to the email adapter. The generated Worker version requires the request's `env`, but neither adapter setup supplies it. TypeScript can reject the callback, and calling it without `env` fails at `env.BREW_API_KEY`, so auth emails cannot send.

Generate an adapter factory that takes the request's `env` and passes a zero-argument client callback. Both the Better Auth and Auth.js templates need this.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/detect.ts
Comment on lines +192 to +198
if (
has('vite') &&
(has('react') ||
has('@vitejs/plugin-react') ||
has('@vitejs/plugin-react-swc'))
) {
return { framework: 'vite-react', dependency: 'vite' }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Server setup gets skipped

If a TanStack Start app declares vite and react, this branch returns before the later @tanstack/react-start check. detectProject calls it a browser-only app, so init skips installing the SDK and writes no server client or auth helper, despite the app having a server.

Check known full-stack frameworks before the generic Vite/React branch.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/detect.ts
Line: 192-198

Comment:
**Server setup gets skipped**

If a TanStack Start app declares `vite` and `react`, this branch returns before the later `@tanstack/react-start` check. `detectProject` calls it a browser-only app, so `init` skips installing the SDK and writes no server client or auth helper, despite the app having a server.

Check known full-stack frameworks before the generic Vite/React branch.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

thedogwiththedataonit added a commit that referenced this pull request Oct 20, 2026
…ted code

- RELEASING app email SDK gate and the 0.17.0 changelog name the SDK
  versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's
  13.0.0; the CLI takes `^13.2.0`.
- `app-emails upsert` passes `--input` payloadSchema fields through as
  given, so `secret: true` on a one-time code or sign-in link reaches the
  API; a test pins it (typed against the vendored spec) and a new example
  shows it.
- A test pins `send`'s not-started line naming `DOMAIN_REQUIRED` (#2059),
  which SDK 13.2.0 types now that its spec includes #2059.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 20, 2026
…^13.2.0

The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64
(`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's
generated code imports the adapters (Better Auth, Auth.js, the Supabase
hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for
`@brew.new/sdk/resend`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 20, 2026
The install step skipped any app that already declared @brew.new/sdk
12.3 or later, but the generated code needs ^13.2.0 (the adapters and
`appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the
files init writes (Greptile on #55). `reachesSdkRange` (templates.ts,
next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover
12.3, 13.1, 13.2 and 14.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 20, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thedogwiththedataonit thedogwiththedataonit changed the title init + the brew.new / create-brew launchers (C4, 0.18.0) init + the brew.new / create-brew launchers (C4, 0.19.0) Oct 20, 2026
thedogwiththedataonit added a commit that referenced this pull request Oct 21, 2026
…ted code

- RELEASING app email SDK gate and the 0.17.0 changelog name the SDK
  versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's
  13.0.0; the CLI takes `^13.2.0`.
- `app-emails upsert` passes `--input` payloadSchema fields through as
  given, so `secret: true` on a one-time code or sign-in link reaches the
  API; a test pins it (typed against the vendored spec) and a new example
  shows it.
- A test pins `send`'s not-started line naming `DOMAIN_REQUIRED` (#2059),
  which SDK 13.2.0 types now that its spec includes #2059.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 21, 2026
…^13.2.0

The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64
(`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's
generated code imports the adapters (Better Auth, Auth.js, the Supabase
hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for
`@brew.new/sdk/resend`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 21, 2026
The install step skipped any app that already declared @brew.new/sdk
12.3 or later, but the generated code needs ^13.2.0 (the adapters and
`appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the
files init writes (Greptile on #55). `reachesSdkRange` (templates.ts,
next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover
12.3, 13.1, 13.2 and 14.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 21, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/lib/init/pipeline.ts
const nextSteps: NextStep[] = []
const sets: readonly AppEmailKitSet[] = options.sets ?? ['auth', 'lifecycle']
const lifecycle = sets.includes('lifecycle')
const stack = options.stack ?? kitStackFor(project.auth)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Selected auth library stays unwired

--stack changes the kit request, but generated helpers and patches still follow project.auth. For example, --stack better-auth in an app detected as none creates the Better Auth kit but generates sendAuthEmail instead of brewEmails and offers no Better Auth patch. The user must wire the selected library by hand. Use the selected stack consistently for email helpers and patches, or reject conflicting selections.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 241

Comment:
**Selected auth library stays unwired**

`--stack` changes the kit request, but generated helpers and patches still follow `project.auth`. For example, `--stack better-auth` in an app detected as `none` creates the Better Auth kit but generates `sendAuthEmail` instead of `brewEmails` and offers no Better Auth patch. The user must wire the selected library by hand. Use the selected stack consistently for email helpers and patches, or reject conflicting selections.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/patches.ts
Comment on lines +165 to +167
if (text.includes('brewEmails')) {
return {
patch: { ...meta, path: config.path, status: 'already-applied' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Welcome emails stay unwired

betterAuthPatch treats any mention of brewEmails as proof that all requested callbacks are wired. Run setup with --sets auth and apply its patch, then rerun with both sets: the kit adds lifecycle emails, but this check skips adding databaseHooks.user.create.after. The welcome email never runs, and setup leaves no blocking step to add it. Check the callbacks needed for the current sets and offer a patch or manual step for missing ones.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/patches.ts
Line: 165-167

Comment:
**Welcome emails stay unwired**

`betterAuthPatch` treats any mention of `brewEmails` as proof that all requested callbacks are wired. Run setup with `--sets auth` and apply its patch, then rerun with both sets: the kit adds lifecycle emails, but this check skips adding `databaseHooks.user.create.after`. The welcome email never runs, and setup leaves no blocking step to add it. Check the callbacks needed for the current sets and offer a patch or manual step for missing ones.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/pipeline.ts
id: 'send-test-email',
actor: 'agent',
kind: 'run-command',
command: `${input.prefix} send ${key} --to ${to} --yes`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Test retry loses its account

The test-email retry command drops the credentials and API settings used by setup. init can use an app key from .env.local, but send only reads flags, environment variables, or CLI config. If only the app key is saved locally, this retry exits with an auth error. If a different CLI key is saved, it targets the wrong account. Build a retry that preserves the setup context without printing the key, so users do not have to reconstruct those settings.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 1449

Comment:
**Test retry loses its account**

The test-email retry command drops the credentials and API settings used by setup. `init` can use an app key from `.env.local`, but `send` only reads flags, environment variables, or CLI config. If only the app key is saved locally, this retry exits with an auth error. If a different CLI key is saved, it targets the wrong account. Build a retry that preserves the setup context without printing the key, so users do not have to reconstruct those settings.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

thedogwiththedataonit and others added 10 commits October 25, 2026 22:29
`brew-cli init` (destructive; `--dry-run --json` plans, `--yes --json` runs):
detect (package manager, monorepo, framework, auth library + Auth.js
version, Stripe, existing email providers, editors, cloud shells) → sign
in through the browser for a CLI key and an app key (`--no-wait` exits 3
with the link and code, `--resume` continues) → .env.local (app key only,
0600, git-ignored first) → brand → domain (sandbox report, `--domain` adds
one and prints DNS records, never waits) → `appEmails.setUpKit` for the
detected stack → install @brew.new/sdk with the project's package
manager → create-only managed files (lib/brew.ts, lib/brew-auth.ts per
auth library, lib/brew-contracts.ts with the key registry, the Supabase
Send Email Hook function, .env.example, brew.json with hashes) → patches
for the user's auth config (applied only with --apply-patches when the
file still hashes to the plan) → MCP for detected editors → AGENTS.md
block and, in Claude Code projects, the skills → a sandbox test email.

One JSON report (schemas/init-report.json, schemaVersion 1) with steps,
files, patches, nextSteps (actor agent|human) and agentInstructions; exit
0 complete/action_required, 1 failed, 2 usage/ambiguous monorepo, 3 sign-in
required, 4 confirmation. Idempotency keys `init:<projectId>:<step>:<body
hash>`, so identical re-runs replay. Keys are masked everywhere.

Tests: detection matrix over 16 fixture projects; MSW end-to-end runs
(exit 4, dry run with zero mutating requests, full run with exact request
bodies, files, brew.json and schema, re-run unchanged with the same keys,
exit 3 + resume, monorepo exit 2, per-stack runs); generated files
type-checked against the installed SDK under a strict tsconfig (with a
negative control); a secret canary on every run's stdout and stderr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
packages/launchers/{brew.new,create-brew}: each runs @brew.new/cli in the
same process, pinned to exactly the CLI's version. The shared launcher
inserts `init` when no subcommand is given (create-brew also for a bare
--help), and sets BREW_CLI_INVOKED_AS (`npx -y brew.new@<version>`,
`npm create brew@latest --`) so every printed command re-enters through
the launcher.

- scripts/sync-launchers.ts [--check]: version, exact CLI pin, launcher
  copy and LICENSE per package (CI + release gate).
- scripts/smoke-launchers.ts: npm pack the CLI and both launchers, install
  into a scratch project (offline, or --registry after the CLI
  publishes), run --version and --help.
- release.yml publishes both launchers after the CLI, skipping a version
  npm already has. Nothing is published from this change.
- Unit tests for the argv rules, the invocation prefix round trip and
  launch(); package metadata pinned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- package.json and both launchers go to 0.18.0 (`sync:launchers`), and
  init's notes get their own 0.18.0 section.
- `types` (and init's `lib/brew-contracts.ts`) now read the platform's
  TypeScript contract per trigger, so the init tests serve
  `GET /v1/automations/triggers/{id}/contract` from the same trigger rows
  (tests/helpers/contract-artifacts.ts).
- The agent sign-in tests pin the fake clock: the login test server's
  session expires 10 minutes after a fixed instant, so on the real clock
  they started failing once that date passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of init against the plan and the platform:

- release.yml: the launcher step runs after the CLI is published and never
  fails the release. Sync check, pack smoke and registry smoke moved into
  it; a version npm already has is skipped, and a failed check or publish
  (a name the token does not own yet) leaves a warning. CI still gates
  sync and pack smoke.
- Generated files, the AGENTS.md block, the brew-app-emails skill, README
  and the report schema say `npx @brew.new/cli` (on npm today) instead of
  `npx brew.new`, whose name is not ours yet.
- `--apply-patches <id>@<hash>,…`: the report's nextStep carries the hash
  of the file the diff was computed on, and a file edited since then is
  reported `stale` and left alone. Re-run commands drop a flag's value
  with the flag.
- The Auth.js patch is manual when the config has no database adapter
  (Auth.js refuses an email provider without one, breaking all sign-in).
- After a browser sign-in the run acts as the new app key, so the kit,
  contracts and test email land in the brand that key is bound to, even
  when an explicit key of another brand started the run.
- lib/brew.ts reads the key through `$env/dynamic/private` (SvelteKit) and
  `import.meta.env` (Astro); Express, Hono and plain Node get a blocking
  step to load .env.local. SvelteKit and Astro join the typecheck matrix.
- A test email that does not go out is a non-blocking retry step, and the
  dry-run names the key the real run would send.
- `--app-url` is shown on the approval page so the right brand is picked.
- create-brew pins its version in BREW_CLI_INVOKED_AS
  (`npm create brew@<version> --`), and the launcher smoke runs the packed
  launchers under Node, including an `init --dry-run --json` argv check.
- The generated code installs `@brew.new/sdk@^12.5.0` (npm's 12.3.0 has
  no `send` or `appEmails`); the Better Auth patch no longer promises a
  reset hook it could not add; a git-tracked .env.local gets the
  `git rm --cached` hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…^13.2.0

The SDK numbering is final: #55 (`send`) ships as 13.1.0 and #64
(`appEmails`, the auth adapters) as 13.2.0, after main's 13.0.0. init's
generated code imports the adapters (Better Auth, Auth.js, the Supabase
hook), so SDK_RANGE is `^13.2.0`; `migrate resend` shares it for
`@brew.new/sdk/resend`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The install step skipped any app that already declared @brew.new/sdk
12.3 or later, but the generated code needs ^13.2.0 (the adapters and
`appEmails`), so an app on 12.3 to 13.1 kept an SDK that cannot load the
files init writes (Greptile on #55). `reachesSdkRange` (templates.ts,
next to SDK_RANGE) takes the threshold from SDK_RANGE itself; tests cover
12.3, 13.1, 13.2 and 14.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…red pager

main published 0.17.0, so #54 is 0.18.0 and this release (with the
launchers, synced) is 0.19.0. `init` writes the contracts file through the
`types` pager, which now walks main's cycle-safe `collectAll` with the
command context, and passes the command's stop signal to the contract
reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `--domain` on a domain that already publishes another DKIM key where
  Brew asks for its record (POST /v1/domains `warnings[]`
  DKIM_SELECTOR_IN_USE, read structurally since the published SDK's row
  type predates it): the report swaps the DNS-records step for a
  `dkim-selector-in-use` step naming the first suggested subdomain.
- A kit key refused with APP_EMAIL_TRIGGER_IN_USE gets a human
  `trigger-in-use:<key>` review step (the server's message names the
  automations) ahead of the re-run step.
- CHANGELOG names brew-v2 #2169 @ 6f8ef50544 as the API side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… ^13.6.0

npm has CLI 0.18.0 and SDK 13.1.0-13.3.0 without the DX features, so the
DX numbers move up: this PR is 0.20.0 (after #54's 0.19.0) and SDK_RANGE is
the adapters release, 13.6.0. A declared ^13.3.0 (published, no adapters)
now installs ^13.6.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 25, 2026
…ted code

- RELEASING app email SDK gate and the 0.17.0 changelog name the SDK
  versions: #55 ships as 13.1.0 and #64 as 13.2.0, after the SDK's
  13.0.0; the CLI takes `^13.2.0`.
- `app-emails upsert` passes `--input` payloadSchema fields through as
  given, so `secret: true` on a one-time code or sign-in link reaches the
  API; a test pins it (typed against the vendored spec) and a new example
  shows it.
- A test pins `send`'s not-started line naming `DOMAIN_REQUIRED` (#2059),
  which SDK 13.2.0 types now that its spec includes #2059.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 25, 2026
…ANGE ^13.6.0

After main's published 0.18.0 the DX numbers move up one: #54 0.19.0, #55
0.20.0, this PR 0.21.0. A declared ^13.3.0 or ^13.5.0 still gets the
install-sdk next step; ^13.6.0 does not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thedogwiththedataonit added a commit that referenced this pull request Oct 25, 2026
After main's published 0.18.0 the DX numbers move up one (#54 0.19.0, #55
0.20.0, #61 0.21.0, this PR 0.22.0), and the SDK's DX releases follow its
published 13.3.0 and pending 13.4.0: adapters 13.6.0 (SDK_RANGE), connect
13.7.0. doctor --project's tests use those ranges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/lib/init/pipeline.ts
Comment on lines +559 to +572
let selectorInUse: DkimSelectorInUse | undefined
if (options.domain !== undefined && target === undefined) {
if (dryRun) {
domain.name = options.domain
} else {
const body = {
name: options.domain,
sendingPurpose: 'transactional' as const,
}
target = await client.domains.add(body, {
idempotencyKey: idempotencyKey('domain', body),
})
domain.added = true
selectorInUse = dkimSelectorInUse(target)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Reruns forget DKIM conflicts

A rerun with the same --domain loses the DKIM_SELECTOR_IN_USE warning. selectorInUse is set only after domains.add, so finding the existing domain skips that check and produces an add-dns-records step. Following that step can replace another sender's DKIM key and stop its emails from passing authentication.

Keep the warning across reruns and withhold conflicting records from both report formats until they are safe to publish.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 559-572

Comment:
**Reruns forget DKIM conflicts**

A rerun with the same `--domain` loses the `DKIM_SELECTOR_IN_USE` warning. `selectorInUse` is set only after `domains.add`, so finding the existing domain skips that check and produces an `add-dns-records` step. Following that step can replace another sender's DKIM key and stop its emails from passing authentication.

Keep the warning across reruns and withhold conflicting records from both report formats until they are safe to publish.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

Comment thread src/lib/init/pipeline.ts
steps.push({ id: 'domain', status: 'planned', detail: afterSignIn })
} else {
try {
const list = await client.domains.list({ limit: 100 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Setup misses existing domains

init reads only the first 100 domains before deciding whether --domain already exists. If the requested domain is on a later page, setup calls domains.add again. The API returns DOMAIN_ALREADY_EXISTS, so a valid rerun gets a failed domain step instead of the existing domain's records. Setup can also miss a verified sending domain on a later page.

Follow the remaining pages before choosing or adding a domain.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/init/pipeline.ts
Line: 546

Comment:
**Setup misses existing domains**

`init` reads only the first 100 domains before deciding whether `--domain` already exists. If the requested domain is on a later page, setup calls `domains.add` again. The API returns `DOMAIN_ALREADY_EXISTS`, so a valid rerun gets a failed domain step instead of the existing domain's records. Setup can also miss a verified sending domain on a later page.

Follow the remaining pages before choosing or adding a domain.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

@thedogwiththedataonit thedogwiththedataonit changed the title init + the brew.new / create-brew launchers (C4, 0.19.0) init + the brew.new / create-brew launchers (C4, 0.20.0) Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant