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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4387,8 +4387,7 @@ struct CMUXCLI {
// `vm_image_config_error`.
/// `--size` spellings → memory in MB. The supported base-image ladder is
/// 4 GB, 8 GB, 16 GB, 24 GB, 32 GB, and 64 GB of RAM, with disk sizes
/// following each image. Pricing separately describes 5 vCPU, 20 GB RAM,
/// and 200 GB disk as one pool shared across the plan's Cloud VMs.
/// following each image. Each machine has its own resources.
private static let cloudVMSizeAliases: [String: Int] = [
"4g": 4096, "4gb": 4096,
"8g": 8192, "8gb": 8192,
Expand Down
88 changes: 44 additions & 44 deletions Resources/Localizable.xcstrings

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions Sources/Cloud/NewMachineModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ final class NewMachineModel {
static let legacyPlanMachineMemoryMb = 20480
/// Mirrors `maxMemoryMbForPlan`: development and paid plans may use the
/// largest supported base image unless an operator sets a lower ceiling.
/// The pricing page separately describes the 5 vCPU / 20 GB RAM / 200 GB
/// disk pool shared across a paid plan's Cloud VMs.
/// Each machine has its own resources within the paid machine allowance.
static func maxMemoryMb(planId: String?) -> Int {
_ = planId
return memoryOptionsMb.max() ?? planMachineMemoryMb
Expand Down
8 changes: 4 additions & 4 deletions Sources/PricingPlansScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ private struct NativePricingPlansView: View {
isProminent: true,
features: [
String(localized: "pricing.native.pro.feature.vms", defaultValue: "Cloud agents on isolated Cloud VMs"),
String(localized: "pricing.native.pro.feature.hours", defaultValue: "Up to 50 Cloud VMs, all sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk; each VM starts at 8 GB RAM and 32 GB disk, with sizes from 4 to 64 GB RAM available as capacity allows"),
String(localized: "pricing.native.pro.feature.hours", defaultValue: "Up to 50 Cloud VMs, each with its own resources; default size 8 GB RAM and 32 GB disk, with 4 to 64 GB RAM available"),
Comment thread
austinywang marked this conversation as resolved.
String(localized: "pricing.native.pro.feature.gateway", defaultValue: "Unlimited workspaces"),
String(localized: "pricing.native.pro.feature.ios", defaultValue: "cmux iOS app and email support"),
]
Expand All @@ -432,7 +432,7 @@ private struct NativePricingPlansView: View {
features: [
String(localized: "pricing.native.team.feature.billing", defaultValue: "Unified billing for the whole team"),
String(localized: "pricing.native.team.feature.seats", defaultValue: "Centralized seat management"),
String(localized: "pricing.native.team.feature.compute", defaultValue: "Up to 50 Cloud VMs per user, all sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk; each VM starts at 8 GB RAM and 32 GB disk, with sizes from 4 to 64 GB RAM available as capacity allows"),
String(localized: "pricing.native.team.feature.compute", defaultValue: "Up to 50 Cloud VMs per user, each with its own resources; default size 8 GB RAM and 32 GB disk, with 4 to 64 GB RAM available"),
String(localized: "pricing.native.team.feature.gateway", defaultValue: "Team-wide model gateway analytics"),
String(localized: "pricing.native.team.feature.support", defaultValue: "Priority email support"),
]
Expand Down Expand Up @@ -762,12 +762,12 @@ private struct NativePricingTableCell: View {
private struct NativePricingSizeSection: View {
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(String(localized: "pricing.native.sizes.title", defaultValue: "Shared Cloud VM resources"))
Text(String(localized: "pricing.native.sizes.title", defaultValue: "Cloud VM resources"))
.font(.system(size: 13, weight: .medium))
.foregroundStyle(.secondary)
Text(String(
localized: "pricing.native.sizes.body",
defaultValue: "Pro includes up to 50 Cloud VMs sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk. Team includes the same shared pool per user. The default VM size is 8 GB RAM and 32 GB disk; sizes from 4 to 64 GB RAM are available as capacity allows. New VM disks start at 32 GB and can grow within the shared pool. There is no metering or overage billing."
defaultValue: "Pro includes up to 50 Cloud VMs. Team includes up to 50 Cloud VMs per user. Each machine has its own CPU, memory, and disk. The default size is 8 GB RAM and 32 GB disk; sizes from 4 to 64 GB RAM are available. Disks can grow up to 256 GB. There is no metering or overage billing."
))
.font(.system(size: 13))
.foregroundStyle(.secondary)
Expand Down
2 changes: 1 addition & 1 deletion skills/cmux-billing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Read before changing billing, pricing, Stripe, Pro entitlement, checkout, webhoo

Prices live in `web/services/billing/plans.ts` and are provisioned by `web/scripts/stripe/provision-catalog.sh`. Current checkout keys: `cmux-pro-monthly-50` ($50/mo), `cmux-pro-yearly-480` ($480/yr, $40/mo equivalent), `cmux-team-monthly-60` ($60/user/mo), `cmux-team-yearly-576` ($576/user/yr, $48/mo equivalent). Stripe amounts are immutable, so a price change mints a new lookup key carrying the amount and adds the old key to `LEGACY_PRICE_LOOKUP_KEYS`; grandfathered Prices (`cmux-pro-monthly` $30, `cmux-pro-yearly` $240, `cmux-pro-yearly-288` $288, `cmux-team-monthly` $35, `cmux-team-yearly-336` $336) stay active for the subscriptions on them and `/dashboard/billing` prices every row from its own Stripe amount. Env price-id overrides carry the amount in their name (`STRIPE_PRO_MONTHLY_50_PRICE_ID` and friends); a retired name fails env validation, so delete it from Vercel before deploying a price change. Test-mode Pro product `prod_UyHgRPpmCzrkLJ`, live `prod_Uq4a28vk0fP3E6`. Staging webhook endpoint `we_1Tq1SZGhInAdn3JbWJReKNEN` forwards to `cmux-staging.vercel.app`; its secrets are already in the `cmux-staging` Vercel project.

The paid Cloud VM allowance lives in `web/services/vms/machineSpec.ts`: Pro allows up to 50 machines for one paid account. Team grants 50 machines per paid seat, so each paid seat adds one allowance and one shared pool. Each allowance shares 5 vCPU, 20 GB RAM, and 200 GB disk across its Cloud VMs. New provider machines start with a 32 GB disk and can grow. The VM repository persists reservations and enforces the aggregate pool under the billing-team lock, treating CPU and memory as shared ceilings and adding disk claims; the entitlement module only resolves the allowance and shared-capacity policy. `web/tests/pro-pricing.test.ts` pins the pricing copy and the provider starting disk.
The paid Cloud VM allowance lives in `web/services/vms/machineSpec.ts`: Pro allows up to 50 machines for one paid account. Team grants 50 machines per paid seat. Each machine has its own CPU, memory, and disk; there is no aggregate resource pool. The default machine has 8 GB RAM and 32 GB disk. Disk grows independently up to 256 GB. The repository enforces machine counts under the billing-team lock and tracks individual shapes for fork, snapshot, and resize recovery. Paid-limit environment overrides are retired; the create kill switch remains the incident control.

## Feature flags

Expand Down
5 changes: 0 additions & 5 deletions web/app/api/vm/base/routeShared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
isVmCreateInProgressError,
isVmImageConfigError,
isVmLimitExceededError,
isVmSharedResourceLimitExceededError,
} from "../../../../services/vms/errors";
import {
inferVmProviderForImage,
Expand All @@ -26,7 +25,6 @@ import {
requestedVmTeamIdFromRequest,
vmActiveLimitExceededResponse,
vmErrorResponse,
vmSharedResourceLimitExceededResponse,
vmWorkflowErrorResponse,
resolveVmProvisioningAccountScope,
} from "../../../../services/vms/routeHelpers";
Expand Down Expand Up @@ -186,9 +184,6 @@ async function baseWorkflowErrorResponse(
phase: "create",
});
}
if (isVmSharedResourceLimitExceededError(err)) {
return vmSharedResourceLimitExceededResponse(err, "create", locale);
}
if (isVmCreateCreditsInsufficientError(err)) {
return vmErrorResponse({
error: "vm_create_credits_insufficient",
Expand Down
3 changes: 1 addition & 2 deletions web/app/api/vm/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -404,8 +404,7 @@ export async function POST(request: Request): Promise<Response> {
const requestedMemoryMb = candidate.memoryMb as number | undefined;
// The server owns the supported size ladder. A stale client request
// that is not on the ladder resolves to the plan default instead of
// failing the create. The paid plan's 5 vCPU / 20 GB RAM / 200 GB
// shared pool is enforced separately by the repository.
// failing the create. The repository enforces the machine-count allowance.
// Clients ship their own size table and always trail the server: the
// 2026-09-02 pricing change (#11610) left every installed nightly
// sending its old 24 GB default and the server rejecting each create
Expand Down
5 changes: 0 additions & 5 deletions web/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@
"title": "يتطلب cmux Pro",
"message": "تتطلب أجهزة Cloud VM خطة cmux Pro.",
"action": "قم بالترقية إلى cmux Pro عبر {upgradeUrl} لإنشاء أجهزة Cloud VM."
},
"sharedResource": {
"message": "امتلأ تجمع موارد {resource} المشترك لأجهزة Cloud VM.",
"action": "احذف جهاز Cloud VM غير مستخدم ثم أعد المحاولة.",
"oversizedAction": "اطلب حجم {resource} أصغر لجهاز Cloud VM ليتسع في التجمع المشترك."
}
},
"billingRecovery": {
Expand Down
5 changes: 0 additions & 5 deletions web/messages/bs.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@
"title": "Potreban je cmux Pro",
"message": "Cloud VM-ovi zahtijevaju cmux Pro plan.",
"action": "Nadogradite na cmux Pro na {upgradeUrl} da biste kreirali Cloud VM-ove."
},
"sharedResource": {
"message": "Zajednički skup {resource} resursa za Cloud VM je pun.",
"action": "Obrišite nekorišteni Cloud VM, pa pokušajte ponovo.",
"oversizedAction": "Zatražite manju {resource} veličinu Cloud VM-a koja stane u zajednički skup."
}
},
"billingRecovery": {
Expand Down
5 changes: 0 additions & 5 deletions web/messages/da.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@
"title": "cmux Pro påkrævet",
"message": "Cloud VM'er kræver en cmux Pro-plan.",
"action": "Opgrader til cmux Pro på {upgradeUrl} for at oprette Cloud VM'er."
},
"sharedResource": {
"message": "Den delte {resource}-ressourcepulje til Cloud VM er fuld.",
"action": "Slet en ubrugt Cloud VM, og prøv igen.",
"oversizedAction": "Anmod om en mindre {resource}-størrelse til Cloud VM, som passer i den delte pulje."
}
},
"billingRecovery": {
Expand Down
5 changes: 0 additions & 5 deletions web/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@
"title": "cmux Pro erforderlich",
"message": "Cloud-VMs erfordern einen cmux Pro-Plan.",
"action": "Führen Sie unter {upgradeUrl} ein Upgrade auf cmux Pro durch, um Cloud-VMs zu erstellen."
},
"sharedResource": {
"message": "Der gemeinsame {resource}-Ressourcenpool für Cloud-VMs ist voll.",
"action": "Löschen Sie eine ungenutzte Cloud-VM und versuchen Sie es erneut.",
"oversizedAction": "Fordern Sie eine kleinere {resource}-Größe für die Cloud-VM an, die in den gemeinsamen Pool passt."
}
},
"billingRecovery": {
Expand Down
17 changes: 6 additions & 11 deletions web/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,6 @@
"title": "cmux Pro required",
"message": "Cloud VMs require a cmux Pro plan.",
"action": "Upgrade to cmux Pro at {upgradeUrl} to create Cloud VMs."
},
"sharedResource": {
"message": "The shared Cloud VM {resource} pool is full.",
"action": "Delete an unused Cloud VM, then retry.",
"oversizedAction": "Request a smaller Cloud VM {resource} size that fits within the shared pool."
}
},
"ios": {
Expand Down Expand Up @@ -835,7 +830,7 @@
"featuresLead": "Everything in Free, plus:",
"features": [
"Cloud agents on Cloud VMs in isolated remote sandboxes",
"Up to 50 Cloud VMs, all sharing a total pool of 5 vCPU, 20 GB RAM, and 200 GB disk; each VM starts at 8 GB RAM and 32 GB disk, with sizes from 4 to 64 GB RAM available as capacity allows",
"Up to 50 Cloud VMs, each with its own resources; default size 8 GB RAM and 32 GB disk, with 4 to 64 GB RAM available",
"Unlimited workspaces",
"The cmux iOS app and email support"
],
Expand Down Expand Up @@ -933,10 +928,10 @@
"enterprise": "Custom"
},
{
"label": "Resources shared across Cloud VMs",
"label": "Resources per Cloud VM",
"free": "false",
"pro": "5 vCPU, 20 GB RAM, 200 GB disk total; default VM size 8 GB RAM and 32 GB disk, with sizes from 4 to 64 GB RAM available as capacity allows",
"team": "Per user: 5 vCPU, 20 GB RAM, and 200 GB disk total; default VM size 8 GB RAM and 32 GB disk, with sizes from 4 to 64 GB RAM available as capacity allows",
"pro": "Default 8 GB RAM and 32 GB disk; 4 to 64 GB RAM, disk up to 256 GB",
"team": "Default 8 GB RAM and 32 GB disk; 4 to 64 GB RAM, disk up to 256 GB",
"enterprise": "Custom"
},
{
Expand Down Expand Up @@ -1015,7 +1010,7 @@
},
{
"q": "What are Cloud VMs?",
"a": "Persistent Linux machines in the cloud: pets, not cattle. Following the Zen of cmux, a machine is a primitive, not a managed solution: it keeps its files, installed tools, and state, and what you build on it is yours. Agents clone repositories, run commands, and open pull requests there without touching your Mac. Pro includes up to 50 machines sharing a total pool of 5 vCPU, 20 GB of RAM, and 200 GB of disk. Team includes up to 50 machines per user, with the same shared pool for each user. The default VM size is 8 GB RAM and 32 GB disk; sizes from 4 to 64 GB RAM are available as capacity allows. New VM disks start at 32 GB and can grow within the shared pool."
"a": "Persistent Linux machines in the cloud: pets, not cattle. Following the Zen of cmux, a machine is a primitive, not a managed solution: it keeps its files, installed tools, and state, and what you build on it is yours. Agents clone repositories, run commands, and open pull requests there without touching your Mac. Pro includes up to 50 machines. Team includes up to 50 Cloud VMs per user. Each machine has its own CPU, memory, and disk. The default size is 8 GB RAM and 32 GB disk; sizes from 4 to 64 GB RAM are available. Disks can grow up to 256 GB. There is no metering or overage billing."
},
{
"q": "What is cmux Vault?",
Expand All @@ -1028,7 +1023,7 @@
},
{
"q": "How does billing work?",
"a": "Pro is $50/mo, or $40/mo when billed annually (a 20% savings). Team is $60/user/mo, or $48/user/mo when billed annually. Paid plans include up to 50 Cloud VMs per user. Each user's VMs share a total of 5 vCPU, 20 GB of RAM, and 200 GB of disk, with no metering or overages. The terminal and local agents stay free."
"a": "Pro is $50/mo, or $40/mo when billed annually (a 20% savings). Team is $60/user/mo, or $48/user/mo when billed annually. Paid plans include up to 50 Cloud VMs per user. Each machine has its own CPU, memory, and disk, with no metering or overage billing. The terminal and local agents stay free."
},
{
"q": "Can I self-host the cloud?",
Expand Down
5 changes: 0 additions & 5 deletions web/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@
"title": "Se requiere cmux Pro",
"message": "Las Cloud VM requieren un plan cmux Pro.",
"action": "Actualiza a cmux Pro en {upgradeUrl} para crear Cloud VM."
},
"sharedResource": {
"message": "El grupo compartido de recursos {resource} de Cloud VM está lleno.",
"action": "Elimina una Cloud VM que no uses y vuelve a intentarlo.",
"oversizedAction": "Solicita un tamaño {resource} menor para la Cloud VM que quepa en el grupo compartido."
}
},
"billingRecovery": {
Expand Down
5 changes: 0 additions & 5 deletions web/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@
"title": "cmux Pro requis",
"message": "Les Cloud VM nécessitent un abonnement cmux Pro.",
"action": "Passez à cmux Pro sur {upgradeUrl} pour créer des Cloud VM."
},
"sharedResource": {
"message": "Le pool partagé de ressources {resource} des Cloud VM est plein.",
"action": "Supprimez une Cloud VM inutilisée, puis réessayez.",
"oversizedAction": "Demandez une taille {resource} de Cloud VM plus petite, compatible avec le pool partagé."
}
},
"billingRecovery": {
Expand Down
5 changes: 0 additions & 5 deletions web/messages/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@
"title": "Richiesto cmux Pro",
"message": "Le Cloud VM richiedono un piano cmux Pro.",
"action": "Passa a cmux Pro su {upgradeUrl} per creare Cloud VM."
},
"sharedResource": {
"message": "Il pool condiviso di risorse {resource} delle Cloud VM è pieno.",
"action": "Elimina una Cloud VM inutilizzata e riprova.",
"oversizedAction": "Richiedi una dimensione {resource} più piccola per la Cloud VM, compatibile con il pool condiviso."
}
},
"billingRecovery": {
Expand Down
Loading
Loading