From 49a87423099306d626748e1a85c7486b0991d18b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 9 Apr 2026 20:12:01 +0000 Subject: [PATCH 1/3] Fix sf images list/upload by passing workspace param to v2 API The v2 API requires a workspace query param on GET /v2/images and a workspace field in the body for POST /v2/images. The CLI was sending neither, causing 400 errors. - Add src/lib/images/utils.ts with getDefaultWorkspace() that resolves the account ID (from config or /v0/me) and returns the workspace URN sfc:workspace:{account_id}:default - Update list.ts to pass workspace as a query param - Update upload.ts to include workspace in the POST body Co-authored-by: Daniel Tao --- src/lib/images/list.ts | 7 ++++++- src/lib/images/upload.ts | 5 ++++- src/lib/images/utils.ts | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 src/lib/images/utils.ts diff --git a/src/lib/images/list.ts b/src/lib/images/list.ts index 83c13a1..4cfecf4 100644 --- a/src/lib/images/list.ts +++ b/src/lib/images/list.ts @@ -6,6 +6,7 @@ import ora from "ora"; import { apiClient } from "../../apiClient.ts"; import { logAndQuit } from "../../helpers/errors.ts"; import { formatDate } from "../../helpers/format-time.ts"; +import { getDefaultWorkspace } from "./utils.ts"; const list = new Command("list") .alias("ls") @@ -28,9 +29,13 @@ Examples:\n ) .action(async (options) => { const client = await apiClient(); + const workspace = await getDefaultWorkspace(); const spinner = ora("Fetching images...").start(); - const { data: result, response } = await client.GET("/v2/images"); + // biome-ignore lint/suspicious/noExplicitAny: OpenAPI schema is stale and doesn't include the workspace query param + const { data: result, response } = await (client as any).GET("/v2/images", { + params: { query: { workspace } }, + }); spinner.stop(); if (!response.ok || !result) { diff --git a/src/lib/images/upload.ts b/src/lib/images/upload.ts index deb2a41..36f17e2 100644 --- a/src/lib/images/upload.ts +++ b/src/lib/images/upload.ts @@ -12,6 +12,7 @@ import cliSpinners from "cli-spinners"; import ora, { type Ora } from "ora"; import { getAuthToken, loadConfig } from "../../helpers/config.ts"; import { logAndQuit } from "../../helpers/errors.ts"; +import { getDefaultWorkspace } from "./utils.ts"; async function readChunk( filePath: string, @@ -84,11 +85,13 @@ const upload = new Command("upload") preparingSpinner = ora(`Preparing upload for ${name}...`).start(); + const workspace = await getDefaultWorkspace(); + // Create image via v2 API const startResponse = await fetch(`${config.api_url}/v2/images`, { method: "POST", headers: apiHeaders, - body: JSON.stringify({ name }), + body: JSON.stringify({ name, workspace }), }); if (!startResponse.ok) { diff --git a/src/lib/images/utils.ts b/src/lib/images/utils.ts new file mode 100644 index 0000000..d641a4d --- /dev/null +++ b/src/lib/images/utils.ts @@ -0,0 +1,21 @@ +import { apiClient } from "../../apiClient.ts"; +import { loadConfig, saveConfig } from "../../helpers/config.ts"; + +async function getAccountId(): Promise { + const config = await loadConfig(); + if (config.account_id) { + return config.account_id; + } + const client = await apiClient(); + const { data } = await client.GET("/v0/me"); + if (data?.id) { + await saveConfig({ ...config, account_id: data.id }); + return data.id; + } + throw new Error("Could not determine account ID. Run 'sf login' first."); +} + +export async function getDefaultWorkspace(): Promise { + const accountId = await getAccountId(); + return `sfc:workspace:${accountId}:default`; +} From 54d9c6d06ae0ee242f89c0fdeabcfcb662e471c6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 9 Apr 2026 20:22:34 +0000 Subject: [PATCH 2/3] Add workspace param to OpenAPI schema, remove as-any cast in list.ts Update schema.ts to include the workspace query param on list_images_v2 and the workspace field on vmorch_StartUploadRequest, so the openapi-fetch client is properly typed. This removes the need for the 'as any' cast in list.ts. Co-authored-by: Daniel Tao --- src/lib/images/list.ts | 3 +-- src/schema.ts | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/images/list.ts b/src/lib/images/list.ts index 4cfecf4..c6c2113 100644 --- a/src/lib/images/list.ts +++ b/src/lib/images/list.ts @@ -32,8 +32,7 @@ Examples:\n const workspace = await getDefaultWorkspace(); const spinner = ora("Fetching images...").start(); - // biome-ignore lint/suspicious/noExplicitAny: OpenAPI schema is stale and doesn't include the workspace query param - const { data: result, response } = await (client as any).GET("/v2/images", { + const { data: result, response } = await client.GET("/v2/images", { params: { query: { workspace } }, }); spinner.stop(); diff --git a/src/schema.ts b/src/schema.ts index 7b06c12..d567aa8 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -2414,6 +2414,8 @@ export interface components { }; vmorch_StartUploadRequest: { name: components["schemas"]["vmorch_Name"]; + /** @description Workspace URN (e.g. sfc:workspace:{account_id}:default). */ + workspace?: string; }; /** * Format: int64 @@ -4988,6 +4990,8 @@ export interface operations { starting_after?: components["schemas"]["vmorch_ImagesCursor"]; /** @description Cursor for backward pagination. */ ending_before?: components["schemas"]["vmorch_ImagesCursor"]; + /** @description Workspace URN (e.g. sfc:workspace:{account_id}:default). */ + workspace?: string; }; header?: never; path?: never; From 6b1e2b230a9987a5f49c3cacfb4fc02d4f3e6d7e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 10 Apr 2026 20:39:17 +0000 Subject: [PATCH 3/3] Inline getAccountId into getDefaultWorkspace (LoB) Co-authored-by: Daniel Tao --- src/lib/images/utils.ts | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/lib/images/utils.ts b/src/lib/images/utils.ts index d641a4d..a43076a 100644 --- a/src/lib/images/utils.ts +++ b/src/lib/images/utils.ts @@ -1,21 +1,18 @@ import { apiClient } from "../../apiClient.ts"; import { loadConfig, saveConfig } from "../../helpers/config.ts"; -async function getAccountId(): Promise { +export async function getDefaultWorkspace(): Promise { const config = await loadConfig(); - if (config.account_id) { - return config.account_id; - } - const client = await apiClient(); - const { data } = await client.GET("/v0/me"); - if (data?.id) { - await saveConfig({ ...config, account_id: data.id }); - return data.id; + let accountId = config.account_id; + if (!accountId) { + const client = await apiClient(); + const { data } = await client.GET("/v0/me"); + if (data?.id) { + await saveConfig({ ...config, account_id: data.id }); + accountId = data.id; + } else { + throw new Error("Could not determine account ID. Run 'sf login' first."); + } } - throw new Error("Could not determine account ID. Run 'sf login' first."); -} - -export async function getDefaultWorkspace(): Promise { - const accountId = await getAccountId(); return `sfc:workspace:${accountId}:default`; }