diff --git a/.changeset/all-papayas-rest.md b/.changeset/all-papayas-rest.md new file mode 100644 index 0000000..00020d4 --- /dev/null +++ b/.changeset/all-papayas-rest.md @@ -0,0 +1,5 @@ +--- +"@mixedbread/cli": patch +--- + +Replaced ora and inquirer with clack for improved terminal UI diff --git a/packages/cli/jest.config.ts b/packages/cli/jest.config.ts index 8de13ee..429e094 100644 --- a/packages/cli/jest.config.ts +++ b/packages/cli/jest.config.ts @@ -15,8 +15,7 @@ const config: JestConfigWithTsJest = { moduleNameMapper: { "^@/(.*)$": "/src/$1", "^chalk$": "/tests/__mocks__/chalk.ts", - "^ora$": "/tests/__mocks__/ora.ts", - "^inquirer$": "/tests/__mocks__/inquirer.ts", + "^@clack/prompts$": "/tests/__mocks__/clack-prompts.ts", "^glob$": "/tests/__mocks__/glob.ts", "^p-limit$": "/tests/__mocks__/p-limit.ts", }, diff --git a/packages/cli/package.json b/packages/cli/package.json index 405b2a0..cb5f830 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -49,6 +49,7 @@ "setup-cli": "node ./scripts/setup-cli.js" }, "dependencies": { + "@clack/prompts": "^1.0.1", "@mixedbread/sdk": "^0.51.0", "@pnpm/tabtab": "^0.5.4", "chalk": "^5.6.2", @@ -56,17 +57,14 @@ "commander": "^14.0.3", "dotenv": "^17.3.1", "glob": "^13.0.3", - "inquirer": "^13.2.2", "mime-types": "^3.0.2", "minimatch": "^10.2.0", - "ora": "^9.3.0", "p-limit": "^7.3.0", "yaml": "^2.8.2", "zod": "^4.3.6" }, "devDependencies": { "@jest/globals": "^30.2.0", - "@types/inquirer": "^9.0.9", "@types/jest": "^30.0.0", "@types/mime-types": "^3.0.1", "@types/minimatch": "^6.0.0", diff --git a/packages/cli/src/commands/completion.ts b/packages/cli/src/commands/completion.ts index 82de683..d0b4202 100644 --- a/packages/cli/src/commands/completion.ts +++ b/packages/cli/src/commands/completion.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import { log as clackLog, spinner } from "@clack/prompts"; import { getShellFromEnv, install, @@ -8,7 +9,6 @@ import { } from "@pnpm/tabtab"; import chalk from "chalk"; import { Command } from "commander"; -import ora from "ora"; import { getCurrentKeyName, getStoresForCompletion, @@ -172,16 +172,19 @@ export function createCompletionCommand(): Command { const parsedOptions = parseOptions(BaseGlobalOptionsSchema, { ...mergedOptions, }); - const spinner = ora("Refreshing completion cache...").start(); + const refreshSpinner = spinner(); + refreshSpinner.start("Refreshing completion cache..."); try { await refreshAllCaches(parsedOptions); - spinner.succeed("Completion cache refreshed successfully"); + refreshSpinner.stop("Completion cache refreshed successfully"); } catch (error) { - spinner.fail("Failed to refresh completion cache"); - if (error instanceof Error) { - console.error(chalk.red("✗"), error.message); - } + refreshSpinner.stop(); + clackLog.error( + error instanceof Error + ? error.message + : "Failed to refresh completion cache" + ); } }); diff --git a/packages/cli/src/commands/config/keys.ts b/packages/cli/src/commands/config/keys.ts index 9d5aab0..5c3ce2f 100644 --- a/packages/cli/src/commands/config/keys.ts +++ b/packages/cli/src/commands/config/keys.ts @@ -1,6 +1,6 @@ +import { cancel, confirm, isCancel, log, text } from "@clack/prompts"; import chalk from "chalk"; import { Command } from "commander"; -import inquirer from "inquirer"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { @@ -41,7 +41,7 @@ export function createKeysCommand(): Command { }); if (!isMxbaiAPIKey(key)) { - console.error(chalk.red("✗"), 'API key must start with "mxb_"'); + log.error('API key must start with "mxb_"'); return; } @@ -49,29 +49,28 @@ export function createKeysCommand(): Command { // Prompt for name if not provided if (!name) { - const response = await inquirer.prompt<{ name: string }>({ - type: "input", - name: "name", + const nameResult = await text({ message: "Enter a name for this API key (e.g., 'work', 'personal'):", - validate: (input: string) => { - if (!input.trim()) { - return "Name cannot be empty"; - } + validate: (input) => { + if (!input.trim()) return "Name cannot be empty"; if (config.api_keys?.[input.trim()]) { return `API key "${input.trim()}" already exists`; } - return true; }, }); - name = response.name.trim(); + if (isCancel(nameResult)) { + cancel("Operation cancelled."); + process.exit(0); + } + name = nameResult.trim(); } else { // Validate name if provided if (!name.trim()) { - console.error(chalk.red("✗"), "Name cannot be empty"); + log.error("Name cannot be empty"); return; } if (config.api_keys?.[name]) { - console.error(chalk.red("✗"), `API key "${name}" already exists`); + log.error(`API key "${name}" already exists`); return; } } @@ -90,10 +89,7 @@ export function createKeysCommand(): Command { saveConfig(config); - console.log( - chalk.green("✓"), - `API key "${name}" saved and set as default` - ); + log.success(`API key "${name}" saved and set as default`); // Populate completion cache for the new key const client = createClient({ @@ -133,10 +129,7 @@ export function createKeysCommand(): Command { const config = loadConfig(); if (!config.api_keys?.[parsedOptions.name]) { - console.error( - chalk.red("✗"), - `No API key found with name "${parsedOptions.name}"` - ); + log.error(`No API key found with name "${parsedOptions.name}"`); if (config.api_keys && Object.keys(config.api_keys).length > 0) { console.log("\nAvailable API keys:"); @@ -149,15 +142,13 @@ export function createKeysCommand(): Command { // Confirm removal unless yes flag is used if (!parsedOptions.yes) { - const response = await inquirer.prompt<{ confirm: boolean }>({ - type: "confirm", - name: "confirm", + const confirmed = await confirm({ message: `Remove API key "${parsedOptions.name}"${isDefault ? " (currently default)" : ""}?`, - default: false, + initialValue: false, }); - if (!response.confirm) { - console.log(chalk.yellow("Removal cancelled.")); + if (isCancel(confirmed) || !confirmed) { + log.warn("Removal cancelled."); return; } } @@ -175,15 +166,9 @@ export function createKeysCommand(): Command { } saveConfig(config); - console.log( - chalk.green("✓"), - `API key "${parsedOptions.name}" removed` - ); - - console.log( - chalk.yellow("⚠"), - "No default API key set. Set a new default:" - ); + log.success(`API key "${parsedOptions.name}" removed`); + + log.warn("No default API key set. Set a new default:"); if (Object.keys(config.api_keys).length > 0) { Object.keys(config.api_keys).forEach((keyName) => { console.log(` mxbai config keys set-default ${keyName}`); @@ -193,10 +178,7 @@ export function createKeysCommand(): Command { } } else { saveConfig(config); - console.log( - chalk.green("✓"), - `API key "${parsedOptions.name}" removed` - ); + log.success(`API key "${parsedOptions.name}" removed`); } }); @@ -212,7 +194,7 @@ export function createKeysCommand(): Command { const config = loadConfig(); if (!config.api_keys?.[name]) { - console.error(chalk.red("✗"), `No API key found with name "${name}"`); + log.error(`No API key found with name "${name}"`); if (config.api_keys && Object.keys(config.api_keys).length > 0) { console.log("\nAvailable API keys:"); @@ -227,7 +209,7 @@ export function createKeysCommand(): Command { config.defaults.api_key = name; saveConfig(config); - console.log(chalk.green("✓"), `"${name}" set as default API key`); + log.success(`"${name}" set as default API key`); // Refresh cache for the newly set default key const client = createClient({ diff --git a/packages/cli/src/commands/store/create.ts b/packages/cli/src/commands/store/create.ts index cb25164..e0113ba 100644 --- a/packages/cli/src/commands/store/create.ts +++ b/packages/cli/src/commands/store/create.ts @@ -1,6 +1,5 @@ -import chalk from "chalk"; +import { log, spinner } from "@clack/prompts"; import { Command } from "commander"; -import ora, { type Ora } from "ora"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { @@ -58,7 +57,7 @@ export function createCreateCommand(): Command { ); command.action(async (name: string, options: CreateOptions) => { - let spinner: Ora; + const createSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(command, options); @@ -71,7 +70,7 @@ export function createCreateCommand(): Command { const metadata = validateMetadata(parsedOptions.metadata); - spinner = ora("Creating store...").start(); + createSpinner.start("Creating store..."); const store = await client.stores.create({ name: parsedOptions.name, @@ -87,7 +86,7 @@ export function createCreateCommand(): Command { metadata, }); - spinner.succeed(`Store "${name}" created successfully`); + createSpinner.stop(`Store "${name}" created successfully`); formatOutput( { @@ -111,12 +110,10 @@ export function createCreateCommand(): Command { updateCacheAfterCreate(keyName, store.name); } } catch (error) { - spinner?.fail("Failed to create store"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to create store"); - } + createSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to create store" + ); process.exit(1); } }); diff --git a/packages/cli/src/commands/store/delete.ts b/packages/cli/src/commands/store/delete.ts index 9cc6994..4f5e992 100644 --- a/packages/cli/src/commands/store/delete.ts +++ b/packages/cli/src/commands/store/delete.ts @@ -1,7 +1,5 @@ -import chalk from "chalk"; +import { confirm, isCancel, log, spinner } from "@clack/prompts"; import { Command } from "commander"; -import inquirer from "inquirer"; -import ora, { type Ora } from "ora"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { @@ -36,7 +34,7 @@ export function createDeleteCommand(): Command { ); command.action(async (nameOrId: string, options: DeleteOptions) => { - let spinner: Ora; + const deleteSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(command, options); @@ -51,26 +49,22 @@ export function createDeleteCommand(): Command { // Confirmation prompt unless --yes is used if (!parsedOptions.yes) { - const { confirmed } = await inquirer.prompt([ - { - type: "confirm", - name: "confirmed", - message: `Are you sure you want to delete store "${store.name}" (${store.id})? This action cannot be undone.`, - default: false, - }, - ]); + const confirmed = await confirm({ + message: `Are you sure you want to delete store "${store.name}" (${store.id})? This action cannot be undone.`, + initialValue: false, + }); - if (!confirmed) { - console.log(chalk.yellow("Deletion cancelled.")); + if (isCancel(confirmed) || !confirmed) { + log.warn("Deletion cancelled."); return; } } - spinner = ora("Deleting store...").start(); + deleteSpinner.start("Deleting store..."); await client.stores.delete(store.id); - spinner.succeed(`Store "${store.name}" deleted successfully`); + deleteSpinner.stop(`Store "${store.name}" deleted successfully`); // Update completion cache by removing the deleted store const keyName = getCurrentKeyName(); @@ -78,12 +72,10 @@ export function createDeleteCommand(): Command { updateCacheAfterDelete(keyName, store.name); } } catch (error) { - spinner?.fail("Failed to delete store"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to delete store"); - } + deleteSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to delete store" + ); process.exit(1); } }); diff --git a/packages/cli/src/commands/store/files/delete.ts b/packages/cli/src/commands/store/files/delete.ts index 24841b7..074a51d 100644 --- a/packages/cli/src/commands/store/files/delete.ts +++ b/packages/cli/src/commands/store/files/delete.ts @@ -1,7 +1,5 @@ -import chalk from "chalk"; +import { confirm, isCancel, log, spinner } from "@clack/prompts"; import { Command } from "commander"; -import inquirer from "inquirer"; -import ora, { type Ora } from "ora"; import z from "zod"; import { createClient } from "../../../utils/client"; import { @@ -31,7 +29,7 @@ export function createDeleteCommand(): Command { deleteCommand.action( async (nameOrId: string, fileId: string, options: GlobalOptions) => { - let spinner: Ora; + const deleteSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(deleteCommand, options); @@ -46,35 +44,29 @@ export function createDeleteCommand(): Command { // Confirmation prompt unless --yes is used if (!parsedOptions.yes) { - const { confirmed } = await inquirer.prompt([ - { - type: "confirm", - name: "confirmed", - message: `Are you sure you want to delete file "${parsedOptions.fileId}" from store "${store.name}" (${store.id})? This action cannot be undone.`, - default: false, - }, - ]); + const confirmed = await confirm({ + message: `Are you sure you want to delete file "${parsedOptions.fileId}" from store "${store.name}" (${store.id})? This action cannot be undone.`, + initialValue: false, + }); - if (!confirmed) { - console.log(chalk.yellow("Deletion cancelled.")); + if (isCancel(confirmed) || !confirmed) { + log.warn("Deletion cancelled."); return; } } - spinner = ora("Deleting file...").start(); + deleteSpinner.start("Deleting file..."); await client.stores.files.delete(parsedOptions.fileId, { store_identifier: store.id, }); - spinner.succeed(`File ${parsedOptions.fileId} deleted successfully`); + deleteSpinner.stop(`File ${parsedOptions.fileId} deleted successfully`); } catch (error) { - spinner?.fail("Failed to delete file"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to delete file"); - } + deleteSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to delete file" + ); process.exit(1); } } diff --git a/packages/cli/src/commands/store/files/get.ts b/packages/cli/src/commands/store/files/get.ts index 5d1c671..258e4a2 100644 --- a/packages/cli/src/commands/store/files/get.ts +++ b/packages/cli/src/commands/store/files/get.ts @@ -1,6 +1,5 @@ -import chalk from "chalk"; +import { log, spinner } from "@clack/prompts"; import { Command } from "commander"; -import ora, { type Ora } from "ora"; import { z } from "zod"; import { createClient } from "../../../utils/client"; import { @@ -28,7 +27,7 @@ export function createGetCommand(): Command { getCommand.action( async (nameOrId: string, fileId: string, options: GlobalOptions) => { - let spinner: Ora; + const getSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(getCommand, options); @@ -40,14 +39,14 @@ export function createGetCommand(): Command { }); const client = createClient(parsedOptions); - spinner = ora("Loading file details...").start(); + getSpinner.start("Loading file details..."); const store = await resolveStore(client, parsedOptions.nameOrId); const file = await client.stores.files.retrieve(parsedOptions.fileId, { store_identifier: store.id, }); - spinner.succeed("File details loaded"); + getSpinner.stop("File details loaded"); const formattedData = { id: file.id, @@ -63,12 +62,10 @@ export function createGetCommand(): Command { formatOutput(formattedData, parsedOptions.format); } catch (error) { - spinner.fail("Failed to load file details"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to get file details"); - } + getSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to get file details" + ); process.exit(1); } } diff --git a/packages/cli/src/commands/store/files/list.ts b/packages/cli/src/commands/store/files/list.ts index bf01b6b..951c751 100644 --- a/packages/cli/src/commands/store/files/list.ts +++ b/packages/cli/src/commands/store/files/list.ts @@ -1,7 +1,6 @@ +import { log, spinner } from "@clack/prompts"; import type { StoreFile } from "@mixedbread/sdk/resources/stores"; -import chalk from "chalk"; import { Command } from "commander"; -import ora, { type Ora } from "ora"; import { z } from "zod"; import { createClient } from "../../../utils/client"; import { @@ -48,7 +47,7 @@ export function createListCommand(): Command { ); listCommand.action(async (nameOrId: string, options: FilesOptions) => { - let spinner: Ora; + const listSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(listCommand, options); @@ -58,7 +57,7 @@ export function createListCommand(): Command { }); const client = createClient(parsedOptions); - spinner = ora("Loading files...").start(); + listSpinner.start("Loading files..."); const store = await resolveStore(client, parsedOptions.nameOrId); const response = await client.stores.files.list(store.id, { @@ -75,11 +74,12 @@ export function createListCommand(): Command { } if (files.length === 0) { - spinner.info("No files found."); + listSpinner.stop(); + log.info("No files found."); return; } - spinner.succeed(`Found ${formatCountWithSuffix(files.length, "file")}`); + listSpinner.stop(`Found ${formatCountWithSuffix(files.length, "file")}`); // Format data for output const formattedData = files.map((file) => ({ @@ -96,12 +96,10 @@ export function createListCommand(): Command { formatOutput(formattedData, parsedOptions.format); } catch (error) { - spinner.fail("Failed to load files"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to list files"); - } + listSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to list files" + ); process.exit(1); } }); diff --git a/packages/cli/src/commands/store/get.ts b/packages/cli/src/commands/store/get.ts index ccdc3b1..834ac47 100644 --- a/packages/cli/src/commands/store/get.ts +++ b/packages/cli/src/commands/store/get.ts @@ -1,6 +1,5 @@ -import chalk from "chalk"; +import { log, spinner } from "@clack/prompts"; import { Command } from "commander"; -import ora, { type Ora } from "ora"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { @@ -27,7 +26,7 @@ export function createGetCommand(): Command { ); command.action(async (nameOrId: string, options: GetOptions) => { - let spinner: Ora; + const getSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(command, options); @@ -38,10 +37,10 @@ export function createGetCommand(): Command { }); const client = createClient(parsedOptions); - spinner = ora("Loading store details...").start(); + getSpinner.start("Loading store details..."); const store = await resolveStore(client, parsedOptions.nameOrId); - spinner.succeed("Store details loaded"); + getSpinner.stop("Store details loaded"); const formattedData = { name: store.name, @@ -68,12 +67,10 @@ export function createGetCommand(): Command { formatOutput(formattedData, parsedOptions.format); } catch (error) { - spinner?.fail("Failed to load store details"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to get store details"); - } + getSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to get store details" + ); process.exit(1); } }); diff --git a/packages/cli/src/commands/store/list.ts b/packages/cli/src/commands/store/list.ts index 254b63b..9b5b836 100644 --- a/packages/cli/src/commands/store/list.ts +++ b/packages/cli/src/commands/store/list.ts @@ -1,6 +1,5 @@ -import chalk from "chalk"; +import { log, spinner } from "@clack/prompts"; import { Command } from "commander"; -import ora, { type Ora } from "ora"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { @@ -44,7 +43,7 @@ export function createListCommand(): Command { ); command.action(async (options: ListOptions) => { - let spinner: Ora; + const listSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(command, options); @@ -54,7 +53,7 @@ export function createListCommand(): Command { ); const client = createClient(parsedOptions); - spinner = ora("Loading stores...").start(); + listSpinner.start("Loading stores..."); const response = await client.stores.list({ limit: parsedOptions.limit || 100, }); @@ -70,7 +69,8 @@ export function createListCommand(): Command { } if (stores.length === 0) { - spinner.info("No stores found."); + listSpinner.stop(); + log.info("No stores found."); return; } @@ -87,7 +87,7 @@ export function createListCommand(): Command { created: new Date(store.created_at).toLocaleDateString(), })); - spinner.succeed(`Found ${formatCountWithSuffix(stores.length, "store")}`); + listSpinner.stop(`Found ${formatCountWithSuffix(stores.length, "store")}`); formatOutput(formattedData, parsedOptions.format); // Update completion cache with the fetched stores @@ -96,12 +96,10 @@ export function createListCommand(): Command { refreshCacheForKey(keyName, client); } } catch (error) { - spinner?.fail("Failed to load stores"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to list stores"); - } + listSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to list stores" + ); process.exit(1); } }); diff --git a/packages/cli/src/commands/store/qa.ts b/packages/cli/src/commands/store/qa.ts index eeb9f9d..3e8582c 100644 --- a/packages/cli/src/commands/store/qa.ts +++ b/packages/cli/src/commands/store/qa.ts @@ -1,6 +1,6 @@ +import { log, spinner } from "@clack/prompts"; import chalk from "chalk"; import { Command } from "commander"; -import ora, { type Ora } from "ora"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { loadConfig } from "../../utils/config"; @@ -54,7 +54,7 @@ export function createQACommand(): Command { command.action( async (nameOrId: string, question: string, options: QAOptions) => { - let spinner: Ora; + const qaSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(command, options); @@ -65,7 +65,7 @@ export function createQACommand(): Command { }); const client = createClient(parsedOptions); - spinner = ora("Processing question...").start(); + qaSpinner.start("Processing question..."); const store = await resolveStore(client, parsedOptions.nameOrId); const config = loadConfig(); @@ -86,10 +86,10 @@ export function createQACommand(): Command { }, }); - spinner.succeed("Question processed"); + qaSpinner.stop("Question processed"); // Display the answer - console.log(chalk.bold(chalk.blue("Answer:"))); + console.log(chalk.bold(chalk.blue("\nAnswer:"))); console.log(response.answer); // Display sources if available @@ -118,12 +118,10 @@ export function createQACommand(): Command { formatOutput(sources, parsedOptions.format); } } catch (error) { - spinner?.fail("Failed to process question"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to process question"); - } + qaSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to process question" + ); process.exit(1); } } diff --git a/packages/cli/src/commands/store/search.ts b/packages/cli/src/commands/store/search.ts index 6698b76..be01031 100644 --- a/packages/cli/src/commands/store/search.ts +++ b/packages/cli/src/commands/store/search.ts @@ -1,7 +1,6 @@ +import { log, spinner } from "@clack/prompts"; import type Mixedbread from "@mixedbread/sdk"; -import chalk from "chalk"; import { Command } from "commander"; -import ora, { type Ora } from "ora"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { loadConfig } from "../../utils/config"; @@ -93,7 +92,7 @@ export function createSearchCommand(): Command { command.action( async (nameOrId: string, query: string, options: SearchOptions) => { - let spinner: Ora; + const searchSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(command, options); @@ -104,7 +103,7 @@ export function createSearchCommand(): Command { }); const client = createClient(parsedOptions); - spinner = ora("Searching store...").start(); + searchSpinner.start("Searching store..."); const store = await resolveStore(client, parsedOptions.nameOrId); const config = loadConfig(); @@ -128,13 +127,12 @@ export function createSearchCommand(): Command { }); if (!results.data || results.data.length === 0) { - spinner.info("No results found."); + searchSpinner.stop(); + log.info("No results found."); return; } - spinner.succeed( - `Found ${formatCountWithSuffix(results.data.length, "result")}` - ); + searchSpinner.stop(`Found ${formatCountWithSuffix(results.data.length, "result")}`); const output = results.data.map((result) => { const metadata = @@ -161,12 +159,10 @@ export function createSearchCommand(): Command { formatOutput(output, parsedOptions.format); } catch (error) { - spinner?.fail("Search failed"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to search store"); - } + searchSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to search store" + ); process.exit(1); } } diff --git a/packages/cli/src/commands/store/sync.ts b/packages/cli/src/commands/store/sync.ts index a3c055a..b2fccf7 100644 --- a/packages/cli/src/commands/store/sync.ts +++ b/packages/cli/src/commands/store/sync.ts @@ -1,7 +1,7 @@ +import { confirm, isCancel, log, spinner } from "@clack/prompts"; import type { FileCreateParams } from "@mixedbread/sdk/resources/stores"; import chalk from "chalk"; import { Command } from "commander"; -import ora from "ora"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { warnContextualizationDeprecated } from "../../utils/deprecation"; @@ -98,18 +98,17 @@ export function createSyncCommand(): Command { const client = createClient(parsedOptions); - console.log(chalk.bold.blue("🔄 Starting Store Sync\n")); + console.log(chalk.bold.blue("🔄 Starting Store Sync")); if (parsedOptions.contextualization) { warnContextualizationDeprecated("store sync"); } // Step 0: Resolve store - const resolveSpinner = ora( - `Looking up store "${parsedOptions.nameOrId}"...` - ).start(); + const resolveSpinner = spinner(); + resolveSpinner.start(`Looking up store "${parsedOptions.nameOrId}"...`); const store = await resolveStore(client, parsedOptions.nameOrId); - resolveSpinner.succeed(`Found store: ${store.name}`); + resolveSpinner.stop(`Found store: ${store.name}`); // Parse metadata if provided const additionalMetadata = validateMetadata(parsedOptions.metadata); @@ -117,45 +116,32 @@ export function createSyncCommand(): Command { // Get git info const gitInfo = await getGitInfo(); - const spinner = ora("Loading existing files from store...").start(); + const loadSpinner = spinner(); + loadSpinner.start("Loading existing files from store..."); const syncedFiles = await getSyncedFiles(client, store.id); - spinner.succeed( + loadSpinner.stop( `Found ${formatCountWithSuffix(syncedFiles.size, "existing file")} in store` ); const fromGit = parsedOptions.fromGit; if (parsedOptions.force) { - console.log( - chalk.green( - "✓ Force upload enabled - all files will be re-uploaded" - ) - ); + log.success("Force upload enabled - all files will be re-uploaded"); } else if (fromGit && gitInfo.isRepo) { - console.log( - chalk.green( - `✓ Git-based detection enabled (from commit ${fromGit.substring(0, 7)})` - ) + log.success( + `Git-based detection enabled (from commit ${fromGit.substring(0, 7)})` ); } else if (fromGit && !gitInfo.isRepo) { - console.error( - chalk.red("✗"), - "--from-git specified but not in a git repository" - ); + log.error("--from-git specified but not in a git repository"); process.exit(1); } else { - console.log( - chalk.green( - "✓ Hash-based detection enabled (comparing file contents)" - ) - ); + log.success("Hash-based detection enabled (comparing file contents)"); } - const analyzeSpinner = ora( - "Scanning files and detecting changes..." - ).start(); + const analyzeSpinner = spinner(); + analyzeSpinner.start("Scanning files and detecting changes..."); const analysis = await analyzeChanges({ patterns, syncedFiles, @@ -164,7 +150,7 @@ export function createSyncCommand(): Command { forceUpload: parsedOptions.force, }); - analyzeSpinner.succeed("Change analysis complete"); + analyzeSpinner.stop("Change analysis complete"); const totalChanges = analysis.added.length + @@ -172,9 +158,7 @@ export function createSyncCommand(): Command { analysis.deleted.length; if (totalChanges === 0) { - console.log( - chalk.green("✓ Store is already in sync - no changes needed!") - ); + log.success("Store is already in sync - no changes needed!"); return; } @@ -202,22 +186,16 @@ export function createSyncCommand(): Command { // Confirm changes unless yes flag is set if (!parsedOptions.yes) { - const { default: inquirer } = await import("inquirer"); - const { proceed } = await inquirer.prompt([ - { - type: "confirm", - name: "proceed", - message: "Apply these changes to the store?", - default: false, - }, - ]); - - if (!proceed) { - console.log(chalk.yellow("Sync cancelled by user")); + const proceed = await confirm({ + message: "Apply these changes to the store?", + }); + + if (isCancel(proceed) || !proceed) { + log.warn("Sync cancelled by user"); return; } } else if (parsedOptions.yes) { - console.log(chalk.green("✓ Auto-proceeding with --yes flag")); + log.success("Auto-proceeding with --yes flag"); } // Execute changes @@ -239,9 +217,9 @@ export function createSyncCommand(): Command { }); } catch (error) { if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); + log.error(error.message); } else { - console.error(chalk.red("\n✗"), "Failed to sync store"); + log.error("Failed to sync store"); } process.exit(1); } diff --git a/packages/cli/src/commands/store/update.ts b/packages/cli/src/commands/store/update.ts index e5b852d..6d67ca3 100644 --- a/packages/cli/src/commands/store/update.ts +++ b/packages/cli/src/commands/store/update.ts @@ -1,7 +1,6 @@ +import { log, spinner } from "@clack/prompts"; import type { StoreUpdateParams } from "@mixedbread/sdk/resources/index"; -import chalk from "chalk"; import { Command } from "commander"; -import ora, { type Ora } from "ora"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { @@ -59,7 +58,7 @@ export function createUpdateCommand(): Command { ); command.action(async (nameOrId: string, options: UpdateOptions) => { - let spinner: Ora; + const updateSpinner = spinner(); try { const mergedOptions = mergeCommandOptions(command, options); @@ -89,18 +88,17 @@ export function createUpdateCommand(): Command { }; if (Object.keys(updateData).length === 0) { - console.error( - chalk.red("✗"), + log.error( "No update fields provided. Use --name, --description, --public, or --metadata" ); process.exit(1); } - spinner = ora("Updating store...").start(); + updateSpinner.start("Updating store..."); const updatedStore = await client.stores.update(store.id, updateData); - spinner.succeed(`Store "${store.name}" updated successfully`); + updateSpinner.stop(`Store "${store.name}" updated successfully`); formatOutput( { @@ -132,12 +130,10 @@ export function createUpdateCommand(): Command { } } } catch (error) { - spinner?.fail("Failed to update store"); - if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); - } else { - console.error(chalk.red("\n✗"), "Failed to update store"); - } + updateSpinner.stop(); + log.error( + error instanceof Error ? error.message : "Failed to update store" + ); process.exit(1); } }); diff --git a/packages/cli/src/commands/store/upload.ts b/packages/cli/src/commands/store/upload.ts index 351de40..c139dc5 100644 --- a/packages/cli/src/commands/store/upload.ts +++ b/packages/cli/src/commands/store/upload.ts @@ -1,9 +1,9 @@ import { statSync } from "node:fs"; +import { log, spinner } from "@clack/prompts"; import type { FileCreateParams } from "@mixedbread/sdk/resources/stores"; import chalk from "chalk"; import { Command } from "commander"; import { glob } from "glob"; -import ora from "ora"; import { z } from "zod"; import { createClient } from "../../utils/client"; import { loadConfig } from "../../utils/config"; @@ -95,11 +95,12 @@ export function createUploadCommand(): Command { } const client = createClient(parsedOptions); - const spinner = ora("Initializing upload...").start(); + const initializeSpinner = spinner(); + initializeSpinner.start("Initializing upload..."); const store = await resolveStore(client, parsedOptions.nameOrId); const config = loadConfig(); - spinner.succeed("Upload initialized"); + initializeSpinner.stop("Upload initialized"); // Handle manifest file upload if (parsedOptions.manifest) { @@ -112,8 +113,7 @@ export function createUploadCommand(): Command { } if (!parsedOptions.patterns || parsedOptions.patterns.length === 0) { - console.error( - chalk.red("✗"), + log.error( "No file patterns provided. Use --manifest for manifest-based uploads." ); process.exit(1); @@ -141,7 +141,7 @@ export function createUploadCommand(): Command { if (parsedOptions.patterns) { if (uniqueFiles.length === 0) { - console.log(chalk.yellow("No files found matching the patterns.")); + log.warn("No files found matching the patterns."); return; } @@ -181,7 +181,8 @@ export function createUploadCommand(): Command { // Handle --unique flag: check for existing files let existingFiles: Map = new Map(); if (parsedOptions.unique) { - const spinner = ora("Checking for existing files...").start(); + const uniqueSpinner = spinner(); + uniqueSpinner.start("Checking for existing files..."); try { const storeFiles = await getStoreFiles(client, store.id); existingFiles = new Map( @@ -200,11 +201,12 @@ export function createUploadCommand(): Command { f.id, ]) ); - spinner.succeed( + uniqueSpinner.stop( `Found ${formatCountWithSuffix(existingFiles.size, "existing file")}` ); } catch (error) { - spinner.fail("Failed to check existing files"); + uniqueSpinner.stop(); + log.error("Failed to check existing files"); throw error; } } @@ -224,9 +226,9 @@ export function createUploadCommand(): Command { }); } catch (error) { if (error instanceof Error) { - console.error(chalk.red("\n✗"), error.message); + log.error(error.message); } else { - console.error(chalk.red("\n✗"), "Failed to upload files"); + log.error("Failed to upload files"); } process.exit(1); } diff --git a/packages/cli/src/utils/config.ts b/packages/cli/src/utils/config.ts index 690a35d..36706b8 100644 --- a/packages/cli/src/utils/config.ts +++ b/packages/cli/src/utils/config.ts @@ -145,6 +145,15 @@ export function saveConfig(config: CLIConfig): void { writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); } +function formatAvailableKeys(config: CLIConfig): string { + return Object.keys(config.api_keys) + .map((name) => { + const isDefault = config.defaults?.api_key === name; + return ` ${isDefault ? "*" : " "} ${name}${isDefault ? " (default)" : ""}`; + }) + .join("\n"); +} + export function getApiKey(options?: { apiKey?: string; savedKey?: string; @@ -154,11 +163,9 @@ export function getApiKey(options?: { // Handle --api-key (actual API key) if (options?.apiKey) { if (!isMxbaiAPIKey(options.apiKey)) { - console.error( - chalk.red("✗"), + throw new Error( "Invalid API key format. API keys must start with 'mxb_'." ); - process.exit(1); } displayApiKeyUsage(options.apiKey, "from --api-key"); return options.apiKey; @@ -168,20 +175,13 @@ export function getApiKey(options?: { if (options?.savedKey) { const config = loadConfig(); if (!config.api_keys?.[options.savedKey]) { - console.error( - chalk.red("✗"), - `No saved API key found with name "${options.savedKey}".` - ); + let message = `No saved API key found with name "${options.savedKey}".`; if (config.api_keys && Object.keys(config.api_keys).length > 0) { - console.log("\nAvailable saved keys:"); - outputAvailableKeys(config); + message += `\n\nAvailable saved keys:\n${formatAvailableKeys(config)}`; } else { - console.log( - "\nNo saved keys found. Add one with: ", - chalk.cyan("mxbai config keys add ") - ); + message += `\n\nNo saved keys found. Add one with: ${chalk.cyan("mxbai config keys add ")}`; } - process.exit(1); + throw new Error(message); } const resolvedKey = config.api_keys[options.savedKey]; displayApiKeyUsage(resolvedKey, "from --saved-key", options.savedKey); @@ -197,25 +197,23 @@ export function getApiKey(options?: { // Check for old format and prompt for migration if (existsSync(CONFIG_FILE)) { + let rawConfig: Record | undefined; try { - const rawConfig = JSON.parse(readFileSync(CONFIG_FILE, "utf-8")); - if ( - rawConfig.api_key && - Object.keys(rawConfig.api_keys || {}).length === 0 - ) { - console.log(chalk.yellow("\n\n⚠ Migration Required")); - console.log( - "The API key storage format has changed. Please migrate your existing API key:" - ); - console.log( - chalk.cyan(" mxbai config keys add ") - ); - console.log("\nYour current key will not work until migrated.\n"); - process.exit(1); - } + rawConfig = JSON.parse(readFileSync(CONFIG_FILE, "utf-8")); } catch { // If we can't read the config file, continue with normal flow } + if ( + rawConfig?.api_key && + Object.keys(rawConfig.api_keys || {}).length === 0 + ) { + throw new Error( + `${chalk.yellow("Migration Required")}\n` + + "The API key storage format has changed. Please migrate your existing API key:\n" + + `${chalk.cyan(" mxbai config keys add ")}\n\n` + + "Your current key will not work until migrated." + ); + } } // Get default API key from new format @@ -228,23 +226,23 @@ export function getApiKey(options?: { // If no default but keys exist, show available keys if (config.api_keys && Object.keys(config.api_keys).length > 0) { - console.error(chalk.red("\n\n✗"), "No default API key set.\n"); - console.log("Available API keys:"); - outputAvailableKeys(config); - console.log("\nSet a default API key:"); - console.log(chalk.cyan(" mxbai config keys set-default \n")); - process.exit(1); + throw new Error( + "No default API key set.\n\n" + + "Available API keys:\n" + + formatAvailableKeys(config) + + "\n\nSet a default API key:\n" + + `${chalk.cyan(" mxbai config keys set-default ")}` + ); } - console.error(chalk.red("\n\n✗"), "No API key found.\n"); - console.log("Please add an API key using:"); - console.log(" 1. Command flag: --api-key or --saved-key "); - console.log(" 2. Environment variable: export MXBAI_API_KEY=mxb_xxxxx"); - console.log(" 3. Config file: mxbai config keys add \n"); - console.log( - "Get your API key at: https://www.platform.mixedbread.com/platform?next=api-keys" + throw new Error( + "No API key found.\n\n" + + "Please add an API key using:\n" + + " 1. Command flag: --api-key or --saved-key \n" + + " 2. Environment variable: export MXBAI_API_KEY=mxb_xxxxx\n" + + " 3. Config file: mxbai config keys add \n\n" + + "Get your API key at: https://www.platform.mixedbread.com/platform?next=api-keys" ); - process.exit(1); } export function isMxbaiAPIKey(key: string): boolean { @@ -299,37 +297,24 @@ export function parseConfigValue(key: string, value: string) { const targetSchema = resolveSchemaPath(CLIConfigSchema, pathSegments); if (!targetSchema) { - console.error( - chalk.red("✗"), + throw new Error( `Unknown config key: ${key}. Use 'mxbai config --help' to see available options.` ); - process.exit(1); } const parsed = targetSchema.safeParse(value); if (!parsed.success) { - console.error( - chalk.red("✗"), + throw new Error( `Invalid value for ${key}: ${parsed.error.issues.map((i) => i.message).join(", ")}` ); - process.exit(1); } return parsed.data; } export function outputAvailableKeys(config?: CLIConfig) { - if (!config) { - config = loadConfig(); - } - - Object.keys(config.api_keys).forEach((name) => { - const isDefault = config.defaults?.api_key === name; - console.log( - ` ${isDefault ? "*" : " "} ${name}${isDefault ? " (default)" : ""}` - ); - }); + console.log(formatAvailableKeys(config ?? loadConfig())); } function truncateApiKey(apiKey: string): string { diff --git a/packages/cli/src/utils/deprecation.ts b/packages/cli/src/utils/deprecation.ts index c303718..0447941 100644 --- a/packages/cli/src/utils/deprecation.ts +++ b/packages/cli/src/utils/deprecation.ts @@ -9,4 +9,4 @@ export function warnContextualizationDeprecated(source: string): void { ].join(" ") ) ); -} \ No newline at end of file +} diff --git a/packages/cli/src/utils/global-options.ts b/packages/cli/src/utils/global-options.ts index 3de9da8..46a671b 100644 --- a/packages/cli/src/utils/global-options.ts +++ b/packages/cli/src/utils/global-options.ts @@ -1,4 +1,3 @@ -import chalk from "chalk"; import type { Command } from "commander"; import { z } from "zod"; @@ -100,11 +99,7 @@ export function parseOptions( const parsed = schema.safeParse(options); if (!parsed.success) { - console.error( - chalk.red("\n✗"), - parsed.error.issues.map((i) => i.message).join(", ") - ); - process.exit(1); + throw new Error(parsed.error.issues.map((i) => i.message).join(", ")); } return parsed.data; diff --git a/packages/cli/src/utils/manifest.ts b/packages/cli/src/utils/manifest.ts index ddbe5ec..7c1b474 100644 --- a/packages/cli/src/utils/manifest.ts +++ b/packages/cli/src/utils/manifest.ts @@ -1,8 +1,8 @@ import { readFileSync, statSync } from "node:fs"; +import { log, spinner } from "@clack/prompts"; import type { Mixedbread } from "@mixedbread/sdk"; import chalk from "chalk"; import { glob } from "glob"; -import ora from "ora"; import { parse } from "yaml"; import { z } from "zod"; import type { UploadOptions } from "../commands/store/upload"; @@ -172,7 +172,8 @@ export async function uploadFromManifest( // Handle --unique flag: check for existing files let existingFiles: Map = new Map(); if (options.unique) { - const spinner = ora("Checking for existing files...").start(); + const checkSpinner = spinner(); + checkSpinner.start("Checking for existing files..."); try { const storeFiles = await getStoreFiles(client, storeIdentifier); existingFiles = new Map( @@ -190,11 +191,12 @@ export async function uploadFromManifest( ) .map((f) => [(f.metadata as { file_path: string }).file_path, f.id]) ); - spinner.succeed( + checkSpinner.stop( `Found ${formatCountWithSuffix(existingFiles.size, "existing file")}` ); } catch (error) { - spinner.fail("Failed to check existing files"); + checkSpinner.stop(); + log.error("Failed to check existing files"); throw error; } } @@ -207,15 +209,11 @@ export async function uploadFromManifest( }); } catch (error) { if (error instanceof z.ZodError) { - console.error(chalk.red("✗"), "Invalid manifest file format:"); - error.issues.forEach((err) => { - console.error(chalk.red(` - ${err.path.join(".")}: ${err.message}`)); - }); - } else if (error instanceof Error) { - console.error(chalk.red("✗"), error.message); - } else { - console.error(chalk.red("✗"), "Failed to process manifest file"); + const details = error.issues + .map((err) => ` - ${err.path.join(".")}: ${err.message}`) + .join("\n"); + throw new Error(`Invalid manifest file format:\n${details}`); } - process.exit(1); + throw error; } } diff --git a/packages/cli/src/utils/metadata.ts b/packages/cli/src/utils/metadata.ts index 6a1cd90..f1a1611 100644 --- a/packages/cli/src/utils/metadata.ts +++ b/packages/cli/src/utils/metadata.ts @@ -1,10 +1,8 @@ -import chalk from "chalk"; - /** * Validates and parses a JSON metadata string * @param metadataString The JSON string to parse * @returns Parsed metadata object or undefined if input is undefined - * @throws Exits process with code 1 if JSON is invalid + * @throws Error if JSON is invalid */ export function validateMetadata( metadataString?: string @@ -16,7 +14,6 @@ export function validateMetadata( try { return JSON.parse(metadataString); } catch (_error) { - console.error(chalk.red("\n✗"), "Invalid JSON in metadata option\n"); - process.exit(1); + throw new Error("Invalid JSON in metadata option"); } } diff --git a/packages/cli/src/utils/store.ts b/packages/cli/src/utils/store.ts index 93089be..c39e982 100644 --- a/packages/cli/src/utils/store.ts +++ b/packages/cli/src/utils/store.ts @@ -1,11 +1,10 @@ +import { isCancel, select } from "@clack/prompts"; import type { Mixedbread } from "@mixedbread/sdk"; import type { FileListParams, Store, StoreFile, } from "@mixedbread/sdk/resources/stores"; -import chalk from "chalk"; -import inquirer from "inquirer"; import { resolveStoreName } from "./config"; export async function resolveStore( @@ -29,9 +28,9 @@ export async function resolveStore( ); if (fuzzyMatches.length === 0) { - console.error(chalk.red("✗"), `Store "${nameOrId}" not found.\n`); - console.error("Run 'mxbai store list' to see all stores."); - process.exit(1); + throw new Error( + `Store "${nameOrId}" not found.\nRun 'mxbai store list' to see all stores.` + ); } if (fuzzyMatches.length === 1) { @@ -40,26 +39,24 @@ export async function resolveStore( // Multiple fuzzy matches if (interactive) { - const { selected } = await inquirer.prompt([ - { - type: "select", - name: "selected", - message: "Multiple stores found. Select one:", - choices: fuzzyMatches.map((store) => ({ - name: `${store.name} (${store.id})`, - value: store, - })), - }, - ]); + const selected = await select({ + message: "Multiple stores found. Select one:", + options: fuzzyMatches.map((store) => ({ + value: store, + label: `${store.name} (${store.id})`, + })), + }); + if (isCancel(selected)) { + throw new Error("Operation cancelled."); + } return selected; } else { - console.error(chalk.red("✗"), `Store "${nameOrId}" not found.\n`); - console.log("Did you mean one of these?"); - fuzzyMatches.forEach((store) => { - console.log(` • ${store.name}`); - }); - console.log("\nRun 'mxbai store list' to see all stores."); - process.exit(1); + const suggestions = fuzzyMatches + .map((store) => ` • ${store.name}`) + .join("\n"); + throw new Error( + `Store "${nameOrId}" not found.\nDid you mean one of these?\n${suggestions}\n\nRun 'mxbai store list' to see all stores.` + ); } } diff --git a/packages/cli/src/utils/sync.ts b/packages/cli/src/utils/sync.ts index 8cf7875..1aedefb 100644 --- a/packages/cli/src/utils/sync.ts +++ b/packages/cli/src/utils/sync.ts @@ -1,11 +1,11 @@ import { statSync } from "node:fs"; import fs from "node:fs/promises"; import path from "node:path"; +import { log } from "@clack/prompts"; import type Mixedbread from "@mixedbread/sdk"; import type { FileCreateParams } from "@mixedbread/sdk/resources/stores"; import chalk from "chalk"; import { glob } from "glob"; -import ora from "ora"; import pLimit from "p-limit"; import { getChangedFiles, normalizeGitPatterns } from "./git"; import { calculateFileHash, hashesMatch } from "./hash"; @@ -290,21 +290,18 @@ export async function executeSyncChanges( const deletePromises: Promise[] = filesToDelete.map((file) => limit(async () => { - const deleteSpinner = ora( - `Deleting ${path.relative(process.cwd(), file.path)}` - ).start(); try { await client.stores.files.delete(file.fileId!, { store_identifier: storeIdentifier, }); completed++; - deleteSpinner.succeed( + log.success( `[${completed}/${totalOperations}] Deleted ${path.relative(process.cwd(), file.path)}` ); return { file, success: true }; } catch (error) { completed++; - deleteSpinner.fail( + log.error( `[${completed}/${totalOperations}] Failed to delete ${path.relative(process.cwd(), file.path)}: ${error instanceof Error ? error.message : "Unknown error"}` ); return { @@ -339,9 +336,6 @@ export async function executeSyncChanges( const uploadPromises: Promise[] = filesToUpload.map((file) => limit(async () => { - const uploadSpinner = ora( - `Uploading ${path.relative(process.cwd(), file.path)}` - ).start(); try { // Calculate hash if not already done const fileHash = @@ -364,7 +358,7 @@ export async function executeSyncChanges( const stats = statSync(file.path); if (stats.size === 0) { completed++; - uploadSpinner.warn( + log.warn( `[${completed}/${totalOperations}] Skipped empty file ${path.relative(process.cwd(), file.path)}` ); return { file, success: false, skipped: true }; @@ -378,13 +372,13 @@ export async function executeSyncChanges( }); completed++; - uploadSpinner.succeed( + log.success( `[${completed}/${totalOperations}] Uploaded ${path.relative(process.cwd(), file.path)}` ); return { file, success: true }; } catch (error) { completed++; - uploadSpinner.fail( + log.error( `[${completed}/${totalOperations}] Failed to upload ${path.relative(process.cwd(), file.path)}: ${error instanceof Error ? error.message : "Unknown error"}` ); return { diff --git a/packages/cli/src/utils/upload.ts b/packages/cli/src/utils/upload.ts index 9b4cbc2..b3c8d5e 100644 --- a/packages/cli/src/utils/upload.ts +++ b/packages/cli/src/utils/upload.ts @@ -1,10 +1,10 @@ import { readFileSync, statSync } from "node:fs"; import { basename, relative } from "node:path"; +import { log } from "@clack/prompts"; import type Mixedbread from "@mixedbread/sdk"; import type { FileCreateParams } from "@mixedbread/sdk/resources/stores"; import chalk from "chalk"; import { lookup } from "mime-types"; -import ora from "ora"; import { formatBytes, formatCountWithSuffix } from "./output"; export const UPLOAD_TIMEOUT = 1000 * 60 * 10; // 10 minutes @@ -137,13 +137,10 @@ export async function uploadFilesInBatch( for (let i = 0; i < files.length; i += parallel) { const batch = files.slice(i, i + parallel); const promises = batch.map(async (file) => { - const spinner = ora( - `Uploading ${relative(process.cwd(), file.path)}...` - ).start(); + const relativePath = relative(process.cwd(), file.path); try { // Delete existing file if using --unique - const relativePath = relative(process.cwd(), file.path); if (unique && existingFiles.has(relativePath)) { const existingFileId = existingFiles.get(relativePath); await client.stores.files.delete(existingFileId, { @@ -160,9 +157,7 @@ export async function uploadFilesInBatch( // Check if file is empty const stats = statSync(file.path); if (stats.size === 0) { - spinner.warn( - `${relative(process.cwd(), file.path)} - Empty file skipped` - ); + log.warn(`${relativePath} - Empty file skipped`); results.skipped++; return; } @@ -196,18 +191,18 @@ export async function uploadFilesInBatch( results.successfulSize += stats.size; - let successMessage = `${relative(process.cwd(), file.path)} (${formatBytes(stats.size)})`; + let successMessage = `${relativePath} (${formatBytes(stats.size)})`; if (isManifestUpload) { successMessage += ` [${file.strategy}]`; } - spinner.succeed(successMessage); + log.success(successMessage); } catch (error) { results.failed++; const errorMsg = error instanceof Error ? error.message : "Unknown error"; - spinner.fail(`${relative(process.cwd(), file.path)} - ${errorMsg}`); + log.error(`${relativePath} - ${errorMsg}`); } }); @@ -218,9 +213,8 @@ export async function uploadFilesInBatch( console.log(`\n${chalk.bold("Upload Summary:")}`); if (results.uploaded > 0) { console.log( - chalk.green( - `✓ ${formatCountWithSuffix(results.uploaded, "file")} uploaded successfully` - ) + chalk.green("✓"), + `${formatCountWithSuffix(results.uploaded, "file")} uploaded successfully` ); } if (results.updated > 0) { @@ -230,14 +224,14 @@ export async function uploadFilesInBatch( } if (results.skipped > 0) { console.log( - chalk.yellow( - `⚠ ${formatCountWithSuffix(results.skipped, "file")} skipped` - ) + chalk.yellow("⚠"), + `${formatCountWithSuffix(results.skipped, "file")} skipped` ); } if (results.failed > 0) { console.log( - chalk.red(`✗ ${formatCountWithSuffix(results.failed, "file")} failed`) + chalk.red("✗"), + `${formatCountWithSuffix(results.failed, "file")} failed` ); } diff --git a/packages/cli/tests/__mocks__/clack-prompts.ts b/packages/cli/tests/__mocks__/clack-prompts.ts new file mode 100644 index 0000000..d9f412c --- /dev/null +++ b/packages/cli/tests/__mocks__/clack-prompts.ts @@ -0,0 +1,42 @@ +import { jest } from "@jest/globals"; + +// Mock all @clack/prompts functions +const mock = { + confirm: jest.fn(), + text: jest.fn(), + select: jest.fn(), + multiselect: jest.fn(), + isCancel: jest.fn().mockReturnValue(false), + spinner: jest.fn(() => ({ + start: jest.fn(), + stop: jest.fn((msg?: string) => { + if (msg) console.log("✓", msg); + }), + message: jest.fn(), + })), + log: { + info: jest.fn((msg?: string) => { + if (msg) console.log(msg); + }), + success: jest.fn((msg?: string) => { + if (msg) console.log("✓", msg); + }), + warn: jest.fn((msg?: string) => { + if (msg) console.log("⚠", msg); + }), + error: jest.fn((msg?: string) => { + if (msg) console.log("✗", msg); + }), + step: jest.fn((msg?: string) => { + if (msg) console.log(msg); + }), + message: jest.fn((msg?: string) => { + if (msg) console.log(msg); + }), + }, + intro: jest.fn(), + outro: jest.fn(), + cancel: jest.fn(), +}; + +export = mock; diff --git a/packages/cli/tests/__mocks__/inquirer.ts b/packages/cli/tests/__mocks__/inquirer.ts deleted file mode 100644 index 0bd726a..0000000 --- a/packages/cli/tests/__mocks__/inquirer.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { jest } from "@jest/globals"; - -// Mock inquirer to avoid ESM issues in tests -const mockInquirer = { - prompt: jest.fn(), - registerPrompt: jest.fn(), - createPromptModule: jest.fn(), -}; - -export default mockInquirer; diff --git a/packages/cli/tests/__mocks__/ora.ts b/packages/cli/tests/__mocks__/ora.ts deleted file mode 100644 index ae502cf..0000000 --- a/packages/cli/tests/__mocks__/ora.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { jest } from "@jest/globals"; - -// Mock ora to avoid ESM issues in tests -const mockOra = jest.fn(() => ({ - start: jest.fn().mockReturnThis(), - succeed: jest.fn((message?: string) => { - if (message) { - console.log("✓", message); - } - return this; - }), - info: jest.fn((message?: string) => { - if (message) { - console.log(message); - } - return this; - }), - fail: jest.fn((message?: string) => { - if (message) { - console.log("✗", message); - } - return this; - }), - warn: jest.fn((message?: string) => { - if (message) { - console.log("⚠", message); - } - return this; - }), - stop: jest.fn().mockReturnThis(), - text: "", -})); - -export default mockOra; diff --git a/packages/cli/tests/commands/completion.test.ts b/packages/cli/tests/commands/completion.test.ts index a092ead..4fe9240 100644 --- a/packages/cli/tests/commands/completion.test.ts +++ b/packages/cli/tests/commands/completion.test.ts @@ -35,12 +35,6 @@ jest.mock("chalk", () => ({ }, })); -// Mock ora -jest.mock("ora", () => ({ - __esModule: true, - default: jest.fn(), -})); - // Mock completion-cache module jest.mock("../../src/utils/completion-cache", () => ({ getCurrentKeyName: jest.fn(() => "test-key"), @@ -374,28 +368,12 @@ describe("Completion Commands", () => { let mockRefreshAllCaches: jest.MockedFunction< typeof import("../../src/utils/completion-cache").refreshAllCaches >; - let mockSpinner: { - start: jest.MockedFunction<(text?: string) => unknown>; - succeed: jest.MockedFunction<(text?: string) => unknown>; - fail: jest.MockedFunction<(text?: string) => unknown>; - }; beforeEach(() => { const completionCache = jest.mocked( require("../../src/utils/completion-cache") ); mockRefreshAllCaches = completionCache.refreshAllCaches; - - // Setup ora mock - mockSpinner = { - start: jest.fn().mockReturnThis(), - succeed: jest.fn().mockReturnThis(), - fail: jest.fn().mockReturnThis(), - }; - const ora = require("ora").default; - (ora as jest.MockedFunction).mockImplementation( - () => mockSpinner - ); }); it("should refresh completion cache successfully", async () => { @@ -405,9 +383,8 @@ describe("Completion Commands", () => { await parseCommand(command, ["refresh"]); expect(mockRefreshAllCaches).toHaveBeenCalled(); - expect(mockSpinner.start).toHaveBeenCalled(); - expect(mockSpinner.succeed).toHaveBeenCalledWith( - "Completion cache refreshed successfully" + expect(mockConsoleOutput.logs).toContainEqual( + expect.stringContaining("Completion cache refreshed successfully") ); }); @@ -418,10 +395,7 @@ describe("Completion Commands", () => { const command = createCompletionCommand(); await parseCommand(command, ["refresh"]); - expect(mockSpinner.fail).toHaveBeenCalledWith( - "Failed to refresh completion cache" - ); - expect(mockConsoleOutput.errors).toContainEqual( + expect(mockConsoleOutput.logs).toContainEqual( expect.stringContaining(errorMessage) ); }); diff --git a/packages/cli/tests/commands/config/keys.test.ts b/packages/cli/tests/commands/config/keys.test.ts index e4d242d..ea25396 100644 --- a/packages/cli/tests/commands/config/keys.test.ts +++ b/packages/cli/tests/commands/config/keys.test.ts @@ -13,20 +13,21 @@ import { createKeysCommand } from "../../../src/commands/config/keys"; import { loadConfig } from "../../../src/utils/config"; import { getTestConfigDir } from "../../helpers/test-utils"; -// Mock inquirer -jest.mock("inquirer"); +// Mock @clack/prompts +jest.mock("@clack/prompts"); describe("Config Keys Command", () => { const configDir = getTestConfigDir(); const configFile = join(configDir, "config.json"); let command: Command; - let mockInquirer: jest.Mocked; + let mockClack: jest.Mocked; beforeEach(async () => { command = createKeysCommand(); - mockInquirer = (await import("inquirer")).default as jest.Mocked< - typeof import("inquirer").default + mockClack = (await import("@clack/prompts")) as unknown as jest.Mocked< + typeof import("@clack/prompts") >; + mockClack.isCancel.mockReturnValue(false); jest.clearAllMocks(); }); @@ -47,7 +48,7 @@ describe("Config Keys Command", () => { expect(config.api_keys?.work).toBe("mxb_test123"); expect(config.defaults?.api_key).toBe("work"); expect(console.log).toHaveBeenCalledWith( - expect.stringContaining("✓"), + "✓", expect.stringContaining('API key "work" saved and set as default') ); }); @@ -57,16 +58,14 @@ describe("Config Keys Command", () => { [configFile]: JSON.stringify({ version: "1.0", api_keys: {} }), }); - mockInquirer.prompt.mockResolvedValue({ name: "personal" }); + mockClack.text.mockResolvedValue("personal"); await command.parseAsync(["node", "keys", "add", "mxb_test123"]); const config = loadConfig(); expect(config.api_keys?.personal).toBe("mxb_test123"); expect(config.defaults?.api_key).toBe("personal"); - expect(mockInquirer.prompt).toHaveBeenCalledWith({ - type: "input", - name: "name", + expect(mockClack.text).toHaveBeenCalledWith({ message: expect.stringContaining("Enter a name for this API key"), validate: expect.any(Function), }); @@ -79,8 +78,8 @@ describe("Config Keys Command", () => { command.parse(["node", "keys", "add", "invalid_key", "test"]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), + expect(console.log).toHaveBeenCalledWith( + "✗", 'API key must start with "mxb_"' ); }); @@ -92,10 +91,7 @@ describe("Config Keys Command", () => { command.parse(["node", "keys", "add", "mxb_test123", " "]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), - "Name cannot be empty" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Name cannot be empty"); }); it("should check for duplicate names", () => { @@ -108,8 +104,8 @@ describe("Config Keys Command", () => { command.parse(["node", "keys", "add", "mxb_test123", "work"]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), + expect(console.log).toHaveBeenCalledWith( + "✗", 'API key "work" already exists' ); }); @@ -122,17 +118,17 @@ describe("Config Keys Command", () => { }), }); - mockInquirer.prompt.mockResolvedValue({ name: "new" }); + mockClack.text.mockResolvedValue("new"); await command.parseAsync(["node", "keys", "add", "mxb_test123"]); - const promptCall = mockInquirer.prompt.mock.calls[0][0] as any; + const promptCall = mockClack.text.mock.calls[0][0] as any; const validate = promptCall.validate; expect(validate("")).toBe("Name cannot be empty"); expect(validate(" ")).toBe("Name cannot be empty"); expect(validate("existing")).toBe('API key "existing" already exists'); - expect(validate("different")).toBe(true); + expect(validate("different")).toBeUndefined(); }); }); @@ -191,17 +187,14 @@ describe("Config Keys Command", () => { }), }); - mockInquirer.prompt.mockResolvedValue({ confirm: true }); + mockClack.confirm.mockResolvedValue(true); await command.parseAsync(["node", "keys", "remove", "work"]); const config = loadConfig(); expect(config.api_keys?.work).toBeUndefined(); expect(config.api_keys?.personal).toBe("mxb_personal123"); - expect(console.log).toHaveBeenCalledWith( - expect.stringContaining("✓"), - 'API key "work" removed' - ); + expect(console.log).toHaveBeenCalledWith("✓", 'API key "work" removed'); }); it("should warn when removing default API key", async () => { @@ -218,14 +211,14 @@ describe("Config Keys Command", () => { }), }); - mockInquirer.prompt.mockResolvedValue({ confirm: true }); + mockClack.confirm.mockResolvedValue(true); await command.parseAsync(["node", "keys", "remove", "work"]); const config = loadConfig(); expect(config.defaults?.api_key).toBeUndefined(); expect(console.log).toHaveBeenCalledWith( - expect.stringContaining("⚠"), + "⚠", "No default API key set. Set a new default:" ); expect(console.log).toHaveBeenCalledWith( @@ -243,13 +236,14 @@ describe("Config Keys Command", () => { }), }); - mockInquirer.prompt.mockResolvedValue({ confirm: false }); + mockClack.confirm.mockResolvedValue(false); await command.parseAsync(["node", "keys", "remove", "work"]); const config = loadConfig(); expect(config.api_keys?.work).toBe("mxb_work123"); expect(console.log).toHaveBeenCalledWith( + "⚠", expect.stringContaining("Removal cancelled.") ); }); @@ -266,8 +260,8 @@ describe("Config Keys Command", () => { await command.parseAsync(["node", "keys", "remove", "nonexistent"]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), + expect(console.log).toHaveBeenCalledWith( + "✗", 'No API key found with name "nonexistent"' ); }); @@ -288,12 +282,9 @@ describe("Config Keys Command", () => { const config = loadConfig(); expect(config.api_keys?.work).toBeUndefined(); expect(config.api_keys?.personal).toBe("mxb_personal123"); - expect(console.log).toHaveBeenCalledWith( - expect.stringContaining("✓"), - 'API key "work" removed' - ); - // Should not call inquirer.prompt when using --yes - expect(mockInquirer.prompt).not.toHaveBeenCalled(); + expect(console.log).toHaveBeenCalledWith("✓", 'API key "work" removed'); + // Should not call confirm when using --yes + expect(mockClack.confirm).not.toHaveBeenCalled(); }); it("should remove default API key with --yes flag without confirmation", async () => { @@ -315,16 +306,13 @@ describe("Config Keys Command", () => { const config = loadConfig(); expect(config.api_keys?.work).toBeUndefined(); expect(config.defaults?.api_key).toBeUndefined(); + expect(console.log).toHaveBeenCalledWith("✓", 'API key "work" removed'); expect(console.log).toHaveBeenCalledWith( - expect.stringContaining("✓"), - 'API key "work" removed' - ); - expect(console.log).toHaveBeenCalledWith( - expect.stringContaining("⚠"), + "⚠", "No default API key set. Set a new default:" ); - // Should not call inquirer.prompt when using --yes - expect(mockInquirer.prompt).not.toHaveBeenCalled(); + // Should not call confirm when using --yes + expect(mockClack.confirm).not.toHaveBeenCalled(); }); it("should handle --yes flag with non-existent key", async () => { @@ -345,12 +333,12 @@ describe("Config Keys Command", () => { "--yes", ]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), + expect(console.log).toHaveBeenCalledWith( + "✗", 'No API key found with name "nonexistent"' ); - // Should not call inquirer.prompt even with --yes when key doesn't exist - expect(mockInquirer.prompt).not.toHaveBeenCalled(); + // Should not call confirm even with --yes when key doesn't exist + expect(mockClack.confirm).not.toHaveBeenCalled(); }); }); @@ -374,7 +362,7 @@ describe("Config Keys Command", () => { const config = loadConfig(); expect(config.defaults?.api_key).toBe("personal"); expect(console.log).toHaveBeenCalledWith( - expect.stringContaining("✓"), + "✓", '"personal" set as default API key' ); }); @@ -391,8 +379,8 @@ describe("Config Keys Command", () => { command.parse(["node", "keys", "set-default", "nonexistent"]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), + expect(console.log).toHaveBeenCalledWith( + "✗", 'No API key found with name "nonexistent"' ); expect(console.log).toHaveBeenCalledWith("\nAvailable API keys:"); diff --git a/packages/cli/tests/commands/config/set.test.ts b/packages/cli/tests/commands/config/set.test.ts index 9727b36..342ae76 100644 --- a/packages/cli/tests/commands/config/set.test.ts +++ b/packages/cli/tests/commands/config/set.test.ts @@ -131,6 +131,7 @@ describe("Config Set Command", () => { expect(console.error).toHaveBeenCalledWith( expect.any(String), + "Failed to set configuration:", expect.stringContaining("Invalid value for defaults.upload.strategy:") ); expect(process.exit).toHaveBeenCalledWith(1); @@ -165,6 +166,7 @@ describe("Config Set Command", () => { expect(console.error).toHaveBeenCalledWith( expect.any(String), + "Failed to set configuration:", expect.stringContaining("Invalid value for defaults.upload.parallel:") ); expect(process.exit).toHaveBeenCalledWith(1); @@ -179,6 +181,7 @@ describe("Config Set Command", () => { expect(console.error).toHaveBeenCalledWith( expect.any(String), + "Failed to set configuration:", expect.stringContaining("Unknown config key: unknown.key") ); expect(process.exit).toHaveBeenCalledWith(1); diff --git a/packages/cli/tests/commands/store/create.test.ts b/packages/cli/tests/commands/store/create.test.ts index 0c8cd48..37bfdca 100644 --- a/packages/cli/tests/commands/store/create.test.ts +++ b/packages/cli/tests/commands/store/create.test.ts @@ -201,9 +201,9 @@ describe("Store Create Command", () => { "invalid-json", ]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), - expect.stringContaining("Invalid JSON in metadata option") + expect(console.log).toHaveBeenCalledWith( + "✗", + "Invalid JSON in metadata option" ); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -304,10 +304,7 @@ describe("Store Create Command", () => { await command.parseAsync(["node", "create", "test-store"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "API Error: Unauthorized" - ); + expect(console.log).toHaveBeenCalledWith("✗", "API Error: Unauthorized"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -317,10 +314,7 @@ describe("Store Create Command", () => { await command.parseAsync(["node", "create", "test-store"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Network error" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Network error"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -329,10 +323,7 @@ describe("Store Create Command", () => { await command.parseAsync(["node", "create", "test-store"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Failed to create store" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Failed to create store"); expect(process.exit).toHaveBeenCalledWith(1); }); }); @@ -512,8 +503,8 @@ describe("Store Create Command", () => { "--contextualization=,,,", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining("Invalid value for --contextualization") ); expect(process.exit).toHaveBeenCalledWith(1); @@ -565,8 +556,8 @@ describe("Store Create Command", () => { "-5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"expires-after" must be positive') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -581,8 +572,8 @@ describe("Store Create Command", () => { "5.5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"expires-after" must be an integer') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -591,8 +582,8 @@ describe("Store Create Command", () => { it("should validate name is not empty", async () => { await command.parseAsync(["node", "create", ""]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"name" is required') ); expect(process.exit).toHaveBeenCalledWith(1); diff --git a/packages/cli/tests/commands/store/delete.test.ts b/packages/cli/tests/commands/store/delete.test.ts index 26afe0d..f86ca4f 100644 --- a/packages/cli/tests/commands/store/delete.test.ts +++ b/packages/cli/tests/commands/store/delete.test.ts @@ -100,10 +100,7 @@ describe("Delete Command", () => { await command.parseAsync(["node", "delete", "test-store", "--yes"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "API Error: Unauthorized" - ); + expect(console.log).toHaveBeenCalledWith("✗", "API Error: Unauthorized"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -118,10 +115,7 @@ describe("Delete Command", () => { "--yes", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Store not found" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Store not found"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -130,10 +124,7 @@ describe("Delete Command", () => { await command.parseAsync(["node", "delete", "test-store", "--yes"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Failed to delete store" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Failed to delete store"); expect(process.exit).toHaveBeenCalledWith(1); }); }); @@ -166,8 +157,8 @@ describe("Delete Command", () => { it("should validate required name-or-id argument", async () => { await command.parseAsync(["node", "delete", "", "--yes"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"name-or-id" is required') ); expect(process.exit).toHaveBeenCalledWith(1); diff --git a/packages/cli/tests/commands/store/files.test.ts b/packages/cli/tests/commands/store/files.test.ts index bd78439..cfdf206 100644 --- a/packages/cli/tests/commands/store/files.test.ts +++ b/packages/cli/tests/commands/store/files.test.ts @@ -245,8 +245,8 @@ describe("Files Command", () => { "-5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"limit" must be positive') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -262,8 +262,8 @@ describe("Files Command", () => { "invalid", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining( '"status" must be one of: all, completed, in_progress, failed' ) @@ -367,10 +367,7 @@ describe("Files Command", () => { "file_123", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "File not found" - ); + expect(console.log).toHaveBeenCalledWith("✗", "File not found"); expect(process.exit).toHaveBeenCalledWith(1); }); }); @@ -418,10 +415,7 @@ describe("Files Command", () => { "--yes", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "API Error: Unauthorized" - ); + expect(console.log).toHaveBeenCalledWith("✗", "API Error: Unauthorized"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -437,10 +431,7 @@ describe("Files Command", () => { "--yes", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Failed to delete file" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Failed to delete file"); expect(process.exit).toHaveBeenCalledWith(1); }); }); @@ -449,8 +440,8 @@ describe("Files Command", () => { it("should validate required name-or-id argument for list", async () => { await command.parseAsync(["node", "files", "list", ""]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"name-or-id" is required') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -459,8 +450,8 @@ describe("Files Command", () => { it("should validate required file-id argument for get", async () => { await command.parseAsync(["node", "files", "get", "test-store", ""]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"file-id" is required') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -476,8 +467,8 @@ describe("Files Command", () => { "--yes", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"file-id" is required') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -529,10 +520,7 @@ describe("Files Command", () => { await command.parseAsync(["node", "files", "list", "nonexistent-store"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Store not found" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Store not found"); expect(process.exit).toHaveBeenCalledWith(1); }); }); diff --git a/packages/cli/tests/commands/store/get.test.ts b/packages/cli/tests/commands/store/get.test.ts index c74c0d7..2aa463b 100644 --- a/packages/cli/tests/commands/store/get.test.ts +++ b/packages/cli/tests/commands/store/get.test.ts @@ -226,10 +226,7 @@ describe("Store Get Command", () => { await command.parseAsync(["node", "get", "nonexistent-store"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Store not found" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Store not found"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -238,8 +235,8 @@ describe("Store Get Command", () => { await command.parseAsync(["node", "get", "test-store"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", "Failed to get store details" ); expect(process.exit).toHaveBeenCalledWith(1); @@ -286,8 +283,8 @@ describe("Store Get Command", () => { it("should validate required name-or-id argument", async () => { await command.parseAsync(["node", "get", ""]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"name-or-id" is required') ); expect(process.exit).toHaveBeenCalledWith(1); diff --git a/packages/cli/tests/commands/store/list.test.ts b/packages/cli/tests/commands/store/list.test.ts index 9db09bf..e86b2f2 100644 --- a/packages/cli/tests/commands/store/list.test.ts +++ b/packages/cli/tests/commands/store/list.test.ts @@ -211,8 +211,8 @@ describe("Store List Command", () => { it("should validate limit is positive", async () => { await command.parseAsync(["node", "list", "--limit", "-5"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"limit" must be positive') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -288,8 +288,8 @@ describe("Store List Command", () => { await command.parseAsync(["node", "list"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", "API Error: Rate limit exceeded" ); expect(process.exit).toHaveBeenCalledWith(1); @@ -301,10 +301,7 @@ describe("Store List Command", () => { await command.parseAsync(["node", "list"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "ECONNREFUSED" - ); + expect(console.log).toHaveBeenCalledWith("✗", "ECONNREFUSED"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -313,10 +310,7 @@ describe("Store List Command", () => { await command.parseAsync(["node", "list"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Failed to list stores" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Failed to list stores"); expect(process.exit).toHaveBeenCalledWith(1); }); }); @@ -399,8 +393,8 @@ describe("Store List Command", () => { "not-a-valid-url", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"base-url" must be a valid URL') ); expect(process.exit).toHaveBeenCalledWith(1); diff --git a/packages/cli/tests/commands/store/qa.test.ts b/packages/cli/tests/commands/store/qa.test.ts index 7e16029..b039039 100644 --- a/packages/cli/tests/commands/store/qa.test.ts +++ b/packages/cli/tests/commands/store/qa.test.ts @@ -384,8 +384,8 @@ describe("QA Command", () => { it("should validate required name-or-id argument", async () => { await command.parseAsync(["node", "qa", "", "question"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"name-or-id" is required') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -394,8 +394,8 @@ describe("QA Command", () => { it("should validate required question argument", async () => { await command.parseAsync(["node", "qa", "test-store", ""]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"question" is required') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -411,8 +411,8 @@ describe("QA Command", () => { "-5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"top-k" must be positive') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -428,8 +428,8 @@ describe("QA Command", () => { "5.5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"top-k" must be an integer') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -445,8 +445,8 @@ describe("QA Command", () => { "150", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"top-k" must be less than or equal to 100') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -462,8 +462,8 @@ describe("QA Command", () => { "-0.1", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining( '"threshold" must be greater than or equal to 0' ) @@ -481,8 +481,8 @@ describe("QA Command", () => { "1.5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"threshold" must be less than or equal to 1') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -496,8 +496,8 @@ describe("QA Command", () => { await command.parseAsync(["node", "qa", "test-store", "question"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", "API Error: Service unavailable" ); expect(process.exit).toHaveBeenCalledWith(1); @@ -509,10 +509,7 @@ describe("QA Command", () => { await command.parseAsync(["node", "qa", "nonexistent-store", "question"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Store not found" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Store not found"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -521,8 +518,8 @@ describe("QA Command", () => { await command.parseAsync(["node", "qa", "test-store", "question"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", "Failed to process question" ); expect(process.exit).toHaveBeenCalledWith(1); diff --git a/packages/cli/tests/commands/store/search.test.ts b/packages/cli/tests/commands/store/search.test.ts index 5f99262..ef413cd 100644 --- a/packages/cli/tests/commands/store/search.test.ts +++ b/packages/cli/tests/commands/store/search.test.ts @@ -431,8 +431,8 @@ describe("Store Search Command", () => { it("should validate required name-or-id argument", async () => { await command.parseAsync(["node", "search", "", "query"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"name-or-id" is required') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -441,8 +441,8 @@ describe("Store Search Command", () => { it("should validate required query argument", async () => { await command.parseAsync(["node", "search", "test-store", ""]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"query" is required') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -458,8 +458,8 @@ describe("Store Search Command", () => { "-5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"top-k" must be positive') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -475,8 +475,8 @@ describe("Store Search Command", () => { "5.5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"top-k" must be an integer') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -492,8 +492,8 @@ describe("Store Search Command", () => { "150", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"top-k" must be less than or equal to 100') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -509,8 +509,8 @@ describe("Store Search Command", () => { "-0.1", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining( '"threshold" must be greater than or equal to 0' ) @@ -528,8 +528,8 @@ describe("Store Search Command", () => { "1.5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"threshold" must be less than or equal to 1') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -543,8 +543,8 @@ describe("Store Search Command", () => { await command.parseAsync(["node", "search", "test-store", "query"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", "API Error: Rate limit exceeded" ); expect(process.exit).toHaveBeenCalledWith(1); @@ -561,10 +561,7 @@ describe("Store Search Command", () => { "query", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Store not found" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Store not found"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -573,10 +570,7 @@ describe("Store Search Command", () => { await command.parseAsync(["node", "search", "test-store", "query"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Failed to search store" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Failed to search store"); expect(process.exit).toHaveBeenCalledWith(1); }); }); diff --git a/packages/cli/tests/commands/store/sync.test.ts b/packages/cli/tests/commands/store/sync.test.ts index 772c237..d7d95ea 100644 --- a/packages/cli/tests/commands/store/sync.test.ts +++ b/packages/cli/tests/commands/store/sync.test.ts @@ -175,8 +175,8 @@ describe("Store Sync Command", () => { it("should validate required arguments", async () => { await command.parseAsync(["node", "sync"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining("required") ); }); diff --git a/packages/cli/tests/commands/store/update.test.ts b/packages/cli/tests/commands/store/update.test.ts index 903289f..2a52a26 100644 --- a/packages/cli/tests/commands/store/update.test.ts +++ b/packages/cli/tests/commands/store/update.test.ts @@ -258,12 +258,7 @@ describe("Store Update Command", () => { mockClient.stores.update.mockResolvedValue(updatedStore); - await command.parseAsync([ - "node", - "update", - "test-store", - "--public", - ]); + await command.parseAsync(["node", "update", "test-store", "--public"]); expect(mockClient.stores.update).toHaveBeenCalledWith( "550e8400-e29b-41d4-a716-446655440060", @@ -385,8 +380,8 @@ describe("Store Update Command", () => { "invalid-json", ]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining("Invalid JSON in metadata option") ); expect(process.exit).toHaveBeenCalledWith(1); @@ -496,8 +491,8 @@ describe("Store Update Command", () => { "-5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"expires-after" must be positive') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -512,8 +507,8 @@ describe("Store Update Command", () => { "5.5", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"expires-after" must be an integer') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -522,8 +517,8 @@ describe("Store Update Command", () => { it("should require at least one update field", async () => { await command.parseAsync(["node", "update", "test-store"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining( "No update fields provided. Use --name, --description, --public, or --metadata" ) @@ -534,8 +529,8 @@ describe("Store Update Command", () => { it("should validate required name-or-id argument", async () => { await command.parseAsync(["node", "update", "", "--name", "new-name"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"name-or-id" is required') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -555,10 +550,7 @@ describe("Store Update Command", () => { "new-name", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "API Error: Unauthorized" - ); + expect(console.log).toHaveBeenCalledWith("✗", "API Error: Unauthorized"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -574,10 +566,7 @@ describe("Store Update Command", () => { "new-name", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Store not found" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Store not found"); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -592,10 +581,7 @@ describe("Store Update Command", () => { "new-name", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Failed to update store" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Failed to update store"); expect(process.exit).toHaveBeenCalledWith(1); }); }); diff --git a/packages/cli/tests/commands/store/upload.test.ts b/packages/cli/tests/commands/store/upload.test.ts index dd1bf72..9e38b16 100644 --- a/packages/cli/tests/commands/store/upload.test.ts +++ b/packages/cli/tests/commands/store/upload.test.ts @@ -180,6 +180,7 @@ describe("Store Upload Command", () => { ]); expect(console.log).toHaveBeenCalledWith( + "⚠", expect.stringContaining("No files found matching the patterns") ); }); @@ -187,8 +188,8 @@ describe("Store Upload Command", () => { it("should error when no patterns provided", async () => { await command.parseAsync(["node", "upload", "test-store"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", "No file patterns provided. Use --manifest for manifest-based uploads." ); expect(process.exit).toHaveBeenCalledWith(1); @@ -283,9 +284,9 @@ describe("Store Upload Command", () => { "invalid-json", ]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), - expect.stringContaining("Invalid JSON in metadata option") + expect(console.log).toHaveBeenCalledWith( + "✗", + "Invalid JSON in metadata option" ); expect(process.exit).toHaveBeenCalledWith(1); }); @@ -300,8 +301,8 @@ describe("Store Upload Command", () => { "201", ]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining('"parallel" must be less than or equal to 200') ); expect(process.exit).toHaveBeenCalledWith(1); @@ -403,8 +404,8 @@ describe("Store Upload Command", () => { "--unique", ]); - expect(console.error).toHaveBeenCalledWith( - expect.stringContaining("✗"), + expect(console.log).toHaveBeenCalledWith( + "✗", expect.stringContaining("List failed") ); expect(process.exit).toHaveBeenCalledWith(1); @@ -489,10 +490,7 @@ describe("Store Upload Command", () => { await command.parseAsync(["node", "upload", "invalid-store", "*.md"]); - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Store not found" - ); + expect(console.log).toHaveBeenCalledWith("✗", "Store not found"); expect(process.exit).toHaveBeenCalledWith(1); }); diff --git a/packages/cli/tests/utils/config.test.ts b/packages/cli/tests/utils/config.test.ts index 0e21a78..74e896f 100644 --- a/packages/cli/tests/utils/config.test.ts +++ b/packages/cli/tests/utils/config.test.ts @@ -175,22 +175,10 @@ describe("Config Utils", () => { expect(apiKey).toBe("mxb_cli123"); }); - it("should exit with error when --api-key has invalid format", () => { - // Mock process.exit to throw an error to stop execution - const mockExit = jest.fn().mockImplementation(() => { - throw new Error("process.exit called"); - }); - process.exit = mockExit as never; - + it("should throw error when --api-key has invalid format", () => { expect(() => { getApiKey({ apiKey: "invalid_key_format" }); - }).toThrow("process.exit called"); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "Invalid API key format. API keys must start with 'mxb_'." - ); - expect(process.exit).toHaveBeenCalledWith(1); + }).toThrow("Invalid API key format. API keys must start with 'mxb_'."); }); it("should resolve API key name from --saved-key option", () => { @@ -248,7 +236,7 @@ describe("Config Utils", () => { expect(apiKey).toBe("mxb_work123"); }); - it("should prompt for migration when old format detected", () => { + it("should throw error for migration when old format detected", () => { delete process.env.MXBAI_API_KEY; mockFs({ [configFile]: JSON.stringify({ @@ -258,15 +246,12 @@ describe("Config Utils", () => { }), }); - getApiKey(); - - expect(console.log).toHaveBeenCalledWith( - expect.stringContaining("⚠ Migration Required") - ); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + getApiKey(); + }).toThrow("Migration Required"); }); - it("should show available keys when no default set", () => { + it("should throw error with available keys when no default set", () => { delete process.env.MXBAI_API_KEY; mockFs({ [configFile]: JSON.stringify({ @@ -278,17 +263,12 @@ describe("Config Utils", () => { }), }); - getApiKey(); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "No default API key set.\n" - ); - expect(console.log).toHaveBeenCalledWith("Available API keys:"); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + getApiKey(); + }).toThrow("No default API key set."); }); - it("should exit with error when saved API key name not found", () => { + it("should throw error when saved API key name not found", () => { mockFs({ [configFile]: JSON.stringify({ version: "1.0", @@ -298,24 +278,12 @@ describe("Config Utils", () => { }), }); - // Mock process.exit to throw an error to stop execution - const mockExit = jest.fn().mockImplementation(() => { - throw new Error("process.exit called"); - }); - process.exit = mockExit as never; - expect(() => { getApiKey({ savedKey: "nonexistent" }); - }).toThrow("process.exit called"); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - 'No saved API key found with name "nonexistent".' - ); - expect(process.exit).toHaveBeenCalledWith(1); + }).toThrow('No saved API key found with name "nonexistent".'); }); - it("should exit with error when no API key found", () => { + it("should throw error when no API key found", () => { delete process.env.MXBAI_API_KEY; mockFs({ [configFile]: JSON.stringify({ @@ -324,13 +292,9 @@ describe("Config Utils", () => { }), }); - getApiKey(); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - "No API key found.\n" - ); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + getApiKey(); + }).toThrow("No API key found."); }); }); @@ -472,51 +436,27 @@ describe("Config Utils", () => { }); it("should validate URL format", () => { - parseConfigValue("base_url", "invalid_url"); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining("Invalid value for base_url:") - ); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + parseConfigValue("base_url", "invalid_url"); + }).toThrow("Invalid value for base_url:"); }); it("should validate enum constraints", () => { - parseConfigValue("defaults.upload.strategy", "invalid_strategy"); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining("Invalid value for defaults.upload.strategy:") - ); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + parseConfigValue("defaults.upload.strategy", "invalid_strategy"); + }).toThrow("Invalid value for defaults.upload.strategy:"); }); it("should validate number constraints", () => { - parseConfigValue("defaults.upload.parallel", "-5"); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining("Invalid value for defaults.upload.parallel:") - ); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + parseConfigValue("defaults.upload.parallel", "-5"); + }).toThrow("Invalid value for defaults.upload.parallel:"); }); it("should handle unknown config keys", () => { - // Mock process.exit to throw an error to stop execution - const mockExit = jest.fn().mockImplementation(() => { - throw new Error("process.exit called"); - }); - process.exit = mockExit as never; - expect(() => { parseConfigValue("unknown.key", "value"); - }).toThrow("process.exit called"); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining("Unknown config key: unknown.key") - ); - expect(process.exit).toHaveBeenCalledWith(1); + }).toThrow("Unknown config key: unknown.key"); }); }); }); diff --git a/packages/cli/tests/utils/global-options.test.ts b/packages/cli/tests/utils/global-options.test.ts index fc6e47a..b8dac9b 100644 --- a/packages/cli/tests/utils/global-options.test.ts +++ b/packages/cli/tests/utils/global-options.test.ts @@ -171,15 +171,9 @@ describe("Global Options", () => { savedKey: "work", }; - parseOptions(GlobalOptionsSchema, options); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining( - "Cannot specify both --api-key and --saved-key options" - ) - ); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + parseOptions(GlobalOptionsSchema, options); + }).toThrow("Cannot specify both --api-key and --saved-key options"); }); it("should allow optional fields to be undefined", () => { @@ -214,15 +208,9 @@ describe("Global Options", () => { format: "invalid", }; - parseOptions(GlobalOptionsSchema, options); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining( - '"format" must be either "table", "json", or "csv"' - ) - ); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + parseOptions(GlobalOptionsSchema, options); + }).toThrow('"format" must be either "table", "json", or "csv"'); }); it("should handle validation errors for format", () => { @@ -231,15 +219,9 @@ describe("Global Options", () => { format: "invalid", }; - parseOptions(GlobalOptionsSchema, options); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining( - '"format" must be either "table", "json", or "csv"' - ) - ); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + parseOptions(GlobalOptionsSchema, options); + }).toThrow('"format" must be either "table", "json", or "csv"'); }); it("should parse custom schemas", () => { @@ -269,15 +251,9 @@ describe("Global Options", () => { customField: "test", }; - parseOptions(customSchema, options); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining( - "Cannot specify both --api-key and --saved-key options" - ) - ); - expect(process.exit).toHaveBeenCalledWith(1); + expect(() => { + parseOptions(customSchema, options); + }).toThrow("Cannot specify both --api-key and --saved-key options"); }); }); }); diff --git a/packages/cli/tests/utils/vector-store.test.ts b/packages/cli/tests/utils/vector-store.test.ts index b2db558..ce38ea2 100644 --- a/packages/cli/tests/utils/vector-store.test.ts +++ b/packages/cli/tests/utils/vector-store.test.ts @@ -114,18 +114,14 @@ describe("Store Utils", () => { ); mockClient.stores.list.mockResolvedValue({ data: [] }); - await resolveStore( - mockClient as unknown as Mixedbread, - "550e8400-e29b-41d4-a716-446655440002" - ); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining( - 'Store "550e8400-e29b-41d4-a716-446655440002" not found' + await expect( + resolveStore( + mockClient as unknown as Mixedbread, + "550e8400-e29b-41d4-a716-446655440002" ) + ).rejects.toThrow( + 'Store "550e8400-e29b-41d4-a716-446655440002" not found' ); - expect(process.exit).toHaveBeenCalledWith(1); }); it("should handle store not found by name", async () => { @@ -139,15 +135,12 @@ describe("Store Utils", () => { ], }); - await resolveStore( - mockClient as unknown as Mixedbread, - "nonexistent-store" - ); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining('Store "nonexistent-store" not found') - ); + await expect( + resolveStore( + mockClient as unknown as Mixedbread, + "nonexistent-store" + ) + ).rejects.toThrow('Store "nonexistent-store" not found'); }); it("should handle empty store list", async () => { @@ -156,12 +149,9 @@ describe("Store Utils", () => { mockClient.stores.list.mockResolvedValue({ data: [] }); - await resolveStore(mockClient as unknown as Mixedbread, "any-store"); - - expect(console.error).toHaveBeenCalledWith( - expect.any(String), - expect.stringContaining('Store "any-store" not found') - ); + await expect( + resolveStore(mockClient as unknown as Mixedbread, "any-store") + ).rejects.toThrow('Store "any-store" not found'); }); it("should handle API errors when listing", async () => { @@ -219,10 +209,12 @@ describe("Store Utils", () => { mockClient.stores.retrieve.mockRejectedValue(new Error("Not found")); mockClient.stores.list.mockResolvedValue({ data: [] }); - await resolveStore( - mockClient as unknown as Mixedbread, - "550e8400-e29b-41d4-a716-446655440003" - ); + await expect( + resolveStore( + mockClient as unknown as Mixedbread, + "550e8400-e29b-41d4-a716-446655440003" + ) + ).rejects.toThrow(); expect(mockClient.stores.retrieve).toHaveBeenCalledWith( "550e8400-e29b-41d4-a716-446655440003" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1e2f16f..5810640 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: packages/cli: dependencies: + '@clack/prompts': + specifier: ^1.0.1 + version: 1.0.1 '@mixedbread/sdk': specifier: ^0.51.0 version: 0.51.0 @@ -47,18 +50,12 @@ importers: glob: specifier: ^13.0.3 version: 13.0.3 - inquirer: - specifier: ^13.2.2 - version: 13.2.2(@types/node@25.2.3) mime-types: specifier: ^3.0.2 version: 3.0.2 minimatch: specifier: ^10.2.0 version: 10.2.0 - ora: - specifier: ^9.3.0 - version: 9.3.0 p-limit: specifier: ^7.3.0 version: 7.3.0 @@ -72,9 +69,6 @@ importers: '@jest/globals': specifier: ^30.2.0 version: 30.2.0 - '@types/inquirer': - specifier: ^9.0.9 - version: 9.0.9 '@types/jest': specifier: ^30.0.0 version: 30.0.0 @@ -477,6 +471,12 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@clack/core@1.0.1': + resolution: {integrity: sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==} + + '@clack/prompts@1.0.1': + resolution: {integrity: sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==} + '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -619,55 +619,6 @@ packages: cpu: [x64] os: [win32] - '@inquirer/ansi@2.0.3': - resolution: {integrity: sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - - '@inquirer/checkbox@5.0.4': - resolution: {integrity: sha512-DrAMU3YBGMUAp6ArwTIp/25CNDtDbxk7UjIrrtM25JVVrlVYlVzHh5HR1BDFu9JMyUoZ4ZanzeaHqNDttf3gVg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/confirm@6.0.4': - resolution: {integrity: sha512-WdaPe7foUnoGYvXzH4jp4wH/3l+dBhZ3uwhKjXjwdrq5tEIFaANxj6zrGHxLdsIA0yKM0kFPVcEalOZXBB5ISA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/core@11.1.1': - resolution: {integrity: sha512-hV9o15UxX46OyQAtaoMqAOxGR8RVl1aZtDx1jHbCtSJy1tBdTfKxLPKf7utsE4cRy4tcmCQ4+vdV+ca+oNxqNA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/editor@5.0.4': - resolution: {integrity: sha512-QI3Jfqcv6UO2/VJaEFONH8Im1ll++Xn/AJTBn9Xf+qx2M+H8KZAdQ5sAe2vtYlo+mLW+d7JaMJB4qWtK4BG3pw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/expand@5.0.4': - resolution: {integrity: sha512-0I/16YwPPP0Co7a5MsomlZLpch48NzYfToyqYAOWtBmaXSB80RiNQ1J+0xx2eG+Wfxt0nHtpEWSRr6CzNVnOGg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@inquirer/external-editor@1.0.1': resolution: {integrity: sha512-Oau4yL24d2B5IL4ma4UpbQigkVhzPDXLoqy1ggK4gnHg/stmkffJE4oOXHXF3uz0UEpywG68KcyXsyYpA1Re/Q==} engines: {node: '>=18'} @@ -677,91 +628,6 @@ packages: '@types/node': optional: true - '@inquirer/external-editor@2.0.3': - resolution: {integrity: sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/figures@2.0.3': - resolution: {integrity: sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - - '@inquirer/input@5.0.4': - resolution: {integrity: sha512-4B3s3jvTREDFvXWit92Yc6jF1RJMDy2VpSqKtm4We2oVU65YOh2szY5/G14h4fHlyQdpUmazU5MPCFZPRJ0AOw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/number@4.0.4': - resolution: {integrity: sha512-CmMp9LF5HwE+G/xWsC333TlCzYYbXMkcADkKzcawh49fg2a1ryLc7JL1NJYYt1lJ+8f4slikNjJM9TEL/AljYQ==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/password@5.0.4': - resolution: {integrity: sha512-ZCEPyVYvHK4W4p2Gy6sTp9nqsdHQCfiPXIP9LbJVW4yCinnxL/dDDmPaEZVysGrj8vxVReRnpfS2fOeODe9zjg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/prompts@8.2.0': - resolution: {integrity: sha512-rqTzOprAj55a27jctS3vhvDDJzYXsr33WXTjODgVOru21NvBo9yIgLIAf7SBdSV0WERVly3dR6TWyp7ZHkvKFA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/rawlist@5.2.0': - resolution: {integrity: sha512-CciqGoOUMrFo6HxvOtU5uL8fkjCmzyeB6fG7O1vdVAZVSopUBYECOwevDBlqNLyyYmzpm2Gsn/7nLrpruy9RFg==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/search@4.1.0': - resolution: {integrity: sha512-EAzemfiP4IFvIuWnrHpgZs9lAhWDA0GM3l9F4t4mTQ22IFtzfrk8xbkMLcAN7gmVML9O/i+Hzu8yOUyAaL6BKA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/select@5.0.4': - resolution: {integrity: sha512-s8KoGpPYMEQ6WXc0dT9blX2NtIulMdLOO3LA1UKOiv7KFWzlJ6eLkEYTDBIi+JkyKXyn8t/CD6TinxGjyLt57g==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/type@4.0.3': - resolution: {integrity: sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1373,9 +1239,6 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - '@types/inquirer@9.0.9': - resolution: {integrity: sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==} - '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -1427,9 +1290,6 @@ packages: '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - '@types/through@0.0.33': - resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} - '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1719,9 +1579,6 @@ packages: chardet@2.1.0: resolution: {integrity: sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==} - chardet@2.1.1: - resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -1744,22 +1601,10 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - - cli-spinners@3.4.0: - resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} - engines: {node: '>=18.20'} - cli-table3@0.6.5: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -1887,9 +1732,6 @@ packages: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2012,10 +1854,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} - engines: {node: '>=18'} - get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} @@ -2119,10 +1957,6 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.2: - resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} - engines: {node: '>=0.10.0'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2150,15 +1984,6 @@ packages: inline-style-parser@0.2.4: resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} - inquirer@13.2.2: - resolution: {integrity: sha512-+hlN8I88JE9T3zjWHGnMhryniRDbSgFNJHJTyD2iKO5YNpMRyfghQ6wVoe+gV4ygMM4r4GzlsBxNa1g/UUZixA==} - engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -2193,10 +2018,6 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -2213,10 +2034,6 @@ packages: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} - is-windows@1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -2495,10 +2312,6 @@ packages: lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - log-symbols@7.0.1: - resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} - engines: {node: '>=18'} - longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -2691,10 +2504,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - minimatch@10.2.0: resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==} engines: {node: 20 || >=22} @@ -2753,10 +2562,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mute-stream@3.0.0: - resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} - engines: {node: ^20.17.0 || >=22.9.0} - nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2821,20 +2626,12 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - oniguruma-parser@0.12.1: resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} oniguruma-to-es@4.3.3: resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} - ora@9.3.0: - resolution: {integrity: sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==} - engines: {node: '>=20'} - outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -3074,24 +2871,13 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - run-async@4.0.6: - resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==} - engines: {node: '>=0.12.0'} - run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -3140,6 +2926,9 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -3168,10 +2957,6 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} - stdin-discarder@0.3.1: - resolution: {integrity: sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==} - engines: {node: '>=18'} - string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -3187,14 +2972,6 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string-width@8.1.1: - resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} - engines: {node: '>=20'} - string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -3525,10 +3302,6 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} - wrap-ansi@9.0.2: - resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} - engines: {node: '>=18'} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -3572,10 +3345,6 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} - yoctocolors@2.1.2: - resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} - engines: {node: '>=18'} - zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -3959,6 +3728,17 @@ snapshots: human-id: 4.1.1 prettier: 2.8.8 + '@clack/core@1.0.1': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@1.0.1': + dependencies: + '@clack/core': 1.0.1 + picocolors: 1.1.1 + sisteransi: 1.0.5 + '@colors/colors@1.5.0': optional: true @@ -4073,51 +3853,6 @@ snapshots: '@img/sharp-win32-x64@0.34.3': optional: true - '@inquirer/ansi@2.0.3': {} - - '@inquirer/checkbox@5.0.4(@types/node@25.2.3)': - dependencies: - '@inquirer/ansi': 2.0.3 - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/figures': 2.0.3 - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/confirm@6.0.4(@types/node@25.2.3)': - dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/core@11.1.1(@types/node@25.2.3)': - dependencies: - '@inquirer/ansi': 2.0.3 - '@inquirer/figures': 2.0.3 - '@inquirer/type': 4.0.3(@types/node@25.2.3) - cli-width: 4.1.0 - mute-stream: 3.0.0 - signal-exit: 4.1.0 - wrap-ansi: 9.0.2 - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/editor@5.0.4(@types/node@25.2.3)': - dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/external-editor': 2.0.3(@types/node@25.2.3) - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/expand@5.0.4(@types/node@25.2.3)': - dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - '@inquirer/external-editor@1.0.1(@types/node@25.2.3)': dependencies: chardet: 2.1.0 @@ -4125,80 +3860,6 @@ snapshots: optionalDependencies: '@types/node': 25.2.3 - '@inquirer/external-editor@2.0.3(@types/node@25.2.3)': - dependencies: - chardet: 2.1.1 - iconv-lite: 0.7.2 - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/figures@2.0.3': {} - - '@inquirer/input@5.0.4(@types/node@25.2.3)': - dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/number@4.0.4(@types/node@25.2.3)': - dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/password@5.0.4(@types/node@25.2.3)': - dependencies: - '@inquirer/ansi': 2.0.3 - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/prompts@8.2.0(@types/node@25.2.3)': - dependencies: - '@inquirer/checkbox': 5.0.4(@types/node@25.2.3) - '@inquirer/confirm': 6.0.4(@types/node@25.2.3) - '@inquirer/editor': 5.0.4(@types/node@25.2.3) - '@inquirer/expand': 5.0.4(@types/node@25.2.3) - '@inquirer/input': 5.0.4(@types/node@25.2.3) - '@inquirer/number': 4.0.4(@types/node@25.2.3) - '@inquirer/password': 5.0.4(@types/node@25.2.3) - '@inquirer/rawlist': 5.2.0(@types/node@25.2.3) - '@inquirer/search': 4.1.0(@types/node@25.2.3) - '@inquirer/select': 5.0.4(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/rawlist@5.2.0(@types/node@25.2.3)': - dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/search@4.1.0(@types/node@25.2.3)': - dependencies: - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/figures': 2.0.3 - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/select@5.0.4(@types/node@25.2.3)': - dependencies: - '@inquirer/ansi': 2.0.3 - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/figures': 2.0.3 - '@inquirer/type': 4.0.3(@types/node@25.2.3) - optionalDependencies: - '@types/node': 25.2.3 - - '@inquirer/type@4.0.3(@types/node@25.2.3)': - optionalDependencies: - '@types/node': 25.2.3 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -4891,11 +4552,6 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/inquirer@9.0.9': - dependencies: - '@types/through': 0.0.33 - rxjs: 7.8.2 - '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -4949,10 +4605,6 @@ snapshots: '@types/stack-utils@2.0.3': {} - '@types/through@0.0.33': - dependencies: - '@types/node': 25.2.3 - '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -5202,8 +4854,6 @@ snapshots: chardet@2.1.0: {} - chardet@2.1.1: {} - chownr@3.0.0: {} ci-info@3.9.0: {} @@ -5218,20 +4868,12 @@ snapshots: clean-stack@2.2.0: {} - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - - cli-spinners@3.4.0: {} - cli-table3@0.6.5: dependencies: string-width: 4.2.3 optionalDependencies: '@colors/colors': 1.5.0 - cli-width@4.1.0: {} - client-only@0.0.1: {} cliui@8.0.1: @@ -5324,8 +4966,6 @@ snapshots: emittery@0.13.1: {} - emoji-regex@10.6.0: {} - emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -5445,8 +5085,6 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} - get-nonce@1.0.1: {} get-package-type@0.1.0: {} @@ -5613,10 +5251,6 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.2: - dependencies: - safer-buffer: 2.1.2 - ignore@5.3.2: {} import-local@3.2.0: @@ -5637,18 +5271,6 @@ snapshots: inline-style-parser@0.2.4: {} - inquirer@13.2.2(@types/node@25.2.3): - dependencies: - '@inquirer/ansi': 2.0.3 - '@inquirer/core': 11.1.1(@types/node@25.2.3) - '@inquirer/prompts': 8.2.0(@types/node@25.2.3) - '@inquirer/type': 4.0.3(@types/node@25.2.3) - mute-stream: 3.0.0 - run-async: 4.0.6 - rxjs: 7.8.2 - optionalDependencies: - '@types/node': 25.2.3 - is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -5675,8 +5297,6 @@ snapshots: is-hexadecimal@2.0.1: {} - is-interactive@2.0.0: {} - is-number@7.0.0: {} is-plain-obj@4.1.0: {} @@ -5687,8 +5307,6 @@ snapshots: dependencies: better-path-resolve: 1.0.0 - is-unicode-supported@2.1.0: {} - is-windows@1.0.2: {} isexe@2.0.0: {} @@ -6131,11 +5749,6 @@ snapshots: lodash.startcase@4.4.0: {} - log-symbols@7.0.1: - dependencies: - is-unicode-supported: 2.1.0 - yoctocolors: 2.1.2 - longest-streak@3.1.0: {} lru-cache@10.4.3: {} @@ -6549,8 +6162,6 @@ snapshots: mimic-fn@2.1.0: {} - mimic-function@5.0.1: {} - minimatch@10.2.0: dependencies: brace-expansion: 5.0.2 @@ -6593,8 +6204,6 @@ snapshots: ms@2.1.3: {} - mute-stream@3.0.0: {} - nanoid@3.3.11: {} napi-postinstall@0.3.4: {} @@ -6649,10 +6258,6 @@ snapshots: dependencies: mimic-fn: 2.1.0 - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 - oniguruma-parser@0.12.1: {} oniguruma-to-es@4.3.3: @@ -6661,17 +6266,6 @@ snapshots: regex: 6.0.1 regex-recursion: 6.0.2 - ora@9.3.0: - dependencies: - chalk: 5.6.2 - cli-cursor: 5.0.0 - cli-spinners: 3.4.0 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 7.0.1 - stdin-discarder: 0.3.1 - string-width: 8.1.1 - outdent@0.5.0: {} p-all@3.0.0: @@ -6946,23 +6540,12 @@ snapshots: resolve-from@5.0.0: {} - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - reusify@1.1.0: {} - run-async@4.0.6: {} - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} @@ -7031,6 +6614,8 @@ snapshots: is-arrayish: 0.3.2 optional: true + sisteransi@1.0.5: {} + slash@3.0.0: {} source-map-js@1.2.1: {} @@ -7055,8 +6640,6 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 - stdin-discarder@0.3.1: {} - string-length@4.0.2: dependencies: char-regex: 1.0.2 @@ -7078,17 +6661,6 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 - - string-width@8.1.1: - dependencies: - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 - string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -7419,12 +6991,6 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.2 - wrap-ansi@9.0.2: - dependencies: - ansi-styles: 6.2.3 - string-width: 7.2.0 - strip-ansi: 7.1.2 - wrappy@1.0.2: {} write-file-atomic@5.0.1: @@ -7458,8 +7024,6 @@ snapshots: yocto-queue@1.2.2: {} - yoctocolors@2.1.2: {} - zod@3.25.76: {} zod@4.3.6: {}