Skip to content

Commit e3defae

Browse files
authored
feat(genkit-tools/cli): Update gemini-cli to support .md installation. (#3415)
1 parent 63c4096 commit e3defae

File tree

5 files changed

+144
-66
lines changed

5 files changed

+144
-66
lines changed

genkit-tools/cli/src/commands/init-ai-tools/ai-tools/claude.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types';
2121
import {
2222
GENKIT_PROMPT_PATH,
2323
calculateHash,
24-
getGenkitContext,
25-
initOrReplaceFile,
24+
initGenkitFile,
2625
updateContentInPlace,
2726
} from '../utils';
2827

@@ -55,7 +54,7 @@ export const claude: AIToolModule = {
5554
// File doesn't exist or is invalid JSON, start fresh
5655
}
5756

58-
// Check if firebase server already exists
57+
// Check if genkit server already exists
5958
if (!existingConfig.mcpServers?.genkit) {
6059
if (!existingConfig.mcpServers) {
6160
existingConfig.mcpServers = {};
@@ -71,19 +70,15 @@ export const claude: AIToolModule = {
7170
files.push({ path: CLAUDE_MCP_PATH, updated: settingsUpdated });
7271

7372
logger.info('Copying the Genkit context to GENKIT.md...');
74-
const genkitContext = getGenkitContext();
75-
const { updated: genkitContextUpdated } = await initOrReplaceFile(
76-
GENKIT_PROMPT_PATH,
77-
genkitContext
78-
);
79-
files.push({ path: GENKIT_PROMPT_PATH, updated: genkitContextUpdated });
73+
const mdResult = await initGenkitFile();
74+
files.push({ path: GENKIT_PROMPT_PATH, updated: mdResult.updated });
8075

8176
logger.info('Updating CLAUDE.md to include Genkit context...');
82-
const claudeImportTag = `\nGenkit Framework Instructions:\n - @GENKIT.md\n`;
77+
const claudeImportTag = `\nGenkit Framework Instructions:\n - @./GENKIT.md\n`;
8378
const baseResult = await updateContentInPlace(
8479
CLAUDE_PROMPT_PATH,
8580
claudeImportTag,
86-
{ hash: calculateHash(genkitContext) }
81+
{ hash: calculateHash(mdResult.hash) }
8782
);
8883

8984
files.push({ path: CLAUDE_PROMPT_PATH, updated: baseResult.updated });

genkit-tools/cli/src/commands/init-ai-tools/ai-tools/cursor.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import * as path from 'path';
2020
import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types';
2121
import {
2222
GENKIT_PROMPT_PATH,
23-
getGenkitContext,
23+
initGenkitFile,
2424
initOrReplaceFile,
2525
} from '../utils';
2626

@@ -57,12 +57,8 @@ export const cursor: AIToolModule = {
5757

5858
// Create the base GENKIT context file (GENKIT.md).
5959
// This file contains fundamental details about the GENKIT project.
60-
const genkitContext = getGenkitContext();
61-
const baseResult = await initOrReplaceFile(
62-
GENKIT_PROMPT_PATH,
63-
genkitContext
64-
);
65-
files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated });
60+
const mdResult = await initGenkitFile();
61+
files.push({ path: GENKIT_PROMPT_PATH, updated: mdResult.updated });
6662

6763
// Handle MCP configuration - merge with existing if present.
6864
// This allows Cursor to communicate with Genkit tools.

genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts

Lines changed: 122 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,25 @@
1515
*/
1616

1717
import { logger } from '@genkit-ai/tools-common/utils';
18-
import { mkdir } from 'fs/promises';
18+
import { select } from '@inquirer/prompts';
19+
import { existsSync, readFileSync } from 'fs';
20+
import { mkdir, writeFile } from 'fs/promises';
1921
import path from 'path';
2022
import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types';
2123
import {
2224
GENKIT_PROMPT_PATH,
23-
getGenkitContext,
25+
initGenkitFile,
2426
initOrReplaceFile,
27+
updateContentInPlace,
2528
} from '../utils';
2629

27-
// Define constants at the module level for clarity and reuse.
28-
const GENKIT_EXT_DIR = path.join('.gemini', 'extensions', 'genkit');
30+
// GEMINI specific paths
31+
const GEMINI_DIR = '.gemini';
32+
const GEMINI_SETTINGS_PATH = path.join(GEMINI_DIR, 'settings.json');
33+
const GEMINI_MD_PATH = path.join('GEMINI.md');
34+
35+
// GENKIT specific constants
36+
const GENKIT_EXT_DIR = path.join(GEMINI_DIR, 'extensions', 'genkit');
2937
const GENKIT_MD_REL_PATH = path.join('..', '..', '..', GENKIT_PROMPT_PATH);
3038
const GENKIT_EXTENSION_CONFIG = {
3139
name: 'genkit',
@@ -46,6 +54,10 @@ const GENKIT_EXTENSION_CONFIG = {
4654
contextFileName: GENKIT_MD_REL_PATH,
4755
};
4856

57+
const EXT_INSTALLATION = 'extension';
58+
const MD_INSTALLATION = 'geminimd';
59+
type InstallationType = typeof EXT_INSTALLATION | typeof MD_INSTALLATION;
60+
4961
/** Configuration module for Gemini CLI */
5062
export const gemini: AIToolModule = {
5163
name: 'gemini',
@@ -55,42 +67,115 @@ export const gemini: AIToolModule = {
5567
* Configures the Gemini CLI extension for Genkit.
5668
*/
5769
async configure(options?: InitConfigOptions): Promise<AIToolConfigResult> {
58-
// TODO(ssbushi): Support option to install as file import vs extension
59-
const files: AIToolConfigResult['files'] = [];
70+
let installationMethod: InstallationType = EXT_INSTALLATION;
71+
if (!options?.yesMode) {
72+
installationMethod = await select({
73+
message: 'Select your preferred installation method',
74+
choices: [
75+
{
76+
name: 'Gemini CLI Extension',
77+
value: 'extension',
78+
description:
79+
'Use Gemini Extension to install Genkit context in a modular fashion',
80+
},
81+
{
82+
name: 'GEMINI.md',
83+
value: 'geminimd',
84+
description: 'Incorporate Genkit context within the GEMINI.md file',
85+
},
86+
],
87+
});
88+
}
89+
90+
if (installationMethod === EXT_INSTALLATION) {
91+
logger.info('Installing as part of GEMINI.md');
92+
return await installAsExtension();
93+
} else {
94+
logger.info('Installing as Gemini CLI extension');
95+
return await installInMdFile();
96+
}
97+
},
98+
};
6099

61-
// Part 1: Generate GENKIT.md file.
100+
async function installInMdFile(): Promise<AIToolConfigResult> {
101+
const files: AIToolConfigResult['files'] = [];
102+
// Part 1: Generate GENKIT.md file.
62103

63-
logger.info('Copying the GENKIT.md file...');
64-
const genkitContext = getGenkitContext();
65-
const baseResult = await initOrReplaceFile(
66-
GENKIT_PROMPT_PATH,
67-
genkitContext
104+
logger.info('Installing the Genkit MCP server for Gemini CLI');
105+
// Handle MCP configuration - merge with existing if present
106+
let existingConfig: any = {};
107+
let settingsUpdated = false;
108+
try {
109+
const fileExists = existsSync(GEMINI_SETTINGS_PATH);
110+
if (fileExists) {
111+
existingConfig = JSON.parse(readFileSync(GEMINI_SETTINGS_PATH, 'utf-8'));
112+
} else {
113+
await mkdir(GEMINI_DIR, { recursive: true });
114+
}
115+
} catch (e) {
116+
// File doesn't exist or is invalid JSON, start fresh
117+
}
118+
119+
// Check if genkit server already exists
120+
if (!existingConfig.mcpServers?.genkit) {
121+
if (!existingConfig.mcpServers) {
122+
existingConfig.mcpServers = {};
123+
}
124+
existingConfig.mcpServers.genkit =
125+
GENKIT_EXTENSION_CONFIG.mcpServers.genkit;
126+
await writeFile(
127+
GEMINI_SETTINGS_PATH,
128+
JSON.stringify(existingConfig, null, 2)
68129
);
69-
files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated });
70-
71-
// Part 2: Configure the main gemini-extension.json file, and gemini config directory if needed.
72-
logger.info('Configuring extentions files in user workspace...');
73-
await mkdir(GENKIT_EXT_DIR, { recursive: true });
74-
const extensionPath = path.join(GENKIT_EXT_DIR, 'gemini-extension.json');
75-
76-
let extensionUpdated = false;
77-
try {
78-
const { updated } = await initOrReplaceFile(
79-
extensionPath,
80-
JSON.stringify(GENKIT_EXTENSION_CONFIG, null, 2)
130+
settingsUpdated = true;
131+
}
132+
files.push({ path: GEMINI_SETTINGS_PATH, updated: settingsUpdated });
133+
134+
// Copy GENKIT.md file
135+
logger.info('Copying the GENKIT.md file...');
136+
const baseResult = await initGenkitFile();
137+
files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated });
138+
139+
logger.info('Updating GEMINI.md to include Genkit context');
140+
const geminiImportTag = `\nGenkit Framework Instructions:\n - @./GENKIT.md\n`;
141+
const { updated: mdUpdated } = await updateContentInPlace(
142+
GEMINI_MD_PATH,
143+
geminiImportTag,
144+
{ hash: baseResult.hash }
145+
);
146+
files.push({ path: GEMINI_MD_PATH, updated: mdUpdated });
147+
148+
return { files };
149+
}
150+
151+
async function installAsExtension(): Promise<AIToolConfigResult> {
152+
const files: AIToolConfigResult['files'] = [];
153+
// Part 1: Generate GENKIT.md file.
154+
const baseResult = await initGenkitFile();
155+
files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated });
156+
157+
// Part 2: Configure the main gemini-extension.json file, and gemini config directory if needed.
158+
logger.info('Configuring extentions files in user workspace');
159+
await mkdir(GENKIT_EXT_DIR, { recursive: true });
160+
const extensionPath = path.join(GENKIT_EXT_DIR, 'gemini-extension.json');
161+
162+
let extensionUpdated = false;
163+
try {
164+
const { updated } = await initOrReplaceFile(
165+
extensionPath,
166+
JSON.stringify(GENKIT_EXTENSION_CONFIG, null, 2)
167+
);
168+
extensionUpdated = updated;
169+
if (extensionUpdated) {
170+
logger.info(
171+
`Genkit extension for Gemini CLI initialized at ${extensionPath}`
81172
);
82-
extensionUpdated = updated;
83-
if (extensionUpdated) {
84-
logger.info(
85-
`Genkit extension for Gemini CLI initialized at ${extensionPath}`
86-
);
87-
}
88-
} catch (err) {
89-
logger.error(err);
90-
process.exit(1);
91173
}
92-
files.push({ path: extensionPath, updated: extensionUpdated });
174+
} catch (err) {
175+
logger.error(err);
176+
process.exit(1);
177+
}
178+
files.push({ path: extensionPath, updated: extensionUpdated });
93179

94-
return { files };
95-
},
96-
};
180+
return { files };
181+
}

