Source rename across the monorepo. Every publishable package now ships
under its @hexclave/* name natively, no rewrite-at-publish indirection.
Workflow + tooling:
- Delete scripts/rewrite-packages-to-hexclave.ts (one-shot mirror).
- Remove the mirror-publish block from .github/workflows/npm-publish.yaml.
The remaining `pnpm publish -r` step publishes @hexclave/* natively.
- Flip the auto-bump changeset target from @stackframe/stack to
@hexclave/next so 'Update package versions on dev' keeps working.
- Delete packages/template/src/internal/deprecation-warning.ts and its
imports — @hexclave/* never warns about itself, and after PR 3 no
@stackframe/* artifact is ever built from source again.
Package renames (publishable):
@stackframe/react → @hexclave/react
@stackframe/stack → @hexclave/next
@stackframe/js → @hexclave/js
@stackframe/stack-shared → @hexclave/shared
@stackframe/stack-ui → @hexclave/ui
@stackframe/stack-sc → @hexclave/sc
@stackframe/stack-cli → @hexclave/cli
@stackframe/tanstack-start → @hexclave/tanstack-start
@stackframe/dashboard-ui-components → @hexclave/dashboard-ui-components
Internal monorepo packages (private, never published) also renamed for
brand consistency: backend, dashboard, docs, mcp, skills, e2e-tests,
example apps, the swift-sdk, the monorepo root, etc. Cost is mechanical;
payoff is no stray @stackframe/* names left under apps/, examples/, sdks/.
Carve-outs intentionally kept under their legacy names:
- @stackframe/emails — virtual module imported by customer-stored email
templates; the renderer in apps/backend/src/lib/email-rendering.tsx
dual-aliases both names to the same backing module indefinitely.
- @stackframe/template — internal codegen source, never published; per
docs-mintlify/migration.mdx 'internal packages keep names'.
- @stackframe/init-stack — deprecated; now marked private: true so the
last published version on npm continues to serve old install commands
but the workspace stops publishing it.
Backward-compat detection (so projects still on the last @stackframe/*
release keep working):
- packages/stack-shared/src/config-rendering.ts — CONFIG_IMPORT_PACKAGES
table includes both @hexclave/* (canonical, first match wins) and
legacy @stackframe/* names. Function renamed
detectStackframeImportPackage → detectConfigImportPackage.
- apps/dashboard/src/lib/github-config-push.ts — import detection regex
now matches both @hexclave/<name> and @stackframe/<name>, hexclave
preferred.
Versions: every renamed package reset to 1.0.0 in source. The repo's
existing 'bump versions before merging to main' flow will move them to
1.0.1 on the first publish run, so the dual-publish 1.0.0 from PR 2 is
not overwritten.
Other touch-ups discovered during sweep:
- Root package.json: 'fern' script filter was @stackframe/docs (legacy
typo, never resolved) → @hexclave/docs.
- README.md contributor note: @stackframe/XYZ → @hexclave/XYZ.
- packages/stack-cli/package.json: register `hexclave` bin alongside
the legacy `stack` bin so `npx @hexclave/cli init` works on the
natively-published artifact (PR 1481's rewrite script did this at
publish time; now it's in source).
- packages/template/package-template.json: per-platform names + version
flipped to hexclave + 1.0.0 to stay in sync with generated package.json.
- docs/package.json (legacy fumadocs folder, otherwise carved out of the
brand sweep): workspace deps and name updated minimally so `pnpm
install` resolves — content (MDX) intentionally untouched per the
PR 2 scoping decision.
Carve-out files (skipped entirely by the sweep, intentional history):
- docs-mintlify/migration.mdx — teaches the rename, references both.
- RENAME-TO-HEXCLAVE.md — planning doc, references both indefinitely.
- legacy docs/ folder — content untouched per PR 2 carve-out.
generate-sdks regenerated packages/{react,stack,js} from template.
pnpm-lock.yaml regenerated. Typecheck green on stack-shared, stack, js,
react. Dashboard typecheck has pre-existing 'X is of type unknown'
errors that need to be investigated separately (likely a local
node_modules build state issue, not source).
Rebased onto dev after PR 1475 (cl/hexclave-pr1) was squash-merged.
Squashes the original 46-commit branch (including PR1-duplicate commits
that arrived via cherry-picks/merges) into a single commit containing
only PR2's net delta over dev.
Original PR 1481 head: 94872de407873a1cabd4085deb21b69afe8d7699
(kept locally at backup/cl-romantic-mendel-5a2c25-pre-rebase)
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
### Summary
Reworks `stack init` UX, adds Sentry error reporting to the CLI,
polishes the emulator start flow, and overhauls the local-emulator
dashboard's "Open config file" dialog.
#### `stack init` flow
- **New top-level flow.** Drops the old "link existing vs. create new
local" fork. `init` now asks *where* to create the project — "Stack Auth
Cloud" or "Local". Adds a new `create-cloud` mode that logs the user in,
creates a cloud project, mints keys, and writes `.env` — no round-trip
through the dashboard.
- **Conditional emulator-install warning.** The "Local" choice label
only shows "(requires local emulator installation, ~1.3gb storage
required)" when the QEMU image isn't already on disk; otherwise it shows
"(emulator already installed)". Driven by a new
`isEmulatorImageInstalled()` helper in `commands/emulator.ts`.
- **Auto-create on zero-projects.** When the link-from-cloud path hits
an empty project list, the CLI now prompts *"You don't have any Stack
Auth projects yet. Would you like to create one?"* and, on yes, runs the
same flow as `stack project create`. Skips the pointless "select a
project" prompt when we just created one.
- **MCP-server notice.** Before invoking the coding agent, the CLI
announces that it's also registering the Stack Auth MCP server
(`mcp.stack-auth.com`) so the agent can answer Stack-specific questions
going forward.
- **Local-emulator env header.** When `writeProjectKeysToEnv` runs in
`local` mode it writes a 3-line comment header above the keys explaining
they're emulator-only and only valid while the emulator is running.
- **"What's next" footer.** After setup finishes, prints a short
orientation block: where the sign-up/sign-in routes live
(`/handler/sign-up`, `/handler/sign-in`), how to start the local
emulator (for `create` mode), a dashboard deep link for cloud projects
(respects `STACK_DASHBOARD_URL`), and a docs link.
#### Sentry error reporting (`lib/sentry.ts`, `index.ts`,
`tsdown.config.ts`)
- New `lib/sentry.ts` initializes `@sentry/node` with PII scrubbing
(Stack key prefixes, JWTs, home-dir paths, sensitive field names like
`token`/`secret`/`password`/`dsn`).
- DSN is baked at build time via a tsdown `define` sentinel
(`__STACK_CLI_SENTRY_DSN__`) — no DSN in source, no runtime env-var
dependency for installed users. CI sets `STACK_CLI_SENTRY_DSN_BUILD`
before `pnpm build`.
- Disabled when `NODE_ENV=development` or `CI`. No user opt-out.
- Wired into `main()`'s catch (only for unexpected errors —
`CliError`/`AuthError` still print and exit cleanly) plus
`uncaughtException` and `unhandledRejection` handlers via a
`handleFatal` helper.
#### `stack emulator start` welcome
- After a fresh start (not when reusing a running VM, not when
`--config-file` keeps stdout JSON-only), prints a short "Emulator is up"
block with service URLs (dashboard / backend / inbucket) and common
commands (`status`, `stop`, `reset`, `run`).
#### Local-emulator dashboard "Open config file" dialog
The dialog at `http://localhost:26700` (when no project is loaded) used
to be a single text input asking for an absolute path, with no
explanation of where that path comes from.
**Backend**
(`apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`):
- POST is now tolerant of directory paths or paths that don't end in
`.ts`/`.js`/`.mjs` — it appends `stack.config.ts` and creates the file
if missing (`writeConfigToFile` mkdir's parents). Lets users paste a
project folder instead of hunting for the config file.
- New GET endpoint returns up to 20 most-recent `LocalEmulatorProject`
rows joined with their display names, sorted by `updatedAt` desc. Same
`isLocalEmulatorEnabled()` + client-auth gating as POST.
**Dashboard**
(`apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx`):
- Title changed to "Open your Stack Auth project". Description now
explicitly ties the file to `stack init`: *"Point the local dashboard at
the `stack.config.ts` in your project. If you just ran `stack init`, it
was created at the root of that project."*
- Added: *"Don't have one yet? Paste your project folder path instead
and we'll create stack.config.ts for you."*
- Recent-projects list (clickable rows that prefill the input) fetched
from the new GET endpoint when the dialog opens.
- OS-specific copy-path tip below the input (macOS ⌥-Copy as Pathname,
Windows Shift+RC Copy as path, Linux `realpath`).
- "Open project" button is disabled when the input is empty.
- All error paths (empty input, non-absolute path, server errors,
exceptions) surface via destructive toasts instead of throwing.
Why no native file picker: browsers do not expose absolute filesystem
paths from `<input type="file">`, drag-and-drop, or the File System
Access API. The backend requires an absolute path, so a Finder-style
picker isn't possible from a web page. The recent list + OS tips are the
workaround.
### Goal
The previous `init` flow dead-ended new users: if you had no project you
got an error telling you to go create one in the dashboard and come
back. The happy path also forced a choice between "link existing" and
"create local emulator" — not the question most users are trying to
answer. The emulator dashboard's open-project dialog had similar
friction: an unexplained path field with no recall of previously-opened
projects. And the CLI silently swallowed unexpected errors with no
telemetry. This branch makes the first-run path work end-to-end from the
terminal, gives the emulator dashboard a usable open-project surface,
and turns CLI crashes into actionable bug reports.
### How to review
- Start with `packages/stack-cli/src/commands/init.ts` — the whole
user-facing flow lives in `runInit`. Mode dispatch at the top,
`handleCreateCloud` is the new cloud branch, `printNextSteps` is the
footer, the MCP notice prints right before `runClaudeAgent`.
- `packages/stack-cli/src/lib/sentry.ts` is small and self-contained;
the sentinel-replacement contract is in `tsdown.config.ts`'s `define`
block. Confirm `dist/index.js` contains zero `__STACK_CLI_SENTRY_DSN__`
occurrences after a build with the env var unset, and the actual DSN
host after a build with it set.
- `packages/stack-cli/src/commands/emulator.ts` —
`printEmulatorWelcome()` is the welcome block;
`isEmulatorImageInstalled()` is the new exported helper used by
`init.ts`.
-
`apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`
— the directory-tolerance branch is in the POST handler around the
`looksLikeConfigFile` check; the GET handler is appended at the bottom.
-
`apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx`
— dialog markup, recent-list fetch effect, `pathCopyTip` memo, and the
toast-based error handling in `handleOpenConfigFile`.
- Non-interactive (CI) paths stay strict: empty-project list still
errors with a pointer to `stack project create --display-name`. No
surprise project creation in CI.
- No tests. The CLI has no harness for the interactive flow;
verification is manual.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Recent local emulator projects listed in the config dialog for quick
selection.
* New CLI create-cloud mode and --display-name flag; interactive cloud
project creation and clearer next steps.
* Emulator start shows a welcome banner with service URLs when a new
instance starts.
* **Improvements**
* Config dialog UX, validation, error-toasting, and platform-aware copy
refined; “Open project” disabled for empty/invalid paths.
* CLI: centralized interactive project creation and improved fatal error
handling.
* **Chores**
* Sentry added and initialized for CLI error reporting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Bilal Godil <bg2002@gmail.com>
## Summary
`stack emulator start` now resumes a fully-warm VM snapshot instead of
cold-booting, bringing startup from 30–120s down to ~5–8s with
per-install secret rotation, or ~2.5s with rotation opt-out. The
snapshot is captured **locally on first `stack emulator pull`**, not
shipped from CI — QEMU migration state isn't portable across
accelerators (KVM/HVF/TCG) or `-cpu max` feature sets, so a CI-captured
snapshot couldn't resume reliably on arbitrary user hardware.
Also bundles a pile of CLI QoL fixes (progress bars, PR/run artifact
pulls, PR-build download, native-TS ISO writer replacing
`hdiutil`/`mkisofs`/`genisoimage` host dep, unit tests).
| Scenario | Before | After |
|---|---|---|
| Cold boot (no snapshot) | 30–120s | same, works as fallback |
| `stack emulator pull` (one-time, includes local snapshot capture) |
~30s download | ~30s download + ~1–3 min cold-boot capture |
| Snapshot resume, normal start | — | **~5–8s** |
| Snapshot resume, `EMULATOR_NO_ROTATION=1` | — | **~2.5s** |
Backend (`/health?db=1`) and dashboard (`/handler/sign-in`) return 200
on all paths. Two successive snapshot resumes produce different rotated
PCK/SSK/SAK/CRON_SECRET values per install.
## How it works
**Build (CI)** — `docker/local-emulator/qemu/build-image.sh`:
1. Cloud-init provisioning runs to completion (migrations, seed,
slim-image) producing `stack-emulator-<arch>.qcow2`.
2. Image is built with a topology compatible with later snapshot capture
(pinned SMP=4, phantom seed/bundle ISOs, STACKCFG runtime ISO mounted at
build time, qemu-guest-agent running, placeholder hex secrets baked in
under `STACK_EMULATOR_BUILD_SNAPSHOT=1`).
3. CI publishes **only the qcow2** — no `.savevm.zst` ships.
**Pull (user's machine)** —
`packages/stack-cli/src/commands/emulator.ts` + `run-emulator.sh
capture`:
1. `stack emulator pull` downloads the qcow2 with a progress bar (or
from a PR / workflow run via `--pr` / `--run`).
2. CLI invokes `run-emulator.sh capture`: cold-boots the qcow2 with a
matching device layout (phantom ISOs, fsdev, pcie-root-port, virtfs
detached — migration-incompatible), waits for backend+dashboard health,
then drives QMP: `stop` → set `mapped-ram` + `multifd` caps → `migrate
file:state.raw` → poll `query-migrate` → `quit`. Raw mapped-ram file is
zstd-compressed to `stack-emulator-<arch>.savevm.zst` in the images dir.
3. `--skip-snapshot` opts out (first `start` will then cold-boot).
**Runtime** — `run-emulator.sh start`:
1. Launch QEMU with `-incoming defer` when a `.savevm.zst` is present;
decompress on first use, keep the `.raw` cached for subsequent starts.
2. QMP: same `mapped-ram` + `multifd` caps → `migrate-incoming
file:<.raw>` → poll for `paused` → `cont`.
3. Generate fresh per-install secrets on the host; pipe them
base64-encoded through QGA `guest-exec input-data` →
`trigger-fast-rotate` in the guest → `docker exec -e … rotate-secrets`.
4. `rotate-secrets` in the container: validate keys (hex-only), targeted
`sed` on the placeholder PCK across built JS, `UPDATE ApiKeySet`,
`supervisorctl restart stack-app cron-jobs` (with
`stopasgroup`/`killasgroup` so the Node children actually die and
release their ports).
5. Poll backend+dashboard health; if anything fails, clean up and fall
back to cold boot transparently.
**Security model**: placeholder hex values are baked into the snapshot
(`00…ff` PCK, `00…ee` SSK, `00…dd` SAK, `00…cc` CRON_SECRET). They are
non-secret by construction. Real per-install secrets are generated at
each `emulator start` and never leave the host.
## CLI changes (`packages/stack-cli`)
- **`src/lib/iso.ts`** (new): native TypeScript ISO 9660 + Joliet
writer, replacing the host-side `hdiutil`/`mkisofs`/`genisoimage`
dependency for generating the STACKCFG runtime config disk. Unit tests
in `src/lib/iso.test.ts`.
- **`src/commands/emulator.ts`**:
- `pull`: streamed downloads with progress bar + ETA; `--pr <number>`
and `--run <id>` to pull from a PR build's CI artifacts (uses
`extract-zip` for the nested zip); `--skip-snapshot` to opt out of the
one-time local capture.
- `start` (existing, extended): auto-pulls AND auto-captures when no
image exists, so first-ever `start` is self-bootstrapping; emits
`STACK_EMULATOR_CLI_WROTE_ISO=1` so the shell helper skips its own ISO
regen (avoids the genisoimage host dep).
- `capture` (new, invoked by `pull` and the auto-pull path of `start`):
drives the local snapshot capture via `run-emulator.sh`.
- `status`, `stop`, `reset`, `list-releases`: preflight +
path-resolution tightening (`STACK_EMULATOR_HOME` → images/run dirs).
- Unit tests in `src/commands/emulator.test.ts`.
- **`EMULATOR_NO_ROTATION=1`** env var skips the post-resume rotation
(intended for tests/CI where the placeholder secrets are fine — comes
with a loud warning).
## CI (`.github/workflows/qemu-emulator-build.yaml`)
- Builds **QEMU 10.2.2 from source** (cached), because
`mapped-ram`/`multifd` migration capabilities aren't available in the
distro's QEMU. Enables KVM on ubicloud runners so amd64 boots at
hardware speed.
- amd64 + arm64 both build on the same amd64 matrix
(`ubicloud-standard-8`); arm64 runs under cross-arch TCG (provisioning
only — boot/verify smoke test is amd64-only).
- Verification now runs through the CLI: `emulator start` → `emulator
status` → `emulator stop` against the freshly-built qcow2 (via
`STACK_EMULATOR_HOME` pointing at the workspace, so the CLI doesn't
silently auto-pull a prior release).
- Packages **only** the qcow2. No `.savevm.zst` upload / publish.
- Release notes updated.
## Key files
**Shell / guest:**
- `docker/local-emulator/qemu/build-image.sh` — snapshot-compatible
device topology + STACKCFG runtime ISO at build time
- `docker/local-emulator/qemu/run-emulator.sh` — `start`, `capture`,
`stop`, `reset`, `status`; `-incoming defer`, `.raw` cache, QGA-driven
rotation, cold-boot fallback
- `docker/local-emulator/qemu/common.sh` (new) — shared `qmp_session` +
`capture_vm_state` (factored out so build-image.sh and run-emulator.sh
share the capture path)
- `docker/local-emulator/qemu/cloud-init/emulator/user-data` —
placeholder secrets in snapshot mode, `wait-for-stack-ready`,
`trigger-fast-rotate`, qemu-guest-agent enabled
- `docker/local-emulator/rotate-secrets.sh` (new) — in-container
rotation (sed + UPDATE + supervisorctl)
- `docker/local-emulator/supervisord.conf` — `stopasgroup`/`killasgroup`
on `stack-app` and `cron-jobs`
- `docker/local-emulator/entrypoint.sh` — only mint CRON_SECRET if unset
(placeholder supplied in snapshot mode via --env-file)
- `docker/local-emulator/Dockerfile` — ships `rotate-secrets` to
`/usr/local/bin`
- `docker/server/entrypoint.sh` — source
`/run/stack-auth/rotated-secrets.env`; skip full-tree sentinel scan on
warm restarts via marker
**CLI:**
- `packages/stack-cli/src/lib/iso.ts` (new) + `iso.test.ts` (new)
- `packages/stack-cli/src/commands/emulator.ts` + `emulator.test.ts`
(new)
- `packages/stack-cli/vitest.config.ts` (new)
**CI:**
- `.github/workflows/qemu-emulator-build.yaml`
## Test plan
- [x] `docker/local-emulator/qemu/build-image.sh {amd64,arm64}` produces
`stack-emulator-<arch>.qcow2` with snapshot-compatible topology
- [x] `stack emulator pull` downloads qcow2 with progress, then captures
locally (~1–3 min) and writes `stack-emulator-<arch>.savevm.zst` in the
images dir
- [x] `stack emulator pull --skip-snapshot` stops after download
- [x] `stack emulator pull --pr <n>` / `--run <id>` pull from PR /
workflow run artifacts
- [x] `stack emulator start` on a fresh dir auto-pulls **and**
auto-captures, then starts; subsequent starts fast-resume in ~5–8s;
backend + dashboard return 200
- [x] `EMULATOR_NO_ROTATION=1 stack emulator start` completes in ~2.5s;
backend + dashboard return 200 with warning printed
- [x] Two consecutive `emulator start` invocations produce different PCK
values in the internal `ApiKeySet` row
- [x] `stack emulator status` / `stop` / `reset` resolve paths from
`STACK_EMULATOR_HOME`
- [x] Verified end-to-end on arm64 macOS under HVF (capture ~50s,
fast-resume ~6.5s)
- [x] `pnpm lint` and `pnpm typecheck` pass; stack-cli unit tests (iso +
emulator) pass
- [ ] CI green on this PR (qemu-emulator-build matrix, smoke test)
- [ ] `gh release download emulator-<branch>-latest` contains only
`stack-emulator-<arch>.qcow2` once this PR merges and publish runs
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Snapshot fast-start/resume with optional warm-snapshot assets, runtime
ISO generation, and a cached QEMU build to speed emulator setup.
* CLI: streamed artifact downloads with progress, improved release/asset
handling, stronger preflight checks, and start/status/stop emulator
commands.
* Automated secret rotation and ability to apply rotated secrets at
container startup; supervisor control socket enabled.
* **Bug Fixes**
* More robust start/stop/resume flows with automatic fallback to cold
boot and improved process-group shutdown behavior.
* **Tests**
* New tests for CLI utilities and ISO image generation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!--
Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md
-->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **New Features**
* Added Stripe, OAuth, and Freestyle mock services to the local emulator
* Introduced `emulator run` CLI command to execute applications with
emulator credentials automatically injected
* Enhanced credential management for local development
* **Improvements**
* Improved ARM64 QEMU emulation with cross-architecture support
* Better error detection and logging during emulator provisioning
* Added example middleware configuration with authentication support
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!--
Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md
-->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Interactive init workflow (create, link-config, link-cloud) with safe
non-interactive behavior; writes/updates project config and .env, and
prints STACK AUTH setup instructions.
* CLI assistant/agent with a progress UI for long-running tasks.
* Backend AI proxy endpoint that validates and forwards AI requests to
an external provider.
* **Tests**
* End-to-end tests covering all init modes, outputs, env linking, and
error cases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
## Release Notes
* **New Features**
* Added Stack CLI with authentication (login/logout) commands.
* Added project management commands to list and create projects.
* Added configuration management to pull and push project settings.
* Added code execution capability to run JavaScript expressions.
* Added initialization command for Stack Auth setup.
* **Tests**
* Added comprehensive end-to-end test suite for CLI functionality.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->