diff --git a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/claude.ts b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/claude.ts index 68796fd205..b915a0dbe0 100644 --- a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/claude.ts +++ b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/claude.ts @@ -21,8 +21,7 @@ import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types'; import { GENKIT_PROMPT_PATH, calculateHash, - getGenkitContext, - initOrReplaceFile, + initGenkitFile, updateContentInPlace, } from '../utils'; @@ -55,7 +54,7 @@ export const claude: AIToolModule = { // File doesn't exist or is invalid JSON, start fresh } - // Check if firebase server already exists + // Check if genkit server already exists if (!existingConfig.mcpServers?.genkit) { if (!existingConfig.mcpServers) { existingConfig.mcpServers = {}; @@ -71,19 +70,15 @@ export const claude: AIToolModule = { files.push({ path: CLAUDE_MCP_PATH, updated: settingsUpdated }); logger.info('Copying the Genkit context to GENKIT.md...'); - const genkitContext = getGenkitContext(); - const { updated: genkitContextUpdated } = await initOrReplaceFile( - GENKIT_PROMPT_PATH, - genkitContext - ); - files.push({ path: GENKIT_PROMPT_PATH, updated: genkitContextUpdated }); + const mdResult = await initGenkitFile(); + files.push({ path: GENKIT_PROMPT_PATH, updated: mdResult.updated }); logger.info('Updating CLAUDE.md to include Genkit context...'); - const claudeImportTag = `\nGenkit Framework Instructions:\n - @GENKIT.md\n`; + const claudeImportTag = `\nGenkit Framework Instructions:\n - @./GENKIT.md\n`; const baseResult = await updateContentInPlace( CLAUDE_PROMPT_PATH, claudeImportTag, - { hash: calculateHash(genkitContext) } + { hash: calculateHash(mdResult.hash) } ); files.push({ path: CLAUDE_PROMPT_PATH, updated: baseResult.updated }); diff --git a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/cursor.ts b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/cursor.ts index 2b5085a7d0..a2b242531a 100644 --- a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/cursor.ts +++ b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/cursor.ts @@ -20,7 +20,7 @@ import * as path from 'path'; import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types'; import { GENKIT_PROMPT_PATH, - getGenkitContext, + initGenkitFile, initOrReplaceFile, } from '../utils'; @@ -57,12 +57,8 @@ export const cursor: AIToolModule = { // Create the base GENKIT context file (GENKIT.md). // This file contains fundamental details about the GENKIT project. - const genkitContext = getGenkitContext(); - const baseResult = await initOrReplaceFile( - GENKIT_PROMPT_PATH, - genkitContext - ); - files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated }); + const mdResult = await initGenkitFile(); + files.push({ path: GENKIT_PROMPT_PATH, updated: mdResult.updated }); // Handle MCP configuration - merge with existing if present. // This allows Cursor to communicate with Genkit tools. diff --git a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts index d4de7efe22..eac022a60e 100644 --- a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts +++ b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/gemini.ts @@ -15,17 +15,25 @@ */ import { logger } from '@genkit-ai/tools-common/utils'; -import { mkdir } from 'fs/promises'; +import { select } from '@inquirer/prompts'; +import { existsSync, readFileSync } from 'fs'; +import { mkdir, writeFile } from 'fs/promises'; import path from 'path'; import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types'; import { GENKIT_PROMPT_PATH, - getGenkitContext, + initGenkitFile, initOrReplaceFile, + updateContentInPlace, } from '../utils'; -// Define constants at the module level for clarity and reuse. -const GENKIT_EXT_DIR = path.join('.gemini', 'extensions', 'genkit'); +// GEMINI specific paths +const GEMINI_DIR = '.gemini'; +const GEMINI_SETTINGS_PATH = path.join(GEMINI_DIR, 'settings.json'); +const GEMINI_MD_PATH = path.join('GEMINI.md'); + +// GENKIT specific constants +const GENKIT_EXT_DIR = path.join(GEMINI_DIR, 'extensions', 'genkit'); const GENKIT_MD_REL_PATH = path.join('..', '..', '..', GENKIT_PROMPT_PATH); const GENKIT_EXTENSION_CONFIG = { name: 'genkit', @@ -46,6 +54,10 @@ const GENKIT_EXTENSION_CONFIG = { contextFileName: GENKIT_MD_REL_PATH, }; +const EXT_INSTALLATION = 'extension'; +const MD_INSTALLATION = 'geminimd'; +type InstallationType = typeof EXT_INSTALLATION | typeof MD_INSTALLATION; + /** Configuration module for Gemini CLI */ export const gemini: AIToolModule = { name: 'gemini', @@ -55,42 +67,115 @@ export const gemini: AIToolModule = { * Configures the Gemini CLI extension for Genkit. */ async configure(options?: InitConfigOptions): Promise { - // TODO(ssbushi): Support option to install as file import vs extension - const files: AIToolConfigResult['files'] = []; + let installationMethod: InstallationType = EXT_INSTALLATION; + if (!options?.yesMode) { + installationMethod = await select({ + message: 'Select your preferred installation method', + choices: [ + { + name: 'Gemini CLI Extension', + value: 'extension', + description: + 'Use Gemini Extension to install Genkit context in a modular fashion', + }, + { + name: 'GEMINI.md', + value: 'geminimd', + description: 'Incorporate Genkit context within the GEMINI.md file', + }, + ], + }); + } + + if (installationMethod === EXT_INSTALLATION) { + logger.info('Installing as part of GEMINI.md'); + return await installAsExtension(); + } else { + logger.info('Installing as Gemini CLI extension'); + return await installInMdFile(); + } + }, +}; - // Part 1: Generate GENKIT.md file. +async function installInMdFile(): Promise { + const files: AIToolConfigResult['files'] = []; + // Part 1: Generate GENKIT.md file. - logger.info('Copying the GENKIT.md file...'); - const genkitContext = getGenkitContext(); - const baseResult = await initOrReplaceFile( - GENKIT_PROMPT_PATH, - genkitContext + logger.info('Installing the Genkit MCP server for Gemini CLI'); + // Handle MCP configuration - merge with existing if present + let existingConfig: any = {}; + let settingsUpdated = false; + try { + const fileExists = existsSync(GEMINI_SETTINGS_PATH); + if (fileExists) { + existingConfig = JSON.parse(readFileSync(GEMINI_SETTINGS_PATH, 'utf-8')); + } else { + await mkdir(GEMINI_DIR, { recursive: true }); + } + } catch (e) { + // File doesn't exist or is invalid JSON, start fresh + } + + // Check if genkit server already exists + if (!existingConfig.mcpServers?.genkit) { + if (!existingConfig.mcpServers) { + existingConfig.mcpServers = {}; + } + existingConfig.mcpServers.genkit = + GENKIT_EXTENSION_CONFIG.mcpServers.genkit; + await writeFile( + GEMINI_SETTINGS_PATH, + JSON.stringify(existingConfig, null, 2) ); - files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated }); - - // Part 2: Configure the main gemini-extension.json file, and gemini config directory if needed. - logger.info('Configuring extentions files in user workspace...'); - await mkdir(GENKIT_EXT_DIR, { recursive: true }); - const extensionPath = path.join(GENKIT_EXT_DIR, 'gemini-extension.json'); - - let extensionUpdated = false; - try { - const { updated } = await initOrReplaceFile( - extensionPath, - JSON.stringify(GENKIT_EXTENSION_CONFIG, null, 2) + settingsUpdated = true; + } + files.push({ path: GEMINI_SETTINGS_PATH, updated: settingsUpdated }); + + // Copy GENKIT.md file + logger.info('Copying the GENKIT.md file...'); + const baseResult = await initGenkitFile(); + files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated }); + + logger.info('Updating GEMINI.md to include Genkit context'); + const geminiImportTag = `\nGenkit Framework Instructions:\n - @./GENKIT.md\n`; + const { updated: mdUpdated } = await updateContentInPlace( + GEMINI_MD_PATH, + geminiImportTag, + { hash: baseResult.hash } + ); + files.push({ path: GEMINI_MD_PATH, updated: mdUpdated }); + + return { files }; +} + +async function installAsExtension(): Promise { + const files: AIToolConfigResult['files'] = []; + // Part 1: Generate GENKIT.md file. + const baseResult = await initGenkitFile(); + files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated }); + + // Part 2: Configure the main gemini-extension.json file, and gemini config directory if needed. + logger.info('Configuring extentions files in user workspace'); + await mkdir(GENKIT_EXT_DIR, { recursive: true }); + const extensionPath = path.join(GENKIT_EXT_DIR, 'gemini-extension.json'); + + let extensionUpdated = false; + try { + const { updated } = await initOrReplaceFile( + extensionPath, + JSON.stringify(GENKIT_EXTENSION_CONFIG, null, 2) + ); + extensionUpdated = updated; + if (extensionUpdated) { + logger.info( + `Genkit extension for Gemini CLI initialized at ${extensionPath}` ); - extensionUpdated = updated; - if (extensionUpdated) { - logger.info( - `Genkit extension for Gemini CLI initialized at ${extensionPath}` - ); - } - } catch (err) { - logger.error(err); - process.exit(1); } - files.push({ path: extensionPath, updated: extensionUpdated }); + } catch (err) { + logger.error(err); + process.exit(1); + } + files.push({ path: extensionPath, updated: extensionUpdated }); - return { files }; - }, -}; + return { files }; +} diff --git a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/generic.ts b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/generic.ts index 9a72c14d28..f29f4d4670 100644 --- a/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/generic.ts +++ b/genkit-tools/cli/src/commands/init-ai-tools/ai-tools/generic.ts @@ -16,11 +16,7 @@ import { logger } from '@genkit-ai/tools-common/utils'; import { AIToolConfigResult, AIToolModule, InitConfigOptions } from '../types'; -import { - GENKIT_PROMPT_PATH, - getGenkitContext, - initOrReplaceFile, -} from '../utils'; +import { GENKIT_PROMPT_PATH, initGenkitFile } from '../utils'; /** Configuration module for GENKIT.md context file for generic use */ export const generic: AIToolModule = { @@ -35,12 +31,9 @@ export const generic: AIToolModule = { // Generate GENKIT.md file. logger.info('Updating GENKIT.md...'); - const genkitContext = getGenkitContext(); - const baseResult = await initOrReplaceFile( - GENKIT_PROMPT_PATH, - genkitContext - ); - files.push({ path: GENKIT_PROMPT_PATH, updated: baseResult.updated }); + const mdResult = await initGenkitFile(); + files.push({ path: GENKIT_PROMPT_PATH, updated: mdResult.updated }); + logger.info('\n'); logger.info( 'GENKIT.md updated. Provide this file as context with your AI tool.' diff --git a/genkit-tools/cli/src/commands/init-ai-tools/utils.ts b/genkit-tools/cli/src/commands/init-ai-tools/utils.ts index da9f1fa8f6..005bbbfa58 100644 --- a/genkit-tools/cli/src/commands/init-ai-tools/utils.ts +++ b/genkit-tools/cli/src/commands/init-ai-tools/utils.ts @@ -151,3 +151,12 @@ export function getGenkitContext(): string { const content = readFileSync(contextPath, 'utf8'); return content; } + +/** + * Initializes the GENKIT.md file + */ +export async function initGenkitFile() { + const genkitContext = getGenkitContext(); + const result = await initOrReplaceFile(GENKIT_PROMPT_PATH, genkitContext); + return { updated: result.updated, hash: calculateHash(genkitContext) }; +}