From 4548437882d626fba16520d9f3ff20dd3b4374ea Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Fri, 5 Dec 2025 18:30:05 -0800 Subject: [PATCH 01/10] build: bump package version to alpha.27 --- deno.lock | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deno.lock b/deno.lock index a77c69c9..38e1b38e 100644 --- a/deno.lock +++ b/deno.lock @@ -107,7 +107,7 @@ "@inquirer/figures", "@inquirer/type@2.0.0", "@types/mute-stream", - "@types/node@22.19.1", + "@types/node@22.19.2", "@types/wrap-ansi", "ansi-escapes@4.3.2", "cli-width", @@ -225,8 +225,8 @@ "@types/node@24.2.0" ] }, - "@types/node@22.19.1": { - "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", + "@types/node@22.19.2": { + "integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==", "dependencies": [ "undici-types@6.21.0" ] diff --git a/package.json b/package.json index 3258e2f3..822bbe8d 100644 --- a/package.json +++ b/package.json @@ -55,4 +55,4 @@ "chalk": "^5.6.2" }, "version": "0.26.0" -} +} \ No newline at end of file From 76a4c4015b2de9f4df11a1a6b92161ef080dc384 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Fri, 5 Dec 2025 18:30:50 -0800 Subject: [PATCH 02/10] style: run deno fmt --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 822bbe8d..3258e2f3 100644 --- a/package.json +++ b/package.json @@ -55,4 +55,4 @@ "chalk": "^5.6.2" }, "version": "0.26.0" -} \ No newline at end of file +} From c842f49906fbf6145243627298736823ffbe2378 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Fri, 5 Dec 2025 17:17:10 -0800 Subject: [PATCH 03/10] feat: [WIP] nodes ls --verbose rework --- src/lib/nodes/list.tsx | 77 +++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/src/lib/nodes/list.tsx b/src/lib/nodes/list.tsx index 815ed5f1..dfa8d55f 100644 --- a/src/lib/nodes/list.tsx +++ b/src/lib/nodes/list.tsx @@ -9,6 +9,7 @@ import advanced from "dayjs/plugin/advancedFormat"; import timezone from "dayjs/plugin/timezone"; import { Box, render, Text } from "ink"; import type { SFCNodes } from "@sfcompute/nodes-sdk-alpha"; +import { formatDuration, intervalToDuration } from "date-fns"; import { getAuthToken } from "../../helpers/config.ts"; import { logAndQuit } from "../../helpers/errors.ts"; @@ -27,6 +28,7 @@ import { jsonOption, pluralizeNodes, printNodeType, + printVMStatus, } from "./utils.ts"; dayjs.extend(utc); @@ -63,7 +65,7 @@ function VMTable({ vms }: { vms: NonNullable["data"] }) { borderColor="gray" > - Virtual Machines + Previous VMs Status @@ -268,12 +270,24 @@ function getActionsForNode(node: SFCNodes.Node) { // Component for displaying a single node in verbose format function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { // Convert Unix timestamps to dates and calculate duration - const startDate = node.start_at && dayjs.unix(node.start_at); - const endDate = node.end_at && dayjs.unix(node.end_at); + const startDate = node.start_at ? dayjs.unix(node.start_at) : null; + const endDate = node.end_at ? dayjs.unix(node.end_at) : null; let duration = endDate && startDate && endDate.diff(startDate, "hours"); if (typeof duration === "number" && duration < 1) { duration = 1; } + const durationLabel = duration + ? formatDuration( + intervalToDuration({ + start: 0, + end: duration * 60 * 60 * 1000, + }), + { + delimiter: ", ", + format: ["years", "months", "weeks", "days", "hours"], + }, + ) + : null; // Convert max_price_per_node_hour from cents to dollars const pricePerHour = node.max_price_per_node_hour ? (node.max_price_per_node_hour / 100) @@ -285,6 +299,8 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { // Get available actions for this node const nodeActions = getActionsForNode(node); + const lastVM = getLastVM(node); + return ( + {lastVM && ( + <> + + Active VM: + + + + + + + + + + )} + + {node.vms?.data && node.vms.data.length > 1 && ( + + + + + + )} + - 📅 Schedule: + Schedule: @@ -354,7 +403,7 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { {duration && ( )} @@ -362,7 +411,7 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { {node.max_price_per_node_hour && ( <> - 💰 Pricing: + Pricing: {node.node_type === "autoreserved" && ( @@ -392,22 +441,10 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { )} - {/* VMs Section - Show if node has VMs */} - {node.vms?.data && node.vms.data.length > 0 && ( - - - 💿 - - - - - - )} - {node.vms?.data?.[0]?.image_id && ( <> - 💾 Current VM Image: + Current VM Image: 0 && ( <> - 🎯 Actions: + Actions: {nodeActions.map((action, index) => ( From 96fef064855584f1c090fa0bc1d1d6be3db5bf01 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Mon, 8 Dec 2025 19:28:33 -0800 Subject: [PATCH 04/10] chore: improve display of current VM data --- src/lib/nodes/list.tsx | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/lib/nodes/list.tsx b/src/lib/nodes/list.tsx index dfa8d55f..99217369 100644 --- a/src/lib/nodes/list.tsx +++ b/src/lib/nodes/list.tsx @@ -347,16 +347,35 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { - + )} From ecc4334740e9efe06f6e553c03098deefcb49e65 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Mon, 8 Dec 2025 19:32:10 -0800 Subject: [PATCH 05/10] feat: display when an `anywhere` node lands on a zone --- src/lib/nodes/list.tsx | 4 +++- src/lib/nodes/utils.ts | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/lib/nodes/list.tsx b/src/lib/nodes/list.tsx index 99217369..f5398550 100644 --- a/src/lib/nodes/list.tsx +++ b/src/lib/nodes/list.tsx @@ -330,7 +330,9 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { diff --git a/src/lib/nodes/utils.ts b/src/lib/nodes/utils.ts index 047f5060..b4d80139 100644 --- a/src/lib/nodes/utils.ts +++ b/src/lib/nodes/utils.ts @@ -144,7 +144,12 @@ export function createNodesTable( getStatusColor(node.status), lastVm?.id ?? "", node.gpu_type, - node.zone || (node.node_type === "autoreserved" ? "Any matching" : "N/A"), + node.zone || + (node.node_type === "autoreserved" + ? (lastVm?.zone + ? `Any matching (${brightBlack(lastVm.zone)})` + : "Any matching") + : "N/A"), startEnd, maxPrice, ]); From 625f4142c2a8739e147dc52d66b06218e30906c4 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Mon, 8 Dec 2025 19:32:38 -0800 Subject: [PATCH 06/10] refactor: restructure VMTable to support flex grow/shrink --- src/lib/nodes/list.tsx | 150 ++++++++++++++++++++++++++++------------- 1 file changed, 104 insertions(+), 46 deletions(-) diff --git a/src/lib/nodes/list.tsx b/src/lib/nodes/list.tsx index f5398550..35bfb11f 100644 --- a/src/lib/nodes/list.tsx +++ b/src/lib/nodes/list.tsx @@ -1,6 +1,6 @@ import React from "react"; import { Command, Option } from "@commander-js/extra-typings"; -import { brightBlack, gray } from "jsr:@std/fmt/colors"; +import { brightBlack, cyan, gray } from "jsr:@std/fmt/colors"; import console from "node:console"; import ora from "ora"; import dayjs from "dayjs"; @@ -28,7 +28,6 @@ import { jsonOption, pluralizeNodes, printNodeType, - printVMStatus, } from "./utils.ts"; dayjs.extend(utc); @@ -53,52 +52,111 @@ function VMTable({ vms }: { vms: NonNullable["data"] }) { return ( - {/* Header */} - - - Previous VMs - - - Status - - - Start/End - - - - {/* VM rows */} - {vmsToShow.map((vm) => { - const startDate = vm.start_at ? dayjs.unix(vm.start_at) : null; - const endDate = vm.end_at ? dayjs.unix(vm.end_at) : null; - const startEnd = formatNullableDateRange(startDate, endDate); - - return ( - - + {/* Build table as columns */} + + {/* Column 1: VM IDs */} + + + Previous VMs + + {vmsToShow.map((vm) => ( + {vm.id} - + ))} + + + {/* Column 2: Status */} + + + Status + + {vmsToShow.map((vm) => ( + {getVMStatusColor(vm.status)} - - {startEnd} + ))} + + + {/* Column 3: Zone */} + + + Zone + + {vmsToShow.map((vm) => ( + + {vm.zone} + ))} + + + {/* Column 4: Start/End */} + + + Start/End - ); - })} + {vmsToShow.map((vm) => { + const startDate = vm.start_at ? dayjs.unix(vm.start_at) : null; + const endDate = vm.end_at ? dayjs.unix(vm.end_at) : null; + const startEnd = formatNullableDateRange(startDate, endDate); + return ( + + {startEnd} + + ); + })} + + {/* Show message if there are more VMs */} {remainingVms > 0 && ( - + {remainingVms} past {remainingVms === 1 ? "VM" : "VMs"} not shown. @@ -329,11 +387,11 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { @@ -343,7 +401,7 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { Active VM: - + Schedule: - + Pricing: - + {node.node_type === "autoreserved" && ( <> Current VM Image: - + Actions: - + {nodeActions.map((action, index) => ( Date: Mon, 8 Dec 2025 19:32:53 -0800 Subject: [PATCH 07/10] build: regen lockfile --- deno.lock | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/deno.lock b/deno.lock index 38e1b38e..5253b3e0 100644 --- a/deno.lock +++ b/deno.lock @@ -2,6 +2,7 @@ "version": "5", "specifiers": { "jsr:@std/assert@1.0.11": "1.0.11", + "jsr:@std/fmt@*": "1.0.8", "jsr:@std/internal@^1.0.5": "1.0.12", "npm:@commander-js/extra-typings@^12.1.0": "12.1.0_commander@12.1.0", "npm:@inkjs/ui@2": "2.0.0_ink@5.2.1__@types+react@18.3.27__react@18.3.1_@types+react@18.3.27_react@18.3.1", @@ -13,9 +14,11 @@ "npm:@types/semver@^7.5.8": "7.7.1", "npm:async-retry@^1.3.3": "1.3.3", "npm:axios@^1.8.4": "1.13.2", + "npm:boxen@8.0.1": "8.0.1", "npm:boxen@^8.0.1": "8.0.1", "npm:chrono-node@^2.9.0": "2.9.0", "npm:cli-progress@^3.12.0": "3.12.0", + "npm:cli-spinners@*": "3.3.0", "npm:cli-table3@0.6.5": "0.6.5", "npm:commander@^12.1.0": "12.1.0", "npm:date-fns@^4.1.0": "4.1.0", @@ -39,9 +42,12 @@ "npm:semver@^7.6.3": "7.7.3", "npm:shescape@^2.1.1": "2.1.7", "npm:tiny-invariant@^1.3.3": "1.3.3", + "npm:tweetnacl-util@*": "0.15.1", "npm:tweetnacl-util@~0.15.1": "0.15.1", + "npm:tweetnacl@*": "1.0.3", "npm:tweetnacl@^1.0.3": "1.0.3", - "npm:yaml@2.6.1": "2.6.1" + "npm:yaml@2.6.1": "2.6.1", + "npm:yn@*": "5.1.0" }, "jsr": { "@std/assert@1.0.11": { @@ -50,6 +56,9 @@ "jsr:@std/internal" ] }, + "@std/fmt@1.0.8": { + "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" + }, "@std/internal@1.0.12": { "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" } @@ -1033,6 +1042,9 @@ "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "bin": true }, + "yn@5.1.0": { + "integrity": "sha512-TfXLvT6eVsBNIm8rAXTwJYdQFtOXaHQ+rA7LU8HL8C/BFfaSfhvFE5T1rHAdBCbAj808HaqjXVkmo8jmeGOqhw==" + }, "yoctocolors-cjs@2.1.3": { "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==" }, From 38dbc77af5f2921afae5e31aedcd0b943f60fbd6 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Mon, 8 Dec 2025 19:34:52 -0800 Subject: [PATCH 08/10] chore: delete redundant image_id section --- src/lib/nodes/list.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/lib/nodes/list.tsx b/src/lib/nodes/list.tsx index 35bfb11f..6a10665a 100644 --- a/src/lib/nodes/list.tsx +++ b/src/lib/nodes/list.tsx @@ -520,20 +520,6 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { )} - {node.vms?.data?.[0]?.image_id && ( - <> - - Current VM Image: - - - - - - )} - {/* Actions Section - Show based on available actions */} {nodeActions.length > 0 && ( <> From e660b127c178779339e6aad3abbb27b125613939 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Mon, 8 Dec 2025 19:35:51 -0800 Subject: [PATCH 09/10] fix: defensive programming on which VMs are rendered --- src/lib/nodes/list.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/nodes/list.tsx b/src/lib/nodes/list.tsx index 6a10665a..82caf2d7 100644 --- a/src/lib/nodes/list.tsx +++ b/src/lib/nodes/list.tsx @@ -444,7 +444,11 @@ function NodeVerboseDisplay({ node }: { node: SFCNodes.Node }) { - + + vm.id !== lastVM?.id + )} + /> )} From a0fd007d368c1ee9c85d5797971d436972091350 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Mon, 8 Dec 2025 19:42:18 -0800 Subject: [PATCH 10/10] fix: adjust message spacing --- src/lib/nodes/list.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/nodes/list.tsx b/src/lib/nodes/list.tsx index 82caf2d7..520ea73d 100644 --- a/src/lib/nodes/list.tsx +++ b/src/lib/nodes/list.tsx @@ -156,7 +156,7 @@ function VMTable({ vms }: { vms: NonNullable["data"] }) { {/* Show message if there are more VMs */} {remainingVms > 0 && ( - + {remainingVms} past {remainingVms === 1 ? "VM" : "VMs"} not shown.