Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/all-papayas-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mixedbread/cli": patch
---

Replaced ora and inquirer with clack for improved terminal UI
3 changes: 1 addition & 2 deletions packages/cli/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ const config: JestConfigWithTsJest = {
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
"^chalk$": "<rootDir>/tests/__mocks__/chalk.ts",
"^ora$": "<rootDir>/tests/__mocks__/ora.ts",
"^inquirer$": "<rootDir>/tests/__mocks__/inquirer.ts",
"^@clack/prompts$": "<rootDir>/tests/__mocks__/clack-prompts.ts",
"^glob$": "<rootDir>/tests/__mocks__/glob.ts",
"^p-limit$": "<rootDir>/tests/__mocks__/p-limit.ts",
},
Expand Down
4 changes: 1 addition & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,22 @@
"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",
"cli-table3": "^0.6.5",
"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",
Expand Down
17 changes: 10 additions & 7 deletions packages/cli/src/commands/completion.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import path from "node:path";
import { log as clackLog, spinner } from "@clack/prompts";
import {
getShellFromEnv,
install,
Expand All @@ -8,7 +9,6 @@ import {
} from "@pnpm/tabtab";
import chalk from "chalk";
import { Command } from "commander";
import ora from "ora";
import {
getCurrentKeyName,
getStoresForCompletion,
Expand Down Expand Up @@ -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"
);
}
});

Expand Down
66 changes: 24 additions & 42 deletions packages/cli/src/commands/config/keys.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -41,37 +41,36 @@ 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;
}

const config = loadConfig();

// 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;
}
}
Expand All @@ -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({
Expand Down Expand Up @@ -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:");
Expand All @@ -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;
}
}
Expand All @@ -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}`);
Expand All @@ -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`);
}
});

Expand All @@ -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:");
Expand All @@ -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({
Expand Down
19 changes: 8 additions & 11 deletions packages/cli/src/commands/store/create.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -87,7 +86,7 @@ export function createCreateCommand(): Command {
metadata,
});

spinner.succeed(`Store "${name}" created successfully`);
createSpinner.stop(`Store "${name}" created successfully`);

formatOutput(
{
Expand All @@ -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);
}
});
Expand Down
36 changes: 14 additions & 22 deletions packages/cli/src/commands/store/delete.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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);
Expand All @@ -51,39 +49,33 @@ 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,
});
Comment thread
nainglinnkhant marked this conversation as resolved.

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();
if (keyName) {
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);
}
});
Expand Down
Loading