diff --git a/package.json b/package.json index 0f55ffba..997ef85a 100644 --- a/package.json +++ b/package.json @@ -49,5 +49,5 @@ "peerDependencies": { "typescript": "^5.6.2" }, - "version": "0.19.13" + "version": "0.20.0" } diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 6f723680..db26f5f1 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -90,7 +90,7 @@ export function _registerBuy(program: Command) { .option("-y, --yes", "Automatically confirm the order") .option( "-colo, --colocate ", - "Colocate with existing contracts", + "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored.", ) .option( "-q, --quote", @@ -109,9 +109,11 @@ export function _registerBuy(program: Command) { "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored.", ) .hook("preAction", (command) => { - const { type, zone, cluster } = command.opts(); - if (!type && !zone && !cluster) { - console.error(chalk.yellow("Must specify either --type or --zone")); + const { type, zone, cluster, colocate } = command.opts(); + if (!type && !zone && !cluster && !colocate) { + console.error( + chalk.yellow("Must specify either --type, --zone or --colocate"), + ); command.help(); process.exit(1); } @@ -129,19 +131,19 @@ export function _registerBuy(program: Command) { ` Examples: \x1b[2m# Buy 8 H100s for 1 hour at market price\x1b[0m - $ sf buy -n 8 -d 1h + $ sf buy -t h100v -n 8 -d 1h \x1b[2m# Buy 32 H100s for 6 hours starting in 3 hours\x1b[0m - $ sf buy -n 32 -d 6h -s +3h + $ sf buy -t h100v -n 32 -d 6h -s +3h \x1b[2m# Buy 64 H100s for 12 hours starting tomorrow at 9am\x1b[0m - $ sf buy -n 64 -d 12h -s "tomorrow at 9am" + $ sf buy -t h100v -n 64 -d 12h -s "tomorrow at 9am" \x1b[2m# Extend an existing contract that ends at 4pm by 4 hours\x1b[0m $ sf buy -s 4pm -d 4h -colo \x1b[2m# Place a standing order at a specific price\x1b[0m - $ sf buy -n 16 -d 24h -p 1.50 --standing + $ sf buy -t h100v -n 16 -d 24h -p 1.50 --standing `, ) .action(function buyOrderAction(options) { @@ -275,7 +277,7 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { props.options; setOrderProps({ - type: type ?? "h100i", // We still need to pass something even if --zone is provided + type, price: pricePerGpuHour, size: accelerators / GPUS_PER_NODE, startAt, @@ -308,13 +310,7 @@ export function getTotalPrice( return Math.ceil(pricePerGpuHour * size * GPUS_PER_NODE * durationInHours); } -function BuyOrderPreview(props: { - price: number; - size: number; - startAt: Date | "NOW"; - endsAt: Date; - type: string; -}) { +function BuyOrderPreview(props: BuyOrderProps) { const startDate = props.startAt === "NOW" ? dayjs() : dayjs(props.startAt); const start = startDate.format("MMM D h:mm a").toLowerCase(); @@ -332,9 +328,10 @@ function BuyOrderPreview(props: { const totalPrice = getTotalPrice(props.price, props.size, realDurationHours) / 100; - const isSupportedType = props.type in InstanceTypeMetadata; + const isSupportedType = typeof props.type === "string" && + props.type in InstanceTypeMetadata; const typeLabel = isSupportedType - ? InstanceTypeMetadata[props.type].displayName + ? InstanceTypeMetadata[props.type!]?.displayName : props.type; return ( @@ -365,19 +362,43 @@ function BuyOrderPreview(props: { head="size" value={`${props.size * GPUS_PER_NODE} gpus`} /> - - - type + {typeLabel && ( + + + type + + + {typeLabel} + {isSupportedType && ( + + ({props.type!}) + + )} + - - {typeLabel} - {isSupportedType && ( - - ({props.type}) - - )} + )} + {props.cluster && ( + + + zone + + + {props.cluster} + - + )} + {props.colocate && ( + + + + colocate with + + + + {props.colocate} + + + )}