@rlabs-inc/gemini-mcp 0.6.3 → 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 (39) 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/index.d.ts +6 -3
  35. package/dist/index.js +26 -218
  36. package/dist/server.d.ts +7 -0
  37. package/dist/server.js +221 -0
  38. package/dist/tools/deep-research.js +2 -2
  39. package/package.json +9 -3
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Research Command
3
+ *
4
+ * Deep research agent for comprehensive investigation.
5
+ * gemini research "your research question"
6
+ */
7
+ import { parseArgs } from 'node:util';
8
+ import { initGeminiClient, startDeepResearch, checkDeepResearch } 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 research', 'Deep research agent'));
14
+ print('');
15
+ print(theme.colors.primary('Usage:'));
16
+ print(` gemini research ${theme.colors.muted('"your research question"')}`);
17
+ print('');
18
+ print(theme.colors.primary('Options:'));
19
+ print(` ${theme.colors.highlight('--format, -f')} ${theme.colors.muted('Output format: report, outline, brief (default: report)')}`);
20
+ print(` ${theme.colors.highlight('--wait, -w')} ${theme.colors.muted('Wait for completion (can take 5-60 mins)')}`);
21
+ print(` ${theme.colors.highlight('--help, -h')} ${theme.colors.muted('Show this help')}`);
22
+ print('');
23
+ print(theme.colors.primary('Examples:'));
24
+ print(theme.colors.muted(' gemini research "MCP ecosystem and best practices"'));
25
+ print(theme.colors.muted(' gemini research "AI coding assistants comparison" --format outline'));
26
+ print(theme.colors.muted(' gemini research "Bun vs Node.js performance" --wait'));
27
+ print('');
28
+ print(theme.colors.warning(`${theme.symbols.warning} Deep research typically takes 5-20 minutes (max 60 min)`));
29
+ }
30
+ export async function researchCommand(argv) {
31
+ const { values, positionals } = parseArgs({
32
+ args: argv,
33
+ options: {
34
+ help: { type: 'boolean', short: 'h', default: false },
35
+ format: { type: 'string', short: 'f', default: 'report' },
36
+ wait: { type: 'boolean', short: 'w', default: false },
37
+ },
38
+ allowPositionals: true,
39
+ });
40
+ if (values.help) {
41
+ showHelp();
42
+ return;
43
+ }
44
+ // Get query from positional args
45
+ const query = positionals.join(' ');
46
+ if (!query) {
47
+ printError('No research question provided');
48
+ printMuted('Usage: gemini research "your question"');
49
+ process.exit(1);
50
+ }
51
+ const theme = t();
52
+ const s = spinner();
53
+ const format = values.format;
54
+ const shouldWait = values.wait;
55
+ try {
56
+ // Suppress logger output for CLI
57
+ setupLogger('quiet');
58
+ // Initialize Gemini client
59
+ s.start('Connecting to Gemini...');
60
+ await initGeminiClient();
61
+ // Build the research prompt with format
62
+ let researchPrompt = query;
63
+ if (format && format !== 'report') {
64
+ researchPrompt = `${query}\n\nFormat the output as: ${format}`;
65
+ }
66
+ // Start the research
67
+ s.update('Starting deep research agent...');
68
+ const result = await startDeepResearch(researchPrompt);
69
+ s.success('Research started!');
70
+ print('');
71
+ // Show research info
72
+ const infoLines = [
73
+ `${theme.colors.primary('Research ID:')} ${result.id}`,
74
+ `${theme.colors.primary('Query:')} ${query.substring(0, 60)}${query.length > 60 ? '...' : ''}`,
75
+ `${theme.colors.primary('Format:')} ${format}`,
76
+ `${theme.colors.primary('Status:')} ${theme.colors.warning('In Progress')}`,
77
+ ];
78
+ print(box(infoLines, { title: 'Deep Research' }));
79
+ print('');
80
+ if (!shouldWait) {
81
+ // Not waiting - give instructions
82
+ print(theme.colors.info(`${theme.symbols.info} Research is running in the background.`));
83
+ print('');
84
+ print('To check status:');
85
+ print(theme.colors.muted(` gemini research-status ${result.id}`));
86
+ print('');
87
+ print(theme.colors.muted('Research typically takes 5-20 minutes (max 60 min).'));
88
+ print(theme.colors.muted('Results will be saved to your configured output directory.'));
89
+ return;
90
+ }
91
+ // Wait for completion
92
+ print(theme.colors.info(`${theme.symbols.info} Waiting for research to complete...`));
93
+ print(theme.colors.muted('This may take 5-60 minutes. Press Ctrl+C to exit (research continues in background).'));
94
+ print('');
95
+ const p = progress({ total: 100, showEta: false });
96
+ p.start('Researching');
97
+ let lastStatus = 'pending';
98
+ let attempts = 0;
99
+ const maxAttempts = 180; // 30 seconds * 180 = 90 minutes max
100
+ const pollInterval = 30000; // 30 seconds
101
+ while (attempts < maxAttempts) {
102
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
103
+ attempts++;
104
+ // Update progress (fake progress since we don't know actual %)
105
+ const fakeProgress = Math.min(95, attempts * 2);
106
+ p.update(fakeProgress, `Researching (${Math.floor(attempts * 0.5)}m)`);
107
+ try {
108
+ const status = await checkDeepResearch(result.id);
109
+ if (status.status === 'completed') {
110
+ p.done('Research complete!');
111
+ print('');
112
+ // Show the results
113
+ if (status.outputs && status.outputs.length > 0) {
114
+ const resultText = status.outputs[status.outputs.length - 1].text || 'No text output';
115
+ print(theme.colors.primary('Research Results:'));
116
+ print('');
117
+ print(resultText);
118
+ }
119
+ if (status.savedPath) {
120
+ print('');
121
+ printSuccess(`Full response saved to: ${status.savedPath}`);
122
+ }
123
+ return;
124
+ }
125
+ else if (status.status === 'failed') {
126
+ p.fail('Research failed');
127
+ printError(status.error || 'Unknown error');
128
+ process.exit(1);
129
+ }
130
+ lastStatus = status.status;
131
+ }
132
+ catch (error) {
133
+ // Polling error - continue trying
134
+ console.error('Polling error:', error);
135
+ }
136
+ }
137
+ // Timed out
138
+ p.fail('Research timed out');
139
+ printWarning('Research is still running. Check status later:');
140
+ print(theme.colors.muted(` gemini research-status ${result.id}`));
141
+ }
142
+ catch (error) {
143
+ s.error('Research failed');
144
+ printError(error instanceof Error ? error.message : String(error));
145
+ process.exit(1);
146
+ }
147
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Search Command
3
+ *
4
+ * Real-time web search powered by Gemini + Google Search.
5
+ * gemini search "your query"
6
+ */
7
+ export declare function searchCommand(argv: string[]): Promise<void>;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Search Command
3
+ *
4
+ * Real-time web search powered by Gemini + Google Search.
5
+ * gemini search "your query"
6
+ */
7
+ import { parseArgs } from 'node:util';
8
+ import { GoogleGenAI } from '@google/genai';
9
+ import { setupLogger } from '../../utils/logger.js';
10
+ import { spinner, print, printError, printMuted, t, header } from '../ui/index.js';
11
+ function showHelp() {
12
+ const theme = t();
13
+ print(header('gemini search', 'Real-time web search'));
14
+ print('');
15
+ print(theme.colors.primary('Usage:'));
16
+ print(` gemini search ${theme.colors.muted('"your query"')}`);
17
+ print('');
18
+ print(theme.colors.primary('Options:'));
19
+ print(` ${theme.colors.highlight('--no-citations')} ${theme.colors.muted('Hide inline citations')}`);
20
+ print(` ${theme.colors.highlight('--help, -h')} ${theme.colors.muted('Show this help')}`);
21
+ print('');
22
+ print(theme.colors.primary('Examples:'));
23
+ print(theme.colors.muted(' gemini search "latest news about AI"'));
24
+ print(theme.colors.muted(' gemini search "weather in São Paulo"'));
25
+ print(theme.colors.muted(' gemini search "MCP Model Context Protocol"'));
26
+ }
27
+ /**
28
+ * Add inline citations to text based on grounding metadata
29
+ */
30
+ function addCitations(text, supports, chunks) {
31
+ if (!supports || !chunks || supports.length === 0) {
32
+ return text;
33
+ }
34
+ // Sort supports by endIndex in descending order to avoid shifting issues
35
+ const sortedSupports = [...supports].sort((a, b) => (b.segment?.endIndex ?? 0) - (a.segment?.endIndex ?? 0));
36
+ let result = text;
37
+ for (const support of sortedSupports) {
38
+ const endIndex = support.segment?.endIndex;
39
+ if (endIndex === undefined || !support.groundingChunkIndices?.length) {
40
+ continue;
41
+ }
42
+ const citationLinks = support.groundingChunkIndices
43
+ .map((i) => {
44
+ const uri = chunks[i]?.web?.uri;
45
+ const title = chunks[i]?.web?.title;
46
+ if (uri) {
47
+ return `[${title || i + 1}](${uri})`;
48
+ }
49
+ return null;
50
+ })
51
+ .filter(Boolean);
52
+ if (citationLinks.length > 0) {
53
+ const citationString = ' ' + citationLinks.join(', ');
54
+ result = result.slice(0, endIndex) + citationString + result.slice(endIndex);
55
+ }
56
+ }
57
+ return result;
58
+ }
59
+ export async function searchCommand(argv) {
60
+ const { values, positionals } = parseArgs({
61
+ args: argv,
62
+ options: {
63
+ help: { type: 'boolean', short: 'h', default: false },
64
+ 'no-citations': { type: 'boolean', default: false },
65
+ },
66
+ allowPositionals: true,
67
+ });
68
+ if (values.help) {
69
+ showHelp();
70
+ return;
71
+ }
72
+ // Get query from positional args
73
+ const query = positionals.join(' ');
74
+ if (!query) {
75
+ printError('No search query provided');
76
+ printMuted('Usage: gemini search "your query"');
77
+ process.exit(1);
78
+ }
79
+ const theme = t();
80
+ const s = spinner();
81
+ const returnCitations = !values['no-citations'];
82
+ try {
83
+ // Suppress logger output for CLI
84
+ setupLogger('quiet');
85
+ const apiKey = process.env.GEMINI_API_KEY;
86
+ if (!apiKey) {
87
+ throw new Error('GEMINI_API_KEY not set');
88
+ }
89
+ s.start('Searching the web...');
90
+ const genAI = new GoogleGenAI({ apiKey });
91
+ const model = process.env.GEMINI_PRO_MODEL || 'gemini-3-pro-preview';
92
+ // Execute with Google Search tool enabled
93
+ const response = await genAI.models.generateContent({
94
+ model,
95
+ contents: query,
96
+ config: {
97
+ tools: [{ googleSearch: {} }],
98
+ },
99
+ });
100
+ const candidate = response.candidates?.[0];
101
+ if (!candidate) {
102
+ throw new Error('No response from search');
103
+ }
104
+ let responseText = response.text || '';
105
+ const groundingMetadata = candidate.groundingMetadata;
106
+ // Build response with citations if requested
107
+ const sources = [];
108
+ if (returnCitations && groundingMetadata) {
109
+ const supports = groundingMetadata.groundingSupports || [];
110
+ const chunks = groundingMetadata.groundingChunks || [];
111
+ if (supports.length > 0 && chunks.length > 0) {
112
+ responseText = addCitations(responseText, supports, chunks);
113
+ }
114
+ // Collect unique sources
115
+ const seenUrls = new Set();
116
+ for (const chunk of chunks) {
117
+ if (chunk.web?.uri && !seenUrls.has(chunk.web.uri)) {
118
+ seenUrls.add(chunk.web.uri);
119
+ sources.push({
120
+ title: chunk.web.title || 'Source',
121
+ url: chunk.web.uri,
122
+ });
123
+ }
124
+ }
125
+ }
126
+ s.success('Search complete');
127
+ print('');
128
+ // Display the response
129
+ print(responseText);
130
+ print('');
131
+ // Display sources if available
132
+ if (sources.length > 0) {
133
+ print(theme.colors.muted('─'.repeat(40)));
134
+ print(theme.colors.primary('Sources:'));
135
+ for (const source of sources) {
136
+ print(` ${theme.symbols.bullet} ${theme.colors.info(source.title)}`);
137
+ print(` ${theme.colors.muted(source.url)}`);
138
+ }
139
+ }
140
+ // Show search queries used
141
+ if (groundingMetadata?.webSearchQueries?.length) {
142
+ print('');
143
+ print(theme.colors.muted(`Searches: ${groundingMetadata.webSearchQueries.join(', ')}`));
144
+ }
145
+ print('');
146
+ }
147
+ catch (error) {
148
+ s.error('Search failed');
149
+ printError(error instanceof Error ? error.message : String(error));
150
+ process.exit(1);
151
+ }
152
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Speak Command
3
+ *
4
+ * Text-to-speech with multiple voices.
5
+ * gemini speak "your text" --voice Kore
6
+ */
7
+ export declare function speakCommand(argv: string[]): Promise<void>;
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Speak Command
3
+ *
4
+ * Text-to-speech with multiple voices.
5
+ * gemini speak "your text" --voice Kore
6
+ */
7
+ import { parseArgs } from 'node:util';
8
+ import { GoogleGenAI, Modality } from '@google/genai';
9
+ import { setupLogger } from '../../utils/logger.js';
10
+ import { getOutputDir } from '../../gemini-client.js';
11
+ import { spinner, print, printError, printSuccess, printMuted, t, header, box } from '../ui/index.js';
12
+ import { join } from 'node:path';
13
+ // Available voices
14
+ const VOICES = [
15
+ 'Zephyr', 'Puck', 'Charon', 'Kore', 'Fenrir', 'Leda', 'Orus', 'Aoede',
16
+ 'Callirrhoe', 'Autonoe', 'Enceladus', 'Iapetus', 'Umbriel', 'Algieba',
17
+ 'Despina', 'Erinome', 'Algenib', 'Rasalgethi', 'Laomedeia', 'Achernar',
18
+ 'Alnilam', 'Schedar', 'Gacrux', 'Pulcherrima', 'Achird', 'Zubenelgenubi',
19
+ 'Vindemiatrix', 'Sadachbia', 'Sadaltager', 'Sulafat'
20
+ ];
21
+ function showHelp() {
22
+ const theme = t();
23
+ print(header('gemini speak', 'Text-to-speech'));
24
+ print('');
25
+ print(theme.colors.primary('Usage:'));
26
+ print(` gemini speak ${theme.colors.muted('"your text"')} [options]`);
27
+ print('');
28
+ print(theme.colors.primary('Options:'));
29
+ print(` ${theme.colors.highlight('--voice, -v')} ${theme.colors.muted('Voice to use (default: Kore)')}`);
30
+ print(` ${theme.colors.highlight('--output, -o')} ${theme.colors.muted('Output file path')}`);
31
+ print(` ${theme.colors.highlight('--style, -s')} ${theme.colors.muted('Speaking style (e.g., "cheerfully", "sadly")')}`);
32
+ print(` ${theme.colors.highlight('--list-voices')} ${theme.colors.muted('List all available voices')}`);
33
+ print(` ${theme.colors.highlight('--help, -h')} ${theme.colors.muted('Show this help')}`);
34
+ print('');
35
+ print(theme.colors.primary('Popular Voices:'));
36
+ print(theme.colors.muted(' Kore - Firm, authoritative'));
37
+ print(theme.colors.muted(' Puck - Upbeat, energetic'));
38
+ print(theme.colors.muted(' Zephyr - Bright, clear'));
39
+ print(theme.colors.muted(' Charon - Informative, calm'));
40
+ print(theme.colors.muted(' Aoede - Breezy, light'));
41
+ print('');
42
+ print(theme.colors.primary('Examples:'));
43
+ print(theme.colors.muted(' gemini speak "Hello world!" --voice Puck'));
44
+ print(theme.colors.muted(' gemini speak "Important message" -v Kore -o message.mp3'));
45
+ print(theme.colors.muted(' gemini speak "Exciting news!" --style "enthusiastically"'));
46
+ print(theme.colors.muted(' gemini speak --list-voices'));
47
+ }
48
+ function listVoices() {
49
+ const theme = t();
50
+ print(header('Available Voices', '30 TTS voices'));
51
+ print('');
52
+ // Group voices into columns
53
+ const cols = 5;
54
+ const rows = Math.ceil(VOICES.length / cols);
55
+ for (let row = 0; row < rows; row++) {
56
+ let line = ' ';
57
+ for (let col = 0; col < cols; col++) {
58
+ const idx = row + col * rows;
59
+ if (idx < VOICES.length) {
60
+ const voice = VOICES[idx];
61
+ line += voice.padEnd(16);
62
+ }
63
+ }
64
+ print(theme.colors.muted(line));
65
+ }
66
+ print('');
67
+ }
68
+ export async function speakCommand(argv) {
69
+ const { values, positionals } = parseArgs({
70
+ args: argv,
71
+ options: {
72
+ help: { type: 'boolean', short: 'h', default: false },
73
+ voice: { type: 'string', short: 'v', default: 'Kore' },
74
+ output: { type: 'string', short: 'o' },
75
+ style: { type: 'string', short: 's' },
76
+ 'list-voices': { type: 'boolean', default: false },
77
+ },
78
+ allowPositionals: true,
79
+ });
80
+ if (values.help) {
81
+ showHelp();
82
+ return;
83
+ }
84
+ if (values['list-voices']) {
85
+ listVoices();
86
+ return;
87
+ }
88
+ // Get text from positional args
89
+ const text = positionals.join(' ');
90
+ if (!text) {
91
+ printError('No text provided');
92
+ printMuted('Usage: gemini speak "your text"');
93
+ process.exit(1);
94
+ }
95
+ const theme = t();
96
+ const s = spinner();
97
+ const voice = values.voice;
98
+ const style = values.style;
99
+ // Validate voice
100
+ if (!VOICES.includes(voice)) {
101
+ printError(`Unknown voice: ${voice}`);
102
+ printMuted(`Run 'gemini speak --list-voices' to see available voices`);
103
+ process.exit(1);
104
+ }
105
+ try {
106
+ // Suppress logger output for CLI
107
+ setupLogger('quiet');
108
+ const apiKey = process.env.GEMINI_API_KEY;
109
+ if (!apiKey) {
110
+ throw new Error('GEMINI_API_KEY not set');
111
+ }
112
+ s.start(`Generating speech with ${voice}...`);
113
+ const genAI = new GoogleGenAI({ apiKey });
114
+ // Build prompt with optional style
115
+ let prompt = text;
116
+ if (style) {
117
+ prompt = `Say this ${style}: "${text}"`;
118
+ }
119
+ // Generate speech
120
+ const response = await genAI.models.generateContent({
121
+ model: 'gemini-2.5-flash-preview-tts',
122
+ contents: prompt,
123
+ config: {
124
+ responseModalities: [Modality.AUDIO],
125
+ speechConfig: {
126
+ voiceConfig: {
127
+ prebuiltVoiceConfig: {
128
+ voiceName: voice,
129
+ },
130
+ },
131
+ },
132
+ },
133
+ });
134
+ // Extract audio data
135
+ const candidate = response.candidates?.[0];
136
+ if (!candidate?.content?.parts?.[0]) {
137
+ throw new Error('No audio generated');
138
+ }
139
+ const part = candidate.content.parts[0];
140
+ if (!('inlineData' in part) || !part.inlineData?.data) {
141
+ throw new Error('No audio data in response');
142
+ }
143
+ // Determine output path
144
+ const outputPath = values.output ||
145
+ join(getOutputDir(), `speech-${Date.now()}.mp3`);
146
+ // Save audio file
147
+ const audioBuffer = Buffer.from(part.inlineData.data, 'base64');
148
+ await Bun.write(outputPath, audioBuffer);
149
+ s.success('Speech generated!');
150
+ print('');
151
+ // Show info
152
+ const infoLines = [
153
+ `${theme.colors.primary('Voice:')} ${voice}`,
154
+ `${theme.colors.primary('Text:')} ${text.substring(0, 50)}${text.length > 50 ? '...' : ''}`,
155
+ style ? `${theme.colors.primary('Style:')} ${style}` : null,
156
+ `${theme.colors.primary('File:')} ${outputPath}`,
157
+ `${theme.colors.primary('Size:')} ${(audioBuffer.length / 1024).toFixed(1)} KB`,
158
+ ].filter(Boolean);
159
+ print(box(infoLines, { title: 'Text-to-Speech' }));
160
+ print('');
161
+ printSuccess(`Audio saved to: ${outputPath}`);
162
+ }
163
+ catch (error) {
164
+ s.error('Speech generation failed');
165
+ printError(error instanceof Error ? error.message : String(error));
166
+ process.exit(1);
167
+ }
168
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tokens Command
3
+ *
4
+ * Count tokens in text or files.
5
+ * gemini tokens "your text"
6
+ * gemini tokens @file.txt
7
+ */
8
+ export declare function tokensCommand(argv: string[]): Promise<void>;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Tokens Command
3
+ *
4
+ * Count tokens in text or files.
5
+ * gemini tokens "your text"
6
+ * gemini tokens @file.txt
7
+ */
8
+ import { parseArgs } from 'node:util';
9
+ import { initGeminiClient, countTokens } from '../../gemini-client.js';
10
+ import { setupLogger } from '../../utils/logger.js';
11
+ import { spinner, print, printError, printMuted, t, header, box } from '../ui/index.js';
12
+ function showHelp() {
13
+ const theme = t();
14
+ print(header('gemini tokens', 'Count tokens in text or files'));
15
+ print('');
16
+ print(theme.colors.primary('Usage:'));
17
+ print(` gemini tokens ${theme.colors.muted('"your text"')}`);
18
+ print(` gemini tokens ${theme.colors.muted('@file.txt')}`);
19
+ print('');
20
+ print(theme.colors.primary('Options:'));
21
+ print(` ${theme.colors.highlight('--model, -m')} ${theme.colors.muted('Model for tokenization: pro, flash (default: flash)')}`);
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 tokens "Hello, world!"'));
26
+ print(theme.colors.muted(' gemini tokens @README.md'));
27
+ print(theme.colors.muted(' gemini tokens @src/index.ts --model pro'));
28
+ }
29
+ export async function tokensCommand(argv) {
30
+ const { values, positionals } = parseArgs({
31
+ args: argv,
32
+ options: {
33
+ help: { type: 'boolean', short: 'h', default: false },
34
+ model: { type: 'string', short: 'm', default: 'flash' },
35
+ },
36
+ allowPositionals: true,
37
+ });
38
+ if (values.help) {
39
+ showHelp();
40
+ return;
41
+ }
42
+ // Get input from positional args
43
+ const input = positionals.join(' ');
44
+ if (!input) {
45
+ printError('No text or file provided');
46
+ printMuted('Usage: gemini tokens "your text" or gemini tokens @file.txt');
47
+ process.exit(1);
48
+ }
49
+ const theme = t();
50
+ const s = spinner();
51
+ try {
52
+ // Suppress logger output for CLI
53
+ setupLogger('quiet');
54
+ // Check if input is a file reference
55
+ let text;
56
+ let source;
57
+ if (input.startsWith('@')) {
58
+ const filePath = input.slice(1);
59
+ s.start(`Reading ${filePath}...`);
60
+ const file = Bun.file(filePath);
61
+ const exists = await file.exists();
62
+ if (!exists) {
63
+ s.error(`File not found: ${filePath}`);
64
+ process.exit(1);
65
+ }
66
+ text = await file.text();
67
+ source = filePath;
68
+ }
69
+ else {
70
+ text = input;
71
+ source = 'text input';
72
+ }
73
+ // Initialize Gemini client
74
+ s.update('Connecting to Gemini...');
75
+ await initGeminiClient();
76
+ // Count tokens
77
+ s.update('Counting tokens...');
78
+ const model = values.model;
79
+ const result = await countTokens(text, model);
80
+ s.success('Token count complete');
81
+ print('');
82
+ // Display results in a nice box
83
+ const lines = [
84
+ `${theme.colors.primary('Source:')} ${source}`,
85
+ `${theme.colors.primary('Model:')} ${model}`,
86
+ `${theme.colors.primary('Tokens:')} ${theme.colors.highlight(result.totalTokens.toLocaleString())}`,
87
+ ];
88
+ // Add character count for context
89
+ lines.push(`${theme.colors.muted('Characters:')} ${text.length.toLocaleString()}`);
90
+ // Estimate cost (rough estimates based on typical pricing)
91
+ // Input: ~$0.075 per 1M tokens for Flash, ~$1.25 per 1M tokens for Pro
92
+ const costPer1M = model === 'flash' ? 0.075 : 1.25;
93
+ const estimatedCost = (result.totalTokens / 1_000_000) * costPer1M;
94
+ if (estimatedCost > 0.0001) {
95
+ lines.push(`${theme.colors.muted('Est. cost:')} $${estimatedCost.toFixed(6)}`);
96
+ }
97
+ print(box(lines, { title: 'Token Count' }));
98
+ print('');
99
+ }
100
+ catch (error) {
101
+ s.error('Token count failed');
102
+ printError(error instanceof Error ? error.message : String(error));
103
+ process.exit(1);
104
+ }
105
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Video Command
3
+ *
4
+ * Generate videos with Gemini's Veo model.
5
+ * gemini video "a cat playing piano"
6
+ */
7
+ export declare function videoCommand(argv: string[]): Promise<void>;