From 4bfca90de1ef498152347c8a8c0d6094f23ef119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:19:50 -0700 Subject: [PATCH 1/9] fix: show actual cluster type instead of user-specified --- src/lib/buy/index.tsx | 275 ++++++++++++++++++++++-------------------- 1 file changed, 146 insertions(+), 129 deletions(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 0512ace..2954d88 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -45,44 +45,41 @@ export function _registerBuy(program: Command) { .command("buy") .description("Place a buy order") .showHelpAfterError() - .option( - "-t, --type ", - "Type of GPU", - ) + .option("-t, --type ", "Type of GPU") .option( "-n, --accelerators ", "Number of GPUs to purchase", - (val) => parseAccelerators(val, "buy"), - 8, + val => parseAccelerators(val, "buy"), + 8 ) .option( "-d, --duration ", "Duration of reservation (rounded up to the nearest hour)", - parseDuration, + parseDuration ) .option( "-p, --price ", - "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate", + "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate" ) .option( "-s, --start ", "Start time (date, relative time like '+1d', or 'NOW')", parseStartDateOrNow, - "NOW", + "NOW" ) .addOption( new Option( "-e, --end ", - "End time (date or relative time like '+1d', rounded up to nearest hour)", + "End time (date or relative time like '+1d', rounded up to nearest hour)" ) .argParser(parseEnd) - .conflicts("duration"), + .conflicts("duration") ) - .hook("preAction", (command) => { + .hook("preAction", command => { const { duration, end } = command.opts(); if ((!duration && !end) || (!!duration && !!end)) { console.error( - chalk.yellow("Specify either --duration or --end, but not both"), + chalk.yellow("Specify either --duration or --end, but not both") ); command.help(); process.exit(1); @@ -91,36 +88,42 @@ export function _registerBuy(program: Command) { .option("-y, --yes", "Automatically confirm the order") .option( "-colo, --colocate ", - "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored.", + "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored." ) .option( "-q, --quote", - "Get a price quote without placing an order. Useful for scripting.", + "Get a price quote without placing an order. Useful for scripting." ) .option( "--standing", - "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately.", + "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately." ) .option( "-z, --zone ", - "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored.", + "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored." ) .option( "-c, --cluster ", - "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored.", + "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored." ) - .hook("preAction", (command) => { + .hook("preAction", command => { const { type, zone, cluster, colocate } = command.opts(); if (!type && !zone && !cluster && !colocate) { console.error( - chalk.yellow("Must specify either --type, --zone or --colocate"), + chalk.yellow("Must specify either --type, --zone or --colocate") ); command.help(); process.exit(1); } + // let user know if they're using a zone or cluster and it's overriding the instance type + if (type && (zone || cluster)) { + console.warn( + `Warning: Zone '${zone}' takes precedence over instance type '${type}'` + ); + } }) .configureHelp({ - optionDescription: (option) => { + optionDescription: option => { if (option.flags === "-h, --help") { return "Display help for buy"; } @@ -145,7 +148,7 @@ Examples: \x1b[2m# Place a standing order at a specific price\x1b[0m $ sf buy -t h100v -n 16 -d 24h -p 1.50 --standing -`, +` ) .action(function buyOrderAction(options) { /* @@ -220,16 +223,16 @@ export function QuoteComponent(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return isLoading - ? ( + return isLoading ? ( + + - - - Getting quote... - + Getting quote... - ) - : ; + + ) : ( + + ); } export function QuoteAndBuy(props: { options: SfBuyOptions }) { @@ -247,13 +250,13 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (duration) { // If duration is set, calculate end from start + duration endsAt = roundEndDate( - dayjs(coercedStart).add(duration, "seconds").toDate(), + dayjs(coercedStart).add(duration, "seconds").toDate() ); } else if (end) { endsAt = end; props.options.duration = dayjs(endsAt).diff( dayjs(coercedStart), - "seconds", + "seconds" ); } else { throw new Error("Either duration or end must be set"); @@ -263,7 +266,7 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { const quote = await getQuoteFromParsedSfBuyOptions(props.options); if (!quote) { return logAndQuit( - "No quote found for the desired order. Try with a different start date, duration, or price.", + "No quote found for the desired order. Try with a different start date, duration, or price." ); } @@ -277,8 +280,17 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { const { type, accelerators, colocate, yes, standing, cluster } = props.options; + // If the user specifies a cluster, use the hardware type of the zone + let actualType = type; + if (cluster) { + const zoneMetadata = await getZoneMetadata(cluster); + if (zoneMetadata) { + actualType = zoneMetadata.hardwareType; + } + } + setOrderProps({ - type, + type: actualType, price: pricePerGpuHour, size: accelerators / GPUS_PER_NODE, startAt, @@ -291,22 +303,22 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return orderProps === null - ? ( + return orderProps === null ? ( + + - - - Getting quote... - + Getting quote... - ) - : ; + + ) : ( + + ); } export function getTotalPrice( pricePerGpuHour: number, size: number, - durationInHours: number, + durationInHours: number ) { return Math.ceil(pricePerGpuHour * size * GPUS_PER_NODE * durationInHours); } @@ -326,11 +338,11 @@ function BuyOrderPreview(props: BuyOrderProps) { const realDurationHours = realDuration / 3600 / 1000; const realDurationString = ms(realDuration); - const totalPrice = getTotalPrice(props.price, props.size, realDurationHours) / - 100; + const totalPrice = + getTotalPrice(props.price, props.size, realDurationHours) / 100; - const isSupportedType = typeof props.type === "string" && - props.type in InstanceTypeMetadata; + const isSupportedType = + typeof props.type === "string" && props.type in InstanceTypeMetadata; const typeLabel = isSupportedType ? InstanceTypeMetadata[props.type!]?.displayName : props.type; @@ -370,11 +382,7 @@ function BuyOrderPreview(props: BuyOrderProps) { {typeLabel} - {isSupportedType && ( - - ({props.type!}) - - )} + {isSupportedType && ({props.type!})} )} @@ -435,7 +443,7 @@ function BuyOrder(props: BuyOrderProps) { const [order, setOrder] = useState(null); const [loadingMsg, setLoadingMsg] = useState( - "Placing order...", + "Placing order..." ); const submitOrder = useCallback(async () => { @@ -449,7 +457,7 @@ function BuyOrder(props: BuyOrderProps) { totalPriceInCents: getTotalPrice( props.price, props.size, - realDurationInHours, + realDurationInHours ), startsAt: props.startAt, endsAt, @@ -465,12 +473,12 @@ function BuyOrder(props: BuyOrderProps) { const handleSubmit = useCallback( (submitValue: boolean) => { const { startAt, endsAt } = props; - const realDurationInHours = dayjs(endsAt).diff(dayjs(startAt)) / 1000 / - 3600; + const realDurationInHours = + dayjs(endsAt).diff(dayjs(startAt)) / 1000 / 3600; const totalPriceInCents = getTotalPrice( props.price, props.size, - realDurationInHours, + realDurationInHours ); analytics.track({ @@ -517,7 +525,7 @@ function BuyOrder(props: BuyOrderProps) { }); submitOrder(); }, - [props, exit, submitOrder], + [props, exit, submitOrder] ); useEffect(() => { @@ -529,7 +537,7 @@ function BuyOrder(props: BuyOrderProps) { const o = await getOrder(order.id); if (!o) { setLoadingMsg( - "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed.", + "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed." ); // Schedule next poll setTimeout(pollForOrder, 200); @@ -558,10 +566,7 @@ function BuyOrder(props: BuyOrderProps) { Place order? (y/n) - + )} @@ -590,52 +595,47 @@ function BuyOrder(props: BuyOrderProps) { order.status === "filled" && (order as Awaited>) && order.execution_price && ( - - {order.start_at && order.end_at && - order.start_at !== order.end_at && ( - - )} - - {order.execution_price && Number(order.price) > 0 && - Number(order.execution_price) > 0 && - Number(order.execution_price) < Number(order.price) && ( + + {order.start_at && + order.end_at && + order.start_at !== order.end_at && ( + + )} - )} - - )} + {order.execution_price && + Number(order.price) > 0 && + Number(order.execution_price) > 0 && + Number(order.execution_price) < Number(order.price) && ( + + )} + + )} )} @@ -670,12 +670,12 @@ export async function placeBuyOrder(options: { }) { invariant( options.totalPriceInCents === Math.ceil(options.totalPriceInCents), - "totalPriceInCents must be a whole number", + "totalPriceInCents must be a whole number" ); invariant(options.numberNodes > 0, "numberNodes must be greater than 0"); invariant( options.numberNodes === Math.ceil(options.numberNodes), - "numberNodes must be a whole number", + "numberNodes must be a whole number" ); const api = await apiClient(); @@ -700,8 +700,9 @@ export async function placeBuyOrder(options: { start_at, end_at: roundEndDate(options.endsAt).toISOString(), price: options.totalPriceInCents, - colocate_with: - (options.colocateWith ? [options.colocateWith] : []) as string[], + colocate_with: (options.colocateWith + ? [options.colocateWith] + : []) as string[], flags: { ioc: !options.standing, }, @@ -716,12 +717,12 @@ export async function placeBuyOrder(options: { case 400: { if (error?.message === "Insufficient balance") { return logAndQuit( - "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true", + "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true" ); } return logAndQuit( - `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}`, + `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}` ); } case 401: @@ -732,14 +733,14 @@ export async function placeBuyOrder(options: { return logAndQuit( `Failed to place order: ${response.status} ${response.statusText} - ${ error ? `[${error}] ` : "" - }${error?.message || "Unknown error"}`, + }${error?.message || "Unknown error"}` ); } } if (!data) { return logAndQuit( - `Failed to place order: Unexpected response from server: ${response}`, + `Failed to place order: Unexpected response from server: ${response}` ); } @@ -752,9 +753,10 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { // from the market's perspective, "NOW" means at the beginning of the next minute. // when the order duration is very short, this can cause the rate to be computed incorrectly // if we implicitly assume it to mean `new Date()`. - const coercedStartTime = startTimeOrNow === "NOW" - ? roundDateUpToNextMinute(new Date()) - : startTimeOrNow; + const coercedStartTime = + startTimeOrNow === "NOW" + ? roundDateUpToNextMinute(new Date()) + : startTimeOrNow; const durationSeconds = dayjs(quote.end_at).diff(dayjs(coercedStartTime)); const durationHours = durationSeconds / 3600 / 1000; @@ -762,9 +764,10 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { } async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { - const startsAt = options.start === "NOW" - ? "NOW" - : roundStartDate(parseStartDate(options.start)); + const startsAt = + options.start === "NOW" + ? "NOW" + : roundStartDate(parseStartDate(options.start)); const durationSeconds = options.duration ? options.duration : dayjs(options.end).diff(dayjs(parseStartDate(startsAt)), "seconds"); @@ -772,11 +775,11 @@ async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { const minDurationSeconds = Math.max( 1, - durationSeconds - Math.ceil(durationSeconds * 0.1), + durationSeconds - Math.ceil(durationSeconds * 0.1) ); const maxDurationSeconds = Math.max( durationSeconds + 3600, - durationSeconds + Math.ceil(durationSeconds * 0.1), + durationSeconds + Math.ceil(durationSeconds * 0.1) ); return await getQuote({ @@ -810,12 +813,14 @@ export async function getQuote(options: QuoteOptions) { side: "buy", instance_type: options.instanceType, quantity: options.quantity, - min_start_date: options.minStartTime === "NOW" - ? ("NOW" as const) - : options.minStartTime.toISOString(), - max_start_date: options.maxStartTime === "NOW" - ? ("NOW" as const) - : options.maxStartTime.toISOString(), + min_start_date: + options.minStartTime === "NOW" + ? ("NOW" as const) + : options.minStartTime.toISOString(), + max_start_date: + options.maxStartTime === "NOW" + ? ("NOW" as const) + : options.maxStartTime.toISOString(), min_duration: options.minDurationSeconds, max_duration: options.maxDurationSeconds, cluster: options.cluster, @@ -844,7 +849,7 @@ export async function getQuote(options: QuoteOptions) { if (!data) { return logAndQuit( - `Failed to get quote: Unexpected response from server: ${response}`, + `Failed to get quote: Unexpected response from server: ${response}` ); } @@ -874,3 +879,15 @@ export async function getOrder(orderId: string) { return order; } + +async function getZoneMetadata(zoneName: string) { + const api = await apiClient(); + const { data } = await api.GET("/v0/zones", {}); + const zone = data?.data?.find(z => z.name === zoneName); + return zone + ? { + deliveryType: zone.delivery_type, // "K8s" or "VM" + hardwareType: zone.hardware_type, // "h100i", "h100v", etc. + } + : null; +} From 7c3b7c91fc9d8d5493f34ad382e5fe763d91f700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:43:42 -0700 Subject: [PATCH 2/9] Include delivery type in type information --- src/lib/buy/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 2954d88..0a5f4a5 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -285,7 +285,13 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (cluster) { const zoneMetadata = await getZoneMetadata(cluster); if (zoneMetadata) { - actualType = zoneMetadata.hardwareType; + const DeliveryTypeMetadata = { + "K8s": { displayName: "Kubernetes" }, + "VM": { displayName: "Virtual Machine" } + }; + + const deliveryDisplayName = DeliveryTypeMetadata[zoneMetadata.deliveryType]?.displayName || zoneMetadata.deliveryType; + actualType = `${deliveryDisplayName} (${zoneMetadata.hardwareType})`; } } From 06508d49f8111da7a7600e20e04a22afe6e17bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:44:46 -0700 Subject: [PATCH 3/9] deno fmt --- src/lib/buy/index.tsx | 249 +++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 123 deletions(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 0a5f4a5..667def0 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -49,37 +49,37 @@ export function _registerBuy(program: Command) { .option( "-n, --accelerators ", "Number of GPUs to purchase", - val => parseAccelerators(val, "buy"), - 8 + (val) => parseAccelerators(val, "buy"), + 8, ) .option( "-d, --duration ", "Duration of reservation (rounded up to the nearest hour)", - parseDuration + parseDuration, ) .option( "-p, --price ", - "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate" + "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate", ) .option( "-s, --start ", "Start time (date, relative time like '+1d', or 'NOW')", parseStartDateOrNow, - "NOW" + "NOW", ) .addOption( new Option( "-e, --end ", - "End time (date or relative time like '+1d', rounded up to nearest hour)" + "End time (date or relative time like '+1d', rounded up to nearest hour)", ) .argParser(parseEnd) - .conflicts("duration") + .conflicts("duration"), ) - .hook("preAction", command => { + .hook("preAction", (command) => { const { duration, end } = command.opts(); if ((!duration && !end) || (!!duration && !!end)) { console.error( - chalk.yellow("Specify either --duration or --end, but not both") + chalk.yellow("Specify either --duration or --end, but not both"), ); command.help(); process.exit(1); @@ -88,29 +88,29 @@ export function _registerBuy(program: Command) { .option("-y, --yes", "Automatically confirm the order") .option( "-colo, --colocate ", - "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored." + "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored.", ) .option( "-q, --quote", - "Get a price quote without placing an order. Useful for scripting." + "Get a price quote without placing an order. Useful for scripting.", ) .option( "--standing", - "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately." + "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately.", ) .option( "-z, --zone ", - "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored." + "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored.", ) .option( "-c, --cluster ", - "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored." + "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored.", ) - .hook("preAction", command => { + .hook("preAction", (command) => { const { type, zone, cluster, colocate } = command.opts(); if (!type && !zone && !cluster && !colocate) { console.error( - chalk.yellow("Must specify either --type, --zone or --colocate") + chalk.yellow("Must specify either --type, --zone or --colocate"), ); command.help(); process.exit(1); @@ -118,12 +118,12 @@ export function _registerBuy(program: Command) { // let user know if they're using a zone or cluster and it's overriding the instance type if (type && (zone || cluster)) { console.warn( - `Warning: Zone '${zone}' takes precedence over instance type '${type}'` + `Warning: Zone '${zone}' takes precedence over instance type '${type}'`, ); } }) .configureHelp({ - optionDescription: option => { + optionDescription: (option) => { if (option.flags === "-h, --help") { return "Display help for buy"; } @@ -148,7 +148,7 @@ Examples: \x1b[2m# Place a standing order at a specific price\x1b[0m $ sf buy -t h100v -n 16 -d 24h -p 1.50 --standing -` +`, ) .action(function buyOrderAction(options) { /* @@ -223,16 +223,16 @@ export function QuoteComponent(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return isLoading ? ( - - + return isLoading + ? ( - Getting quote... + + + Getting quote... + - - ) : ( - - ); + ) + : ; } export function QuoteAndBuy(props: { options: SfBuyOptions }) { @@ -250,13 +250,13 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (duration) { // If duration is set, calculate end from start + duration endsAt = roundEndDate( - dayjs(coercedStart).add(duration, "seconds").toDate() + dayjs(coercedStart).add(duration, "seconds").toDate(), ); } else if (end) { endsAt = end; props.options.duration = dayjs(endsAt).diff( dayjs(coercedStart), - "seconds" + "seconds", ); } else { throw new Error("Either duration or end must be set"); @@ -266,7 +266,7 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { const quote = await getQuoteFromParsedSfBuyOptions(props.options); if (!quote) { return logAndQuit( - "No quote found for the desired order. Try with a different start date, duration, or price." + "No quote found for the desired order. Try with a different start date, duration, or price.", ); } @@ -287,10 +287,12 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (zoneMetadata) { const DeliveryTypeMetadata = { "K8s": { displayName: "Kubernetes" }, - "VM": { displayName: "Virtual Machine" } + "VM": { displayName: "Virtual Machine" }, }; - - const deliveryDisplayName = DeliveryTypeMetadata[zoneMetadata.deliveryType]?.displayName || zoneMetadata.deliveryType; + + const deliveryDisplayName = + DeliveryTypeMetadata[zoneMetadata.deliveryType]?.displayName || + zoneMetadata.deliveryType; actualType = `${deliveryDisplayName} (${zoneMetadata.hardwareType})`; } } @@ -309,22 +311,22 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return orderProps === null ? ( - - + return orderProps === null + ? ( - Getting quote... + + + Getting quote... + - - ) : ( - - ); + ) + : ; } export function getTotalPrice( pricePerGpuHour: number, size: number, - durationInHours: number + durationInHours: number, ) { return Math.ceil(pricePerGpuHour * size * GPUS_PER_NODE * durationInHours); } @@ -344,11 +346,11 @@ function BuyOrderPreview(props: BuyOrderProps) { const realDurationHours = realDuration / 3600 / 1000; const realDurationString = ms(realDuration); - const totalPrice = - getTotalPrice(props.price, props.size, realDurationHours) / 100; + const totalPrice = getTotalPrice(props.price, props.size, realDurationHours) / + 100; - const isSupportedType = - typeof props.type === "string" && props.type in InstanceTypeMetadata; + const isSupportedType = typeof props.type === "string" && + props.type in InstanceTypeMetadata; const typeLabel = isSupportedType ? InstanceTypeMetadata[props.type!]?.displayName : props.type; @@ -449,7 +451,7 @@ function BuyOrder(props: BuyOrderProps) { const [order, setOrder] = useState(null); const [loadingMsg, setLoadingMsg] = useState( - "Placing order..." + "Placing order...", ); const submitOrder = useCallback(async () => { @@ -463,7 +465,7 @@ function BuyOrder(props: BuyOrderProps) { totalPriceInCents: getTotalPrice( props.price, props.size, - realDurationInHours + realDurationInHours, ), startsAt: props.startAt, endsAt, @@ -479,12 +481,12 @@ function BuyOrder(props: BuyOrderProps) { const handleSubmit = useCallback( (submitValue: boolean) => { const { startAt, endsAt } = props; - const realDurationInHours = - dayjs(endsAt).diff(dayjs(startAt)) / 1000 / 3600; + const realDurationInHours = dayjs(endsAt).diff(dayjs(startAt)) / 1000 / + 3600; const totalPriceInCents = getTotalPrice( props.price, props.size, - realDurationInHours + realDurationInHours, ); analytics.track({ @@ -531,7 +533,7 @@ function BuyOrder(props: BuyOrderProps) { }); submitOrder(); }, - [props, exit, submitOrder] + [props, exit, submitOrder], ); useEffect(() => { @@ -543,7 +545,7 @@ function BuyOrder(props: BuyOrderProps) { const o = await getOrder(order.id); if (!o) { setLoadingMsg( - "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed." + "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed.", ); // Schedule next poll setTimeout(pollForOrder, 200); @@ -601,47 +603,53 @@ function BuyOrder(props: BuyOrderProps) { order.status === "filled" && (order as Awaited>) && order.execution_price && ( - - {order.start_at && - order.end_at && - order.start_at !== order.end_at && ( - - )} + + {order.start_at && + order.end_at && + order.start_at !== order.end_at && ( + + )} + + {order.execution_price && + Number(order.price) > 0 && + Number(order.execution_price) > 0 && + Number(order.execution_price) < Number(order.price) && ( - {order.execution_price && - Number(order.price) > 0 && - Number(order.execution_price) > 0 && - Number(order.execution_price) < Number(order.price) && ( - - )} - - )} + )} + + )} )} @@ -676,12 +684,12 @@ export async function placeBuyOrder(options: { }) { invariant( options.totalPriceInCents === Math.ceil(options.totalPriceInCents), - "totalPriceInCents must be a whole number" + "totalPriceInCents must be a whole number", ); invariant(options.numberNodes > 0, "numberNodes must be greater than 0"); invariant( options.numberNodes === Math.ceil(options.numberNodes), - "numberNodes must be a whole number" + "numberNodes must be a whole number", ); const api = await apiClient(); @@ -706,9 +714,8 @@ export async function placeBuyOrder(options: { start_at, end_at: roundEndDate(options.endsAt).toISOString(), price: options.totalPriceInCents, - colocate_with: (options.colocateWith - ? [options.colocateWith] - : []) as string[], + colocate_with: + (options.colocateWith ? [options.colocateWith] : []) as string[], flags: { ioc: !options.standing, }, @@ -723,12 +730,12 @@ export async function placeBuyOrder(options: { case 400: { if (error?.message === "Insufficient balance") { return logAndQuit( - "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true" + "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true", ); } return logAndQuit( - `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}` + `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}`, ); } case 401: @@ -739,14 +746,14 @@ export async function placeBuyOrder(options: { return logAndQuit( `Failed to place order: ${response.status} ${response.statusText} - ${ error ? `[${error}] ` : "" - }${error?.message || "Unknown error"}` + }${error?.message || "Unknown error"}`, ); } } if (!data) { return logAndQuit( - `Failed to place order: Unexpected response from server: ${response}` + `Failed to place order: Unexpected response from server: ${response}`, ); } @@ -759,10 +766,9 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { // from the market's perspective, "NOW" means at the beginning of the next minute. // when the order duration is very short, this can cause the rate to be computed incorrectly // if we implicitly assume it to mean `new Date()`. - const coercedStartTime = - startTimeOrNow === "NOW" - ? roundDateUpToNextMinute(new Date()) - : startTimeOrNow; + const coercedStartTime = startTimeOrNow === "NOW" + ? roundDateUpToNextMinute(new Date()) + : startTimeOrNow; const durationSeconds = dayjs(quote.end_at).diff(dayjs(coercedStartTime)); const durationHours = durationSeconds / 3600 / 1000; @@ -770,10 +776,9 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { } async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { - const startsAt = - options.start === "NOW" - ? "NOW" - : roundStartDate(parseStartDate(options.start)); + const startsAt = options.start === "NOW" + ? "NOW" + : roundStartDate(parseStartDate(options.start)); const durationSeconds = options.duration ? options.duration : dayjs(options.end).diff(dayjs(parseStartDate(startsAt)), "seconds"); @@ -781,11 +786,11 @@ async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { const minDurationSeconds = Math.max( 1, - durationSeconds - Math.ceil(durationSeconds * 0.1) + durationSeconds - Math.ceil(durationSeconds * 0.1), ); const maxDurationSeconds = Math.max( durationSeconds + 3600, - durationSeconds + Math.ceil(durationSeconds * 0.1) + durationSeconds + Math.ceil(durationSeconds * 0.1), ); return await getQuote({ @@ -819,14 +824,12 @@ export async function getQuote(options: QuoteOptions) { side: "buy", instance_type: options.instanceType, quantity: options.quantity, - min_start_date: - options.minStartTime === "NOW" - ? ("NOW" as const) - : options.minStartTime.toISOString(), - max_start_date: - options.maxStartTime === "NOW" - ? ("NOW" as const) - : options.maxStartTime.toISOString(), + min_start_date: options.minStartTime === "NOW" + ? ("NOW" as const) + : options.minStartTime.toISOString(), + max_start_date: options.maxStartTime === "NOW" + ? ("NOW" as const) + : options.maxStartTime.toISOString(), min_duration: options.minDurationSeconds, max_duration: options.maxDurationSeconds, cluster: options.cluster, @@ -855,7 +858,7 @@ export async function getQuote(options: QuoteOptions) { if (!data) { return logAndQuit( - `Failed to get quote: Unexpected response from server: ${response}` + `Failed to get quote: Unexpected response from server: ${response}`, ); } @@ -889,11 +892,11 @@ export async function getOrder(orderId: string) { async function getZoneMetadata(zoneName: string) { const api = await apiClient(); const { data } = await api.GET("/v0/zones", {}); - const zone = data?.data?.find(z => z.name === zoneName); + const zone = data?.data?.find((z) => z.name === zoneName); return zone ? { - deliveryType: zone.delivery_type, // "K8s" or "VM" - hardwareType: zone.hardware_type, // "h100i", "h100v", etc. - } + deliveryType: zone.delivery_type, // "K8s" or "VM" + hardwareType: zone.hardware_type, // "h100i", "h100v", etc. + } : null; } From e11990657c12623b836c0f779c160bac11ac2f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:46:23 -0700 Subject: [PATCH 4/9] deno fmt (#191) --- src/lib/buy/index.tsx | 249 +++++++++++++++++++++--------------------- 1 file changed, 126 insertions(+), 123 deletions(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 0a5f4a5..667def0 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -49,37 +49,37 @@ export function _registerBuy(program: Command) { .option( "-n, --accelerators ", "Number of GPUs to purchase", - val => parseAccelerators(val, "buy"), - 8 + (val) => parseAccelerators(val, "buy"), + 8, ) .option( "-d, --duration ", "Duration of reservation (rounded up to the nearest hour)", - parseDuration + parseDuration, ) .option( "-p, --price ", - "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate" + "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate", ) .option( "-s, --start ", "Start time (date, relative time like '+1d', or 'NOW')", parseStartDateOrNow, - "NOW" + "NOW", ) .addOption( new Option( "-e, --end ", - "End time (date or relative time like '+1d', rounded up to nearest hour)" + "End time (date or relative time like '+1d', rounded up to nearest hour)", ) .argParser(parseEnd) - .conflicts("duration") + .conflicts("duration"), ) - .hook("preAction", command => { + .hook("preAction", (command) => { const { duration, end } = command.opts(); if ((!duration && !end) || (!!duration && !!end)) { console.error( - chalk.yellow("Specify either --duration or --end, but not both") + chalk.yellow("Specify either --duration or --end, but not both"), ); command.help(); process.exit(1); @@ -88,29 +88,29 @@ export function _registerBuy(program: Command) { .option("-y, --yes", "Automatically confirm the order") .option( "-colo, --colocate ", - "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored." + "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored.", ) .option( "-q, --quote", - "Get a price quote without placing an order. Useful for scripting." + "Get a price quote without placing an order. Useful for scripting.", ) .option( "--standing", - "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately." + "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately.", ) .option( "-z, --zone ", - "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored." + "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored.", ) .option( "-c, --cluster ", - "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored." + "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored.", ) - .hook("preAction", command => { + .hook("preAction", (command) => { const { type, zone, cluster, colocate } = command.opts(); if (!type && !zone && !cluster && !colocate) { console.error( - chalk.yellow("Must specify either --type, --zone or --colocate") + chalk.yellow("Must specify either --type, --zone or --colocate"), ); command.help(); process.exit(1); @@ -118,12 +118,12 @@ export function _registerBuy(program: Command) { // let user know if they're using a zone or cluster and it's overriding the instance type if (type && (zone || cluster)) { console.warn( - `Warning: Zone '${zone}' takes precedence over instance type '${type}'` + `Warning: Zone '${zone}' takes precedence over instance type '${type}'`, ); } }) .configureHelp({ - optionDescription: option => { + optionDescription: (option) => { if (option.flags === "-h, --help") { return "Display help for buy"; } @@ -148,7 +148,7 @@ Examples: \x1b[2m# Place a standing order at a specific price\x1b[0m $ sf buy -t h100v -n 16 -d 24h -p 1.50 --standing -` +`, ) .action(function buyOrderAction(options) { /* @@ -223,16 +223,16 @@ export function QuoteComponent(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return isLoading ? ( - - + return isLoading + ? ( - Getting quote... + + + Getting quote... + - - ) : ( - - ); + ) + : ; } export function QuoteAndBuy(props: { options: SfBuyOptions }) { @@ -250,13 +250,13 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (duration) { // If duration is set, calculate end from start + duration endsAt = roundEndDate( - dayjs(coercedStart).add(duration, "seconds").toDate() + dayjs(coercedStart).add(duration, "seconds").toDate(), ); } else if (end) { endsAt = end; props.options.duration = dayjs(endsAt).diff( dayjs(coercedStart), - "seconds" + "seconds", ); } else { throw new Error("Either duration or end must be set"); @@ -266,7 +266,7 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { const quote = await getQuoteFromParsedSfBuyOptions(props.options); if (!quote) { return logAndQuit( - "No quote found for the desired order. Try with a different start date, duration, or price." + "No quote found for the desired order. Try with a different start date, duration, or price.", ); } @@ -287,10 +287,12 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (zoneMetadata) { const DeliveryTypeMetadata = { "K8s": { displayName: "Kubernetes" }, - "VM": { displayName: "Virtual Machine" } + "VM": { displayName: "Virtual Machine" }, }; - - const deliveryDisplayName = DeliveryTypeMetadata[zoneMetadata.deliveryType]?.displayName || zoneMetadata.deliveryType; + + const deliveryDisplayName = + DeliveryTypeMetadata[zoneMetadata.deliveryType]?.displayName || + zoneMetadata.deliveryType; actualType = `${deliveryDisplayName} (${zoneMetadata.hardwareType})`; } } @@ -309,22 +311,22 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return orderProps === null ? ( - - + return orderProps === null + ? ( - Getting quote... + + + Getting quote... + - - ) : ( - - ); + ) + : ; } export function getTotalPrice( pricePerGpuHour: number, size: number, - durationInHours: number + durationInHours: number, ) { return Math.ceil(pricePerGpuHour * size * GPUS_PER_NODE * durationInHours); } @@ -344,11 +346,11 @@ function BuyOrderPreview(props: BuyOrderProps) { const realDurationHours = realDuration / 3600 / 1000; const realDurationString = ms(realDuration); - const totalPrice = - getTotalPrice(props.price, props.size, realDurationHours) / 100; + const totalPrice = getTotalPrice(props.price, props.size, realDurationHours) / + 100; - const isSupportedType = - typeof props.type === "string" && props.type in InstanceTypeMetadata; + const isSupportedType = typeof props.type === "string" && + props.type in InstanceTypeMetadata; const typeLabel = isSupportedType ? InstanceTypeMetadata[props.type!]?.displayName : props.type; @@ -449,7 +451,7 @@ function BuyOrder(props: BuyOrderProps) { const [order, setOrder] = useState(null); const [loadingMsg, setLoadingMsg] = useState( - "Placing order..." + "Placing order...", ); const submitOrder = useCallback(async () => { @@ -463,7 +465,7 @@ function BuyOrder(props: BuyOrderProps) { totalPriceInCents: getTotalPrice( props.price, props.size, - realDurationInHours + realDurationInHours, ), startsAt: props.startAt, endsAt, @@ -479,12 +481,12 @@ function BuyOrder(props: BuyOrderProps) { const handleSubmit = useCallback( (submitValue: boolean) => { const { startAt, endsAt } = props; - const realDurationInHours = - dayjs(endsAt).diff(dayjs(startAt)) / 1000 / 3600; + const realDurationInHours = dayjs(endsAt).diff(dayjs(startAt)) / 1000 / + 3600; const totalPriceInCents = getTotalPrice( props.price, props.size, - realDurationInHours + realDurationInHours, ); analytics.track({ @@ -531,7 +533,7 @@ function BuyOrder(props: BuyOrderProps) { }); submitOrder(); }, - [props, exit, submitOrder] + [props, exit, submitOrder], ); useEffect(() => { @@ -543,7 +545,7 @@ function BuyOrder(props: BuyOrderProps) { const o = await getOrder(order.id); if (!o) { setLoadingMsg( - "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed." + "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed.", ); // Schedule next poll setTimeout(pollForOrder, 200); @@ -601,47 +603,53 @@ function BuyOrder(props: BuyOrderProps) { order.status === "filled" && (order as Awaited>) && order.execution_price && ( - - {order.start_at && - order.end_at && - order.start_at !== order.end_at && ( - - )} + + {order.start_at && + order.end_at && + order.start_at !== order.end_at && ( + + )} + + {order.execution_price && + Number(order.price) > 0 && + Number(order.execution_price) > 0 && + Number(order.execution_price) < Number(order.price) && ( - {order.execution_price && - Number(order.price) > 0 && - Number(order.execution_price) > 0 && - Number(order.execution_price) < Number(order.price) && ( - - )} - - )} + )} + + )} )} @@ -676,12 +684,12 @@ export async function placeBuyOrder(options: { }) { invariant( options.totalPriceInCents === Math.ceil(options.totalPriceInCents), - "totalPriceInCents must be a whole number" + "totalPriceInCents must be a whole number", ); invariant(options.numberNodes > 0, "numberNodes must be greater than 0"); invariant( options.numberNodes === Math.ceil(options.numberNodes), - "numberNodes must be a whole number" + "numberNodes must be a whole number", ); const api = await apiClient(); @@ -706,9 +714,8 @@ export async function placeBuyOrder(options: { start_at, end_at: roundEndDate(options.endsAt).toISOString(), price: options.totalPriceInCents, - colocate_with: (options.colocateWith - ? [options.colocateWith] - : []) as string[], + colocate_with: + (options.colocateWith ? [options.colocateWith] : []) as string[], flags: { ioc: !options.standing, }, @@ -723,12 +730,12 @@ export async function placeBuyOrder(options: { case 400: { if (error?.message === "Insufficient balance") { return logAndQuit( - "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true" + "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true", ); } return logAndQuit( - `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}` + `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}`, ); } case 401: @@ -739,14 +746,14 @@ export async function placeBuyOrder(options: { return logAndQuit( `Failed to place order: ${response.status} ${response.statusText} - ${ error ? `[${error}] ` : "" - }${error?.message || "Unknown error"}` + }${error?.message || "Unknown error"}`, ); } } if (!data) { return logAndQuit( - `Failed to place order: Unexpected response from server: ${response}` + `Failed to place order: Unexpected response from server: ${response}`, ); } @@ -759,10 +766,9 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { // from the market's perspective, "NOW" means at the beginning of the next minute. // when the order duration is very short, this can cause the rate to be computed incorrectly // if we implicitly assume it to mean `new Date()`. - const coercedStartTime = - startTimeOrNow === "NOW" - ? roundDateUpToNextMinute(new Date()) - : startTimeOrNow; + const coercedStartTime = startTimeOrNow === "NOW" + ? roundDateUpToNextMinute(new Date()) + : startTimeOrNow; const durationSeconds = dayjs(quote.end_at).diff(dayjs(coercedStartTime)); const durationHours = durationSeconds / 3600 / 1000; @@ -770,10 +776,9 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { } async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { - const startsAt = - options.start === "NOW" - ? "NOW" - : roundStartDate(parseStartDate(options.start)); + const startsAt = options.start === "NOW" + ? "NOW" + : roundStartDate(parseStartDate(options.start)); const durationSeconds = options.duration ? options.duration : dayjs(options.end).diff(dayjs(parseStartDate(startsAt)), "seconds"); @@ -781,11 +786,11 @@ async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { const minDurationSeconds = Math.max( 1, - durationSeconds - Math.ceil(durationSeconds * 0.1) + durationSeconds - Math.ceil(durationSeconds * 0.1), ); const maxDurationSeconds = Math.max( durationSeconds + 3600, - durationSeconds + Math.ceil(durationSeconds * 0.1) + durationSeconds + Math.ceil(durationSeconds * 0.1), ); return await getQuote({ @@ -819,14 +824,12 @@ export async function getQuote(options: QuoteOptions) { side: "buy", instance_type: options.instanceType, quantity: options.quantity, - min_start_date: - options.minStartTime === "NOW" - ? ("NOW" as const) - : options.minStartTime.toISOString(), - max_start_date: - options.maxStartTime === "NOW" - ? ("NOW" as const) - : options.maxStartTime.toISOString(), + min_start_date: options.minStartTime === "NOW" + ? ("NOW" as const) + : options.minStartTime.toISOString(), + max_start_date: options.maxStartTime === "NOW" + ? ("NOW" as const) + : options.maxStartTime.toISOString(), min_duration: options.minDurationSeconds, max_duration: options.maxDurationSeconds, cluster: options.cluster, @@ -855,7 +858,7 @@ export async function getQuote(options: QuoteOptions) { if (!data) { return logAndQuit( - `Failed to get quote: Unexpected response from server: ${response}` + `Failed to get quote: Unexpected response from server: ${response}`, ); } @@ -889,11 +892,11 @@ export async function getOrder(orderId: string) { async function getZoneMetadata(zoneName: string) { const api = await apiClient(); const { data } = await api.GET("/v0/zones", {}); - const zone = data?.data?.find(z => z.name === zoneName); + const zone = data?.data?.find((z) => z.name === zoneName); return zone ? { - deliveryType: zone.delivery_type, // "K8s" or "VM" - hardwareType: zone.hardware_type, // "h100i", "h100v", etc. - } + deliveryType: zone.delivery_type, // "K8s" or "VM" + hardwareType: zone.hardware_type, // "h100i", "h100v", etc. + } : null; } From 60ca4fc31c2453a057228d570c2511317c6235b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:19:50 -0700 Subject: [PATCH 5/9] fix: show actual cluster type instead of user-specified --- src/lib/buy/index.tsx | 151 ++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 73 deletions(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 667def0..e780d4d 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -49,37 +49,37 @@ export function _registerBuy(program: Command) { .option( "-n, --accelerators ", "Number of GPUs to purchase", - (val) => parseAccelerators(val, "buy"), - 8, + val => parseAccelerators(val, "buy"), + 8 ) .option( "-d, --duration ", "Duration of reservation (rounded up to the nearest hour)", - parseDuration, + parseDuration ) .option( "-p, --price ", - "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate", + "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate" ) .option( "-s, --start ", "Start time (date, relative time like '+1d', or 'NOW')", parseStartDateOrNow, - "NOW", + "NOW" ) .addOption( new Option( "-e, --end ", - "End time (date or relative time like '+1d', rounded up to nearest hour)", + "End time (date or relative time like '+1d', rounded up to nearest hour)" ) .argParser(parseEnd) - .conflicts("duration"), + .conflicts("duration") ) - .hook("preAction", (command) => { + .hook("preAction", command => { const { duration, end } = command.opts(); if ((!duration && !end) || (!!duration && !!end)) { console.error( - chalk.yellow("Specify either --duration or --end, but not both"), + chalk.yellow("Specify either --duration or --end, but not both") ); command.help(); process.exit(1); @@ -88,29 +88,29 @@ export function _registerBuy(program: Command) { .option("-y, --yes", "Automatically confirm the order") .option( "-colo, --colocate ", - "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored.", + "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored." ) .option( "-q, --quote", - "Get a price quote without placing an order. Useful for scripting.", + "Get a price quote without placing an order. Useful for scripting." ) .option( "--standing", - "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately.", + "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately." ) .option( "-z, --zone ", - "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored.", + "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored." ) .option( "-c, --cluster ", - "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored.", + "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored." ) - .hook("preAction", (command) => { + .hook("preAction", command => { const { type, zone, cluster, colocate } = command.opts(); if (!type && !zone && !cluster && !colocate) { console.error( - chalk.yellow("Must specify either --type, --zone or --colocate"), + chalk.yellow("Must specify either --type, --zone or --colocate") ); command.help(); process.exit(1); @@ -118,12 +118,12 @@ export function _registerBuy(program: Command) { // let user know if they're using a zone or cluster and it's overriding the instance type if (type && (zone || cluster)) { console.warn( - `Warning: Zone '${zone}' takes precedence over instance type '${type}'`, + `Warning: Zone '${zone}' takes precedence over instance type '${type}'` ); } }) .configureHelp({ - optionDescription: (option) => { + optionDescription: option => { if (option.flags === "-h, --help") { return "Display help for buy"; } @@ -148,7 +148,7 @@ Examples: \x1b[2m# Place a standing order at a specific price\x1b[0m $ sf buy -t h100v -n 16 -d 24h -p 1.50 --standing -`, +` ) .action(function buyOrderAction(options) { /* @@ -223,16 +223,16 @@ export function QuoteComponent(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return isLoading - ? ( + return isLoading ? ( + + - - - Getting quote... - + Getting quote... - ) - : ; + + ) : ( + + ); } export function QuoteAndBuy(props: { options: SfBuyOptions }) { @@ -250,13 +250,13 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (duration) { // If duration is set, calculate end from start + duration endsAt = roundEndDate( - dayjs(coercedStart).add(duration, "seconds").toDate(), + dayjs(coercedStart).add(duration, "seconds").toDate() ); } else if (end) { endsAt = end; props.options.duration = dayjs(endsAt).diff( dayjs(coercedStart), - "seconds", + "seconds" ); } else { throw new Error("Either duration or end must be set"); @@ -266,7 +266,7 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { const quote = await getQuoteFromParsedSfBuyOptions(props.options); if (!quote) { return logAndQuit( - "No quote found for the desired order. Try with a different start date, duration, or price.", + "No quote found for the desired order. Try with a different start date, duration, or price." ); } @@ -311,22 +311,22 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return orderProps === null - ? ( + return orderProps === null ? ( + + - - - Getting quote... - + Getting quote... - ) - : ; + + ) : ( + + ); } export function getTotalPrice( pricePerGpuHour: number, size: number, - durationInHours: number, + durationInHours: number ) { return Math.ceil(pricePerGpuHour * size * GPUS_PER_NODE * durationInHours); } @@ -346,11 +346,11 @@ function BuyOrderPreview(props: BuyOrderProps) { const realDurationHours = realDuration / 3600 / 1000; const realDurationString = ms(realDuration); - const totalPrice = getTotalPrice(props.price, props.size, realDurationHours) / - 100; + const totalPrice = + getTotalPrice(props.price, props.size, realDurationHours) / 100; - const isSupportedType = typeof props.type === "string" && - props.type in InstanceTypeMetadata; + const isSupportedType = + typeof props.type === "string" && props.type in InstanceTypeMetadata; const typeLabel = isSupportedType ? InstanceTypeMetadata[props.type!]?.displayName : props.type; @@ -451,7 +451,7 @@ function BuyOrder(props: BuyOrderProps) { const [order, setOrder] = useState(null); const [loadingMsg, setLoadingMsg] = useState( - "Placing order...", + "Placing order..." ); const submitOrder = useCallback(async () => { @@ -465,7 +465,7 @@ function BuyOrder(props: BuyOrderProps) { totalPriceInCents: getTotalPrice( props.price, props.size, - realDurationInHours, + realDurationInHours ), startsAt: props.startAt, endsAt, @@ -481,12 +481,12 @@ function BuyOrder(props: BuyOrderProps) { const handleSubmit = useCallback( (submitValue: boolean) => { const { startAt, endsAt } = props; - const realDurationInHours = dayjs(endsAt).diff(dayjs(startAt)) / 1000 / - 3600; + const realDurationInHours = + dayjs(endsAt).diff(dayjs(startAt)) / 1000 / 3600; const totalPriceInCents = getTotalPrice( props.price, props.size, - realDurationInHours, + realDurationInHours ); analytics.track({ @@ -533,7 +533,7 @@ function BuyOrder(props: BuyOrderProps) { }); submitOrder(); }, - [props, exit, submitOrder], + [props, exit, submitOrder] ); useEffect(() => { @@ -545,7 +545,7 @@ function BuyOrder(props: BuyOrderProps) { const o = await getOrder(order.id); if (!o) { setLoadingMsg( - "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed.", + "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed." ); // Schedule next poll setTimeout(pollForOrder, 200); @@ -684,12 +684,12 @@ export async function placeBuyOrder(options: { }) { invariant( options.totalPriceInCents === Math.ceil(options.totalPriceInCents), - "totalPriceInCents must be a whole number", + "totalPriceInCents must be a whole number" ); invariant(options.numberNodes > 0, "numberNodes must be greater than 0"); invariant( options.numberNodes === Math.ceil(options.numberNodes), - "numberNodes must be a whole number", + "numberNodes must be a whole number" ); const api = await apiClient(); @@ -714,8 +714,9 @@ export async function placeBuyOrder(options: { start_at, end_at: roundEndDate(options.endsAt).toISOString(), price: options.totalPriceInCents, - colocate_with: - (options.colocateWith ? [options.colocateWith] : []) as string[], + colocate_with: (options.colocateWith + ? [options.colocateWith] + : []) as string[], flags: { ioc: !options.standing, }, @@ -730,12 +731,12 @@ export async function placeBuyOrder(options: { case 400: { if (error?.message === "Insufficient balance") { return logAndQuit( - "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true", + "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true" ); } return logAndQuit( - `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}`, + `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}` ); } case 401: @@ -746,14 +747,14 @@ export async function placeBuyOrder(options: { return logAndQuit( `Failed to place order: ${response.status} ${response.statusText} - ${ error ? `[${error}] ` : "" - }${error?.message || "Unknown error"}`, + }${error?.message || "Unknown error"}` ); } } if (!data) { return logAndQuit( - `Failed to place order: Unexpected response from server: ${response}`, + `Failed to place order: Unexpected response from server: ${response}` ); } @@ -766,9 +767,10 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { // from the market's perspective, "NOW" means at the beginning of the next minute. // when the order duration is very short, this can cause the rate to be computed incorrectly // if we implicitly assume it to mean `new Date()`. - const coercedStartTime = startTimeOrNow === "NOW" - ? roundDateUpToNextMinute(new Date()) - : startTimeOrNow; + const coercedStartTime = + startTimeOrNow === "NOW" + ? roundDateUpToNextMinute(new Date()) + : startTimeOrNow; const durationSeconds = dayjs(quote.end_at).diff(dayjs(coercedStartTime)); const durationHours = durationSeconds / 3600 / 1000; @@ -776,9 +778,10 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { } async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { - const startsAt = options.start === "NOW" - ? "NOW" - : roundStartDate(parseStartDate(options.start)); + const startsAt = + options.start === "NOW" + ? "NOW" + : roundStartDate(parseStartDate(options.start)); const durationSeconds = options.duration ? options.duration : dayjs(options.end).diff(dayjs(parseStartDate(startsAt)), "seconds"); @@ -786,11 +789,11 @@ async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { const minDurationSeconds = Math.max( 1, - durationSeconds - Math.ceil(durationSeconds * 0.1), + durationSeconds - Math.ceil(durationSeconds * 0.1) ); const maxDurationSeconds = Math.max( durationSeconds + 3600, - durationSeconds + Math.ceil(durationSeconds * 0.1), + durationSeconds + Math.ceil(durationSeconds * 0.1) ); return await getQuote({ @@ -824,12 +827,14 @@ export async function getQuote(options: QuoteOptions) { side: "buy", instance_type: options.instanceType, quantity: options.quantity, - min_start_date: options.minStartTime === "NOW" - ? ("NOW" as const) - : options.minStartTime.toISOString(), - max_start_date: options.maxStartTime === "NOW" - ? ("NOW" as const) - : options.maxStartTime.toISOString(), + min_start_date: + options.minStartTime === "NOW" + ? ("NOW" as const) + : options.minStartTime.toISOString(), + max_start_date: + options.maxStartTime === "NOW" + ? ("NOW" as const) + : options.maxStartTime.toISOString(), min_duration: options.minDurationSeconds, max_duration: options.maxDurationSeconds, cluster: options.cluster, @@ -858,7 +863,7 @@ export async function getQuote(options: QuoteOptions) { if (!data) { return logAndQuit( - `Failed to get quote: Unexpected response from server: ${response}`, + `Failed to get quote: Unexpected response from server: ${response}` ); } From 9bcdb9a628d7a7655d51d8daa8da784e03dd02be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:43:42 -0700 Subject: [PATCH 6/9] Include delivery type in type information --- src/lib/buy/index.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index e780d4d..08878f3 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -287,12 +287,10 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (zoneMetadata) { const DeliveryTypeMetadata = { "K8s": { displayName: "Kubernetes" }, - "VM": { displayName: "Virtual Machine" }, + "VM": { displayName: "Virtual Machine" } }; - - const deliveryDisplayName = - DeliveryTypeMetadata[zoneMetadata.deliveryType]?.displayName || - zoneMetadata.deliveryType; + + const deliveryDisplayName = DeliveryTypeMetadata[zoneMetadata.deliveryType]?.displayName || zoneMetadata.deliveryType; actualType = `${deliveryDisplayName} (${zoneMetadata.hardwareType})`; } } From 4396cea274a3279b0a5642d0163716529f57c44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:44:46 -0700 Subject: [PATCH 7/9] deno fmt --- src/lib/buy/index.tsx | 159 +++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 81 deletions(-) diff --git a/src/lib/buy/index.tsx b/src/lib/buy/index.tsx index 08878f3..667def0 100644 --- a/src/lib/buy/index.tsx +++ b/src/lib/buy/index.tsx @@ -49,37 +49,37 @@ export function _registerBuy(program: Command) { .option( "-n, --accelerators ", "Number of GPUs to purchase", - val => parseAccelerators(val, "buy"), - 8 + (val) => parseAccelerators(val, "buy"), + 8, ) .option( "-d, --duration ", "Duration of reservation (rounded up to the nearest hour)", - parseDuration + parseDuration, ) .option( "-p, --price ", - "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate" + "Sets the maximize price per gpu/hr you're willing to pay. If the market rate is lower, then you'll pay the market rate", ) .option( "-s, --start ", "Start time (date, relative time like '+1d', or 'NOW')", parseStartDateOrNow, - "NOW" + "NOW", ) .addOption( new Option( "-e, --end ", - "End time (date or relative time like '+1d', rounded up to nearest hour)" + "End time (date or relative time like '+1d', rounded up to nearest hour)", ) .argParser(parseEnd) - .conflicts("duration") + .conflicts("duration"), ) - .hook("preAction", command => { + .hook("preAction", (command) => { const { duration, end } = command.opts(); if ((!duration && !end) || (!!duration && !!end)) { console.error( - chalk.yellow("Specify either --duration or --end, but not both") + chalk.yellow("Specify either --duration or --end, but not both"), ); command.help(); process.exit(1); @@ -88,29 +88,29 @@ export function _registerBuy(program: Command) { .option("-y, --yes", "Automatically confirm the order") .option( "-colo, --colocate ", - "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored." + "Colocate with existing contracts. If provided, `-t`/`--type` will be ignored.", ) .option( "-q, --quote", - "Get a price quote without placing an order. Useful for scripting." + "Get a price quote without placing an order. Useful for scripting.", ) .option( "--standing", - "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately." + "Places a standing order. Default behavior is to place an order that auto-cancels if it can't be filled immediately.", ) .option( "-z, --zone ", - "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored." + "Send into a specific zone. If provided, \`-t\`/`--type` will be ignored.", ) .option( "-c, --cluster ", - "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored." + "Send into a specific cluster (deprecated, alias for --zone). If provided, \`-t\`/`--type` will be ignored.", ) - .hook("preAction", command => { + .hook("preAction", (command) => { const { type, zone, cluster, colocate } = command.opts(); if (!type && !zone && !cluster && !colocate) { console.error( - chalk.yellow("Must specify either --type, --zone or --colocate") + chalk.yellow("Must specify either --type, --zone or --colocate"), ); command.help(); process.exit(1); @@ -118,12 +118,12 @@ export function _registerBuy(program: Command) { // let user know if they're using a zone or cluster and it's overriding the instance type if (type && (zone || cluster)) { console.warn( - `Warning: Zone '${zone}' takes precedence over instance type '${type}'` + `Warning: Zone '${zone}' takes precedence over instance type '${type}'`, ); } }) .configureHelp({ - optionDescription: option => { + optionDescription: (option) => { if (option.flags === "-h, --help") { return "Display help for buy"; } @@ -148,7 +148,7 @@ Examples: \x1b[2m# Place a standing order at a specific price\x1b[0m $ sf buy -t h100v -n 16 -d 24h -p 1.50 --standing -` +`, ) .action(function buyOrderAction(options) { /* @@ -223,16 +223,16 @@ export function QuoteComponent(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return isLoading ? ( - - + return isLoading + ? ( - Getting quote... + + + Getting quote... + - - ) : ( - - ); + ) + : ; } export function QuoteAndBuy(props: { options: SfBuyOptions }) { @@ -250,13 +250,13 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (duration) { // If duration is set, calculate end from start + duration endsAt = roundEndDate( - dayjs(coercedStart).add(duration, "seconds").toDate() + dayjs(coercedStart).add(duration, "seconds").toDate(), ); } else if (end) { endsAt = end; props.options.duration = dayjs(endsAt).diff( dayjs(coercedStart), - "seconds" + "seconds", ); } else { throw new Error("Either duration or end must be set"); @@ -266,7 +266,7 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { const quote = await getQuoteFromParsedSfBuyOptions(props.options); if (!quote) { return logAndQuit( - "No quote found for the desired order. Try with a different start date, duration, or price." + "No quote found for the desired order. Try with a different start date, duration, or price.", ); } @@ -287,10 +287,12 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { if (zoneMetadata) { const DeliveryTypeMetadata = { "K8s": { displayName: "Kubernetes" }, - "VM": { displayName: "Virtual Machine" } + "VM": { displayName: "Virtual Machine" }, }; - - const deliveryDisplayName = DeliveryTypeMetadata[zoneMetadata.deliveryType]?.displayName || zoneMetadata.deliveryType; + + const deliveryDisplayName = + DeliveryTypeMetadata[zoneMetadata.deliveryType]?.displayName || + zoneMetadata.deliveryType; actualType = `${deliveryDisplayName} (${zoneMetadata.hardwareType})`; } } @@ -309,22 +311,22 @@ export function QuoteAndBuy(props: { options: SfBuyOptions }) { })(); }, [props.options]); - return orderProps === null ? ( - - + return orderProps === null + ? ( - Getting quote... + + + Getting quote... + - - ) : ( - - ); + ) + : ; } export function getTotalPrice( pricePerGpuHour: number, size: number, - durationInHours: number + durationInHours: number, ) { return Math.ceil(pricePerGpuHour * size * GPUS_PER_NODE * durationInHours); } @@ -344,11 +346,11 @@ function BuyOrderPreview(props: BuyOrderProps) { const realDurationHours = realDuration / 3600 / 1000; const realDurationString = ms(realDuration); - const totalPrice = - getTotalPrice(props.price, props.size, realDurationHours) / 100; + const totalPrice = getTotalPrice(props.price, props.size, realDurationHours) / + 100; - const isSupportedType = - typeof props.type === "string" && props.type in InstanceTypeMetadata; + const isSupportedType = typeof props.type === "string" && + props.type in InstanceTypeMetadata; const typeLabel = isSupportedType ? InstanceTypeMetadata[props.type!]?.displayName : props.type; @@ -449,7 +451,7 @@ function BuyOrder(props: BuyOrderProps) { const [order, setOrder] = useState(null); const [loadingMsg, setLoadingMsg] = useState( - "Placing order..." + "Placing order...", ); const submitOrder = useCallback(async () => { @@ -463,7 +465,7 @@ function BuyOrder(props: BuyOrderProps) { totalPriceInCents: getTotalPrice( props.price, props.size, - realDurationInHours + realDurationInHours, ), startsAt: props.startAt, endsAt, @@ -479,12 +481,12 @@ function BuyOrder(props: BuyOrderProps) { const handleSubmit = useCallback( (submitValue: boolean) => { const { startAt, endsAt } = props; - const realDurationInHours = - dayjs(endsAt).diff(dayjs(startAt)) / 1000 / 3600; + const realDurationInHours = dayjs(endsAt).diff(dayjs(startAt)) / 1000 / + 3600; const totalPriceInCents = getTotalPrice( props.price, props.size, - realDurationInHours + realDurationInHours, ); analytics.track({ @@ -531,7 +533,7 @@ function BuyOrder(props: BuyOrderProps) { }); submitOrder(); }, - [props, exit, submitOrder] + [props, exit, submitOrder], ); useEffect(() => { @@ -543,7 +545,7 @@ function BuyOrder(props: BuyOrderProps) { const o = await getOrder(order.id); if (!o) { setLoadingMsg( - "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed." + "Can't find order. This could be a network issue, try ctrl-c and running 'sf orders ls' to see if it was placed.", ); // Schedule next poll setTimeout(pollForOrder, 200); @@ -682,12 +684,12 @@ export async function placeBuyOrder(options: { }) { invariant( options.totalPriceInCents === Math.ceil(options.totalPriceInCents), - "totalPriceInCents must be a whole number" + "totalPriceInCents must be a whole number", ); invariant(options.numberNodes > 0, "numberNodes must be greater than 0"); invariant( options.numberNodes === Math.ceil(options.numberNodes), - "numberNodes must be a whole number" + "numberNodes must be a whole number", ); const api = await apiClient(); @@ -712,9 +714,8 @@ export async function placeBuyOrder(options: { start_at, end_at: roundEndDate(options.endsAt).toISOString(), price: options.totalPriceInCents, - colocate_with: (options.colocateWith - ? [options.colocateWith] - : []) as string[], + colocate_with: + (options.colocateWith ? [options.colocateWith] : []) as string[], flags: { ioc: !options.standing, }, @@ -729,12 +730,12 @@ export async function placeBuyOrder(options: { case 400: { if (error?.message === "Insufficient balance") { return logAndQuit( - "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true" + "Order not placed. You don't have enough funds. Add funds with\n\t🏦 Bank transfer: https://sfcompute.com/dashboard?bankTransferDialogOpen=true\n\t💳 Credit card: https://sfcompute.com/dashboard?payWithCardDialogOpen=true", ); } return logAndQuit( - `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}` + `Bad Request: ${error?.message}; ${JSON.stringify(error, null, 2)}`, ); } case 401: @@ -745,14 +746,14 @@ export async function placeBuyOrder(options: { return logAndQuit( `Failed to place order: ${response.status} ${response.statusText} - ${ error ? `[${error}] ` : "" - }${error?.message || "Unknown error"}` + }${error?.message || "Unknown error"}`, ); } } if (!data) { return logAndQuit( - `Failed to place order: Unexpected response from server: ${response}` + `Failed to place order: Unexpected response from server: ${response}`, ); } @@ -765,10 +766,9 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { // from the market's perspective, "NOW" means at the beginning of the next minute. // when the order duration is very short, this can cause the rate to be computed incorrectly // if we implicitly assume it to mean `new Date()`. - const coercedStartTime = - startTimeOrNow === "NOW" - ? roundDateUpToNextMinute(new Date()) - : startTimeOrNow; + const coercedStartTime = startTimeOrNow === "NOW" + ? roundDateUpToNextMinute(new Date()) + : startTimeOrNow; const durationSeconds = dayjs(quote.end_at).diff(dayjs(coercedStartTime)); const durationHours = durationSeconds / 3600 / 1000; @@ -776,10 +776,9 @@ export function getPricePerGpuHourFromQuote(quote: NonNullable) { } async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { - const startsAt = - options.start === "NOW" - ? "NOW" - : roundStartDate(parseStartDate(options.start)); + const startsAt = options.start === "NOW" + ? "NOW" + : roundStartDate(parseStartDate(options.start)); const durationSeconds = options.duration ? options.duration : dayjs(options.end).diff(dayjs(parseStartDate(startsAt)), "seconds"); @@ -787,11 +786,11 @@ async function getQuoteFromParsedSfBuyOptions(options: SfBuyOptions) { const minDurationSeconds = Math.max( 1, - durationSeconds - Math.ceil(durationSeconds * 0.1) + durationSeconds - Math.ceil(durationSeconds * 0.1), ); const maxDurationSeconds = Math.max( durationSeconds + 3600, - durationSeconds + Math.ceil(durationSeconds * 0.1) + durationSeconds + Math.ceil(durationSeconds * 0.1), ); return await getQuote({ @@ -825,14 +824,12 @@ export async function getQuote(options: QuoteOptions) { side: "buy", instance_type: options.instanceType, quantity: options.quantity, - min_start_date: - options.minStartTime === "NOW" - ? ("NOW" as const) - : options.minStartTime.toISOString(), - max_start_date: - options.maxStartTime === "NOW" - ? ("NOW" as const) - : options.maxStartTime.toISOString(), + min_start_date: options.minStartTime === "NOW" + ? ("NOW" as const) + : options.minStartTime.toISOString(), + max_start_date: options.maxStartTime === "NOW" + ? ("NOW" as const) + : options.maxStartTime.toISOString(), min_duration: options.minDurationSeconds, max_duration: options.maxDurationSeconds, cluster: options.cluster, @@ -861,7 +858,7 @@ export async function getQuote(options: QuoteOptions) { if (!data) { return logAndQuit( - `Failed to get quote: Unexpected response from server: ${response}` + `Failed to get quote: Unexpected response from server: ${response}`, ); } From 529f789d529ad48ad1726500a053f6bb7938148e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Mon, 8 Sep 2025 10:46:23 -0700 Subject: [PATCH 8/9] deno fmt (#191) From e3d365cd46c6eea0c9ce443060da1b68ac8d0d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jepsen=20=E2=9C=A8?= <57912727+0xJepsen@users.noreply.github.com> Date: Mon, 8 Sep 2025 12:08:45 -0700 Subject: [PATCH 9/9] Merge branch 'main' of https://github.com/sfcompute/cli