Platform SDK
The platform toolkit for backend + infrastructure.
pf scaffolds real-world platforms: backend services, event-driven messaging, infrastructure. All wired together and kept in sync.
Quick Start • Tutorial • Commands • Deployment
🚀 Quick Start
# With Bun
bun add -g @crossdelta/platform-sdk
# With npm
npm install -g @crossdelta/platform-sdk
# Or auto-install (macOS/Linux/WSL)
curl -fsSL https://unpkg.com/@crossdelta/platform-sdk@latest/install.sh | bash
Windows users: Use WSL for the best experience.
Then create your first platform:
pf new workspace my-platform -y && cd my-platform
pf dev
| ✅ | Turborepo monorepo with Biome linting |
| ✅ | NATS + JetStream for event-driven messaging |
| ✅ | Pulumi infrastructure ready for deployment |
| ✅ | Auto-generated .env.local with service ports |
🎯 5-Minute Tutorial
Add two services and wire an event between them:
# 1. Create two services
pf new hono-micro services/orders
pf new hono-micro services/notifications
# 2. Wire an event from orders → notifications
pf cloudevents add order.created --service services/notifications
# 3. Restart to pick up new services
pf dev
# 4. Publish a test event, watch notifications react
pf cloudevents publish order.created
| What just happened |
|---|
|
📦 Two services with health checks and Pulumi configs 📝 One event contract in packages/contracts/🔗 One handler that auto-discovers the event ✨ No manual wiring. No config drift. Everything is explicit. |
🧰 Commands
| Command | What it does |
|---|---|
pf dev | 🚀 Start development infrastructure (NATS) + all services in watch mode |
pf new hono-micro <path> | ⚡ Create a Hono service (lightweight, Bun-optimized) |
pf new nest-micro <path> | 🏢 Create a NestJS service (full-featured, decorators) |
pf new workspace [name] | 🏗️ Create a new workspace with infrastructure setup |
pf cloudevents add <type> --service <path> | 🔗 Add contract + handler + wire stream |
pf cloudevents list | 📋 List available events and consumers |
pf cloudevents publish <type> | 📤 Publish mock event to NATS |
pf setup completion | 🔧 Install shell autocompletion (zsh/bash) |
pf test | 🧪 Run tests across the monorepo |
pf build | 📦 Build all packages and services |
pf lint | ✨ Lint and format with Biome |
pf audit | 🔒 Run security audit on dependencies |
pf pulumi <args> | ☁️ Run Pulumi in the workspace's infra/ directory, e.g. pf pulumi up |
Your workspace can add its own commands: an entry under
pf.commandsin the rootpackage.json(optionally withcwdandcommand), or any rootscriptsentry, runs aspf <name>. Plugins listed underpf.pluginsadd command groups, as@crossdelta/cloudeventsdoes forpf cloudevents. The plugin interface is described indocs/plugins.mdin the package source.
🧩 Works with coding agents
@crossdelta/pf-mcp is a Model Context Protocol server for the Platform SDK. It exposes four tools:
| Tool | What it does |
|---|---|
scan_workspace | Scan the workspace for services, contracts, and configuration |
generate_service | Generate a plan for a new microservice (dry-run by default) |
plan_review | Validate a generation plan before applying |
apply_changes | Apply the changes from a generation plan |
The server plans the structure; the agent writes the content. A service is created in this order: the agent calls generate_service with a prompt, receives a plan with files and a planHash, writes the code for those files, optionally calls plan_review, asks you to confirm, and then calls apply_changes with expectedPlanHash.
bun add @crossdelta/pf-mcp
node ./node_modules/@crossdelta/pf-mcp/dist/cli.js --stdio
The server talks over stdio, so the MCP client of your agent has to start that command. It also serves the generator guidelines from docs/generators/ in the package source as the MCP resource docs://generators.
🚢 Deployment
pf pulumi up # deploys the current Pulumi stack
Each workspace ships with one Pulumi stack and pre-configured GitHub Actions. Add more stacks (e.g. stage, production) as your platform grows. pf and generate-env work with any stack name.
pf dev generates .env.local from the current stack's Pulumi config (secrets, API keys) and discovered service ports.
Pre-configured GitHub Actions included:
| Trigger | Action |
|---|---|
PR to main | 🧪 Lint + test |
Push to main | 🚀 Build, push to GHCR, deploy |
Changes in packages/ | 📦 Publish to npm |
🔐 Required secrets
| Secret | Description |
|---|---|
PULUMI_ACCESS_TOKEN | Pulumi Cloud token |
DIGITALOCEAN_TOKEN | DO API token |
⚙️ Requirements
| Requirement | Notes |
|---|---|
| Bun or Node.js ≥ 21 | Bun recommended for speed |
| Pulumi CLI | For infrastructure deployment |
| Docker | For local NATS |
🔍 Under the Hood
📁 Project structure
my-platform/
├── services/ # Your microservices
│ ├── orders/
│ └── notifications/
├── packages/
│ └── contracts/ # Event contracts (single source of truth)
├── infra/
│ └── services/ # Auto-generated Pulumi configs
└── .env.local # Auto-generated from infra
The key insight: Services define their ports, env vars, and event subscriptions once. Everything else is derived.
🛠️ Tech stack
| Layer | Technology |
|---|---|
| Monorepo | Turborepo |
| Services | Hono or NestJS |
| Messaging | NATS + JetStream |
| Events | @crossdelta/cloudevents (CloudEvents + Zod) |
| Infrastructure | Pulumi → DigitalOcean (extensible) |
| Runtime | Bun |
⚙️ Workspace configuration
{
"pf": {
"registry": "my-org/my-platform",
"commands": {
"deploy": {
"cwd": "infra",
"command": "pulumi up --yes",
"description": "Deploy infrastructure to production"
}
},
"paths": {
"services": { "path": "services", "watch": true },
"contracts": { "path": "packages/contracts" }
}
}
}
🏗️ Service infrastructure config
// infra/services/orders.ts
import { ports, type K8sServiceConfig } from '@crossdelta/infrastructure'
const config: K8sServiceConfig = {
name: 'orders',
ports: ports().http(4001).build(),
replicas: 1,
healthCheck: { httpPath: '/health' },
resources: {
requests: { cpu: '50m', memory: '64Mi' },
limits: { cpu: '150m', memory: '128Mi' },
},
}
export default config
See @crossdelta/infrastructure for full options.
📡 Event-driven architecture
Contracts live in packages/contracts/ as the single source of truth:
// packages/contracts/src/events/orders/created.ts
import { createContract } from '@crossdelta/cloudevents'
import { z } from 'zod'
export const OrderCreatedSchema = z.object({
orderId: z.string(),
total: z.number(),
})
export type OrderCreatedData = z.infer<typeof OrderCreatedSchema>
export const OrdersCreatedContract = createContract({
type: 'order.created',
channel: { stream: 'ORDERS' },
schema: OrderCreatedSchema,
})
Handlers import contracts and delegate to use cases:
// services/notifications/src/events/order-created.handler.ts
import { handleEvent } from '@crossdelta/cloudevents'
import { OrdersCreatedContract } from '@my-platform/contracts'
import { sendOrderNotification } from '../use-cases/send-notification.use-case'
export default handleEvent(OrdersCreatedContract, async (data) => {
await sendOrderNotification(data)
})
Handlers are auto-discovered. No manual subscriptions.
See @crossdelta/cloudevents for full event API.
⚡ NATS message broker
Local NATS started by
pf devis development infrastructure. Production streams are materialized via Pulumi from contracts.
Every workspace includes pre-configured NATS with JetStream for development:
Development:
- Auto-started with
pf dev(docker-based, ephemeral) - Ports:
4222(client),8222(monitoring) - Health check:
curl http://localhost:8222/healthz - Streams: Auto-created by
pf devfrom contracts (memory, 1h retention)
Production:
- Deployed via Pulumi (
infra/) - Streams materialized from contracts with retention policies
- Persistent storage (file-based) with explicit limits
Services never create streams. In dev,
pf devauto-creates from contracts. In prod, Pulumi materializes.
License
MIT © crossdelta