npx agentspec-cli install 6f3845f3-b463-493f-a1a6-4fa74dc8be89 --file-type agents-md→ .agents/
# Saasaloy — agent overview
## Architecture philosophy: portable capabilities, swappable providers
**Cloudflare is a provider, not the architecture.** Every capability that wraps an external service ships as a neutral core plus provider modules, so a project swaps the vendor without touching the core, a consumer, or any other module. `email` + `email-cloudflare` / `email-plunk` / `email-console` is the reference shape; `logger` and `sms` follow it. `queue` + `queue-cloudflare` / `queue-memory` / a later `queue-upstash`, and `kv` + `kv-cloudflare` / `kv-upstash`, are the same shape applied to bindings: the platform owns the messages and the entries, the project never migrates them, so a runtime env var may pick the vendor (ADR 0033). `storage` is the exception on this list. The project addresses objects by key and a swap moves every one, so it takes drivers.
Hold these rules when you design or extend a capability:
- **The core is vendor-blind.** `packages/<cap>` has zero runtime dependencies. It imports no vendor SDK and no Workers binding. It exports a provider contract (`provider.ts`), a `define<Cap>({ providers })` registry (`define.ts`), a `create<Cap>(env)` factory, and one normalized error type with stable codes.
- **`env` goes in whole.** The factory takes the Worker environment as an opaque object. Which key a provider reads — a binding, a secret, nothing — is exactly what a caller must not learn. Never read `process.env`.
- **A provider is one runtime file plus its registration.** It lands in the core's `providers/` folder and registers through a `plugin-array` patch into the `providers` array. It owns only what differs per vendor: a binding, an npm dependency, a secret. A provider that needs a Workers binding may carry several descriptor patches, and it registers a handler export through the `handlers` table in `apps/api/src/worker.ts`, never through `infra` (ADR 0033). See `.agents/skills/create-provider/`.
- **`<CAP>_PROVIDER` selects at runtime.** Required even when one provider is installed. An unset or unknown value throws at construction. Never fall back to a default, in either direction.
- **Providers normalize their own failures.** Map the vendor code onto the capability's error codes, keep the raw one in `providerCode`, and set `retryable` honestly. A caller's `catch` sees one shape.
- **Ship a local provider.** `-console` or `-memory`, so a project develops and tests the capability with no vendor account and no network.
- **Cloudflare is the default, never the assumption.** If a new provider would need a second runtime file or a scaffold of its own, the contract is wrong. Fix the contract.
Two boundaries stay in force. A capability owns its vendor packages, and no other workspace imports them (ADR 0020). A capability whose alternate implementation **replaces files the core owns** takes [driver modules](CONTEXT.md) instead — `conflictsWith`, exactly one installed, chosen at `add` time, not by an env var — which is how `database`, `database-d1` and `database-postgres` split (ADR 0026). A three-question test puts each capability on one side of that line: ADR 0033 asks who owns the schema and whether a swap moves data, and ADR 0037 adds the deciding third question, replace-files versus add-one-file. `queue`, `kv`, `email`, `sms`, `logger` and `storage` take providers; `database` takes drivers. Read both ADRs before you pick.
## Conventions
- **Package manager is pnpm 11**; all non-auth settings live in `pnpm-workspace.yaml` (camelCase), never `.npmrc`. Exact versions are pinned (`saveExact`).
- Always use `.dev` directory for testing or running `saasaloy` cli commands.
- **Every scaffolded workspace ships a `clean` script.** Any `package.json` under `packages/cli/templates/base/` or `modules/*/files/` must declare `"clean"` backed by an exact-pinned `rimraf` devDependency — never `rm -rf` (not cross-platform). Clean only what the workspace generates (`dist`, `.astro`, `.wrangler`, `*.tsbuildinfo`); the template's root `clean` handles `node_modules` and `.turbo` for the whole repo. See the template's `AGENTS.md`.
- **`pnpm lint` is four passes and it covers everything we ship** — oxlint type-aware over `packages/cli/src scripts`, oxlint plain over `.`, Stylelint, then `prettier --check .`. That includes `packages/cli/templates/base/**` and `modules/*/files/**`. The `-c oxlint.config.mjs` flag is not optional, and **never run `oxlint --fix-suggestions`** (it rewrites `a[i++]` to `a[i += 1]` and the tests still pass). Fix a violation, or suppress the one line with `// oxlint-disable-next-line <rule>` above it and a reason; a repo-wide rule off goes in `oxlint.config.mjs`'s `suppressed` block with its reason. Markdown is deliberately unformatted. See CONTRIBUTING.md "Linting and formatting" and ADRs 0023/0025.
- **Template + module-descriptor deps are pnpm-invisible** — keep them fresh with `pnpm deps:update` (interactive: grouped report → pick bumps → confirm; majors are their own opt-in group) → `pnpm deps:verify`. Exact-pinned, within-major, 3-day cooldown; `--allow-major`/`--allow-fresh` to override, `--yes` for non-interactive. `pnpm deps:check` is the read-only CI gate. See CONTRIBUTING.md "Updating dependencies" and ADR 0016. The repo's own workspace deps stay on `pnpm outdated`/`update`.
- **Every table name is plural snake_case**, Better Auth's included: `users`, `feature_flag_overrides`, `waitlist_entries`. The Drizzle export key is the camelCase plural (`featureFlagOverrides`), a column stays singular (`user_id`), and an index starts with its table (`sessions_user_id_idx`). `packages/auth/src/auth.ts` passes `usePlural: true` to `drizzleAdapter`, which looks a model up by `<model>s` export key and appends that `s` to a plugin's custom `modelName` too, so `modelName: "X"` needs the export `Xs`. A singular key compiles and then fails every auth call at runtime. `scripts/table-names.test.ts` guards it. See ADR 0038 and the `## Table names` section of `modules/database/skills/saasaloy-database/SKILL.md`.
- **Every `docs/` artifact carries a creation-order serial**: `NNNN-<type>-<slug>-YYYY-MM-DD.md`, where `NNNN` is a four-digit serial that is monotonic per directory (`docs/plans/`, `docs/qa/`, and so on each start their own count at `0001`) and never reused. `docs/adr/` is the exception — an ADR keeps its existing decision number, moved to the front, because other ADRs cite it. `docs/wiki/` is reader-facing and stays unnumbered. Assign the next serial by listing the target directory and incrementing its highest one.
agents-sdk
Build, debug, or review Cloudflare Agents SDK applications using the agents package.
by AgentHub Crawler
cloudflare-email-service
Implement or troubleshoot Cloudflare Email Sending and Email Routing integrations and their delivery configuration.
by AgentHub Crawler
cloudflare-one
Design, configure, troubleshoot, or review Cloudflare One Zero Trust and SASE deployments. Use cloudflare-one-migrations for migration planning from other vendors.
by AgentHub Crawler
Sign in to leave a comment.
cloudflare
Discover and choose Cloudflare products for apps, APIs, AI agents, storage, networking, and security. Use for architecture and product selection, including when the user describes a need without naming a Cloudflare product; then find the relevant skill or documentation.
by AgentHub Crawler