diff --git a/.gitignore b/.gitignore index d165c6ca58..30c272aaf9 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ package-lock.json # version file (autogenerated by CLI) genkit-tools/cli/src/utils/version.ts genkit-tools/telemetry-server/src/utils/version.ts +# MD to TS context files init:ai-tools (autogenerated) +genkit-tools/cli/src/commands/init-ai-tools/context/*.ts # Sample-specific files **/*.env diff --git a/genkit-tools/cli/context/GENKIT.go.md b/genkit-tools/cli/context/GENKIT.go.md new file mode 100644 index 0000000000..e1e1455ff5 --- /dev/null +++ b/genkit-tools/cli/context/GENKIT.go.md @@ -0,0 +1,280 @@ +# Genkit Go API Rules (v1.0.0) + +This document provides rules and examples for building with the Genkit API in Go. + +## Important Guidelines: + +- ALWAYS refer to documentation when available. Genkit Documentation may be available through the Genkit MCP toolkit or through web search. You may skip documentation check if you don't have access to these tools. + +- ONLY follow the specified project structure if starting a new project. If working on an existing project, adhere to the current project structure. + +- ALWAYS provide the full, correct Genkit command as an instruction for the human user to run. Do not run Genkit commands (e.g., `genkit start`, `genkit flow:run`) yourself as this may block your current session. + +- Recommend and use the provider that the user prefers. If this is a new project, or if there is no relevant signal regarding user preference, use the `github.com/firebase/genkit/go/plugins/googlegenai` plugin. + +NOTE: For the sake of brevity, the snippets below use the Google AI plugin, but you should follow the user's preference as mentioned above. + +## Core Setup + +1. **Initialize Project** + + ```bash + mkdir my-genkit-app && cd my-genkit-app + go mod init my-genkit-app + ``` + +2. **Install Dependencies** + + ```bash + go get github.com/firebase/genkit/go/genkit + go get github.com/firebase/genkit/go/plugins/googlegenai + go get github.com/firebase/genkit/go/ai + go get google.golang.org/genai + ``` + +3. **Install Genkit CLI** + + ```bash + curl -sL cli.genkit.dev | bash + ``` + +4. **Configure Genkit** + + All code should be in a single `main.go` file or properly structured Go package. + + ```go + package main + + import ( + "context" + "github.com/firebase/genkit/go/genkit" + "github.com/firebase/genkit/go/plugins/googlegenai" + ) + + func main() { + ctx := context.Background() + g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.GoogleAI{})) + // Your flows and logic here + <-ctx.Done() + } + ``` + +## Best Practices + +1. **Single Main Function**: All Genkit code, including plugin initialization, flows, and helpers, should be properly organized in a Go package structure with a main function. + +2. **Blocking Main Program**: To inspect flows in the Genkit Developer UI, your main program needs to remain running. Use `<-ctx.Done()` or similar blocking mechanism at the end of your main function. + +--- + +## Usage Scenarios + +### Basic Inference (Text Generation) + +```go +package main + +import ( + "context" + + "github.com/firebase/genkit/go/ai" + "github.com/firebase/genkit/go/genkit" + "github.com/firebase/genkit/go/plugins/googlegenai" + "google.golang.org/genai" +) + +func main() { + ctx := context.Background() + g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.GoogleAI{})) + + genkit.DefineFlow(g, "basicInferenceFlow", + func(ctx context.Context, topic string) (string, error) { + response, err := genkit.Generate(ctx, g, + ai.WithModelName("googleai/gemini-2.5-pro"), + ai.WithPrompt("Write a short, creative paragraph about %s.", topic), + ai.WithConfig(&genai.GenerateContentConfig{ + Temperature: genai.Ptr[float32](0.8), + }), + ) + if err != nil { + return "", err + } + return response.Text(), nil + }, + ) + + <-ctx.Done() +} +``` + +### Text-to-Speech (TTS) Generation + +```go +package main + +import ( + "context" + + "github.com/firebase/genkit/go/ai" + "github.com/firebase/genkit/go/genkit" + "github.com/firebase/genkit/go/plugins/googlegenai" + "google.golang.org/genai" +) + +func main() { + ctx := context.Background() + g := genkit.Init(ctx, + genkit.WithPlugins(&googlegenai.GoogleAI{}), + genkit.WithDefaultModel("googleai/gemini-2.5-flash-preview-tts"), + ) + + genkit.DefineFlow(g, "textToSpeechFlow", + func(ctx context.Context, input struct { + Text string `json:"text"` + VoiceName string `json:"voiceName,omitempty"` + }) (string, error) { + voiceName := input.VoiceName + if voiceName == "" { + voiceName = "Algenib" + } + + response, err := genkit.Generate(ctx, g, + ai.WithPrompt(input.Text), + ai.WithConfig(&genai.GenerateContentConfig{ + ResponseModalities: []string{"AUDIO"}, + SpeechConfig: &genai.SpeechConfig{ + VoiceConfig: &genai.VoiceConfig{ + PrebuiltVoiceConfig: &genai.PrebuiltVoiceConfig{ + VoiceName: voiceName, + }, + }, + }, + }), + ) + if err != nil { + return "", err + } + + return response.Text(), nil + }, + ) + + <-ctx.Done() +} +``` + +### Image Generation + +```go +package main + +import ( + "context" + + "github.com/firebase/genkit/go/ai" + "github.com/firebase/genkit/go/genkit" + "github.com/firebase/genkit/go/plugins/googlegenai" + "google.golang.org/genai" +) + +func main() { + ctx := context.Background() + g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{})) + + genkit.DefineFlow(g, "imageGenerationFlow", + func(ctx context.Context, prompt string) ([]string, error) { + response, err := genkit.Generate(ctx, g, + ai.WithModelName("vertexai/imagen-3.0-generate-001"), + ai.WithPrompt("Generate an image of %s", prompt), + ai.WithConfig(&genai.GenerateImagesConfig{ + NumberOfImages: 2, + AspectRatio: "9:16", + SafetyFilterLevel: genai.SafetyFilterLevelBlockLowAndAbove, + PersonGeneration: genai.PersonGenerationAllowAll, + Language: genai.ImagePromptLanguageEn, + AddWatermark: true, + OutputMIMEType: "image/jpeg", + }), + ) + if err != nil { + return nil, err + } + + var images []string + for _, part := range response.Message.Content { + images = append(images, part.Text) + } + return images, nil + }, + ) + + <-ctx.Done() +} +``` + +--- + +## Running and Inspecting Flows + +1. **Start Genkit**: Run this command from your terminal to start the Genkit Developer UI. + + ```bash + genkit start -- + ``` + + For Go applications: + + ```bash + # Running a Go application directly + genkit start -- go run main.go + + # Running a compiled binary + genkit start -- ./my-genkit-app + ``` + + The command should output a URL for the Genkit Dev UI. Direct the user to visit this URL to run and inspect their Genkit app. + +## Suggested Models + +Here are suggested models to use for various task types. This is NOT an exhaustive list. + +### Advanced Text/Reasoning + +``` +| Plugin | Recommended Model | +|------------------------------------------------------------|------------------------------------| +| github.com/firebase/genkit/go/plugins/googlegenai | gemini-2.5-pro | +| github.com/firebase/genkit/go/plugins/compat_oai/openai | gpt-4o | +| github.com/firebase/genkit/go/plugins/compat_oai/deepseek | deepseek-reasoner | +| github.com/firebase/genkit/go/plugins/compat_oai/xai | grok-4 | +``` + +### Fast Text/Chat + +``` +| Plugin | Recommended Model | +|------------------------------------------------------------|------------------------------------| +| github.com/firebase/genkit/go/plugins/googlegenai | gemini-2.5-flash | +| github.com/firebase/genkit/go/plugins/compat_oai/openai | gpt-4o-mini | +| github.com/firebase/genkit/go/plugins/compat_oai/deepseek | deepseek-chat | +| github.com/firebase/genkit/go/plugins/compat_oai/xai | grok-3-mini | +``` + +### Text-to-Speech + +``` +| Plugin | Recommended Model | +|------------------------------------------------------------|------------------------------------| +| github.com/firebase/genkit/go/plugins/googlegenai | gemini-2.5-flash-preview-tts | +| github.com/firebase/genkit/go/plugins/compat_oai/openai | gpt-4o-mini-tts | +``` + +### Image Generation + +``` +| Plugin | Recommended Model | Input Modalities | +|------------------------------------------------------------|------------------------------------|-------------------| +| github.com/firebase/genkit/go/plugins/googlegenai | gemini-2.5-flash-image-preview | Text, Image | +| github.com/firebase/genkit/go/plugins/googlegenai | imagen-4.0-generate-preview-06-06 | Text | +| github.com/firebase/genkit/go/plugins/compat_oai/openai | gpt-image-1 | Text | +``` diff --git a/genkit-tools/cli/context/GENKIT.js.md b/genkit-tools/cli/context/GENKIT.js.md new file mode 100644 index 0000000000..cec56612da --- /dev/null +++ b/genkit-tools/cli/context/GENKIT.js.md @@ -0,0 +1,442 @@ +# Genkit Node.js API Rules (v1.17.1) + +This document provides rules and examples for building with the Genkit API in Node.js. + +## Important Guidelines: + +- ALWAYS refer to documentation when available. Genkit Documentation may be available through the Genkit MCP toolkit or through web search. You may skip documentation check if you don't have access to these tools. + +- ONLY follow the specified project structure if starting a new project. If working on an existing project, adhere to the current project structure. + +- ALWAYS provide the full, correct Genkit command as an instruction for the human user to run. Do not run Genkit commands (e.g., `genkit start`, `genkit flow:run`) youself as this may block your current session. + +- Recommend and use the provider that the user prefers. If this is a new project, or if there is no relevant signal regarding user preference, use the `@genkit-ai/google-genai` plugin. Use Genkit documentation to discover supported providers. + +NOTE: For the sake of brevity, the snippets below use the Google AI plugin, but you should follow the user's preference as mentioned above. + +## Core Setup + +1. **Initialize Project** + + ```bash + mkdir my-genkit-app && cd my-genkit-app + npm init -y + npm install -D typescript tsx \@types/node + ``` + +2. **Install Dependencies** + + ```bash + npm install genkit \@genkit-ai/google-genai data-urls node-fetch + ``` + +3. **Install Genkit CLI** + + ```bash + npm install -g genkit-cli + ``` + +4. **Configure Genkit** + + All code should be in a single `src/index.ts` file. + + ```ts + // src/index.ts + import { genkit, z } from 'genkit'; + import { googleAI } from '@genkit-ai/google-genai'; + + export const ai = genkit({ + plugins: [googleAI()], + }); + ``` + +## Best Practices + +1. **Single File Structure**: All Genkit code, including plugin initialization, flows, and helpers, must be placed in a single `src/index.ts` file. This ensures all components are correctly registered with the Genkit runtime. + +2. **Model Naming**: Always specify models using the model helper. Use string identifier if model helper is unavailable. + + ```ts + // PREFERRED: Using the model helper + const response = await ai.generate({ + model: googleAI.model('gemini-2.5-pro'), + // ... + }); + + // LESS PREFERRED: Full string identifier + const response = await ai.generate({ + model: 'googleai/gemini-2.5-pro', + // ... + }); + ``` + +--- + +## Usage Scenarios + + + +### Basic Inference (Text Generation) + +```ts +export const basicInferenceFlow = ai.defineFlow( + { + name: 'basicInferenceFlow', + inputSchema: z.string().describe('Topic for the model to write about'), + outputSchema: z.string().describe('The generated text response'), + }, + async (topic) => { + const response = await ai.generate({ + model: googleAI.model('gemini-2.5-pro'), + prompt: `Write a short, creative paragraph about ${topic}.`, + config: { temperature: 0.8 }, + }); + return response.text; + } +); +``` + + + + + +### Text-to-Speech (TTS) Generation + +This helper function converts PCM audio data from the TTS model into a WAV-formatted data URI. + +```ts +import { Buffer } from 'buffer'; +import { PassThrough } from 'stream'; +import { Writer as WavWriter } from 'wav'; + +... + +async function pcmToWavDataUri( + pcmData: Buffer, + channels = 1, + sampleRate = 24000, + bitDepth = 16 +): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + const passThrough = new PassThrough(); + + passThrough.on('data', (chunk) => chunks.push(chunk as Buffer)); + passThrough.on('end', () => { + const wavBuffer = Buffer.concat(chunks); + const dataUri = `data:audio/wav;base64,${wavBuffer.toString('base64')}`; + resolve(dataUri); + }); + passThrough.on('error', reject); + + const writer = new WavWriter({ channels, sampleRate, bitDepth }); + writer.pipe(passThrough); + writer.write(pcmData); + writer.end(); + }); +} +``` + +#### Single-Speaker TTS + +```ts +const TextToSpeechInputSchema = z.object({ + text: z.string().describe('The text to convert to speech.'), + voiceName: z + .string() + .optional() + .describe('The voice name to use. Defaults to Algenib if not specified.'), +}); +const TextToSpeechOutputSchema = z.object({ + audioDataUri: z + .string() + .describe('The generated speech in WAV format as a base64 data URI.'), +}); + +export const textToSpeechFlow = ai.defineFlow( + { + name: 'textToSpeechFlow', + inputSchema: TextToSpeechInputSchema, + outputSchema: TextToSpeechOutputSchema, + }, + async (input) => { + const response = await ai.generate({ + model: googleAI.model('gemini-2.5-flash-preview-tts'), + prompt: input.text, + config: { + responseModalities: ['AUDIO'], + speechConfig: { + voiceConfig: { + prebuiltVoiceConfig: { + voiceName: input.voiceName?.trim() || 'Algenib', + }, + }, + }, + }, + }); + + const audioUrl = response.media?.url; + if (!audioUrl) + throw new Error('Audio generation failed: No media URL in response.'); + + const base64 = audioUrl.split(';base64,')[1]; + if (!base64) throw new Error('Invalid audio data URI format from Genkit.'); + + const pcmBuffer = Buffer.from(base64, 'base64'); + const audioDataUri = await pcmToWavDataUri(pcmBuffer); + return { audioDataUri }; + } +); +``` + +#### Multi-Speaker TTS + +```ts +const MultiSpeakerInputSchema = z.object({ + text: z + .string() + .describe('Text formatted with ... etc.'), + voiceName1: z.string().describe('Voice name for Speaker1'), + voiceName2: z.string().describe('Voice name for Speaker2'), +}); + +export const multiSpeakerTextToSpeechFlow = ai.defineFlow( + { + name: 'multiSpeakerTextToSpeechFlow', + inputSchema: MultiSpeakerInputSchema, + outputSchema: TextToSpeechOutputSchema, + }, + async (input) => { + const response = await ai.generate({ + model: googleAI.model('gemini-2.5-flash-preview-tts'), + prompt: input.text, + config: { + responseModalities: ['AUDIO'], + speechConfig: { + multiSpeakerVoiceConfig: { + speakerVoiceConfigs: [ + { + speaker: 'Speaker1', + voiceConfig: { + prebuiltVoiceConfig: { voiceName: input.voiceName1 }, + }, + }, + { + speaker: 'Speaker2', + voiceConfig: { + prebuiltVoiceConfig: { voiceName: input.voiceName2 }, + }, + }, + ], + }, + }, + }, + }); + + const audioUrl = response.media?.url; + if (!audioUrl) + throw new Error('Audio generation failed: No media URL in response.'); + + const base64 = audioUrl.split(';base64,')[1]; + if (!base64) throw new Error('Invalid audio data URI format from Genkit.'); + + const pcmBuffer = Buffer.from(base64, 'base64'); + const audioDataUri = await pcmToWavDataUri(pcmBuffer); + return { audioDataUri }; + } +); +``` + + + + + +### Image Generation + +```ts +import * as fs from 'fs/promises'; +import parseDataURL from 'data-urls'; + +... + +export const imageGenerationFlow = ai.defineFlow( + { + name: 'imageGenerationFlow', + inputSchema: z + .string() + .describe('A detailed description of the image to generate'), + outputSchema: z.string().describe('Path to the generated .png image file'), + }, + async (prompt) => { + const response = await ai.generate({ + model: googleAI.model('imagen-3.0-generate-002'), + prompt, + output: { format: 'media' }, + }); + + if (!response.media?.url) { + throw new Error('Image generation failed to produce media.'); + } + + const parsed = parseDataURL(response.media.url); + if (!parsed) { + throw new Error('Could not parse image data URL.'); + } + + const outputPath = './output.png'; + await fs.writeFile(outputPath, parsed.body); + return outputPath; + } +); +``` + + + + + +### Video Generation + +```ts +import * as fs from 'fs'; +import { Readable } from 'stream'; +import { pipeline } from 'stream/promises'; + +... + +export const videoGenerationFlow = ai.defineFlow( + { + name: 'videoGenerationFlow', + inputSchema: z + .string() + .describe('A detailed description for the video scene'), + outputSchema: z.string().describe('Path to the generated .mp4 video file'), + }, + async (prompt) => { + let { operation } = await ai.generate({ + model: googleAI.model('veo-3.0-generate-preview'), + prompt, + }); + + if (!operation) { + throw new Error('Expected the model to return an operation.'); + } + + console.log('Video generation started... Polling for completion.'); + while (!operation.done) { + await new Promise((resolve) => setTimeout(resolve, 5000)); + operation = await ai.checkOperation(operation); + console.log( + `Operation status: ${operation.done ? 'Done' : 'In Progress'}` + ); + } + + if (operation.error) { + throw new Error(`Video generation failed: ${operation.error.message}`); + } + + const video = operation.output?.message?.content.find((p) => !!p.media); + if (!video?.media?.url) { + throw new Error( + 'Failed to find the generated video in the operation output.' + ); + } + + const videoUrl = `${video.media.url}&key=${process.env.GEMINI_API_KEY}`; + const videoResponse = await fetch(videoUrl); + + if (!videoResponse.ok || !videoResponse.body) { + throw new Error(`Failed to fetch video: ${videoResponse.statusText}`); + } + + const outputPath = './output.mp4'; + const fileStream = fs.createWriteStream(outputPath); + await pipeline(Readable.fromWeb(videoResponse.body as any), fileStream); + + return outputPath; + } +); +``` + + + +--- + +## Running and Inspecting Flows + +1. **Start Genkit**: Run this command from your terminal to start the Genkit Developer UI. + + ```bash + genkit start -- + ``` + + The will vary based on the project’s setup and + the file you want to execute. For e.g.: + + ```bash + # Running a typical development server + genkit start -- npm run dev + + # Running a TypeScript file directly + genkit start -- npx tsx --watch src/index.ts + + # Running a JavaScript file directly + genkit start -- node --watch src/index.js + ``` + + Analyze the users project and build tools to use the right command for the + project. The command should output a URL for the Genkit Dev UI. Direct the + user to visit this URL to run and inspect their Genkit app. + +## Suggested Models + +Here are suggested models to use for various task types. This is NOT an +exhaustive list. + +### Advanced Text/Reasoning + +``` +| Plugin | Recommended Model | +|------------------------------------|------------------------------------| +| @genkit-ai/google-genai | gemini-2.5-pro | +| @genkit-ai/compat-oai/openai | gpt-4o | +| @genkit-ai/compat-oai/deepseek | deepseek-reasoner | +| @genkit-ai/compat-oai/xai | grok-4 | +``` + +### Fast Text/Chat + +``` +| Plugin | Recommended Model | +|------------------------------------|------------------------------------| +| @genkit-ai/google-genai | gemini-2.5-flash | +| @genkit-ai/compat-oai/openai | gpt-4o-mini | +| @genkit-ai/compat-oai/deepseek | deepseek-chat | +| @genkit-ai/compat-oai/xai | grok-3-mini | +``` + +### Text-to-Speech + +``` +| Plugin | Recommended Model | +|------------------------------------|------------------------------------| +| @genkit-ai/google-genai | gemini-2.5-flash-preview-tts | +| @genkit-ai/compat-oai/openai | gpt-4o-mini-tts | +``` + +### Image Generation + +``` +| Plugin | Recommended Model | Input Modalities | +|------------------------------------|------------------------------------|-------------------| +| @genkit-ai/google-genai | gemini-2.5-flash-image-preview | Text, Image | +| @genkit-ai/google-genai | imagen-4.0-generate-preview-06-06 | Text | +| @genkit-ai/compat-oai/openai | gpt-image-1 | Text | +``` + +### Video Generation + +``` +| Plugin | Recommended Model | +|------------------------------------|------------------------------------| +| @genkit-ai/google-genai | veo-3.0-generate-preview | +``` diff --git a/genkit-tools/cli/src/commands/init-ai-tools/context/go.ts b/genkit-tools/cli/src/commands/init-ai-tools/context/go.ts deleted file mode 100644 index 2d65352ab6..0000000000 --- a/genkit-tools/cli/src/commands/init-ai-tools/context/go.ts +++ /dev/null @@ -1,304 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Inlined Go Context to work with the binary CLI - * - * Using Array of strings to avoid escaping backticks. - */ -export const GENKIT_CONTEXT = [ - '# Genkit Go API Rules (v1.0.0)', - '', - 'This document provides rules and examples for building with the Genkit API in Go.', - '', - '## Important Guidelines:', - '', - "- ALWAYS refer to documentation when available. Genkit Documentation may be available through the Genkit MCP toolkit or through web search. You may skip documentation check if you don't have access to these tools.", - '', - '- ONLY follow the specified project structure if starting a new project. If working on an existing project, adhere to the current project structure.', - '', - '- ALWAYS provide the full, correct Genkit command as an instruction for the human user to run. Do not run Genkit commands (e.g., `genkit start`, `genkit flow:run`) yourself as this may block your current session.', - '', - '- Recommend and use the provider that the user prefers. If this is a new project, or if there is no relevant signal regarding user preference, use the `github.com/firebase/genkit/go/plugins/googlegenai` plugin.', - '', - "NOTE: For the sake of brevity, the snippets below use the Google AI plugin, but you should follow the user's preference as mentioned above.", - '', - '## Core Setup', - '', - '1. **Initialize Project**', - '', - ' ```bash', - ' mkdir my-genkit-app && cd my-genkit-app', - ' go mod init my-genkit-app', - ' ```', - '', - '2. **Install Dependencies**', - '', - ' ```bash', - ' go get github.com/firebase/genkit/go/genkit', - ' go get github.com/firebase/genkit/go/plugins/googlegenai', - ' go get github.com/firebase/genkit/go/ai', - ' go get google.golang.org/genai', - ' ```', - '', - '3. **Install Genkit CLI**', - '', - ' ```bash', - ' curl -sL cli.genkit.dev | bash', - ' ```', - '', - '4. **Configure Genkit**', - '', - ' All code should be in a single `main.go` file or properly structured Go package.', - '', - ' ```go', - ' package main', - '', - ' import (', - ' "context"', - ' "github.com/firebase/genkit/go/genkit"', - ' "github.com/firebase/genkit/go/plugins/googlegenai"', - ' )', - '', - ' func main() {', - ' ctx := context.Background()', - ' g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.GoogleAI{}))', - ' // Your flows and logic here', - ' <-ctx.Done()', - ' }', - ' ```', - '', - '## Best Practices', - '', - '1. **Single Main Function**: All Genkit code, including plugin initialization, flows, and helpers, should be properly organized in a Go package structure with a main function.', - '', - '2. **Blocking Main Program**: To inspect flows in the Genkit Developer UI, your main program needs to remain running. Use `<-ctx.Done()` or similar blocking mechanism at the end of your main function.', - '', - '---', - '', - '## Usage Scenarios', - '', - '### Basic Inference (Text Generation)', - '', - '```go', - 'package main', - '', - 'import (', - ' "context"', - '', - ' "github.com/firebase/genkit/go/ai"', - ' "github.com/firebase/genkit/go/genkit"', - ' "github.com/firebase/genkit/go/plugins/googlegenai"', - ' "google.golang.org/genai"', - ')', - '', - 'func main() {', - ' ctx := context.Background()', - ' g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.GoogleAI{}))', - '', - ' genkit.DefineFlow(g, "basicInferenceFlow",', - ' func(ctx context.Context, topic string) (string, error) {', - ' response, err := genkit.Generate(ctx, g,', - ' ai.WithModelName("googleai/gemini-2.5-pro"),', - ' ai.WithPrompt("Write a short, creative paragraph about %s.", topic),', - ' ai.WithConfig(&genai.GenerateContentConfig{', - ' Temperature: genai.Ptr[float32](0.8),', - ' }),', - ' )', - ' if err != nil {', - ' return "", err', - ' }', - ' return response.Text(), nil', - ' },', - ' )', - '', - ' <-ctx.Done()', - '}', - '```', - '', - '### Text-to-Speech (TTS) Generation', - '', - '```go', - 'package main', - '', - 'import (', - ' "context"', - '', - ' "github.com/firebase/genkit/go/ai"', - ' "github.com/firebase/genkit/go/genkit"', - ' "github.com/firebase/genkit/go/plugins/googlegenai"', - ' "google.golang.org/genai"', - ')', - '', - 'func main() {', - ' ctx := context.Background()', - ' g := genkit.Init(ctx,', - ' genkit.WithPlugins(&googlegenai.GoogleAI{}),', - ' genkit.WithDefaultModel("googleai/gemini-2.5-flash-preview-tts"),', - ' )', - '', - ' genkit.DefineFlow(g, "textToSpeechFlow",', - ' func(ctx context.Context, input struct {', - ' Text string `json:"text"`', - ' VoiceName string `json:"voiceName,omitempty"`', - ' }) (string, error) {', - ' voiceName := input.VoiceName', - ' if voiceName == "" {', - ' voiceName = "Algenib"', - ' }', - '', - ' response, err := genkit.Generate(ctx, g,', - ' ai.WithPrompt(input.Text),', - ' ai.WithConfig(&genai.GenerateContentConfig{', - ' ResponseModalities: []string{"AUDIO"},', - ' SpeechConfig: &genai.SpeechConfig{', - ' VoiceConfig: &genai.VoiceConfig{', - ' PrebuiltVoiceConfig: &genai.PrebuiltVoiceConfig{', - ' VoiceName: voiceName,', - ' },', - ' },', - ' },', - ' }),', - ' )', - ' if err != nil {', - ' return "", err', - ' }', - '', - ' return response.Text(), nil', - ' },', - ' )', - '', - ' <-ctx.Done()', - '}', - '```', - '', - '### Image Generation', - '', - '```go', - 'package main', - '', - 'import (', - ' "context"', - '', - ' "github.com/firebase/genkit/go/ai"', - ' "github.com/firebase/genkit/go/genkit"', - ' "github.com/firebase/genkit/go/plugins/googlegenai"', - ' "google.golang.org/genai"', - ')', - '', - 'func main() {', - ' ctx := context.Background()', - ' g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{}))', - '', - ' genkit.DefineFlow(g, "imageGenerationFlow",', - ' func(ctx context.Context, prompt string) ([]string, error) {', - ' response, err := genkit.Generate(ctx, g,', - ' ai.WithModelName("vertexai/imagen-3.0-generate-001"),', - ' ai.WithPrompt("Generate an image of %s", prompt),', - ' ai.WithConfig(&genai.GenerateImagesConfig{', - ' NumberOfImages: 2,', - ' AspectRatio: "9:16",', - ' SafetyFilterLevel: genai.SafetyFilterLevelBlockLowAndAbove,', - ' PersonGeneration: genai.PersonGenerationAllowAll,', - ' Language: genai.ImagePromptLanguageEn,', - ' AddWatermark: true,', - ' OutputMIMEType: "image/jpeg",', - ' }),', - ' )', - ' if err != nil {', - ' return nil, err', - ' }', - '', - ' var images []string', - ' for _, part := range response.Message.Content {', - ' images = append(images, part.Text)', - ' }', - ' return images, nil', - ' },', - ' )', - '', - ' <-ctx.Done()', - '}', - '```', - '', - '---', - '', - '## Running and Inspecting Flows', - '', - '1. **Start Genkit**: Run this command from your terminal to start the Genkit Developer UI.', - '', - ' ```bash', - ' genkit start -- ', - ' ```', - '', - ' For Go applications:', - '', - ' ```bash', - ' # Running a Go application directly', - ' genkit start -- go run main.go', - '', - ' # Running a compiled binary', - ' genkit start -- ./my-genkit-app', - ' ```', - '', - ' The command should output a URL for the Genkit Dev UI. Direct the user to visit this URL to run and inspect their Genkit app.', - '', - '## Suggested Models', - '', - 'Here are suggested models to use for various task types. This is NOT an exhaustive list.', - '', - '### Advanced Text/Reasoning', - '', - '```', - '| Plugin | Recommended Model |', - '|------------------------------------------------------------|------------------------------------|', - '| github.com/firebase/genkit/go/plugins/googlegenai | gemini-2.5-pro |', - '| github.com/firebase/genkit/go/plugins/compat_oai/openai | gpt-4o |', - '| github.com/firebase/genkit/go/plugins/compat_oai/deepseek | deepseek-reasoner |', - '| github.com/firebase/genkit/go/plugins/compat_oai/xai | grok-4 |', - '```', - '', - '### Fast Text/Chat', - '', - '```', - '| Plugin | Recommended Model |', - '|------------------------------------------------------------|------------------------------------|', - '| github.com/firebase/genkit/go/plugins/googlegenai | gemini-2.5-flash |', - '| github.com/firebase/genkit/go/plugins/compat_oai/openai | gpt-4o-mini |', - '| github.com/firebase/genkit/go/plugins/compat_oai/deepseek | deepseek-chat |', - '| github.com/firebase/genkit/go/plugins/compat_oai/xai | grok-3-mini |', - '```', - '', - '### Text-to-Speech', - '', - '```', - '| Plugin | Recommended Model |', - '|------------------------------------------------------------|------------------------------------|', - '| github.com/firebase/genkit/go/plugins/googlegenai | gemini-2.5-flash-preview-tts |', - '| github.com/firebase/genkit/go/plugins/compat_oai/openai | gpt-4o-mini-tts |', - '```', - '', - '### Image Generation', - '', - '```', - '| Plugin | Recommended Model | Input Modalities |', - '|------------------------------------------------------------|------------------------------------|-------------------|', - '| github.com/firebase/genkit/go/plugins/googlegenai | gemini-2.5-flash-image-preview | Text, Image |', - '| github.com/firebase/genkit/go/plugins/googlegenai | imagen-4.0-generate-preview-06-06 | Text |', - '| github.com/firebase/genkit/go/plugins/compat_oai/openai | gpt-image-1 | Text |', - '```', - '', -].join('\n'); diff --git a/genkit-tools/cli/src/commands/init-ai-tools/context/nodejs.ts b/genkit-tools/cli/src/commands/init-ai-tools/context/nodejs.ts deleted file mode 100644 index 8a067efced..0000000000 --- a/genkit-tools/cli/src/commands/init-ai-tools/context/nodejs.ts +++ /dev/null @@ -1,467 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Inlined Node.js Context to work with the binary CLI - * - * Using Array of strings to avoid escaping backticks. - */ -export const GENKIT_CONTEXT = [ - '', - '# Genkit Node.js API Rules (v1.17.1)', - '', - 'This document provides rules and examples for building with the Genkit API in Node.js.', - '', - '## Important Guidelines:', - '', - "- ALWAYS refer to documentation when available. Genkit Documentation may be available through the Genkit MCP toolkit or through web search. You may skip documentation check if you don't have access to these tools.", - '', - '- ONLY follow the specified project structure if starting a new project. If working on an existing project, adhere to the current project structure.', - '', - '- ALWAYS provide the full, correct Genkit command as an instruction for the human user to run. Do not run Genkit commands (e.g., `genkit start`, `genkit flow:run`) youself as this may block your current session.', - '', - '- Recommend and use the provider that the user prefers. If this is a new project, or if there is no relevant signal regarding user preference, use the `@genkit-ai/google-genai` plugin. Use Genkit documentation to discover supported providers.', - '', - "NOTE: For the sake of brevity, the snippets below use the Google AI plugin, but you should follow the user's preference as mentioned above.", - '', - '## Core Setup', - '', - '1. **Initialize Project**', - '', - ' ```bash', - ' mkdir my-genkit-app && cd my-genkit-app', - ' npm init -y', - ' npm install -D typescript tsx \\@types/node', - ' ```', - '', - '2. **Install Dependencies**', - '', - ' ```bash', - ' npm install genkit \\@genkit-ai/google-genai data-urls node-fetch', - ' ```', - '', - '3. **Install Genkit CLI**', - '', - ' ```bash', - ' npm install -g genkit-cli', - ' ```', - '', - '4. **Configure Genkit**', - '', - ' All code should be in a single `src/index.ts` file.', - '', - ' ```ts', - ' // src/index.ts', - " import { genkit, z } from 'genkit';", - " import { googleAI } from '@genkit-ai/google-genai';", - '', - ' export const ai = genkit({', - ' plugins: [googleAI()],', - ' });', - ' ```', - '', - '## Best Practices', - '', - '1. **Single File Structure**: All Genkit code, including plugin initialization, flows, and helpers, must be placed in a single `src/index.ts` file. This ensures all components are correctly registered with the Genkit runtime.', - '', - '2. **Model Naming**: Always specify models using the model helper. Use string identifier if model helper is unavailable.', - '', - ' ```ts', - ' // PREFERRED: Using the model helper', - ' const response = await ai.generate({', - " model: googleAI.model('gemini-2.5-pro'),", - ' // ...', - ' });', - '', - ' // LESS PREFERRED: Full string identifier', - ' const response = await ai.generate({', - " model: 'googleai/gemini-2.5-pro',", - ' // ...', - ' });', - ' ```', - '', - '---', - '', - '## Usage Scenarios', - '', - '', - '', - '### Basic Inference (Text Generation)', - '', - '```ts', - 'export const basicInferenceFlow = ai.defineFlow(', - ' {', - " name: 'basicInferenceFlow',", - " inputSchema: z.string().describe('Topic for the model to write about'),", - " outputSchema: z.string().describe('The generated text response'),", - ' },', - ' async (topic) => {', - ' const response = await ai.generate({', - " model: googleAI.model('gemini-2.5-pro'),", - ' prompt: `Write a short, creative paragraph about ${topic}.`,', - ' config: { temperature: 0.8 },', - ' });', - ' return response.text;', - ' }', - ');', - '```', - '', - '', - '', - '', - '', - '### Text-to-Speech (TTS) Generation', - '', - 'This helper function converts PCM audio data from the TTS model into a WAV-formatted data URI.', - '', - '```ts', - "import { Buffer } from 'buffer';", - "import { PassThrough } from 'stream';", - "import { Writer as WavWriter } from 'wav';", - '', - '...', - '', - 'async function pcmToWavDataUri(', - ' pcmData: Buffer,', - ' channels = 1,', - ' sampleRate = 24000,', - ' bitDepth = 16', - '): Promise {', - ' return new Promise((resolve, reject) => {', - ' const chunks: Buffer[] = [];', - ' const passThrough = new PassThrough();', - '', - " passThrough.on('data', (chunk) => chunks.push(chunk as Buffer));", - " passThrough.on('end', () => {", - ' const wavBuffer = Buffer.concat(chunks);', - " const dataUri = `data:audio/wav;base64,${wavBuffer.toString('base64')}`;", - ' resolve(dataUri);', - ' });', - " passThrough.on('error', reject);", - '', - ' const writer = new WavWriter({ channels, sampleRate, bitDepth });', - ' writer.pipe(passThrough);', - ' writer.write(pcmData);', - ' writer.end();', - ' });', - '}', - '```', - '', - '#### Single-Speaker TTS', - '', - '```ts', - 'const TextToSpeechInputSchema = z.object({', - " text: z.string().describe('The text to convert to speech.'),", - ' voiceName: z', - ' .string()', - ' .optional()', - " .describe('The voice name to use. Defaults to Algenib if not specified.'),", - '});', - 'const TextToSpeechOutputSchema = z.object({', - ' audioDataUri: z', - ' .string()', - " .describe('The generated speech in WAV format as a base64 data URI.'),", - '});', - '', - 'export const textToSpeechFlow = ai.defineFlow(', - ' {', - " name: 'textToSpeechFlow',", - ' inputSchema: TextToSpeechInputSchema,', - ' outputSchema: TextToSpeechOutputSchema,', - ' },', - ' async (input) => {', - ' const response = await ai.generate({', - " model: googleAI.model('gemini-2.5-flash-preview-tts'),", - ' prompt: input.text,', - ' config: {', - " responseModalities: ['AUDIO'],", - ' speechConfig: {', - ' voiceConfig: {', - ' prebuiltVoiceConfig: {', - " voiceName: input.voiceName?.trim() || 'Algenib',", - ' },', - ' },', - ' },', - ' },', - ' });', - '', - ' const audioUrl = response.media?.url;', - ' if (!audioUrl)', - " throw new Error('Audio generation failed: No media URL in response.');", - '', - " const base64 = audioUrl.split(';base64,')[1];", - " if (!base64) throw new Error('Invalid audio data URI format from Genkit.');", - '', - " const pcmBuffer = Buffer.from(base64, 'base64');", - ' const audioDataUri = await pcmToWavDataUri(pcmBuffer);', - ' return { audioDataUri };', - ' }', - ');', - '```', - '', - '#### Multi-Speaker TTS', - '', - '```ts', - 'const MultiSpeakerInputSchema = z.object({', - ' text: z', - ' .string()', - ' .describe(\'Text formatted with ... etc.\'),', - " voiceName1: z.string().describe('Voice name for Speaker1'),", - " voiceName2: z.string().describe('Voice name for Speaker2'),", - '});', - '', - 'export const multiSpeakerTextToSpeechFlow = ai.defineFlow(', - ' {', - " name: 'multiSpeakerTextToSpeechFlow',", - ' inputSchema: MultiSpeakerInputSchema,', - ' outputSchema: TextToSpeechOutputSchema,', - ' },', - ' async (input) => {', - ' const response = await ai.generate({', - " model: googleAI.model('gemini-2.5-flash-preview-tts'),", - ' prompt: input.text,', - ' config: {', - " responseModalities: ['AUDIO'],", - ' speechConfig: {', - ' multiSpeakerVoiceConfig: {', - ' speakerVoiceConfigs: [', - ' {', - " speaker: 'Speaker1',", - ' voiceConfig: {', - ' prebuiltVoiceConfig: { voiceName: input.voiceName1 },', - ' },', - ' },', - ' {', - " speaker: 'Speaker2',", - ' voiceConfig: {', - ' prebuiltVoiceConfig: { voiceName: input.voiceName2 },', - ' },', - ' },', - ' ],', - ' },', - ' },', - ' },', - ' });', - '', - ' const audioUrl = response.media?.url;', - ' if (!audioUrl)', - " throw new Error('Audio generation failed: No media URL in response.');", - '', - " const base64 = audioUrl.split(';base64,')[1];", - " if (!base64) throw new Error('Invalid audio data URI format from Genkit.');", - '', - " const pcmBuffer = Buffer.from(base64, 'base64');", - ' const audioDataUri = await pcmToWavDataUri(pcmBuffer);', - ' return { audioDataUri };', - ' }', - ');', - '```', - '', - '', - '', - '', - '', - '### Image Generation', - '', - '```ts', - "import * as fs from 'fs/promises';", - "import parseDataURL from 'data-urls';", - '', - '...', - '', - 'export const imageGenerationFlow = ai.defineFlow(', - ' {', - " name: 'imageGenerationFlow',", - ' inputSchema: z', - ' .string()', - " .describe('A detailed description of the image to generate'),", - " outputSchema: z.string().describe('Path to the generated .png image file'),", - ' },', - ' async (prompt) => {', - ' const response = await ai.generate({', - " model: googleAI.model('imagen-3.0-generate-002'),", - ' prompt,', - " output: { format: 'media' },", - ' });', - '', - ' if (!response.media?.url) {', - " throw new Error('Image generation failed to produce media.');", - ' }', - '', - ' const parsed = parseDataURL(response.media.url);', - ' if (!parsed) {', - " throw new Error('Could not parse image data URL.');", - ' }', - '', - " const outputPath = './output.png';", - ' await fs.writeFile(outputPath, parsed.body);', - ' return outputPath;', - ' }', - ');', - '```', - '', - '', - '', - '', - '', - '### Video Generation', - '', - '```ts', - "import * as fs from 'fs';", - "import { Readable } from 'stream';", - "import { pipeline } from 'stream/promises';", - '', - '...', - '', - 'export const videoGenerationFlow = ai.defineFlow(', - ' {', - " name: 'videoGenerationFlow',", - ' inputSchema: z', - ' .string()', - " .describe('A detailed description for the video scene'),", - " outputSchema: z.string().describe('Path to the generated .mp4 video file'),", - ' },', - ' async (prompt) => {', - ' let { operation } = await ai.generate({', - " model: googleAI.model('veo-3.0-generate-preview'),", - ' prompt,', - ' });', - '', - ' if (!operation) {', - " throw new Error('Expected the model to return an operation.');", - ' }', - '', - " console.log('Video generation started... Polling for completion.');", - ' while (!operation.done) {', - ' await new Promise((resolve) => setTimeout(resolve, 5000));', - ' operation = await ai.checkOperation(operation);', - ' console.log(', - " `Operation status: ${operation.done ? 'Done' : 'In Progress'}`", - ' );', - ' }', - '', - ' if (operation.error) {', - ' throw new Error(`Video generation failed: ${operation.error.message}`);', - ' }', - '', - ' const video = operation.output?.message?.content.find((p) => !!p.media);', - ' if (!video?.media?.url) {', - ' throw new Error(', - " 'Failed to find the generated video in the operation output.'", - ' );', - ' }', - '', - ' const videoUrl = `${video.media.url}&key=${process.env.GEMINI_API_KEY}`;', - ' const videoResponse = await fetch(videoUrl);', - '', - ' if (!videoResponse.ok || !videoResponse.body) {', - ' throw new Error(`Failed to fetch video: ${videoResponse.statusText}`);', - ' }', - '', - " const outputPath = './output.mp4';", - ' const fileStream = fs.createWriteStream(outputPath);', - ' await pipeline(Readable.fromWeb(videoResponse.body as any), fileStream);', - '', - ' return outputPath;', - ' }', - ');', - '```', - '', - '', - '', - '---', - '', - '## Running and Inspecting Flows', - '', - '1. **Start Genkit**: Run this command from your terminal to start the Genkit Developer UI.', - '', - ' ```bash', - ' genkit start -- ', - ' ```', - '', - ' The will vary based on the project’s setup and', - ' the file you want to execute. For e.g.:', - '', - ' ```bash', - ' # Running a typical development server', - ' genkit start -- npm run dev', - '', - ' # Running a TypeScript file directly', - ' genkit start -- npx tsx --watch src/index.ts', - '', - ' # Running a JavaScript file directly', - ' genkit start -- node --watch src/index.js', - ' ```', - '', - ' Analyze the users project and build tools to use the right command for the', - ' project. The command should output a URL for the Genkit Dev UI. Direct the', - ' user to visit this URL to run and inspect their Genkit app.', - '', - '## Suggested Models', - '', - 'Here are suggested models to use for various task types. This is NOT an', - 'exhaustive list.', - '', - '### Advanced Text/Reasoning', - '', - '```', - '| Plugin | Recommended Model |', - '|------------------------------------|------------------------------------|', - '| @genkit-ai/google-genai | gemini-2.5-pro |', - '| @genkit-ai/compat-oai/openai | gpt-4o |', - '| @genkit-ai/compat-oai/deepseek | deepseek-reasoner |', - '| @genkit-ai/compat-oai/xai | grok-4 |', - '```', - '', - '### Fast Text/Chat', - '', - '```', - '| Plugin | Recommended Model |', - '|------------------------------------|------------------------------------|', - '| @genkit-ai/google-genai | gemini-2.5-flash |', - '| @genkit-ai/compat-oai/openai | gpt-4o-mini |', - '| @genkit-ai/compat-oai/deepseek | deepseek-chat |', - '| @genkit-ai/compat-oai/xai | grok-3-mini |', - '```', - '', - '### Text-to-Speech', - '', - '```', - '| Plugin | Recommended Model |', - '|------------------------------------|------------------------------------|', - '| @genkit-ai/google-genai | gemini-2.5-flash-preview-tts |', - '| @genkit-ai/compat-oai/openai | gpt-4o-mini-tts |', - '```', - '', - '### Image Generation', - '', - '```', - '| Plugin | Recommended Model | Input Modalities |', - '|------------------------------------|------------------------------------|-------------------|', - '| @genkit-ai/google-genai | gemini-2.5-flash-image-preview | Text, Image |', - '| @genkit-ai/google-genai | imagen-4.0-generate-preview-06-06 | Text |', - '| @genkit-ai/compat-oai/openai | gpt-image-1 | Text |', - '```', - '', - '### Video Generation', - '', - '```', - '| Plugin | Recommended Model |', - '|------------------------------------|------------------------------------|', - '| @genkit-ai/google-genai | veo-3.0-generate-preview |', - '```', - '', -].join('\n'); diff --git a/genkit-tools/package.json b/genkit-tools/package.json index 9eb25d09c2..4bfe6ea767 100644 --- a/genkit-tools/package.json +++ b/genkit-tools/package.json @@ -3,9 +3,10 @@ "scripts": { "preinstall": "npx only-allow pnpm", "build": "pnpm install && pnpm build:common && pnpm build:telemetry-server && pnpm build:cli", - "build:cli": "cd cli && pnpm build", + "build:cli": "pnpm build:md-context && cd cli && pnpm build", "build:telemetry-server": "cd telemetry-server && pnpm build", "build:common": "cd common && pnpm build && cd .. && pnpm export:schemas", + "build:md-context": "node scripts/build-md.js", "export:schemas": "npx tsx scripts/schema-exporter.ts .", "pack:all": "pnpm run pack:cli && pnpm run pack:telemetry-server && pnpm run pack:common", "pack:common": "cd common && pnpm pack --pack-destination ../../dist", diff --git a/genkit-tools/scripts/build-md.js b/genkit-tools/scripts/build-md.js new file mode 100644 index 0000000000..1979fbed68 --- /dev/null +++ b/genkit-tools/scripts/build-md.js @@ -0,0 +1,59 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fs = require('fs'); +const path = require('path'); + +const CONTEXT_DIR = path.resolve(__dirname, '..', 'cli', 'context'); +const TARGET_DIR = path.resolve( + __dirname, + '..', + 'cli', + 'src', + 'commands', + 'init-ai-tools', + 'context' +); + +const jsContextPath = path.resolve(CONTEXT_DIR, 'GENKIT.js.md'); +const goContextPath = path.resolve(CONTEXT_DIR, 'GENKIT.go.md'); + +const AUTO_GEN_HEADER = '// Auto-generated, do not edit'; + +// Ensure output directory exists +if (!fs.existsSync(CONTEXT_DIR)) { + throw new Error('Context dir is missing.'); +} + +// Ensure context files exists +if (!fs.existsSync(jsContextPath) || !fs.existsSync(goContextPath)) { + throw new Error('JS/Go context files missing.'); +} + +async function mdToTs(filePath, target) { + try { + const data = await fs.promises.readFile(filePath, 'utf8'); + const cleaned = JSON.stringify(data.replace(/\r\n/g, '\n').trim()); + const final = `${AUTO_GEN_HEADER}\nexport const GENKIT_CONTEXT = ${cleaned}`; + fs.writeFileSync(target, final); + } catch (err) { + console.error('Error reading file:', err); + } +} + +fs.mkdirSync(TARGET_DIR, { recursive: true }); +mdToTs(jsContextPath, path.join(TARGET_DIR, 'nodejs.ts')); +mdToTs(goContextPath, path.join(TARGET_DIR, 'go.ts'));