From 5aa255422564c9e30e5f7e3b578bf5e7583d99c1 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Tue, 20 Jan 2026 03:17:12 -0800 Subject: [PATCH 1/5] feat: expand zones availability into 3 columns --- src/lib/zones.tsx | 184 +++++++++++++++++++++++++++++++++------------- 1 file changed, 134 insertions(+), 50 deletions(-) diff --git a/src/lib/zones.tsx b/src/lib/zones.tsx index bbaf3b4..8c589e0 100644 --- a/src/lib/zones.tsx +++ b/src/lib/zones.tsx @@ -2,6 +2,7 @@ import * as console from "node:console"; import type { Command } from "@commander-js/extra-typings"; import chalk from "chalk"; import Table from "cli-table3"; +import dayjs from "dayjs"; import { Box, render, Text } from "ink"; import { apiClient } from "../apiClient.ts"; import { isLoggedIn } from "../helpers/config.ts"; @@ -26,17 +27,77 @@ function formatDeliveryType(deliveryType: string): string { return DeliveryTypeMetadata[deliveryType]?.displayName || deliveryType; } -function getCurrentAvailableCapacity(zone: ZoneInfo): number { - const oldestShape = zone.available_capacity - ?.sort((a, b) => a.start_timestamp - b.start_timestamp) - .at(0); - if ( - oldestShape?.start_timestamp && - oldestShape.start_timestamp >= Math.floor(Date.now() / 1000) - ) { - return 0; +function getZoneCapacityMetrics(zone: ZoneInfo): { + availableNow: number; + availableWithin1Day: number; + availableWithin1Week: number; +} { + if (zone.available_capacity.length === 0) { + return { + availableNow: 0, + availableWithin1Day: 0, + availableWithin1Week: 0, + }; } - return oldestShape?.quantity ?? 0; + + const now = dayjs().startOf("hour"); + const nowTimestamp = now.unix(); + const oneDayTimestamp = now.add(1, "day").unix(); + const oneWeekTimestamp = now.add(1, "week").unix(); + + // Sort capacity windows by start_timestamp + const sortedCapacity = [...zone.available_capacity].sort( + (a, b) => a.start_timestamp - b.start_timestamp, + ); + + let availableNow = 0; + let maxWithin1Day = 0; + let maxWithin1Week = 0; + + // Iterate through each capacity window + for (const win of sortedCapacity) { + // Skip windows that have already ended + if (win.end_timestamp <= nowTimestamp) { + continue; + } + + // Early exit: windows are sorted, so if this one starts after 1 week, all subsequent ones will too + if (win.start_timestamp > oneWeekTimestamp) { + break; + } + + const quantity = win.quantity; + + // Check if window contains "now" + if ( + nowTimestamp >= win.start_timestamp && + nowTimestamp < win.end_timestamp + ) { + availableNow = quantity; + } + + // Check if window overlaps with "within 1 day" period + if ( + win.start_timestamp < oneDayTimestamp && + win.end_timestamp > nowTimestamp + ) { + maxWithin1Day = Math.max(maxWithin1Day, quantity); + } + + // Check if window overlaps with "within 1 week" period + if ( + win.start_timestamp < oneWeekTimestamp && + win.end_timestamp > nowTimestamp + ) { + maxWithin1Week = Math.max(maxWithin1Week, quantity); + } + } + + return { + availableNow, + availableWithin1Day: maxWithin1Day, + availableWithin1Week: maxWithin1Week, + }; } // Region conversion to short slugs @@ -66,8 +127,6 @@ Examples: \x1b[2m# List zones with JSON output\x1b[0m $ sf zones ls --json - -Note: This is an early access feature (v0) that may change at any time. `, ); zones @@ -81,10 +140,6 @@ Note: This is an early access feature (v0) that may change at any time. } async function listZonesAction(options: { json?: boolean }) { - console.error( - "\x1b[33mNote: This is an early access feature (v0) and may change at any time.\x1b[0m\n", - ); - const loggedIn = await isLoggedIn(); if (!loggedIn) { logLoginMessageAndQuit(); @@ -134,63 +189,92 @@ function displayZonesTable(zones: ZoneInfo[]) { return; } - // Sort zones so available ones come first, then alphabetically by name + // Sort zones by availability: now > today > 1 week, then alphabetically by name const sortedZones = [...zones].sort((a, b) => { - // Available zones first (true comes before false) - const aAvailable = getCurrentAvailableCapacity(a) > 0; - const bAvailable = getCurrentAvailableCapacity(b) > 0; - if (aAvailable !== bAvailable) { - return bAvailable ? 1 : -1; + const aMetrics = getZoneCapacityMetrics(a); + const bMetrics = getZoneCapacityMetrics(b); + + // Sort by availableNow (higher first) + if (aMetrics.availableNow !== bMetrics.availableNow) { + return bMetrics.availableNow - aMetrics.availableNow; + } + + // Break ties with availableWithin1Day (higher first) + if (aMetrics.availableWithin1Day !== bMetrics.availableWithin1Day) { + return bMetrics.availableWithin1Day - aMetrics.availableWithin1Day; } - // Then sort by name alphabetically + + // Break ties with availableWithin1Week (higher first) + if (aMetrics.availableWithin1Week !== bMetrics.availableWithin1Week) { + return bMetrics.availableWithin1Week - aMetrics.availableWithin1Week; + } + + // Finally sort by name alphabetically return a.name.localeCompare(b.name); }); const table = new Table({ - head: [ - chalk.cyan("Zone"), - chalk.cyan("Delivery Type"), - chalk.cyan("Available Nodes"), - chalk.cyan("GPU Type"), - chalk.cyan("Interconnect"), - chalk.cyan("Region"), - ], style: { head: [], border: ["gray"], }, }); + // Multi-row header: first row with "Available Nodes" spanning 3 columns + table.push([ + { content: chalk.cyan("Zone"), rowSpan: 2 }, + { content: chalk.cyan("Delivery Type"), rowSpan: 2 }, + { content: chalk.cyan("Available Nodes Starting"), colSpan: 3 }, + { content: chalk.cyan("GPU Type"), rowSpan: 2 }, + { content: chalk.cyan("Interconnect"), rowSpan: 2 }, + { content: chalk.cyan("Region"), rowSpan: 2 }, + ]); + + // Second header row with time periods + table.push([ + chalk.cyan("Now "), + chalk.cyan("Today "), + chalk.cyan("1 Week"), + ]); + sortedZones.forEach((zone) => { - const available = getCurrentAvailableCapacity(zone); - const availableNodesText = - available > 0 - ? chalk.green(available.toString()) - : chalk.red(available.toString()); + const metrics = getZoneCapacityMetrics(zone); + const formatAvailability = (value: number) => + value > 0 ? chalk.green(value.toString()) : chalk.red(value.toString()); table.push([ zone.name, formatDeliveryType(zone.delivery_type), - availableNodesText, + formatAvailability(metrics.availableNow), + formatAvailability(metrics.availableWithin1Day), + formatAvailability(metrics.availableWithin1Week), zone.hardware_type, zone.interconnect_type || "None", formatRegion(zone.region), ]); }); - const availableZones = sortedZones.filter( - (zone) => getCurrentAvailableCapacity(zone) > 0, - ); - const availableZoneName = availableZones?.[0]?.name ?? "alamo"; - console.log(table.toString()); - console.log( - `\n${chalk.gray("Use zone names when placing orders or configuring nodes.")}\n`, - ); - console.log(chalk.gray("Examples:")); - console.log(` sf buy --zone ${chalk.green(availableZoneName)}`); - console.log( - ` sf scale create -n 16 --zone ${chalk.green(availableZoneName)}`, - ); + const availableZones = sortedZones.filter((zone) => { + const metrics = getZoneCapacityMetrics(zone); + return ( + metrics.availableNow > 0 || + metrics.availableWithin1Day > 0 || + metrics.availableWithin1Week > 0 + ); + }); + if (availableZones.length > 0) { + console.log(table.toString()); + console.log( + `\n${ + chalk.gray("Use zone names when placing orders or configuring nodes.") + }\n`, + ); + console.log(chalk.gray("Examples:")); + console.log(` sf buy --zone ${chalk.green(availableZones[0].name)}`); + console.log( + ` sf scale create -n 16 --zone ${chalk.green(availableZones[0].name)}`, + ); + } } function EmptyZonesDisplay() { From 9a09bcd7c4390f0ac4a7cc1ac0be1c3114a2f435 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Wed, 21 Jan 2026 18:17:29 -0800 Subject: [PATCH 2/5] chore: nudge towards `sf nodes create` and not `sf buy` --- src/lib/contracts/ContractDisplay.tsx | 4 ++-- src/lib/orders/OrderDisplay.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/contracts/ContractDisplay.tsx b/src/lib/contracts/ContractDisplay.tsx index 05e78b7..61ddfc4 100644 --- a/src/lib/contracts/ContractDisplay.tsx +++ b/src/lib/contracts/ContractDisplay.tsx @@ -170,8 +170,8 @@ export function ContractList(props: { contracts: Contract[] }) { - # Place a buy order to get started - sf buy + # Buy a node to get started + sf nodes create ); diff --git a/src/lib/orders/OrderDisplay.tsx b/src/lib/orders/OrderDisplay.tsx index 71011cc..33e59a2 100644 --- a/src/lib/orders/OrderDisplay.tsx +++ b/src/lib/orders/OrderDisplay.tsx @@ -189,7 +189,7 @@ export function OrderDisplay(props: { # Place an order to buy compute - sf buy + sf nodes create ); From 2d6dad8db265ce6c66cca06c562c3f27142ab28c Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Wed, 21 Jan 2026 18:40:32 -0800 Subject: [PATCH 3/5] feat: compress/redesign `zones ls` command --- src/lib/zones.tsx | 492 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 393 insertions(+), 99 deletions(-) diff --git a/src/lib/zones.tsx b/src/lib/zones.tsx index 8c589e0..0038e4d 100644 --- a/src/lib/zones.tsx +++ b/src/lib/zones.tsx @@ -1,8 +1,7 @@ import * as console from "node:console"; import type { Command } from "@commander-js/extra-typings"; -import chalk from "chalk"; -import Table from "cli-table3"; import dayjs from "dayjs"; +import { differenceInMinutes, differenceInHours, differenceInDays } from "date-fns"; import { Box, render, Text } from "ink"; import { apiClient } from "../apiClient.ts"; import { isLoggedIn } from "../helpers/config.ts"; @@ -16,31 +15,22 @@ import { isFeatureEnabled } from "./posthog.ts"; type ZoneInfo = components["schemas"]["node-api_ZoneInfo"]; -// Delivery type conversion similar to InstanceTypeMetadata pattern -const DeliveryTypeMetadata: Record = { - K8s: { displayName: "Kubernetes" }, - K8sNamespace: { displayName: "Kubernetes" }, - VM: { displayName: "Virtual Machine" }, -} as const; - -function formatDeliveryType(deliveryType: string): string { - return DeliveryTypeMetadata[deliveryType]?.displayName || deliveryType; -} - -function getZoneCapacityMetrics(zone: ZoneInfo): { +type CapacityMetrics = { availableNow: number; - availableWithin1Day: number; - availableWithin1Week: number; -} { + availableWithin1Day: { count: number; startTimestamp: number | null }; + availableWithin1Week: { count: number; startTimestamp: number | null }; +}; + +function getZoneCapacityMetrics(zone: ZoneInfo): CapacityMetrics { if (zone.available_capacity.length === 0) { return { availableNow: 0, - availableWithin1Day: 0, - availableWithin1Week: 0, + availableWithin1Day: { count: 0, startTimestamp: null }, + availableWithin1Week: { count: 0, startTimestamp: null }, }; } - const now = dayjs().startOf("hour"); + const now = dayjs(); const nowTimestamp = now.unix(); const oneDayTimestamp = now.add(1, "day").unix(); const oneWeekTimestamp = now.add(1, "week").unix(); @@ -52,7 +42,9 @@ function getZoneCapacityMetrics(zone: ZoneInfo): { let availableNow = 0; let maxWithin1Day = 0; + let maxWithin1DayStart: number | null = null; let maxWithin1Week = 0; + let maxWithin1WeekStart: number | null = null; // Iterate through each capacity window for (const win of sortedCapacity) { @@ -81,7 +73,10 @@ function getZoneCapacityMetrics(zone: ZoneInfo): { win.start_timestamp < oneDayTimestamp && win.end_timestamp > nowTimestamp ) { - maxWithin1Day = Math.max(maxWithin1Day, quantity); + if (quantity > maxWithin1Day) { + maxWithin1Day = quantity; + maxWithin1DayStart = Math.max(win.start_timestamp, nowTimestamp); + } } // Check if window overlaps with "within 1 week" period @@ -89,28 +84,56 @@ function getZoneCapacityMetrics(zone: ZoneInfo): { win.start_timestamp < oneWeekTimestamp && win.end_timestamp > nowTimestamp ) { - maxWithin1Week = Math.max(maxWithin1Week, quantity); + if (quantity > maxWithin1Week) { + maxWithin1Week = quantity; + maxWithin1WeekStart = Math.max(win.start_timestamp, nowTimestamp); + } } } return { availableNow, - availableWithin1Day: maxWithin1Day, - availableWithin1Week: maxWithin1Week, + availableWithin1Day: { count: maxWithin1Day, startTimestamp: maxWithin1DayStart }, + availableWithin1Week: { count: maxWithin1Week, startTimestamp: maxWithin1WeekStart }, }; } // Region conversion to short slugs const RegionMetadata: Record = { - NorthAmerica: { slug: "North America" }, - AsiaPacific: { slug: "Asia" }, - EuropeMiddleEastAfrica: { slug: "EMEA" }, + NorthAmerica: { slug: "north america" }, + AsiaPacific: { slug: "asia" }, + EuropeMiddleEastAfrica: { slug: "emea" }, } as const; function formatRegion(region: string): string { return RegionMetadata[region]?.slug || region; } +// Bright colors not used elsewhere in this component +const REGION_COLORS = [ + "green", + "yellow", + "blueBright", + "magentaBright", + "cyanBright", +] as const; + +// Simple deterministic hash for consistent color assignment +function hashString(str: string): number { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32bit integer + } + return Math.abs(hash); +} + +function getRegionColor(region: string): (typeof REGION_COLORS)[number] { + const hash = hashString(region); + return REGION_COLORS[hash % REGION_COLORS.length]; +} + export async function registerZones(program: Command) { const isEnabled = await isFeatureEnabled("zones"); if (!isEnabled) return; @@ -122,9 +145,12 @@ export async function registerZones(program: Command) { "after", ` Examples: - \x1b[2m# List all zones\x1b[0m + \x1b[2m# List zones with availability\x1b[0m $ sf zones ls + \x1b[2m# List all zones, including those with no availability\x1b[0m + $ sf zones ls --all + \x1b[2m# List zones with JSON output\x1b[0m $ sf zones ls --json `, @@ -133,13 +159,14 @@ Examples: .command("list") .alias("ls") .description("List all zones") + .option("--all", "Show all zones, including those with no availability") .option("--json", "Output in JSON format") .action(async (options) => { await listZonesAction(options); }); } -async function listZonesAction(options: { json?: boolean }) { +async function listZonesAction(options: { all?: boolean; json?: boolean }) { const loggedIn = await isLoggedIn(); if (!loggedIn) { logLoginMessageAndQuit(); @@ -180,17 +207,306 @@ async function listZonesAction(options: { json?: boolean }) { return; } - displayZonesTable(filteredZones); + await displayZonesTable(filteredZones, { showAll: options.all }); +} + +function hasAvailability(zone: ZoneInfo): boolean { + const metrics = getZoneCapacityMetrics(zone); + return ( + metrics.availableNow > 0 || + metrics.availableWithin1Day.count > 0 || + metrics.availableWithin1Week.count > 0 + ); +} + + +function formatShortDistance(date: Date): string { + const now = new Date(); + const minutes = differenceInMinutes(date, now); + const hours = differenceInHours(date, now); + const days = differenceInDays(date, now); + + if (days >= 7) { + const weeks = Math.floor(days / 7); + return `${weeks}w`; + } + if (days >= 1) { + return `${days}d`; + } + if (hours >= 1) { + return `${hours}h`; + } + return `${minutes}m`; +} + +function AvailabilityDisplay({ + count, + startTimestamp, + isNow = false, + padWidth = 1, +}: { + count: number; + startTimestamp: number | null; + isNow?: boolean; + padWidth?: number; +}) { + const countStr = String(count).padStart(padWidth); + + if (isNow && count === 0) { + return sold out; + } + + if (isNow) { + return ( + <> + {countStr} + {" now "} + + ); + } + + if (count === 0 || startTimestamp === null) { + return ( + <> + {countStr} + {" now "} + + ); + } + + const startDate = new Date(startTimestamp * 1000); + const now = new Date(); + const minutesUntil = differenceInMinutes(startDate, now); + + // If available now or within 1 minute, show "now" + if (minutesUntil <= 1) { + return ( + <> + {countStr} + {" now "} + + ); + } + + const distance = formatShortDistance(startDate).padEnd(3); + return ( + <> + {countStr} + {` in ${distance}`} + + ); +} + +// Column widths +const COL = { + gpu: 6, + region: 15, + now: 11, + soonest: 11, + max: 11, +}; + +function ZonesTableDisplay({ + zones, + hiddenCount, + firstAvailableZone, + firstAvailableZoneStartTime, +}: { + zones: ZoneInfo[]; + hiddenCount: number; + firstAvailableZone: string | null; + firstAvailableZoneStartTime: string | null; +}) { + // Calculate dynamic zone column width based on longest zone name + const zoneWidth = Math.max( + 6, // minimum width for "zone" header + padding + ...zones.map((z) => z.name.length + 2), // +2 for padding + ); + + // Pre-calculate metrics for all zones to determine padding widths + const allMetrics = zones.map((z) => getZoneCapacityMetrics(z)); + + // Calculate max values for each column to determine padding + const maxNow = Math.max(1, ...allMetrics.map((m) => m.availableNow)); + const maxToday = Math.max(1, ...allMetrics.map((m) => m.availableWithin1Day.count)); + const maxWeek = Math.max(1, ...allMetrics.map((m) => m.availableWithin1Week.count)); + + // Calculate pad widths (number of digits needed) + const nowPadWidth = String(maxNow).length; + const todayPadWidth = String(maxToday).length; + const weekPadWidth = String(maxWeek).length; + + return ( + + + {/* Header row */} + + + zone + (slug) + + + gpu + + + region + + + nodes + now + + + + soonest + + + + max + + + + {/* Body rows */} + {zones.map((zone, idx) => { + const metrics = allMetrics[idx]; + const allSoldOut = + metrics.availableNow === 0 && + metrics.availableWithin1Day.count === 0 && + metrics.availableWithin1Week.count === 0; + + // Total width of node columns: now(11) + separator(1) + soonest(11) + separator(1) + max(11) = 35 + const nodeColsWidth = COL.now + 1 + COL.soonest + 1 + COL.max; + const soldOutText = "sold out"; + const dashCount = Math.floor((nodeColsWidth - soldOutText.length - 2) / 2); + const soldOutDisplay = `${"-".repeat(dashCount)} ${soldOutText} ${"-".repeat(dashCount)}`; + + return ( + + + {zone.name} + + + {zone.hardware_type} + + + {formatRegion(zone.region)} + + {allSoldOut ? ( + + {soldOutDisplay} + + ) : ( + <> + + + + + + + + + + + + + )} + + ); + })} + + {/* Footer */} + {hiddenCount > 0 && ( + + + {hiddenCount} sold-out zones hidden. Use{" "} + + sf zones ls + --all + to show. + + )} + + + {/* Examples */} + {firstAvailableZone && ( + + + Use zone names when launching nodes. + + + Examples: + + {" "}sf nodes create --zone {firstAvailableZone} + {firstAvailableZoneStartTime && ( + <> + {" "}-s {firstAvailableZoneStartTime} + + )} + + + + )} + + ); } -function displayZonesTable(zones: ZoneInfo[]) { +async function displayZonesTable( + zones: ZoneInfo[], + options: { showAll?: boolean } = {}, +) { if (zones.length === 0) { - render(); + const { waitUntilExit } = render(); + await waitUntilExit(); + return; + } + + // Separate zones with and without availability + const zonesWithAvailability = zones.filter(hasAvailability); + const zonesWithoutAvailability = zones.filter((z) => !hasAvailability(z)); + + if (zonesWithAvailability.length === 0) { + const { waitUntilExit } = render(); + await waitUntilExit(); return; } + // Determine which zones to display + const zonesToDisplay = options.showAll ? zones : zonesWithAvailability; + // Sort zones by availability: now > today > 1 week, then alphabetically by name - const sortedZones = [...zones].sort((a, b) => { + const sortedZones = [...zonesToDisplay].sort((a, b) => { const aMetrics = getZoneCapacityMetrics(a); const bMetrics = getZoneCapacityMetrics(b); @@ -200,90 +516,68 @@ function displayZonesTable(zones: ZoneInfo[]) { } // Break ties with availableWithin1Day (higher first) - if (aMetrics.availableWithin1Day !== bMetrics.availableWithin1Day) { - return bMetrics.availableWithin1Day - aMetrics.availableWithin1Day; + if ( + aMetrics.availableWithin1Day.count !== bMetrics.availableWithin1Day.count + ) { + return ( + bMetrics.availableWithin1Day.count - aMetrics.availableWithin1Day.count + ); } // Break ties with availableWithin1Week (higher first) - if (aMetrics.availableWithin1Week !== bMetrics.availableWithin1Week) { - return bMetrics.availableWithin1Week - aMetrics.availableWithin1Week; + if ( + aMetrics.availableWithin1Week.count !== bMetrics.availableWithin1Week.count + ) { + return ( + bMetrics.availableWithin1Week.count - aMetrics.availableWithin1Week.count + ); } // Finally sort by name alphabetically return a.name.localeCompare(b.name); }); - const table = new Table({ - style: { - head: [], - border: ["gray"], - }, - }); - - // Multi-row header: first row with "Available Nodes" spanning 3 columns - table.push([ - { content: chalk.cyan("Zone"), rowSpan: 2 }, - { content: chalk.cyan("Delivery Type"), rowSpan: 2 }, - { content: chalk.cyan("Available Nodes Starting"), colSpan: 3 }, - { content: chalk.cyan("GPU Type"), rowSpan: 2 }, - { content: chalk.cyan("Interconnect"), rowSpan: 2 }, - { content: chalk.cyan("Region"), rowSpan: 2 }, - ]); - - // Second header row with time periods - table.push([ - chalk.cyan("Now "), - chalk.cyan("Today "), - chalk.cyan("1 Week"), - ]); - - sortedZones.forEach((zone) => { - const metrics = getZoneCapacityMetrics(zone); - const formatAvailability = (value: number) => - value > 0 ? chalk.green(value.toString()) : chalk.red(value.toString()); - - table.push([ - zone.name, - formatDeliveryType(zone.delivery_type), - formatAvailability(metrics.availableNow), - formatAvailability(metrics.availableWithin1Day), - formatAvailability(metrics.availableWithin1Week), - zone.hardware_type, - zone.interconnect_type || "None", - formatRegion(zone.region), - ]); - }); - - const availableZones = sortedZones.filter((zone) => { - const metrics = getZoneCapacityMetrics(zone); - return ( - metrics.availableNow > 0 || - metrics.availableWithin1Day > 0 || - metrics.availableWithin1Week > 0 - ); - }); - if (availableZones.length > 0) { - console.log(table.toString()); - console.log( - `\n${ - chalk.gray("Use zone names when placing orders or configuring nodes.") - }\n`, - ); - console.log(chalk.gray("Examples:")); - console.log(` sf buy --zone ${chalk.green(availableZones[0].name)}`); - console.log( - ` sf scale create -n 16 --zone ${chalk.green(availableZones[0].name)}`, - ); + const hiddenCount = options.showAll ? 0 : zonesWithoutAvailability.length; + const firstAvailableZone = + zonesWithAvailability.length > 0 ? zonesWithAvailability[0].name : null; + + // Calculate earliest start time for first available zone + let firstAvailableZoneStartTime: string | null = null; + if (zonesWithAvailability.length > 0) { + const metrics = getZoneCapacityMetrics(zonesWithAvailability[0]); + if (metrics.availableNow > 0) { + firstAvailableZoneStartTime = "now"; + } else if (metrics.availableWithin1Day.startTimestamp) { + firstAvailableZoneStartTime = dayjs + .unix(metrics.availableWithin1Day.startTimestamp) + .toISOString(); + } else if (metrics.availableWithin1Week.startTimestamp) { + firstAvailableZoneStartTime = dayjs + .unix(metrics.availableWithin1Week.startTimestamp) + .toISOString(); + } } + + const { waitUntilExit } = render( + , + ); + await waitUntilExit(); } function EmptyZonesDisplay() { return ( - No zones found. + No zones with availability found. # Check back later for available zones sf zones ls + # To show all zones, including those with no availability + sf zones ls --all ); From 4160a4bddbba705e15e8b23335b5e9ed9b270899 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Wed, 21 Jan 2026 18:44:35 -0800 Subject: [PATCH 4/5] style: run biome --- src/lib/zones.tsx | 100 +++++++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/src/lib/zones.tsx b/src/lib/zones.tsx index 0038e4d..5b1785e 100644 --- a/src/lib/zones.tsx +++ b/src/lib/zones.tsx @@ -1,7 +1,11 @@ import * as console from "node:console"; import type { Command } from "@commander-js/extra-typings"; +import { + differenceInDays, + differenceInHours, + differenceInMinutes, +} from "date-fns"; import dayjs from "dayjs"; -import { differenceInMinutes, differenceInHours, differenceInDays } from "date-fns"; import { Box, render, Text } from "ink"; import { apiClient } from "../apiClient.ts"; import { isLoggedIn } from "../helpers/config.ts"; @@ -93,8 +97,14 @@ function getZoneCapacityMetrics(zone: ZoneInfo): CapacityMetrics { return { availableNow, - availableWithin1Day: { count: maxWithin1Day, startTimestamp: maxWithin1DayStart }, - availableWithin1Week: { count: maxWithin1Week, startTimestamp: maxWithin1WeekStart }, + availableWithin1Day: { + count: maxWithin1Day, + startTimestamp: maxWithin1DayStart, + }, + availableWithin1Week: { + count: maxWithin1Week, + startTimestamp: maxWithin1WeekStart, + }, }; } @@ -123,7 +133,7 @@ function hashString(str: string): number { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; + hash = (hash << 5) - hash + char; hash = hash & hash; // Convert to 32bit integer } return Math.abs(hash); @@ -219,7 +229,6 @@ function hasAvailability(zone: ZoneInfo): boolean { ); } - function formatShortDistance(date: Date): string { const now = new Date(); const minutes = differenceInMinutes(date, now); @@ -253,7 +262,11 @@ function AvailabilityDisplay({ const countStr = String(count).padStart(padWidth); if (isNow && count === 0) { - return sold out; + return ( + + sold out + + ); } if (isNow) { @@ -268,7 +281,12 @@ function AvailabilityDisplay({ if (count === 0 || startTimestamp === null) { return ( <> - {countStr} + + {countStr} + {" now "} ); @@ -325,12 +343,18 @@ function ZonesTableDisplay({ // Pre-calculate metrics for all zones to determine padding widths const allMetrics = zones.map((z) => getZoneCapacityMetrics(z)); - + // Calculate max values for each column to determine padding const maxNow = Math.max(1, ...allMetrics.map((m) => m.availableNow)); - const maxToday = Math.max(1, ...allMetrics.map((m) => m.availableWithin1Day.count)); - const maxWeek = Math.max(1, ...allMetrics.map((m) => m.availableWithin1Week.count)); - + const maxToday = Math.max( + 1, + ...allMetrics.map((m) => m.availableWithin1Day.count), + ); + const maxWeek = Math.max( + 1, + ...allMetrics.map((m) => m.availableWithin1Week.count), + ); + // Calculate pad widths (number of digits needed) const nowPadWidth = String(maxNow).length; const todayPadWidth = String(maxToday).length; @@ -368,11 +392,21 @@ function ZonesTableDisplay({ now - + soonest - + max @@ -388,7 +422,9 @@ function ZonesTableDisplay({ // Total width of node columns: now(11) + separator(1) + soonest(11) + separator(1) + max(11) = 35 const nodeColsWidth = COL.now + 1 + COL.soonest + 1 + COL.max; const soldOutText = "sold out"; - const dashCount = Math.floor((nodeColsWidth - soldOutText.length - 2) / 2); + const dashCount = Math.floor( + (nodeColsWidth - soldOutText.length - 2) / 2, + ); const soldOutDisplay = `${"-".repeat(dashCount)} ${soldOutText} ${"-".repeat(dashCount)}`; return ( @@ -400,7 +436,9 @@ function ZonesTableDisplay({ {zone.hardware_type} - {formatRegion(zone.region)} + + {formatRegion(zone.region)} + {allSoldOut ? ( @@ -420,7 +458,9 @@ function ZonesTableDisplay({ @@ -428,7 +468,9 @@ function ZonesTableDisplay({ @@ -449,9 +491,7 @@ function ZonesTableDisplay({ borderColor="gray" paddingLeft={1} > - - {hiddenCount} sold-out zones hidden. Use{" "} - + {hiddenCount} sold-out zones hidden. Use sf zones ls --all to show. @@ -462,16 +502,16 @@ function ZonesTableDisplay({ {/* Examples */} {firstAvailableZone && ( - - Use zone names when launching nodes. - + Use zone names when launching nodes. Examples: - {" "}sf nodes create --zone {firstAvailableZone} + {" "}sf nodes create --zone{" "} + {firstAvailableZone} {firstAvailableZoneStartTime && ( <> - {" "}-s {firstAvailableZoneStartTime} + {" "} + -s {firstAvailableZoneStartTime} )} @@ -526,10 +566,12 @@ async function displayZonesTable( // Break ties with availableWithin1Week (higher first) if ( - aMetrics.availableWithin1Week.count !== bMetrics.availableWithin1Week.count + aMetrics.availableWithin1Week.count !== + bMetrics.availableWithin1Week.count ) { return ( - bMetrics.availableWithin1Week.count - aMetrics.availableWithin1Week.count + bMetrics.availableWithin1Week.count - + aMetrics.availableWithin1Week.count ); } @@ -576,7 +618,9 @@ function EmptyZonesDisplay() { # Check back later for available zones sf zones ls - # To show all zones, including those with no availability + + # To show all zones, including those with no availability + sf zones ls --all From 5b4e57934b79433a61ef5d69ac97f306d3cd850b Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Wed, 21 Jan 2026 18:49:40 -0800 Subject: [PATCH 5/5] chore: update docs to the absolute state of things --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b778a2e..56de1cb 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ Releases are managed through GitHub Actions. To create a new release: 3. Click on the "Release" workflow 4. Click "Run workflow" 5. Select the version bump type: - - `patch`: for backwards-compatible bug fixes (0.0.x) - - `minor`: for backwards-compatible new features (0.x.0) - - `major`: for breaking changes (x.0.0) + - `patch`: for backwards-compatible changes (0.0.x) + - `minor`: for breaking changes (0.x.0) + - `major`: for major breaking changes (x.0.0) - `prerelease`: for pre-release versions (0.0.0-pre.timestamp) 6. Click "Run workflow" to start the release process