@rlabs-inc/gemini-mcp 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +46 -43
  2. package/dist/cli/commands/config.d.ts +8 -0
  3. package/dist/cli/commands/config.js +147 -0
  4. package/dist/cli/commands/image.d.ts +7 -0
  5. package/dist/cli/commands/image.js +133 -0
  6. package/dist/cli/commands/query.d.ts +7 -0
  7. package/dist/cli/commands/query.js +94 -0
  8. package/dist/cli/commands/research.d.ts +7 -0
  9. package/dist/cli/commands/research.js +147 -0
  10. package/dist/cli/commands/search.d.ts +7 -0
  11. package/dist/cli/commands/search.js +152 -0
  12. package/dist/cli/commands/speak.d.ts +7 -0
  13. package/dist/cli/commands/speak.js +168 -0
  14. package/dist/cli/commands/tokens.d.ts +8 -0
  15. package/dist/cli/commands/tokens.js +105 -0
  16. package/dist/cli/commands/video.d.ts +7 -0
  17. package/dist/cli/commands/video.js +154 -0
  18. package/dist/cli/config.d.ts +23 -0
  19. package/dist/cli/config.js +89 -0
  20. package/dist/cli/index.d.ts +6 -0
  21. package/dist/cli/index.js +180 -0
  22. package/dist/cli/ui/box.d.ts +20 -0
  23. package/dist/cli/ui/box.js +112 -0
  24. package/dist/cli/ui/colors.d.ts +46 -0
  25. package/dist/cli/ui/colors.js +106 -0
  26. package/dist/cli/ui/index.d.ts +21 -0
  27. package/dist/cli/ui/index.js +42 -0
  28. package/dist/cli/ui/progress.d.ts +37 -0
  29. package/dist/cli/ui/progress.js +125 -0
  30. package/dist/cli/ui/spinner.d.ts +42 -0
  31. package/dist/cli/ui/spinner.js +96 -0
  32. package/dist/cli/ui/theme.d.ts +48 -0
  33. package/dist/cli/ui/theme.js +200 -0
  34. package/dist/gemini-client.d.ts +1 -0
  35. package/dist/gemini-client.js +35 -8
  36. package/dist/index.d.ts +6 -3
  37. package/dist/index.js +26 -218
  38. package/dist/server.d.ts +7 -0
  39. package/dist/server.js +221 -0
  40. package/dist/tools/deep-research.js +9 -2
  41. package/package.json +9 -3
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Video Command
3
+ *
4
+ * Generate videos with Gemini's Veo model.
5
+ * gemini video "a cat playing piano"
6
+ */
7
+ import { parseArgs } from 'node:util';
8
+ import { initGeminiClient, startVideoGeneration, checkVideoStatus } from '../../gemini-client.js';
9
+ import { setupLogger } from '../../utils/logger.js';
10
+ import { spinner, progress, print, printError, printSuccess, printMuted, printWarning, t, header, box } from '../ui/index.js';
11
+ function showHelp() {
12
+ const theme = t();
13
+ print(header('gemini video', 'Generate videos with AI'));
14
+ print('');
15
+ print(theme.colors.primary('Usage:'));
16
+ print(` gemini video ${theme.colors.muted('"your prompt"')} [options]`);
17
+ print('');
18
+ print(theme.colors.primary('Options:'));
19
+ print(` ${theme.colors.highlight('--ratio, -r')} ${theme.colors.muted('Aspect ratio: 16:9, 9:16 (default: 16:9)')}`);
20
+ print(` ${theme.colors.highlight('--wait, -w')} ${theme.colors.muted('Wait for completion (can take several minutes)')}`);
21
+ print(` ${theme.colors.highlight('--negative')} ${theme.colors.muted('Things to avoid (e.g., "text, watermarks")')}`);
22
+ print(` ${theme.colors.highlight('--help, -h')} ${theme.colors.muted('Show this help')}`);
23
+ print('');
24
+ print(theme.colors.primary('Examples:'));
25
+ print(theme.colors.muted(' gemini video "a cat playing piano"'));
26
+ print(theme.colors.muted(' gemini video "sunset timelapse" --ratio 16:9 --wait'));
27
+ print(theme.colors.muted(' gemini video "robot dancing" -r 9:16 --negative "text, blur"'));
28
+ print('');
29
+ print(theme.colors.warning(`${theme.symbols.warning} Video generation can take 2-5 minutes`));
30
+ }
31
+ export async function videoCommand(argv) {
32
+ const { values, positionals } = parseArgs({
33
+ args: argv,
34
+ options: {
35
+ help: { type: 'boolean', short: 'h', default: false },
36
+ ratio: { type: 'string', short: 'r', default: '16:9' },
37
+ wait: { type: 'boolean', short: 'w', default: false },
38
+ negative: { type: 'string' },
39
+ },
40
+ allowPositionals: true,
41
+ });
42
+ if (values.help) {
43
+ showHelp();
44
+ return;
45
+ }
46
+ // Get prompt from positional args
47
+ const prompt = positionals.join(' ');
48
+ if (!prompt) {
49
+ printError('No video prompt provided');
50
+ printMuted('Usage: gemini video "your prompt"');
51
+ process.exit(1);
52
+ }
53
+ const theme = t();
54
+ const s = spinner();
55
+ const ratio = values.ratio;
56
+ const shouldWait = values.wait;
57
+ const negativePrompt = values.negative;
58
+ // Validate ratio
59
+ if (ratio !== '16:9' && ratio !== '9:16') {
60
+ printError(`Invalid ratio: ${ratio}`);
61
+ printMuted('Valid ratios: 16:9, 9:16');
62
+ process.exit(1);
63
+ }
64
+ try {
65
+ // Suppress logger output for CLI
66
+ setupLogger('quiet');
67
+ // Initialize Gemini client
68
+ s.start('Connecting to Gemini...');
69
+ await initGeminiClient();
70
+ // Start video generation
71
+ s.update('Starting video generation...');
72
+ const result = await startVideoGeneration(prompt, {
73
+ aspectRatio: ratio,
74
+ negativePrompt,
75
+ });
76
+ if (!result.operationName) {
77
+ throw new Error('Failed to start video generation');
78
+ }
79
+ s.success('Video generation started!');
80
+ print('');
81
+ // Show info
82
+ const infoLines = [
83
+ `${theme.colors.primary('Operation:')} ${result.operationName}`,
84
+ `${theme.colors.primary('Prompt:')} ${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}`,
85
+ `${theme.colors.primary('Ratio:')} ${ratio}`,
86
+ negativePrompt ? `${theme.colors.primary('Avoid:')} ${negativePrompt}` : null,
87
+ `${theme.colors.primary('Status:')} ${theme.colors.warning('Processing')}`,
88
+ ].filter(Boolean);
89
+ print(box(infoLines, { title: 'Video Generation' }));
90
+ print('');
91
+ if (!shouldWait) {
92
+ // Not waiting - give instructions
93
+ print(theme.colors.info(`${theme.symbols.info} Video is being generated in the background.`));
94
+ print('');
95
+ print('Check status with the MCP tool: gemini-check-video');
96
+ print('');
97
+ print(theme.colors.muted('Video generation typically takes 2-5 minutes.'));
98
+ return;
99
+ }
100
+ // Wait for completion
101
+ print(theme.colors.info(`${theme.symbols.info} Waiting for video to complete...`));
102
+ print(theme.colors.muted('This typically takes 2-5 minutes. Press Ctrl+C to exit.'));
103
+ print('');
104
+ const p = progress({ total: 100, showEta: false });
105
+ p.start('Generating video');
106
+ let attempts = 0;
107
+ const maxAttempts = 60; // 10 seconds * 60 = 10 minutes max
108
+ const pollInterval = 10000; // 10 seconds
109
+ while (attempts < maxAttempts) {
110
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
111
+ attempts++;
112
+ // Update progress (fake progress)
113
+ const fakeProgress = Math.min(95, attempts * 3);
114
+ p.update(fakeProgress, `Generating video (${attempts * 10}s)`);
115
+ try {
116
+ const status = await checkVideoStatus(result.operationName);
117
+ if (status.status === 'completed' && status.filePath) {
118
+ p.done('Video complete!');
119
+ print('');
120
+ // Get file stats
121
+ const file = Bun.file(status.filePath);
122
+ const fileSize = file.size;
123
+ const resultLines = [
124
+ `${theme.colors.primary('File:')} ${status.filePath}`,
125
+ `${theme.colors.primary('Size:')} ${(fileSize / (1024 * 1024)).toFixed(1)} MB`,
126
+ ];
127
+ print(box(resultLines, { title: 'Video Ready' }));
128
+ print('');
129
+ printSuccess(`Video saved to: ${status.filePath}`);
130
+ print('');
131
+ print(theme.colors.muted(`Open with: open "${status.filePath}"`));
132
+ return;
133
+ }
134
+ else if (status.status === 'failed') {
135
+ p.fail('Video generation failed');
136
+ printError(status.error || 'Unknown error');
137
+ process.exit(1);
138
+ }
139
+ // Still processing, continue
140
+ }
141
+ catch (error) {
142
+ // Polling error - continue trying
143
+ }
144
+ }
145
+ // Timed out
146
+ p.fail('Video generation timed out');
147
+ printWarning('Video may still be generating.');
148
+ }
149
+ catch (error) {
150
+ s.error('Video generation failed');
151
+ printError(error instanceof Error ? error.message : String(error));
152
+ process.exit(1);
153
+ }
154
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * CLI Configuration
3
+ *
4
+ * Loads and manages CLI configuration using Bun's native file APIs.
5
+ * Config file: ~/.config/gemini-cli/config.json
6
+ */
7
+ export interface CLIConfig {
8
+ theme: string;
9
+ outputDir: string;
10
+ defaultVoice: string;
11
+ defaultImageSize: '1K' | '2K' | '4K';
12
+ defaultImageRatio: string;
13
+ defaultVideoRatio: '16:9' | '9:16';
14
+ apiKey?: string;
15
+ }
16
+ export declare function getConfigDir(): string;
17
+ export declare function getConfigPath(): string;
18
+ export declare function loadConfig(): Promise<CLIConfig>;
19
+ export declare function saveConfig(config: Partial<CLIConfig>): Promise<void>;
20
+ export declare function getOutputDir(config: CLIConfig): string;
21
+ export declare function getApiKey(config: CLIConfig): string | undefined;
22
+ export declare function getConfig(): CLIConfig;
23
+ export declare function setCachedConfig(config: CLIConfig): void;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * CLI Configuration
3
+ *
4
+ * Loads and manages CLI configuration using Bun's native file APIs.
5
+ * Config file: ~/.config/gemini-cli/config.json
6
+ */
7
+ import { homedir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { setTheme } from './ui/index.js';
10
+ const DEFAULT_CONFIG = {
11
+ theme: 'terminal',
12
+ outputDir: '~/Downloads',
13
+ defaultVoice: 'Kore',
14
+ defaultImageSize: '2K',
15
+ defaultImageRatio: '1:1',
16
+ defaultVideoRatio: '16:9',
17
+ };
18
+ // Expand ~ to home directory
19
+ function expandPath(path) {
20
+ if (path.startsWith('~/')) {
21
+ return join(homedir(), path.slice(2));
22
+ }
23
+ return path;
24
+ }
25
+ // Get config directory path
26
+ export function getConfigDir() {
27
+ return join(homedir(), '.config', 'gemini-cli');
28
+ }
29
+ // Get config file path
30
+ export function getConfigPath() {
31
+ return join(getConfigDir(), 'config.json');
32
+ }
33
+ // Load configuration using Bun
34
+ export async function loadConfig() {
35
+ const configPath = getConfigPath();
36
+ try {
37
+ const file = Bun.file(configPath);
38
+ const exists = await file.exists();
39
+ if (!exists) {
40
+ // Return defaults if no config file
41
+ return { ...DEFAULT_CONFIG };
42
+ }
43
+ const content = await file.json();
44
+ const config = { ...DEFAULT_CONFIG, ...content };
45
+ // Apply theme immediately
46
+ if (config.theme) {
47
+ setTheme(config.theme);
48
+ }
49
+ return config;
50
+ }
51
+ catch {
52
+ // Return defaults on any error
53
+ return { ...DEFAULT_CONFIG };
54
+ }
55
+ }
56
+ // Save configuration using Bun
57
+ export async function saveConfig(config) {
58
+ const configPath = getConfigPath();
59
+ const configDir = getConfigDir();
60
+ // Ensure config directory exists
61
+ await Bun.write(join(configDir, '.keep'), ''); // Creates dir as side effect
62
+ // Load existing config and merge
63
+ const existing = await loadConfig();
64
+ const merged = { ...existing, ...config };
65
+ // Write config file
66
+ await Bun.write(configPath, JSON.stringify(merged, null, 2));
67
+ }
68
+ // Get resolved output directory
69
+ export function getOutputDir(config) {
70
+ // First check env var, then config
71
+ const dir = process.env.GEMINI_OUTPUT_DIR || config.outputDir;
72
+ return expandPath(dir);
73
+ }
74
+ // Get API key from env or config
75
+ export function getApiKey(config) {
76
+ return process.env.GEMINI_API_KEY || config.apiKey;
77
+ }
78
+ // Sync version for quick access (uses cache after first load)
79
+ let cachedConfig = null;
80
+ export function getConfig() {
81
+ if (!cachedConfig) {
82
+ // Return defaults - actual loading should be done with loadConfig()
83
+ return { ...DEFAULT_CONFIG };
84
+ }
85
+ return cachedConfig;
86
+ }
87
+ export function setCachedConfig(config) {
88
+ cachedConfig = config;
89
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Gemini CLI Router
3
+ *
4
+ * Routes commands to their handlers and provides the main CLI interface.
5
+ */
6
+ export declare function runCli(argv: string[]): Promise<void>;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Gemini CLI Router
3
+ *
4
+ * Routes commands to their handlers and provides the main CLI interface.
5
+ */
6
+ import { loadConfig, setCachedConfig, getApiKey } from './config.js';
7
+ import { setTheme, header, print, printError, printMuted, t } from './ui/index.js';
8
+ // Import commands
9
+ import { queryCommand } from './commands/query.js';
10
+ import { tokensCommand } from './commands/tokens.js';
11
+ import { searchCommand } from './commands/search.js';
12
+ import { researchCommand } from './commands/research.js';
13
+ import { speakCommand } from './commands/speak.js';
14
+ import { configCommand } from './commands/config.js';
15
+ import { imageCommand } from './commands/image.js';
16
+ import { videoCommand } from './commands/video.js';
17
+ const VERSION = '0.7.0';
18
+ // Placeholder command for development
19
+ const placeholderCommand = (name) => ({
20
+ name,
21
+ description: `${name} command (coming soon)`,
22
+ run: async () => {
23
+ printMuted(`${name} command not yet implemented`);
24
+ },
25
+ });
26
+ const commands = {
27
+ query: {
28
+ name: 'query',
29
+ description: 'Query Gemini directly',
30
+ run: queryCommand,
31
+ },
32
+ search: {
33
+ name: 'search',
34
+ description: 'Real-time web search',
35
+ run: searchCommand,
36
+ },
37
+ tokens: {
38
+ name: 'tokens',
39
+ description: 'Count tokens in text or files',
40
+ run: tokensCommand,
41
+ },
42
+ research: {
43
+ name: 'research',
44
+ description: 'Deep research agent',
45
+ run: researchCommand,
46
+ },
47
+ image: {
48
+ name: 'image',
49
+ description: 'Generate images',
50
+ run: imageCommand,
51
+ },
52
+ speak: {
53
+ name: 'speak',
54
+ description: 'Text-to-speech',
55
+ run: speakCommand,
56
+ },
57
+ video: {
58
+ name: 'video',
59
+ description: 'Generate videos',
60
+ run: videoCommand,
61
+ },
62
+ music: placeholderCommand('music'),
63
+ config: {
64
+ name: 'config',
65
+ description: 'Set API key and preferences',
66
+ run: configCommand,
67
+ },
68
+ };
69
+ function showHelp() {
70
+ const theme = t();
71
+ print(header('Gemini CLI', `AI-powered tools at your terminal (v${VERSION})`));
72
+ print('');
73
+ print(theme.colors.primary('Usage:'));
74
+ print(` gemini ${theme.colors.muted('<command>')} [options]`);
75
+ print('');
76
+ print(theme.colors.primary('Commands:'));
77
+ print(` ${theme.colors.highlight('query')} ${theme.colors.muted('Query Gemini directly')}`);
78
+ print(` ${theme.colors.highlight('search')} ${theme.colors.muted('Real-time web search')}`);
79
+ print(` ${theme.colors.highlight('tokens')} ${theme.colors.muted('Count tokens in text/file')}`);
80
+ print(` ${theme.colors.highlight('research')} ${theme.colors.muted('Deep research agent')}`);
81
+ print(` ${theme.colors.highlight('image')} ${theme.colors.muted('Generate images')}`);
82
+ print(` ${theme.colors.highlight('speak')} ${theme.colors.muted('Text-to-speech')}`);
83
+ print(` ${theme.colors.highlight('video')} ${theme.colors.muted('Generate videos')}`);
84
+ print(` ${theme.colors.highlight('music')} ${theme.colors.muted('Generate music')}`);
85
+ print(` ${theme.colors.highlight('config')} ${theme.colors.muted('Set API key and preferences')}`);
86
+ print('');
87
+ print(theme.colors.primary('Options:'));
88
+ print(` ${theme.colors.highlight('-h, --help')} ${theme.colors.muted('Show this help')}`);
89
+ print(` ${theme.colors.highlight('-v, --version')} ${theme.colors.muted('Show version')}`);
90
+ print(` ${theme.colors.highlight('--theme')} ${theme.colors.muted('Set color theme (terminal, neon, minimal, ocean, forest)')}`);
91
+ print('');
92
+ print(theme.colors.primary('Examples:'));
93
+ print(theme.colors.muted(' gemini query "What is the meaning of life?"'));
94
+ print(theme.colors.muted(' gemini search "latest AI news"'));
95
+ print(theme.colors.muted(' gemini image "a cat in space" --size 4K'));
96
+ print(theme.colors.muted(' gemini research "MCP ecosystem" --format outline'));
97
+ print('');
98
+ print(theme.colors.muted(`Run 'gemini <command> --help' for command-specific options.`));
99
+ }
100
+ function showVersion() {
101
+ const theme = t();
102
+ print(`${theme.colors.primary('gemini')} ${theme.colors.highlight(`v${VERSION}`)}`);
103
+ }
104
+ export async function runCli(argv) {
105
+ // Load config first
106
+ const config = await loadConfig();
107
+ setCachedConfig(config);
108
+ // Extract global flags and find command
109
+ // Global flags: --theme <value>, --help/-h, --version/-v
110
+ let themeName = config.theme;
111
+ let showHelpFlag = false;
112
+ let showVersionFlag = false;
113
+ let commandName = null;
114
+ let commandStartIndex = 0;
115
+ for (let i = 0; i < argv.length; i++) {
116
+ const arg = argv[i];
117
+ if (arg === '--theme' && argv[i + 1]) {
118
+ themeName = argv[i + 1];
119
+ i++; // Skip the theme value
120
+ }
121
+ else if (arg === '--help' || arg === '-h') {
122
+ showHelpFlag = true;
123
+ }
124
+ else if (arg === '--version' || arg === '-v') {
125
+ showVersionFlag = true;
126
+ }
127
+ else if (!arg.startsWith('-')) {
128
+ // Found a command
129
+ commandName = arg;
130
+ commandStartIndex = i + 1;
131
+ break;
132
+ }
133
+ }
134
+ // Apply theme
135
+ setTheme(themeName);
136
+ // Handle version flag
137
+ if (showVersionFlag) {
138
+ showVersion();
139
+ return;
140
+ }
141
+ // Handle help flag or no command
142
+ if (showHelpFlag || !commandName) {
143
+ showHelp();
144
+ return;
145
+ }
146
+ // Get command handler
147
+ const command = commands[commandName];
148
+ if (!command) {
149
+ printError(`Unknown command: ${commandName}`);
150
+ printMuted(`Run 'gemini --help' for available commands`);
151
+ process.exit(1);
152
+ }
153
+ // Get command arguments (everything after the command)
154
+ const commandArgs = argv.slice(commandStartIndex);
155
+ // Check if command has --help flag (don't require API key for help)
156
+ const isCommandHelp = commandArgs.includes('--help') || commandArgs.includes('-h');
157
+ // Commands that don't need API key
158
+ const noApiKeyCommands = ['config'];
159
+ // Check for API key (only needed for actual commands, not help or config)
160
+ if (!isCommandHelp && !noApiKeyCommands.includes(commandName)) {
161
+ const apiKey = getApiKey(config);
162
+ if (!apiKey) {
163
+ printError('GEMINI_API_KEY environment variable is required');
164
+ printMuted('Set it with: gemini config set api-key YOUR_KEY');
165
+ process.exit(1);
166
+ }
167
+ // Set env var from config for gemini-client.ts to use
168
+ if (!process.env.GEMINI_API_KEY && apiKey) {
169
+ process.env.GEMINI_API_KEY = apiKey;
170
+ }
171
+ }
172
+ // Run command with remaining args
173
+ try {
174
+ await command.run(commandArgs);
175
+ }
176
+ catch (error) {
177
+ printError(error instanceof Error ? error.message : String(error));
178
+ process.exit(1);
179
+ }
180
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Box Component for CLI
3
+ *
4
+ * Creates beautiful bordered boxes for headers, results, and highlights.
5
+ */
6
+ export interface BoxOptions {
7
+ title?: string;
8
+ padding?: number;
9
+ margin?: number;
10
+ borderColor?: (text: string) => string;
11
+ titleColor?: (text: string) => string;
12
+ width?: number | 'auto';
13
+ align?: 'left' | 'center' | 'right';
14
+ }
15
+ export declare function box(content: string | string[], options?: BoxOptions): string;
16
+ export declare function header(title: string, subtitle?: string): string;
17
+ export declare function success(message: string): string;
18
+ export declare function error(message: string): string;
19
+ export declare function warning(message: string): string;
20
+ export declare function info(message: string): string;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Box Component for CLI
3
+ *
4
+ * Creates beautiful bordered boxes for headers, results, and highlights.
5
+ */
6
+ import { getTheme } from './theme.js';
7
+ // Get terminal width safely
8
+ function getTerminalWidth() {
9
+ return process.stdout.columns ?? 80;
10
+ }
11
+ // Strip ANSI codes for length calculation
12
+ function stripAnsi(str) {
13
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
14
+ }
15
+ // Pad string to width
16
+ function pad(str, width, align = 'left') {
17
+ const visibleLength = stripAnsi(str).length;
18
+ const padding = width - visibleLength;
19
+ if (padding <= 0)
20
+ return str;
21
+ switch (align) {
22
+ case 'center': {
23
+ const left = Math.floor(padding / 2);
24
+ const right = padding - left;
25
+ return ' '.repeat(left) + str + ' '.repeat(right);
26
+ }
27
+ case 'right':
28
+ return ' '.repeat(padding) + str;
29
+ default:
30
+ return str + ' '.repeat(padding);
31
+ }
32
+ }
33
+ export function box(content, options = {}) {
34
+ const theme = getTheme();
35
+ const { title, padding = 1, margin = 0, borderColor = theme.colors.primary, titleColor = theme.colors.highlight, width = 'auto', align = 'left', } = options;
36
+ const { topLeft, topRight, bottomLeft, bottomRight, horizontal, vertical } = theme.box;
37
+ // Normalize content to array of lines
38
+ const lines = Array.isArray(content) ? content : content.split('\n');
39
+ // Calculate widths
40
+ const termWidth = getTerminalWidth();
41
+ const maxLineWidth = Math.max(...lines.map(l => stripAnsi(l).length));
42
+ const titleWidth = title ? stripAnsi(title).length + 2 : 0; // +2 for spaces around title
43
+ let innerWidth;
44
+ if (width === 'auto') {
45
+ innerWidth = Math.max(maxLineWidth, titleWidth) + padding * 2;
46
+ }
47
+ else {
48
+ innerWidth = Math.min(width, termWidth - 4) - 2; // -2 for borders
49
+ }
50
+ const marginStr = ' '.repeat(margin);
51
+ const paddingStr = ' '.repeat(padding);
52
+ // Build the box
53
+ const result = [];
54
+ // Top border with optional title
55
+ if (title) {
56
+ const titleStr = ` ${titleColor(title)} `;
57
+ const leftBorder = horizontal.repeat(2);
58
+ const rightBorderLen = innerWidth - 4 - stripAnsi(title).length;
59
+ const rightBorder = horizontal.repeat(Math.max(0, rightBorderLen));
60
+ result.push(marginStr + borderColor(topLeft + leftBorder) + titleStr + borderColor(rightBorder + topRight));
61
+ }
62
+ else {
63
+ result.push(marginStr + borderColor(topLeft + horizontal.repeat(innerWidth) + topRight));
64
+ }
65
+ // Content lines with padding
66
+ for (const line of lines) {
67
+ const paddedLine = pad(line, innerWidth - padding * 2, align);
68
+ result.push(marginStr + borderColor(vertical) + paddingStr + paddedLine + paddingStr + borderColor(vertical));
69
+ }
70
+ // Bottom border
71
+ result.push(marginStr + borderColor(bottomLeft + horizontal.repeat(innerWidth) + bottomRight));
72
+ return result.join('\n');
73
+ }
74
+ // Quick box variants
75
+ export function header(title, subtitle) {
76
+ const theme = getTheme();
77
+ const content = subtitle ? [title, theme.colors.muted(subtitle)] : [title];
78
+ return box(content, {
79
+ borderColor: theme.colors.primary,
80
+ titleColor: theme.colors.highlight,
81
+ align: 'center',
82
+ padding: 2,
83
+ });
84
+ }
85
+ export function success(message) {
86
+ const theme = getTheme();
87
+ return box(`${theme.symbols.success} ${message}`, {
88
+ borderColor: theme.colors.success,
89
+ padding: 1,
90
+ });
91
+ }
92
+ export function error(message) {
93
+ const theme = getTheme();
94
+ return box(`${theme.symbols.error} ${message}`, {
95
+ borderColor: theme.colors.error,
96
+ padding: 1,
97
+ });
98
+ }
99
+ export function warning(message) {
100
+ const theme = getTheme();
101
+ return box(`${theme.symbols.warning} ${message}`, {
102
+ borderColor: theme.colors.warning,
103
+ padding: 1,
104
+ });
105
+ }
106
+ export function info(message) {
107
+ const theme = getTheme();
108
+ return box(`${theme.symbols.info} ${message}`, {
109
+ borderColor: theme.colors.info,
110
+ padding: 1,
111
+ });
112
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Terminal Color Utilities
3
+ *
4
+ * Uses ANSI escape codes directly for zero dependencies.
5
+ * Respects NO_COLOR and FORCE_COLOR environment variables.
6
+ * Bun handles these natively in TTY detection.
7
+ */
8
+ export declare const black: (text: string) => string;
9
+ export declare const red: (text: string) => string;
10
+ export declare const green: (text: string) => string;
11
+ export declare const yellow: (text: string) => string;
12
+ export declare const blue: (text: string) => string;
13
+ export declare const magenta: (text: string) => string;
14
+ export declare const cyan: (text: string) => string;
15
+ export declare const white: (text: string) => string;
16
+ export declare const brightBlack: (text: string) => string;
17
+ export declare const brightRed: (text: string) => string;
18
+ export declare const brightGreen: (text: string) => string;
19
+ export declare const brightYellow: (text: string) => string;
20
+ export declare const brightBlue: (text: string) => string;
21
+ export declare const brightMagenta: (text: string) => string;
22
+ export declare const brightCyan: (text: string) => string;
23
+ export declare const brightWhite: (text: string) => string;
24
+ export declare const bgBlack: (text: string) => string;
25
+ export declare const bgRed: (text: string) => string;
26
+ export declare const bgGreen: (text: string) => string;
27
+ export declare const bgYellow: (text: string) => string;
28
+ export declare const bgBlue: (text: string) => string;
29
+ export declare const bgMagenta: (text: string) => string;
30
+ export declare const bgCyan: (text: string) => string;
31
+ export declare const bgWhite: (text: string) => string;
32
+ export declare const bold: (text: string) => string;
33
+ export declare const dim: (text: string) => string;
34
+ export declare const italic: (text: string) => string;
35
+ export declare const underline: (text: string) => string;
36
+ export declare const inverse: (text: string) => string;
37
+ export declare const strikethrough: (text: string) => string;
38
+ export declare function rgb(r: number, g: number, b: number): (text: string) => string;
39
+ export declare function bgRgb(r: number, g: number, b: number): (text: string) => string;
40
+ export declare function color256(code: number): (text: string) => string;
41
+ export declare function hex(hexColor: string): (text: string) => string;
42
+ export declare const colors: {
43
+ enabled: boolean;
44
+ reset: string;
45
+ };
46
+ export declare function style(...styles: Array<(text: string) => string>): (text: string) => string;