genkit-tools/cli/src/commands/init-ai-tools/ai-tools/generic.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,7 @@
1616

1717
import { logger } from '@genkit-ai/tools-common/utils';
1818
import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types';
19-
import {
20-
GENKIT_PROMPT_PATH,
21-
getGenkitContext,
22-
initOrReplaceFile,
23-
} from '../utils';
19+
import { GENKIT_PROMPT_PATH, initGenkitFile } from '../utils';
2420

2521
/** Configuration module for GENKIT.md context file for generic use */
2622
export const generic: AIToolModule = {
@@ -35,12 +31,9 @@ export const generic: AIToolModule = {
3531

3632
// Generate GENKIT.md file.
3733
logger.info('Updating GENKIT.md...');
38-
const genkitContext = getGenkitContext();
39-
const baseResult = await initOrReplaceFile(
40-
GENKIT_PROMPT_PATH,
41-
genkitContext
42-
);
43-
files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated });
34+
const mdResult = await initGenkitFile();
35+
files.push({ path: GENKIT_PROMPT_PATH, updated: mdResult.updated });
36+
4437
logger.info('\n');
4538
logger.info(
4639
'GENKIT.md updated. Provide this file as context with your AI tool.'

genkit-tools/cli/src/commands/init-ai-tools/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,12 @@ export function getGenkitContext(): string {
151151
const content = readFileSync(contextPath, 'utf8');
152152
return content;
153153
}
154+
155+
/**
156+
* Initializes the GENKIT.md file
157+
*/
158+
export async function initGenkitFile() {
159+
const genkitContext = getGenkitContext();
160+
const result = await initOrReplaceFile(GENKIT_PROMPT_PATH, genkitContext);
161+
return { updated: result.updated, hash: calculateHash(genkitContext) };
162+
}

0 commit comments

Comments
 (0)