mastra
Version:
cli for mastra
1,433 lines (1,363 loc) • 54.8 kB
JavaScript
import { loadCredentials, getToken } from './chunk-EXG3XKBK.js';
import { randomUUID } from 'crypto';
import * as fs3 from 'fs';
import fs3__default, { existsSync, readFileSync, writeFileSync } from 'fs';
import os from 'os';
import path from 'path';
import { fileURLToPath } from 'url';
import { PostHog } from 'posthog-node';
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 fs4 from 'fs/promises';
import util from 'util';
import * as p 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__default.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);
const packageList = packages.join(" ");
return execa(`${pm} ${installCommand} ${packageList}`, {
all: true,
shell: true,
stdio: "pipe"
});
}
async checkDependencies(dependencies) {
try {
const packageJsonPath = path.join(process.cwd(), "package.json");
try {
await fs4.access(packageJsonPath);
} catch {
return "No package.json file found in the current directory";
}
const packageJson = JSON.parse(await fs4.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.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.readFile("package.json", "utf-8"));
packageJson.scripts = {
...packageJson.scripts,
...scripts
};
await fs4.writeFile("package.json", JSON.stringify(packageJson, null, 2));
}
};
// src/services/service.env.ts
var EnvService = class {
};
// src/services/service.fileEnv.ts
var FileEnvService = class extends EnvService {
filePath;
constructor(filePath) {
super();
this.filePath = filePath;
}
readFile(filePath) {
return new Promise((resolve, reject) => {
fs3.readFile(filePath, "utf8", (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
writeFile({ filePath, data }) {
return new Promise((resolve, reject) => {
fs3.writeFile(filePath, data, "utf8", (err) => {
if (err) reject(err);
else resolve();
});
});
}
async updateEnvData({
key,
value,
filePath = this.filePath,
data
}) {
const regex = new RegExp(`^${key}=.*$`, "m");
if (data.match(regex)) {
data = data.replace(regex, `${key}=${value}`);
} else {
data += `
${key}=${value}`;
}
await this.writeFile({ filePath, data });
console.info(`${key} set to ${value} in ENV file.`);
return data;
}
async getEnvValue(key) {
try {
const data = await this.readFile(this.filePath);
const regex = new RegExp(`^${key}=(.*)$`, "m");
const match = data.match(regex);
return match?.[1] || null;
} catch (err) {
console.error(`Error reading ENV value: ${err}`);
return null;
}
}
async setEnvValue(key, value) {
try {
const data = await this.readFile(this.filePath);
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 __filename2 = fileURLToPath(import.meta.url);
const __dirname2 = path.dirname(__filename2);
const filePath = path.resolve(__dirname2, "starter-files", inputFile);
const fileString = fs3__default.readFileSync(filePath, "utf8");
if (fs3__default.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__default.existsSync(f)) {
return f;
}
}
throw new Error("Missing required file, checked the following paths: " + files.join(", "));
}
replaceValuesInFile({
filePath,
replacements
}) {
let fileContent = fs3__default.readFileSync(filePath, "utf8");
replacements.forEach(({ search, replace }) => {
fileContent = fileContent.replaceAll(search, replace);
});
fs3__default.writeFileSync(filePath, fileContent);
}
};
// 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 p.select({
message: "Enable Mastra Observability? (will open auth flow)",
options: [
{ value: "yes", label: "Yes" },
{ value: "no", label: "No" }
],
initialValue: "yes"
});
if (p.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) p.log.info(`Logged in as ${creds.user.email}`);
}
return { enabled: true, token };
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
p.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.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}¤t=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.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.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.writeFile(destPath, "");
const filteredExports = [
addWorkflow ? `workflows: { weatherWorkflow },` : "",
addAgent ? `agents: { weatherAgent },` : "",
addScorers ? `scorers: { toolCallAppropriatenessScorer, completenessScorer, translationScorer },` : ""
].filter(Boolean);
if (!addExample) {
await fs4.writeFile(
destPath,
`
import { Mastra } from '@mastra/core/mastra';
export const mastra = new Mastra()
`
);
return;
}
await fs4.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",
url: "file:./mastra.db",
}),
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_GENERATIVE_AI_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.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.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) {
p.intro(color.inverse(" Mastra Init "));
}
const mastraProject = await p.group(
{
directory: () => skip?.directory ? void 0 : p.text({
message: "Where should we create the Mastra files? (default: src/)",
placeholder: "src/",
defaultValue: "src/"
}),
llmProvider: () => skip?.llmProvider ? void 0 : p.select({
message: "Select a default provider:",
options: LLM_PROVIDERS
}),
llmApiKey: async ({ results: { llmProvider } }) => {
if (skip?.llmApiKey) return void 0;
const llmName = LLM_PROVIDERS.find((p2) => p2.value === llmProvider)?.label || "provider";
const keyChoice = await p.select({
message: `Enter your ${llmName} API key?`,
options: [
{ value: "skip", label: "Skip for now", hint: "default" },
{ value: "enter", label: "Enter API key" }
],
initialValue: "skip"
});
if (keyChoice === "enter") {
return p.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 p.select({
message: `Configure Mastra tooling for agents?`,
options: [
{ value: "skills", label: "Skills", hint: "recommended" },
{ value: "mcp", label: "MCP Docs Server" }
],
initialValue: "skills"
});
if (p.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 p.select({
message: `Select your agent:`,
options: [...POPULAR_AGENTS, { value: "__show_all__", label: "+ Show all agents" }],
initialValue: "universal"
});
if (p.isCancel(initialSelection)) {
return { skills: void 0, mcpServer: void 0 };
}
let selectedAgents = /* @__PURE__ */ new Set();
if (initialSelection === "__show_all__") {
const followUpSelection = await p.select({
message: `Select your agent:`,
options: ALL_AGENTS
});
if (p.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 p.select({
message: `Which editor?`,
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 (p.isCancel(editor)) {
return { skills: void 0, mcpServer: void 0 };
}
if (editor === `cursor`) {
p.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 p.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 p.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 p.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 p.confirm({
message: "Initialize a new git repository?",
initialValue: true
});
}
},
{
onCancel: () => {
p.cancel("Operation cancelled.");
process.exit(0);
}
}
);
const { configureMastraToolingForAgents, observability, ...rest } = mastraProject;
return {
...rest,
observability: observability?.enabled,
observabilityToken: observability?.token,
skills: configureMastraToolingForAgents?.skills,
mcpServer: configureMastraToolingForAgents?.mcpServer
};
};
var checkForPkgJson = async () => {
const cwd = process.cwd();
const pkgJsonPath = path.join(cwd, "package.json");
try {
await fs4.access(pkgJsonPath);
} catch {
p.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.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
You are a TypeScript developer experienced with the Mastra framework. You build AI agents, tools, workflows, and scorers. You follow strict TypeScript practices and always consult up-to-date Mastra documentation before making changes.
`;
if (hasSkills) {
content += `
## CRITICAL: Load \`mastra\` skill
**BEFORE doing ANYTHING with Mastra, load the \`mastra\` skill FIRST.** Never rely on cached knowledge as Mastra's APIs change frequently between versions. Use the skill to read up-to-date documentation from \`node_modules\`.
`;
}
content += `
## Project Overview
This is a **Mastra** project written in TypeScript. Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack. The Node.js runtime is \`>=22.13.0\`.
## Commands
\`\`\`bash
npm run dev # Start Mastra Studio at localhost:4111 (long-running, use a separate terminal)
npm run build # Build a production-ready server (use a separate terminal)
\`\`\`
## Project Structure
| Folder | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| \`src/mastra\` | Entry point for all Mastra-related code and configuration. |
| \`src/mastra/agents\` | Define and configure your agents - their behavior, goals, and tools. |
| \`src/mastra/workflows\` | Define multi-step workflows that orchestrate agents and tools together. |
| \`src/mastra/tools\` | Create reusable tools that your agents can call |
| \`src/mastra/mcp\` | (Optional) Implement custom MCP servers to share your tools with external agents |
| \`src/mastra/scorers\` | (Optional) Define scorers for evaluating agent performance over time |
| \`src/mastra/public\` | (Optional) Contents are copied into the \`.build/output\` directory during the build process, making them available for serving at runtime |
### Top-level files
Top-level files define how your Mastra project is configured, built, and connected to its environment.
| File | Description |
| --------------------- | ----------------------------------------------------------------------------------------------------------------- |
| \`src/mastra/index.ts\` | Central entry point where you configure and initialize Mastra. |
| \`.env.example\` | Template for environment variables - copy and rename to \`.env\` to add your secret [model provider](/models) keys. |
| \`package.json\` | Defines project metadata, dependencies, and available npm scripts. |
| \`tsconfig.json\` | Configures TypeScript options such as path aliases, compiler settings, and build output. |
## Boundaries
### Always do
- Load the \`mastra\` skill before any Mastra-related work
- Register new agents, tools, workflows, and scorers in \`src/mastra/index.ts\`
- Use schemas for tool inputs and outputs
- Run \`npm run build\` to verify changes compile
### Never do
- Never commit \`.env\` files or secrets
- Never modify \`node_modules\` or Mastra's database files directly
- Never hardcode API keys (always use environment variables)
`;
if (hasMcp) {
const editorName = mcpServer === "cursor-global" ? "Cursor (global)" : mcpServer.charAt(0).toUpperCase() + mcpServer.slice(1);
content += `## MCP Docs Server
This project has the Mastra MCP Docs Server configured for ${editorName}.
### Using MCP Docs
The MCP server provides embedded documentation access within your editor:
1. The server was automatically configured during project creation
2. Restart your editor to load the MCP server
3. Use the Mastra docs tools in your editor to access:
- API references
- Code examples
- Integration guides
Learn more in the [MCP Documentation](https://mastra.ai/docs/mcp/overview).
`;
}
content += `## Resources
- [Mastra Documentation](https://mastra.ai/llms.txt)
- [Mastra .well-known 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.writeFile(filePath, content);
}
async function writeClaudeMarkdown() {
const filePath = path.join(process.cwd(), "CLAUDE.md");
await fs4.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 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 __filename$1 = fileURLToPath(import.meta.url);
var __dirname$1 = path.dirname(__filename$1);
var analyticsInstance = null;
function getAnalytics() {
return analyticsInstance;
}
function setAnalytics(instance) {
analyticsInstance = instance;
}
var PosthogAnalytics = class {
sessionId;
client;
distinctId;
version;
packageManager;
constructor({
version,
apiKey,
host = "https://app.posthog.com"
}) {
this.version = version;
this.packageManager = getPackageManager();
const cliConfigPath = path.join(__dirname$1, "mastra-cli.json");
if (existsSync(cliConfigPath)) {
try {