From b68554a27f205ff719c91a6ca5a9a5b2849ee297 Mon Sep 17 00:00:00 2001 From: Shantanu Joshi Date: Mon, 13 Apr 2026 10:22:58 -0700 Subject: [PATCH 1/7] refactor(image/show): migrate from nodes-sdk-alpha to apiClient Amp-Thread-ID: https://ampcode.com/threads/T-019d87dd-dad2-7700-a643-f114d525cd0a Co-authored-by: Amp --- src/lib/nodes/image/show.tsx | 148 ++++++++++++++++++++++++----------- 1 file changed, 102 insertions(+), 46 deletions(-) diff --git a/src/lib/nodes/image/show.tsx b/src/lib/nodes/image/show.tsx index 9456fe8..c125761 100644 --- a/src/lib/nodes/image/show.tsx +++ b/src/lib/nodes/image/show.tsx @@ -1,6 +1,5 @@ import console from "node:console"; import { Command } from "@commander-js/extra-typings"; -import type SFCNodes from "@sfcompute/nodes-sdk-alpha"; import chalk from "chalk"; import dayjs from "dayjs"; import advanced from "dayjs/plugin/advancedFormat"; @@ -8,8 +7,9 @@ import timezone from "dayjs/plugin/timezone"; import utc from "dayjs/plugin/utc"; import { Box, render, Text } from "ink"; import Link from "ink-link"; +import { apiClient } from "../../../apiClient.ts"; +import { logAndQuit } from "../../../helpers/errors.ts"; import { formatDate } from "../../../helpers/format-time.ts"; -import { handleNodesError, nodesClient } from "../../../nodesClient.ts"; import { Row } from "../../Row.tsx"; dayjs.extend(utc); @@ -18,20 +18,25 @@ dayjs.extend(timezone); export function ImageDisplay({ image, + download, }: { - image: SFCNodes.VMs.ImageGetResponse; + image: { + name: string; + id: string; + upload_status: string; + }; + download: { download_url: string; expires_at: number } | null; }) { - const expiresAt = image.expires_at ? new Date(image.expires_at) : null; + const expiresAt = download?.expires_at + ? new Date(download.expires_at * 1000) + : null; const isExpired = expiresAt ? expiresAt < new Date() : false; - const statusColor = isExpired ? "red" : "green"; - const statusText = isExpired ? "Expired" : "Ready"; - return ( - Image: {image.name} ({image.image_id}) + Image: {image.name} ({image.id}) @@ -40,62 +45,113 @@ export function ImageDisplay({ head="Status: " value={ - {statusText} + + {formatStatusText(image.upload_status)} + } /> - - Use curl or wget to download. - - {image.download_url} - - - } - /> - {expiresAt && ( - - - {expiresAt.toISOString()}{" "} - {chalk.blackBright( - `(${formatDate(dayjs(expiresAt).toDate())} ${dayjs( - expiresAt, - ).format("z")})`, - )} - - {isExpired && (Expired)} - - } - /> + {download && ( + <> + + Use curl or wget to download. + + {download.download_url} + + + } + /> + {expiresAt && ( + + + {expiresAt.toISOString()}{" "} + {chalk.blackBright( + `(${formatDate(dayjs(expiresAt).toDate())} ${dayjs( + expiresAt, + ).format("z")})`, + )} + + {isExpired && (Expired)} + + } + /> + )} + )} ); } +function formatStatusColor(status: string): string { + switch (status) { + case "started": + return "green"; + case "uploading": + return "yellow"; + case "completed": + return "cyan"; + case "failed": + return "red"; + default: + return "gray"; + } +} + +function formatStatusText(status: string): string { + switch (status) { + case "started": + return "Started"; + case "uploading": + return "Uploading"; + case "completed": + return "Completed"; + case "failed": + return "Failed"; + default: + return "Unknown"; + } +} + const show = new Command("show") .description("Show VM image details and download URL") .argument("", "ID of the image") .option("--json", "Output JSON") .action(async (imageId, opts) => { - try { - const client = await nodesClient(); - const data = await client.vms.images.get(imageId); + const client = await apiClient(); - if (opts.json) { - console.log(JSON.stringify(data, null, 2)); - return; + const { data: image, response } = await client.GET("/v2/images/{id}", { + params: { path: { id: imageId } }, + }); + if (!response.ok || !image) { + logAndQuit( + `Failed to get image: ${response.status} ${response.statusText}`, + ); + } + + let download = null; + if (image.upload_status === "completed") { + const { data: downloadData } = await client.GET( + "/v2/images/{id}/download", + { params: { path: { id: imageId } } }, + ); + if (downloadData) { + download = downloadData; } + } - render(); - } catch (err) { - handleNodesError(err); + if (opts.json) { + console.log(JSON.stringify({ ...image, download }, null, 2)); + return; } + + render(); }); export default show; From af39ecec7ea4ebf7b55ac5d9fc774613a88a2609 Mon Sep 17 00:00:00 2001 From: Shantanu Joshi Date: Mon, 13 Apr 2026 10:24:04 -0700 Subject: [PATCH 2/7] fix(image/show): use correct download.url field from API response Amp-Thread-ID: https://ampcode.com/threads/T-019d87dd-dad2-7700-a643-f114d525cd0a Co-authored-by: Amp --- src/lib/nodes/image/show.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/nodes/image/show.tsx b/src/lib/nodes/image/show.tsx index c125761..65c554c 100644 --- a/src/lib/nodes/image/show.tsx +++ b/src/lib/nodes/image/show.tsx @@ -25,7 +25,7 @@ export function ImageDisplay({ id: string; upload_status: string; }; - download: { download_url: string; expires_at: number } | null; + download: { url: string; expires_at: number } | null; }) { const expiresAt = download?.expires_at ? new Date(download.expires_at * 1000) @@ -58,8 +58,8 @@ export function ImageDisplay({ value={ Use curl or wget to download. - - {download.download_url} + + {download.url} } From fb7247a15d00ae1afb162194e45a43bb0e199dc3 Mon Sep 17 00:00:00 2001 From: Shantanu Joshi Date: Mon, 13 Apr 2026 10:26:25 -0700 Subject: [PATCH 3/7] fix(images/get): use correct download.url field from API response Amp-Thread-ID: https://ampcode.com/threads/T-019d87dd-dad2-7700-a643-f114d525cd0a Co-authored-by: Amp --- src/lib/images/get.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/images/get.tsx b/src/lib/images/get.tsx index 6dae00c..fb58fd8 100644 --- a/src/lib/images/get.tsx +++ b/src/lib/images/get.tsx @@ -26,7 +26,7 @@ function ImageDisplay({ upload_status: string; sha256_hash: string | null; }; - download: { download_url: string; expires_at: number } | null; + download: { url: string; expires_at: number } | null; }) { const expiresAt = download?.expires_at ? new Date(download.expires_at * 1000) @@ -51,8 +51,8 @@ function ImageDisplay({ value={ Use curl or wget to download. - - {download.download_url} + + {download.url} } From 41f2802d1e90aef2e19ecd4568455ca83b15afbc Mon Sep 17 00:00:00 2001 From: Shantanu Joshi Date: Mon, 13 Apr 2026 10:43:11 -0700 Subject: [PATCH 4/7] update schema --- src/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/schema.ts b/src/schema.ts index d567aa8..0c80087 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -2204,7 +2204,7 @@ export interface components { */ vmorch_ImageDiscriminator: "image"; vmorch_ImageDownloadResponse: { - download_url: string; + url: string; expires_at: components["schemas"]["vmorch_UnixEpoch"]; sha256_hash: string; /** Format: u-int64 */ From f7481067466e7dbaa133ac58b15cfe6cdee45188 Mon Sep 17 00:00:00 2001 From: Shantanu Joshi Date: Mon, 13 Apr 2026 12:56:20 -0700 Subject: [PATCH 5/7] fix(images): validate download endpoint response before using data Amp-Thread-ID: https://ampcode.com/threads/T-019d87dd-dad2-7700-a643-f114d525cd0a Co-authored-by: Amp --- src/lib/images/get.tsx | 4 ++-- src/lib/nodes/image/show.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/images/get.tsx b/src/lib/images/get.tsx index fb58fd8..84a471f 100644 --- a/src/lib/images/get.tsx +++ b/src/lib/images/get.tsx @@ -116,11 +116,11 @@ const get = new Command("get") // Fetch download URL if image is completed let download = null; if (image.upload_status === "completed") { - const { data: downloadData } = await client.GET( + const { data: downloadData, response: downloadResponse } = await client.GET( "/v2/images/{id}/download", { params: { path: { id } } }, ); - if (downloadData) { + if (downloadResponse.ok && downloadData) { download = downloadData; } } diff --git a/src/lib/nodes/image/show.tsx b/src/lib/nodes/image/show.tsx index 65c554c..83717cd 100644 --- a/src/lib/nodes/image/show.tsx +++ b/src/lib/nodes/image/show.tsx @@ -137,11 +137,11 @@ const show = new Command("show") let download = null; if (image.upload_status === "completed") { - const { data: downloadData } = await client.GET( + const { data: downloadData, response: downloadResponse } = await client.GET( "/v2/images/{id}/download", { params: { path: { id: imageId } } }, ); - if (downloadData) { + if (downloadResponse.ok && downloadData) { download = downloadData; } } From 4e81a4d778b268594b1d1599e127782047e27c05 Mon Sep 17 00:00:00 2001 From: Shantanu Joshi Date: Mon, 13 Apr 2026 13:08:57 -0700 Subject: [PATCH 6/7] fix formatting --- src/lib/images/get.tsx | 8 ++++---- src/lib/nodes/image/show.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/images/get.tsx b/src/lib/images/get.tsx index 84a471f..854981a 100644 --- a/src/lib/images/get.tsx +++ b/src/lib/images/get.tsx @@ -116,10 +116,10 @@ const get = new Command("get") // Fetch download URL if image is completed let download = null; if (image.upload_status === "completed") { - const { data: downloadData, response: downloadResponse } = await client.GET( - "/v2/images/{id}/download", - { params: { path: { id } } }, - ); + const { data: downloadData, response: downloadResponse } = + await client.GET("/v2/images/{id}/download", { + params: { path: { id } }, + }); if (downloadResponse.ok && downloadData) { download = downloadData; } diff --git a/src/lib/nodes/image/show.tsx b/src/lib/nodes/image/show.tsx index 83717cd..733e32e 100644 --- a/src/lib/nodes/image/show.tsx +++ b/src/lib/nodes/image/show.tsx @@ -137,10 +137,10 @@ const show = new Command("show") let download = null; if (image.upload_status === "completed") { - const { data: downloadData, response: downloadResponse } = await client.GET( - "/v2/images/{id}/download", - { params: { path: { id: imageId } } }, - ); + const { data: downloadData, response: downloadResponse } = + await client.GET("/v2/images/{id}/download", { + params: { path: { id: imageId } }, + }); if (downloadResponse.ok && downloadData) { download = downloadData; } From 36eaeff8e5ab25cb0b08a22a4ab532050365869e Mon Sep 17 00:00:00 2001 From: Shantanu Joshi Date: Mon, 13 Apr 2026 13:09:24 -0700 Subject: [PATCH 7/7] fix(biome): use contract-id+start for unique key --- src/lib/contracts/ContractDisplay.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/contracts/ContractDisplay.tsx b/src/lib/contracts/ContractDisplay.tsx index 61ddfc4..a64f397 100644 --- a/src/lib/contracts/ContractDisplay.tsx +++ b/src/lib/contracts/ContractDisplay.tsx @@ -140,7 +140,7 @@ export function ContractDisplay(props: { contract: Contract }) { {intervalData.map((data, index) => { return ( {index === 0 && (