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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CLI/CMUXCLI+VMTransfer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ extension CMUXCLI {

static var vmRunUsage: String {
"""
Usage: cmux vm run [--sync] [--pull <remote-path>] [--machine <id>] [--new] [--size <2g|4g|8g|16g|24g|32g>] [--timeout <seconds>] -- <command...>
Usage: cmux vm run [--sync] [--pull <remote-path>] [--machine <id>] [--new] [--size <20g>] [--timeout <seconds>] -- <command...>
Comment thread
lawrencecchen marked this conversation as resolved.
Resolved

Run a command on a cloud machine without naming one: reuses an idle
machine the router itself provisioned earlier (shown as "\(vmRunPoolLabel)"
Expand Down Expand Up @@ -1090,7 +1090,7 @@ extension CMUXCLI {
extension CMUXCLI {
static var vmRouteUsage: String {
"""
Usage: cmux vm route [--cwd <dir>] [--new] [--provision] [--size <2g|4g|8g|16g|24g|32g>] [--json]
Usage: cmux vm route [--cwd <dir>] [--new] [--provision] [--size <20g>] [--json]

Print the machine `cmux vm run` / `cmux vm agent` would use for work in a
directory, and why — without running anything. The policy is the router's
Expand Down
11 changes: 4 additions & 7 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4294,14 +4294,11 @@ struct CMUXCLI {
// never pins an image id unless the person passes `--image`: a pinned id
// that drifted from the web deploy's manifest failed every create with
// `vm_image_config_error`.
/// `--size` spellings → memory in MB. vCPUs scale with memory.
/// `--size` spellings → memory in MB. Every plan sells exactly the plan
/// machine (5 vCPU / 20 GB / 200 GB), so 20g is the only preset; the
/// backend refuses other sizes with `vm_memory_unsupported`.
private static let cloudVMSizeAliases: [String: Int] = [
"2g": 2048, "2gb": 2048, "small": 2048,
"4g": 4096, "4gb": 4096, "medium": 4096,
"8g": 8192, "8gb": 8192, "large": 8192,
"16g": 16384, "16gb": 16384, "xl": 16384,
"24g": 24576, "24gb": 24576,
"32g": 32768, "32gb": 32768, "xxl": 32768,
"20g": 20480, "20gb": 20480,
]
static func parseCloudVMSize(_ raw: String) -> Int? {
let key = raw.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ struct ProUpgradeCard: View {
}
return String(
localized: "settings.account.pro.subtitle",
defaultValue: "Cloud dev boxes, the iOS app, and cmux AI. $30/month, or $288/year."
defaultValue: "Cloud dev boxes, the iOS app, and cmux AI. $50/month, or $480/year."
)
}

Expand Down
57 changes: 37 additions & 20 deletions Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -126948,13 +126948,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Subscribe to cmux Pro for unlimited machines"
"value": "Subscribe to cmux Pro to create Cloud machines"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "cmux Pro に登録するとマシンを無制限に利用できます"
"value": "cmux Pro に登録すると Cloud マシンを作成できます"
}
}
}
Expand Down Expand Up @@ -149068,13 +149068,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "20 hrs/mo, then usage-based"
"value": "Included"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "月 20 時間、その後は従量課金"
"value": "含まれます"
}
}
}
Expand All @@ -149085,13 +149085,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Pooled, usage-based"
"value": "Included"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "共有、従量課金"
"value": "含まれます"
}
}
}
Expand Down Expand Up @@ -149130,6 +149130,23 @@
}
}
},
"pricing.native.compare.concurrent.paid": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "50"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "50"
}
}
}
},
"pricing.native.compare.custom": {
"extractionState": "manual",
"localizations": {
Expand Down Expand Up @@ -149850,13 +149867,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Model gateway with usage and cost analytics"
"value": "Unlimited workspaces"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "使用量とコスト分析付きのモデルゲートウェイ"
"value": "ワークスペースは無制限"
}
}
}
Expand All @@ -149867,13 +149884,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "20 active compute-hours per month, then usage-based"
"value": "Up to 50 Cloud VMs, each with 5 vCPU, 20 GB RAM, and 200 GB disk"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "月20時間のアクティブ計算時間、その後は従量課金"
"value": "最大 50 台の Cloud VM、各 5 vCPU / 20 GB RAM / 200 GB ディスク"
}
}
}
Expand Down Expand Up @@ -149918,13 +149935,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "$30"
"value": "$50"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "$30"
"value": "$50"
}
}
}
Expand Down Expand Up @@ -150088,13 +150105,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Pick a VM size per agent. You are billed per active compute-hour, and idle VMs suspend automatically. Pro includes 20 hours per month on the 4 vCPU / 16 GB size."
"value": "Every Cloud VM is 5 vCPU, 20 GB RAM, and 200 GB disk. Pro and Team include up to 50 machines with no metering or overages."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "エージェントごとに VM サイズを選びます。課金はアクティブなコンピュート時間単位で、アイドル状態の VM は自動停止します。Pro には 4 vCPU / 16 GB サイズで月 20 時間が含まれます。"
"value": "すべての Cloud VM は 5 vCPU / 20 GB RAM / 200 GB ディスクです。Pro と Team には最大 50 台のマシンが含まれ、従量課金や超過料金はありません。"
}
}
}
Expand Down Expand Up @@ -150275,13 +150292,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Pooled Cloud VM compute hours"
"value": "The same Cloud VM allowance as Pro"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "共有 Cloud VM コンピュート時間"
"value": "Pro と同じ Cloud VM 枠"
}
}
}
Expand Down Expand Up @@ -150343,13 +150360,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "$35"
"value": "$60"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "$35"
"value": "$60"
}
}
}
Expand Down Expand Up @@ -158176,13 +158193,13 @@
"en": {
"stringUnit": {
"state": "translated",
"value": "Cloud dev boxes, the iOS app, and cmux AI. $30/month, or $288/year."
"value": "Cloud dev boxes, the iOS app, and cmux AI. $50/month, or $480/year."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "クラウド開発マシン、iOSアプリ、cmux AI。月額$30、または年額$288。"
"value": "クラウド開発マシン、iOSアプリ、cmux AI。月額$50、または年額$480。"
}
}
}
Expand Down
12 changes: 7 additions & 5 deletions Sources/Cloud/MachinesPanelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -523,12 +523,13 @@ struct MachinesPanelView: View {

/// Free plans: "Upgrade to use more than 1 machine" — the ceiling plus the
/// way past it in one line. A plan with no machines at all has no ceiling
/// to cite: upgrading is what grants access in the first place.
/// to cite: upgrading is what grants access in the first place (the paid
/// allowance itself is stated on /pricing, not guessed here).
private func upgradeNudgeLabel(_ plan: MachinePlanSnapshot) -> String {
guard let maxActiveVms = plan.maxActiveVms, maxActiveVms > 0 else {
return String(
localized: "machines.empty.upgrade.none",
defaultValue: "Subscribe to cmux Pro for unlimited machines"
defaultValue: "Subscribe to cmux Pro to create Cloud machines"
)
}
if plan.isSingleMachinePlan {
Expand All @@ -543,9 +544,10 @@ struct MachinesPanelView: View {
)
}

/// Paid plans: "Your plan includes unlimited machines" under the create
/// button, so the empty state answers "what do I get" before the meter
/// shows a count.
/// Paid plans: "Your plan includes 50 machines" under the create button,
/// so the empty state answers "what do I get" before the meter shows a
/// count. The uncapped wording only appears when an operator lifted the
/// cap.
private func planIncludesLabel(_ plan: MachinePlanSnapshot) -> String {
guard let maxActiveVms = plan.maxActiveVms else {
return String(
Expand Down
18 changes: 11 additions & 7 deletions Sources/Cloud/NewMachineModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,20 @@ final class NewMachineModel {
typealias Launch = @MainActor ([String], @escaping @MainActor (CloudVMActionLauncher.Completion) -> Void) -> Bool

/// Memory sizes the backend accepts (`VM_MEMORY_OPTIONS_MB` in
/// `web/services/vms/entitlements.ts`); the plan ceiling trims the tail.
static let memoryOptionsMb: [Int] = [2048, 4096, 8192, 16384, 24576, 32768]
/// Mirrors `maxMemoryMbForPlan`: the free machine is a full-size computer;
/// paid plans unlock the largest size.
/// `web/services/vms/entitlements.ts`): exactly the plan machine, so the
/// picker mirrors what /pricing promises. The plan ceiling trims the tail.
static let memoryOptionsMb: [Int] = [planMachineMemoryMb]
/// The plan machine (`PLAN_MACHINE_MEMORY_MB`): 20 GB, 5 vCPU, 200 GB disk.
static let planMachineMemoryMb = 20480
/// Mirrors `maxMemoryMbForPlan`: every plan, free included, gets the plan
/// machine; the paywall is machine count and the free access window.
static func maxMemoryMb(planId: String?) -> Int {
planId == nil || planId == "free" ? 24576 : 32768
_ = planId
return planMachineMemoryMb
}
/// Mirrors `defaultMemoryMbForPlan`: 24 GB, never above the plan's max.
/// Mirrors `defaultMemoryMbForPlan`: the plan machine, never above the max.
static func defaultMemoryMb(planId: String?) -> Int {
min(24576, maxMemoryMb(planId: planId))
min(planMachineMemoryMb, maxMemoryMb(planId: planId))
}

let mode: Mode
Expand Down
Loading