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
12 changes: 12 additions & 0 deletions .changeset/tired-keys-tell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@mixedbread/cli": minor
---

Add intelligent shell completion for vector store names

- **Dynamic completions**: Tab completion now suggests vector store names in commands like `mxbai vs sync [TAB]`, `mxbai vs upload [TAB]`, etc.
- **Multi-key support**: Completions work seamlessly with multiple API keys, showing stores for the current default key
- **New command**: `mxbai completion refresh` to manually refresh the completion cache

Breaking changes:
- Removed undocumented `vector-store` alias (use `vs` instead)
3 changes: 1 addition & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,5 +104,4 @@ export MXBAI_API_KEY="your-api-key"
- File upload with processing strategies (high_quality, fast, auto)
- Git-based and hash-based sync capabilities
- Manifest-based bulk uploads via YAML configuration
- Support for aliases and configuration management
```
- Support for aliases and configuration management
37 changes: 36 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ mxbai config keys add mxb_xxxxx work
# Or use environment variable
export MXBAI_API_KEY=mxb_xxxxx

# Install shell completion for better experience (optional but recommended)
mxbai completion install

## Check available commands and their options
mxbai --help

Expand Down Expand Up @@ -94,6 +97,7 @@ mxbai vs upload "My Documents" --manifest upload-manifest.yaml
- `mxbai completion install` - Install shell completion
- Options: `--shell <shell>` (manually specify shell: bash, zsh, fish, pwsh)
- `mxbai completion uninstall` - Uninstall shell completion
- `mxbai completion refresh` - Refresh completion cache for vector store names

## Features

Expand Down Expand Up @@ -301,7 +305,9 @@ mxbai config keys set-default personal

## Shell Completion

The CLI supports tab completion for commands and subcommands. To set up completion:
The CLI supports intelligent tab completion for commands, subcommands, and **vector store names**.

### Installation

```bash
# Install completion (auto-detects your shell)
Expand All @@ -325,6 +331,34 @@ After installation, restart your shell or reload your shell configuration:
- **fish**: Completion is ready to use (fish auto-loads completions)
- **pwsh**: `. $PROFILE` or restart terminal

### Dynamic Vector Store Name Completion

The CLI provides intelligent tab completion for vector store names in commands:

```bash
# Tab completion shows your vector store names
mxbai vs get [TAB] # Shows: store1 store2 my-docs ...
mxbai vs delete [TAB] # Shows: store1 store2 my-docs ...
mxbai vs sync [TAB] # Shows: store1 store2 my-docs ...
mxbai vs upload [TAB] # Shows: store1 store2 my-docs ...

# Also works with files subcommands
mxbai vs files list [TAB] # Shows: store1 store2 my-docs ...
```

**How it works:**
- Vector store names are cached locally for instant completion (no API latency)
- Cache updates automatically when you create, update, delete, or list vector stores
- Supports multiple API keys - completions show stores for your current default key
- Manual refresh available: `mxbai completion refresh`

**Cache management:**
- Caches up to 50 most recent vector store names per API key
- Cache location follows your config directory:
- Linux/Unix: `~/.config/mixedbread/completion-cache.json`
- macOS: `~/Library/Application Support/mixedbread/completion-cache.json`
- Windows: `%APPDATA%\mixedbread\completion-cache.json`

## Global Options

All commands support these global options:
Expand Down Expand Up @@ -450,6 +484,7 @@ src/
│ └── index.ts
├── utils/ # Shared utilities
│ ├── client.ts # API client setup
│ ├── completion-cache.ts # Shell completion cache management
│ ├── config.ts # Configuration management
│ ├── git.ts # Git integration utilities
│ ├── global-options.ts # Common CLI options
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"glob": "^10.4.5",
"inquirer": "^9.2.23",
"mime-types": "^3.0.1",
"minimatch": "^10.0.3",
"ora": "^8.0.1",
"p-limit": "^6.2.0",
"yaml": "^2.4.5",
Expand All @@ -74,6 +75,7 @@
"jest": "^29.4.0",
"mock-fs": "^5.5.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.2",
"tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.8/tsc-multi.tgz",
"typescript": "5.8.3"
}
Expand Down
94 changes: 87 additions & 7 deletions packages/cli/src/commands/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ import {
} from "@pnpm/tabtab";
import chalk from "chalk";
import { Command } from "commander";
import ora from "ora";
import {
getCurrentKeyName,
getStoresForCompletion,
refreshAllCaches,
} from "../utils/completion-cache";
import {
addGlobalOptions,
BaseGlobalOptionsSchema,
type GlobalOptions,
mergeCommandOptions,
parseOptions,
} from "../utils/global-options";

