@rlabs-inc/gemini-mcp 0.7.1 → 0.8.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 +17 -17
- package/dist/cli/commands/config.d.ts +2 -2
- package/dist/cli/commands/config.js +11 -11
- package/dist/cli/commands/image.d.ts +1 -1
- package/dist/cli/commands/image.js +8 -8
- package/dist/cli/commands/query.d.ts +1 -1
- package/dist/cli/commands/query.js +7 -7
- package/dist/cli/commands/research.d.ts +1 -1
- package/dist/cli/commands/research.js +9 -9
- package/dist/cli/commands/search.d.ts +1 -1
- package/dist/cli/commands/search.js +7 -7
- package/dist/cli/commands/speak.d.ts +1 -1
- package/dist/cli/commands/speak.js +9 -9
- package/dist/cli/commands/tokens.d.ts +2 -2
- package/dist/cli/commands/tokens.js +9 -9
- package/dist/cli/commands/video.d.ts +1 -1
- package/dist/cli/commands/video.js +7 -7
- package/dist/cli/index.js +10 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/server.js +3 -1
- package/dist/tools/image-analyze.d.ts +15 -0
- package/dist/tools/image-analyze.js +363 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A Model Context Protocol (MCP) server for integrating Google's Gemini 3 models w
|
|
|
5
5
|
[](https://www.npmjs.com/package/@rlabs-inc/gemini-mcp)
|
|
6
6
|
[](https://registry.modelcontextprotocol.io)
|
|
7
7
|
|
|
8
|
-
## What's New in v0.7.
|
|
8
|
+
## What's New in v0.7.2
|
|
9
9
|
|
|
10
10
|
**Beautiful CLI with Themes!** Use Gemini directly from your terminal:
|
|
11
11
|
|
|
@@ -14,26 +14,26 @@ A Model Context Protocol (MCP) server for integrating Google's Gemini 3 models w
|
|
|
14
14
|
npm install -g @rlabs-inc/gemini-mcp
|
|
15
15
|
|
|
16
16
|
# Set your API key once
|
|
17
|
-
|
|
17
|
+
gcli config set api-key YOUR_KEY
|
|
18
18
|
|
|
19
19
|
# Generate images, videos, search, research, and more!
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
gcli image "a cat astronaut" --size 4K
|
|
21
|
+
gcli search "latest AI news"
|
|
22
|
+
gcli research "quantum computing applications" --wait
|
|
23
|
+
gcli speak "Hello world" --voice Puck
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
**5 Beautiful Themes:** terminal, neon, ocean, forest, minimal
|
|
27
27
|
|
|
28
28
|
**CLI Commands:**
|
|
29
|
-
- `
|
|
30
|
-
- `
|
|
31
|
-
- `
|
|
32
|
-
- `
|
|
33
|
-
- `
|
|
34
|
-
- `
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
29
|
+
- `gcli query` - Direct Gemini queries with thinking levels
|
|
30
|
+
- `gcli search` - Real-time web search with citations
|
|
31
|
+
- `gcli research` - Deep research agent
|
|
32
|
+
- `gcli image` - Generate images (up to 4K)
|
|
33
|
+
- `gcli video` - Generate videos with Veo
|
|
34
|
+
- `gcli speak` - Text-to-speech with 30 voices
|
|
35
|
+
- `gcli tokens` - Count tokens and estimate costs
|
|
36
|
+
- `gcli config` - Manage settings
|
|
37
37
|
|
|
38
38
|
**MCP Registry Support:** Now discoverable in the official MCP ecosystem!
|
|
39
39
|
|
|
@@ -92,11 +92,11 @@ claude mcp add gemini -s user -- env GEMINI_API_KEY=YOUR_KEY bunx @rlabs-inc/gem
|
|
|
92
92
|
npm install -g @rlabs-inc/gemini-mcp
|
|
93
93
|
|
|
94
94
|
# Set your API key once (stored securely)
|
|
95
|
-
|
|
95
|
+
gcli config set api-key YOUR_KEY
|
|
96
96
|
|
|
97
97
|
# Now use any command!
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
gcli search "latest news"
|
|
99
|
+
glci image "sunset over mountains" --ratio 16:9
|
|
100
100
|
```
|
|
101
101
|
|
|
102
102
|
**Get your API key:** Visit [Google AI Studio](https://aistudio.google.com/apikey) - it's free and takes seconds!
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
* Config Command
|
|
3
3
|
*
|
|
4
4
|
* Set and view CLI configuration.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* gcli config set api-key YOUR_KEY
|
|
6
|
+
* gcli config show
|
|
7
7
|
*/
|
|
8
8
|
import { parseArgs } from 'node:util';
|
|
9
9
|
import { loadConfig, saveConfig, getConfigPath } from '../config.js';
|
|
10
10
|
import { print, printError, printSuccess, printMuted, printWarning, t, header, box } from '../ui/index.js';
|
|
11
11
|
function showHelp() {
|
|
12
12
|
const theme = t();
|
|
13
|
-
print(header('
|
|
13
|
+
print(header('gcli config', 'CLI configuration'));
|
|
14
14
|
print('');
|
|
15
15
|
print(theme.colors.primary('Usage:'));
|
|
16
|
-
print(`
|
|
16
|
+
print(` gcli config ${theme.colors.muted('<command>')} [options]`);
|
|
17
17
|
print('');
|
|
18
18
|
print(theme.colors.primary('Commands:'));
|
|
19
19
|
print(` ${theme.colors.highlight('set')} ${theme.colors.muted('Set a configuration value')}`);
|
|
@@ -28,10 +28,10 @@ function showHelp() {
|
|
|
28
28
|
print(` ${theme.colors.highlight('default-model')} ${theme.colors.muted('Default model (pro or flash)')}`);
|
|
29
29
|
print('');
|
|
30
30
|
print(theme.colors.primary('Examples:'));
|
|
31
|
-
print(theme.colors.muted('
|
|
32
|
-
print(theme.colors.muted('
|
|
33
|
-
print(theme.colors.muted('
|
|
34
|
-
print(theme.colors.muted('
|
|
31
|
+
print(theme.colors.muted(' gcli config set api-key AIzaSy...'));
|
|
32
|
+
print(theme.colors.muted(' gcli config set theme neon'));
|
|
33
|
+
print(theme.colors.muted(' gcli config set output-dir ~/Documents/Gemini'));
|
|
34
|
+
print(theme.colors.muted(' gcli config show'));
|
|
35
35
|
}
|
|
36
36
|
async function showConfig() {
|
|
37
37
|
const theme = t();
|
|
@@ -51,7 +51,7 @@ async function showConfig() {
|
|
|
51
51
|
print('');
|
|
52
52
|
if (!config.apiKey) {
|
|
53
53
|
printWarning('API key not set. Set it with:');
|
|
54
|
-
print(theme.colors.muted('
|
|
54
|
+
print(theme.colors.muted(' gcli config set api-key YOUR_API_KEY'));
|
|
55
55
|
print('');
|
|
56
56
|
print(theme.colors.muted('Or set the GEMINI_API_KEY environment variable.'));
|
|
57
57
|
}
|
|
@@ -133,8 +133,8 @@ export async function configCommand(argv) {
|
|
|
133
133
|
const key = positionals[1];
|
|
134
134
|
const value = positionals.slice(2).join(' ');
|
|
135
135
|
if (!key || !value) {
|
|
136
|
-
printError('Usage:
|
|
137
|
-
printMuted('Example:
|
|
136
|
+
printError('Usage: gcli config set <key> <value>');
|
|
137
|
+
printMuted('Example: gcli config set api-key YOUR_KEY');
|
|
138
138
|
process.exit(1);
|
|
139
139
|
}
|
|
140
140
|
await setConfig(key, value);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Image Command
|
|
3
3
|
*
|
|
4
4
|
* Generate images with Gemini's Imagen model.
|
|
5
|
-
*
|
|
5
|
+
* gcli image "a cat in space"
|
|
6
6
|
*/
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import { initGeminiClient, generateImage } from '../../gemini-client.js';
|
|
@@ -13,10 +13,10 @@ const VALID_SIZES = ['1K', '2K', '4K'];
|
|
|
13
13
|
const VALID_RATIOS = ['1:1', '2:3', '3:2', '3:4', '4:3', '4:5', '5:4', '9:16', '16:9', '21:9'];
|
|
14
14
|
function showHelp() {
|
|
15
15
|
const theme = t();
|
|
16
|
-
print(header('
|
|
16
|
+
print(header('gcli image', 'Generate images with AI'));
|
|
17
17
|
print('');
|
|
18
18
|
print(theme.colors.primary('Usage:'));
|
|
19
|
-
print(`
|
|
19
|
+
print(` gcli image ${theme.colors.muted('"your prompt"')} [options]`);
|
|
20
20
|
print('');
|
|
21
21
|
print(theme.colors.primary('Options:'));
|
|
22
22
|
print(` ${theme.colors.highlight('--size, -s')} ${theme.colors.muted('Resolution: 1K, 2K, 4K (default: 2K)')}`);
|
|
@@ -35,10 +35,10 @@ function showHelp() {
|
|
|
35
35
|
print(theme.colors.muted(' 21:9 - Ultrawide cinematic'));
|
|
36
36
|
print('');
|
|
37
37
|
print(theme.colors.primary('Examples:'));
|
|
38
|
-
print(theme.colors.muted('
|
|
39
|
-
print(theme.colors.muted('
|
|
40
|
-
print(theme.colors.muted('
|
|
41
|
-
print(theme.colors.muted('
|
|
38
|
+
print(theme.colors.muted(' gcli image "a cat astronaut floating in space"'));
|
|
39
|
+
print(theme.colors.muted(' gcli image "sunset over mountains" --ratio 16:9 --size 4K'));
|
|
40
|
+
print(theme.colors.muted(' gcli image "portrait of a robot" -r 3:4 --style "oil painting"'));
|
|
41
|
+
print(theme.colors.muted(' gcli image "Eiffel Tower at night" --search'));
|
|
42
42
|
}
|
|
43
43
|
export async function imageCommand(argv) {
|
|
44
44
|
const { values, positionals } = parseArgs({
|
|
@@ -61,7 +61,7 @@ export async function imageCommand(argv) {
|
|
|
61
61
|
const prompt = positionals.join(' ');
|
|
62
62
|
if (!prompt) {
|
|
63
63
|
printError('No image prompt provided');
|
|
64
|
-
printMuted('Usage:
|
|
64
|
+
printMuted('Usage: gcli image "your prompt"');
|
|
65
65
|
process.exit(1);
|
|
66
66
|
}
|
|
67
67
|
const theme = t();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Query Command
|
|
3
3
|
*
|
|
4
4
|
* Direct queries to Gemini with thinking level control.
|
|
5
|
-
*
|
|
5
|
+
* gcli query "your prompt" [--thinking high]
|
|
6
6
|
*/
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import { initGeminiClient, generateWithGeminiPro, generateWithGeminiFlash } from '../../gemini-client.js';
|
|
@@ -10,10 +10,10 @@ import { setupLogger } from '../../utils/logger.js';
|
|
|
10
10
|
import { spinner, print, printError, printMuted, t, header } from '../ui/index.js';
|
|
11
11
|
function showHelp() {
|
|
12
12
|
const theme = t();
|
|
13
|
-
print(header('
|
|
13
|
+
print(header('gcli query', 'Query Gemini directly'));
|
|
14
14
|
print('');
|
|
15
15
|
print(theme.colors.primary('Usage:'));
|
|
16
|
-
print(`
|
|
16
|
+
print(` gcli query ${theme.colors.muted('"your prompt"')} [options]`);
|
|
17
17
|
print('');
|
|
18
18
|
print(theme.colors.primary('Options:'));
|
|
19
19
|
print(` ${theme.colors.highlight('--thinking, -t')} ${theme.colors.muted('Thinking level: minimal, low, medium, high (default: high)')}`);
|
|
@@ -21,9 +21,9 @@ function showHelp() {
|
|
|
21
21
|
print(` ${theme.colors.highlight('--help, -h')} ${theme.colors.muted('Show this help')}`);
|
|
22
22
|
print('');
|
|
23
23
|
print(theme.colors.primary('Examples:'));
|
|
24
|
-
print(theme.colors.muted('
|
|
25
|
-
print(theme.colors.muted('
|
|
26
|
-
print(theme.colors.muted('
|
|
24
|
+
print(theme.colors.muted(' gcli query "What is the meaning of life?"'));
|
|
25
|
+
print(theme.colors.muted(' gcli query "Explain quantum computing" --thinking high'));
|
|
26
|
+
print(theme.colors.muted(' gcli query "Quick fact check" -t minimal -m flash'));
|
|
27
27
|
}
|
|
28
28
|
export async function queryCommand(argv) {
|
|
29
29
|
const { values, positionals } = parseArgs({
|
|
@@ -43,7 +43,7 @@ export async function queryCommand(argv) {
|
|
|
43
43
|
const prompt = positionals.join(' ');
|
|
44
44
|
if (!prompt) {
|
|
45
45
|
printError('No prompt provided');
|
|
46
|
-
printMuted('Usage:
|
|
46
|
+
printMuted('Usage: gcli query "your prompt"');
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
49
|
// Validate thinking level
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Research Command
|
|
3
3
|
*
|
|
4
4
|
* Deep research agent for comprehensive investigation.
|
|
5
|
-
*
|
|
5
|
+
* gcli research "your research question"
|
|
6
6
|
*/
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import { initGeminiClient, startDeepResearch, checkDeepResearch } from '../../gemini-client.js';
|
|
@@ -10,10 +10,10 @@ import { setupLogger } from '../../utils/logger.js';
|
|
|
10
10
|
import { spinner, progress, print, printError, printSuccess, printMuted, printWarning, t, header, box } from '../ui/index.js';
|
|
11
11
|
function showHelp() {
|
|
12
12
|
const theme = t();
|
|
13
|
-
print(header('
|
|
13
|
+
print(header('gcli research', 'Deep research agent'));
|
|
14
14
|
print('');
|
|
15
15
|
print(theme.colors.primary('Usage:'));
|
|
16
|
-
print(`
|
|
16
|
+
print(` gcli research ${theme.colors.muted('"your research question"')}`);
|
|
17
17
|
print('');
|
|
18
18
|
print(theme.colors.primary('Options:'));
|
|
19
19
|
print(` ${theme.colors.highlight('--format, -f')} ${theme.colors.muted('Output format: report, outline, brief (default: report)')}`);
|
|
@@ -21,9 +21,9 @@ function showHelp() {
|
|
|
21
21
|
print(` ${theme.colors.highlight('--help, -h')} ${theme.colors.muted('Show this help')}`);
|
|
22
22
|
print('');
|
|
23
23
|
print(theme.colors.primary('Examples:'));
|
|
24
|
-
print(theme.colors.muted('
|
|
25
|
-
print(theme.colors.muted('
|
|
26
|
-
print(theme.colors.muted('
|
|
24
|
+
print(theme.colors.muted(' gcli research "MCP ecosystem and best practices"'));
|
|
25
|
+
print(theme.colors.muted(' gcli research "AI coding assistants comparison" --format outline'));
|
|
26
|
+
print(theme.colors.muted(' gcli research "Bun vs Node.js performance" --wait'));
|
|
27
27
|
print('');
|
|
28
28
|
print(theme.colors.warning(`${theme.symbols.warning} Deep research typically takes 5-20 minutes (max 60 min)`));
|
|
29
29
|
}
|
|
@@ -45,7 +45,7 @@ export async function researchCommand(argv) {
|
|
|
45
45
|
const query = positionals.join(' ');
|
|
46
46
|
if (!query) {
|
|
47
47
|
printError('No research question provided');
|
|
48
|
-
printMuted('Usage:
|
|
48
|
+
printMuted('Usage: gcli research "your question"');
|
|
49
49
|
process.exit(1);
|
|
50
50
|
}
|
|
51
51
|
const theme = t();
|
|
@@ -82,7 +82,7 @@ export async function researchCommand(argv) {
|
|
|
82
82
|
print(theme.colors.info(`${theme.symbols.info} Research is running in the background.`));
|
|
83
83
|
print('');
|
|
84
84
|
print('To check status:');
|
|
85
|
-
print(theme.colors.muted(`
|
|
85
|
+
print(theme.colors.muted(` gcli research-status ${result.id}`));
|
|
86
86
|
print('');
|
|
87
87
|
print(theme.colors.muted('Research typically takes 5-20 minutes (max 60 min).'));
|
|
88
88
|
print(theme.colors.muted('Results will be saved to your configured output directory.'));
|
|
@@ -137,7 +137,7 @@ export async function researchCommand(argv) {
|
|
|
137
137
|
// Timed out
|
|
138
138
|
p.fail('Research timed out');
|
|
139
139
|
printWarning('Research is still running. Check status later:');
|
|
140
|
-
print(theme.colors.muted(`
|
|
140
|
+
print(theme.colors.muted(` gcli research-status ${result.id}`));
|
|
141
141
|
}
|
|
142
142
|
catch (error) {
|
|
143
143
|
s.error('Research failed');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Search Command
|
|
3
3
|
*
|
|
4
4
|
* Real-time web search powered by Gemini + Google Search.
|
|
5
|
-
*
|
|
5
|
+
* gcli search "your query"
|
|
6
6
|
*/
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import { GoogleGenAI } from '@google/genai';
|
|
@@ -10,19 +10,19 @@ import { setupLogger } from '../../utils/logger.js';
|
|
|
10
10
|
import { spinner, print, printError, printMuted, t, header } from '../ui/index.js';
|
|
11
11
|
function showHelp() {
|
|
12
12
|
const theme = t();
|
|
13
|
-
print(header('
|
|
13
|
+
print(header('gcli search', 'Real-time web search'));
|
|
14
14
|
print('');
|
|
15
15
|
print(theme.colors.primary('Usage:'));
|
|
16
|
-
print(`
|
|
16
|
+
print(` gcli search ${theme.colors.muted('"your query"')}`);
|
|
17
17
|
print('');
|
|
18
18
|
print(theme.colors.primary('Options:'));
|
|
19
19
|
print(` ${theme.colors.highlight('--no-citations')} ${theme.colors.muted('Hide inline citations')}`);
|
|
20
20
|
print(` ${theme.colors.highlight('--help, -h')} ${theme.colors.muted('Show this help')}`);
|
|
21
21
|
print('');
|
|
22
22
|
print(theme.colors.primary('Examples:'));
|
|
23
|
-
print(theme.colors.muted('
|
|
24
|
-
print(theme.colors.muted('
|
|
25
|
-
print(theme.colors.muted('
|
|
23
|
+
print(theme.colors.muted(' gcli search "latest news about AI"'));
|
|
24
|
+
print(theme.colors.muted(' gcli search "weather in São Paulo"'));
|
|
25
|
+
print(theme.colors.muted(' gcli search "MCP Model Context Protocol"'));
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Add inline citations to text based on grounding metadata
|
|
@@ -73,7 +73,7 @@ export async function searchCommand(argv) {
|
|
|
73
73
|
const query = positionals.join(' ');
|
|
74
74
|
if (!query) {
|
|
75
75
|
printError('No search query provided');
|
|
76
|
-
printMuted('Usage:
|
|
76
|
+
printMuted('Usage: gcli search "your query"');
|
|
77
77
|
process.exit(1);
|
|
78
78
|
}
|
|
79
79
|
const theme = t();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Speak Command
|
|
3
3
|
*
|
|
4
4
|
* Text-to-speech with multiple voices.
|
|
5
|
-
*
|
|
5
|
+
* gcli speak "your text" --voice Kore
|
|
6
6
|
*/
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import { GoogleGenAI, Modality } from '@google/genai';
|
|
@@ -20,10 +20,10 @@ const VOICES = [
|
|
|
20
20
|
];
|
|
21
21
|
function showHelp() {
|
|
22
22
|
const theme = t();
|
|
23
|
-
print(header('
|
|
23
|
+
print(header('gcli speak', 'Text-to-speech'));
|
|
24
24
|
print('');
|
|
25
25
|
print(theme.colors.primary('Usage:'));
|
|
26
|
-
print(`
|
|
26
|
+
print(` gcli speak ${theme.colors.muted('"your text"')} [options]`);
|
|
27
27
|
print('');
|
|
28
28
|
print(theme.colors.primary('Options:'));
|
|
29
29
|
print(` ${theme.colors.highlight('--voice, -v')} ${theme.colors.muted('Voice to use (default: Kore)')}`);
|
|
@@ -40,10 +40,10 @@ function showHelp() {
|
|
|
40
40
|
print(theme.colors.muted(' Aoede - Breezy, light'));
|
|
41
41
|
print('');
|
|
42
42
|
print(theme.colors.primary('Examples:'));
|
|
43
|
-
print(theme.colors.muted('
|
|
44
|
-
print(theme.colors.muted('
|
|
45
|
-
print(theme.colors.muted('
|
|
46
|
-
print(theme.colors.muted('
|
|
43
|
+
print(theme.colors.muted(' gcli speak "Hello world!" --voice Puck'));
|
|
44
|
+
print(theme.colors.muted(' gcli speak "Important message" -v Kore -o message.mp3'));
|
|
45
|
+
print(theme.colors.muted(' gcli speak "Exciting news!" --style "enthusiastically"'));
|
|
46
|
+
print(theme.colors.muted(' gcli speak --list-voices'));
|
|
47
47
|
}
|
|
48
48
|
function listVoices() {
|
|
49
49
|
const theme = t();
|
|
@@ -89,7 +89,7 @@ export async function speakCommand(argv) {
|
|
|
89
89
|
const text = positionals.join(' ');
|
|
90
90
|
if (!text) {
|
|
91
91
|
printError('No text provided');
|
|
92
|
-
printMuted('Usage:
|
|
92
|
+
printMuted('Usage: gcli speak "your text"');
|
|
93
93
|
process.exit(1);
|
|
94
94
|
}
|
|
95
95
|
const theme = t();
|
|
@@ -99,7 +99,7 @@ export async function speakCommand(argv) {
|
|
|
99
99
|
// Validate voice
|
|
100
100
|
if (!VOICES.includes(voice)) {
|
|
101
101
|
printError(`Unknown voice: ${voice}`);
|
|
102
|
-
printMuted(`Run '
|
|
102
|
+
printMuted(`Run 'gcli speak --list-voices' to see available voices`);
|
|
103
103
|
process.exit(1);
|
|
104
104
|
}
|
|
105
105
|
try {
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Tokens Command
|
|
3
3
|
*
|
|
4
4
|
* Count tokens in text or files.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* gcli tokens "your text"
|
|
6
|
+
* gcli tokens @file.txt
|
|
7
7
|
*/
|
|
8
8
|
import { parseArgs } from 'node:util';
|
|
9
9
|
import { initGeminiClient, countTokens } from '../../gemini-client.js';
|
|
@@ -11,20 +11,20 @@ import { setupLogger } from '../../utils/logger.js';
|
|
|
11
11
|
import { spinner, print, printError, printMuted, t, header, box } from '../ui/index.js';
|
|
12
12
|
function showHelp() {
|
|
13
13
|
const theme = t();
|
|
14
|
-
print(header('
|
|
14
|
+
print(header('gcli tokens', 'Count tokens in text or files'));
|
|
15
15
|
print('');
|
|
16
16
|
print(theme.colors.primary('Usage:'));
|
|
17
|
-
print(`
|
|
18
|
-
print(`
|
|
17
|
+
print(` gcli tokens ${theme.colors.muted('"your text"')}`);
|
|
18
|
+
print(` gcli tokens ${theme.colors.muted('@file.txt')}`);
|
|
19
19
|
print('');
|
|
20
20
|
print(theme.colors.primary('Options:'));
|
|
21
21
|
print(` ${theme.colors.highlight('--model, -m')} ${theme.colors.muted('Model for tokenization: pro, flash (default: flash)')}`);
|
|
22
22
|
print(` ${theme.colors.highlight('--help, -h')} ${theme.colors.muted('Show this help')}`);
|
|
23
23
|
print('');
|
|
24
24
|
print(theme.colors.primary('Examples:'));
|
|
25
|
-
print(theme.colors.muted('
|
|
26
|
-
print(theme.colors.muted('
|
|
27
|
-
print(theme.colors.muted('
|
|
25
|
+
print(theme.colors.muted(' gcli tokens "Hello, world!"'));
|
|
26
|
+
print(theme.colors.muted(' gcli tokens @README.md'));
|
|
27
|
+
print(theme.colors.muted(' gcli tokens @src/index.ts --model pro'));
|
|
28
28
|
}
|
|
29
29
|
export async function tokensCommand(argv) {
|
|
30
30
|
const { values, positionals } = parseArgs({
|
|
@@ -43,7 +43,7 @@ export async function tokensCommand(argv) {
|
|
|
43
43
|
const input = positionals.join(' ');
|
|
44
44
|
if (!input) {
|
|
45
45
|
printError('No text or file provided');
|
|
46
|
-
printMuted('Usage:
|
|
46
|
+
printMuted('Usage: gcli tokens "your text" or gcli tokens @file.txt');
|
|
47
47
|
process.exit(1);
|
|
48
48
|
}
|
|
49
49
|
const theme = t();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Video Command
|
|
3
3
|
*
|
|
4
4
|
* Generate videos with Gemini's Veo model.
|
|
5
|
-
*
|
|
5
|
+
* gcli video "a cat playing piano"
|
|
6
6
|
*/
|
|
7
7
|
import { parseArgs } from 'node:util';
|
|
8
8
|
import { initGeminiClient, startVideoGeneration, checkVideoStatus } from '../../gemini-client.js';
|
|
@@ -10,10 +10,10 @@ import { setupLogger } from '../../utils/logger.js';
|
|
|
10
10
|
import { spinner, progress, print, printError, printSuccess, printMuted, printWarning, t, header, box } from '../ui/index.js';
|
|
11
11
|
function showHelp() {
|
|
12
12
|
const theme = t();
|
|
13
|
-
print(header('
|
|
13
|
+
print(header('gcli video', 'Generate videos with AI'));
|
|
14
14
|
print('');
|
|
15
15
|
print(theme.colors.primary('Usage:'));
|
|
16
|
-
print(`
|
|
16
|
+
print(` gcli video ${theme.colors.muted('"your prompt"')} [options]`);
|
|
17
17
|
print('');
|
|
18
18
|
print(theme.colors.primary('Options:'));
|
|
19
19
|
print(` ${theme.colors.highlight('--ratio, -r')} ${theme.colors.muted('Aspect ratio: 16:9, 9:16 (default: 16:9)')}`);
|
|
@@ -22,9 +22,9 @@ function showHelp() {
|
|
|
22
22
|
print(` ${theme.colors.highlight('--help, -h')} ${theme.colors.muted('Show this help')}`);
|
|
23
23
|
print('');
|
|
24
24
|
print(theme.colors.primary('Examples:'));
|
|
25
|
-
print(theme.colors.muted('
|
|
26
|
-
print(theme.colors.muted('
|
|
27
|
-
print(theme.colors.muted('
|
|
25
|
+
print(theme.colors.muted(' gcli video "a cat playing piano"'));
|
|
26
|
+
print(theme.colors.muted(' gcli video "sunset timelapse" --ratio 16:9 --wait'));
|
|
27
|
+
print(theme.colors.muted(' gcli video "robot dancing" -r 9:16 --negative "text, blur"'));
|
|
28
28
|
print('');
|
|
29
29
|
print(theme.colors.warning(`${theme.symbols.warning} Video generation can take 2-5 minutes`));
|
|
30
30
|
}
|
|
@@ -47,7 +47,7 @@ export async function videoCommand(argv) {
|
|
|
47
47
|
const prompt = positionals.join(' ');
|
|
48
48
|
if (!prompt) {
|
|
49
49
|
printError('No video prompt provided');
|
|
50
|
-
printMuted('Usage:
|
|
50
|
+
printMuted('Usage: gcli video "your prompt"');
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
53
53
|
const theme = t();
|
package/dist/cli/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import { speakCommand } from './commands/speak.js';
|
|
|
14
14
|
import { configCommand } from './commands/config.js';
|
|
15
15
|
import { imageCommand } from './commands/image.js';
|
|
16
16
|
import { videoCommand } from './commands/video.js';
|
|
17
|
-
const VERSION = '0.7.
|
|
17
|
+
const VERSION = '0.7.2';
|
|
18
18
|
// Placeholder command for development
|
|
19
19
|
const placeholderCommand = (name) => ({
|
|
20
20
|
name,
|
|
@@ -71,7 +71,7 @@ function showHelp() {
|
|
|
71
71
|
print(header('Gemini CLI', `AI-powered tools at your terminal (v${VERSION})`));
|
|
72
72
|
print('');
|
|
73
73
|
print(theme.colors.primary('Usage:'));
|
|
74
|
-
print(`
|
|
74
|
+
print(` gcli ${theme.colors.muted('<command>')} [options]`);
|
|
75
75
|
print('');
|
|
76
76
|
print(theme.colors.primary('Commands:'));
|
|
77
77
|
print(` ${theme.colors.highlight('query')} ${theme.colors.muted('Query Gemini directly')}`);
|
|
@@ -90,16 +90,16 @@ function showHelp() {
|
|
|
90
90
|
print(` ${theme.colors.highlight('--theme')} ${theme.colors.muted('Set color theme (terminal, neon, minimal, ocean, forest)')}`);
|
|
91
91
|
print('');
|
|
92
92
|
print(theme.colors.primary('Examples:'));
|
|
93
|
-
print(theme.colors.muted('
|
|
94
|
-
print(theme.colors.muted('
|
|
95
|
-
print(theme.colors.muted('
|
|
96
|
-
print(theme.colors.muted('
|
|
93
|
+
print(theme.colors.muted(' gcli query "What is the meaning of life?"'));
|
|
94
|
+
print(theme.colors.muted(' gcli search "latest AI news"'));
|
|
95
|
+
print(theme.colors.muted(' gcli image "a cat in space" --size 4K'));
|
|
96
|
+
print(theme.colors.muted(' gcli research "MCP ecosystem" --format outline'));
|
|
97
97
|
print('');
|
|
98
|
-
print(theme.colors.muted(`Run '
|
|
98
|
+
print(theme.colors.muted(`Run 'gcli <command> --help' for command-specific options.`));
|
|
99
99
|
}
|
|
100
100
|
function showVersion() {
|
|
101
101
|
const theme = t();
|
|
102
|
-
print(`${theme.colors.primary('
|
|
102
|
+
print(`${theme.colors.primary('gcli')} ${theme.colors.highlight(`v${VERSION}`)}`);
|
|
103
103
|
}
|
|
104
104
|
export async function runCli(argv) {
|
|
105
105
|
// Load config first
|
|
@@ -147,7 +147,7 @@ export async function runCli(argv) {
|
|
|
147
147
|
const command = commands[commandName];
|
|
148
148
|
if (!command) {
|
|
149
149
|
printError(`Unknown command: ${commandName}`);
|
|
150
|
-
printMuted(`Run '
|
|
150
|
+
printMuted(`Run 'gcli --help' for available commands`);
|
|
151
151
|
process.exit(1);
|
|
152
152
|
}
|
|
153
153
|
// Get command arguments (everything after the command)
|
|
@@ -161,7 +161,7 @@ export async function runCli(argv) {
|
|
|
161
161
|
const apiKey = getApiKey(config);
|
|
162
162
|
if (!apiKey) {
|
|
163
163
|
printError('GEMINI_API_KEY environment variable is required');
|
|
164
|
-
printMuted('Set it with:
|
|
164
|
+
printMuted('Set it with: gcli config set api-key YOUR_KEY');
|
|
165
165
|
process.exit(1);
|
|
166
166
|
}
|
|
167
167
|
// Set env var from config for gemini-client.ts to use
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Gemini - Dual Mode Entry Point
|
|
4
4
|
*
|
|
5
5
|
* 1. MCP Server Mode (default): `gemini-mcp` or `gemini serve`
|
|
6
|
-
* 2. CLI Mode: `
|
|
6
|
+
* 2. CLI Mode: `gcli <command> [options]`
|
|
7
7
|
*
|
|
8
8
|
* This integrates Google's Gemini models with Claude Code (MCP)
|
|
9
9
|
* and provides a beautiful standalone CLI experience.
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Gemini - Dual Mode Entry Point
|
|
4
4
|
*
|
|
5
5
|
* 1. MCP Server Mode (default): `gemini-mcp` or `gemini serve`
|
|
6
|
-
* 2. CLI Mode: `
|
|
6
|
+
* 2. CLI Mode: `gcli <command> [options]`
|
|
7
7
|
*
|
|
8
8
|
* This integrates Google's Gemini models with Claude Code (MCP)
|
|
9
9
|
* and provides a beautiful standalone CLI experience.
|
package/dist/server.js
CHANGED
|
@@ -25,6 +25,7 @@ import { registerCacheTool } from './tools/cache.js';
|
|
|
25
25
|
import { registerSpeechTool } from './tools/speech.js';
|
|
26
26
|
import { registerTokenCountTool } from './tools/token-count.js';
|
|
27
27
|
import { registerDeepResearchTool } from './tools/deep-research.js';
|
|
28
|
+
import { registerImageAnalyzeTool } from './tools/image-analyze.js';
|
|
28
29
|
// Import Gemini client and logger
|
|
29
30
|
import { initGeminiClient } from './gemini-client.js';
|
|
30
31
|
import { setupLogger, logger } from './utils/logger.js';
|
|
@@ -114,7 +115,7 @@ For CLI mode, run: gemini --help
|
|
|
114
115
|
// Create MCP server
|
|
115
116
|
const server = new McpServer({
|
|
116
117
|
name: 'Gemini',
|
|
117
|
-
version: '0.7.
|
|
118
|
+
version: '0.7.2',
|
|
118
119
|
});
|
|
119
120
|
// Register tools
|
|
120
121
|
registerQueryTool(server);
|
|
@@ -134,6 +135,7 @@ For CLI mode, run: gemini --help
|
|
|
134
135
|
registerSpeechTool(server);
|
|
135
136
|
registerTokenCountTool(server);
|
|
136
137
|
registerDeepResearchTool(server);
|
|
138
|
+
registerImageAnalyzeTool(server);
|
|
137
139
|
// Start server with stdio transport
|
|
138
140
|
const transport = new StdioServerTransport();
|
|
139
141
|
// Set up error handling for transport
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Analysis Tool - Analyze images with object detection and bounding boxes
|
|
3
|
+
*
|
|
4
|
+
* This tool uses Gemini's vision capabilities to analyze images and detect objects
|
|
5
|
+
* with bounding box coordinates. Returns both normalized box_2d format and pixel coordinates.
|
|
6
|
+
*
|
|
7
|
+
* Bounding Box Format:
|
|
8
|
+
* - box_2d: [y_min, x_min, y_max, x_max] in 0-1000 normalized coordinates
|
|
9
|
+
* - bbox_pixels: {x, y, width, height} in pixel coordinates (when dimensions available)
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
12
|
+
/**
|
|
13
|
+
* Register image analysis tools with the MCP server
|
|
14
|
+
*/
|
|
15
|
+
export declare function registerImageAnalyzeTool(server: McpServer): void;
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Analysis Tool - Analyze images with object detection and bounding boxes
|
|
3
|
+
*
|
|
4
|
+
* This tool uses Gemini's vision capabilities to analyze images and detect objects
|
|
5
|
+
* with bounding box coordinates. Returns both normalized box_2d format and pixel coordinates.
|
|
6
|
+
*
|
|
7
|
+
* Bounding Box Format:
|
|
8
|
+
* - box_2d: [y_min, x_min, y_max, x_max] in 0-1000 normalized coordinates
|
|
9
|
+
* - bbox_pixels: {x, y, width, height} in pixel coordinates (when dimensions available)
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { GoogleGenAI } from "@google/genai";
|
|
13
|
+
import { logger } from "../utils/logger.js";
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
/**
|
|
17
|
+
* Extract image dimensions from PNG or JPEG file
|
|
18
|
+
*/
|
|
19
|
+
function extractImageDimensions(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
const buffer = fs.readFileSync(filePath);
|
|
22
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
23
|
+
if (ext === ".png") {
|
|
24
|
+
// PNG format: width and height are at bytes 16-23 (big-endian)
|
|
25
|
+
if (buffer.length < 24)
|
|
26
|
+
return null;
|
|
27
|
+
if (buffer.toString("ascii", 1, 4) !== "PNG")
|
|
28
|
+
return null;
|
|
29
|
+
const width = buffer.readUInt32BE(16);
|
|
30
|
+
const height = buffer.readUInt32BE(20);
|
|
31
|
+
return { width, height };
|
|
32
|
+
}
|
|
33
|
+
else if (ext === ".jpg" || ext === ".jpeg") {
|
|
34
|
+
// JPEG format: scan for SOF0 or SOF2 markers
|
|
35
|
+
let offset = 2; // Skip initial 0xFFD8
|
|
36
|
+
while (offset < buffer.length - 8) {
|
|
37
|
+
if (buffer[offset] !== 0xff)
|
|
38
|
+
break;
|
|
39
|
+
const marker = buffer[offset + 1];
|
|
40
|
+
const segmentLength = buffer.readUInt16BE(offset + 2);
|
|
41
|
+
// SOF0 (0xC0) or SOF2 (0xC2) markers contain dimensions
|
|
42
|
+
if (marker === 0xc0 || marker === 0xc2) {
|
|
43
|
+
const height = buffer.readUInt16BE(offset + 5);
|
|
44
|
+
const width = buffer.readUInt16BE(offset + 7);
|
|
45
|
+
return { width, height };
|
|
46
|
+
}
|
|
47
|
+
offset += 2 + segmentLength;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
logger.debug(`Failed to extract dimensions: ${error}`);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Convert box_2d normalized coordinates to pixel coordinates
|
|
59
|
+
*/
|
|
60
|
+
function convertToPixelCoords(box2d, width, height) {
|
|
61
|
+
const [yMin, xMin, yMax, xMax] = box2d;
|
|
62
|
+
return {
|
|
63
|
+
x: Math.round((xMin / 1000) * width),
|
|
64
|
+
y: Math.round((yMin / 1000) * height),
|
|
65
|
+
width: Math.round(((xMax - xMin) / 1000) * width),
|
|
66
|
+
height: Math.round(((yMax - yMin) / 1000) * height),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get MIME type from file extension
|
|
71
|
+
*/
|
|
72
|
+
function getImageMimeType(filePath) {
|
|
73
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
74
|
+
const mimeTypes = {
|
|
75
|
+
".jpg": "image/jpeg",
|
|
76
|
+
".jpeg": "image/jpeg",
|
|
77
|
+
".png": "image/png",
|
|
78
|
+
".webp": "image/webp",
|
|
79
|
+
".heic": "image/heic",
|
|
80
|
+
".heif": "image/heif",
|
|
81
|
+
".gif": "image/gif",
|
|
82
|
+
};
|
|
83
|
+
return mimeTypes[ext] || "image/jpeg";
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Register image analysis tools with the MCP server
|
|
87
|
+
*/
|
|
88
|
+
export function registerImageAnalyzeTool(server) {
|
|
89
|
+
server.tool("gemini-analyze-image", {
|
|
90
|
+
imagePath: z
|
|
91
|
+
.string()
|
|
92
|
+
.describe("Path to image file. Supports JPEG, PNG, WebP, HEIC, HEIF, GIF"),
|
|
93
|
+
query: z
|
|
94
|
+
.string()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe('Specific question about the image (e.g., "What objects are in this image?", "Count the people"). Default: "Analyze this image in detail."'),
|
|
97
|
+
detectObjects: z
|
|
98
|
+
.boolean()
|
|
99
|
+
.default(true)
|
|
100
|
+
.describe("Enable object detection with bounding boxes (returns box_2d coordinates). Default: true"),
|
|
101
|
+
model: z
|
|
102
|
+
.enum(["pro", "flash"])
|
|
103
|
+
.default("flash")
|
|
104
|
+
.describe("Model to use: pro (more accurate) or flash (faster). Default: flash"),
|
|
105
|
+
thinkingLevel: z
|
|
106
|
+
.enum(["minimal", "low", "medium", "high"])
|
|
107
|
+
.optional()
|
|
108
|
+
.describe("Reasoning depth: minimal/low for fast responses, medium/high for complex analysis. " +
|
|
109
|
+
"Pro supports low/high only. Flash supports all levels. Default: high"),
|
|
110
|
+
mediaResolution: z
|
|
111
|
+
.enum(["low", "medium", "high"])
|
|
112
|
+
.default("medium")
|
|
113
|
+
.describe("Resolution for processing: low (faster), medium (balanced), high (more detail). Default: medium"),
|
|
114
|
+
}, async ({ imagePath, query, detectObjects, model, thinkingLevel, mediaResolution }) => {
|
|
115
|
+
logger.info(`Analyzing image: ${imagePath}`);
|
|
116
|
+
try {
|
|
117
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
118
|
+
if (!apiKey) {
|
|
119
|
+
throw new Error("GEMINI_API_KEY not set");
|
|
120
|
+
}
|
|
121
|
+
if (!fs.existsSync(imagePath)) {
|
|
122
|
+
throw new Error(`File not found: ${imagePath}`);
|
|
123
|
+
}
|
|
124
|
+
const fileBuffer = fs.readFileSync(imagePath);
|
|
125
|
+
const mimeType = getImageMimeType(imagePath);
|
|
126
|
+
const dimensions = extractImageDimensions(imagePath);
|
|
127
|
+
const genAI = new GoogleGenAI({ apiKey });
|
|
128
|
+
const modelName = model === "pro"
|
|
129
|
+
? process.env.GEMINI_PRO_MODEL || "gemini-3-pro-preview"
|
|
130
|
+
: process.env.GEMINI_FLASH_MODEL || "gemini-3-flash-preview";
|
|
131
|
+
const fileSize = fileBuffer.length;
|
|
132
|
+
logger.debug(`Image size: ${fileSize} bytes, MIME type: ${mimeType}`);
|
|
133
|
+
// Log image dimensions (for pixel coordinate conversion)
|
|
134
|
+
if (dimensions) {
|
|
135
|
+
logger.debug(`Image dimensions: ${dimensions.width}x${dimensions.height}`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
logger.debug("Could not extract image dimensions");
|
|
139
|
+
}
|
|
140
|
+
// Build prompt based on parameters
|
|
141
|
+
let prompt = query || "Analyze this image in detail.";
|
|
142
|
+
if (detectObjects) {
|
|
143
|
+
prompt += `\n\nFor each object you identify, provide bounding box coordinates in the box_2d format: [y_min, x_min, y_max, x_max] where coordinates are normalized to 0-1000 scale.
|
|
144
|
+
|
|
145
|
+
Return your response as a JSON object with this structure:
|
|
146
|
+
{
|
|
147
|
+
"description": "Overall description of the image",
|
|
148
|
+
"objects": [
|
|
149
|
+
{
|
|
150
|
+
"label": "object name",
|
|
151
|
+
"confidence": "high/medium/low",
|
|
152
|
+
"box_2d": [y_min, x_min, y_max, x_max]
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
If you cannot detect specific objects or bounding boxes are not applicable, return an empty objects array.`;
|
|
158
|
+
}
|
|
159
|
+
// Map resolution to API parameter
|
|
160
|
+
const resolutionMap = {
|
|
161
|
+
low: "media_resolution_low",
|
|
162
|
+
medium: "media_resolution_medium",
|
|
163
|
+
high: "media_resolution_high",
|
|
164
|
+
};
|
|
165
|
+
// For files <20MB, use inline data. For larger files, use Files API
|
|
166
|
+
let responseText;
|
|
167
|
+
if (fileSize > 20 * 1024 * 1024) {
|
|
168
|
+
// Upload using Files API
|
|
169
|
+
logger.info("Large file detected, uploading via Files API...");
|
|
170
|
+
const uploadedFile = await genAI.files.upload({
|
|
171
|
+
file: new Blob([new Uint8Array(fileBuffer)], { type: mimeType }),
|
|
172
|
+
config: { mimeType },
|
|
173
|
+
});
|
|
174
|
+
const config = {};
|
|
175
|
+
if (mediaResolution !== "medium") {
|
|
176
|
+
config.mediaResolution = resolutionMap[mediaResolution];
|
|
177
|
+
}
|
|
178
|
+
// Add thinking config for Gemini 3
|
|
179
|
+
if (thinkingLevel) {
|
|
180
|
+
// Pro only supports low/high, Flash supports all levels
|
|
181
|
+
const effectiveLevel = model === "pro"
|
|
182
|
+
? (thinkingLevel === "minimal" || thinkingLevel === "low" ? "low" : "high")
|
|
183
|
+
: thinkingLevel;
|
|
184
|
+
config.thinkingConfig = { thinkingLevel: effectiveLevel };
|
|
185
|
+
logger.debug(`Using thinking level: ${effectiveLevel}${model === "pro" && effectiveLevel !== thinkingLevel ? ` (requested: ${thinkingLevel})` : ""}`);
|
|
186
|
+
}
|
|
187
|
+
// Add structured output for object detection
|
|
188
|
+
if (detectObjects) {
|
|
189
|
+
config.responseMimeType = "application/json";
|
|
190
|
+
config.responseJsonSchema = {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
description: { type: "string" },
|
|
194
|
+
objects: {
|
|
195
|
+
type: "array",
|
|
196
|
+
items: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
label: { type: "string" },
|
|
200
|
+
confidence: { type: "string" },
|
|
201
|
+
box_2d: {
|
|
202
|
+
type: "array",
|
|
203
|
+
items: { type: "number" },
|
|
204
|
+
minItems: 4,
|
|
205
|
+
maxItems: 4,
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ["label", "confidence", "box_2d"],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
required: ["description", "objects"],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const response = await genAI.models.generateContent({
|
|
216
|
+
model: modelName,
|
|
217
|
+
contents: [
|
|
218
|
+
{
|
|
219
|
+
role: "user",
|
|
220
|
+
parts: [
|
|
221
|
+
{
|
|
222
|
+
fileData: {
|
|
223
|
+
fileUri: uploadedFile.uri,
|
|
224
|
+
mimeType: uploadedFile.mimeType,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
{ text: prompt },
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
config: Object.keys(config).length > 0 ? config : undefined,
|
|
232
|
+
});
|
|
233
|
+
responseText = response.text || "";
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// Use inline data for smaller files
|
|
237
|
+
const base64Data = fileBuffer.toString("base64");
|
|
238
|
+
const inlineConfig = {};
|
|
239
|
+
if (mediaResolution !== "medium") {
|
|
240
|
+
inlineConfig.mediaResolution = resolutionMap[mediaResolution];
|
|
241
|
+
}
|
|
242
|
+
// Add thinking config for Gemini 3
|
|
243
|
+
if (thinkingLevel) {
|
|
244
|
+
// Pro only supports low/high, Flash supports all levels
|
|
245
|
+
const effectiveLevel = model === "pro"
|
|
246
|
+
? (thinkingLevel === "minimal" || thinkingLevel === "low" ? "low" : "high")
|
|
247
|
+
: thinkingLevel;
|
|
248
|
+
inlineConfig.thinkingConfig = { thinkingLevel: effectiveLevel };
|
|
249
|
+
logger.debug(`Using thinking level: ${effectiveLevel}${model === "pro" && effectiveLevel !== thinkingLevel ? ` (requested: ${thinkingLevel})` : ""}`);
|
|
250
|
+
}
|
|
251
|
+
// Add structured output for object detection
|
|
252
|
+
if (detectObjects) {
|
|
253
|
+
inlineConfig.responseMimeType = "application/json";
|
|
254
|
+
inlineConfig.responseJsonSchema = {
|
|
255
|
+
type: "object",
|
|
256
|
+
properties: {
|
|
257
|
+
description: { type: "string" },
|
|
258
|
+
objects: {
|
|
259
|
+
type: "array",
|
|
260
|
+
items: {
|
|
261
|
+
type: "object",
|
|
262
|
+
properties: {
|
|
263
|
+
label: { type: "string" },
|
|
264
|
+
confidence: { type: "string" },
|
|
265
|
+
box_2d: {
|
|
266
|
+
type: "array",
|
|
267
|
+
items: { type: "number" },
|
|
268
|
+
minItems: 4,
|
|
269
|
+
maxItems: 4,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
required: ["label", "confidence", "box_2d"],
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
required: ["description", "objects"],
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
const response = await genAI.models.generateContent({
|
|
280
|
+
model: modelName,
|
|
281
|
+
contents: [
|
|
282
|
+
{
|
|
283
|
+
role: "user",
|
|
284
|
+
parts: [
|
|
285
|
+
{
|
|
286
|
+
inlineData: {
|
|
287
|
+
mimeType,
|
|
288
|
+
data: base64Data,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{ text: prompt },
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
config: Object.keys(inlineConfig).length > 0 ? inlineConfig : undefined,
|
|
296
|
+
});
|
|
297
|
+
responseText = response.text || "";
|
|
298
|
+
}
|
|
299
|
+
// Process response
|
|
300
|
+
if (detectObjects && responseText) {
|
|
301
|
+
try {
|
|
302
|
+
const parsed = JSON.parse(responseText);
|
|
303
|
+
// Add pixel coordinates if dimensions are available
|
|
304
|
+
if (dimensions && parsed.objects && Array.isArray(parsed.objects)) {
|
|
305
|
+
parsed.objects = parsed.objects.map((obj) => {
|
|
306
|
+
if (Array.isArray(obj.box_2d) && obj.box_2d.length === 4) {
|
|
307
|
+
return {
|
|
308
|
+
...obj,
|
|
309
|
+
bbox_pixels: convertToPixelCoords(obj.box_2d, dimensions.width, dimensions.height),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return obj;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
const formattedJson = JSON.stringify(parsed, null, 2);
|
|
316
|
+
logger.info("Image analysis completed with object detection");
|
|
317
|
+
return {
|
|
318
|
+
content: [
|
|
319
|
+
{
|
|
320
|
+
type: "text",
|
|
321
|
+
text: `**Image Analysis Results:**\n\`\`\`json\n${formattedJson}\n\`\`\``,
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
catch (parseError) {
|
|
327
|
+
// Fallback to plain text if JSON parsing fails
|
|
328
|
+
logger.warn("Failed to parse JSON response, returning as text");
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: responseText,
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
logger.info("Image analysis completed");
|
|
340
|
+
return {
|
|
341
|
+
content: [
|
|
342
|
+
{
|
|
343
|
+
type: "text",
|
|
344
|
+
text: responseText || "Unable to analyze image.",
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
351
|
+
logger.error(`Error in image analysis: ${errorMessage}`);
|
|
352
|
+
return {
|
|
353
|
+
content: [
|
|
354
|
+
{
|
|
355
|
+
type: "text",
|
|
356
|
+
text: `Error analyzing image: ${errorMessage}`,
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
isError: true,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rlabs-inc/gemini-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"mcpName": "io.github.RLabs-Inc/gemini-mcp",
|
|
5
5
|
"description": "MCP server for Gemini 3 integration with Claude Code - 30+ AI tools including image/video generation, deep research, code execution, and beautiful CLI",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"bin": {
|
|
9
9
|
"gemini-mcp": "dist/index.js",
|
|
10
|
-
"
|
|
10
|
+
"gcli": "dist/index.js"
|
|
11
11
|
},
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|