@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.
- package/README.md +46 -43
- package/dist/cli/commands/config.d.ts +8 -0
- package/dist/cli/commands/config.js +147 -0
- package/dist/cli/commands/image.d.ts +7 -0
- package/dist/cli/commands/image.js +133 -0
- package/dist/cli/commands/query.d.ts +7 -0
- package/dist/cli/commands/query.js +94 -0
- package/dist/cli/commands/research.d.ts +7 -0
- package/dist/cli/commands/research.js +147 -0
- package/dist/cli/commands/search.d.ts +7 -0
- package/dist/cli/commands/search.js +152 -0
- package/dist/cli/commands/speak.d.ts +7 -0
- package/dist/cli/commands/speak.js +168 -0
- package/dist/cli/commands/tokens.d.ts +8 -0
- package/dist/cli/commands/tokens.js +105 -0
- package/dist/cli/commands/video.d.ts +7 -0
- package/dist/cli/commands/video.js +154 -0
- package/dist/cli/config.d.ts +23 -0
- package/dist/cli/config.js +89 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +180 -0
- package/dist/cli/ui/box.d.ts +20 -0
- package/dist/cli/ui/box.js +112 -0
- package/dist/cli/ui/colors.d.ts +46 -0
- package/dist/cli/ui/colors.js +106 -0
- package/dist/cli/ui/index.d.ts +21 -0
- package/dist/cli/ui/index.js +42 -0
- package/dist/cli/ui/progress.d.ts +37 -0
- package/dist/cli/ui/progress.js +125 -0
- package/dist/cli/ui/spinner.d.ts +42 -0
- package/dist/cli/ui/spinner.js +96 -0
- package/dist/cli/ui/theme.d.ts +48 -0
- package/dist/cli/ui/theme.js +200 -0
- package/dist/gemini-client.d.ts +1 -0
- package/dist/gemini-client.js +35 -8
- package/dist/index.d.ts +6 -3
- package/dist/index.js +26 -218
- package/dist/server.d.ts +7 -0
- package/dist/server.js +221 -0
- package/dist/tools/deep-research.js +9 -2
- 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,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;
|