const SUPPORTED_SHELLS = ["bash", "zsh", "fish", "pwsh"] as const;
export type SupportedShell = (typeof SUPPORTED_SHELLS)[number];
Expand Down Expand Up @@ -128,7 +141,6 @@ export function createCompletionCommand(): Command {
}
} catch (error) {
console.error(chalk.red("✗"), "Error installing completion:", error);
process.exit(1);
}
});

Expand All @@ -146,10 +158,35 @@ export function createCompletionCommand(): Command {
);
} catch (error) {
console.error(chalk.red("✗"), "Error uninstalling completion:", error);
process.exit(1);
}
});

const refreshCommand = addGlobalOptions(
new Command("refresh").description(
"Refresh completion cache for all API keys"
)
);

refreshCommand.action(async (options: GlobalOptions) => {
const mergedOptions = mergeCommandOptions(refreshCommand, options);
const parsedOptions = parseOptions(BaseGlobalOptionsSchema, {
...mergedOptions,
});
const spinner = ora("Refreshing completion cache...").start();

try {
await refreshAllCaches(parsedOptions);
spinner.succeed("Completion cache refreshed successfully");
} catch (error) {
spinner.fail("Failed to refresh completion cache");
if (error instanceof Error) {
console.error(chalk.red("✗"), error.message);
}
}
});

completionCommand.addCommand(refreshCommand);

