diff --git a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Debug/ControlCommandCoordinator+Debug.swift b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Debug/ControlCommandCoordinator+Debug.swift index 580720573ab5..c61c2728dc75 100644 --- a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Debug/ControlCommandCoordinator+Debug.swift +++ b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Debug/ControlCommandCoordinator+Debug.swift @@ -38,6 +38,8 @@ extension ControlCommandCoordinator { return debugWorkspaceTodoChecklistAddField() case "debug.pro_welcome_checklist.show": return debugShowProWelcomeChecklist() + case "debug.native_pricing.show": + return debugShowNativePricing() case "debug.command_palette.toggle": return debugCommandPaletteEvent(.toggle, request.params) case "debug.command_palette.rename_tab.open": @@ -222,6 +224,16 @@ extension ControlCommandCoordinator { return .ok(.object(["shown": .bool(true)])) } + // MARK: - debug.native_pricing.show — show the native pricing screen + + func debugShowNativePricing() -> ControlCallResult { + guard let debugContext else { + return .err(code: "unavailable", message: "Control context unavailable", data: nil) + } + debugContext.controlDebugShowNativePricing() + return .ok(.object(["shown": .bool(true)])) + } + // MARK: - debug.textbox.* /// `debug.textbox.inline_fixture` — install the inline text-box fixture. diff --git a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Debug/ControlDebugContext.swift b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Debug/ControlDebugContext.swift index 695132ca8134..9fd6ed497b20 100644 --- a/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Debug/ControlDebugContext.swift +++ b/Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Debug/ControlDebugContext.swift @@ -68,6 +68,9 @@ public protocol ControlDebugContext: AnyObject { /// `debug.pro_welcome_checklist.show`. func controlDebugShowProWelcomeChecklist() + /// Shows the native pricing screen for `debug.native_pricing.show`. + func controlDebugShowNativePricing() + /// Runs the shared v1 `is_terminal_focused` body for /// `debug.terminal.is_focused`. /// diff --git a/Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandContextTestStubs+Debug.swift b/Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandContextTestStubs+Debug.swift index 8a28f038414d..e83a975ee68a 100644 --- a/Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandContextTestStubs+Debug.swift +++ b/Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandContextTestStubs+Debug.swift @@ -16,6 +16,7 @@ extension ControlDebugContext { func controlDebugActivateApp() -> String { "ERROR: not implemented" } func controlDebugRequestWorkspaceTodoChecklistAddField() -> UUID? { nil } func controlDebugShowProWelcomeChecklist() {} + func controlDebugShowNativePricing() {} func controlDebugIsTerminalFocused(surfaceArgument: String) -> String { "ERROR: not implemented" } func controlDebugReadTerminalText(surfaceArgument: String) -> String { "ERROR: not implemented" } func controlDebugRenderStats(surfaceArgument: String) -> String { "ERROR: not implemented" } diff --git a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Account/ProUpgradeCard.swift b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Account/ProUpgradeCard.swift index e8d3f2341436..7647f3066d12 100644 --- a/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Account/ProUpgradeCard.swift +++ b/Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Account/ProUpgradeCard.swift @@ -70,7 +70,7 @@ struct ProUpgradeCard: View { } return String( localized: "settings.account.pro.subtitle", - defaultValue: "Cloud dev boxes, the iOS app, and cmux AI. $50/month, or $480/year." + defaultValue: "Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM, and the iOS app. $50/month." ) } diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 7535d9eaeb35..f0e8cad803a0 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -271123,65 +271123,6 @@ } } }, - "pricing.native.compare.cloud.free": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "1 VM trial" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "VM トライアル 1 回" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "1 个 VM 试用" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "1 Test-VM" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Essai d’une VM" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "تجربة جهاز افتراضي واحد" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Prueba de 1 VM" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "1 個 VM 試用" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "VM 1개 체험" - } - } - } - }, "pricing.native.compare.cloud.pro": { "extractionState": "manual", "localizations": { @@ -271359,42 +271300,25 @@ } } }, - "pricing.native.compare.concurrent.free": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "1" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "1" - } - } - } - }, "pricing.native.compare.concurrent.paid": { "extractionState": "manual", "localizations": { "en": { "stringUnit": { "state": "translated", - "value": "50" + "value": "5" } }, "ja": { "stringUnit": { "state": "translated", - "value": "50" + "value": "5" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "50" + "value": "5" } } } @@ -271405,55 +271329,55 @@ "en": { "stringUnit": { "state": "translated", - "value": "50 per user" + "value": "5 per user" } }, "ja": { "stringUnit": { "state": "translated", - "value": "ユーザーごとに 50" + "value": "ユーザーごとに 5" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "每用户 50 个" + "value": "每用户 5 个" } }, "de": { "stringUnit": { "state": "translated", - "value": "50 pro Benutzer" + "value": "5 pro Benutzer" } }, "fr": { "stringUnit": { "state": "translated", - "value": "50 par utilisateur" + "value": "5 par utilisateur" } }, "ar": { "stringUnit": { "state": "translated", - "value": "50 لكل مستخدم" + "value": "5 لكل مستخدم" } }, "es": { "stringUnit": { "state": "translated", - "value": "50 por usuario" + "value": "5 por usuario" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "每位使用者 50 個" + "value": "每位使用者 5 個" } }, "ko": { "stringUnit": { "state": "translated", - "value": "사용자당 50개" + "value": "사용자당 5개" } } } @@ -273009,65 +272933,6 @@ } } }, - "pricing.native.free.feature.trial": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "Local session history and one Cloud VM trial" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "ローカルセッション履歴と Cloud VM の1回分トライアル" - } - }, - "zh-Hans": { - "stringUnit": { - "state": "translated", - "value": "本地会话历史与一次 Cloud VM 试用" - } - }, - "de": { - "stringUnit": { - "state": "translated", - "value": "Lokaler Sitzungsverlauf und eine Cloud-VM-Testversion" - } - }, - "fr": { - "stringUnit": { - "state": "translated", - "value": "Historique de session local et essai d’une VM cloud" - } - }, - "ar": { - "stringUnit": { - "state": "translated", - "value": "سجل جلسات محلي وتجربة لجهاز افتراضي سحابي واحد" - } - }, - "es": { - "stringUnit": { - "state": "translated", - "value": "Historial de sesiones locales y una prueba de VM en la nube" - } - }, - "zh-Hant": { - "stringUnit": { - "state": "translated", - "value": "本地工作階段歷程紀錄與一個 Cloud VM 試用" - } - }, - "ko": { - "stringUnit": { - "state": "translated", - "value": "로컬 세션 기록 및 Cloud VM 1개 체험" - } - } - } - }, "pricing.native.free.feature.workspaces": { "extractionState": "manual", "localizations": { @@ -273875,55 +273740,55 @@ "en": { "stringUnit": { "state": "translated", - "value": "Up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs" + "value": "Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM" } }, "ja": { "stringUnit": { "state": "translated", - "value": "最大 50 台の Cloud VM で、24 GB RAM と 6 vCPU を共有" + "value": "最大 5 台の Cloud VM、VM あたり最大 4 vCPU と 8 GB RAM" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "最多 50 台云端虚拟机,所有虚拟机共享 24 GB 内存和 6 个虚拟 CPU" + "value": "最多 5 台云端虚拟机,每台最多 4 个虚拟 CPU 和 8 GB 内存" } }, "de": { "stringUnit": { "state": "translated", - "value": "Bis zu 50 Cloud-VMs mit insgesamt 24 GB RAM und 6 vCPUs, die alle VMs gemeinsam nutzen" + "value": "Bis zu 5 Cloud-VMs mit bis zu 4 vCPUs und 8 GB RAM pro VM" } }, "fr": { "stringUnit": { "state": "translated", - "value": "Jusqu’à 50 VM cloud, avec 24 Go de RAM et 6 vCPU partagés entre toutes les VM" + "value": "Jusqu’à 5 VM cloud, jusqu’à 4 vCPU et 8 Go de RAM par VM" } }, "ar": { "stringUnit": { "state": "translated", - "value": "ما يصل إلى 50 جهازًا افتراضيًا سحابيًا، مع ذاكرة وصول عشوائي بسعة 24 جيجابايت و6 وحدات معالجة مركزية افتراضية مشتركة بين جميع الأجهزة" + "value": "ما يصل إلى 5 أجهزة افتراضية سحابية، بحد أقصى 4 وحدات معالجة مركزية افتراضية و8 جيجابايت من الذاكرة لكل جهاز" } }, "es": { "stringUnit": { "state": "translated", - "value": "Hasta 50 VM en la nube, con 24 GB de RAM y 6 vCPU compartidos entre todas las VM" + "value": "Hasta 5 VM en la nube, con hasta 4 vCPU y 8 GB de RAM por VM" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "最多 50 台雲端虛擬機,所有虛擬機共用 24 GB 記憶體與 6 個虛擬 CPU" + "value": "最多 5 台雲端虛擬機,每台最多 4 個虛擬 CPU 與 8 GB 記憶體" } }, "ko": { "stringUnit": { "state": "translated", - "value": "최대 50개의 클라우드 VM이 RAM 24 GB와 vCPU 6개를 공유합니다" + "value": "최대 5개의 클라우드 VM, VM당 최대 vCPU 4개와 RAM 8 GB" } } } @@ -274480,121 +274345,121 @@ "en": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro includes up to 5 Cloud VMs, with up to 4 vCPUs and 8 GB RAM per VM. Team includes the same limits per user. There is no metering or overage billing." } }, "ja": { "stringUnit": { "state": "translated", - "value": "Pro では最大 50 台の Cloud VM で、24 GB RAM と 6 vCPU を共有します。Team ではユーザーごとに同じ上限が適用されます。従量課金や超過料金はありません。" + "value": "Pro には最大 5 台の Cloud VM が含まれ、VM あたり最大 4 vCPU と 8 GB RAM を利用できます。Team ではユーザーごとに同じ上限が適用されます。従量課金や超過料金はありません。" } }, "ar": { "stringUnit": { "state": "translated", - "value": "تتضمن باقة Pro ما يصل إلى 50 جهازًا افتراضيًا سحابيًا، مع ذاكرة وصول عشوائي بسعة 24 جيجابايت و6 وحدات معالجة مركزية افتراضية مشتركة بين جميع الأجهزة. وتمنح باقة Team الحدود نفسها لكل مستخدم. لا توجد فوترة حسب الاستهلاك أو رسوم إضافية لتجاوز الحدود." + "value": "تتضمن باقة Pro ما يصل إلى 5 أجهزة افتراضية سحابية، بحد أقصى 4 وحدات معالجة مركزية افتراضية و8 جيجابايت من الذاكرة لكل جهاز. وتمنح باقة Team الحدود نفسها لكل مستخدم. لا توجد فوترة حسب الاستهلاك أو رسوم إضافية لتجاوز الحدود." } }, "bs": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro uključuje do 5 Cloud VM-ova, s najviše 4 vCPU-a i 8 GB RAM-a po VM-u. Team uključuje ista ograničenja po korisniku. Nema naplate prema potrošnji niti naplate prekoračenja." } }, "da": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro inkluderer op til 5 Cloud VMs med op til 4 vCPU'er og 8 GB RAM pr. VM. Team inkluderer de samme grænser pr. bruger. Der er ingen forbrugsafregning eller betaling for overforbrug." } }, "de": { "stringUnit": { "state": "translated", - "value": "Pro beinhaltet bis zu 50 Cloud-VMs mit 24 GB RAM und 6 vCPUs, die von allen VMs gemeinsam genutzt werden. Team beinhaltet dieselben Limits pro Benutzer. Es gibt keine verbrauchsabhängige Abrechnung oder Nachberechnung." + "value": "Pro beinhaltet bis zu 5 Cloud-VMs mit bis zu 4 vCPUs und 8 GB RAM pro VM. Team beinhaltet dieselben Limits pro Benutzer. Es gibt keine verbrauchsabhängige Abrechnung oder Nachberechnung." } }, "es": { "stringUnit": { "state": "translated", - "value": "Pro incluye hasta 50 VM en la nube, con 24 GB de RAM y 6 vCPUs compartidos entre todas las VM. Team incluye los mismos límites por usuario. No hay facturación por consumo ni por exceso." + "value": "Pro incluye hasta 5 VM en la nube, con hasta 4 vCPU y 8 GB de RAM por VM. Team incluye los mismos límites por usuario. No hay facturación por consumo ni por exceso." } }, "fr": { "stringUnit": { "state": "translated", - "value": "Pro inclut jusqu'à 50 VM Cloud, avec 24 Go de RAM et 6 vCPUs partagés entre toutes les VM. Team inclut les mêmes limites par utilisateur. Il n'y a pas de facturation à l'utilisation ou de dépassement." + "value": "Pro inclut jusqu'à 5 VM Cloud, avec jusqu'à 4 vCPU et 8 Go de RAM par VM. Team inclut les mêmes limites par utilisateur. Il n'y a pas de facturation à l'utilisation ou de dépassement." } }, "it": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro include fino a 5 Cloud VM, con fino a 4 vCPU e 8 GB di RAM per VM. Team include gli stessi limiti per utente. Non è prevista fatturazione a consumo né per eccedenze." } }, "km": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro រួមបញ្ចូល Cloud VMs រហូតដល់ 5 ដោយមាន vCPU រហូតដល់ 4 និង RAM 8 GB ក្នុងមួយ VM។ Team រួមបញ្ចូលដែនកំណត់ដូចគ្នាសម្រាប់អ្នកប្រើម្នាក់ៗ។ មិនមានការគិតថ្លៃតាមការប្រើប្រាស់ ឬថ្លៃលើសកំណត់ទេ។" } }, "ko": { "stringUnit": { "state": "translated", - "value": "Pro는 최대 50개의 클라우드 VM을 포함하며, 모든 VM이 RAM 24 GB와 vCPU 6개를 공유합니다. Team은 사용자마다 동일한 한도가 적용됩니다. 사용량 기반 과금이나 초과 요금은 없습니다." + "value": "Pro는 최대 5개의 클라우드 VM을 포함하며, VM당 최대 vCPU 4개와 RAM 8 GB를 사용할 수 있습니다. Team은 사용자마다 동일한 한도가 적용됩니다. 사용량 기반 과금이나 초과 요금은 없습니다." } }, "nb": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro inkluderer opptil 5 Cloud VMs med opptil 4 vCPU-er og 8 GB RAM per VM. Team inkluderer de samme grensene per bruker. Det er ingen forbruksbasert fakturering eller betaling for overforbruk." } }, "pl": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro obejmuje do 5 Cloud VMs, z maksymalnie 4 vCPU i 8 GB RAM na VM. Team obejmuje te same limity na użytkownika. Nie ma rozliczeń za zużycie ani opłat za przekroczenie limitów." } }, "pt-BR": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "O Pro inclui até 5 Cloud VMs, com até 4 vCPUs e 8 GB de RAM por VM. O Team inclui os mesmos limites por usuário. Não há cobrança por uso nem por excedente." } }, "ru": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro включает до 5 Cloud VMs, до 4 vCPU и 8 ГБ RAM на каждую VM. Team включает те же лимиты на пользователя. Нет оплаты по факту использования или за превышение." } }, "th": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro มี Cloud VMs ได้สูงสุด 5 เครื่อง แต่ละ VM มีได้สูงสุด 4 vCPU และ RAM 8 GB Team มีขีดจำกัดเดียวกันต่อผู้ใช้ ไม่มีการคิดค่าบริการตามการใช้งานหรือค่าใช้งานส่วนเกิน" } }, "tr": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro, VM başına en fazla 4 vCPU ve 8 GB RAM ile en fazla 5 Cloud VM içerir. Team, kullanıcı başına aynı sınırları içerir. Kullanıma göre ücretlendirme veya aşım ücreti yoktur." } }, "uk": { "stringUnit": { "state": "translated", - "value": "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + "value": "Pro включає до 5 Cloud VMs, до 4 vCPU і 8 ГБ RAM на кожну VM. Team включає ті самі ліміти на користувача. Немає оплати за фактичне використання чи за перевищення." } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "Pro 最多包含 50 台云端虚拟机,所有虚拟机共享 24 GB 内存和 6 个虚拟 CPU。Team 的每位用户享有相同上限。不按用量计费,也不收取超额费用。" + "value": "Pro 最多包含 5 台云端虚拟机,每台最多 4 个虚拟 CPU 和 8 GB 内存。Team 的每位用户享有相同上限。不按用量计费,也不收取超额费用。" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "Pro 包含最多 50 台雲端虛擬機,所有虛擬機共用 24 GB 記憶體與 6 個虛擬 CPU。Team 的每位使用者享有相同上限。不按用量計費,也不收取超額費用。" + "value": "Pro 包含最多 5 台雲端虛擬機,每台最多 4 個虛擬 CPU 與 8 GB 記憶體。Team 的每位使用者享有相同上限。不按用量計費,也不收取超額費用。" } } } @@ -275261,55 +275126,55 @@ "en": { "stringUnit": { "state": "translated", - "value": "Up to 50 Cloud VMs per user, with 24 GB RAM and 6 vCPUs per user shared across all their VMs" + "value": "Up to 5 Cloud VMs per user, up to 4 vCPUs and 8 GB RAM per VM" } }, "ja": { "stringUnit": { "state": "translated", - "value": "ユーザーごとに最大 50 台の Cloud VM で、ユーザーあたり 24 GB RAM と 6 vCPU を共有" + "value": "ユーザーごとに最大 5 台の Cloud VM、VM あたり最大 4 vCPU と 8 GB RAM" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "每位用户最多 50 台云端虚拟机,该用户的所有虚拟机共享 24 GB 内存和 6 个虚拟 CPU" + "value": "每位用户最多 5 台云端虚拟机,每台最多 4 个虚拟 CPU 和 8 GB 内存" } }, "de": { "stringUnit": { "state": "translated", - "value": "Bis zu 50 Cloud-VMs pro Benutzer mit jeweils insgesamt 24 GB RAM und 6 vCPUs, die alle VMs dieses Benutzers gemeinsam nutzen" + "value": "Bis zu 5 Cloud-VMs pro Benutzer mit bis zu 4 vCPUs und 8 GB RAM pro VM" } }, "fr": { "stringUnit": { "state": "translated", - "value": "Jusqu’à 50 VM cloud par utilisateur, avec 24 Go de RAM et 6 vCPU par utilisateur partagés entre toutes ses VM" + "value": "Jusqu’à 5 VM cloud par utilisateur, jusqu’à 4 vCPU et 8 Go de RAM par VM" } }, "ar": { "stringUnit": { "state": "translated", - "value": "ما يصل إلى 50 جهازًا افتراضيًا سحابيًا لكل مستخدم، مع ذاكرة وصول عشوائي بسعة 24 جيجابايت و6 وحدات معالجة مركزية افتراضية لكل مستخدم مشتركة بين جميع أجهزته" + "value": "ما يصل إلى 5 أجهزة افتراضية سحابية لكل مستخدم، بحد أقصى 4 وحدات معالجة مركزية افتراضية و8 جيجابايت من الذاكرة لكل جهاز" } }, "es": { "stringUnit": { "state": "translated", - "value": "Hasta 50 VM en la nube por usuario, con 24 GB de RAM y 6 vCPU por usuario compartidos entre todas sus VM" + "value": "Hasta 5 VM en la nube por usuario, con hasta 4 vCPU y 8 GB de RAM por VM" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "每位使用者最多 50 台雲端虛擬機,該使用者的所有虛擬機共用 24 GB 記憶體與 6 個虛擬 CPU" + "value": "每位使用者最多 5 台雲端虛擬機,每台最多 4 個虛擬 CPU 與 8 GB 記憶體" } }, "ko": { "stringUnit": { "state": "translated", - "value": "사용자당 최대 50개의 클라우드 VM이 사용자당 RAM 24 GB와 vCPU 6개를 공유합니다" + "value": "사용자당 최대 5개의 클라우드 VM, VM당 최대 vCPU 4개와 RAM 8 GB" } } } @@ -292714,55 +292579,55 @@ "en": { "stringUnit": { "state": "translated", - "value": "Cloud dev boxes, the iOS app, and cmux AI. $50/month, or $480/year." + "value": "Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM, and the iOS app. $50/month." } }, "ja": { "stringUnit": { "state": "translated", - "value": "クラウド開発マシン、iOSアプリ、cmux AI。月額$50、または年額$480。" + "value": "最大 5 台の Cloud VM(VM あたり最大 4 vCPU・8 GB RAM)と iOS アプリ。月額$50。" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "云端开发机、iOS 应用与 cmux AI。每月 $50,或每年 $480。" + "value": "最多 5 台云端虚拟机(每台最多 4 个虚拟 CPU 和 8 GB 内存)与 iOS 应用。每月 $50。" } }, "de": { "stringUnit": { "state": "translated", - "value": "Cloud-Entwicklerboxen, die iOS-App und cmux AI. 50 $/Monat oder 480 $/Jahr." + "value": "Bis zu 5 Cloud-VMs mit bis zu 4 vCPUs und 8 GB RAM pro VM sowie die iOS-App. 50 $/Monat." } }, "fr": { "stringUnit": { "state": "translated", - "value": "Machines de dev cloud, l’application iOS et l’IA cmux. 50 $/mois, ou 480 $/an." + "value": "Jusqu’à 5 VM cloud, jusqu’à 4 vCPU et 8 Go de RAM par VM, et l’application iOS. 50 $/mois." } }, "ar": { "stringUnit": { "state": "translated", - "value": "بيئات تطوير سحابية، وتطبيق iOS، ومساعد cmux الذكي. 50$ شهريًا، أو 480$ سنويًا." + "value": "ما يصل إلى 5 أجهزة افتراضية سحابية، بحد أقصى 4 وحدات معالجة مركزية افتراضية و8 جيجابايت من الذاكرة لكل جهاز، وتطبيق iOS. 50$ شهريًا." } }, "es": { "stringUnit": { "state": "translated", - "value": "Entornos de desarrollo en la nube, la aplicación para iOS y cmux AI. $50 al mes o $480 al año." + "value": "Hasta 5 VM en la nube, con hasta 4 vCPU y 8 GB de RAM por VM, y la aplicación para iOS. $50 al mes." } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "雲端開發盒、iOS 應用程式和 cmux AI。每月 50 美元,或每年 480 美元。" + "value": "最多 5 台雲端虛擬機(每台最多 4 個虛擬 CPU 與 8 GB 記憶體)與 iOS 應用程式。每月 50 美元。" } }, "ko": { "stringUnit": { "state": "translated", - "value": "클라우드 개발 상자, iOS 앱 및 cmux AI. 월 $50 또는 연 $480." + "value": "최대 5개의 클라우드 VM(VM당 최대 vCPU 4개, RAM 8 GB)과 iOS 앱. 월 $50." } } } @@ -521576,55 +521441,55 @@ "en": { "stringUnit": { "state": "translated", - "value": "32 GB and 64 GB machines need cmux Max." + "value": "Max includes up to 5 Cloud VMs, with up to 16 vCPUs and 32 GB RAM per VM." } }, "de": { "stringUnit": { "state": "translated", - "value": "Maschinen mit 32 GB und 64 GB benötigen cmux Max." + "value": "Max beinhaltet bis zu 5 Cloud-VMs mit bis zu 16 vCPUs und 32 GB RAM pro VM." } }, "fr": { "stringUnit": { "state": "translated", - "value": "Les machines de 32 Go et 64 Go nécessitent cmux Max." + "value": "Max inclut jusqu'à 5 VM Cloud, avec jusqu'à 16 vCPU et 32 Go de RAM par VM." } }, "ar": { "stringUnit": { "state": "translated", - "value": "الأجهزة ذات 32 و64 غيغابايت تتطلب cmux Max." + "value": "تتضمن باقة Max ما يصل إلى 5 أجهزة افتراضية سحابية، بحد أقصى 16 وحدة معالجة مركزية افتراضية و32 جيجابايت من الذاكرة لكل جهاز." } }, "es": { "stringUnit": { "state": "translated", - "value": "Las máquinas de 32 GB y 64 GB requieren cmux Max." + "value": "Max incluye hasta 5 VM en la nube, con hasta 16 vCPU y 32 GB de RAM por VM." } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "32 GB 和 64 GB 機器需要 cmux Max。" + "value": "Max 包含最多 5 台雲端虛擬機,每台最多 16 個虛擬 CPU 與 32 GB 記憶體。" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "32 GB 和 64 GB 机器需要 cmux Max。" + "value": "Max 最多包含 5 台云端虚拟机,每台最多 16 个虚拟 CPU 和 32 GB 内存。" } }, "ko": { "stringUnit": { "state": "translated", - "value": "32 GB 및 64 GB 머신에는 cmux Max가 필요합니다." + "value": "Max는 최대 5개의 클라우드 VM을 포함하며, VM당 최대 vCPU 16개와 RAM 32 GB를 사용할 수 있습니다." } }, "ja": { "stringUnit": { "state": "translated", - "value": "32 GB と 64 GB のマシンには cmux Max が必要です。" + "value": "Max には最大 5 台の Cloud VM が含まれ、VM あたり最大 16 vCPU と 32 GB RAM を利用できます。" } } } @@ -522284,55 +522149,55 @@ "en": { "stringUnit": { "state": "translated", - "value": "Everything in Pro: 50 Cloud VMs, unlimited workspaces, the iOS app" + "value": "Unlimited workspaces and the iOS app" } }, "de": { "stringUnit": { "state": "translated", - "value": "Alles aus Pro: 50 Cloud-VMs, unbegrenzte Arbeitsbereiche, die iOS-App" + "value": "Unbegrenzte Arbeitsbereiche und die iOS-App" } }, "fr": { "stringUnit": { "state": "translated", - "value": "Tout Pro : 50 VM Cloud, espaces de travail illimités, application iOS" + "value": "Espaces de travail illimités et application iOS" } }, "ar": { "stringUnit": { "state": "translated", - "value": "كل ما في Pro: 50 جهازاً سحابياً، ومساحات عمل غير محدودة، وتطبيق iOS" + "value": "مساحات عمل غير محدودة وتطبيق iOS" } }, "es": { "stringUnit": { "state": "translated", - "value": "Todo Pro: 50 máquinas virtuales Cloud, espacios de trabajo ilimitados y la app iOS" + "value": "Espacios de trabajo ilimitados y la app iOS" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "包含 Pro 所有功能:50 台雲端 VM、無限工作區、iOS 應用程式" + "value": "無限工作區與 iOS 應用程式" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "包含 Pro 所有功能:50 台云端 VM、无限工作区、iOS 应用" + "value": "无限工作区与 iOS 应用" } }, "ko": { "stringUnit": { "state": "translated", - "value": "Pro의 모든 기능: Cloud VM 50대, 무제한 작업 공간, iOS 앱" + "value": "무제한 작업 공간과 iOS 앱" } }, "ja": { "stringUnit": { "state": "translated", - "value": "Pro の全機能:50 台の Cloud VM、無制限のワークスペース、iOS アプリ" + "value": "無制限のワークスペースと iOS アプリ" } } } @@ -522402,55 +522267,55 @@ "en": { "stringUnit": { "state": "translated", - "value": "Up to 64 GB RAM per machine" + "value": "Up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM" } }, "de": { "stringUnit": { "state": "translated", - "value": "Bis zu 64 GB RAM pro Maschine" + "value": "Bis zu 5 Cloud-VMs mit bis zu 16 vCPUs und 32 GB RAM pro VM" } }, "fr": { "stringUnit": { "state": "translated", - "value": "Jusqu’à 64 Go de RAM par machine" + "value": "Jusqu’à 5 VM cloud, jusqu’à 16 vCPU et 32 Go de RAM par VM" } }, "ar": { "stringUnit": { "state": "translated", - "value": "حتى 64 غيغابايت من الذاكرة لكل جهاز" + "value": "ما يصل إلى 5 أجهزة افتراضية سحابية، بحد أقصى 16 وحدة معالجة مركزية افتراضية و32 جيجابايت من الذاكرة لكل جهاز" } }, "es": { "stringUnit": { "state": "translated", - "value": "Hasta 64 GB de RAM por máquina" + "value": "Hasta 5 VM en la nube, con hasta 16 vCPU y 32 GB de RAM por VM" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "每台機器最高 64 GB RAM" + "value": "最多 5 台雲端虛擬機,每台最多 16 個虛擬 CPU 與 32 GB 記憶體" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "每台机器最高 64 GB RAM" + "value": "最多 5 台云端虚拟机,每台最多 16 个虚拟 CPU 和 32 GB 内存" } }, "ko": { "stringUnit": { "state": "translated", - "value": "머신당 최대 64 GB RAM" + "value": "최대 5개의 클라우드 VM, VM당 최대 vCPU 16개와 RAM 32 GB" } }, "ja": { "stringUnit": { "state": "translated", - "value": "マシンごとに最大 64 GB RAM" + "value": "最大 5 台の Cloud VM、VM あたり最大 16 vCPU と 32 GB RAM" } } } @@ -522579,55 +522444,55 @@ "en": { "stringUnit": { "state": "translated", - "value": "64 GB RAM" + "value": "32 GB RAM" } }, "de": { "stringUnit": { "state": "translated", - "value": "64 GB Arbeitsspeicher" + "value": "32 GB Arbeitsspeicher" } }, "fr": { "stringUnit": { "state": "translated", - "value": "64 Go de RAM" + "value": "32 Go de RAM" } }, "ar": { "stringUnit": { "state": "translated", - "value": "64 غيغابايت من الذاكرة" + "value": "32 غيغابايت من الذاكرة" } }, "es": { "stringUnit": { "state": "translated", - "value": "64 GB de RAM" + "value": "32 GB de RAM" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "64 GB 記憶體" + "value": "32 GB 記憶體" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "64 GB 内存" + "value": "32 GB 内存" } }, "ko": { "stringUnit": { "state": "translated", - "value": "64 GB 메모리" + "value": "32 GB 메모리" } }, "ja": { "stringUnit": { "state": "translated", - "value": "64 GB メモリ" + "value": "32 GB メモリ" } } } @@ -523228,55 +523093,55 @@ "en": { "stringUnit": { "state": "translated", - "value": "24 GB RAM" + "value": "8 GB RAM" } }, "de": { "stringUnit": { "state": "translated", - "value": "24 GB Arbeitsspeicher" + "value": "8 GB Arbeitsspeicher" } }, "fr": { "stringUnit": { "state": "translated", - "value": "24 Go de RAM" + "value": "8 Go de RAM" } }, "ar": { "stringUnit": { "state": "translated", - "value": "24 غيغابايت من الذاكرة" + "value": "8 غيغابايت من الذاكرة" } }, "es": { "stringUnit": { "state": "translated", - "value": "24 GB de RAM" + "value": "8 GB de RAM" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "24 GB 記憶體" + "value": "8 GB 記憶體" } }, "zh-Hans": { "stringUnit": { "state": "translated", - "value": "24 GB 内存" + "value": "8 GB 内存" } }, "ko": { "stringUnit": { "state": "translated", - "value": "24 GB 메모리" + "value": "8 GB 메모리" } }, "ja": { "stringUnit": { "state": "translated", - "value": "24 GB メモリ" + "value": "8 GB メモリ" } } } @@ -609559,6 +609424,131 @@ } } }, + "pricing.native.free.feature.history": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "Local session history" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ローカルセッション履歴" + } + }, + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "本地会话历史" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Lokaler Sitzungsverlauf" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Historique de session local" + } + }, + "ar": { + "stringUnit": { + "state": "translated", + "value": "سجل جلسات محلي" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Historial de sesiones locales" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "本地工作階段歷程紀錄" + } + }, + "ko": { + "stringUnit": { + "state": "translated", + "value": "로컬 세션 기록" + } + }, + "bs": { + "stringUnit": { + "state": "translated", + "value": "Lokalna historija sesija" + } + }, + "da": { + "stringUnit": { + "state": "translated", + "value": "Lokal sessionshistorik" + } + }, + "it": { + "stringUnit": { + "state": "translated", + "value": "Cronologia sessioni locale" + } + }, + "km": { + "stringUnit": { + "state": "translated", + "value": "ប្រវត្តិវគ្គក្នុងម៉ាស៊ីន" + } + }, + "nb": { + "stringUnit": { + "state": "translated", + "value": "Lokal øktlogg" + } + }, + "pl": { + "stringUnit": { + "state": "translated", + "value": "Lokalna historia sesji" + } + }, + "pt-BR": { + "stringUnit": { + "state": "translated", + "value": "Histórico de sessões local" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Локальная история сеансов" + } + }, + "th": { + "stringUnit": { + "state": "translated", + "value": "ประวัติเซสชันในเครื่อง" + } + }, + "tr": { + "stringUnit": { + "state": "translated", + "value": "Yerel oturum geçmişi" + } + }, + "uk": { + "stringUnit": { + "state": "translated", + "value": "Локальна історія сеансів" + } + } + } + }, "commandPalette.kind.cloudWorkspace": { "extractionState": "manual", "localizations": { diff --git a/Sources/Cloud/MachinesPanelView.swift b/Sources/Cloud/MachinesPanelView.swift index 0414863aac1d..bc3c25904a01 100644 --- a/Sources/Cloud/MachinesPanelView.swift +++ b/Sources/Cloud/MachinesPanelView.swift @@ -486,7 +486,8 @@ struct MachinesPanelView: View { // Max-only size and wait for a server rejection. let planMemoryGiB = viewModel.memoryOptionsMb.map { $0 / 1024 }.filter { $0 > 0 } machineActions.resizeMemoryOptionsGiB = planMemoryGiB - machineActions.resizeCPUOptions = planMemoryGiB.map { max(1, ($0 + 3) / 4) } + // The image ladder pairs one vCPU with every 2 GB (8 GB = 4 vCPU). + machineActions.resizeCPUOptions = planMemoryGiB.map { max(1, ($0 + 1) / 2) } viewModel.bindMachineOrdering(to: &machineActions) machineActions.create = MachineCreateRowActions.bound(coordinator: viewModel.createCoordinator) var nodeActions = CloudTreeNodeActions.bound( @@ -662,7 +663,7 @@ struct MachinesPanelView: View { ) } - /// Paid plans: "Your plan includes 50 machines" under the create button, + /// Paid plans: "Your plan includes 5 machines" under the create button, /// so the empty state answers "what do I get" before the Cloud Machines /// header shows a count. The uncapped wording only appears when an /// operator lifted the cap. diff --git a/Sources/Cloud/NewMachineModel.swift b/Sources/Cloud/NewMachineModel.swift index 259db544e6ec..b644526a507c 100644 --- a/Sources/Cloud/NewMachineModel.swift +++ b/Sources/Cloud/NewMachineModel.swift @@ -92,23 +92,23 @@ final class NewMachineModel { /// The base-image sizes the backend exposes, in ascending memory order. /// Each row is a validated Freestyle snapshot: 4/16, 8/32, 16/64, - /// 24/96, 32/128, or 64/128 GB of memory/disk. The server's list trims + /// 24/96, or 32/128 GB of memory/disk. The server's list trims /// this set for plan limits. The 128 MiB BusyBox image is intentionally /// not a coding-machine option because it has no baked dev tools. - nonisolated static let memoryOptionsMb: [Int] = [4096, 8192, 16384, 24576, 32768, 65536] + nonisolated static let memoryOptionsMb: [Int] = [4096, 8192, 16384, 24576, 32768] static let planMachineMemoryMb = 8192 /// The pre-ladder backend default. It is used only when the server omits /// `limits.memoryOptionsMb`, so the client does not send an unsupported /// `--size` flag during a rolling upgrade. static let legacyPlanMachineMemoryMb = 20480 - /// The plan that sells the ladder's 32 GB and 64 GB rows + /// The plan that sells the ladder's 16, 24, and 32 GB rows /// (`MEMORY_UPGRADE_PLAN_ID` on the server). nonisolated static let maxPlanId = "max" /// The largest machine every plan except Max may start /// (`PLAN_MAX_MEMORY_MB` on the server). - nonisolated static let standardPlanMaxMemoryMb = 24576 + nonisolated static let standardPlanMaxMemoryMb = 8192 /// Mirrors `maxMemoryMbForPlan` without its env overrides: Max gets the - /// whole ladder, every other plan (and an unknown plan) stops at 24 GB. + /// whole ladder, every other plan (and an unknown plan) stops at 8 GB. /// The server's `limits.lockedMemoryOptionsMb` wins whenever it is sent; /// this mirror only covers a control plane that predates that field. nonisolated static func maxMemoryMb(planId: String?) -> Int { @@ -344,7 +344,7 @@ final class NewMachineModel { return String(format: format, size.menuTitle, memoryUpgradePlanName) } - /// "32 GB and 64 GB machines need cmux Max."; nil when nothing is locked + /// "16 GB, 24 GB, and 32 GB machines need cmux Max."; nil when nothing is locked /// or no plan sells the locked sizes. var lockedSizesNoteText: String? { guard supportsSize, !lockedMemoryOptions.isEmpty, let memoryUpgradePlanNames = lockedMemoryUpgradePlanNames else { return nil } diff --git a/Sources/PricingPlansScreen.swift b/Sources/PricingPlansScreen.swift index 0679c54822f3..31f59943d457 100644 --- a/Sources/PricingPlansScreen.swift +++ b/Sources/PricingPlansScreen.swift @@ -445,7 +445,7 @@ private struct NativePricingPlansView: View { String(localized: "pricing.native.free.feature.terminal", defaultValue: "Native Ghostty-based terminal"), String(localized: "pricing.native.free.feature.agents", defaultValue: "Claude Code, Codex, Gemini, and local CLI agents"), String(localized: "pricing.native.free.feature.workspaces", defaultValue: "Vertical tabs, split panes, browser panels, and notifications"), - String(localized: "pricing.native.free.feature.trial", defaultValue: "Local session history and one Cloud VM trial"), + String(localized: "pricing.native.free.feature.history", defaultValue: "Local session history"), String(localized: "pricing.native.free.feature.community", defaultValue: "Community support on Discord and GitHub"), ] ) @@ -475,7 +475,7 @@ private struct NativePricingPlansView: View { isProminent: !snapshot.isMax && !snapshot.isGo, 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, with 24 GB RAM and 6 vCPUs shared across all VMs"), + String(localized: "pricing.native.pro.feature.hours", defaultValue: "Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM"), String(localized: "pricing.native.pro.feature.gateway", defaultValue: "Unlimited workspaces"), String(localized: "pricing.native.pro.feature.ios", defaultValue: "cmux iOS app and email support"), ] @@ -489,7 +489,7 @@ private struct NativePricingPlansView: View { action: snapshot.isMax ? nil : { ProUpgradePresenter.presentCheckout(source: .nativePricingPreview, plan: .max) }, isProminent: snapshot.isMax, features: [ - String(localized: "pricing.native.max.feature.sizes", defaultValue: "Up to 50 Cloud VMs sharing 64 GB RAM and 16 vCPUs"), + String(localized: "pricing.native.max.feature.sizes", defaultValue: "Up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM"), String(localized: "pricing.native.max.feature.pro", defaultValue: "Unlimited workspaces and the iOS app"), ] ) @@ -503,7 +503,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, with 24 GB RAM and 6 vCPUs per user shared across all their VMs"), + String(localized: "pricing.native.team.feature.compute", defaultValue: "Up to 5 Cloud VMs per user, up to 4 vCPUs and 8 GB RAM per VM"), String(localized: "pricing.native.team.feature.gateway", defaultValue: "Team-wide model gateway analytics"), String(localized: "pricing.native.team.feature.support", defaultValue: "Priority email support"), ] @@ -693,7 +693,7 @@ private struct NativePricingComparisonSection: View { NativePricingCompareRow( id: "cloud", label: String(localized: "pricing.native.compare.cloud", defaultValue: "Cloud agents on Cloud VMs"), - free: .text(String(localized: "pricing.native.compare.cloud.free", defaultValue: "1 VM trial")), + free: .unavailable, pro: .text(String(localized: "pricing.native.compare.cloud.pro", defaultValue: "Included")), max: .text(String(localized: "pricing.native.compare.cloud.max", defaultValue: "Included")), team: .text(String(localized: "pricing.native.compare.cloud.team", defaultValue: "Included")), @@ -702,19 +702,19 @@ private struct NativePricingComparisonSection: View { NativePricingCompareRow( id: "concurrent", label: String(localized: "pricing.native.compare.concurrent", defaultValue: "Concurrent Cloud VMs"), - free: .text(String(localized: "pricing.native.compare.concurrent.free", defaultValue: "1")), - pro: .text(String(localized: "pricing.native.compare.concurrent.paid", defaultValue: "50")), - max: .text(String(localized: "pricing.native.compare.concurrent.paid", defaultValue: "50")), - team: .text(String(localized: "pricing.native.compare.concurrent.team", defaultValue: "50 per user")), + free: .unavailable, + pro: .text(String(localized: "pricing.native.compare.concurrent.paid", defaultValue: "5")), + max: .text(String(localized: "pricing.native.compare.concurrent.paid", defaultValue: "5")), + team: .text(String(localized: "pricing.native.compare.concurrent.team", defaultValue: "5 per user")), enterprise: .text(String(localized: "pricing.native.compare.custom", defaultValue: "Custom")) ), NativePricingCompareRow( id: "largestVm", label: String(localized: "pricing.native.compare.largestVm", defaultValue: "Largest Cloud VM"), - free: .text(String(localized: "pricing.native.compare.largestVm.standard", defaultValue: "24 GB RAM")), - pro: .text(String(localized: "pricing.native.compare.largestVm.standard", defaultValue: "24 GB RAM")), - max: .text(String(localized: "pricing.native.compare.largestVm.max", defaultValue: "64 GB RAM from the shared pool")), - team: .text(String(localized: "pricing.native.compare.largestVm.standard", defaultValue: "24 GB RAM")), + free: .unavailable, + pro: .text(String(localized: "pricing.native.compare.largestVm.standard", defaultValue: "8 GB RAM")), + max: .text(String(localized: "pricing.native.compare.largestVm.max", defaultValue: "32 GB RAM")), + team: .text(String(localized: "pricing.native.compare.largestVm.standard", defaultValue: "8 GB RAM")), enterprise: .text(String(localized: "pricing.native.compare.custom", defaultValue: "Custom")) ), NativePricingCompareRow( @@ -884,13 +884,13 @@ private struct NativePricingSizeSection: View { .foregroundStyle(.secondary) Text(String( localized: "pricing.native.sizes.body", - defaultValue: "Pro includes up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same limits per user. There is no metering or overage billing." + defaultValue: "Pro includes up to 5 Cloud VMs, with up to 4 vCPUs and 8 GB RAM per VM. Team includes the same limits per user. There is no metering or overage billing." )) .font(.system(size: 13)) .foregroundStyle(.secondary) Text(String( localized: "pricing.native.sizes.max", - defaultValue: "Max includes 64 GB RAM and 16 vCPUs shared across up to 50 Cloud VMs." + defaultValue: "Max includes up to 5 Cloud VMs, with up to 16 vCPUs and 32 GB RAM per VM." )) .font(.system(size: 13)) .foregroundStyle(.secondary) diff --git a/Sources/TerminalController+ControlDebugContext.swift b/Sources/TerminalController+ControlDebugContext.swift index 237be90d77f2..77a97a24d02e 100644 --- a/Sources/TerminalController+ControlDebugContext.swift +++ b/Sources/TerminalController+ControlDebugContext.swift @@ -84,6 +84,10 @@ extension TerminalController: ControlDebugContext { ProWelcomeChecklistPresenter.present() } + func controlDebugShowNativePricing() { + ProUpgradePresenter.presentNativePricingPreview() + } + func controlDebugIsTerminalFocused(surfaceArgument: String) -> String { isTerminalFocused(surfaceArgument) } diff --git a/Sources/TerminalController+DebugMethodNames.swift b/Sources/TerminalController+DebugMethodNames.swift index d04ef07f826c..592ae05f01fd 100644 --- a/Sources/TerminalController+DebugMethodNames.swift +++ b/Sources/TerminalController+DebugMethodNames.swift @@ -13,6 +13,7 @@ extension TerminalController { "debug.app.activate", "debug.workspace_todo.checklist_add_field", "debug.pro_welcome_checklist.show", + "debug.native_pricing.show", "debug.command_palette.toggle", "debug.command_palette.rename_tab.open", "debug.command_palette.visible", diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 78454fc1854b..e43deed7126e 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -400,6 +400,7 @@ class TerminalController { "notification.open", "notification.jump_to_unread", "debug.command_palette.toggle", "debug.pro_welcome_checklist.show", + "debug.native_pricing.show", "debug.notification.focus", "debug.app.activate", "debug.cloudtree.spacing", "debug.right_sidebar.focus", diff --git a/cmuxTests/NewMachineModelTests.swift b/cmuxTests/NewMachineModelTests.swift index ea644a7aa4e1..aac726f152c2 100644 --- a/cmuxTests/NewMachineModelTests.swift +++ b/cmuxTests/NewMachineModelTests.swift @@ -52,8 +52,8 @@ struct NewMachineModelTests { init(_ value: Value) { self.value = value } } - private static let proPlan = MachinePlanSnapshot(activeCount: 0, maxActiveVms: 50, planId: "pro") - private static let maxPlan = MachinePlanSnapshot(activeCount: 0, maxActiveVms: 50, planId: "max") + private static let proPlan = MachinePlanSnapshot(activeCount: 0, maxActiveVms: 5, planId: "pro") + private static let maxPlan = MachinePlanSnapshot(activeCount: 0, maxActiveVms: 5, planId: "max") private func makeModel( mode: NewMachineModel.Mode = .newMachine, @@ -95,32 +95,32 @@ struct NewMachineModelTests { @Test func defaultSizeIsTheSmallestSupportedBaseImage() { let (model, _) = makeModel(plan: Self.maxPlan) - #expect(model.memoryOptions == [4096, 8192, 16384, 24576, 32768, 65536]) + #expect(model.memoryOptions == [4096, 8192, 16384, 24576, 32768]) #expect(model.memoryMb == 8192) #expect(model.selectedSize == MachineSizeOption(memoryMb: 8192)) } /// The client mirror of the server ladder: Pro (and every plan but Max) - /// stops at 24 GB, and the two rows above it are locked and sold by Max. + /// stops at 8 GB, and the three rows above it are locked and sold by Max. @Test func proPlanLocksTheMaxSizesWhenTheServerOmitsThem() { let (model, _) = makeModel(plan: Self.proPlan) - #expect(model.memoryOptions == [4096, 8192, 16384, 24576]) - #expect(model.lockedMemoryOptions == [32768, 65536]) + #expect(model.memoryOptions == [4096, 8192]) + #expect(model.lockedMemoryOptions == [16384, 24576, 32768]) #expect(model.memoryUpgradePlanId == "max") #expect(model.memoryUpgradePlanName == "Max") - #expect(model.lockedSizesNoteText == "32 GB and 64 GB machines need cmux Max.") + #expect(model.lockedSizesNoteText == "16 GB, 24 GB, and 32 GB machines need cmux Max.") #expect(model.memoryUpgradeButtonTitle == "Upgrade to Max") #expect(model.lockedSizeMenuTitle(MachineSizeOption(memoryMb: 32768)!) == "32 GB RAM · 128 GB disk · Requires Max") - #expect(NewMachineModel.maxMemoryMb(planId: "pro") == 24576) - #expect(NewMachineModel.maxMemoryMb(planId: "free") == 24576) - #expect(NewMachineModel.maxMemoryMb(planId: nil) == 24576) - #expect(NewMachineModel.maxMemoryMb(planId: "max") == 65536) - #expect(NewMachineModel.maxMemoryMb(planId: " Max\n") == 65536) + #expect(NewMachineModel.maxMemoryMb(planId: "pro") == 8192) + #expect(NewMachineModel.maxMemoryMb(planId: "free") == 8192) + #expect(NewMachineModel.maxMemoryMb(planId: nil) == 8192) + #expect(NewMachineModel.maxMemoryMb(planId: "max") == 32768) + #expect(NewMachineModel.maxMemoryMb(planId: " Max\n") == 32768) } @Test func maxPlanHasTheWholeLadderAndNothingLocked() { let (model, _) = makeModel(plan: Self.maxPlan) - #expect(model.memoryOptions == [4096, 8192, 16384, 24576, 32768, 65536]) + #expect(model.memoryOptions == [4096, 8192, 16384, 24576, 32768]) #expect(model.lockedMemoryOptions == []) #expect(model.memoryUpgradePlanId == nil) #expect(model.lockedSizesNoteText == nil) @@ -142,15 +142,15 @@ struct NewMachineModelTests { #expect(tighter.lockedSizesNoteText == "24 GB, 32 GB, and 64 GB machines need cmux Max.") let (open, _) = makeModel(plan: Self.proPlan, lockedMemoryOptionsMb: [], memoryUpgradePlanId: nil) - #expect(open.memoryOptions == [4096, 8192, 16384, 24576, 32768, 65536]) + #expect(open.memoryOptions == [4096, 8192, 16384, 24576, 32768]) #expect(open.lockedMemoryOptions == []) #expect(open.memoryUpgradePlanId == nil) // A locked list without an upgrade plan still names Max, the plan // that sells the ladder, unless the plan already is Max. - let (unnamed, _) = makeModel(plan: Self.proPlan, lockedMemoryOptionsMb: [65536], memoryUpgradePlanId: nil) + let (unnamed, _) = makeModel(plan: Self.proPlan, lockedMemoryOptionsMb: [32768], memoryUpgradePlanId: nil) #expect(unnamed.memoryUpgradePlanId == "max") - #expect(unnamed.memoryOptions == [4096, 8192, 16384, 24576, 32768]) + #expect(unnamed.memoryOptions == [4096, 8192, 16384, 24576]) } /// The Picker binding can only land on an allowed size: a locked pick @@ -158,23 +158,23 @@ struct NewMachineModelTests { /// carries that size. @Test func selectionNeverLandsOnALockedSize() { let (model, recorder) = makeModel(plan: Self.proPlan) - model.memoryMb = 65536 - #expect(model.memoryMb == 24576) model.memoryMb = 32768 - #expect(model.memoryMb == 24576) + #expect(model.memoryMb == 8192) model.memoryMb = 16384 - #expect(model.memoryMb == 16384) - model.memoryMb = 65536 + #expect(model.memoryMb == 8192) + model.memoryMb = 4096 + #expect(model.memoryMb == 4096) + model.memoryMb = 32768 model.create() - #expect(recorder.value.first?.arguments == ["vm", "new", "--desktop", "--size", "24576", "--focus", "false"]) + #expect(recorder.value.first?.arguments == ["vm", "new", "--desktop", "--size", "8192", "--focus", "false"]) let (smallest, _) = makeModel(plan: Self.proPlan, memoryOptionsMb: [8192, 16384], lockedMemoryOptionsMb: [4096, 32768]) smallest.memoryMb = 4096 #expect(smallest.memoryMb == 8192) let (maxModel, _) = makeModel(plan: Self.maxPlan) - maxModel.memoryMb = 65536 - #expect(maxModel.memoryMb == 65536) + maxModel.memoryMb = 32768 + #expect(maxModel.memoryMb == 32768) } @Test func sizeLabelsDescribeMemoryAndDisk() { @@ -196,7 +196,7 @@ struct NewMachineModelTests { @Test func serverOptionsAreSortedAndDeduplicated() { let plan = MachinePlanSnapshot(activeCount: 0, maxActiveVms: 50, planId: "pro") - let (model, _) = makeModel(plan: plan, memoryOptionsMb: [16384, 8192, 8192]) + let (model, _) = makeModel(plan: plan, memoryOptionsMb: [16384, 8192, 8192], lockedMemoryOptionsMb: []) #expect(model.memoryOptions == [8192, 16384]) #expect(model.memoryMb == 8192) } @@ -213,12 +213,12 @@ struct NewMachineModelTests { /// the size is user input here, and it travels as `--size`. @Test func defaultCreateIsADesktopMachineAtTheSelectedSize() { let (model, recorder) = makeModel(plan: Self.maxPlan) - model.memoryMb = 65536 + model.memoryMb = 32768 model.create() let request = recorder.value.first #expect(request?.kind == .desktop) #expect(request?.name == nil) - #expect(request?.arguments == ["vm", "new", "--desktop", "--size", "65536", "--focus", "false"]) + #expect(request?.arguments == ["vm", "new", "--desktop", "--size", "32768", "--focus", "false"]) } @Test func baseSetupHasNoSizeFlagAndDefaultsToADesktop() { diff --git a/scripts/check-socket-capabilities.py b/scripts/check-socket-capabilities.py index c686bfe60ab4..29e0194c3578 100644 --- a/scripts/check-socket-capabilities.py +++ b/scripts/check-socket-capabilities.py @@ -51,6 +51,7 @@ debug.panel_snapshot.reset debug.portal.stats debug.pro_welcome_checklist.show +debug.native_pricing.show debug.right_sidebar.focus debug.session_snapshot_benchmark debug.session_snapshot_seed_scrollback diff --git a/scripts/localization-allowed-omissions.json b/scripts/localization-allowed-omissions.json index affbf2583b0d..b725e5e37464 100644 --- a/scripts/localization-allowed-omissions.json +++ b/scripts/localization-allowed-omissions.json @@ -2059,13 +2059,9 @@ "de": "Reviewed native word, technical abbreviation, or SaaS tier name with the same spelling in this context." } }, - "pricing.native.compare.concurrent.free": { - "class": "format", - "source": "1" - }, "pricing.native.compare.concurrent.paid": { "class": "format", - "source": "50" + "source": "5" }, "pricing.native.compare.support.community": { "source": "Community", diff --git a/skills/cmux-billing/SKILL.md b/skills/cmux-billing/SKILL.md index 391f0c6483a1..cbe349fd4f61 100644 --- a/skills/cmux-billing/SKILL.md +++ b/skills/cmux-billing/SKILL.md @@ -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-go-monthly-10` ($10/mo), `cmux-pro-monthly-50` ($50/mo), `cmux-max-monthly-200` ($200/mo, monthly only, no yearly Price on purpose), `cmux-team-monthly-60` ($60/user/mo), all billed monthly. Go and Max are personal plans like Pro: the user-scoped subscription row's `plan` column is derived from its Price lookup key (`personalPlanIdForSubscription`), `cmuxPlan` mirrors the exact purchased plan, and every `isPro` check passes. A Pro subscriber who buys Max goes through `/api/billing/portal?flow=switch_plan&plan=max`, which opens Stripe's subscription-update flow with the portal configuration stamped `metadata.purpose=personal_plan_switch` (the script provisions it; the account default configuration stays quantity-only). 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, `cmux-pro-yearly-480` $480, `cmux-team-yearly-576` $576) 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`; test-mode Max product `prod_VEpLXrLxxrIFTx` (price `price_1UELhFLbtiW1udY8YwbWvt0P`, switch portal `bpc_1UELhHLbtiW1udY89mZWzSi0`); live Max product `prod_VEpRwst8UwD5cm` (price `price_1UELmdGhInAdn3Jb8Gpe9ilK`, switch portal `bpc_1UELmeGhInAdn3JbgZqLIbP0`). `STRIPE_PERSONAL_PLAN_SWITCH_PORTAL_CONFIGURATION_ID` is pinned in Vercel (production = live id, development and preview = test id) so the restricted production key never has to list portal configurations. 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. 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. Free, Pro, Team, and Founder's machines stop at 24 GB RAM (`PLAN_MAX_MEMORY_MB`); the 32 GB and 64 GB ladder rows are what Max sells (`maxMemoryMbForPlan("max")`). The machine list publishes `lockedMemoryOptionsMb` and `memoryUpgradePlanId`, and a create that asks for a locked size gets `402 vm_memory_requires_plan` instead of a silently smaller machine. 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. +The paid Cloud VM allowance lives in `web/services/vms/machineSpec.ts`: Pro allows up to 5 machines for one paid account. Team grants 5 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. Free, Pro, Team, and Founder's machines stop at 4 vCPU / 8 GB RAM (`PLAN_MAX_MEMORY_MB`); the 16, 24, and 32 GB ladder rows (up to 16 vCPU) are what Max sells (`maxMemoryMbForPlan("max")`). Max also allows 5 machines. The machine list publishes `lockedMemoryOptionsMb` and `memoryUpgradePlanId`, and a create that asks for a locked size gets `402 vm_memory_requires_plan` instead of a silently smaller machine. 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. New annual checkout requests return `annual_unavailable` before creating Stripe state. Annual prices stay active only for existing subscriptions; new checkout price resolution and the personal plan-switch portal allow monthly offers only. Pricing views and CTA analytics always report `interval: month` and `discount_percent: 0`. diff --git a/web/app/[locale]/(landing)/docs/cloud/machines/page.tsx b/web/app/[locale]/(landing)/docs/cloud/machines/page.tsx index 8250b0a1b04d..d435f665c0fb 100644 --- a/web/app/[locale]/(landing)/docs/cloud/machines/page.tsx +++ b/web/app/[locale]/(landing)/docs/cloud/machines/page.tsx @@ -50,10 +50,9 @@ cmux vm new --detach --json`} 4g4 GB216 GB{t("planPaid")} 8g8 GB432 GB{t("planPaid")} - 16g16 GB864 GB{t("planPaid")} - 24g24 GB1296 GB{t("planPaid")} + 16g16 GB864 GB{t("planMax")} + 24g24 GB1296 GB{t("planMax")} 32g32 GB16128 GB{t("planMax")} - 64g64 GB32128 GB{t("planMax")}

{t("sizesDefault")}

@@ -65,6 +64,7 @@ cmux vm new --detach --json`}
  • {t("limitsPaid")}
  • {t("limitsTeam")}
  • {t("limitsMax")}
  • +
  • {t("limitsOversize")}
  • {t("limitsPricing")}

    diff --git a/web/messages/ar.json b/web/messages/ar.json index 5568348cad0c..de7aeb395ef2 100644 --- a/web/messages/ar.json +++ b/web/messages/ar.json @@ -490,11 +490,11 @@ ], "pro": [ "وكلاء سحابيون على Cloud VMs", - "24 غيغابايت RAM و6 vCPU مشتركة بين الأجهزة", + "حتى 5 Cloud VMs، بحد أقصى 4 vCPU و8 غيغابايت RAM لكل VM", "تطبيق cmux لنظام iOS" ], "max": [ - "64 غيغابايت RAM و16 vCPU مشتركة بين الأجهزة", + "حتى 5 Cloud VMs، بحد أقصى 16 vCPU و32 غيغابايت RAM لكل VM", "أحجام أجهزة أكبر", "كل ما في Pro" ], @@ -2840,12 +2840,12 @@ "limitsTitle": "حدود الخطة", "limitsDesc": "تحسب حدود الخطة عدد الأجهزة، وليس مجمعاً مشتركاً من المعالج أو الذاكرة. يحصل كل جهاز على موارد حجمه.", "limitsFree": "لا تستطيع الحسابات المجانية إنشاء أجهزة.", - "limitsPaid": "يمكن لحسابات Pro وMax امتلاك ما يصل إلى 50 جهازاً نشطاً. يكون الجهاز نشطاً ما دام قيد التشغيل أو البدء.", - "limitsTeam": "يمكن لحسابات الفرق امتلاك ما يصل إلى 50 جهازاً نشطاً لكل مقعد مدفوع.", - "limitsMax": "تحتاج الأجهزة الأكبر من 24 GB إلى cmux Max.", + "limitsPaid": "يمكن لحسابات Pro وMax امتلاك ما يصل إلى 5 أجهزة نشطة. يكون الجهاز نشطاً ما دام قيد التشغيل أو البدء.", + "limitsTeam": "يمكن لحسابات الفرق امتلاك ما يصل إلى 5 أجهزة نشطة لكل مقعد مدفوع.", + "limitsMax": "تحتاج الأجهزة الأكبر من 8 GB إلى cmux Max. تصل أجهزة Max إلى 32 GB و16 vCPU.", "limitsPricing": "راجع صفحة الأسعار لمعرفة الأسعار الحالية. لا يوجد قياس للاستهلاك ولا فوترة للاستخدام الزائد.", "resizeTitle": "تغيير الحجم", - "resizeDesc": "يمكنك تكبير جهاز دون فقدان البيانات. انقر بزر الفأرة الأيمن على الجهاز واختر Resize machine، أو استخدم cmux vm resize. يمكن لتغيير الحجم تكبير الجهاز فقط. يكبر القرص بخطوات 4 GB حتى 256 GB، والذاكرة حتى 64 GB، والمعالج حتى 32 vCPU. تحدد خطتك الحد الأقصى الفعلي.", + "resizeDesc": "يمكنك تكبير جهاز دون فقدان البيانات. انقر بزر الفأرة الأيمن على الجهاز واختر Resize machine، أو استخدم cmux vm resize. يمكن لتغيير الحجم تكبير الجهاز فقط. يكبر القرص بخطوات 4 GB حتى 256 GB، والذاكرة حتى 32 GB، والمعالج حتى 16 vCPU. تحدد خطتك الحد الأقصى الفعلي.", "pauseTitle": "الإيقاف المؤقت والاستئناف", "pauseDesc": "يوقف cmux vm pause موارد الحوسبة للجهاز. يبقى القرص ومساحات العمل وسجل الطرفية. تتوقف العمليات العاملة. يعيد cmux vm resume تشغيل الجهاز. كما أن فتح جهاز متوقف مؤقتاً أو تشغيل أمر عليه يبدأ تشغيله أيضاً.", "pauseIdle": "لا تتوقف الأجهزة مؤقتاً من تلقاء نفسها عندما تكون خاملة. يستمر الجهاز في العمل حتى توقفه مؤقتاً أو تحذفه.", @@ -2996,7 +2996,7 @@ "errPlan": "تتطلب آلات Cloud الافتراضية خطة مدفوعة", "fixPlan": "لا تستطيع الحسابات المجانية إنشاء أجهزة. قم بالترقية من صفحة الأسعار.", "errSize": "الحجم يحتاج إلى خطة أكبر", - "fixSize": "تحتاج الأجهزة الأكبر من 24 GB إلى cmux Max. اختر حجماً أصغر أو قم بالترقية.", + "fixSize": "تحتاج الأجهزة الأكبر من 8 GB إلى cmux Max. اختر حجماً أصغر أو قم بالترقية.", "errLimit": "تم بلوغ حد الأجهزة النشطة", "fixLimit": "أوقف جهازاً مؤقتاً أو احذفه، ثم حاول مجدداً.", "errLocked": "الجهاز مقفل", diff --git a/web/messages/bs.json b/web/messages/bs.json index e43a29bade88..96580da31502 100644 --- a/web/messages/bs.json +++ b/web/messages/bs.json @@ -490,11 +490,11 @@ ], "pro": [ "Cloud agenti na Cloud VMs", - "24 GB RAM-a i 6 vCPU-a dijeljeno među VM-ovima", + "Do 5 Cloud VM-ova, najviše 4 vCPU-a i 8 GB RAM-a po VM-u", "cmux aplikacija za iOS" ], "max": [ - "64 GB RAM-a i 16 vCPU-a dijeljeno među VM-ovima", + "Do 5 Cloud VM-ova, najviše 16 vCPU-a i 32 GB RAM-a po VM-u", "Veće mašine", "Sve iz Pro" ], @@ -2843,12 +2843,12 @@ "limitsTitle": "Ograničenja plana", "limitsDesc": "Ograničenja plana broje mašine, a ne zajednički skup CPU-a ili memorije. Svaka mašina dobija resurse svoje veličine.", "limitsFree": "Besplatni računi ne mogu kreirati mašine.", - "limitsPaid": "Pro i Max računi mogu imati do 50 aktivnih mašina. Mašina je aktivna dok radi ili se pokreće.", - "limitsTeam": "Timski računi mogu imati do 50 aktivnih mašina po svakom plaćenom mjestu.", - "limitsMax": "Mašine veće od 24 GB zahtijevaju cmux Max.", + "limitsPaid": "Pro i Max računi mogu imati do 5 aktivnih mašina. Mašina je aktivna dok radi ili se pokreće.", + "limitsTeam": "Timski računi mogu imati do 5 aktivnih mašina po svakom plaćenom mjestu.", + "limitsMax": "Mašine veće od 8 GB zahtijevaju cmux Max. Max mašine idu do 32 GB i 16 vCPU-a.", "limitsPricing": "Trenutne cijene pogledajte na stranici s cijenama. Nema mjerenja potrošnje ni naplate prekoračenja.", "resizeTitle": "Promjena veličine", - "resizeDesc": "Mašinu možete povećati bez gubitka podataka. Kliknite desnim klikom na mašinu i izaberite Resize machine ili koristite cmux vm resize. Veličina se može samo povećati. Disk raste u koracima od 4 GB do 256 GB, memorija do 64 GB, a CPU do 32 vCPU. Stvarni maksimum određuje vaš plan.", + "resizeDesc": "Mašinu možete povećati bez gubitka podataka. Kliknite desnim klikom na mašinu i izaberite Resize machine ili koristite cmux vm resize. Veličina se može samo povećati. Disk raste u koracima od 4 GB do 256 GB, memorija do 32 GB, a CPU do 16 vCPU. Stvarni maksimum određuje vaš plan.", "pauseTitle": "Pauziranje i nastavak", "pauseDesc": "cmux vm pause zaustavlja računarske resurse mašine. Disk, radni prostori i historija terminala ostaju. Pokrenuti procesi se zaustavljaju. cmux vm resume ponovo pokreće mašinu. Otvaranje pauzirane mašine ili pokretanje komande na njoj također je pokreće.", "pauseIdle": "Mašine se ne pauziraju same kada miruju. Mašina radi dok je ne pauzirate ili obrišete.", @@ -2999,7 +2999,7 @@ "errPlan": "Cloud VM-ovi zahtijevaju plaćeni plan", "fixPlan": "Besplatni računi ne mogu kreirati mašine. Nadogradite na stranici s cijenama.", "errSize": "Veličina zahtijeva veći plan", - "fixSize": "Mašine veće od 24 GB zahtijevaju cmux Max. Izaberite manju veličinu ili nadogradite plan.", + "fixSize": "Mašine veće od 8 GB zahtijevaju cmux Max. Izaberite manju veličinu ili nadogradite plan.", "errLimit": "Dostignuto je ograničenje aktivnih mašina", "fixLimit": "Pauzirajte ili obrišite mašinu, pa pokušajte ponovo.", "errLocked": "Mašina je zaključana", diff --git a/web/messages/da.json b/web/messages/da.json index 0e62dc597e54..8fc35179b8fb 100644 --- a/web/messages/da.json +++ b/web/messages/da.json @@ -490,11 +490,11 @@ ], "pro": [ "Cloud-agenter på Cloud VMs", - "24 GB RAM og 6 vCPU'er delt mellem VM'er", + "Op til 5 Cloud VMs, op til 4 vCPU'er og 8 GB RAM pr. VM", "cmux-appen til iOS" ], "max": [ - "64 GB RAM og 16 vCPU'er delt mellem VM'er", + "Op til 5 Cloud VMs, op til 16 vCPU'er og 32 GB RAM pr. VM", "Større maskinstørrelser", "Alt i Pro" ], @@ -2840,12 +2840,12 @@ "limitsTitle": "Abonnementsgrænser", "limitsDesc": "Abonnementsgrænser tæller maskiner, ikke en fælles pulje af CPU eller hukommelse. Hver maskine får ressourcerne for sin størrelse.", "limitsFree": "Gratis konti kan ikke oprette maskiner.", - "limitsPaid": "Pro- og Max-konti kan have op til 50 aktive maskiner. En maskine er aktiv, mens den kører eller starter.", - "limitsTeam": "Teamkonti kan have op til 50 aktive maskiner pr. betalt plads.", - "limitsMax": "Maskiner større end 24 GB kræver cmux Max.", + "limitsPaid": "Pro- og Max-konti kan have op til 5 aktive maskiner. En maskine er aktiv, mens den kører eller starter.", + "limitsTeam": "Teamkonti kan have op til 5 aktive maskiner pr. betalt plads.", + "limitsMax": "Maskiner større end 8 GB kræver cmux Max. Max-maskiner går op til 32 GB og 16 vCPU'er.", "limitsPricing": "Se prissiden for aktuelle priser. Der er ingen forbrugsmåling og ingen fakturering for overforbrug.", "resizeTitle": "Ændr størrelse", - "resizeDesc": "Du kan gøre en maskine større uden tab af data. Højreklik på maskinen, og vælg Resize machine, eller brug cmux vm resize. En størrelsesændring kan kun gøre en maskine større. Disken vokser i trin á 4 GB op til 256 GB, hukommelsen op til 64 GB og CPU op til 32 vCPU. Dit abonnement fastsætter det reelle maksimum.", + "resizeDesc": "Du kan gøre en maskine større uden tab af data. Højreklik på maskinen, og vælg Resize machine, eller brug cmux vm resize. En størrelsesændring kan kun gøre en maskine større. Disken vokser i trin á 4 GB op til 256 GB, hukommelsen op til 32 GB og CPU op til 16 vCPU. Dit abonnement fastsætter det reelle maksimum.", "pauseTitle": "Pause og genoptagelse", "pauseDesc": "cmux vm pause stopper maskinens beregning. Disken, arbejdsområderne og terminalhistorikken bevares. Kørende processer stopper. cmux vm resume starter maskinen igen. Hvis du åbner en maskine på pause eller kører en kommando på den, starter den også.", "pauseIdle": "Maskiner sættes ikke på pause af sig selv, når de er inaktive. En maskine kører, indtil du sætter den på pause eller sletter den.", @@ -2996,7 +2996,7 @@ "errPlan": "Cloud-VM'er kræver et betalt abonnement", "fixPlan": "Gratis konti kan ikke oprette maskiner. Opgrader på prissiden.", "errSize": "En størrelse kræver et større abonnement", - "fixSize": "Maskiner større end 24 GB kræver cmux Max. Vælg en mindre størrelse, eller opgrader.", + "fixSize": "Maskiner større end 8 GB kræver cmux Max. Vælg en mindre størrelse, eller opgrader.", "errLimit": "Grænsen for aktive maskiner er nået", "fixLimit": "Sæt en maskine på pause, eller slet den, og prøv igen.", "errLocked": "Maskinen er låst", diff --git a/web/messages/de.json b/web/messages/de.json index 2fb1406715e1..f11632b59490 100644 --- a/web/messages/de.json +++ b/web/messages/de.json @@ -490,11 +490,11 @@ ], "pro": [ "Cloud-Agenten auf Cloud VMs", - "24 GB RAM und 6 vCPUs für alle VMs", + "Bis zu 5 Cloud VMs mit bis zu 4 vCPUs und 8 GB RAM pro VM", "Die cmux iOS-App" ], "max": [ - "64 GB RAM und 16 vCPUs für alle VMs", + "Bis zu 5 Cloud VMs mit bis zu 16 vCPUs und 32 GB RAM pro VM", "Größere Maschinengrößen", "Alles aus Pro" ], @@ -2843,12 +2843,12 @@ "limitsTitle": "Planlimits", "limitsDesc": "Planlimits zählen Maschinen, keinen gemeinsamen Pool aus CPU oder Arbeitsspeicher. Jede Maschine erhält die Ressourcen ihrer Größe.", "limitsFree": "Kostenlose Konten können keine Maschinen erstellen.", - "limitsPaid": "Pro- und Max-Konten können bis zu 50 aktive Maschinen haben. Eine Maschine ist aktiv, solange sie läuft oder startet.", - "limitsTeam": "Team-Konten können pro bezahltem Platz bis zu 50 aktive Maschinen haben.", - "limitsMax": "Maschinen mit mehr als 24 GB benötigen cmux Max.", + "limitsPaid": "Pro- und Max-Konten können bis zu 5 aktive Maschinen haben. Eine Maschine ist aktiv, solange sie läuft oder startet.", + "limitsTeam": "Team-Konten können pro bezahltem Platz bis zu 5 aktive Maschinen haben.", + "limitsMax": "Maschinen mit mehr als 8 GB benötigen cmux Max. Max-Maschinen gehen bis 32 GB und 16 vCPUs.", "limitsPricing": "Aktuelle Preise findest du auf der Preisseite. Es gibt keine nutzungsbasierte Abrechnung und keine Überschreitungsgebühren.", "resizeTitle": "Größe ändern", - "resizeDesc": "Du kannst eine Maschine ohne Datenverlust vergrößern. Klicke mit der rechten Maustaste auf die Maschine und wähle Resize machine, oder nutze cmux vm resize. Eine Maschine lässt sich nur vergrößern. Die Festplatte wächst in 4-GB-Schritten bis 256 GB, der Arbeitsspeicher bis 64 GB und die CPU bis 32 vCPU. Das tatsächliche Maximum legt dein Plan fest.", + "resizeDesc": "Du kannst eine Maschine ohne Datenverlust vergrößern. Klicke mit der rechten Maustaste auf die Maschine und wähle Resize machine, oder nutze cmux vm resize. Eine Maschine lässt sich nur vergrößern. Die Festplatte wächst in 4-GB-Schritten bis 256 GB, der Arbeitsspeicher bis 32 GB und die CPU bis 16 vCPU. Das tatsächliche Maximum legt dein Plan fest.", "pauseTitle": "Pausieren und fortsetzen", "pauseDesc": "cmux vm pause stoppt die Rechenleistung der Maschine. Festplatte, Workspaces und Terminalverlauf bleiben erhalten. Laufende Prozesse werden beendet. cmux vm resume startet die Maschine wieder. Auch das Öffnen einer pausierten Maschine oder das Ausführen eines Befehls darauf startet sie.", "pauseIdle": "Maschinen pausieren nicht von selbst, wenn sie im Leerlauf sind. Eine Maschine läuft weiter, bis du sie pausierst oder löschst.", @@ -2999,7 +2999,7 @@ "errPlan": "Cloud-VMs erfordern einen kostenpflichtigen Plan", "fixPlan": "Kostenlose Konten können keine Maschinen erstellen. Führe auf der Preisseite ein Upgrade durch.", "errSize": "Eine Größe erfordert einen höheren Plan", - "fixSize": "Maschinen mit mehr als 24 GB benötigen cmux Max. Wähle eine kleinere Größe oder führe ein Upgrade durch.", + "fixSize": "Maschinen mit mehr als 8 GB benötigen cmux Max. Wähle eine kleinere Größe oder führe ein Upgrade durch.", "errLimit": "Limit für aktive Maschinen erreicht", "fixLimit": "Pausiere oder lösche eine Maschine und versuche es erneut.", "errLocked": "Maschine ist gesperrt", diff --git a/web/messages/en.json b/web/messages/en.json index 17b93ea9eb45..09777bf42191 100644 --- a/web/messages/en.json +++ b/web/messages/en.json @@ -652,7 +652,7 @@ "activeBody": "Your Max plan is active. It renews on {date}.", "pendingBody": "Your Max plan ends on {date}.", "monthlyPrice": "${amount}/mo", - "upsell": "Max includes up to 50 Cloud VMs sharing 64 GB RAM and 16 vCPUs for $200 per month, billed monthly." + "upsell": "Max includes up to 5 Cloud VMs, with up to 16 vCPUs and 32 GB RAM per VM, for $200 per month, billed monthly." }, "teamPanel": { "notFound": "This team is not available. You may have left it, or it was deleted.", @@ -717,11 +717,11 @@ ], "pro": [ "Cloud agents on Cloud VMs", - "24 GB RAM and 6 vCPUs shared across VMs", + "Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM", "The cmux iOS app" ], "max": [ - "64 GB RAM and 16 vCPUs shared across VMs", + "Up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM", "Larger machine sizes", "Everything in Pro" ], @@ -1667,7 +1667,7 @@ "featuresLead": "Everything in Free, plus:", "features": [ "Cloud agents on Cloud VMs in isolated remote sandboxes", - "Up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs", + "Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM", "Unlimited workspaces", "The cmux iOS app and email support" ], @@ -1684,7 +1684,7 @@ "cta": "Get Max", "featuresLead": "Everything in Pro, plus:", "features": [ - "Up to 50 Cloud VMs sharing 64 GB RAM and 16 vCPUs", + "Up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM", "Unlimited workspaces and the iOS app" ] }, @@ -1783,27 +1783,27 @@ { "label": "Concurrent Cloud VMs", "free": "false", - "pro": "50", - "max": "50", - "team": "50 per user", + "pro": "5", + "max": "5", + "team": "5 per user", "enterprise": "Custom", "go": "1" }, { - "label": "Resources shared across all Cloud VMs", + "label": "Resources per Cloud VM", "free": "false", - "pro": "24 GB RAM and 6 vCPUs shared across all VMs", - "max": "64 GB RAM and 16 vCPUs shared across all VMs", - "team": "24 GB RAM and 6 vCPUs per user, shared across all their VMs", + "pro": "4 vCPUs and 8 GB RAM", + "max": "16 vCPUs and 32 GB RAM", + "team": "4 vCPUs and 8 GB RAM", "enterprise": "Custom", "go": "2 vCPU, 4 GiB RAM, 16 GiB disk" }, { "label": "Largest Cloud VM", - "free": "24 GB RAM", - "pro": "24 GB RAM", - "max": "64 GB RAM", - "team": "24 GB RAM", + "free": "false", + "pro": "8 GB RAM", + "max": "32 GB RAM", + "team": "8 GB RAM", "enterprise": "Custom", "go": "4 GiB RAM" }, @@ -1901,11 +1901,11 @@ }, { "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 Cloud VMs with 24 GB RAM and 6 vCPUs shared across all VMs. Team includes the same pool per user." + "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 5 Cloud VMs, with up to 4 vCPUs and 8 GB RAM per VM. Team includes the same allowance per user." }, { "q": "What does Max add?", - "a": "Max is $200/mo, billed monthly. Up to 50 Cloud VMs share a total of 64 GB RAM and 16 vCPUs. It includes everything in Pro and supports 32 GB and 64 GB machines within that shared total. Pro, Team, and Free machines top out at 24 GB RAM." + "a": "Max is $200/mo, billed monthly. It includes everything in Pro and up to 5 Cloud VMs, with up to 16 vCPUs and 32 GB RAM per VM. Pro and Team machines top out at 4 vCPUs and 8 GB RAM. Free accounts do not include Cloud VMs." }, { "q": "What is cmux Vault?", @@ -4365,12 +4365,13 @@ "limitsTitle": "Plan limits", "limitsDesc": "Plan limits count machines, not a shared pool of CPU or memory. Each machine gets the resources of its size.", "limitsFree": "Free accounts cannot create machines.", - "limitsPaid": "Pro and Max accounts can have up to 50 active machines. A machine is active while it is running or starting.", - "limitsTeam": "Team accounts can have up to 50 active machines for each paid seat.", - "limitsMax": "Machines larger than 24 GB need cmux Max.", + "limitsPaid": "Pro and Max accounts can have up to 5 active machines. A machine is active while it is running or starting.", + "limitsTeam": "Team accounts can have up to 5 active machines for each paid seat.", + "limitsMax": "Machines larger than 8 GB need cmux Max. Max machines go up to 32 GB and 16 vCPUs.", + "limitsOversize": "A machine larger than your plan allows is locked. You can list and delete it, but you cannot open it, run commands on it, resume it, or resize it until you upgrade.", "limitsPricing": "See the pricing page for current prices. There is no metering and no overage billing.", "resizeTitle": "Resize", - "resizeDesc": "You can make a machine larger without data loss. Right-click the machine and choose Resize machine, or use cmux vm resize. Resize can only grow a machine. Disk grows in 4 GB steps up to 256 GB, memory up to 64 GB, and CPU up to 32 vCPU. Your plan sets the real maximum.", + "resizeDesc": "You can make a machine larger without data loss. Right-click the machine and choose Resize machine, or use cmux vm resize. Resize can only grow a machine. Disk grows in 4 GB steps up to 256 GB, memory up to 32 GB, and CPU up to 16 vCPU. Your plan sets the real maximum.", "pauseTitle": "Pause and resume", "pauseDesc": "cmux vm pause stops the machine's compute. The disk, workspaces, and terminal history stay. Running processes stop. cmux vm resume starts the machine again. Opening a paused machine or running a command on it also starts it.", "pauseIdle": "Machines do not pause by themselves when they are idle. A machine keeps running until you pause it or delete it.", @@ -4521,7 +4522,7 @@ "errPlan": "Cloud VMs require a paid plan", "fixPlan": "Free accounts cannot create machines. Upgrade on the pricing page.", "errSize": "A size needs a larger plan", - "fixSize": "Machines larger than 24 GB need cmux Max. Choose a smaller size or upgrade.", + "fixSize": "Machines larger than 8 GB need cmux Max. Choose a smaller size or upgrade.", "errLimit": "Active machine limit reached", "fixLimit": "Pause or delete a machine, then try again.", "errLocked": "Machine is locked", diff --git a/web/messages/es.json b/web/messages/es.json index fb3071036072..8cc1b7ce9867 100644 --- a/web/messages/es.json +++ b/web/messages/es.json @@ -490,11 +490,11 @@ ], "pro": [ "Agentes en la nube en Cloud VMs", - "24 GB de RAM y 6 vCPU compartidos entre VM", + "Hasta 5 Cloud VMs, con hasta 4 vCPU y 8 GB de RAM por VM", "La app cmux para iOS" ], "max": [ - "64 GB de RAM y 16 vCPU compartidos entre VM", + "Hasta 5 Cloud VMs, con hasta 16 vCPU y 32 GB de RAM por VM", "Máquinas más grandes", "Todo lo de Pro" ], @@ -2842,12 +2842,12 @@ "limitsTitle": "Límites del plan", "limitsDesc": "Los límites del plan cuentan máquinas, no un fondo compartido de CPU o memoria. Cada máquina recibe los recursos de su tamaño.", "limitsFree": "Las cuentas gratuitas no pueden crear máquinas.", - "limitsPaid": "Las cuentas Pro y Max pueden tener hasta 50 máquinas activas. Una máquina está activa mientras está en ejecución o iniciándose.", - "limitsTeam": "Las cuentas de equipo pueden tener hasta 50 máquinas activas por cada puesto de pago.", - "limitsMax": "Las máquinas de más de 24 GB requieren cmux Max.", + "limitsPaid": "Las cuentas Pro y Max pueden tener hasta 5 máquinas activas. Una máquina está activa mientras está en ejecución o iniciándose.", + "limitsTeam": "Las cuentas de equipo pueden tener hasta 5 máquinas activas por cada puesto de pago.", + "limitsMax": "Las máquinas de más de 8 GB requieren cmux Max. Las máquinas Max llegan hasta 32 GB y 16 vCPU.", "limitsPricing": "Consulta la página de precios para ver los precios actuales. No hay medición de uso ni cargos por exceso.", "resizeTitle": "Cambiar tamaño", - "resizeDesc": "Puedes ampliar una máquina sin perder datos. Haz clic derecho en la máquina y elige Resize machine, o usa cmux vm resize. Solo se puede ampliar una máquina, no reducirla. El disco crece en incrementos de 4 GB hasta 256 GB, la memoria hasta 64 GB y la CPU hasta 32 vCPU. Tu plan fija el máximo real.", + "resizeDesc": "Puedes ampliar una máquina sin perder datos. Haz clic derecho en la máquina y elige Resize machine, o usa cmux vm resize. Solo se puede ampliar una máquina, no reducirla. El disco crece en incrementos de 4 GB hasta 256 GB, la memoria hasta 32 GB y la CPU hasta 16 vCPU. Tu plan fija el máximo real.", "pauseTitle": "Pausar y reanudar", "pauseDesc": "cmux vm pause detiene el cómputo de la máquina. El disco, los espacios de trabajo y el historial de los terminales se conservan. Los procesos en ejecución se detienen. cmux vm resume vuelve a iniciar la máquina. Abrir una máquina en pausa o ejecutar un comando en ella también la inicia.", "pauseIdle": "Las máquinas no se pausan solas cuando están inactivas. Una máquina sigue en ejecución hasta que la pausas o la eliminas.", @@ -2998,7 +2998,7 @@ "errPlan": "Las VM de Cloud requieren un plan de pago", "fixPlan": "Las cuentas gratuitas no pueden crear máquinas. Mejora tu plan en la página de precios.", "errSize": "Un tamaño requiere un plan superior", - "fixSize": "Las máquinas de más de 24 GB requieren cmux Max. Elige un tamaño menor o mejora tu plan.", + "fixSize": "Las máquinas de más de 8 GB requieren cmux Max. Elige un tamaño menor o mejora tu plan.", "errLimit": "Se alcanzó el límite de máquinas activas", "fixLimit": "Pausa o elimina una máquina y vuelve a intentarlo.", "errLocked": "La máquina está bloqueada", diff --git a/web/messages/fr.json b/web/messages/fr.json index d47f37c1cab8..1a4b3218b45b 100644 --- a/web/messages/fr.json +++ b/web/messages/fr.json @@ -490,11 +490,11 @@ ], "pro": [ "Agents cloud sur des Cloud VMs", - "24 Go de RAM et 6 vCPU partagés entre les VM", + "Jusqu'à 5 Cloud VMs, jusqu'à 4 vCPU et 8 Go de RAM par VM", "L'app cmux pour iOS" ], "max": [ - "64 Go de RAM et 16 vCPU partagés entre les VM", + "Jusqu'à 5 Cloud VMs, jusqu'à 16 vCPU et 32 Go de RAM par VM", "Des machines plus grandes", "Tout ce qu'inclut Pro" ], @@ -2845,12 +2845,12 @@ "limitsTitle": "Limites de forfait", "limitsDesc": "Les limites de forfait comptent les machines, pas un pool partagé de CPU ou de mémoire. Chaque machine reçoit les ressources de sa taille.", "limitsFree": "Les comptes gratuits ne peuvent pas créer de machines.", - "limitsPaid": "Les comptes Pro et Max peuvent avoir jusqu'à 50 machines actives. Une machine est active tant qu'elle est en cours d'exécution ou de démarrage.", - "limitsTeam": "Les comptes Team peuvent avoir jusqu'à 50 machines actives par siège payant.", - "limitsMax": "Les machines de plus de 24 GB nécessitent cmux Max.", + "limitsPaid": "Les comptes Pro et Max peuvent avoir jusqu'à 5 machines actives. Une machine est active tant qu'elle est en cours d'exécution ou de démarrage.", + "limitsTeam": "Les comptes Team peuvent avoir jusqu'à 5 machines actives par siège payant.", + "limitsMax": "Les machines de plus de 8 GB nécessitent cmux Max. Les machines Max vont jusqu'à 32 GB et 16 vCPU.", "limitsPricing": "Consultez la page des tarifs pour les prix actuels. Il n'y a ni facturation à l'usage ni frais de dépassement.", "resizeTitle": "Redimensionner", - "resizeDesc": "Vous pouvez agrandir une machine sans perte de données. Faites un clic droit sur la machine et choisissez Resize machine, ou utilisez cmux vm resize. Le redimensionnement ne peut qu'agrandir une machine. Le disque augmente par paliers de 4 GB jusqu'à 256 GB, la mémoire jusqu'à 64 GB et le CPU jusqu'à 32 vCPU. Votre forfait fixe le maximum réel.", + "resizeDesc": "Vous pouvez agrandir une machine sans perte de données. Faites un clic droit sur la machine et choisissez Resize machine, ou utilisez cmux vm resize. Le redimensionnement ne peut qu'agrandir une machine. Le disque augmente par paliers de 4 GB jusqu'à 256 GB, la mémoire jusqu'à 32 GB et le CPU jusqu'à 16 vCPU. Votre forfait fixe le maximum réel.", "pauseTitle": "Mettre en pause et reprendre", "pauseDesc": "cmux vm pause arrête le calcul de la machine. Le disque, les espaces de travail et l'historique des terminaux sont conservés. Les processus en cours s'arrêtent. cmux vm resume redémarre la machine. Ouvrir une machine en pause ou y exécuter une commande la redémarre aussi.", "pauseIdle": "Les machines ne se mettent pas en pause d'elles-mêmes lorsqu'elles sont inactives. Une machine continue de tourner jusqu'à ce que vous la mettiez en pause ou la supprimiez.", @@ -3001,7 +3001,7 @@ "errPlan": "Les VM Cloud nécessitent un forfait payant", "fixPlan": "Les comptes gratuits ne peuvent pas créer de machines. Passez à un forfait payant sur la page des tarifs.", "errSize": "Une taille nécessite un forfait supérieur", - "fixSize": "Les machines de plus de 24 GB nécessitent cmux Max. Choisissez une taille plus petite ou passez à un forfait supérieur.", + "fixSize": "Les machines de plus de 8 GB nécessitent cmux Max. Choisissez une taille plus petite ou passez à un forfait supérieur.", "errLimit": "Limite de machines actives atteinte", "fixLimit": "Mettez en pause ou supprimez une machine, puis réessayez.", "errLocked": "La machine est verrouillée", diff --git a/web/messages/it.json b/web/messages/it.json index 8817a23646de..ded2e1484a6d 100644 --- a/web/messages/it.json +++ b/web/messages/it.json @@ -490,11 +490,11 @@ ], "pro": [ "Agenti cloud su Cloud VMs", - "24 GB di RAM e 6 vCPU condivisi tra le VM", + "Fino a 5 Cloud VMs, con fino a 4 vCPU e 8 GB di RAM per VM", "L'app cmux per iOS" ], "max": [ - "64 GB di RAM e 16 vCPU condivisi tra le VM", + "Fino a 5 Cloud VMs, con fino a 16 vCPU e 32 GB di RAM per VM", "Macchine più grandi", "Tutto ciò che include Pro" ], @@ -2843,12 +2843,12 @@ "limitsTitle": "Limiti del piano", "limitsDesc": "I limiti del piano contano le macchine, non un pool condiviso di CPU o memoria. Ogni macchina riceve le risorse della sua dimensione.", "limitsFree": "Gli account gratuiti non possono creare macchine.", - "limitsPaid": "Gli account Pro e Max possono avere fino a 50 macchine attive. Una macchina è attiva mentre è in esecuzione o in avvio.", - "limitsTeam": "Gli account Team possono avere fino a 50 macchine attive per ogni postazione a pagamento.", - "limitsMax": "Le macchine oltre 24 GB richiedono cmux Max.", + "limitsPaid": "Gli account Pro e Max possono avere fino a 5 macchine attive. Una macchina è attiva mentre è in esecuzione o in avvio.", + "limitsTeam": "Gli account Team possono avere fino a 5 macchine attive per ogni postazione a pagamento.", + "limitsMax": "Le macchine oltre 8 GB richiedono cmux Max. Le macchine Max arrivano fino a 32 GB e 16 vCPU.", "limitsPricing": "Consulta la pagina dei prezzi per i prezzi attuali. Non ci sono misurazioni a consumo né addebiti per eccedenze.", "resizeTitle": "Ridimensionare", - "resizeDesc": "Puoi ingrandire una macchina senza perdere dati. Fai clic destro sulla macchina e scegli Resize machine, oppure usa cmux vm resize. Il ridimensionamento può solo ingrandire una macchina. Il disco cresce a passi di 4 GB fino a 256 GB, la memoria fino a 64 GB e la CPU fino a 32 vCPU. Il massimo effettivo dipende dal tuo piano.", + "resizeDesc": "Puoi ingrandire una macchina senza perdere dati. Fai clic destro sulla macchina e scegli Resize machine, oppure usa cmux vm resize. Il ridimensionamento può solo ingrandire una macchina. Il disco cresce a passi di 4 GB fino a 256 GB, la memoria fino a 32 GB e la CPU fino a 16 vCPU. Il massimo effettivo dipende dal tuo piano.", "pauseTitle": "Pausa e ripresa", "pauseDesc": "cmux vm pause arresta il calcolo della macchina. Il disco, i workspace e la cronologia dei terminali restano. I processi in esecuzione si arrestano. cmux vm resume riavvia la macchina. Anche aprire una macchina in pausa o eseguirvi un comando la avvia.", "pauseIdle": "Le macchine non vanno in pausa da sole quando sono inattive. Una macchina resta in esecuzione finché non la metti in pausa o la elimini.", @@ -2999,7 +2999,7 @@ "errPlan": "Le VM Cloud richiedono un piano a pagamento", "fixPlan": "Gli account gratuiti non possono creare macchine. Passa a un piano a pagamento dalla pagina dei prezzi.", "errSize": "Una dimensione richiede un piano superiore", - "fixSize": "Le macchine oltre 24 GB richiedono cmux Max. Scegli una dimensione inferiore o passa a un piano superiore.", + "fixSize": "Le macchine oltre 8 GB richiedono cmux Max. Scegli una dimensione inferiore o passa a un piano superiore.", "errLimit": "Limite di macchine attive raggiunto", "fixLimit": "Metti in pausa o elimina una macchina, poi riprova.", "errLocked": "La macchina è bloccata", diff --git a/web/messages/ja.json b/web/messages/ja.json index 84f6ed2f2fc1..09cbe397a11c 100644 --- a/web/messages/ja.json +++ b/web/messages/ja.json @@ -652,7 +652,7 @@ "activeBody": "Max プランは有効です。次回更新日は {date} です。", "pendingBody": "Max プランは {date} に終了します。", "monthlyPrice": "${amount}/月", - "upsell": "Max は月額 $200 の月払いで、最大 50 台の Cloud VM が 64 GB RAM と 16 vCPU を共有します。" + "upsell": "Max は月額 $200 の月払いで、最大 5 台の Cloud VM を利用でき、VM あたり最大 16 vCPU と 32 GB RAM を使えます。" }, "teamPanel": { "notFound": "このチームは利用できません。チームから退出したか、チームが削除された可能性があります。", @@ -717,11 +717,11 @@ ], "pro": [ "Cloud VM 上のクラウドエージェント", - "VM 全体で共有する 24 GB RAM と 6 vCPU", + "最大 5 台の Cloud VM、VM あたり最大 4 vCPU と 8 GB RAM", "cmux iOS アプリ" ], "max": [ - "VM 全体で共有する 64 GB RAM と 16 vCPU", + "最大 5 台の Cloud VM、VM あたり最大 16 vCPU と 32 GB RAM", "より大きなマシンサイズ", "Pro のすべての機能" ], @@ -1667,7 +1667,7 @@ "featuresLead": "Free のすべてに加えて:", "features": [ "分離されたリモートサンドボックスの Cloud VM 上で動くクラウドエージェント", - "最大 50 台の Cloud VM で、24 GB RAM と 6 vCPUを共有", + "最大 5 台の Cloud VM、VM あたり最大 4 vCPU と 8 GB RAM", "ワークスペースは無制限", "cmux iOS アプリとメールサポート" ], @@ -1684,7 +1684,7 @@ "cta": "Max を入手", "featuresLead": "Pro のすべてに加えて:", "features": [ - "最大 50 台の Cloud VM で 64 GB RAM と 16 vCPU を共有", + "最大 5 台の Cloud VM、VM あたり最大 16 vCPU と 32 GB RAM", "無制限のワークスペースと iOS アプリ" ] }, @@ -1783,27 +1783,27 @@ { "label": "同時 Cloud VM 数", "free": "false", - "pro": "50", - "max": "50", - "team": "ユーザーごとに 50", + "pro": "5", + "max": "5", + "team": "ユーザーごとに 5", "enterprise": "カスタム", "go": "1" }, { - "label": "すべての Cloud VM で共有するリソース", + "label": "Cloud VM あたりのリソース", "free": "false", - "pro": "24 GB RAM と 6 vCPUをすべての VM で共有", - "max": "すべての VM で 64 GB RAM と 16 vCPU を共有", - "team": "ユーザーあたり 24 GB RAM と 6 vCPUを、そのユーザーの VM で共有", + "pro": "4 vCPU と 8 GB RAM", + "max": "16 vCPU と 32 GB RAM", + "team": "4 vCPU と 8 GB RAM", "enterprise": "カスタム", "go": "2 vCPU、4 GiB RAM、16 GiB ディスク" }, { "label": "最大の Cloud VM", - "free": "24 GB RAM", - "pro": "24 GB RAM", - "max": "64 GB RAM", - "team": "24 GB RAM", + "free": "false", + "pro": "8 GB RAM", + "max": "32 GB RAM", + "team": "8 GB RAM", "enterprise": "カスタム", "go": "4 GiB RAM" }, @@ -1901,11 +1901,11 @@ }, { "q": "Cloud VM とは何ですか?", - "a": "クラウド上の永続的な Linux マシンで、使い捨て(cattle)ではなくペットです。Zen of cmux のとおり、マシンはソリューションではなくプリミティブです。ファイル、インストールしたツール、状態はそのまま保持され、その上に構築したものはあなたのものです。エージェントはそこでリポジトリをクローンし、コマンドを実行し、プルリクエストを開くため、お使いの Mac には触れません。Pro では最大 50 台の Cloud VM で、24 GB RAM と 6 vCPUを共有します。Team ではユーザーごとに同じプールが適用されます。" + "a": "クラウド上の永続的な Linux マシンで、使い捨て(cattle)ではなくペットです。Zen of cmux のとおり、マシンはソリューションではなくプリミティブです。ファイル、インストールしたツール、状態はそのまま保持され、その上に構築したものはあなたのものです。エージェントはそこでリポジトリをクローンし、コマンドを実行し、プルリクエストを開くため、お使いの Mac には触れません。Pro では最大 5 台の Cloud VM を利用でき、VM あたり最大 4 vCPU と 8 GB RAM を使えます。Team ではユーザーごとに同じ枠が適用されます。" }, { "q": "Max では何が追加されますか?", - "a": "Max は月払いで $200/月です。最大 50 台の Cloud VM で合計 64 GB RAM と 16 vCPU を共有します。Pro のすべてを含み、この共有リソースの範囲内で 32 GB と 64 GB のマシンを利用できます。Pro、Team、Free のマシンは最大 24 GB RAM です。" + "a": "Max は月払いで $200/月です。Pro のすべてを含み、最大 5 台の Cloud VM を利用でき、VM あたり最大 16 vCPU と 32 GB RAM です。Pro と Team のマシンは最大 4 vCPU と 8 GB RAM です。Free には Cloud VM は含まれません。" }, { "q": "cmux Vault とは何ですか?", @@ -4277,12 +4277,13 @@ "limitsTitle": "プランの上限", "limitsDesc": "プランの上限はマシンの台数で数えます。CPUやメモリの共有プールではありません。各マシンにはそのサイズ分のリソースが割り当てられます。", "limitsFree": "無料アカウントではマシンを作成できません。", - "limitsPaid": "ProおよびMaxアカウントでは、アクティブなマシンを最大50台まで持てます。実行中または起動中のマシンがアクティブとして数えられます。", - "limitsTeam": "Teamアカウントでは、有料シート1つにつきアクティブなマシンを最大50台まで持てます。", - "limitsMax": "24 GBを超えるマシンにはcmux Maxが必要です。", + "limitsPaid": "ProおよびMaxアカウントでは、アクティブなマシンを最大5台まで持てます。実行中または起動中のマシンがアクティブとして数えられます。", + "limitsTeam": "Teamアカウントでは、有料シート1つにつきアクティブなマシンを最大5台まで持てます。", + "limitsMax": "8 GBを超えるマシンにはcmux Maxが必要です。Maxのマシンは最大32 GB、16 vCPUまで使えます。", + "limitsOversize": "プランの上限より大きいマシンはロックされます。一覧表示と削除はできますが、アップグレードするまで開く、コマンドを実行する、再開する、サイズを変更することはできません。", "limitsPricing": "現在の価格は料金ページを参照してください。従量課金や超過料金はありません。", "resizeTitle": "サイズ変更", - "resizeDesc": "データを失わずにマシンを大きくできます。マシンを右クリックして「マシンのサイズを変更」を選ぶか、cmux vm resize を使います。サイズ変更は拡大のみです。ディスクは4 GB単位で最大256 GB、メモリは最大64 GB、CPUは最大32 vCPUまで拡大できます。実際の上限はプランによって決まります。", + "resizeDesc": "データを失わずにマシンを大きくできます。マシンを右クリックして「マシンのサイズを変更」を選ぶか、cmux vm resize を使います。サイズ変更は拡大のみです。ディスクは4 GB単位で最大256 GB、メモリは最大32 GB、CPUは最大16 vCPUまで拡大できます。実際の上限はプランによって決まります。", "pauseTitle": "一時停止と再開", "pauseDesc": "cmux vm pause はマシンのコンピュートを停止します。ディスク、ワークスペース、ターミナルの履歴は残りますが、実行中のプロセスは停止します。cmux vm resume でマシンを再び起動します。一時停止中のマシンを開いたり、そのマシンでコマンドを実行したりしても起動します。", "pauseIdle": "アイドル状態でもマシンは自動では一時停止しません。一時停止するか削除するまで動き続けます。", @@ -4433,7 +4434,7 @@ "errPlan": "Cloud VMには有料プランが必要", "fixPlan": "無料アカウントではマシンを作成できません。料金ページでアップグレードしてください。", "errSize": "サイズに上位プランが必要", - "fixSize": "24 GBを超えるマシンにはcmux Maxが必要です。小さいサイズを選ぶか、アップグレードしてください。", + "fixSize": "8 GBを超えるマシンにはcmux Maxが必要です。小さいサイズを選ぶか、アップグレードしてください。", "errLimit": "アクティブなマシンの上限に達した", "fixLimit": "マシンを一時停止または削除してから、もう一度試してください。", "errLocked": "マシンがロックされている", diff --git a/web/messages/km.json b/web/messages/km.json index 4dd560052346..f4a4221857d5 100644 --- a/web/messages/km.json +++ b/web/messages/km.json @@ -490,11 +490,11 @@ ], "pro": [ "ភ្នាក់ងារពពកនៅលើ Cloud VMs", - "RAM 24 GB និង vCPU 6 ចែករំលែកគ្រប់ VM", + "Cloud VMs រហូតដល់ 5 ដោយមាន vCPU រហូតដល់ 4 និង RAM 8 GB ក្នុងមួយ VM", "កម្មវិធី cmux សម្រាប់ iOS" ], "max": [ - "RAM 64 GB និង vCPU 16 ចែករំលែកគ្រប់ VM", + "Cloud VMs រហូតដល់ 5 ដោយមាន vCPU រហូតដល់ 16 និង RAM 32 GB ក្នុងមួយ VM", "ម៉ាស៊ីនធំជាងមុន", "អ្វីៗទាំងអស់ក្នុង Pro" ], @@ -2841,12 +2841,12 @@ "limitsTitle": "ដែនកំណត់គម្រោង", "limitsDesc": "ដែនកំណត់គម្រោងរាប់ចំនួនម៉ាស៊ីន មិនមែនបណ្ដុំ CPU ឬអង្គចងចាំរួមគ្នាទេ។ ម៉ាស៊ីននីមួយៗទទួលបានធនធានតាមទំហំរបស់វា។", "limitsFree": "គណនីឥតគិតថ្លៃមិនអាចបង្កើតម៉ាស៊ីនបានទេ។", - "limitsPaid": "គណនី Pro និង Max អាចមានម៉ាស៊ីនសកម្មរហូតដល់ 50។ ម៉ាស៊ីនមួយសកម្ម ខណៈវាកំពុងដំណើរការ ឬកំពុងចាប់ផ្ដើម។", - "limitsTeam": "គណនីក្រុមអាចមានម៉ាស៊ីនសកម្មរហូតដល់ 50 សម្រាប់កៅអីបង់ប្រាក់នីមួយៗ។", - "limitsMax": "ម៉ាស៊ីនធំជាង 24 GB ត្រូវការ cmux Max។", + "limitsPaid": "គណនី Pro និង Max អាចមានម៉ាស៊ីនសកម្មរហូតដល់ 5។ ម៉ាស៊ីនមួយសកម្ម ខណៈវាកំពុងដំណើរការ ឬកំពុងចាប់ផ្ដើម។", + "limitsTeam": "គណនីក្រុមអាចមានម៉ាស៊ីនសកម្មរហូតដល់ 5 សម្រាប់កៅអីបង់ប្រាក់នីមួយៗ។", + "limitsMax": "ម៉ាស៊ីនធំជាង 8 GB ត្រូវការ cmux Max។ ម៉ាស៊ីន Max អាចមានរហូតដល់ 32 GB និង 16 vCPU។", "limitsPricing": "សូមមើលទំព័រតម្លៃសម្រាប់តម្លៃបច្ចុប្បន្ន។ មិនមានការវាស់ការប្រើប្រាស់ និងមិនមានការគិតថ្លៃលើសទេ។", "resizeTitle": "ប្ដូរទំហំ", - "resizeDesc": "អ្នកអាចពង្រីកម៉ាស៊ីនដោយមិនបាត់ទិន្នន័យ។ ចុចកណ្ដុរស្ដាំលើម៉ាស៊ីន ហើយជ្រើសរើស Resize machine ឬប្រើ cmux vm resize។ ការប្ដូរទំហំអាចពង្រីកម៉ាស៊ីនបានតែប៉ុណ្ណោះ។ ថាសកើនឡើងម្ដង 4 GB រហូតដល់ 256 GB អង្គចងចាំរហូតដល់ 64 GB និង CPU រហូតដល់ 32 vCPU។ គម្រោងរបស់អ្នកកំណត់កម្រិតអតិបរមាពិតប្រាកដ។", + "resizeDesc": "អ្នកអាចពង្រីកម៉ាស៊ីនដោយមិនបាត់ទិន្នន័យ។ ចុចកណ្ដុរស្ដាំលើម៉ាស៊ីន ហើយជ្រើសរើស Resize machine ឬប្រើ cmux vm resize។ ការប្ដូរទំហំអាចពង្រីកម៉ាស៊ីនបានតែប៉ុណ្ណោះ។ ថាសកើនឡើងម្ដង 4 GB រហូតដល់ 256 GB អង្គចងចាំរហូតដល់ 32 GB និង CPU រហូតដល់ 16 vCPU។ គម្រោងរបស់អ្នកកំណត់កម្រិតអតិបរមាពិតប្រាកដ។", "pauseTitle": "ផ្អាក និងបន្ត", "pauseDesc": "cmux vm pause បញ្ឈប់ការគណនារបស់ម៉ាស៊ីន។ ថាស workspace និងប្រវត្តិទែមីណលនៅដដែល។ ដំណើរការដែលកំពុងដំណើរការនឹងឈប់។ cmux vm resume ចាប់ផ្ដើមម៉ាស៊ីនម្ដងទៀត។ ការបើកម៉ាស៊ីនដែលបានផ្អាក ឬដំណើរការពាក្យបញ្ជានៅលើវា ក៏ចាប់ផ្ដើមវាដែរ។", "pauseIdle": "ម៉ាស៊ីនមិនផ្អាកដោយខ្លួនឯងនៅពេលទំនេរទេ។ ម៉ាស៊ីនបន្តដំណើរការរហូតដល់អ្នកផ្អាក ឬលុបវា។", @@ -2997,7 +2997,7 @@ "errPlan": "Cloud VM តម្រូវឱ្យមានគម្រោងបង់ប្រាក់", "fixPlan": "គណនីឥតគិតថ្លៃមិនអាចបង្កើតម៉ាស៊ីនបានទេ។ ដំឡើងកម្រិតនៅលើទំព័រតម្លៃ។", "errSize": "ទំហំនេះត្រូវការគម្រោងធំជាង", - "fixSize": "ម៉ាស៊ីនធំជាង 24 GB ត្រូវការ cmux Max។ ជ្រើសរើសទំហំតូចជាង ឬដំឡើងកម្រិត។", + "fixSize": "ម៉ាស៊ីនធំជាង 8 GB ត្រូវការ cmux Max។ ជ្រើសរើសទំហំតូចជាង ឬដំឡើងកម្រិត។", "errLimit": "ឈានដល់ដែនកំណត់ម៉ាស៊ីនសកម្ម", "fixLimit": "ផ្អាក ឬលុបម៉ាស៊ីនមួយ រួចព្យាយាមម្ដងទៀត។", "errLocked": "ម៉ាស៊ីនត្រូវបានចាក់សោ", diff --git a/web/messages/ko.json b/web/messages/ko.json index 7f2bedb12e63..3c8c877563ca 100644 --- a/web/messages/ko.json +++ b/web/messages/ko.json @@ -490,11 +490,11 @@ ], "pro": [ "Cloud VM의 클라우드 에이전트", - "VM 전체가 공유하는 24 GB RAM과 vCPU 6개", + "최대 5대의 Cloud VM, VM당 최대 vCPU 4개와 8 GB RAM", "cmux iOS 앱" ], "max": [ - "VM 전체가 공유하는 64 GB RAM과 vCPU 16개", + "최대 5대의 Cloud VM, VM당 최대 vCPU 16개와 32 GB RAM", "더 큰 머신 크기", "Pro의 모든 기능" ], @@ -2840,12 +2840,12 @@ "limitsTitle": "플랜 한도", "limitsDesc": "플랜 한도는 공유 CPU나 메모리 풀이 아니라 머신 수를 기준으로 합니다. 각 머신은 해당 크기만큼의 리소스를 받습니다.", "limitsFree": "무료 계정은 머신을 만들 수 없습니다.", - "limitsPaid": "Pro 및 Max 계정은 활성 머신을 최대 50대까지 가질 수 있습니다. 실행 중이거나 시작 중인 머신이 활성 머신입니다.", - "limitsTeam": "Team 계정은 유료 시트 하나당 활성 머신을 최대 50대까지 가질 수 있습니다.", - "limitsMax": "24 GB보다 큰 머신에는 cmux Max가 필요합니다.", + "limitsPaid": "Pro 및 Max 계정은 활성 머신을 최대 5대까지 가질 수 있습니다. 실행 중이거나 시작 중인 머신이 활성 머신입니다.", + "limitsTeam": "Team 계정은 유료 시트 하나당 활성 머신을 최대 5대까지 가질 수 있습니다.", + "limitsMax": "8 GB보다 큰 머신에는 cmux Max가 필요합니다. Max 머신은 최대 32 GB와 vCPU 16개까지 가능합니다.", "limitsPricing": "현재 가격은 가격 페이지를 확인하세요. 사용량 기반 과금이나 초과 요금은 없습니다.", "resizeTitle": "크기 조정", - "resizeDesc": "데이터 손실 없이 머신을 더 크게 만들 수 있습니다. 머신을 오른쪽 클릭하고 Resize machine을 선택하거나 cmux vm resize를 사용합니다. 크기 조정은 머신을 키우는 것만 가능합니다. 디스크는 4 GB 단위로 최대 256 GB, 메모리는 최대 64 GB, CPU는 최대 32 vCPU까지 늘릴 수 있습니다. 실제 최대치는 플랜에 따라 정해집니다.", + "resizeDesc": "데이터 손실 없이 머신을 더 크게 만들 수 있습니다. 머신을 오른쪽 클릭하고 Resize machine을 선택하거나 cmux vm resize를 사용합니다. 크기 조정은 머신을 키우는 것만 가능합니다. 디스크는 4 GB 단위로 최대 256 GB, 메모리는 최대 32 GB, CPU는 최대 16 vCPU까지 늘릴 수 있습니다. 실제 최대치는 플랜에 따라 정해집니다.", "pauseTitle": "일시 중지와 재개", "pauseDesc": "cmux vm pause는 머신의 컴퓨팅을 중지합니다. 디스크, 워크스페이스, 터미널 기록은 유지되고, 실행 중인 프로세스는 중지됩니다. cmux vm resume은 머신을 다시 시작합니다. 일시 중지된 머신을 열거나 그 머신에서 명령을 실행해도 머신이 시작됩니다.", "pauseIdle": "머신은 유휴 상태여도 스스로 일시 중지되지 않습니다. 일시 중지하거나 삭제할 때까지 계속 실행됩니다.", @@ -2996,7 +2996,7 @@ "errPlan": "Cloud VM에는 유료 플랜이 필요함", "fixPlan": "무료 계정은 머신을 만들 수 없습니다. 가격 페이지에서 업그레이드하세요.", "errSize": "선택한 크기에 더 높은 플랜이 필요함", - "fixSize": "24 GB보다 큰 머신에는 cmux Max가 필요합니다. 더 작은 크기를 고르거나 업그레이드하세요.", + "fixSize": "8 GB보다 큰 머신에는 cmux Max가 필요합니다. 더 작은 크기를 고르거나 업그레이드하세요.", "errLimit": "활성 머신 한도에 도달함", "fixLimit": "머신을 일시 중지하거나 삭제한 뒤 다시 시도하세요.", "errLocked": "머신이 잠겨 있음", diff --git a/web/messages/no.json b/web/messages/no.json index 943b9aadb467..fdc5c855c745 100644 --- a/web/messages/no.json +++ b/web/messages/no.json @@ -490,11 +490,11 @@ ], "pro": [ "Skyagenter på Cloud VMs", - "24 GB RAM og 6 vCPU-er delt mellom VM-er", + "Opptil 5 Cloud VMs, opptil 4 vCPU-er og 8 GB RAM per VM", "cmux-appen for iOS" ], "max": [ - "64 GB RAM og 16 vCPU-er delt mellom VM-er", + "Opptil 5 Cloud VMs, opptil 16 vCPU-er og 32 GB RAM per VM", "Større maskinstørrelser", "Alt i Pro" ], @@ -2840,12 +2840,12 @@ "limitsTitle": "Abonnementsgrenser", "limitsDesc": "Abonnementsgrenser teller maskiner, ikke en felles pott med CPU eller minne. Hver maskin får ressursene til sin størrelse.", "limitsFree": "Gratiskontoer kan ikke opprette maskiner.", - "limitsPaid": "Pro- og Max-kontoer kan ha opptil 50 aktive maskiner. En maskin er aktiv mens den kjører eller starter.", - "limitsTeam": "Teamkontoer kan ha opptil 50 aktive maskiner per betalt lisens.", - "limitsMax": "Maskiner større enn 24 GB krever cmux Max.", + "limitsPaid": "Pro- og Max-kontoer kan ha opptil 5 aktive maskiner. En maskin er aktiv mens den kjører eller starter.", + "limitsTeam": "Teamkontoer kan ha opptil 5 aktive maskiner per betalt lisens.", + "limitsMax": "Maskiner større enn 8 GB krever cmux Max. Max-maskiner går opptil 32 GB og 16 vCPU-er.", "limitsPricing": "Se prissiden for gjeldende priser. Det er ingen forbruksmåling og ingen fakturering for overforbruk.", "resizeTitle": "Endre størrelse", - "resizeDesc": "Du kan gjøre en maskin større uten å miste data. Høyreklikk på maskinen og velg Resize machine, eller bruk cmux vm resize. Størrelsesendring kan bare gjøre en maskin større. Disken vokser i steg på 4 GB opptil 256 GB, minnet opptil 64 GB og CPU opptil 32 vCPU. Abonnementet ditt bestemmer det reelle maksimumet.", + "resizeDesc": "Du kan gjøre en maskin større uten å miste data. Høyreklikk på maskinen og velg Resize machine, eller bruk cmux vm resize. Størrelsesendring kan bare gjøre en maskin større. Disken vokser i steg på 4 GB opptil 256 GB, minnet opptil 32 GB og CPU opptil 16 vCPU. Abonnementet ditt bestemmer det reelle maksimumet.", "pauseTitle": "Pause og gjenoppta", "pauseDesc": "cmux vm pause stopper maskinens beregningsressurser. Disken, arbeidsområdene og terminalhistorikken beholdes. Kjørende prosesser stopper. cmux vm resume starter maskinen igjen. Å åpne en maskin på pause eller kjøre en kommando på den starter den også.", "pauseIdle": "Maskiner settes ikke på pause av seg selv når de er inaktive. En maskin kjører til du setter den på pause eller sletter den.", @@ -2996,7 +2996,7 @@ "errPlan": "Cloud-VM-er krever et betalt abonnement", "fixPlan": "Gratiskontoer kan ikke opprette maskiner. Oppgrader på prissiden.", "errSize": "En størrelse krever et større abonnement", - "fixSize": "Maskiner større enn 24 GB krever cmux Max. Velg en mindre størrelse eller oppgrader.", + "fixSize": "Maskiner større enn 8 GB krever cmux Max. Velg en mindre størrelse eller oppgrader.", "errLimit": "Grensen for aktive maskiner er nådd", "fixLimit": "Sett en maskin på pause eller slett den, og prøv igjen.", "errLocked": "Maskinen er låst", diff --git a/web/messages/pl.json b/web/messages/pl.json index f042e25c027a..0caff8778299 100644 --- a/web/messages/pl.json +++ b/web/messages/pl.json @@ -490,11 +490,11 @@ ], "pro": [ "Agenci w chmurze na Cloud VMs", - "24 GB RAM i 6 vCPU współdzielone przez VM", + "Do 5 Cloud VMs, maksymalnie 4 vCPU i 8 GB RAM na VM", "Aplikacja cmux na iOS" ], "max": [ - "64 GB RAM i 16 vCPU współdzielone przez VM", + "Do 5 Cloud VMs, maksymalnie 16 vCPU i 32 GB RAM na VM", "Większe maszyny", "Wszystko z planu Pro" ], @@ -2843,12 +2843,12 @@ "limitsTitle": "Limity planu", "limitsDesc": "Limity planu dotyczą liczby maszyn, a nie wspólnej puli CPU czy pamięci. Każda maszyna dostaje zasoby odpowiadające jej rozmiarowi.", "limitsFree": "Konta darmowe nie mogą tworzyć maszyn.", - "limitsPaid": "Konta Pro i Max mogą mieć do 50 aktywnych maszyn. Maszyna jest aktywna, gdy działa lub się uruchamia.", - "limitsTeam": "Konta zespołowe mogą mieć do 50 aktywnych maszyn na każde płatne stanowisko.", - "limitsMax": "Maszyny większe niż 24 GB wymagają cmux Max.", + "limitsPaid": "Konta Pro i Max mogą mieć do 5 aktywnych maszyn. Maszyna jest aktywna, gdy działa lub się uruchamia.", + "limitsTeam": "Konta zespołowe mogą mieć do 5 aktywnych maszyn na każde płatne stanowisko.", + "limitsMax": "Maszyny większe niż 8 GB wymagają cmux Max. Maszyny Max mają do 32 GB i 16 vCPU.", "limitsPricing": "Aktualne ceny znajdziesz na stronie z cennikiem. Nie ma pomiaru zużycia ani opłat za przekroczenia.", "resizeTitle": "Zmiana rozmiaru", - "resizeDesc": "Możesz powiększyć maszynę bez utraty danych. Kliknij maszynę prawym przyciskiem i wybierz Resize machine lub użyj cmux vm resize. Zmiana rozmiaru może tylko powiększyć maszynę. Dysk rośnie w krokach po 4 GB do 256 GB, pamięć do 64 GB, a CPU do 32 vCPU. Rzeczywiste maksimum wyznacza Twój plan.", + "resizeDesc": "Możesz powiększyć maszynę bez utraty danych. Kliknij maszynę prawym przyciskiem i wybierz Resize machine lub użyj cmux vm resize. Zmiana rozmiaru może tylko powiększyć maszynę. Dysk rośnie w krokach po 4 GB do 256 GB, pamięć do 32 GB, a CPU do 16 vCPU. Rzeczywiste maksimum wyznacza Twój plan.", "pauseTitle": "Wstrzymywanie i wznawianie", "pauseDesc": "cmux vm pause zatrzymuje moc obliczeniową maszyny. Dysk, obszary robocze i historia terminali zostają zachowane. Działające procesy zostają zatrzymane. cmux vm resume ponownie uruchamia maszynę. Otwarcie wstrzymanej maszyny lub uruchomienie na niej polecenia również ją uruchamia.", "pauseIdle": "Maszyny nie wstrzymują się same, gdy są bezczynne. Maszyna działa, dopóki jej nie wstrzymasz lub nie usuniesz.", @@ -2999,7 +2999,7 @@ "errPlan": "Maszyny wirtualne Cloud wymagają płatnego planu", "fixPlan": "Konta darmowe nie mogą tworzyć maszyn. Zmień plan na stronie z cennikiem.", "errSize": "Rozmiar wymaga wyższego planu", - "fixSize": "Maszyny większe niż 24 GB wymagają cmux Max. Wybierz mniejszy rozmiar lub zmień plan.", + "fixSize": "Maszyny większe niż 8 GB wymagają cmux Max. Wybierz mniejszy rozmiar lub zmień plan.", "errLimit": "Osiągnięto limit aktywnych maszyn", "fixLimit": "Wstrzymaj lub usuń maszynę, a następnie spróbuj ponownie.", "errLocked": "Maszyna jest zablokowana", diff --git a/web/messages/pt-BR.json b/web/messages/pt-BR.json index 291c666287c8..bba7318c4b16 100644 --- a/web/messages/pt-BR.json +++ b/web/messages/pt-BR.json @@ -490,11 +490,11 @@ ], "pro": [ "Agentes na nuvem em Cloud VMs", - "24 GB de RAM e 6 vCPUs compartilhados entre VMs", + "Até 5 Cloud VMs, com até 4 vCPUs e 8 GB de RAM por VM", "O app cmux para iOS" ], "max": [ - "64 GB de RAM e 16 vCPUs compartilhados entre VMs", + "Até 5 Cloud VMs, com até 16 vCPUs e 32 GB de RAM por VM", "Máquinas maiores", "Tudo do Pro" ], @@ -2843,12 +2843,12 @@ "limitsTitle": "Limites do plano", "limitsDesc": "Os limites do plano contam máquinas, não um conjunto compartilhado de CPU ou memória. Cada máquina recebe os recursos do seu tamanho.", "limitsFree": "Contas gratuitas não podem criar máquinas.", - "limitsPaid": "Contas Pro e Max podem ter até 50 máquinas ativas. Uma máquina está ativa enquanto está em execução ou iniciando.", - "limitsTeam": "Contas de equipe podem ter até 50 máquinas ativas por assento pago.", - "limitsMax": "Máquinas maiores que 24 GB exigem o cmux Max.", + "limitsPaid": "Contas Pro e Max podem ter até 5 máquinas ativas. Uma máquina está ativa enquanto está em execução ou iniciando.", + "limitsTeam": "Contas de equipe podem ter até 5 máquinas ativas por assento pago.", + "limitsMax": "Máquinas maiores que 8 GB exigem o cmux Max. Máquinas Max vão até 32 GB e 16 vCPUs.", "limitsPricing": "Consulte a página de preços para ver os valores atuais. Não há medição de uso nem cobrança por excedente.", "resizeTitle": "Redimensionar", - "resizeDesc": "Você pode aumentar uma máquina sem perder dados. Clique com o botão direito na máquina e escolha Resize machine, ou use cmux vm resize. O redimensionamento só pode aumentar uma máquina. O disco cresce em passos de 4 GB até 256 GB, a memória até 64 GB e a CPU até 32 vCPU. Seu plano define o máximo real.", + "resizeDesc": "Você pode aumentar uma máquina sem perder dados. Clique com o botão direito na máquina e escolha Resize machine, ou use cmux vm resize. O redimensionamento só pode aumentar uma máquina. O disco cresce em passos de 4 GB até 256 GB, a memória até 32 GB e a CPU até 16 vCPU. Seu plano define o máximo real.", "pauseTitle": "Pausar e retomar", "pauseDesc": "cmux vm pause interrompe a computação da máquina. O disco, os espaços de trabalho e o histórico dos terminais permanecem. Os processos em execução param. cmux vm resume inicia a máquina novamente. Abrir uma máquina pausada ou executar um comando nela também a inicia.", "pauseIdle": "As máquinas não pausam sozinhas quando estão ociosas. Uma máquina continua rodando até você pausá-la ou excluí-la.", @@ -2999,7 +2999,7 @@ "errPlan": "As VMs do Cloud exigem um plano pago", "fixPlan": "Contas gratuitas não podem criar máquinas. Faça upgrade na página de preços.", "errSize": "Um tamanho exige um plano maior", - "fixSize": "Máquinas maiores que 24 GB exigem o cmux Max. Escolha um tamanho menor ou faça upgrade.", + "fixSize": "Máquinas maiores que 8 GB exigem o cmux Max. Escolha um tamanho menor ou faça upgrade.", "errLimit": "Limite de máquinas ativas atingido", "fixLimit": "Pause ou exclua uma máquina e tente de novo.", "errLocked": "A máquina está bloqueada", diff --git a/web/messages/ru.json b/web/messages/ru.json index 7464e777f0d9..a85652beb204 100644 --- a/web/messages/ru.json +++ b/web/messages/ru.json @@ -490,11 +490,11 @@ ], "pro": [ "Облачные агенты на Cloud VMs", - "24 ГБ RAM и 6 vCPU на все VM", + "До 5 Cloud VMs, до 4 vCPU и 8 ГБ RAM на каждую VM", "Приложение cmux для iOS" ], "max": [ - "64 ГБ RAM и 16 vCPU на все VM", + "До 5 Cloud VMs, до 16 vCPU и 32 ГБ RAM на каждую VM", "Более крупные машины", "Всё из Pro" ], @@ -2843,12 +2843,12 @@ "limitsTitle": "Ограничения тарифа", "limitsDesc": "Ограничения тарифа считают машины, а не общий пул CPU или памяти. Каждая машина получает ресурсы своего размера.", "limitsFree": "Бесплатные аккаунты не могут создавать машины.", - "limitsPaid": "Аккаунты Pro и Max могут иметь до 50 активных машин. Машина считается активной, пока она работает или запускается.", - "limitsTeam": "Командные аккаунты могут иметь до 50 активных машин на каждое платное место.", - "limitsMax": "Для машин больше 24 GB нужен cmux Max.", + "limitsPaid": "Аккаунты Pro и Max могут иметь до 5 активных машин. Машина считается активной, пока она работает или запускается.", + "limitsTeam": "Командные аккаунты могут иметь до 5 активных машин на каждое платное место.", + "limitsMax": "Для машин больше 8 GB нужен cmux Max. Машины Max бывают до 32 GB и 16 vCPU.", "limitsPricing": "Актуальные цены указаны на странице цен. Поминутного учета и оплаты за превышение нет.", "resizeTitle": "Изменение размера", - "resizeDesc": "Машину можно увеличить без потери данных. Щелкните машину правой кнопкой и выберите Resize machine или используйте cmux vm resize. Размер можно только увеличить. Диск растет шагами по 4 GB до 256 GB, память до 64 GB, а CPU до 32 vCPU. Фактический максимум задает ваш тариф.", + "resizeDesc": "Машину можно увеличить без потери данных. Щелкните машину правой кнопкой и выберите Resize machine или используйте cmux vm resize. Размер можно только увеличить. Диск растет шагами по 4 GB до 256 GB, память до 32 GB, а CPU до 16 vCPU. Фактический максимум задает ваш тариф.", "pauseTitle": "Пауза и возобновление", "pauseDesc": "cmux vm pause останавливает вычислительные ресурсы машины. Диск, рабочие пространства и история терминалов сохраняются. Запущенные процессы останавливаются. cmux vm resume снова запускает машину. Открытие приостановленной машины или запуск команды на ней тоже ее запускает.", "pauseIdle": "Машины не встают на паузу сами при простое. Машина работает, пока вы ее не приостановите или не удалите.", @@ -2999,7 +2999,7 @@ "errPlan": "Для облачных VM нужен платный тариф", "fixPlan": "Бесплатные аккаунты не могут создавать машины. Перейдите на платный тариф на странице цен.", "errSize": "Для размера нужен тариф выше", - "fixSize": "Для машин больше 24 GB нужен cmux Max. Выберите размер поменьше или повысьте тариф.", + "fixSize": "Для машин больше 8 GB нужен cmux Max. Выберите размер поменьше или повысьте тариф.", "errLimit": "Достигнут лимит активных машин", "fixLimit": "Приостановите или удалите машину и повторите попытку.", "errLocked": "Машина заблокирована", diff --git a/web/messages/th.json b/web/messages/th.json index 237ed9b8bb67..30ea4bd0be8c 100644 --- a/web/messages/th.json +++ b/web/messages/th.json @@ -490,11 +490,11 @@ ], "pro": [ "เอเจนต์บนคลาวด์ใน Cloud VMs", - "RAM 24 GB และ 6 vCPU ใช้ร่วมกันทุก VM", + "Cloud VMs สูงสุด 5 เครื่อง แต่ละเครื่องมีได้สูงสุด 4 vCPU และ RAM 8 GB", "แอป cmux สำหรับ iOS" ], "max": [ - "RAM 64 GB และ 16 vCPU ใช้ร่วมกันทุก VM", + "Cloud VMs สูงสุด 5 เครื่อง แต่ละเครื่องมีได้สูงสุด 16 vCPU และ RAM 32 GB", "เครื่องขนาดใหญ่ขึ้น", "ทุกอย่างใน Pro" ], @@ -2848,12 +2848,12 @@ "limitsTitle": "ข้อจำกัดของแผน", "limitsDesc": "ข้อจำกัดของแผนนับจำนวนเครื่อง ไม่ใช่พูล CPU หรือหน่วยความจำที่ใช้ร่วมกัน แต่ละเครื่องได้ทรัพยากรตามขนาดของตัวเอง", "limitsFree": "บัญชีฟรีสร้างเครื่องไม่ได้", - "limitsPaid": "บัญชี Pro และ Max มีเครื่องที่ใช้งานอยู่ได้สูงสุด 50 เครื่อง เครื่องจะนับว่าใช้งานอยู่ขณะกำลังทำงานหรือกำลังเริ่มต้น", - "limitsTeam": "บัญชีทีมมีเครื่องที่ใช้งานอยู่ได้สูงสุด 50 เครื่องต่อหนึ่งที่นั่งแบบชำระเงิน", - "limitsMax": "เครื่องที่ใหญ่กว่า 24 GB ต้องใช้ cmux Max", + "limitsPaid": "บัญชี Pro และ Max มีเครื่องที่ใช้งานอยู่ได้สูงสุด 5 เครื่อง เครื่องจะนับว่าใช้งานอยู่ขณะกำลังทำงานหรือกำลังเริ่มต้น", + "limitsTeam": "บัญชีทีมมีเครื่องที่ใช้งานอยู่ได้สูงสุด 5 เครื่องต่อหนึ่งที่นั่งแบบชำระเงิน", + "limitsMax": "เครื่องที่ใหญ่กว่า 8 GB ต้องใช้ cmux Max เครื่อง Max มีได้ถึง 32 GB และ 16 vCPU", "limitsPricing": "ดูราคาปัจจุบันได้ที่หน้าราคา ไม่มีการคิดค่าบริการตามการใช้งานและไม่มีค่าใช้จ่ายส่วนเกิน", "resizeTitle": "ปรับขนาด", - "resizeDesc": "คุณขยายเครื่องได้โดยไม่สูญเสียข้อมูล คลิกขวาที่เครื่องแล้วเลือก Resize machine หรือใช้ cmux vm resize การปรับขนาดทำได้เฉพาะการขยายเท่านั้น ดิสก์ขยายทีละ 4 GB ได้ถึง 256 GB หน่วยความจำได้ถึง 64 GB และ CPU ได้ถึง 32 vCPU แผนของคุณเป็นตัวกำหนดค่าสูงสุดจริง", + "resizeDesc": "คุณขยายเครื่องได้โดยไม่สูญเสียข้อมูล คลิกขวาที่เครื่องแล้วเลือก Resize machine หรือใช้ cmux vm resize การปรับขนาดทำได้เฉพาะการขยายเท่านั้น ดิสก์ขยายทีละ 4 GB ได้ถึง 256 GB หน่วยความจำได้ถึง 32 GB และ CPU ได้ถึง 16 vCPU แผนของคุณเป็นตัวกำหนดค่าสูงสุดจริง", "pauseTitle": "หยุดชั่วคราวและทำงานต่อ", "pauseDesc": "cmux vm pause จะหยุดการประมวลผลของเครื่อง ดิสก์ workspace และประวัติเทอร์มินัลยังคงอยู่ แต่โปรเซสที่ทำงานอยู่จะหยุด cmux vm resume จะเริ่มเครื่องอีกครั้ง การเปิดเครื่องที่หยุดชั่วคราวหรือรันคำสั่งบนเครื่องนั้นก็จะเริ่มเครื่องเช่นกัน", "pauseIdle": "เครื่องจะไม่หยุดชั่วคราวเองเมื่อไม่มีการใช้งาน เครื่องจะทำงานต่อไปจนกว่าคุณจะหยุดชั่วคราวหรือลบ", @@ -3004,7 +3004,7 @@ "errPlan": "Cloud VM ต้องใช้แผนชำระเงิน", "fixPlan": "บัญชีฟรีสร้างเครื่องไม่ได้ อัปเกรดได้ที่หน้าราคา", "errSize": "ขนาดนี้ต้องใช้แผนที่สูงกว่า", - "fixSize": "เครื่องที่ใหญ่กว่า 24 GB ต้องใช้ cmux Max เลือกขนาดที่เล็กกว่าหรืออัปเกรด", + "fixSize": "เครื่องที่ใหญ่กว่า 8 GB ต้องใช้ cmux Max เลือกขนาดที่เล็กกว่าหรืออัปเกรด", "errLimit": "ถึงขีดจำกัดจำนวนเครื่องที่ใช้งานอยู่แล้ว", "fixLimit": "หยุดชั่วคราวหรือลบเครื่อง แล้วลองอีกครั้ง", "errLocked": "เครื่องถูกล็อก", diff --git a/web/messages/tr.json b/web/messages/tr.json index 3969b3e561b8..6436b65efa8b 100644 --- a/web/messages/tr.json +++ b/web/messages/tr.json @@ -490,11 +490,11 @@ ], "pro": [ "Cloud VMs üzerinde bulut ajanları", - "VM'ler arasında paylaşılan 24 GB RAM ve 6 vCPU", + "VM başına en fazla 4 vCPU ve 8 GB RAM ile en fazla 5 Cloud VM", "cmux iOS uygulaması" ], "max": [ - "VM'ler arasında paylaşılan 64 GB RAM ve 16 vCPU", + "VM başına en fazla 16 vCPU ve 32 GB RAM ile en fazla 5 Cloud VM", "Daha büyük makine boyutları", "Pro'daki her şey" ], @@ -2841,12 +2841,12 @@ "limitsTitle": "Plan sınırları", "limitsDesc": "Plan sınırları ortak bir CPU veya bellek havuzunu değil, makineleri sayar. Her makine kendi boyutunun kaynaklarını alır.", "limitsFree": "Ücretsiz hesaplar makine oluşturamaz.", - "limitsPaid": "Pro ve Max hesaplarında en fazla 50 etkin makine olabilir. Bir makine çalışırken veya başlatılırken etkin sayılır.", - "limitsTeam": "Ekip hesaplarında her ücretli koltuk için en fazla 50 etkin makine olabilir.", - "limitsMax": "24 GB'tan büyük makineler için cmux Max gerekir.", + "limitsPaid": "Pro ve Max hesaplarında en fazla 5 etkin makine olabilir. Bir makine çalışırken veya başlatılırken etkin sayılır.", + "limitsTeam": "Ekip hesaplarında her ücretli koltuk için en fazla 5 etkin makine olabilir.", + "limitsMax": "8 GB'tan büyük makineler için cmux Max gerekir. Max makineleri 32 GB'a ve 16 vCPU'ya kadar çıkar.", "limitsPricing": "Güncel fiyatlar için fiyatlandırma sayfasına bakın. Kullanım ölçümü ve aşım ücreti yoktur.", "resizeTitle": "Yeniden boyutlandırma", - "resizeDesc": "Bir makineyi veri kaybı olmadan büyütebilirsiniz. Makineye sağ tıklayıp Resize machine'i seçin veya cmux vm resize kullanın. Yeniden boyutlandırma yalnızca büyütebilir. Disk 4 GB'lık adımlarla 256 GB'a, bellek 64 GB'a ve CPU 32 vCPU'ya kadar büyür. Gerçek üst sınırı planınız belirler.", + "resizeDesc": "Bir makineyi veri kaybı olmadan büyütebilirsiniz. Makineye sağ tıklayıp Resize machine'i seçin veya cmux vm resize kullanın. Yeniden boyutlandırma yalnızca büyütebilir. Disk 4 GB'lık adımlarla 256 GB'a, bellek 32 GB'a ve CPU 16 vCPU'ya kadar büyür. Gerçek üst sınırı planınız belirler.", "pauseTitle": "Duraklatma ve sürdürme", "pauseDesc": "cmux vm pause makinenin işlem kaynaklarını durdurur. Disk, çalışma alanları ve terminal geçmişi korunur. Çalışan işlemler durur. cmux vm resume makineyi yeniden başlatır. Duraklatılmış bir makineyi açmak veya üzerinde komut çalıştırmak da onu başlatır.", "pauseIdle": "Makineler boşta kaldıklarında kendiliğinden duraklamaz. Bir makine siz onu duraklatana veya silene kadar çalışmaya devam eder.", @@ -2997,7 +2997,7 @@ "errPlan": "Cloud VM'ler için ücretli plan gerekir", "fixPlan": "Ücretsiz hesaplar makine oluşturamaz. Fiyatlandırma sayfasından planınızı yükseltin.", "errSize": "Bir boyut daha büyük bir plan gerektiriyor", - "fixSize": "24 GB'tan büyük makineler için cmux Max gerekir. Daha küçük bir boyut seçin veya planınızı yükseltin.", + "fixSize": "8 GB'tan büyük makineler için cmux Max gerekir. Daha küçük bir boyut seçin veya planınızı yükseltin.", "errLimit": "Etkin makine sınırına ulaşıldı", "fixLimit": "Bir makineyi duraklatın veya silin, ardından yeniden deneyin.", "errLocked": "Makine kilitli", diff --git a/web/messages/uk.json b/web/messages/uk.json index 2bd5e52e941e..dd956fcdf079 100644 --- a/web/messages/uk.json +++ b/web/messages/uk.json @@ -490,11 +490,11 @@ ], "pro": [ "Хмарні агенти на Cloud VMs", - "24 ГБ RAM і 6 vCPU на всі VM", + "До 5 Cloud VMs, до 4 vCPU і 8 ГБ RAM на кожну VM", "Застосунок cmux для iOS" ], "max": [ - "64 ГБ RAM і 16 vCPU на всі VM", + "До 5 Cloud VMs, до 16 vCPU і 32 ГБ RAM на кожну VM", "Більші машини", "Усе з Pro" ], @@ -2911,12 +2911,12 @@ "limitsTitle": "Обмеження тарифу", "limitsDesc": "Обмеження тарифу рахують машини, а не спільний пул CPU чи пам’яті. Кожна машина отримує ресурси свого розміру.", "limitsFree": "Безкоштовні акаунти не можуть створювати машини.", - "limitsPaid": "Акаунти Pro і Max можуть мати до 50 активних машин. Машина вважається активною, поки вона працює або запускається.", - "limitsTeam": "Командні акаунти можуть мати до 50 активних машин на кожне платне місце.", - "limitsMax": "Для машин понад 24 GB потрібен cmux Max.", + "limitsPaid": "Акаунти Pro і Max можуть мати до 5 активних машин. Машина вважається активною, поки вона працює або запускається.", + "limitsTeam": "Командні акаунти можуть мати до 5 активних машин на кожне платне місце.", + "limitsMax": "Для машин понад 8 GB потрібен cmux Max. Машини Max мають до 32 GB і 16 vCPU.", "limitsPricing": "Актуальні ціни вказано на сторінці цін. Погодинного обліку й оплати за перевищення немає.", "resizeTitle": "Зміна розміру", - "resizeDesc": "Машину можна збільшити без втрати даних. Клацніть машину правою кнопкою й виберіть Resize machine або скористайтеся cmux vm resize. Розмір можна лише збільшити. Диск зростає кроками по 4 GB до 256 GB, пам’ять до 64 GB, а CPU до 32 vCPU. Фактичний максимум задає ваш тариф.", + "resizeDesc": "Машину можна збільшити без втрати даних. Клацніть машину правою кнопкою й виберіть Resize machine або скористайтеся cmux vm resize. Розмір можна лише збільшити. Диск зростає кроками по 4 GB до 256 GB, пам’ять до 32 GB, а CPU до 16 vCPU. Фактичний максимум задає ваш тариф.", "pauseTitle": "Пауза та відновлення", "pauseDesc": "cmux vm pause зупиняє обчислювальні ресурси машини. Диск, робочі простори й історія терміналів зберігаються. Запущені процеси зупиняються. cmux vm resume знову запускає машину. Відкриття призупиненої машини або запуск команди на ній теж її запускає.", "pauseIdle": "Машини не призупиняються самі під час простою. Машина працює, доки ви її не призупините або не видалите.", @@ -3067,7 +3067,7 @@ "errPlan": "Для хмарних VM потрібен платний тариф", "fixPlan": "Безкоштовні акаунти не можуть створювати машини. Перейдіть на платний тариф на сторінці цін.", "errSize": "Для розміру потрібен вищий тариф", - "fixSize": "Для машин понад 24 GB потрібен cmux Max. Виберіть менший розмір або підвищте тариф.", + "fixSize": "Для машин понад 8 GB потрібен cmux Max. Виберіть менший розмір або підвищте тариф.", "errLimit": "Досягнуто ліміту активних машин", "fixLimit": "Призупиніть або видаліть машину й спробуйте знову.", "errLocked": "Машину заблоковано", diff --git a/web/messages/zh-CN.json b/web/messages/zh-CN.json index 273e05c5a586..a4abda0f2c88 100644 --- a/web/messages/zh-CN.json +++ b/web/messages/zh-CN.json @@ -490,11 +490,11 @@ ], "pro": [ "在 Cloud VM 上运行云端智能体", - "所有 VM 共享 24 GB 内存和 6 个 vCPU", + "最多 5 台 Cloud VM,每台最多 4 个 vCPU 和 8 GB 内存", "cmux iOS 应用" ], "max": [ - "所有 VM 共享 64 GB 内存和 16 个 vCPU", + "最多 5 台 Cloud VM,每台最多 16 个 vCPU 和 32 GB 内存", "更大的机器规格", "Pro 的全部功能" ], @@ -2843,12 +2843,12 @@ "limitsTitle": "套餐限制", "limitsDesc": "套餐限制按机器数量计算,而不是共享的 CPU 或内存池。每台机器都会获得其规格对应的资源。", "limitsFree": "免费账户无法创建机器。", - "limitsPaid": "Pro 和 Max 账户最多可拥有 50 台活跃机器。正在运行或正在启动的机器即为活跃机器。", - "limitsTeam": "Team 账户每个付费席位最多可拥有 50 台活跃机器。", - "limitsMax": "大于 24 GB 的机器需要 cmux Max。", + "limitsPaid": "Pro 和 Max 账户最多可拥有 5 台活跃机器。正在运行或正在启动的机器即为活跃机器。", + "limitsTeam": "Team 账户每个付费席位最多可拥有 5 台活跃机器。", + "limitsMax": "大于 8 GB 的机器需要 cmux Max。Max 机器最高可达 32 GB 和 16 vCPU。", "limitsPricing": "当前价格请查看定价页面。不按用量计费,也没有超额费用。", "resizeTitle": "调整规格", - "resizeDesc": "你可以在不丢失数据的情况下扩大机器。右键点击机器并选择 Resize machine,或使用 cmux vm resize。调整规格只能扩大机器。磁盘以 4 GB 为步长最多扩展到 256 GB,内存最多 64 GB,CPU 最多 32 vCPU。实际上限由你的套餐决定。", + "resizeDesc": "你可以在不丢失数据的情况下扩大机器。右键点击机器并选择 Resize machine,或使用 cmux vm resize。调整规格只能扩大机器。磁盘以 4 GB 为步长最多扩展到 256 GB,内存最多 32 GB,CPU 最多 16 vCPU。实际上限由你的套餐决定。", "pauseTitle": "暂停与恢复", "pauseDesc": "cmux vm pause 会停止机器的计算资源。磁盘、工作区和终端历史会保留,正在运行的进程会停止。cmux vm resume 会重新启动机器。打开已暂停的机器或在其上运行命令也会启动它。", "pauseIdle": "机器空闲时不会自动暂停。机器会一直运行,直到你暂停或删除它。", @@ -2999,7 +2999,7 @@ "errPlan": "Cloud VM 需要付费套餐", "fixPlan": "免费账户无法创建机器。请在定价页面升级。", "errSize": "所选规格需要更高级的套餐", - "fixSize": "大于 24 GB 的机器需要 cmux Max。请选择更小的规格或升级。", + "fixSize": "大于 8 GB 的机器需要 cmux Max。请选择更小的规格或升级。", "errLimit": "已达到活跃机器数量上限", "fixLimit": "暂停或删除一台机器后重试。", "errLocked": "机器已被锁定", diff --git a/web/messages/zh-TW.json b/web/messages/zh-TW.json index 0e90649f46e9..f35cb53e1274 100644 --- a/web/messages/zh-TW.json +++ b/web/messages/zh-TW.json @@ -490,11 +490,11 @@ ], "pro": [ "在 Cloud VM 上執行雲端代理", - "所有 VM 共用 24 GB RAM 和 6 個 vCPU", + "最多 5 台 Cloud VM,每台最多 4 個 vCPU 和 8 GB RAM", "cmux iOS App" ], "max": [ - "所有 VM 共用 64 GB RAM 和 16 個 vCPU", + "最多 5 台 Cloud VM,每台最多 16 個 vCPU 和 32 GB RAM", "更大的機器規格", "Pro 的所有功能" ], @@ -2842,12 +2842,12 @@ "limitsTitle": "方案限制", "limitsDesc": "方案限制以機器數量計算,而不是共用的 CPU 或記憶體資源池。每台機器都會取得其規格對應的資源。", "limitsFree": "免費帳號無法建立機器。", - "limitsPaid": "Pro 和 Max 帳號最多可擁有 50 台使用中的機器。正在執行或正在啟動的機器即為使用中的機器。", - "limitsTeam": "Team 帳號每個付費席位最多可擁有 50 台使用中的機器。", - "limitsMax": "大於 24 GB 的機器需要 cmux Max。", + "limitsPaid": "Pro 和 Max 帳號最多可擁有 5 台使用中的機器。正在執行或正在啟動的機器即為使用中的機器。", + "limitsTeam": "Team 帳號每個付費席位最多可擁有 5 台使用中的機器。", + "limitsMax": "大於 8 GB 的機器需要 cmux Max。Max 機器最高可達 32 GB 和 16 vCPU。", "limitsPricing": "目前的價格請參閱定價頁面。不依用量計費,也沒有超額費用。", "resizeTitle": "調整規格", - "resizeDesc": "你可以在不遺失資料的情況下擴大機器。在機器上按右鍵並選擇 Resize machine,或使用 cmux vm resize。調整規格只能擴大機器。磁碟以 4 GB 為單位最多擴充到 256 GB,記憶體最多 64 GB,CPU 最多 32 vCPU。實際上限由你的方案決定。", + "resizeDesc": "你可以在不遺失資料的情況下擴大機器。在機器上按右鍵並選擇 Resize machine,或使用 cmux vm resize。調整規格只能擴大機器。磁碟以 4 GB 為單位最多擴充到 256 GB,記憶體最多 32 GB,CPU 最多 16 vCPU。實際上限由你的方案決定。", "pauseTitle": "暫停與繼續", "pauseDesc": "cmux vm pause 會停止機器的運算資源。磁碟、工作區和終端機歷史記錄會保留,執行中的程序則會停止。cmux vm resume 會再次啟動機器。開啟已暫停的機器或在其上執行指令也會啟動它。", "pauseIdle": "機器閒置時不會自動暫停。機器會持續執行,直到你暫停或刪除它。", @@ -2998,7 +2998,7 @@ "errPlan": "Cloud VM 需要付費方案", "fixPlan": "免費帳號無法建立機器。請在定價頁面升級。", "errSize": "所選規格需要更高階的方案", - "fixSize": "大於 24 GB 的機器需要 cmux Max。請選擇較小的規格或升級。", + "fixSize": "大於 8 GB 的機器需要 cmux Max。請選擇較小的規格或升級。", "errLimit": "已達使用中機器數量上限", "fixLimit": "暫停或刪除一台機器後再試一次。", "errLocked": "機器已被鎖定", diff --git a/web/scripts/cloud-vm/freeProvisioningAudit.mjs b/web/scripts/cloud-vm/freeProvisioningAudit.mjs index de6d5de0e628..495fa3ca8de4 100644 --- a/web/scripts/cloud-vm/freeProvisioningAudit.mjs +++ b/web/scripts/cloud-vm/freeProvisioningAudit.mjs @@ -19,6 +19,7 @@ function normalized(value) { /** Same decision the runtime makes: does this env open free-plan provisioning? */ export function isFreeProvisioningAllowed(env) { + if (env.VERCEL_ENV === "production") return false; const allow = env[FREE_PROVISIONING_ALLOW_KEY]; if (allow !== undefined) return TRUTHY.has(normalized(allow)); const legacy = env[FREE_PROVISIONING_LEGACY_KEY]; diff --git a/web/scripts/stripe/provision-catalog.sh b/web/scripts/stripe/provision-catalog.sh index af91c3cc65c2..7a8b2b929435 100755 --- a/web/scripts/stripe/provision-catalog.sh +++ b/web/scripts/stripe/provision-catalog.sh @@ -369,6 +369,21 @@ echo "Resolved Max product." >&2 team_product_id="$(canonical_product "cmux-team-monthly" "cmux Team" "team")" echo "Resolved Team product." >&2 +# Checkout shows the product description. Keep it on the plan limits in +# web/services/vms/machineSpec.ts and entitlements.ts (5 VMs per seat; +# 4 vCPU / 8 GB each, 16 vCPU / 32 GB on Max). Updating a description is +# idempotent, so every run converges on this copy. +ensure_product_description() { + local product_id="$1" + local description="$2" + stripe_post "/products/${product_id}" \ + --data-urlencode "description=${description}" >/dev/null + echo "Set description for ${product_id}." >&2 +} +ensure_product_description "$pro_product_id" "Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM, plus the cmux iOS app." +ensure_product_description "$max_product_id" "Everything in Pro, with up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM." +ensure_product_description "$team_product_id" "Per seat: up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM, the cmux iOS app, and priority support." + # Current catalog (web/services/billing/plans.ts). Stripe Price amounts are # immutable, so each price change mints a new lookup key carrying the amount. ensure_price "$pro_product_id" "cmux-pro-monthly-50" "5000" "month" "cmux Pro Monthly" diff --git a/web/services/billing/plans.ts b/web/services/billing/plans.ts index 90bea1e7923d..e271cb984642 100644 --- a/web/services/billing/plans.ts +++ b/web/services/billing/plans.ts @@ -44,8 +44,8 @@ export const TEAM_PRICING_USD = { } as const satisfies MonthlyOnlyPlanPricing; /** - * Max is a personal plan above Pro: the same allowance, plus the 32 GB and - * 64 GB machine sizes Pro cannot start. It is monthly only, so there is no + * Max is a personal plan above Pro: the same machine count, plus the 16, 24, + * and 32 GB machine sizes (up to 16 vCPU) Pro cannot start. It is monthly only, so there is no * annual price and no interval selector on its card. */ export const MAX_PRICING_USD = { diff --git a/web/services/billing/pro.ts b/web/services/billing/pro.ts index 97f3acf6efed..975713b1daec 100644 --- a/web/services/billing/pro.ts +++ b/web/services/billing/pro.ts @@ -28,7 +28,7 @@ import { export const PRO_PLAN_ID = "pro"; export const GO_PLAN_ID = "go"; -// Max is Pro plus the 32 GB and 64 GB machine sizes. It is a personal +// Max is Pro plus the 16, 24, and 32 GB machine sizes. It is a personal // subscription like Pro: same Stripe customer scope, same metadata mirror // (`cmuxPlan: "max"`), and it satisfies every "is Pro" check. export const MAX_PLAN_ID = "max"; @@ -768,7 +768,7 @@ export function metadataPlanId(raw: unknown): string | null { * Writes `cmuxPlan: "team"` and `cmuxSeats` (the subscription quantity) into * a Stack team's clientReadOnlyMetadata while a Stripe Team subscription is * active; both are removed when it lapses. Seats size the team's Cloud VM - * allowance (50 machines per seat), so a quantity change must land here even + * allowance (5 machines per seat), so a quantity change must land here even * when the plan id is unchanged. `cmuxVmPlan` is operator-owned and left * untouched. */ diff --git a/web/services/vms/README.md b/web/services/vms/README.md index 48a839af1d50..d71a3451b3e9 100644 --- a/web/services/vms/README.md +++ b/web/services/vms/README.md @@ -644,15 +644,15 @@ machine (no env, no rule, still no secret) and must never be set in production. The usage ledger is in Postgres. VM create pricing gates can use Stack Auth payment items, but free-plan create credits are opt-in. Configure `CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID` only when the free plan should consume a prepaid create-credit bucket. When enabled, the create workflow records a one-time local grant row, seeds the configured Stack Auth item credits once per billing team, reserves one create credit only for a newly inserted row, calls the provider, and refunds the credit if provisioning fails before a usable VM exists. -Plan limits are team-based. Stack Auth personal teams should stay enabled for both dev/staging and production projects (`createTeamOnSignUp` / `teams.createPersonalTeamOnSignUp`). New VM rows store `billing_team_id` and `billing_plan_id`; the free plan allows zero active VMs by default and remains at zero regardless of stale free-limit env values while the paid-plan gate is on. A deliberate `CMUX_VM_ALLOW_FREE_PROVISIONING=1` escape hatch re-enables the configured free allowance for local demos or a controlled rollback; Go is a $10/month personal starter plan with one active 2 vCPU / 4 GiB / 16 GB VM and two saved VM records. It includes 40 VM-hours per Stripe billing period. PostgreSQL runtime records commit with VM state changes, and an account-level lock limits Go to two retained VMs in total, with one active. Freestyle lifetime runtime caps enforce the remaining allowance and block restarts until cmux grants a new allowance. A minute job reconciles exhausted machines. Go disk growth beyond 16 GiB is refused; Pro and Max get the allowance sold on /pricing, 50 active machines per billing team, multiplied by the Team subscription's paid seats (`cmuxSeats` in the team's Stack metadata, written from the Stripe quantity) so "50 per paid seat" holds for the whole team (`PAID_MAX_ACTIVE_VMS_DEFAULT`; `maxActiveVms` in entitlements and the list response). New machines use validated Freestyle base snapshots from 4 GiB RAM / 16 GB disk through 64 GiB RAM / 128 GB disk, including the 24 GiB / 96 GB intermediate size. The default is 8 GiB RAM and 32 GB disk. Free, Pro, Team, and Founder's plans may start machines up to 24 GiB; the 32 GiB and 64 GiB sizes require the Max plan (`maxMemoryMbForPlan`, `lockedMemoryOptionsMbForPlan`). The list response names the locked sizes and the upgrade plan, and a create with a locked size returns `402 vm_memory_requires_plan` rather than coercing to a smaller machine. Each machine has its own CPU, memory, and disk. The repository enforces only the machine-count allowance under the billing-team lock; resource metadata supports per-machine fork, snapshot, and resize recovery. Disk growth is independent, grow-only, and capped at 256 GiB in 4 GiB steps. The Freestyle driver applies the default at create (`CMUX_VM_DISK_MB` overrides it), and the resize API reads provider stats before and after the provider confirms the change. Destroyed VMs do not count against a limit; pausing frees the active slot, while Go still counts the retained VM toward its saved limit. Paid plan activation should write a readable plan id such as `pro` into Stack Auth team read-only metadata (`cmuxVmPlan`) or equivalent billing sync metadata. Paid-plan `CMUX_VM_PLAN__MAX_ACTIVE_VMS`, `CMUX_VM_PAID_MAX_ACTIVE_VMS`, and `CMUX_VM_SHARED_CPU_LIMIT_ENABLED` are retired and ignored. The paid allowance lives in code; `CMUX_VM_CREATE_ENABLED=0` remains the provisioning incident control. Paid plans only consume Stack Auth create credits when `CMUX_VM_PLAN__CREATE_CREDIT_ITEM_ID` or the global `CMUX_VM_CREATE_CREDIT_ITEM_ID` is configured. +Plan limits are team-based. Stack Auth personal teams should stay enabled for both dev/staging and production projects (`createTeamOnSignUp` / `teams.createPersonalTeamOnSignUp`). New VM rows store `billing_team_id` and `billing_plan_id`; the free plan allows zero active VMs by default and remains at zero regardless of stale free-limit env values while the paid-plan gate is on. A deliberate `CMUX_VM_ALLOW_FREE_PROVISIONING=1` escape hatch re-enables the configured free allowance for local demos and dev-backend stacks; it is ignored when `VERCEL_ENV=production`, so production never gives a free account a machine; Go is a $10/month personal starter plan with one active 2 vCPU / 4 GiB / 16 GB VM and two saved VM records. It includes 40 VM-hours per Stripe billing period. PostgreSQL runtime records commit with VM state changes, and an account-level lock limits Go to two retained VMs in total, with one active. Freestyle lifetime runtime caps enforce the remaining allowance and block restarts until cmux grants a new allowance. A minute job reconciles exhausted machines. Go disk growth beyond 16 GiB is refused; Pro and Max get the allowance sold on /pricing, 5 active machines per billing team, multiplied by the Team subscription's paid seats (`cmuxSeats` in the team's Stack metadata, written from the Stripe quantity) so "5 per paid seat" holds for the whole team (`PAID_MAX_ACTIVE_VMS_DEFAULT`; `maxActiveVms` in entitlements and the list response). New machines use validated Freestyle base snapshots from 4 GiB RAM / 16 GB disk through 32 GiB RAM / 128 GB disk, including the 24 GiB / 96 GB intermediate size. The 64 GiB snapshot stays in the manifest but no plan sells it. The default is 8 GiB RAM and 32 GB disk. Free, Pro, Team, and Founder's plans may start machines up to 4 vCPU / 8 GiB; the 16, 24, and 32 GiB sizes (up to 16 vCPU) require the Max plan (`maxMemoryMbForPlan`, `lockedMemoryOptionsMbForPlan`). Access verbs (attach, exec, ssh, scp, ports, resume, resize, fork) also refuse an existing machine whose recorded memory or vCPU is above the caller's current plan (`requireMachineFitsPlan`); list, status, rename, and delete keep working. The list response names the locked sizes and the upgrade plan, and a create with a locked size returns `402 vm_memory_requires_plan` rather than coercing to a smaller machine. Each machine has its own CPU, memory, and disk. The repository enforces only the machine-count allowance under the billing-team lock; resource metadata supports per-machine fork, snapshot, and resize recovery. Disk growth is independent, grow-only, and capped at 256 GiB in 4 GiB steps. The Freestyle driver applies the default at create (`CMUX_VM_DISK_MB` overrides it), and the resize API reads provider stats before and after the provider confirms the change. Destroyed VMs do not count against a limit; pausing frees the active slot, while Go still counts the retained VM toward its saved limit. Paid plan activation should write a readable plan id such as `pro` into Stack Auth team read-only metadata (`cmuxVmPlan`) or equivalent billing sync metadata. Paid-plan `CMUX_VM_PLAN__MAX_ACTIVE_VMS`, `CMUX_VM_PAID_MAX_ACTIVE_VMS`, and `CMUX_VM_SHARED_CPU_LIMIT_ENABLED` are retired and ignored. The paid allowance lives in code; `CMUX_VM_CREATE_ENABLED=0` remains the provisioning incident control. Paid plans only consume Stack Auth create credits when `CMUX_VM_PLAN__CREATE_CREDIT_ITEM_ID` or the global `CMUX_VM_CREATE_CREDIT_ITEM_ID` is configured. ### The free limit is the paywall moment -`vmActiveLimitExceededResponse` (routeHelpers) renders every provisioning verb's over-limit error. On unpaid plans the message sells the upgrade — with the default zero allowance it is the subscribe gate ("Cloud VMs require a cmux Pro subscription") with `upgradeRequired: true` and `upgradeUrl` pointing at `/pricing` — so clients can show a real upgrade prompt (checkout flow per `skills/cmux-billing`) instead of a dead error. Paid plans see it at the plan allowance (50 active machines, times paid seats on Team) or at an operator incident brake; then the message is operational "delete one" guidance, not a paywall. +`vmActiveLimitExceededResponse` (routeHelpers) renders every provisioning verb's over-limit error. On unpaid plans the message sells the upgrade — with the default zero allowance it is the subscribe gate ("Cloud VMs require a cmux Pro subscription") with `upgradeRequired: true` and `upgradeUrl` pointing at `/pricing` — so clients can show a real upgrade prompt (checkout flow per `skills/cmux-billing`) instead of a dead error. Paid plans see it at the plan allowance (5 active machines, times paid seats on Team) or at an operator incident brake; then the message is operational "delete one" guidance, not a paywall. ### Pricing is flat -Go includes one active VM with the starter resource shape. Pro and Max include up to 50 active VMs (per paid seat on Team) for a flat subscription price, with independent CPU, memory, and disk for each VM. Go is capped by active VM count until usage metering is added. There is no overage billing; an earlier GB-RAM-awake-seconds metering design was considered and dropped to keep pricing simple. Legacy VM resource claims are repaired by the status-reconcile cron in batches of 50, so create and resize requests do not fan out provider stats reads. Legacy resource metadata does not block new machines or consume another machine's capacity. +Go includes one active VM with the starter resource shape. Pro and Max include up to 5 active VMs (per paid seat on Team) for a flat subscription price, with independent CPU, memory, and disk for each VM. Go is capped by active VM count until usage metering is added. There is no overage billing; an earlier GB-RAM-awake-seconds metering design was considered and dropped to keep pricing simple. Legacy VM resource claims are repaired by the status-reconcile cron in batches of 50, so create and resize requests do not fan out provider stats reads. Legacy resource metadata does not block new machines or consume another machine's capacity. # Signed VM model-plane authorization VM model traffic uses one `x-cmux-authorization: Bearer ` header. The diff --git a/web/services/vms/entitlements.ts b/web/services/vms/entitlements.ts index 4dc26d42034a..75c7a25a5cdf 100644 --- a/web/services/vms/entitlements.ts +++ b/web/services/vms/entitlements.ts @@ -162,22 +162,26 @@ function resolveBillingContext( /** * Machine sizes a person can pick, as memory in MB. The supported ladder is - * 4/16, 8/32, 16/64, 24/96, 32/128, and 64/128 (memory/disk in GB). vCPUs - * follow memory (vcpusForMemoryMb). The server owns this list so clients show + * 4/16, 8/32, 16/64, 24/96, and 32/128 (memory/disk in GB). vCPUs follow + * the image ladder, one per 2 GB. The server owns this list so clients show * valid sizes. BusyBox's 128 MiB image is a bootstrap image, not a coding VM. * Every selected size belongs to one machine, independently of other VMs. */ -export const VM_MEMORY_OPTIONS_MB: readonly number[] = [4096, 8192, 16384, 24576, 32768, 65536]; +export const VM_MEMORY_OPTIONS_MB: readonly number[] = [4096, 8192, 16384, 24576, 32768]; /** - * The largest machine Free, Pro, Team, and Founder's Edition may start. The - * 32 GB and 64 GB rows above it are what Max sells; the plan that unlocks - * them is MEMORY_UPGRADE_PLAN_ID so every surface names the same upgrade. + * The largest machine Free, Pro, Team, and Founder's Edition may start + * (4 vCPU / 8 GB). The 16, 24, and 32 GB rows above it are what Max sells; + * the plan that unlocks them is MEMORY_UPGRADE_PLAN_ID so every surface names + * the same upgrade. */ -export const PLAN_MAX_MEMORY_MB = 24576; +export const PLAN_MAX_MEMORY_MB = 8192; export const GO_PLAN_MAX_MEMORY_MB = 4096; export const GO_PLAN_DEFAULT_MEMORY_MB = 4096; +/** Max machines stop at 16 vCPU / 32 GB. */ export const MAX_PLAN_MAX_MEMORY_MB = Math.max(...VM_MEMORY_OPTIONS_MB); +/** The image ladder pairs one vCPU with every 2 GB of memory. */ +export const VM_PLAN_MEMORY_MB_PER_VCPU = 2048; export const MEMORY_UPGRADE_PLAN_ID = MAX_PLAN_ID; export const GO_MEMORY_UPGRADE_PLAN_ID = PRO_PLAN_ID; @@ -205,9 +209,9 @@ export function maxMemoryMbForPlan( if (normalized === MAX_PLAN_ID) return MAX_PLAN_MAX_MEMORY_MB; if (normalized === GO_PLAN_ID) return GO_PLAN_MAX_MEMORY_MB; if (normalized === "free") { - // The free machine is the product demo: the same computer Pro gets, not a - // cut-down teaser. The paywall is the 7-day access window and the machine - // count, never the machine's usefulness. + // Free accounts get no machine in production (isVmFreeProvisioningAllowed). + // Where an operator opens free provisioning for a demo or dev stack, the + // machine is the same size Pro gets. return Math.min(ceiling, positiveInteger( env.CMUX_VM_FREE_MAX_MEMORY_MB ?? String(PLAN_MAX_MEMORY_MB), "CMUX_VM_FREE_MAX_MEMORY_MB", @@ -233,12 +237,12 @@ export function maxDiskMbForPlan( : fallback; } -/** vCPU ceiling is derived from the plan's memory tier. */ +/** vCPU ceiling follows the image ladder row at the plan's memory tier. */ export function maxVcpusForPlan( planId: string | null | undefined, env: Record = process.env, ): number { - return Math.max(1, Math.floor(maxMemoryMbForPlan(planId, env) / 4096)); + return Math.max(1, Math.floor(maxMemoryMbForPlan(planId, env) / VM_PLAN_MEMORY_MB_PER_VCPU)); } /** @@ -305,7 +309,7 @@ export function defaultMemoryMbForPlan( * Active-machine ceiling for a plan, or null when there is none. Paid plans * get the allowance sold on /pricing (PAID_MAX_ACTIVE_VMS_DEFAULT), counted * per billing team; a Team subscription multiplies it by its paid seats - * (`cmuxSeats`), so "50 per user" holds for the whole team. Free plans stay + * (`cmuxSeats`), so "5 per user" holds for the whole team. Free plans stay * capped (zero unless free provisioning is allowed). */ export function maxActiveVmsForPlan( @@ -390,6 +394,10 @@ export function isVmProGateEnforced( export function isVmFreeProvisioningAllowed( env: Record = process.env, ): boolean { + // Free accounts get no Cloud machine in production, trial or otherwise. + // The escape hatch exists for local, preview, and dev-backend stacks only, + // so no production env value can reopen it. + if (env.VERCEL_ENV === "production") return false; // The new name is authoritative when present. A value must be explicitly // truthy; typos and explicit false values fail closed. if (env.CMUX_VM_ALLOW_FREE_PROVISIONING !== undefined) { diff --git a/web/services/vms/machineSpec.ts b/web/services/vms/machineSpec.ts index f50526502b9e..5fea2eadc39b 100644 --- a/web/services/vms/machineSpec.ts +++ b/web/services/vms/machineSpec.ts @@ -1,9 +1,9 @@ /** - * Paid plans include 50 independent machines per seat. CPU, memory, and disk + * Paid plans include 5 independent machines per seat. CPU, memory, and disk * describe each machine; there is no aggregate resource quota. This module is * dependency-free so provider sizing does not import the billing graph. */ -export const PAID_MAX_ACTIVE_VMS_DEFAULT = 50; +export const PAID_MAX_ACTIVE_VMS_DEFAULT = 5; export const PLAN_MACHINE_MEMORY_MB = 8192; export const VM_MEMORY_MB_PER_VCPU = 4096; diff --git a/web/services/vms/routeHelpers.ts b/web/services/vms/routeHelpers.ts index b72d47eae2e5..ae18067f30d5 100644 --- a/web/services/vms/routeHelpers.ts +++ b/web/services/vms/routeHelpers.ts @@ -548,7 +548,7 @@ export async function invalidVmDisplayNameResponse(request: Request): Promise) { + if (!hasVmResourceReservationMetadata(metadata)) return Effect.void; + const shape = vmResourceReservationFromMetadata(metadata); + const maxMemoryMb = maxMemoryMbForPlan(planId); + if (shape.memoryMb <= maxMemoryMb && shape.vcpus <= maxVcpusForPlan(planId)) return Effect.void; + const memoryMb = Math.max(shape.memoryMb, shape.vcpus * VM_PLAN_MEMORY_MB_PER_VCPU); + return Effect.fail(new VmMemoryPlanError({ planId, memoryMb, maxMemoryMb })); +} + function requestedCreateMemory(input: { memoryMb?: number; imageSize?: { memoryMb: number }; resourceReservation?: { memoryMb: number } }) { return Math.max(input.memoryMb ?? 0, input.imageSize?.memoryMb ?? 0, input.resourceReservation?.memoryMb ?? 0); } @@ -4374,6 +4390,9 @@ function requireAccessibleUserVm(input: ExistingVmAccessInput) { } vm = { ...vm, providerMetadata: { ...vm.providerMetadata, [GO_PAUSE_INTENT_KEY]: null } }; } + if (input.callerPlanId && isPaidVmPlan(input.callerPlanId)) { + yield* requireMachineFitsPlan(input.callerPlanId, vm.providerMetadata); + } if (isVmFreeAccessExpired(input.callerPlanId, vm.createdAt ?? undefined)) { return yield* Effect.fail(new VmFreeAccessExpiredError({ vmId: input.providerVmId, diff --git a/web/tests/app-pricing-page.test.tsx b/web/tests/app-pricing-page.test.tsx index 9be01a8ee771..0feab0981e74 100644 --- a/web/tests/app-pricing-page.test.tsx +++ b/web/tests/app-pricing-page.test.tsx @@ -111,11 +111,11 @@ describe("app pricing page", () => { expect(html).toContain("For individuals"); expect(html).toContain("For teams and businesses"); expect(html).toContain("Get Max"); - expect(html).toContain("Up to 50 Cloud VMs sharing 64 GB RAM and 16 vCPUs"); + expect(html).toContain("Up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM"); expect(html).toContain("Largest Cloud VM"); expect(html).toContain("$60/user/mo"); expect(html).toContain( - "Up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs", + "Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM", ); expect(html).toContain('

    Includes:

    '); expect(html).not.toContain('style="min-height:4rem"'); diff --git a/web/tests/billing-max-plan.test.ts b/web/tests/billing-max-plan.test.ts index b8dfab2c2e0e..fbabd6e17ac4 100644 --- a/web/tests/billing-max-plan.test.ts +++ b/web/tests/billing-max-plan.test.ts @@ -31,14 +31,14 @@ describe("Max as a personal plan", () => { for (const options of [{}, { requestedBillingTeamId: "team-1" }]) { const result = resolveVmEntitlements(user, {}, options); expect(result.planId).toBe("max"); - expect(result.maxActiveVms).toBe(150); + expect(result.maxActiveVms).toBe(15); expect(result.billingTeamId).toBe("team-1"); } }); test("legacy memory overrides cannot sell Max sizes to Pro", () => { - expect(maxMemoryMbForPlan("pro", { CMUX_VM_PAID_MAX_MEMORY_MB: "65536" })).toBe(24576); - expect(maxMemoryMbForPlan("pro", { CMUX_VM_PLAN_PRO_MAX_MEMORY_MB: "65536" })).toBe(24576); + expect(maxMemoryMbForPlan("pro", { CMUX_VM_PAID_MAX_MEMORY_MB: "65536" })).toBe(8192); + expect(maxMemoryMbForPlan("pro", { CMUX_VM_PLAN_PRO_MAX_MEMORY_MB: "65536" })).toBe(8192); }); test("max is paid, personal, and outranks pro", () => { expect(isPaidPlanId("max")).toBe(true); diff --git a/web/tests/cloud-vm-env-audit.test.ts b/web/tests/cloud-vm-env-audit.test.ts index e9d1d30073ec..9fbff7494118 100644 --- a/web/tests/cloud-vm-env-audit.test.ts +++ b/web/tests/cloud-vm-env-audit.test.ts @@ -460,6 +460,8 @@ describe("free-provisioning override audit", () => { if (allow !== undefined) env.CMUX_VM_ALLOW_FREE_PROVISIONING = allow; if (legacy !== undefined) env.CMUX_VM_REQUIRE_PRO = legacy; expect(isFreeProvisioningAllowed(env)).toBe(isVmFreeProvisioningAllowed(env)); + const production = { ...env, VERCEL_ENV: "production" }; + expect(isFreeProvisioningAllowed(production)).toBe(isVmFreeProvisioningAllowed(production)); } } }); diff --git a/web/tests/pricing-page.test.tsx b/web/tests/pricing-page.test.tsx index 99910cb51a05..05b93d6a93af 100644 --- a/web/tests/pricing-page.test.tsx +++ b/web/tests/pricing-page.test.tsx @@ -130,7 +130,7 @@ describe("localized pricing page", () => { try { const first = await readInitialMain(reader); expect(first.includes("$50") && first.includes("$200")).toBe(true); - expect(first.includes("Up to 50 Cloud VMs sharing 64 GB RAM and 16 vCPUs")).toBe(true); + expect(first.includes("Up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM")).toBe(true); expect(first.includes("animate-pulse")).toBe(false); expect(first.includes("Current plan")).toBe(false); } finally { @@ -200,9 +200,9 @@ describe("localized pricing page", () => { label: "Concurrent Cloud VMs", go: "1", free: "false", - pro: "50", - max: "50", - team: "50 per user", + pro: "5", + max: "5", + team: "5 per user", enterprise: "Custom", }); expect(enMessages.dashboard.billing.free.upsellTitle).toBe( @@ -302,7 +302,7 @@ describe("localized pricing page", () => { expect(html).toContain("$200"); expect(html).toContain("$200 /mo"); expect(html).not.toContain("$200/mo, billed yearly"); - expect(html).toContain("Up to 50 Cloud VMs sharing 64 GB RAM and 16 vCPUs"); + expect(html).toContain("Up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM"); expect(html).toContain("Get Go"); expect(html).toContain("2 vCPU, 4 GiB RAM, and 16 GiB disk"); expect(html).toContain("For individuals"); @@ -417,7 +417,7 @@ describe("localized pricing page", () => { expect(html).toContain("$50"); expect(html).toContain("$60"); expect(html).toContain( - "Up to 50 Cloud VMs, with 24 GB RAM and 6 vCPUs shared across all VMs", + "Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM", ); expect(html).toContain("Unlimited workspaces"); expect(html).not.toContain("Unlimited active Cloud VMs"); diff --git a/web/tests/pro-pricing.test.ts b/web/tests/pro-pricing.test.ts index 0d17333f54da..b591aca7eb8a 100644 --- a/web/tests/pro-pricing.test.ts +++ b/web/tests/pro-pricing.test.ts @@ -86,8 +86,8 @@ describe("pricing plans", () => { describe("VM defaults and pricing copy", () => { const memoryGb = PLAN_MACHINE_MEMORY_MB / 1024; const startingDiskGb = VM_DISK_MB_DEFAULT / 1024; - test("VM creation defaults are separate from advertised shared plan resources", () => { - expect(PAID_MAX_ACTIVE_VMS_DEFAULT).toBe(50); + test("VM creation defaults are separate from advertised plan limits", () => { + expect(PAID_MAX_ACTIVE_VMS_DEFAULT).toBe(5); expect(memoryGb).toBe(8); expect(startingDiskGb).toBe(32); }); @@ -122,31 +122,30 @@ describe("VM defaults and pricing copy", () => { })).toEqual({ vcpus: 1, memoryMb: 4096, diskMb: 65536 }); }); - // These are the advertised plan limits, not the default shape of one VM. - // Keep cards, comparison rows, FAQs, and native strings on the same policy. - for (const [locale, messages, label, shared] of [ - ["en", enMessages, "Resources shared across all Cloud VMs", "shared across all"], - ["ja", jaMessages, "すべての Cloud VM で共有するリソース", "共有"], + // These are the advertised plan limits: a machine count plus the largest + // shape of each machine. There is no shared pool. Keep cards, comparison + // rows, FAQs, and native strings on the same policy. + for (const [locale, messages, label] of [ + ["en", enMessages, "Resources per Cloud VM"], + ["ja", jaMessages, "Cloud VM あたりのリソース"], ] as const) { - test(`${locale} pricing advertises 24 GB RAM and 6 vCPUs shared across up to 50 VMs`, () => { + test(`${locale} pricing advertises up to 5 VMs with up to 4 vCPUs and 8 GB RAM per VM`, () => { const features = messages.pricing.pro.features.join("\n"); - expect(features).toContain("50 "); + expect(features).toMatch(/(?:^|\D)5(?:\D|$)/); const row = messages.pricing.compare.rows.find(row => row.label === label); expect(row).toBeDefined(); const faq = messages.pricing.faq.items.map(item => item.a).join("\n"); for (const copy of [features, row!.pro, row!.team, faq]) { - expect(copy).toContain("24 GB RAM"); - expect(copy).toContain("6 vCPU"); - expect(copy).toContain(shared); + expect(copy).toContain("8 GB RAM"); + expect(copy).toContain("4 vCPU"); } - // Only Max sells the 32 GB and 64 GB sizes; Free, Pro, and Team copy - // must not mention them. The Max card, the "Largest Cloud VM" row, and - // the Max FAQ are the only places those sizes appear. + // Only Max sells the larger sizes; Free, Pro, and Team copy must not + // mention them or the retired shared pool. const { max: _max, compare, faq: _faq, ...otherPlans } = messages.pricing; const nonMaxRows = compare.rows.map(({ max: _rowMax, ...rest }) => rest); const nonMaxCopy = JSON.stringify([otherPlans, nonMaxRows]); - expect(nonMaxCopy).not.toMatch(/(?:8|32|64|256) GB|5 vCPU|each with its own resources|Each machine has its own|各マシンに専用のリソース|各マシンには独立した/); - expect(JSON.stringify(messages.pricing)).not.toMatch(/(?:8|256) GB|5 vCPU|each with its own resources|Each machine has its own|各マシンに専用のリソース|各マシンには独立した/); + expect(nonMaxCopy).not.toMatch(/(?:16|24|32|64) GB|16 vCPU|shared across|を共有/); + expect(JSON.stringify(messages.pricing)).not.toMatch(/(?:24|64) GB|6 vCPUs shared|50 Cloud VMs/); }); } @@ -154,21 +153,23 @@ describe("VM defaults and pricing copy", () => { ["en", enMessages, "Largest Cloud VM", "What does Max add?"], ["ja", jaMessages, "最大の Cloud VM", "Max では何が追加されますか?"], ] as const) { - test(`${locale} Max copy sells the 32 GB and 64 GB machines Pro cannot start`, () => { + test(`${locale} Max copy sells up to 5 VMs with up to 16 vCPUs and 32 GB RAM per VM`, () => { const features = messages.pricing.max.features.join("\n"); - expect(features).toContain("64 GB"); + expect(features).toContain("32 GB"); expect(features).toContain("16 vCPU"); - expect(features).toContain("50"); - expect(features).toContain(locale === "en" ? "sharing" : "共有"); - const sharedRow = messages.pricing.compare.rows.find(row => row.max.includes("16 vCPU")); - expect(sharedRow!.max).toContain(locale === "en" ? "shared across all VMs" : "共有"); + expect(features).toMatch(/(?:^|\D)5(?:\D|$)/); + const perVmRow = messages.pricing.compare.rows.find(row => row.max.includes("16 vCPU")); + expect(perVmRow!.max).toContain("32 GB RAM"); expect(messages.dashboard.billing.max.upsell).toContain("16 vCPU"); + expect(messages.dashboard.billing.max.upsell).toContain("32 GB"); const row = messages.pricing.compare.rows.find(row => row.label === largestLabel); expect(row).toBeDefined(); - expect(row!.max).toBe("64 GB RAM"); - for (const plan of ["free", "pro", "team"] as const) { - expect(row![plan]).toBe("24 GB RAM"); + expect(row!.max).toBe("32 GB RAM"); + for (const plan of ["pro", "team"] as const) { + expect(row![plan]).toBe("8 GB RAM"); } + // Free accounts include no Cloud VM at all. + expect(row!.free).toBe("false"); for (const row of messages.pricing.compare.rows) { expect(typeof row.max).toBe("string"); } @@ -176,47 +177,61 @@ describe("VM defaults and pricing copy", () => { expect(faq).toBeDefined(); expect(faq!.a).toContain("$200"); expect(faq!.a).toContain("16 vCPU"); - expect(faq!.a).toContain(locale === "en" ? "share a total" : "共有"); expect(faq!.a).toContain("32 GB"); - expect(faq!.a).toContain("64 GB"); - expect(faq!.a).toContain("24 GB"); + expect(faq!.a).toContain("8 GB"); + expect(faq!.a).not.toContain("64 GB"); const billingFaq = messages.pricing.faq.items.map(item => item.a).join("\n"); expect(billingFaq).toContain("$200"); }); } - test("fallback locales inherit the shared resource wording", async () => { + test("fallback locales inherit the per-VM resource wording", async () => { for (const locale of locales) { if (locale === "en" || locale === "ja") continue; const messages = await loadMessages(locale) as unknown as typeof enMessages; - expect(messages.pricing.pro.features.join("\n")).toContain("24 GB RAM and 6 vCPUs shared across all VMs"); - expect(messages.pricing.max.features[0]).toBe("Up to 50 Cloud VMs sharing 64 GB RAM and 16 vCPUs"); - expect(messages.pricing.compare.rows.find(row => row.label === "Resources shared across all Cloud VMs")).toBeDefined(); + expect(messages.pricing.pro.features.join("\n")).toContain("Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM"); + expect(messages.pricing.max.features[0]).toBe("Up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM"); + expect(messages.pricing.compare.rows.find(row => row.label === "Resources per Cloud VM")).toBeDefined(); } }); - test("native pricing translations preserve the shared plan quantities", () => { + test("no native pricing string in any locale sells the retired limits or a free trial", () => { + const catalog = JSON.parse(readFileSync(new URL("../../Resources/Localizable.xcstrings", import.meta.url), "utf8")); + const stale: string[] = []; + for (const [key, entry] of Object.entries(catalog.strings as Record }>)) { + if (!key.startsWith("pricing.native.") && !key.startsWith("settings.account.pro.")) continue; + for (const [locale, localization] of Object.entries(entry.localizations ?? {})) { + const value = localization.stringUnit?.value ?? ""; + // Remove only the $50 Pro price tokens, so a stale "50 Cloud VMs" + // elsewhere in the same value still fails. + const withoutPrice = value.replace(/^\$?50$|\$50\/?|(? { const catalog = JSON.parse(readFileSync(new URL("../../Resources/Localizable.xcstrings", import.meta.url), "utf8")); - const sharedTerms: Record = { - en: "shared across all", - de: "gemeinsam", - fr: "partagés", - ar: "مشتركة", - es: "compartid", - "zh-Hans": "共享", - "zh-Hant": "共用", - ko: "공유", - ja: "共有", - }; for (const key of ["pricing.native.pro.feature.hours", "pricing.native.team.feature.compute", "pricing.native.sizes.body"]) { const localizations = catalog.strings[key].localizations as Record; - for (const [locale, { stringUnit: { value } }] of Object.entries(localizations)) { + for (const [, { stringUnit: { value } }] of Object.entries(localizations)) { // Units and word order are localized; plan quantities stay the same. - for (const quantity of [24, 6, 50]) { + for (const quantity of [5, 4, 8]) { + expect(value).toMatch(new RegExp(`(?:^|\\D)${quantity}(?:\\D|$)`)); + } + expect(value).not.toMatch(/(?:^|\D)(?:6|24|50|64)(?:\D|$)/); + } + } + for (const key of ["pricing.native.max.feature.sizes", "pricing.native.sizes.max"]) { + const localizations = catalog.strings[key].localizations as Record; + for (const [, { stringUnit: { value } }] of Object.entries(localizations)) { + for (const quantity of [5, 16, 32]) { expect(value).toMatch(new RegExp(`(?:^|\\D)${quantity}(?:\\D|$)`)); } - expect(value).toContain(sharedTerms[locale] ?? sharedTerms.en); - expect(value).not.toMatch(/(?:^|\D)(?:8|32|64|256)(?:\D|$)|each with its own resources/); + expect(value).not.toMatch(/(?:^|\D)(?:50|64)(?:\D|$)/); } } }); diff --git a/web/tests/stripe-provision-catalog.test.ts b/web/tests/stripe-provision-catalog.test.ts index 148d0b1c8b80..12cfa8736c3d 100644 --- a/web/tests/stripe-provision-catalog.test.ts +++ b/web/tests/stripe-provision-catalog.test.ts @@ -103,6 +103,18 @@ describe("Stripe catalog provisioning", () => { ).toBe(true); }); + test("sets the checkout description of each plan to the Cloud VM limits", async () => { + const result = await runProvision("test", "valid"); + + expect(result.exitCode).toBe(0); + const description = (productId: string) => result.calls + .find((call) => call.args.includes(`https://api.stripe.com/v1/products/${productId}`) && call.args.includes("POST")) + ?.args.find((argument) => argument.startsWith("description=")); + expect(description("prod_pro")).toBe("description=Up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM, plus the cmux iOS app."); + expect(description("prod_max")).toBe("description=Everything in Pro, with up to 5 Cloud VMs, up to 16 vCPUs and 32 GB RAM per VM."); + expect(description("prod_team")).toBe("description=Per seat: up to 5 Cloud VMs, up to 4 vCPUs and 8 GB RAM per VM, the cmux iOS app, and priority support."); + }); + test("provisions the monthly-only Max price and the Pro/Max plan switch portal", async () => { const result = await runProvision("test", "valid"); @@ -512,6 +524,8 @@ if (url.endsWith("/prices") && !isPost) { ? "prod_new_go" : "prod_new_team", }); +} else if (url.includes("/products/prod_") && isPost) { + respond({ id: url.split("/").at(-1) }); } else if (url.endsWith("/billing_portal/configurations") && !isPost) { if (args.includes("is_default=true")) { respond({ diff --git a/web/tests/vm-billing-limit-paywall.test.ts b/web/tests/vm-billing-limit-paywall.test.ts index 8e2c3275d2f3..ff4d94053460 100644 --- a/web/tests/vm-billing-limit-paywall.test.ts +++ b/web/tests/vm-billing-limit-paywall.test.ts @@ -7,6 +7,7 @@ import { maxActiveVmsForPlan, lockedMemoryOptionsMbForPlan, maxMemoryMbForPlan, + maxVcpusForPlan, memoryOptionsMbForPlan, vcpusForMemoryMb, vmDiskMb, @@ -23,33 +24,34 @@ describe("free plan VM allowance", () => { expect(maxActiveVmsForPlan("free", {})).toBe(0); }); - test("paid plans get the advertised 50-machine allowance", () => { - expect(maxActiveVmsForPlan("pro", {})).toBe(50); - expect(maxActiveVmsForPlan("team", {})).toBe(50); - expect(maxActiveVmsForPlan("founders", {})).toBe(50); + test("paid plans get the advertised 5-machine allowance", () => { + expect(maxActiveVmsForPlan("pro", {})).toBe(5); + expect(maxActiveVmsForPlan("team", {})).toBe(5); + expect(maxActiveVmsForPlan("founders", {})).toBe(5); + expect(maxActiveVmsForPlan("max", {})).toBe(5); }); - test("a Team subscription gets 50 machines per paid seat", () => { - expect(maxActiveVmsForPlan("team", {}, { seats: 4 })).toBe(200); - expect(maxActiveVmsForPlan("team", {}, { seats: 1 })).toBe(50); - expect(maxActiveVmsForPlan("team", {}, { seats: null })).toBe(50); - expect(maxActiveVmsForPlan("team", {}, { seats: 0 })).toBe(50); + test("a Team subscription gets 5 machines per paid seat", () => { + expect(maxActiveVmsForPlan("team", {}, { seats: 4 })).toBe(20); + expect(maxActiveVmsForPlan("team", {}, { seats: 1 })).toBe(5); + expect(maxActiveVmsForPlan("team", {}, { seats: null })).toBe(5); + expect(maxActiveVmsForPlan("team", {}, { seats: 0 })).toBe(5); // Seats only mean something on the Team plan. - expect(maxActiveVmsForPlan("pro", {}, { seats: 4 })).toBe(50); + expect(maxActiveVmsForPlan("pro", {}, { seats: 4 })).toBe(5); expect(maxActiveVmsForPlan("free", {}, { seats: 4 })).toBe(0); // Retired overrides cannot erase paid seats. - expect(maxActiveVmsForPlan("team", { CMUX_VM_PLAN_TEAM_MAX_ACTIVE_VMS: "2" }, { seats: 3 })).toBe(150); - expect(maxActiveVmsForPlan("team", { CMUX_VM_PAID_MAX_ACTIVE_VMS: "5" }, { seats: 4 })).toBe(200); + expect(maxActiveVmsForPlan("team", { CMUX_VM_PLAN_TEAM_MAX_ACTIVE_VMS: "2" }, { seats: 3 })).toBe(15); + expect(maxActiveVmsForPlan("team", { CMUX_VM_PAID_MAX_ACTIVE_VMS: "1" }, { seats: 4 })).toBe(20); }); - test("retired paid overrides cannot lower the product allowance", () => { - expect(maxActiveVmsForPlan("pro", { CMUX_VM_PAID_MAX_ACTIVE_VMS: "5" })).toBe(50); + test("retired paid overrides cannot change the product allowance", () => { + expect(maxActiveVmsForPlan("pro", { CMUX_VM_PAID_MAX_ACTIVE_VMS: "1" })).toBe(5); expect(maxActiveVmsForPlan("pro", { - CMUX_VM_PAID_MAX_ACTIVE_VMS: "5", + CMUX_VM_PAID_MAX_ACTIVE_VMS: "1", CMUX_VM_PLAN_PRO_MAX_ACTIVE_VMS: "25", - })).toBe(50); - expect(maxActiveVmsForPlan("team", { CMUX_VM_PLAN_PRO_MAX_ACTIVE_VMS: "25" })).toBe(50); - expect(maxActiveVmsForPlan("pro", { CMUX_VM_PAID_MAX_ACTIVE_VMS: "0" })).toBe(50); + })).toBe(5); + expect(maxActiveVmsForPlan("team", { CMUX_VM_PLAN_PRO_MAX_ACTIVE_VMS: "25" })).toBe(5); + expect(maxActiveVmsForPlan("pro", { CMUX_VM_PAID_MAX_ACTIVE_VMS: "0" })).toBe(5); }); test("free allowance is env-overridable only with the explicit escape hatch", () => { @@ -75,42 +77,45 @@ describe("free plan VM allowance", () => { }); describe("Cloud VM memory allowance", () => { - test("plans default to 8 GB; Pro-tier plans stop at 24 GB and only Max reaches 64 GB", () => { + test("plans default to 8 GB; Pro-tier plans stop at 4 vCPU / 8 GB and Max at 16 vCPU / 32 GB", () => { expect(PLAN_MACHINE_MEMORY_MB).toBe(8192); - expect(VM_MEMORY_OPTIONS_MB).toEqual([4096, 8192, 16384, 24576, 32768, 65536]); + expect(VM_MEMORY_OPTIONS_MB).toEqual([4096, 8192, 16384, 24576, 32768]); for (const planId of ["free", "pro", "team", "founders"]) { expect(defaultMemoryMbForPlan(planId, {})).toBe(8192); - expect(maxMemoryMbForPlan(planId, {})).toBe(24576); + expect(maxMemoryMbForPlan(planId, {})).toBe(8192); + expect(maxVcpusForPlan(planId, {})).toBe(4); expect(lockedMemoryOptionsMbForPlan(planId, {})).toEqual({ - memoryOptionsMb: [32768, 65536], + memoryOptionsMb: [16384, 24576, 32768], upgradePlanId: "max", }); } expect(defaultMemoryMbForPlan("max", {})).toBe(8192); - expect(maxMemoryMbForPlan("max", {})).toBe(65536); - expect(memoryOptionsMbForPlan("max", {})).toEqual([4096, 8192, 16384, 24576, 32768, 65536]); + expect(maxMemoryMbForPlan("max", {})).toBe(32768); + expect(maxVcpusForPlan("max", {})).toBe(16); + expect(memoryOptionsMbForPlan("max", {})).toEqual([4096, 8192, 16384, 24576, 32768]); expect(lockedMemoryOptionsMbForPlan("max", {})).toEqual({ memoryOptionsMb: [], upgradePlanId: null }); }); test("Go is capped at one 2 vCPU, 4 GB, 16 GB VM", () => { expect(maxActiveVmsForPlan("go", {})).toBe(1); expect(maxMemoryMbForPlan("go", {})).toBe(4096); + expect(maxVcpusForPlan("go", {})).toBe(2); expect(memoryOptionsMbForPlan("go", {})).toEqual([4096]); expect(lockedMemoryOptionsMbForPlan("go", {})).toEqual({ - memoryOptionsMb: [8192, 16384, 24576, 32768, 65536], + memoryOptionsMb: [8192, 16384, 24576, 32768], upgradePlanId: "pro", }); }); test("an operator ceiling on Max leaves nothing to upgrade to", () => { // A lower Max ceiling can still advertise Max as the next tier for Pro. - const env = { CMUX_VM_PLAN_MAX_MAX_MEMORY_MB: "32768" }; + const env = { CMUX_VM_PLAN_MAX_MAX_MEMORY_MB: "16384" }; expect(lockedMemoryOptionsMbForPlan("pro", env)).toEqual({ - memoryOptionsMb: [32768, 65536], + memoryOptionsMb: [16384, 24576, 32768], upgradePlanId: "max", }); expect(lockedMemoryOptionsMbForPlan("max", env)).toEqual({ - memoryOptionsMb: [65536], + memoryOptionsMb: [24576, 32768], upgradePlanId: null, }); }); @@ -129,23 +134,23 @@ describe("Cloud VM memory allowance", () => { }); test("accepted sizes follow the plan ceiling and always include the configured default", () => { - expect(memoryOptionsMbForPlan("pro", {})).toEqual([4096, 8192, 16384, 24576]); - // An operator default below the catalog stays creatable, so an omitted - // size never 400s after an override. - expect(memoryOptionsMbForPlan("free", { CMUX_VM_FREE_DEFAULT_MEMORY_MB: "16384" })).toEqual([4096, 8192, 16384, 24576]); - // A raised paid ceiling reopens the ladder for Pro without touching Max. - expect(memoryOptionsMbForPlan("pro", { CMUX_VM_PAID_MAX_MEMORY_MB: "65536" })).toEqual([4096, 8192, 16384, 24576]); + expect(memoryOptionsMbForPlan("pro", {})).toEqual([4096, 8192]); + // A default above the ceiling is clamped, so an omitted size never 400s. + expect(memoryOptionsMbForPlan("free", { CMUX_VM_FREE_DEFAULT_MEMORY_MB: "16384" })).toEqual([4096, 8192]); + // A raised paid ceiling cannot sell Max sizes to Pro. + expect(memoryOptionsMbForPlan("pro", { CMUX_VM_PAID_MAX_MEMORY_MB: "65536" })).toEqual([4096, 8192]); // A lower ceiling trims the catalog and keeps the (clamped) default. - expect(memoryOptionsMbForPlan("pro", { CMUX_VM_PLAN_PRO_MAX_MEMORY_MB: "16384" })).toEqual([4096, 8192, 16384]); + expect(memoryOptionsMbForPlan("pro", { CMUX_VM_PLAN_PRO_MAX_MEMORY_MB: "4096" })).toEqual([4096]); }); test("memory defaults and caps are independently env-overridable", () => { const env = { - CMUX_VM_PLAN_PRO_DEFAULT_MEMORY_MB: "16384", - CMUX_VM_PLAN_PRO_MAX_MEMORY_MB: "24576", + CMUX_VM_PLAN_PRO_DEFAULT_MEMORY_MB: "4096", + CMUX_VM_PLAN_PRO_MAX_MEMORY_MB: "4096", }; - expect(defaultMemoryMbForPlan("pro", env)).toBe(16384); - expect(maxMemoryMbForPlan("pro", env)).toBe(24576); + expect(defaultMemoryMbForPlan("pro", env)).toBe(4096); + expect(maxMemoryMbForPlan("pro", env)).toBe(4096); + expect(defaultMemoryMbForPlan("max", { CMUX_VM_PLAN_MAX_DEFAULT_MEMORY_MB: "16384" })).toBe(16384); }); }); diff --git a/web/tests/vm-independent-limits.test.ts b/web/tests/vm-independent-limits.test.ts index f584a1da6af9..f2ba951a180a 100644 --- a/web/tests/vm-independent-limits.test.ts +++ b/web/tests/vm-independent-limits.test.ts @@ -23,10 +23,10 @@ const runRepo = (operation: (repo: VmRepositoryShape) => Effect.Effect { test("retired deployment overrides cannot reduce the advertised paid allowance", () => { - const env = { CMUX_VM_PAID_MAX_ACTIVE_VMS: "5", CMUX_VM_PLAN_PRO_MAX_ACTIVE_VMS: "5" }; - expect(maxActiveVmsForPlan("pro", env)).toBe(50); - expect(maxActiveVmsForPlan("founders", env)).toBe(50); - expect(maxActiveVmsForPlan("team", env, { seats: 4 })).toBe(200); + const env = { CMUX_VM_PAID_MAX_ACTIVE_VMS: "1", CMUX_VM_PLAN_PRO_MAX_ACTIVE_VMS: "1" }; + expect(maxActiveVmsForPlan("pro", env)).toBe(5); + expect(maxActiveVmsForPlan("founders", env)).toBe(5); + expect(maxActiveVmsForPlan("team", env, { seats: 4 })).toBe(20); }); dbTest("admits the third machine, all 50 sizes, and exactly one concurrent final slot", async () => { diff --git a/web/tests/vm-max-memory-workflow.test.ts b/web/tests/vm-max-memory-workflow.test.ts index fb46d4bd7ec1..3008bfd09208 100644 --- a/web/tests/vm-max-memory-workflow.test.ts +++ b/web/tests/vm-max-memory-workflow.test.ts @@ -4,7 +4,7 @@ import * as Layer from "effect/Layer"; import { VmRepository, type VmRepositoryShape } from "../services/vms/repository"; import { VmProviderGateway, type VmProviderGatewayShape } from "../services/vms/providerGateway"; import { VmBillingGateway, noOpVmBillingGateway } from "../services/vms/billingGateway"; -import { createVm, forkVm, restoreVm } from "../services/vms/workflows"; +import { createVm, forkVm, restoreVm, resumeVm } from "../services/vms/workflows"; import { vmWorkflowErrorCause, VmProviderOperationError } from "../services/vms/errors"; test("create, fork, and restore reject a 64 GB machine on Pro before provisioning", async () => { @@ -18,7 +18,7 @@ test("create, fork, and restore reject a 64 GB machine on Pro before provisionin } as unknown as VmRepositoryShape; const providers = { getStats: () => Effect.succeed({ memoryTotalMb: 65536, cpus: 16, diskTotalMb: 131072 }) } as unknown as VmProviderGatewayShape; const layer = Layer.mergeAll(Layer.succeed(VmRepository, repo), Layer.succeed(VmProviderGateway, providers), Layer.succeed(VmBillingGateway, noOpVmBillingGateway())); - const caller = { userId: "user", billingCustomerType: "team" as const, billingTeamId: "team", billingPlanId: "pro", maxActiveVms: 50 }; + const caller = { userId: "user", billingCustomerType: "team" as const, billingTeamId: "team", billingPlanId: "pro", maxActiveVms: 5 }; for (const program of [ createVm({ ...caller, provider: "freestyle", image: "snapshot", memoryMb: 65536 }).pipe(Effect.asVoid), forkVm({ ...caller, teamIds: ["team"], providerVmId: "vm" }).pipe(Effect.asVoid), @@ -62,7 +62,7 @@ test("a gateway fork method cannot override the provider capability", async () = } as unknown as VmProviderGatewayShape; const layer = Layer.mergeAll(Layer.succeed(VmRepository, repo), Layer.succeed(VmProviderGateway, provider), Layer.succeed(VmBillingGateway, noOpVmBillingGateway())); await Effect.runPromiseExit(forkVm({ userId: "u", billingCustomerType: "user", billingTeamId: "u", billingPlanId: "pro", - maxActiveVms: 50, providerVmId: "vm" }).pipe(Effect.provide(layer))); + maxActiveVms: 5, providerVmId: "vm" }).pipe(Effect.provide(layer))); expect(snapshots).toBe(1); expect(forks).toBe(0); expect(creates).toBe(0); @@ -77,7 +77,7 @@ test("Pro cannot bypass the memory gate with unknown snapshot or fork dimensions } as unknown as VmRepositoryShape; const layer = Layer.mergeAll(Layer.succeed(VmRepository, repo), Layer.succeed(VmProviderGateway, {} as VmProviderGatewayShape), Layer.succeed(VmBillingGateway, noOpVmBillingGateway())); - const caller = { userId: "u", billingCustomerType: "user" as const, billingTeamId: "u", billingPlanId: "pro", maxActiveVms: 50 }; + const caller = { userId: "u", billingCustomerType: "user" as const, billingTeamId: "u", billingPlanId: "pro", maxActiveVms: 5 }; for (const program of [ forkVm({ ...caller, providerVmId: "vm" }).pipe(Effect.asVoid), restoreVm({ ...caller, provider: "freestyle", snapshotId: "snapshot" }).pipe(Effect.asVoid), @@ -92,3 +92,29 @@ test("Pro cannot bypass the memory gate with unknown snapshot or fork dimensions } } }); + +test("access verbs refuse a machine larger than the caller's current plan", async () => { + const run = async (callerPlanId: string, reservation: { memoryMb: number; vcpus: number; diskMb: number }) => { + const repo = { + findUserVm: () => Effect.succeed({ id: "row", userId: "u", billingTeamId: "u", ownerTeamId: "u", coderouterPoolId: null, status: "running", + provider: "freestyle", providerVmId: "vm", providerMetadata: { cmuxResourceReservation: reservation } }), + } as unknown as VmRepositoryShape; + // No resume/getStatus adapter: a machine that passes the plan gate + // answers "running" without any provider call. + const layer = Layer.mergeAll(Layer.succeed(VmRepository, repo), + Layer.succeed(VmProviderGateway, {} as VmProviderGatewayShape), Layer.succeed(VmBillingGateway, noOpVmBillingGateway())); + return Effect.runPromise(resumeVm({ userId: "u", billingTeamId: "u", providerVmId: "vm", maxActiveVms: 5, callerPlanId }) + .pipe(Effect.provide(layer))) + .then(() => null, (error) => vmWorkflowErrorCause(error)?._tag ?? "unknown"); + }; + // A 24 GB machine made under the old Pro ceiling is locked on Pro. + expect(await run("pro", { memoryMb: 24576, vcpus: 6, diskMb: 98304 })).toBe("VmMemoryPlanError"); + // CPU above the plan also locks an 8 GB machine. + expect(await run("pro", { memoryMb: 8192, vcpus: 6, diskMb: 32768 })).toBe("VmMemoryPlanError"); + expect(await run("team", { memoryMb: 16384, vcpus: 4, diskMb: 65536 })).toBe("VmMemoryPlanError"); + // A 64 GB machine is above every plan, Max included. + expect(await run("max", { memoryMb: 65536, vcpus: 16, diskMb: 131072 })).toBe("VmMemoryPlanError"); + // Machines inside the plan pass. + expect(await run("pro", { memoryMb: 8192, vcpus: 4, diskMb: 32768 })).toBeNull(); + expect(await run("max", { memoryMb: 32768, vcpus: 16, diskMb: 131072 })).toBeNull(); +}); diff --git a/web/tests/vm-pro-gate.test.ts b/web/tests/vm-pro-gate.test.ts index 4fc4a3c28a36..ced863758713 100644 --- a/web/tests/vm-pro-gate.test.ts +++ b/web/tests/vm-pro-gate.test.ts @@ -6,6 +6,7 @@ import { isVmFreeProvisioningAllowed, isVmProGateBlocked, isVmProGateEnforced, + maxActiveVmsForPlan, resolveVmEntitlements, } from "../services/vms/entitlements"; import { PRO_PLAN_ID } from "../services/billing/pro"; @@ -72,6 +73,21 @@ describe("Cloud VM Pro gate", () => { })).toBe(false); }); + test("production never provisions free machines, whatever the env says", () => { + for (const env of [ + { CMUX_VM_ALLOW_FREE_PROVISIONING: "1" }, + { CMUX_VM_REQUIRE_PRO: "0" }, + { CMUX_VM_ALLOW_FREE_PROVISIONING: "1", CMUX_VM_FREE_MAX_ACTIVE_VMS: "3", CMUX_VM_DEFAULT_PLAN: "pro" }, + ]) { + const production = { ...env, VERCEL_ENV: "production" }; + expect(isVmFreeProvisioningAllowed(production)).toBe(false); + expect(isVmProGateBlocked(ent("free"), production)).toBe(true); + expect(maxActiveVmsForPlan("free", production)).toBe(0); + // The same env still opens a preview or dev-backend stack. + expect(isVmFreeProvisioningAllowed({ ...env, VERCEL_ENV: "preview" })).toBe(true); + } + }); + test("the explicit free-provisioning switch never blocks any plan", () => { const env = { CMUX_VM_ALLOW_FREE_PROVISIONING: "1" }; expect(isVmProGateBlocked(ent("free"), env)).toBe(false); diff --git a/web/tests/vm-resize-route.test.ts b/web/tests/vm-resize-route.test.ts index 9aa1b6f3fed3..976bb6c396e8 100644 --- a/web/tests/vm-resize-route.test.ts +++ b/web/tests/vm-resize-route.test.ts @@ -9,7 +9,7 @@ let authedUser: AuthedUser | null = null; const programs: Array> = []; let routeResult: { ok: true; value: Record } | { ok: false; response: Response } = { ok: true, - value: { state: "awake", diskTotalMb: 65536, cpus: 4, memoryTotalMb: 16384 }, + value: { state: "awake", diskTotalMb: 65536, cpus: 4, memoryTotalMb: 8192 }, }; const realAuth = await import("../services/vms/auth"); @@ -84,7 +84,7 @@ describe("POST /api/vm/[id]/resize", () => { programs.length = 0; routeResult = { ok: true, - value: { state: "awake", diskTotalMb: 65536, cpus: 4, memoryTotalMb: 16384 }, + value: { state: "awake", diskTotalMb: 65536, cpus: 4, memoryTotalMb: 8192 }, }; }); @@ -106,16 +106,16 @@ describe("POST /api/vm/[id]/resize", () => { }); test("passes disk, CPU, and memory together and returns confirmed stats", async () => { - const response = await resizeRoute.POST(post({ storageMb: 64 * 1024, cpu: 4, memoryMb: 16 * 1024 }), params); + const response = await resizeRoute.POST(post({ storageMb: 64 * 1024, cpu: 4, memoryMb: 8 * 1024 }), params); expect(response.status).toBe(200); expect(await response.json()).toMatchObject({ id: "fs-route", diskTotalMb: 65536, cpus: 4, - memoryTotalMb: 16384, + memoryTotalMb: 8192, maxDiskMb: 131072, - maxMemoryMb: 24576, - maxVcpus: 6, + maxMemoryMb: 8192, + maxVcpus: 4, }); expect(programs).toHaveLength(1); expect(programs[0]).toMatchObject({ @@ -124,7 +124,7 @@ describe("POST /api/vm/[id]/resize", () => { providerVmId: "fs-route", storageMb: 65536, cpu: 4, - memoryMb: 16384, + memoryMb: 8192, }); }); diff --git a/web/tests/vm-review-regressions.test.ts b/web/tests/vm-review-regressions.test.ts index e9cf9154f414..655379a1a3ac 100644 --- a/web/tests/vm-review-regressions.test.ts +++ b/web/tests/vm-review-regressions.test.ts @@ -5,6 +5,7 @@ import * as Layer from "effect/Layer"; import postgres, { type Sql } from "postgres"; import { closeCloudDbForTests } from "../db/client"; import { maxActiveVmsForPlan } from "../services/vms/entitlements"; +import { PAID_MAX_ACTIVE_VMS_DEFAULT } from "../services/vms/machineSpec"; import { VmBillingGateway, noOpVmBillingGateway } from "../services/vms/billingGateway"; import { VmRepository, VmRepositoryLive } from "../services/vms/repository"; import { VmProviderGateway, type VmProviderGatewayShape } from "../services/vms/providerGateway"; @@ -54,12 +55,12 @@ describe("VM review regressions", () => { })); for (const operation of ["resize", "exec", "attach", "session", "cmux-remote"] as const) { - for (const allowance of [maxActiveVmsForPlan("team", {}, { seats: 4 }), null, 50, undefined]) { + for (const allowance of [maxActiveVmsForPlan("team", {}, { seats: 10 }), null, 20, undefined]) { dbTest(`${operation} resumes a paused Team VM using allowance ${allowance}`, () => withTeam(async team => { await sql` insert into cloud_vms (user_id, billing_team_id, billing_plan_id, provider, provider_vm_id, image_id, status) select ${team}, ${team}, 'team', 'freestyle', ${team} || '-' || n, 'snapshot-test', 'running' - from generate_series(1, 51) n + from generate_series(1, 21) n `; const providerVmId = `${team}-paused`; await sql` @@ -118,9 +119,11 @@ describe("VM review regressions", () => { Layer.succeed(VmBillingGateway, noOpVmBillingGateway()), )), )); - if (allowance === 50 || allowance === undefined) { + if (allowance === 20 || allowance === undefined) { expect(result._tag).toBe("Left"); - if (result._tag === "Left") expect(result.left).toMatchObject({ _tag: "VmLimitExceededError", limit: 50 }); + // Without an explicit allowance, a Team row without seat data gets one seat's machines. + const limit = allowance ?? PAID_MAX_ACTIVE_VMS_DEFAULT; + if (result._tag === "Left") expect(result.left).toMatchObject({ _tag: "VmLimitExceededError", limit }); expect(resumes).toBe(0); expect(operations).toBe(0); } else { diff --git a/web/tests/vm-route-auth.test.ts b/web/tests/vm-route-auth.test.ts index 2769c9458018..dd1923f9752d 100644 --- a/web/tests/vm-route-auth.test.ts +++ b/web/tests/vm-route-auth.test.ts @@ -532,7 +532,7 @@ describe("VM REST auth", () => { billingCustomerType: "team", billingTeamId: "team-1", billingPlanId: "pro", - maxActiveVms: 50, + maxActiveVms: 5, provider: "freestyle", image: "snapshot-test", imageVersion: null, @@ -567,7 +567,7 @@ describe("VM REST auth", () => { expect(response.status).toBe(200); expect(await response.json()).toMatchObject({ - limits: { planId: "team", maxActiveVms: 200, activeVmCount: 1, freeAccessWindowDays: 0, freeAccessExpiresAt: null }, + limits: { planId: "team", maxActiveVms: 20, activeVmCount: 1, freeAccessWindowDays: 0, freeAccessExpiresAt: null }, vms: [{ freeAccessExpiresAt: null }], }); expect(listUserVms).toHaveBeenCalledWith("user-1", "team-1"); @@ -948,7 +948,7 @@ describe("VM REST auth", () => { expect(createVm).toHaveBeenCalledWith(expect.objectContaining({ billingTeamId: "team-1", billingPlanId: "pro", - maxActiveVms: 50, + maxActiveVms: 5, })); }); @@ -1042,12 +1042,12 @@ describe("VM REST auth", () => { upgradePlanId: "max", upgradeUrl: "https://cmux.com/api/billing/checkout?plan=max&cmux_source=vm_memory_limit", memoryMb: 32768, - maxMemoryMb: 24576, + maxMemoryMb: 8192, }); expect(runVmWorkflow).not.toHaveBeenCalled(); }); - test("starts a 64 GB machine on Max", async () => { + test("starts a 32 GB machine on Max", async () => { getUser.mockResolvedValue(stackUserForPlan("max")); runVmWorkflow.mockResolvedValue({ providerVmId: "provider-vm-max", @@ -1061,12 +1061,12 @@ describe("VM REST auth", () => { new Request("https://cmux.test/api/vm", { method: "POST", headers: { origin: "https://cmux.test" }, - body: JSON.stringify({ provider: "freestyle", image: "snapshot-test", memoryMb: 65536 }), + body: JSON.stringify({ provider: "freestyle", image: "snapshot-test", memoryMb: 32768 }), }), ); expect(response.status).toBe(200); - expect(createVm).toHaveBeenCalledWith(expect.objectContaining({ memoryMb: 65536 })); + expect(createVm).toHaveBeenCalledWith(expect.objectContaining({ memoryMb: 32768 })); }); test("rejects malformed memory sizes before billing or provider work", async () => { @@ -2033,7 +2033,7 @@ describe("VM REST auth", () => { billingCustomerType: "team", billingTeamId: "team-2", billingPlanId: "team", - maxActiveVms: 200, + maxActiveVms: 20, })); expect(runVmWorkflow).toHaveBeenCalled(); }); @@ -2144,7 +2144,7 @@ describe("VM REST auth", () => { deviceFingerprint: "fp-device-1", clientCapabilities: ["direct-ws-user-agent"], callerPlanId: "pro", - maxActiveVms: 50, + maxActiveVms: 5, modelPlane: expect.objectContaining({}), }); expect(openAttachEndpoint).not.toHaveBeenCalled(); @@ -2394,7 +2394,7 @@ describe("VM REST auth", () => { providerVmId: "provider-vm-team-1", callerPlanId: "pro", command: "true", - maxActiveVms: 50, + maxActiveVms: 5, timeoutMs: 30_000, modelPlane: expect.objectContaining({}), });