UNPKG

mastra

Version:
1,461 lines (1,413 loc) • 56.1 kB
import { fetchOrgs } from './chunk-EGDDF6K2.js'; import { getCurrentOrgId, setCurrentOrgId, getToken, loadCredentials } from './chunk-YWEV457P.js'; import { randomUUID } from 'crypto'; import fs3, { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs'; import os from 'os'; import path from 'path'; import { PostHog } from 'posthog-node'; import { fileURLToPath } from 'url'; import { InvalidArgumentError } from 'commander'; import { execa } from 'execa'; import fsExtra3 from 'fs-extra'; import fsExtra, { readJSON, ensureFile, writeJSON } from 'fs-extra/esm'; import child_process from 'child_process'; import * as fs4 from 'fs/promises'; import fs4__default from 'fs/promises'; import util from 'util'; import * as p2 from '@clack/prompts'; import color from 'picocolors'; import shellQuote from 'shell-quote'; import yoctoSpinner from 'yocto-spinner'; var createArgs = (versionTag) => { const packageName = versionTag ? `@mastra/mcp-docs-server@${versionTag}` : "@mastra/mcp-docs-server"; return ["-y", packageName]; }; var createMcpConfig = (editor, versionTag) => { const args = createArgs(versionTag); if (editor === "vscode") { return { servers: { mastra: process.platform === `win32` ? { command: "cmd", args: ["/c", "npx", ...args], type: "stdio" } : { command: "npx", args, type: "stdio" } } }; } return { mcpServers: { mastra: { command: "npx", args } } }; }; function makeConfig(original, editor, versionTag) { if (editor === "vscode") { return { ...original, servers: { ...original?.servers || {}, ...createMcpConfig(editor, versionTag).servers } }; } return { ...original, mcpServers: { ...original?.mcpServers || {}, ...createMcpConfig(editor, versionTag).mcpServers } }; } async function writeMergedConfig(configPath, editor, versionTag) { const configExists = existsSync(configPath); const config = makeConfig(configExists ? await readJSON(configPath) : {}, editor, versionTag); await ensureFile(configPath); await writeJSON(configPath, config, { spaces: 2 }); } var windsurfGlobalMCPConfigPath = path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"); var antigravityGlobalMCPConfigPath = path.join(os.homedir(), ".gemini", "antigravity", "mcp_config.json"); var cursorGlobalMCPConfigPath = path.join(os.homedir(), ".cursor", "mcp.json"); path.join(process.cwd(), ".vscode", "mcp.json"); var vscodeGlobalMCPConfigPath = path.join( os.homedir(), process.platform === "win32" ? path.join("AppData", "Roaming", "Code", "User", "settings.json") : process.platform === "darwin" ? path.join("Library", "Application Support", "Code", "User", "settings.json") : path.join(".config", "Code", "User", "settings.json") ); var EDITOR = ["cursor", "cursor-global", "windsurf", "vscode", "antigravity"]; function isValidEditor(value) { return EDITOR.includes(value); } async function installMastraDocsMCPServer({ editor, directory, versionTag }) { if (editor === `cursor`) { await writeMergedConfig(path.join(directory, ".cursor", "mcp.json"), "cursor", versionTag); } if (editor === `vscode`) { await writeMergedConfig(path.join(directory, ".vscode", "mcp.json"), "vscode", versionTag); } if (editor === `cursor-global`) { const alreadyInstalled = await globalMCPIsAlreadyInstalled(editor, versionTag); if (alreadyInstalled) { return; } await writeMergedConfig(cursorGlobalMCPConfigPath, "cursor-global", versionTag); } if (editor === `windsurf`) { const alreadyInstalled = await globalMCPIsAlreadyInstalled(editor, versionTag); if (alreadyInstalled) { return; } await writeMergedConfig(windsurfGlobalMCPConfigPath, editor, versionTag); } if (editor === `antigravity`) { const alreadyInstalled = await globalMCPIsAlreadyInstalled(editor, versionTag); if (alreadyInstalled) { return; } await writeMergedConfig(antigravityGlobalMCPConfigPath, editor, versionTag); } } async function globalMCPIsAlreadyInstalled(editor, versionTag) { let configPath = ``; if (editor === "windsurf") { configPath = windsurfGlobalMCPConfigPath; } else if (editor === "antigravity") { configPath = antigravityGlobalMCPConfigPath; } else if (editor === "cursor-global") { configPath = cursorGlobalMCPConfigPath; } else if (editor === "vscode") { configPath = vscodeGlobalMCPConfigPath; } if (!configPath || !existsSync(configPath)) { return false; } try { const configContents = await readJSON(configPath); if (!configContents) return false; const expectedPackage = versionTag ? `@mastra/mcp-docs-server@${versionTag}` : "@mastra/mcp-docs-server"; if (editor === "vscode") { if (!configContents.servers) return false; const hasMastraMCP2 = Object.values(configContents.servers).some( (server) => server?.args?.find((arg) => arg === expectedPackage) ); return hasMastraMCP2; } if (!configContents?.mcpServers) return false; const hasMastraMCP = Object.values(configContents.mcpServers).some( (server) => server?.args?.find((arg) => arg === expectedPackage) ); return hasMastraMCP; } catch { return false; } } // src/utils/package-manager.ts function getPackageManagerAddCommand(pm) { switch (pm) { case "npm": return "install --audit=false --fund=false --loglevel=error --progress=false --update-notifier=false"; case "yarn": return "add"; case "pnpm": return "add --loglevel=error"; case "bun": return "add"; default: return "add"; } } // src/services/service.deps.ts var DepsService = class { packageManager; constructor() { this.packageManager = this.getPackageManager(); } findLockFile(dir) { const lockFiles = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock", "bun.lockb"]; for (const file of lockFiles) { if (fs3.existsSync(path.join(dir, file))) { return file; } } const parentDir = path.resolve(dir, ".."); if (parentDir !== dir) { return this.findLockFile(parentDir); } return null; } getPackageManager() { const lockFile = this.findLockFile(process.cwd()); switch (lockFile) { case "pnpm-lock.yaml": return "pnpm"; case "package-lock.json": return "npm"; case "yarn.lock": return "yarn"; case "bun.lock": case "bun.lockb": return "bun"; default: return "npm"; } } async installPackages(packages) { const pm = this.packageManager; const installCommand = getPackageManagerAddCommand(pm); return execa(pm, [...installCommand.split(" "), ...packages], { all: true, stdio: "pipe" }); } async checkDependencies(dependencies) { try { const packageJsonPath = path.join(process.cwd(), "package.json"); try { await fs4__default.access(packageJsonPath); } catch { return "No package.json file found in the current directory"; } const packageJson = JSON.parse(await fs4__default.readFile(packageJsonPath, "utf-8")); for (const dependency of dependencies) { if (!packageJson.dependencies || !packageJson.dependencies[dependency]) { return `Please install ${dependency} before running this command (${this.packageManager} install ${dependency})`; } } return "ok"; } catch (err) { console.error(err); return "Could not check dependencies"; } } async getProjectName() { try { const packageJsonPath = path.join(process.cwd(), "package.json"); const packageJson = await fs4__default.readFile(packageJsonPath, "utf-8"); const pkg = JSON.parse(packageJson); return pkg.name; } catch (err) { throw err; } } async addScriptsToPackageJson(scripts) { const packageJson = JSON.parse(await fs4__default.readFile("package.json", "utf-8")); packageJson.scripts = { ...packageJson.scripts, ...scripts }; await fs4__default.writeFile("package.json", JSON.stringify(packageJson, null, 2)); } }; var EnvService = class { }; function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } var FileEnvService = class _FileEnvService extends EnvService { static ENV_KEY_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/; filePath; validateEnvEntry(key, value) { if (!_FileEnvService.ENV_KEY_REGEX.test(key)) { throw new Error(`Invalid ENV key: ${key}`); } if (/[\r\n]/.test(value)) { throw new Error(`Invalid ENV value for ${key}: multiline values are not supported.`); } } constructor(filePath) { super(); this.filePath = filePath; } envLineRegex(key, captureValue = false) { const pattern = captureValue ? `^${escapeRegExp(key)}=(.*)$` : `^${escapeRegExp(key)}=.*$`; return new RegExp(pattern, "m"); } async updateEnvData({ key, value, filePath = this.filePath, data }) { this.validateEnvEntry(key, value); const lineRegex = this.envLineRegex(key); const updated = lineRegex.test(data) ? data.replace(lineRegex, () => `${key}=${value}`) : `${data} ${key}=${value}`; await fs4.writeFile(filePath, updated, "utf8"); console.info(`${key} set in ENV file.`); return updated; } async getEnvValue(key) { try { const data = await fs4.readFile(this.filePath, "utf8"); const match = data.match(this.envLineRegex(key, true)); return match?.[1] ?? null; } catch (err) { console.error(`Error reading ENV value: ${err}`); return null; } } async setEnvValue(key, value) { try { const data = await fs4.readFile(this.filePath, "utf8"); await this.updateEnvData({ key, value, data }); } catch (err) { console.error(`Error writing ENV value: ${err}`); } } }; // src/services/service.file.ts var FileService = class { /** * * @param inputFile the file in the starter files directory to copy * @param outputFilePath the destination path * @param replaceIfExists flag to replace if it exists * @returns */ async copyStarterFile(inputFile, outputFilePath, replaceIfExists) { const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const filePath = path.resolve(__dirname, "starter-files", inputFile); const fileString = fs3.readFileSync(filePath, "utf8"); if (fs3.existsSync(outputFilePath) && !replaceIfExists) { console.info(`${outputFilePath} already exists`); return false; } await fsExtra.outputFile(outputFilePath, fileString); return true; } async setupEnvFile({ dbUrl }) { const envPath = path.join(process.cwd(), ".env.development"); await fsExtra.ensureFile(envPath); const fileEnvService = new FileEnvService(envPath); await fileEnvService.setEnvValue("DB_URL", dbUrl); } getFirstExistingFile(files) { for (const f of files) { if (fs3.existsSync(f)) { return f; } } throw new Error("Missing required file, checked the following paths: " + files.join(", ")); } replaceValuesInFile({ filePath, replacements }) { let fileContent = fs3.readFileSync(filePath, "utf8"); replacements.forEach(({ search, replace }) => { fileContent = fileContent.replaceAll(search, replace); }); fs3.writeFileSync(filePath, fileContent); } }; function isInteractive() { return Boolean(process.stdin.isTTY && process.stdout.isTTY) && !process.env.CI; } async function resolveCurrentOrg(token, opts = {}) { const orgs = await fetchOrgs(token); if (orgs.length === 0) { throw new Error("No organizations found."); } if (orgs.length === 1) { return { orgId: orgs[0].id, orgName: orgs[0].name }; } const envOrgId = process.env.MASTRA_ORG_ID; if (envOrgId) { const match = orgs.find((o) => o.id === envOrgId); if (match) return { orgId: match.id, orgName: match.name }; } const currentOrgId = await getCurrentOrgId(); if (currentOrgId && (!opts.forcePrompt || !isInteractive())) { const match = orgs.find((o) => o.id === currentOrgId); if (match) return { orgId: match.id, orgName: match.name }; } if (!isInteractive()) { throw new Error("Multiple organizations found. Run `mastra auth orgs switch` interactively or set MASTRA_ORG_ID."); } const selected = await p2.select({ message: "Select an organization", initialValue: currentOrgId ?? void 0, options: orgs.map((o) => ({ value: o.id, label: `${o.name}${o.id === currentOrgId ? " (current)" : ""}`, hint: o.id })) }); if (p2.isCancel(selected)) { p2.cancel("Cancelled."); process.exit(0); } const org = orgs.find((o) => o.id === selected); if (org.id !== currentOrgId) { await setCurrentOrgId(org.id); } return { orgId: org.id, orgName: org.name }; } async function listOrgsAction() { const token = await getToken(); const currentOrgId = await getCurrentOrgId(); const orgs = await fetchOrgs(token); if (orgs.length === 0) { console.info("\nNo organizations found.\n"); return; } console.info("\nOrganizations:\n"); for (const org of orgs) { const marker = org.id === currentOrgId ? " (current)" : ""; const role = org.role ? ` [${org.role}]` : ""; console.info(` ${org.name}${role}${marker}`); console.info(` ID: ${org.id}`); } console.info(""); } async function switchOrgAction() { if (process.env.MASTRA_API_TOKEN) { console.error("\nCannot switch org when using MASTRA_API_TOKEN. Unset it and log in with: mastra auth login\n"); process.exit(1); } if (process.env.MASTRA_ORG_ID) { console.error("\nCannot switch org when MASTRA_ORG_ID is set. Unset it to use persistent org selection.\n"); process.exit(1); } const token = await getToken(); const currentOrgId = await getCurrentOrgId(); const orgs = await fetchOrgs(token); if (orgs.length === 0) { console.info("\nNo organizations found.\n"); return; } if (orgs.length === 1) { console.info(` You only have one organization: ${orgs[0].name} `); return; } const selected = await p2.select({ message: "Switch to organization", options: orgs.map((o) => ({ value: o.id, label: `${o.name}${o.id === currentOrgId ? " (current)" : ""}` })) }); if (p2.isCancel(selected)) { p2.cancel("Cancelled."); process.exit(0); } await setCurrentOrgId(selected); const org = orgs.find((o) => o.id === selected); console.info(` Switched to ${org.name} (${org.id}) `); } // src/commands/init/utils.ts var exec = util.promisify(child_process.exec); var LLMProvider = ["openai", "anthropic", "groq", "google", "cerebras", "mistral"]; var COMPONENTS = ["agents", "workflows", "tools", "scorers"]; async function promptForObservability(command, onObservabilitySelected) { while (true) { const choice = await p2.select({ message: "Enable Mastra Observability? (will open auth flow)", options: [ { value: "yes", label: "Yes" }, { value: "no", label: "No" } ], initialValue: "yes", showInstructions: false }); if (p2.isCancel(choice)) return {}; const answer = choice === "yes" ? "yes" : "no"; const enabled = answer === "yes"; onObservabilitySelected?.({ command, enabled, answer, selection_method: "interactive" }); if (!enabled) return { enabled: false }; const hadCachedCreds = await loadCredentials() !== null; try { const token = await getToken(); if (hadCachedCreds) { const creds = await loadCredentials(); if (creds) p2.log.info(`Logged in as ${creds.user.email}`); } const org = await resolveCurrentOrg(token, { forcePrompt: true }); return { enabled: true, token, orgId: org.orgId, orgName: org.orgName }; } catch (err) { const message = err instanceof Error ? err.message : String(err); p2.log.warn(`Could not sign in to Mastra: ${message}`); } } } function isValidLLMProvider(value) { return LLMProvider.includes(value); } function areValidComponents(values) { return values.every((value) => COMPONENTS.includes(value)); } var getModelIdentifier = (llmProvider) => { let model = "openai/gpt-5-mini"; if (llmProvider === "anthropic") { model = "anthropic/claude-sonnet-4-5"; } else if (llmProvider === "groq") { model = "groq/llama-3.3-70b-versatile"; } else if (llmProvider === "google") { model = "google/gemini-2.5-pro"; } else if (llmProvider === "cerebras") { model = "cerebras/llama-3.3-70b"; } else if (llmProvider === "mistral") { model = "mistral/mistral-medium-2508"; } return model; }; async function writeAgentSample(llmProvider, destPath, addExampleTool, addScorers) { const modelString = getModelIdentifier(llmProvider); const instructions = `You are a helpful weather assistant that provides accurate weather information and can help planning activities based on the weather. Your primary function is to help users get weather details for specific locations. When responding: - Always ask for a location if none is provided - If the location name isn't in English, please translate it - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York") - Include relevant details like humidity, wind conditions, and precipitation - Keep responses concise but informative - If the user asks for activities and provides the weather forecast, suggest activities based on the weather forecast. - If the user asks for activities, respond in the format they request.${addExampleTool ? "\n\nUse the weatherTool to fetch current weather data." : ""}`; const imports = [ `import { Agent } from '@mastra/core/agent';`, `import { Memory } from '@mastra/memory';`, addExampleTool ? `import { weatherTool } from '../tools/weather-tool';` : void 0, addScorers ? `import { scorers } from '../scorers/weather-scorer';` : void 0 ].filter(Boolean).join("\n"); const toolsConfig = addExampleTool ? ` tools: { weatherTool }, ` : ""; const scorersConfig = addScorers ? ` scorers: { toolCallAppropriateness: { scorer: scorers.toolCallAppropriatenessScorer, sampling: { type: 'ratio', rate: 1, }, }, completeness: { scorer: scorers.completenessScorer, sampling: { type: 'ratio', rate: 1, }, }, translation: { scorer: scorers.translationScorer, sampling: { type: 'ratio', rate: 1, }, }, }, ` : ""; const content = `${imports} export const weatherAgent = new Agent({ id: 'weather-agent', name: 'Weather Agent', instructions: \`${instructions}\`, model: '${modelString}', ${toolsConfig}${scorersConfig} memory: new Memory(), }); `; await fs4__default.writeFile(destPath, content); } async function writeWorkflowSample(destPath) { const content = `import { createStep, createWorkflow } from '@mastra/core/workflows'; import { z } from 'zod'; const forecastSchema = z.object({ date: z.string(), maxTemp: z.number(), minTemp: z.number(), precipitationChance: z.number(), condition: z.string(), location: z.string(), }) function getWeatherCondition(code: number): string { const conditions: Record<number, string> = { 0: 'Clear sky', 1: 'Mainly clear', 2: 'Partly cloudy', 3: 'Overcast', 45: 'Foggy', 48: 'Depositing rime fog', 51: 'Light drizzle', 53: 'Moderate drizzle', 55: 'Dense drizzle', 61: 'Slight rain', 63: 'Moderate rain', 65: 'Heavy rain', 71: 'Slight snow fall', 73: 'Moderate snow fall', 75: 'Heavy snow fall', 95: 'Thunderstorm', } return conditions[code] || 'Unknown' } const fetchWeather = createStep({ id: 'fetch-weather', description: 'Fetches weather forecast for a given city', inputSchema: z.object({ city: z.string().describe('The city to get the weather for'), }), outputSchema: forecastSchema, execute: async ({ inputData }) => { if (!inputData) { throw new Error('Input data not found'); } const geocodingUrl = \`https://geocoding-api.open-meteo.com/v1/search?name=\${encodeURIComponent(inputData.city)}&count=1\`; const geocodingResponse = await fetch(geocodingUrl); const geocodingData = (await geocodingResponse.json()) as { results: { latitude: number; longitude: number; name: string }[]; }; if (!geocodingData.results?.[0]) { throw new Error(\`Location '\${inputData.city}' not found\`); } const { latitude, longitude, name } = geocodingData.results[0]; const weatherUrl = \`https://api.open-meteo.com/v1/forecast?latitude=\${latitude}&longitude=\${longitude}&current=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m\`; const response = await fetch(weatherUrl); const data = (await response.json()) as { current: { time: string precipitation: number weathercode: number } hourly: { precipitation_probability: number[] temperature_2m: number[] } } const forecast = { date: new Date().toISOString(), maxTemp: Math.max(...data.hourly.temperature_2m), minTemp: Math.min(...data.hourly.temperature_2m), condition: getWeatherCondition(data.current.weathercode), precipitationChance: data.hourly.precipitation_probability.reduce( (acc, curr) => Math.max(acc, curr), 0 ), location: name } return forecast; }, }); const planActivities = createStep({ id: 'plan-activities', description: 'Suggests activities based on weather conditions', inputSchema: forecastSchema, outputSchema: z.object({ activities: z.string(), }), execute: async ({ inputData, mastra }) => { const forecast = inputData if (!forecast) { throw new Error('Forecast data not found') } const agent = mastra?.getAgent('weatherAgent'); if (!agent) { throw new Error('Weather agent not found'); } const prompt = \`Based on the following weather forecast for \${forecast.location}, suggest appropriate activities: \${JSON.stringify(forecast, null, 2)} For each day in the forecast, structure your response exactly as follows: \u{1F4C5} [Day, Month Date, Year] \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 \u{1F321}\uFE0F WEATHER SUMMARY \u2022 Conditions: [brief description] \u2022 Temperature: [X\xB0C/Y\xB0F to A\xB0C/B\xB0F] \u2022 Precipitation: [X% chance] \u{1F305} MORNING ACTIVITIES Outdoor: \u2022 [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] \u{1F31E} AFTERNOON ACTIVITIES Outdoor: \u2022 [Activity Name] - [Brief description including specific location/route] Best timing: [specific time range] Note: [relevant weather consideration] \u{1F3E0} INDOOR ALTERNATIVES \u2022 [Activity Name] - [Brief description including specific venue] Ideal for: [weather condition that would trigger this alternative] \u26A0\uFE0F SPECIAL CONSIDERATIONS \u2022 [Any relevant weather warnings, UV index, wind conditions, etc.] Guidelines: - Suggest 2-3 time-specific outdoor activities per day - Include 1-2 indoor backup options - For precipitation >50%, lead with indoor activities - All activities must be specific to the location - Include specific venues, trails, or locations - Consider activity intensity based on temperature - Keep descriptions concise but informative Maintain this exact formatting for consistency, using the emoji and section headers as shown.\`; const response = await agent.stream([ { role: 'user', content: prompt, }, ]); let activitiesText = ''; for await (const chunk of response.textStream) { process.stdout.write(chunk); activitiesText += chunk; } return { activities: activitiesText, }; }, }); const weatherWorkflow = createWorkflow({ id: 'weather-workflow', inputSchema: z.object({ city: z.string().describe('The city to get the weather for'), }), outputSchema: z.object({ activities: z.string(), }) }) .then(fetchWeather) .then(planActivities); weatherWorkflow.commit(); export { weatherWorkflow };`; await fs4__default.writeFile(destPath, content); } async function writeToolSample(destPath) { const fileService = new FileService(); await fileService.copyStarterFile("tools.ts", destPath); } async function writeScorersSample(llmProvider, destPath) { const modelString = getModelIdentifier(llmProvider); const content = `import { z } from 'zod'; import { createToolCallAccuracyScorerCode } from '@mastra/evals/scorers/prebuilt'; import { createCompletenessScorer } from '@mastra/evals/scorers/prebuilt'; import { getAssistantMessageFromRunOutput, getUserMessageFromRunInput } from '@mastra/evals/scorers/utils'; import { createScorer } from '@mastra/core/evals'; export const toolCallAppropriatenessScorer = createToolCallAccuracyScorerCode({ expectedTool: 'weatherTool', strictMode: false, }); export const completenessScorer = createCompletenessScorer(); // Custom LLM-judged scorer: evaluates if non-English locations are translated appropriately export const translationScorer = createScorer({ id: 'translation-quality-scorer', name: 'Translation Quality', description: 'Checks that non-English location names are translated and used correctly', type: 'agent', judge: { model: '${modelString}', instructions: 'You are an expert evaluator of translation quality for geographic locations. ' + 'Determine whether the user text mentions a non-English location and whether the assistant correctly uses an English translation of that location. ' + 'Be lenient with transliteration differences and diacritics. ' + 'Return only the structured JSON matching the provided schema.', }, }) .preprocess(({ run }) => { const userText = getUserMessageFromRunInput(run.input) || ''; const assistantText = getAssistantMessageFromRunOutput(run.output) || ''; return { userText, assistantText }; }) .analyze({ description: 'Extract location names and detect language/translation adequacy', outputSchema: z.object({ nonEnglish: z.boolean(), translated: z.boolean(), confidence: z.number().min(0).max(1).default(1), explanation: z.string().default(''), }), createPrompt: ({ results }) => \` You are evaluating if a weather assistant correctly handled translation of a non-English location. User text: """ \${results.preprocessStepResult.userText} """ Assistant response: """ \${results.preprocessStepResult.assistantText} """ Tasks: 1) Identify if the user mentioned a location that appears non-English. 2) If non-English, check whether the assistant used a correct English translation of that location in its response. 3) Be lenient with transliteration differences (e.g., accents/diacritics). Return JSON with fields: { "nonEnglish": boolean, "translated": boolean, "confidence": number, // 0-1 "explanation": string } \`, }) .generateScore(({ results }) => { const r = (results as any)?.analyzeStepResult || {}; if (!r.nonEnglish) return 1; // If not applicable, full credit if (r.translated) return Math.max(0, Math.min(1, 0.7 + 0.3 * (r.confidence ?? 1))); return 0; // Non-English but not translated }) .generateReason(({ results, score }) => { const r = (results as any)?.analyzeStepResult || {}; return \`Translation scoring: nonEnglish=\${r.nonEnglish ?? false}, translated=\${r.translated ?? false}, confidence=\${r.confidence ?? 0}. Score=\${score}. \${r.explanation ?? ''}\`; }); export const scorers = { toolCallAppropriatenessScorer, completenessScorer, translationScorer, };`; await fs4__default.writeFile(destPath, content); } async function writeCodeSampleForComponents(llmprovider, component, destPath, importComponents) { switch (component) { case "agents": return writeAgentSample( llmprovider, destPath, importComponents.includes("tools"), importComponents.includes("scorers") ); case "tools": return writeToolSample(destPath); case "workflows": return writeWorkflowSample(destPath); case "scorers": return writeScorersSample(llmprovider, destPath); default: return ""; } } var createComponentsDir = async (dirPath, component) => { const componentPath = dirPath + `/${component}`; await fsExtra.ensureDir(componentPath); }; var writeIndexFile = async ({ dirPath, addAgent, addExample, addWorkflow, addScorers }) => { const indexPath = dirPath + "/index.ts"; const destPath = path.join(indexPath); try { await fs4__default.writeFile(destPath, ""); const filteredExports = [ addWorkflow ? `workflows: { weatherWorkflow },` : "", addAgent ? `agents: { weatherAgent },` : "", addScorers ? `scorers: { toolCallAppropriatenessScorer, completenessScorer, translationScorer },` : "" ].filter(Boolean); if (!addExample) { await fs4__default.writeFile( destPath, ` import { Mastra } from '@mastra/core/mastra'; export const mastra = new Mastra() ` ); return; } await fs4__default.writeFile( destPath, ` import { Mastra } from '@mastra/core/mastra'; import { PinoLogger } from '@mastra/loggers'; import { LibSQLStore } from '@mastra/libsql'; import { DuckDBStore } from "@mastra/duckdb"; import { MastraCompositeStore } from '@mastra/core/storage'; import { Observability, MastraStorageExporter, MastraPlatformExporter, SensitiveDataFilter } from '@mastra/observability'; ${addWorkflow ? `import { weatherWorkflow } from './workflows/weather-workflow';` : ""} ${addAgent ? `import { weatherAgent } from './agents/weather-agent';` : ""} ${addScorers ? `import { toolCallAppropriatenessScorer, completenessScorer, translationScorer } from './scorers/weather-scorer';` : ""} export const mastra = new Mastra({ ${filteredExports.join("\n ")} storage: new MastraCompositeStore({ id: 'composite-storage', default: new LibSQLStore({ id: "mastra-storage", // Uses a hosted database when deployed (mastra env db create --kind turso), // and a local file during development. url: process.env.TURSO_DATABASE_URL ?? "file:./mastra.db", authToken: process.env.TURSO_AUTH_TOKEN, }), domains: { observability: await new DuckDBStore().getStore('observability'), } }), logger: new PinoLogger({ name: 'Mastra', level: 'info', }), observability: new Observability({ configs: { default: { serviceName: 'mastra', exporters: [ new MastraStorageExporter(), // Persists observability events to Mastra Storage new MastraPlatformExporter(), // Sends observability events to Mastra Platform (if MASTRA_PLATFORM_ACCESS_TOKEN is set) ], spanOutputProcessors: [ new SensitiveDataFilter(), // Redacts sensitive data like passwords, tokens, keys ], }, }, }), }); ` ); } catch (err) { throw err; } }; var checkAndInstallCoreDeps = async (addExample, versionTag) => { const spinner = yoctoSpinner({ text: "Installing Mastra core dependencies" }); let packages = []; const mastraVersionTag = versionTag || "latest"; try { const depService = new DepsService(); spinner.start(); const needsCore = await depService.checkDependencies(["@mastra/core"]) !== `ok`; const needsCli = await depService.checkDependencies(["mastra"]) !== `ok`; const needsZod = await depService.checkDependencies(["zod"]) !== `ok`; if (needsCore) { packages.push({ name: "@mastra/core", version: mastraVersionTag }); } if (needsCli) { packages.push({ name: "mastra", version: mastraVersionTag }); } if (needsZod) { packages.push({ name: "zod", version: "^4" }); } if (addExample) { const needsLibsql = await depService.checkDependencies(["@mastra/libsql"]) !== `ok`; if (needsLibsql) { packages.push({ name: "@mastra/libsql", version: mastraVersionTag }); } } if (packages.length > 0) { await depService.installPackages(packages.map((pkg) => `${pkg.name}@${pkg.version}`)); } spinner.success("Successfully installed Mastra core dependencies"); } catch (err) { spinner.error(`Failed to install core dependencies: ${err instanceof Error ? err.message : "Unknown error"}`); } }; var getAPIKey = async (provider) => { let key = "OPENAI_API_KEY"; switch (provider) { case "anthropic": key = "ANTHROPIC_API_KEY"; return key; case "groq": key = "GROQ_API_KEY"; return key; case "google": key = "GOOGLE_API_KEY"; return key; case "cerebras": key = "CEREBRAS_API_KEY"; return key; case "mistral": key = "MISTRAL_API_KEY"; return key; default: return key; } }; var writeAPIKey = async ({ provider, apiKey }) => { const envFileName = apiKey ? ".env" : ".env.example"; const key = await getAPIKey(provider); const escapedKey = shellQuote.quote([key]); const escapedApiKey = shellQuote.quote([apiKey ? apiKey : "your-api-key"]); await exec(`echo ${escapedKey}=${escapedApiKey} >> ${envFileName}`); }; var writeObservabilityEnv = async ({ token, projectId, endpoint } = {}) => { const envFilePath = path.join(process.cwd(), ".env"); const lines = [ "", "# Mastra Observability \u2014 https://projects.mastra.ai", "# Access token and project id wired up automatically when you ran", "# `mastra init` / `create-mastra` with Observability enabled.", `MASTRA_PLATFORM_ACCESS_TOKEN=${token ?? ""}`, `MASTRA_PROJECT_ID=${projectId ?? ""}` ]; if (endpoint) { lines.push(`MASTRA_PLATFORM_OBSERVABILITY_ENDPOINT=${endpoint}`); } lines.push(""); await fs4__default.appendFile(envFilePath, lines.join("\n")); }; var createMastraDir = async (directory) => { let dir = directory.trim().split("/").filter((item) => item !== ""); const dirPath = path.join(process.cwd(), ...dir, "mastra"); try { await fs4__default.access(dirPath); return { ok: false }; } catch { await fsExtra.ensureDir(dirPath); return { ok: true, dirPath }; } }; var writeCodeSample = async (dirPath, component, llmProvider, importComponents) => { const destPath = dirPath + `/${component}/weather-${component.slice(0, -1)}.ts`; try { await writeCodeSampleForComponents(llmProvider, component, destPath, importComponents); } catch (err) { throw err; } }; var LLM_PROVIDERS = [ { value: "openai", label: "OpenAI", hint: "recommended" }, { value: "anthropic", label: "Anthropic" }, { value: "groq", label: "Groq" }, { value: "google", label: "Google" }, { value: "cerebras", label: "Cerebras" }, { value: "mistral", label: "Mistral" } ]; var interactivePrompt = async (args = {}) => { const { skip = {}, options: { command, showBanner = true, onObservabilitySelected } = {} } = args; if (showBanner) { p2.intro(color.inverse(" Mastra Init ")); } const mastraProject = await p2.group( { directory: () => skip?.directory ? void 0 : p2.text({ message: "Where should we create the Mastra files? (default: src/)", placeholder: "src/", defaultValue: "src/" }), llmProvider: () => skip?.llmProvider ? void 0 : p2.select({ message: "Select a default model provider:", options: LLM_PROVIDERS, showInstructions: false }), llmApiKey: async ({ results: { llmProvider } }) => { if (skip?.llmApiKey) return void 0; const llmName = LLM_PROVIDERS.find((p3) => p3.value === llmProvider)?.label || "provider"; const keyChoice = await p2.select({ message: `Enter your ${llmName} API key?`, options: [ { value: "skip", label: "Skip for now", hint: "default" }, { value: "enter", label: "Enter API key" } ], initialValue: "skip", showInstructions: false }); if (keyChoice === "enter") { return p2.password({ message: "Enter your API key:", mask: "*", clearOnError: true, validate: (value) => { if (!value || value.length === 0) return "API key cannot be empty"; } }); } return void 0; }, observability: async () => { if (skip?.observability) return void 0; return promptForObservability(command, onObservabilitySelected); }, configureMastraToolingForAgents: async () => { if (skip?.skills && skip?.mcpServer) return { skills: void 0, mcpServer: void 0 }; const choice = await p2.select({ message: `Select tooling for your coding assistant:`, options: [ { value: "skills", label: "Skills", hint: "recommended" }, { value: "mcp", label: "MCP Docs Server" } ], initialValue: "skills", showInstructions: false }); if (p2.isCancel(choice)) { return { skills: void 0, mcpServer: void 0 }; } if (choice === "skills") { const POPULAR_AGENTS = [ { value: "universal", label: "Universal (Codex, Cursor, Gemini, GitHub, OpenCode)" }, { value: "claude-code", label: "Claude Code" } ]; const ALL_AGENTS = [ ...POPULAR_AGENTS, { value: "adal", label: "AdaL" }, { value: "antigravity", label: "Antigravity" }, { value: "augment", label: "Augment" }, { value: "codebuddy", label: "CodeBuddy" }, { value: "command-code", label: "Command Code" }, { value: "crush", label: "Crush" }, { value: "droid", label: "Droid" }, { value: "goose", label: "Goose" }, { value: "iflow-cli", label: "iFlow CLI" }, { value: "junie", label: "Junie" }, { value: "kilo", label: "Kilo Code" }, { value: "kiro-cli", label: "Kiro CLI" }, { value: "kode", label: "Kode" }, { value: "mcpjam", label: "MCPJam" }, { value: "mistral-vibe", label: "Mistral Vibe" }, { value: "mux", label: "Mux" }, { value: "neovate", label: "Neovate" }, { value: "openclaude", label: "OpenClaude IDE" }, { value: "openclaw", label: "OpenClaw" }, { value: "openhands", label: "OpenHands" }, { value: "pi", label: "Pi" }, { value: "pochi", label: "Pochi" }, { value: "qoder", label: "Qoder" }, { value: "qwen-code", label: "Qwen Code" }, { value: "replit", label: "Replit" }, { value: "roo", label: "Roo Code" }, { value: "trae", label: "Trae" }, { value: "trae-cn", label: "Trae CN" }, { value: "windsurf", label: "Windsurf" }, { value: "zencoder", label: "Zencoder" } ]; const initialSelection = await p2.select({ message: `Select your coding assistant:`, options: [...POPULAR_AGENTS, { value: "__show_all__", label: "+ Show all agents" }], initialValue: "universal" }); if (p2.isCancel(initialSelection)) { return { skills: void 0, mcpServer: void 0 }; } let selectedAgents = /* @__PURE__ */ new Set(); if (initialSelection === "__show_all__") { const followUpSelection = await p2.select({ message: `Select your coding assistant:`, options: ALL_AGENTS }); if (p2.isCancel(followUpSelection)) { return { skills: void 0, mcpServer: void 0 }; } selectedAgents.add(followUpSelection); } else { selectedAgents.add(initialSelection); } selectedAgents.add("universal"); return { skills: Array.from(selectedAgents), mcpServer: void 0 }; } if (choice === "mcp") { const editor = await p2.select({ message: `Select your coding assistant:`, options: [ { value: "cursor", label: "Cursor (project only)" }, { value: "cursor-global", label: "Cursor (global, all projects)" }, { value: "windsurf", label: "Windsurf" }, { value: "vscode", label: "VSCode" }, { value: "antigravity", label: "Antigravity" } ] }); if (p2.isCancel(editor)) { return { skills: void 0, mcpServer: void 0 }; } if (editor === `cursor`) { p2.log.message( ` Note: you will need to go into Cursor Settings -> MCP Settings and manually enable the installed Mastra MCP server. ` ); } if (editor === `cursor-global`) { const confirm2 = await p2.select({ message: `Global install will add/update ${cursorGlobalMCPConfigPath} and make the Mastra docs MCP server available in all your Cursor projects. Continue?`, options: [ { value: "yes", label: "Yes, I understand" }, { value: "no", label: "No, cancel" } ] }); if (confirm2 !== `yes`) { return { skills: void 0, mcpServer: void 0 }; } } if (editor === `windsurf`) { const confirm2 = await p2.select({ message: `Windsurf only supports a global MCP config (at ${windsurfGlobalMCPConfigPath}) is it ok to add/update that global config? This means the Mastra docs MCP server will be available in all your Windsurf projects.`, options: [ { value: "yes", label: "Yes, I understand" }, { value: "no", label: "No, cancel" } ] }); if (confirm2 !== `yes`) { return { skills: void 0, mcpServer: void 0 }; } } if (editor === `antigravity`) { const confirm2 = await p2.select({ message: `Antigravity only supports a global MCP config (at ${antigravityGlobalMCPConfigPath}). Is it ok to add/update that global config? This will make the Mastra docs MCP server available in all Antigravity projects.`, options: [ { value: "yes", label: "Yes, I understand" }, { value: "no", label: "No, cancel" } ] }); if (confirm2 !== `yes`) { return { skills: void 0, mcpServer: void 0 }; } } return { skills: void 0, mcpServer: editor }; } return { skills: void 0, mcpServer: void 0 }; }, initGit: async () => { if (skip?.gitInit) return false; return p2.confirm({ message: "Initialize a new git repository?", initialValue: true }); } }, { onCancel: () => { p2.cancel("Operation cancelled."); process.exit(0); } } ); const { configureMastraToolingForAgents, observability, ...rest } = mastraProject; return { ...rest, observability: observability?.enabled, observabilityToken: observability?.token, observabilityOrgId: observability?.orgId, observabilityOrgName: observability?.orgName, skills: configureMastraToolingForAgents?.skills, mcpServer: configureMastraToolingForAgents?.mcpServer }; }; var checkForPkgJson = async () => { const cwd = process.cwd(); const pkgJsonPath = path.join(cwd, "package.json"); try { await fs4__default.access(pkgJsonPath); } catch { p2.log.error( 'No package.json file found in the current directory. Please run "npm init -y" to create one, or run "npx create-mastra@latest" to create a new Mastra project.' ); process.exit(1); } }; var readPackageName = async () => { try { const raw = await fs4__default.readFile(path.join(process.cwd(), "package.json"), "utf8"); const parsed = JSON.parse(raw); return typeof parsed.name === "string" && parsed.name.trim().length > 0 ? parsed.name : void 0; } catch { return void 0; } }; function generateAgentsMarkdown({ skills, mcpServer }) { const hasSkills = skills && skills.length > 0; const hasMcp = !!mcpServer; let content = `# AGENTS.md `; if (hasSkills) { content += ` ## CRITICAL: Load \`mastra\` skill first Load the \`mastra\` skill BEFORE any Mastra work. Never rely on cached knowledge \u2014 APIs change between versions. `; } content += ` ## Rules - Register all agents, tools, workflows, and scorers in \`src/mastra/index.ts\` - Use the \`dev\` and \`build\` scripts from \`package.json\` instead of running \`mastra dev\` / \`mastra build\` directly `; if (hasMcp) { const editorName = mcpServer === "cursor-global" ? "Cursor (global)" : mcpServer.charAt(0).toUpperCase() + mcpServer.slice(1); content += `## MCP Docs Server Mastra MCP Docs Server is configured for ${editorName}. Restart your editor to load it. `; } content += ` ## Resources - [Mastra Documentation](https://mastra.ai/llms.txt) - [Skills Discovery](https://mastra.ai/.well-known/skills/index.json) `; return content; } async function writeAgentsMarkdown(options) { const content = generateAgentsMarkdown(options); const filePath = path.join(process.cwd(), "AGENTS.md"); await fs4__default.writeFile(filePath, content); } async function writeClaudeMarkdown() { const filePath = path.join(process.cwd(), "CLAUDE.md"); await fs4__default.writeFile(filePath, "@AGENTS.md"); } // src/commands/utils.ts function getPackageManager() { const userAgent = process.env.npm_config_user_agent || ""; const execPath = process.env.npm_execpath || ""; if (userAgent.includes("bun")) { return "bun"; } if (userAgent.includes("yarn")) { return "yarn"; } if (userAgent.includes("pnpm")) { return "pnpm"; } if (userAgent.includes("npm")) { return "npm"; } if (execPath.includes("bun")) { return "bun"; } if (execPath.includes("yarn")) { return "yarn"; } if (execPath.includes("pnpm")) { return "pnpm"; } if (execPath.includes("npm")) { return "npm"; } return "npm"; } function wrapAction(fn) { return (...args) => { fn(...args).catch((err) => { console.error(`Error: ${err.message}`); process.exit(1); }); }; } function parseMcp(value) { if (!isValidEditor(value)) { throw new InvalidArgumentError(`Choose a valid value: ${EDITOR.join(", ")}`); } return value; } function parseSkills(value) { return value.split(",").map((s) => s.trim()).filter(Boolean); } function parseComponents(value) { const parsedValue = value.split(","); if (!areValidComponents(parsedValue)) { throw new InvalidArgumentError(`Choose valid components: ${COMPONENTS.join(", ")}`); } return parsedValue; } function parseLlmProvider(value) { if (!isValidLLMProvider(value)) { throw new InvalidArgumentError(`Choose a valid provider: ${LLMProvider.join(", ")}`); } return value; } function shouldSkipDotenvLoading() { return process.env.MASTRA_SKIP_DOTENV === "true" || process.env.MASTRA_SKIP_DOTENV === "1"; } async function getVersionTag() { try { const pkgPath = fileURLToPath(import.meta.resolve("mastra/package.json")); const json = await fsExtra3.readJSON(pkgPath); const currentVersion = json.version; const { stdout } = await execa("npm", ["dist-tag", "ls", "mastra"], { cwd: import.meta.dirname }); const tagLine = stdout.split("\n").find((distLine) => distLine.endsWith(`: ${currentVersion}`)); const tag = tagLine ? tagLine.split(":")[0]?.trim() : void 0; return tag; } catch { return void 0; } } async function isGitInitialized({ cwd }) { try { await execa("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "ignore" }); return true; } catch { return false; } } async function gitInit({ cwd }) { await execa("git", ["init"], { cwd, stdio: "ignore" }); await execa("git", ["add", "-A"], { cwd, stdio: "ignore" }); await execa( "git", [ "commit", "-m", '"Initial commit from Mastra"', '--author="dane-ai-mastra[bot] <dane-ai-mastra[bot]@users.noreply.github.com>"' ], { cwd, stdio: "ignore" } ); } // src/analytics/index.ts var ANALYTICS_CONFIG_PATH = path.join(os.homedir(), ".mastra", "analytics.json"); function bucketApiHost(apiUrl) { try { const { host, hostname } = new URL(apiUrl); if (hostname === "localhost" || hostname === "127.0.0.1") return "localhost"; if (host === "staging.mastra.cloud" || host.endsWith(".