// Show help without error exit code when no subcommand provided
completionCommand.action(() => {
completionCommand.help();
Expand Down Expand Up @@ -182,8 +219,33 @@ export function createCompletionServerCommand(): Command {
);
}

// Vector store name completions
const STORE_NAME_COMMANDS = [
"get",
"delete",
"update",
"sync",
"upload",
"search",
"qa",
];

if (STORE_NAME_COMMANDS.includes(env.prev)) {
const words = env.line.trim().split(/\s+/);
// Check if previous word is "vs"
if (words.length >= 3 && words[words.length - 2] === "vs") {
const keyName = getCurrentKeyName();
if (keyName) {
const stores = getStoresForCompletion(keyName);
if (stores.length > 0) {
return log(stores, shell, console.log);
}
}
}
}

// Vector store completions
if (env.prev === "vs" || env.prev === "vector-store") {
if (env.prev === "vs") {
return log(
[
"create",
Expand All @@ -206,11 +268,29 @@ export function createCompletionServerCommand(): Command {
if (env.prev === "files") {
// Check if we're in "mxbai vs files " context
const words = env.line.trim().split(/\s+/);
if (words.length >= 3 && words[1] === "vs") {
return log(["list", "get", "delete"], shell, console.log);
}
}

// Vector store name completions for files subcommands
const FILES_SUBCOMMANDS = ["list", "get", "delete"];
if (FILES_SUBCOMMANDS.includes(env.prev)) {
const words = env.line.trim().split(/\s+/);
// Check for "mxbai vs files [subcommand] " context
if (
words.length >= 3 &&
(words[1] === "vs" || words[1] === "vector-store")
words.length >= 4 &&
words[1] === "vs" &&
words[2] === "files" &&
FILES_SUBCOMMANDS.includes(words[3])
) {
return log(["list", "get", "delete"], shell, console.log);
const keyName = getCurrentKeyName();
if (keyName) {
const stores = getStoresForCompletion(keyName);
if (stores.length > 0) {
return log(stores, shell, console.log);
}
}
}
}

Expand All @@ -233,7 +313,7 @@ export function createCompletionServerCommand(): Command {

// Completion completions
if (env.prev === "completion") {
return log(["install", "uninstall"], shell, console.log);
return log(["install", "uninstall", "refresh"], shell, console.log);
}
});

Expand Down
47 changes: 44 additions & 3 deletions packages/cli/src/commands/config/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,44 @@ import chalk from "chalk";
import { Command } from "commander";
import inquirer from "inquirer";
import { z } from "zod";
import { createClient } from "../../utils/client";
import {
clearCacheForKey,
refreshCacheForKey,
} from "../../utils/completion-cache";
import {
isMxbaiAPIKey,
loadConfig,
outputAvailableKeys,
saveConfig,
} from "../../utils/config";
import {
addGlobalOptions,
BaseGlobalOptionsSchema,
type GlobalOptions,
mergeCommandOptions,
parseOptions,
} from "../../utils/global-options";

const RemoveKeySchema = z.object({
name: z.string().min(1, { message: '"name" is required' }),
yes: z.boolean().optional(),
});

export function createKeysCommand(): Command {
const keysCommand = new Command("keys").description("Manage API keys");
const keysCommand = addGlobalOptions(
new Command("keys").description("Manage API keys")
);

keysCommand
.command("add <key> [name]")
.description("Add a new API key")
.action(async (key: string, name?: string) => {
.action(async (key: string, name?: string, options?: GlobalOptions) => {
const mergedOptions = mergeCommandOptions(keysCommand, options);
const parsedOptions = parseOptions(BaseGlobalOptionsSchema, {
...mergedOptions,
});

if (!isMxbaiAPIKey(key)) {
console.error(chalk.red("✗"), 'API key must start with "mxb_"');
return;
Expand Down Expand Up @@ -75,6 +94,13 @@ export function createKeysCommand(): Command {
chalk.green("✓"),
`API key "${name}" saved and set as default`
);

// Populate completion cache for the new key
const client = createClient({
apiKey: key,
baseURL: parsedOptions.baseURL,
});
refreshCacheForKey(name, client);
});

keysCommand
Expand Down Expand Up @@ -139,6 +165,9 @@ export function createKeysCommand(): Command {
// Remove the key
delete config.api_keys[parsedOptions.name];

// Clear completion cache for the removed key
clearCacheForKey(parsedOptions.name);

// If this was the default, clear it and warn
if (isDefault) {
if (config.defaults) {
Expand Down Expand Up @@ -174,7 +203,12 @@ export function createKeysCommand(): Command {
keysCommand
.command("set-default <name>")
.description("Set the default API key")
.action((name: string) => {
.action(async (name: string, options: GlobalOptions) => {
const mergedOptions = mergeCommandOptions(keysCommand, options);
const parsedOptions = parseOptions(BaseGlobalOptionsSchema, {
...mergedOptions,
});

const config = loadConfig();

if (!config.api_keys?.[name]) {
Expand All @@ -194,6 +228,13 @@ export function createKeysCommand(): Command {

saveConfig(config);
console.log(chalk.green("✓"), `"${name}" set as default API key`);

// Refresh cache for the newly set default key
const client = createClient({
apiKey: config.api_keys[name],
baseURL: parsedOptions.baseURL,
});
refreshCacheForKey(name, client);
});

// Show help when no subcommand provided
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/commands/vector-store/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { Command } from "commander";
import ora, { type Ora } from "ora";
import { z } from "zod";
import { createClient } from "../../utils/client";
import {
getCurrentKeyName,
updateCacheAfterCreate,
} from "../../utils/completion-cache";
import {
addGlobalOptions,
extendGlobalOptions,
Expand Down Expand Up @@ -83,6 +87,12 @@ export function createCreateCommand(): Command {
},
parsedOptions.format
);

// Update completion cache with the new store
const keyName = getCurrentKeyName();
if (keyName) {
updateCacheAfterCreate(keyName, vectorStore.name);
}
} catch (error) {
spinner?.fail("Failed to create vector store");
if (error instanceof Error) {
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/commands/vector-store/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import inquirer from "inquirer";
import ora, { type Ora } from "ora";
import { z } from "zod";
import { createClient } from "../../utils/client";
import {
getCurrentKeyName,
updateCacheAfterDelete,
} from "../../utils/completion-cache";
import {
addGlobalOptions,
extendGlobalOptions,
Expand Down Expand Up @@ -72,6 +76,12 @@ export function createDeleteCommand(): Command {
spinner.succeed(
`Vector store "${vectorStore.name}" deleted successfully`
);

// Update completion cache by removing the deleted store
const keyName = getCurrentKeyName();
if (keyName) {
updateCacheAfterDelete(keyName, vectorStore.name);
}
} catch (error) {
spinner?.fail("Failed to delete vector store");
if (error instanceof Error) {
Expand Down
Loading