mastra
Version:
cli for mastra
1,516 lines (1,472 loc) • 51.5 kB
JavaScript
import { getAnalytics } from './chunk-OQQFOUQW.js';
import * as p2 from '@clack/prompts';
import color2 from 'picocolors';
import fs from 'fs/promises';
import child_process from 'child_process';
import util from 'util';
import path3, { dirname } from 'path';
import shellQuote from 'shell-quote';
import yoctoSpinner from 'yocto-spinner';
import { PinoLogger } from '@mastra/loggers';
import * as fs4 from 'fs';
import fs4__default, { existsSync } from 'fs';
import { fileURLToPath } from 'url';
import { execa } from 'execa';
import fsExtra3, { readJSON, ensureFile, writeJSON } from 'fs-extra/esm';
import os from 'os';
import prettier from 'prettier';
// src/commands/utils.ts
function getPackageManager() {
const userAgent = process.env.npm_config_user_agent || "";
const execPath = process.env.npm_execpath || "";
if (userAgent.includes("yarn")) {
return "yarn";
}
if (userAgent.includes("pnpm")) {
return "pnpm";
}
if (userAgent.includes("npm")) {
return "npm";
}
if (execPath.includes("yarn")) {
return "yarn";
}
if (execPath.includes("pnpm")) {
return "pnpm";
}
if (execPath.includes("npm")) {
return "npm";
}
return "npm";
}
function getPackageManagerInstallCommand(pm) {
switch (pm) {
case "npm":
return "install";
case "yarn":
return "add";
case "pnpm":
return "add";
default:
return "install";
}
}
var logger = new PinoLogger({
name: "Mastra CLI",
level: "info"
});
// src/utils/clone-template.ts
var exec = util.promisify(child_process.exec);
async function cloneTemplate(options) {
const { template, projectName, targetDir } = options;
const projectPath = targetDir ? path3.resolve(targetDir, projectName) : path3.resolve(projectName);
const spinner5 = yoctoSpinner({ text: `Cloning template "${template.title}"...` }).start();
try {
if (await directoryExists(projectPath)) {
spinner5.error(`Directory ${projectName} already exists`);
throw new Error(`Directory ${projectName} already exists`);
}
await cloneRepositoryWithoutGit(template.githubUrl, projectPath);
await updatePackageJson(projectPath, projectName);
const envExamplePath = path3.join(projectPath, ".env.example");
if (await fileExists(envExamplePath)) {
await fs.copyFile(envExamplePath, path3.join(projectPath, ".env"));
}
spinner5.success(`Template "${template.title}" cloned successfully to ${projectName}`);
return projectPath;
} catch (error) {
spinner5.error(`Failed to clone template: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
async function directoryExists(dirPath) {
try {
const stat = await fs.stat(dirPath);
return stat.isDirectory();
} catch {
return false;
}
}
async function fileExists(filePath) {
try {
const stat = await fs.stat(filePath);
return stat.isFile();
} catch {
return false;
}
}
async function cloneRepositoryWithoutGit(repoUrl, targetPath) {
await fs.mkdir(targetPath, { recursive: true });
try {
const degitRepo = repoUrl.replace("https://github.com/", "");
const degitCommand = shellQuote.quote(["npx", "degit", degitRepo, targetPath]);
await exec(degitCommand, {
cwd: process.cwd()
});
} catch {
try {
const gitCommand = shellQuote.quote(["git", "clone", repoUrl, targetPath]);
await exec(gitCommand, {
cwd: process.cwd()
});
const gitDir = path3.join(targetPath, ".git");
if (await directoryExists(gitDir)) {
await fs.rm(gitDir, { recursive: true, force: true });
}
} catch (gitError) {
throw new Error(`Failed to clone repository: ${gitError instanceof Error ? gitError.message : "Unknown error"}`);
}
}
}
async function updatePackageJson(projectPath, projectName) {
const packageJsonPath = path3.join(projectPath, "package.json");
try {
const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
const packageJson = JSON.parse(packageJsonContent);
packageJson.name = projectName;
await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), "utf-8");
} catch (error) {
logger.warn(`Could not update package.json: ${error instanceof Error ? error.message : "Unknown error"}`);
}
}
async function installDependencies(projectPath, packageManager) {
const spinner5 = yoctoSpinner({ text: "Installing dependencies..." }).start();
try {
const pm = packageManager || getPackageManager();
const installCommand = shellQuote.quote([pm, "install"]);
await exec(installCommand, {
cwd: projectPath
});
spinner5.success("Dependencies installed successfully");
} catch (error) {
spinner5.error(`Failed to install dependencies: ${error instanceof Error ? error.message : "Unknown error"}`);
throw error;
}
}
var TEMPLATES_API_URL = process.env.MASTRA_TEMPLATES_API_URL || "https://mastra.ai/api/templates.json";
async function loadTemplates() {
try {
const response = await fetch(TEMPLATES_API_URL);
if (!response.ok) {
throw new Error(`Failed to fetch templates: ${response.statusText}`);
}
const templates = await response.json();
return templates;
} catch (error) {
console.error("Error loading templates:", error);
throw new Error("Failed to load templates. Please check your internet connection and try again.");
}
}
function pluralize(count, singular, plural) {
return count === 1 ? singular : plural || `${singular}s`;
}
async function selectTemplate(templates) {
const choices = templates.map((template) => {
const parts = [];
if (template.agents?.length) {
parts.push(`${template.agents.length} ${pluralize(template.agents.length, "agent")}`);
}
if (template.tools?.length) {
parts.push(`${template.tools.length} ${pluralize(template.tools.length, "tool")}`);
}
if (template.workflows?.length) {
parts.push(`${template.workflows.length} ${pluralize(template.workflows.length, "workflow")}`);
}
if (template.mcp?.length) {
parts.push(`${template.mcp.length} ${pluralize(template.mcp.length, "MCP server")}`);
}
if (template.networks?.length) {
parts.push(`${template.networks.length} ${pluralize(template.networks.length, "agent network")}`);
}
return {
value: template,
label: template.title,
hint: parts.join(", ") || "Template components"
};
});
const selected = await p2.select({
message: "Select a template:",
options: choices
});
if (p2.isCancel(selected)) {
return null;
}
return selected;
}
function findTemplateByName(templates, templateName) {
let template = templates.find((t) => t.slug === templateName);
if (template) return template;
const slugWithPrefix = `template-${templateName}`;
template = templates.find((t) => t.slug === slugWithPrefix);
if (template) return template;
template = templates.find((t) => t.title.toLowerCase() === templateName.toLowerCase());
if (template) return template;
return null;
}
function getDefaultProjectName(template) {
return template.slug.replace(/^template-/, "");
}
var DepsService = class {
packageManager;
constructor() {
this.packageManager = this.getPackageManager();
}
findLockFile(dir) {
const lockFiles = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lock"];
for (const file of lockFiles) {
if (fs4__default.existsSync(path3.join(dir, file))) {
return file;
}
}
const parentDir = path3.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":
return "bun";
default:
return "npm";
}
}
async installPackages(packages) {
let runCommand = this.packageManager;
if (this.packageManager === "npm") {
runCommand = `${this.packageManager} i`;
} else {
runCommand = `${this.packageManager} add`;
}
const packageList = packages.join(" ");
return execa(`${runCommand} ${packageList}`, {
all: true,
shell: true,
stdio: "inherit"
});
}
async checkDependencies(dependencies) {
try {
const packageJsonPath = path3.join(process.cwd(), "package.json");
try {
await fs.access(packageJsonPath);
} catch {
return "No package.json file found in the current directory";
}
const packageJson = JSON.parse(await fs.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 = path3.join(process.cwd(), "package.json");
const packageJson = await fs.readFile(packageJsonPath, "utf-8");
const pkg = JSON.parse(packageJson);
return pkg.name;
} catch (err) {
throw err;
}
}
async getPackageVersion() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const pkgJsonPath = path3.join(__dirname, "..", "package.json");
const content = await fsExtra3.readJSON(pkgJsonPath);
return content.version;
}
async addScriptsToPackageJson(scripts) {
const packageJson = JSON.parse(await fs.readFile("package.json", "utf-8"));
packageJson.scripts = {
...packageJson.scripts,
...scripts
};
await fs.writeFile("package.json", JSON.stringify(packageJson, null, 2));
}
};
var args = ["-y", "@mastra/mcp-docs-server"];
var createMcpConfig = (editor) => {
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) {
if (editor === "vscode") {
return {
...original,
servers: {
...original?.servers || {},
...createMcpConfig(editor).servers
}
};
}
return {
...original,
mcpServers: {
...original?.mcpServers || {},
...createMcpConfig(editor).mcpServers
}
};
}
async function writeMergedConfig(configPath, editor) {
const configExists = existsSync(configPath);
const config = makeConfig(configExists ? await readJSON(configPath) : {}, editor);
await ensureFile(configPath);
await writeJSON(configPath, config, {
spaces: 2
});
}
var windsurfGlobalMCPConfigPath = path3.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json");
var cursorGlobalMCPConfigPath = path3.join(os.homedir(), ".cursor", "mcp.json");
path3.join(process.cwd(), ".vscode", "mcp.json");
var vscodeGlobalMCPConfigPath = path3.join(
os.homedir(),
process.platform === "win32" ? path3.join("AppData", "Roaming", "Code", "User", "settings.json") : process.platform === "darwin" ? path3.join("Library", "Application Support", "Code", "User", "settings.json") : path3.join(".config", "Code", "User", "settings.json")
);
async function installMastraDocsMCPServer({ editor, directory }) {
if (editor === `cursor`) {
await writeMergedConfig(path3.join(directory, ".cursor", "mcp.json"), "cursor");
}
if (editor === `vscode`) {
await writeMergedConfig(path3.join(directory, ".vscode", "mcp.json"), "vscode");
}
if (editor === `cursor-global`) {
const alreadyInstalled = await globalMCPIsAlreadyInstalled(editor);
if (alreadyInstalled) {
return;
}
await writeMergedConfig(cursorGlobalMCPConfigPath, "cursor-global");
}
if (editor === `windsurf`) {
const alreadyInstalled = await globalMCPIsAlreadyInstalled(editor);
if (alreadyInstalled) {
return;
}
await writeMergedConfig(windsurfGlobalMCPConfigPath, editor);
}
}
async function globalMCPIsAlreadyInstalled(editor) {
let configPath = ``;
if (editor === "windsurf") {
configPath = windsurfGlobalMCPConfigPath;
} 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;
if (editor === "vscode") {
if (!configContents.servers) return false;
const hasMastraMCP2 = Object.values(configContents.servers).some(
(server) => server?.args?.find((arg) => arg?.includes(`@mastra/mcp-docs-server`))
);
return hasMastraMCP2;
}
if (!configContents?.mcpServers) return false;
const hasMastraMCP = Object.values(configContents.mcpServers).some(
(server) => server?.args?.find((arg) => arg?.includes(`@mastra/mcp-docs-server`))
);
return hasMastraMCP;
} catch {
return false;
}
}
// 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) => {
fs4.readFile(filePath, "utf8", (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
writeFile({ filePath, data }) {
return new Promise((resolve, reject) => {
fs4.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.log(`${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 __filename = fileURLToPath(import.meta.url);
const __dirname = path3.dirname(__filename);
const filePath = path3.resolve(__dirname, "starter-files", inputFile);
const fileString = fs4__default.readFileSync(filePath, "utf8");
if (fs4__default.existsSync(outputFilePath) && !replaceIfExists) {
console.log(`${outputFilePath} already exists`);
return false;
}
await fsExtra3.outputFile(outputFilePath, fileString);
return true;
}
async setupEnvFile({ dbUrl }) {
const envPath = path3.join(process.cwd(), ".env.development");
await fsExtra3.ensureFile(envPath);
const fileEnvService = new FileEnvService(envPath);
await fileEnvService.setEnvValue("DB_URL", dbUrl);
}
getFirstExistingFile(files) {
for (const f of files) {
if (fs4__default.existsSync(f)) {
return f;
}
}
throw new Error("Missing required file, checked the following paths: " + files.join(", "));
}
replaceValuesInFile({
filePath,
replacements
}) {
let fileContent = fs4__default.readFileSync(filePath, "utf8");
replacements.forEach(({ search, replace }) => {
fileContent = fileContent.replaceAll(search, replace);
});
fs4__default.writeFileSync(filePath, fileContent);
}
};
// src/commands/init/utils.ts
var exec2 = util.promisify(child_process.exec);
var getAISDKPackageVersion = (llmProvider) => {
switch (llmProvider) {
case "cerebras":
return "^0.2.14";
default:
return "^1.0.0";
}
};
var getAISDKPackage = (llmProvider) => {
switch (llmProvider) {
case "openai":
return "@ai-sdk/openai";
case "anthropic":
return "@ai-sdk/anthropic";
case "groq":
return "@ai-sdk/groq";
case "google":
return "@ai-sdk/google";
case "cerebras":
return "@ai-sdk/cerebras";
default:
return "@ai-sdk/openai";
}
};
var getProviderImportAndModelItem = (llmProvider) => {
let providerImport = "";
let modelItem = "";
if (llmProvider === "openai") {
providerImport = `import { openai } from '${getAISDKPackage(llmProvider)}';`;
modelItem = `openai('gpt-4o-mini')`;
} else if (llmProvider === "anthropic") {
providerImport = `import { anthropic } from '${getAISDKPackage(llmProvider)}';`;
modelItem = `anthropic('claude-3-5-sonnet-20241022')`;
} else if (llmProvider === "groq") {
providerImport = `import { groq } from '${getAISDKPackage(llmProvider)}';`;
modelItem = `groq('llama-3.3-70b-versatile')`;
} else if (llmProvider === "google") {
providerImport = `import { google } from '${getAISDKPackage(llmProvider)}';`;
modelItem = `google('gemini-2.5-pro')`;
} else if (llmProvider === "cerebras") {
providerImport = `import { cerebras } from '${getAISDKPackage(llmProvider)}';`;
modelItem = `cerebras('llama-3.3-70b')`;
}
return { providerImport, modelItem };
};
async function writeAgentSample(llmProvider, destPath, addExampleTool) {
const { providerImport, modelItem } = getProviderImportAndModelItem(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 ? "Use the weatherTool to fetch current weather data." : ""}
`;
const content = `
${providerImport}
import { Agent } from '@mastra/core/agent';
import { Memory } from '@mastra/memory';
import { LibSQLStore } from '@mastra/libsql';
${addExampleTool ? `import { weatherTool } from '../tools/weather-tool';` : ""}
export const weatherAgent = new Agent({
name: 'Weather Agent',
instructions: \`${instructions}\`,
model: ${modelItem},
${addExampleTool ? "tools: { weatherTool }," : ""}
memory: new Memory({
storage: new LibSQLStore({
url: "file:../mastra.db", // path is relative to the .mastra/output directory
})
})
});
`;
const formattedContent = await prettier.format(content, {
parser: "typescript",
singleQuote: true
});
await fs.writeFile(destPath, "");
await fs.writeFile(destPath, formattedContent);
}
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 };`;
const formattedContent = await prettier.format(content, {
parser: "typescript",
semi: true,
singleQuote: true
});
await fs.writeFile(destPath, formattedContent);
}
async function writeToolSample(destPath) {
const fileService = new FileService();
await fileService.copyStarterFile("tools.ts", destPath);
}
async function writeCodeSampleForComponents(llmprovider, component, destPath, importComponents) {
switch (component) {
case "agents":
return writeAgentSample(llmprovider, destPath, importComponents.includes("tools"));
case "tools":
return writeToolSample(destPath);
case "workflows":
return writeWorkflowSample(destPath);
default:
return "";
}
}
var createComponentsDir = async (dirPath, component) => {
const componentPath = dirPath + `/${component}`;
await fsExtra3.ensureDir(componentPath);
};
var writeIndexFile = async ({
dirPath,
addAgent,
addExample,
addWorkflow
}) => {
const indexPath = dirPath + "/index.ts";
const destPath = path3.join(indexPath);
try {
await fs.writeFile(destPath, "");
const filteredExports = [
addWorkflow ? `workflows: { weatherWorkflow },` : "",
addAgent ? `agents: { weatherAgent },` : ""
].filter(Boolean);
if (!addExample) {
await fs.writeFile(
destPath,
`
import { Mastra } from '@mastra/core';
export const mastra = new Mastra()
`
);
return;
}
await fs.writeFile(
destPath,
`
import { Mastra } from '@mastra/core/mastra';
import { PinoLogger } from '@mastra/loggers';
import { LibSQLStore } from '@mastra/libsql';
${addWorkflow ? `import { weatherWorkflow } from './workflows/weather-workflow';` : ""}
${addAgent ? `import { weatherAgent } from './agents/weather-agent';` : ""}
export const mastra = new Mastra({
${filteredExports.join("\n ")}
storage: new LibSQLStore({
// stores telemetry, evals, ... into memory storage, if it needs to persist, change to file:../mastra.db
url: ":memory:",
}),
logger: new PinoLogger({
name: 'Mastra',
level: 'info',
}),
});
`
);
} catch (err) {
throw err;
}
};
var checkAndInstallCoreDeps = async (addExample) => {
const depsService = new DepsService();
let depCheck = await depsService.checkDependencies(["@mastra/core"]);
if (depCheck !== "ok") {
await installCoreDeps("@mastra/core");
}
if (addExample) {
depCheck = await depsService.checkDependencies(["@mastra/libsql"]);
if (depCheck !== "ok") {
await installCoreDeps("@mastra/libsql");
}
}
};
var spinner = yoctoSpinner({ text: "Installing Mastra core dependencies\n" });
async function installCoreDeps(pkg) {
try {
const confirm2 = await p2.confirm({
message: `You do not have the ${pkg} package installed. Would you like to install it?`,
initialValue: false
});
if (p2.isCancel(confirm2)) {
p2.cancel("Installation Cancelled");
process.exit(0);
}
if (!confirm2) {
p2.cancel("Installation Cancelled");
process.exit(0);
}
spinner.start();
const depsService = new DepsService();
await depsService.installPackages([`${pkg}@latest`]);
spinner.success("@mastra/core installed successfully");
} catch (err) {
console.error(err);
}
}
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;
default:
return key;
}
};
var writeAPIKey = async ({
provider,
apiKey = "your-api-key"
}) => {
const key = await getAPIKey(provider);
const escapedKey = shellQuote.quote([key]);
const escapedApiKey = shellQuote.quote([apiKey]);
await exec2(`echo ${escapedKey}=${escapedApiKey} >> .env`);
};
var createMastraDir = async (directory) => {
let dir = directory.trim().split("/").filter((item) => item !== "");
const dirPath = path3.join(process.cwd(), ...dir, "mastra");
try {
await fs.access(dirPath);
return { ok: false };
} catch {
await fsExtra3.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 interactivePrompt = async () => {
p2.intro(color2.inverse(" Mastra Init "));
const mastraProject = await p2.group(
{
directory: () => p2.text({
message: "Where should we create the Mastra files? (default: src/)",
placeholder: "src/",
defaultValue: "src/"
}),
llmProvider: () => p2.select({
message: "Select default provider:",
options: [
{ value: "openai", label: "OpenAI", hint: "recommended" },
{ value: "anthropic", label: "Anthropic" },
{ value: "groq", label: "Groq" },
{ value: "google", label: "Google" },
{ value: "cerebras", label: "Cerebras" }
]
}),
llmApiKey: async ({ results: { llmProvider } }) => {
const keyChoice = await p2.select({
message: `Enter your ${llmProvider} API key?`,
options: [
{ value: "skip", label: "Skip for now", hint: "default" },
{ value: "enter", label: "Enter API key" }
],
initialValue: "skip"
});
if (keyChoice === "enter") {
return p2.text({
message: "Enter your API key:",
placeholder: "sk-..."
});
}
return void 0;
},
configureEditorWithDocsMCP: async () => {
const windsurfIsAlreadyInstalled = await globalMCPIsAlreadyInstalled(`windsurf`);
const cursorIsAlreadyInstalled = await globalMCPIsAlreadyInstalled(`cursor`);
const vscodeIsAlreadyInstalled = await globalMCPIsAlreadyInstalled(`vscode`);
const editor = await p2.select({
message: `Make your AI IDE into a Mastra expert? (installs Mastra docs MCP server)`,
options: [
{ value: "skip", label: "Skip for now", hint: "default" },
{
value: "cursor",
label: "Cursor (project only)",
hint: cursorIsAlreadyInstalled ? `Already installed globally` : void 0
},
{
value: "cursor-global",
label: "Cursor (global, all projects)",
hint: cursorIsAlreadyInstalled ? `Already installed` : void 0
},
{
value: "windsurf",
label: "Windsurf",
hint: windsurfIsAlreadyInstalled ? `Already installed` : void 0
},
{
value: "vscode",
label: "VSCode",
hint: vscodeIsAlreadyInstalled ? `Already installed` : void 0
}
]
});
if (editor === `skip`) return void 0;
if (editor === `windsurf` && windsurfIsAlreadyInstalled) {
p2.log.message(`
Windsurf is already installed, skipping.`);
return void 0;
}
if (editor === `vscode` && vscodeIsAlreadyInstalled) {
p2.log.message(`
VSCode is already installed, skipping.`);
return 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: "skip", label: "No, skip for now" }
]
});
if (confirm2 !== `yes`) {
return 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: "skip", label: "No, skip for now" }
]
});
if (confirm2 !== `yes`) {
return void 0;
}
}
return editor;
}
},
{
onCancel: () => {
p2.cancel("Operation cancelled.");
process.exit(0);
}
}
);
return mastraProject;
};
var checkPkgJson = async () => {
const cwd = process.cwd();
const pkgJsonPath = path3.join(cwd, "package.json");
let isPkgJsonPresent = false;
try {
await fsExtra3.readJSON(pkgJsonPath);
isPkgJsonPresent = true;
} catch {
isPkgJsonPresent = false;
}
if (isPkgJsonPresent) {
return;
}
logger.debug('package.json not found, create one or run "mastra create" to create a new project');
process.exit(0);
};
// src/commands/init/init.ts
var s = p2.spinner();
var exec3 = util.promisify(child_process.exec);
var init = async ({
directory,
addExample = false,
components,
llmProvider = "openai",
llmApiKey,
configureEditorWithDocsMCP
}) => {
s.start("Initializing Mastra");
try {
const result = await createMastraDir(directory);
if (!result.ok) {
s.stop(color2.inverse(" Mastra already initialized "));
return { success: false };
}
const dirPath = result.dirPath;
await Promise.all([
writeIndexFile({
dirPath,
addExample,
addWorkflow: components.includes("workflows"),
addAgent: components.includes("agents")
}),
...components.map((component) => createComponentsDir(dirPath, component)),
writeAPIKey({ provider: llmProvider, apiKey: llmApiKey })
]);
if (addExample) {
await Promise.all([
...components.map(
(component) => writeCodeSample(dirPath, component, llmProvider, components)
)
]);
const depService = new DepsService();
const needsLibsql = await depService.checkDependencies(["@mastra/libsql"]) !== `ok`;
if (needsLibsql) {
await depService.installPackages(["@mastra/libsql"]);
}
const needsMemory = components.includes(`agents`) && await depService.checkDependencies(["@mastra/memory"]) !== `ok`;
if (needsMemory) {
await depService.installPackages(["@mastra/memory"]);
}
const needsLoggers = await depService.checkDependencies(["@mastra/loggers"]) !== `ok`;
if (needsLoggers) {
await depService.installPackages(["@mastra/loggers"]);
}
}
const key = await getAPIKey(llmProvider || "openai");
const aiSdkPackage = getAISDKPackage(llmProvider);
const aiSdkPackageVersion = getAISDKPackageVersion(llmProvider);
const depsService = new DepsService();
const pm = depsService.packageManager;
const installCommand = getPackageManagerInstallCommand(pm);
await exec3(`${pm} ${installCommand} ${aiSdkPackage}@${aiSdkPackageVersion}`);
if (configureEditorWithDocsMCP) {
await installMastraDocsMCPServer({
editor: configureEditorWithDocsMCP,
directory: process.cwd()
});
}
s.stop();
if (!llmApiKey) {
p2.note(`
${color2.green("Mastra initialized successfully!")}
Add your ${color2.cyan(key)} as an environment variable
in your ${color2.cyan(".env")} file
`);
} else {
p2.note(`
${color2.green("Mastra initialized successfully!")}
`);
}
return { success: true };
} catch (err) {
s.stop(color2.inverse("An error occurred while initializing Mastra"));
console.error(err);
return { success: false };
}
};
var exec4 = util.promisify(child_process.exec);
var execWithTimeout = async (command, timeoutMs) => {
try {
const promise = exec4(command, { killSignal: "SIGTERM" });
if (!timeoutMs) {
return await promise;
}
let timeoutId;
const timeout = new Promise((_, reject) => {
timeoutId = setTimeout(() => reject(new Error("Command timed out")), timeoutMs);
});
try {
const result = await Promise.race([promise, timeout]);
clearTimeout(timeoutId);
return result;
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.message === "Command timed out") {
throw new Error("Something went wrong during installation, please try again.");
}
throw error;
}
} catch (error) {
throw error;
}
};
async function installMastraDependency(pm, dependency, versionTag, isDev, timeout) {
let installCommand = getPackageManagerInstallCommand(pm);
if (isDev) {
installCommand = `${installCommand} --save-dev`;
}
try {
await execWithTimeout(`${pm} ${installCommand} ${dependency}${versionTag}`, timeout);
} catch (err) {
if (versionTag === "@latest") {
throw new Error(
`Failed to install ${dependency}@latest: ${err instanceof Error ? err.message : "Unknown error"}`
);
}
try {
await execWithTimeout(`${pm} ${installCommand} ${dependency}@latest`, timeout);
} catch (fallbackErr) {
throw new Error(
`Failed to install ${dependency} (tried ${versionTag} and @latest): ${fallbackErr instanceof Error ? fallbackErr.message : "Unknown error"}`
);
}
}
}
var createMastraProject = async ({
projectName: name,
createVersionTag,
timeout
}) => {
p2.intro(color2.inverse(" Mastra Create "));
const projectName = name ?? await p2.text({
message: "What do you want to name your project?",
placeholder: "my-mastra-app",
defaultValue: "my-mastra-app"
});
if (p2.isCancel(projectName)) {
p2.cancel("Operation cancelled");
process.exit(0);
}
const s2 = p2.spinner();
try {
s2.start("Creating project");
try {
await fs.mkdir(projectName);
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "EEXIST") {
s2.stop(`A directory named "${projectName}" already exists. Please choose a different name.`);
process.exit(1);
}
throw new Error(
`Failed to create project directory: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
process.chdir(projectName);
const pm = getPackageManager();
const installCommand = getPackageManagerInstallCommand(pm);
s2.message("Initializing project structure");
try {
await exec4(`npm init -y`);
await exec4(`npm pkg set type="module"`);
await exec4(`npm pkg set engines.node=">=20.9.0"`);
const depsService = new DepsService();
await depsService.addScriptsToPackageJson({
dev: "mastra dev",
build: "mastra build",
start: "mastra start"
});
} catch (error) {
throw new Error(
`Failed to initialize project structure: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
s2.stop("Project structure created");
s2.start(`Installing ${pm} dependencies`);
try {
await exec4(`${pm} ${installCommand} zod@^3`);
await exec4(`${pm} ${installCommand} typescript @types/node --save-dev`);
await exec4(`echo '{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"noEmit": true,
"outDir": "dist"
},
"include": [
"src/**/*"
]
}' > tsconfig.json`);
} catch (error) {
throw new Error(
`Failed to install basic dependencies: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
s2.stop(`${pm} dependencies installed`);
s2.start("Installing mastra");
const versionTag = createVersionTag ? `@${createVersionTag}` : "@latest";
try {
await installMastraDependency(pm, "mastra", versionTag, true, timeout);
} catch (error) {
throw new Error(`Failed to install Mastra CLI: ${error instanceof Error ? error.message : "Unknown error"}`);
}
s2.stop("mastra installed");
s2.start("Installing dependencies");
try {
await installMastraDependency(pm, "@mastra/core", versionTag, false, timeout);
await installMastraDependency(pm, "@mastra/libsql", versionTag, false, timeout);
await installMastraDependency(pm, "@mastra/memory", versionTag, false, timeout);
} catch (error) {
throw new Error(
`Failed to install Mastra dependencies: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
s2.stop("Mastra dependencies installed");
s2.start("Adding .gitignore");
try {
await exec4(`echo output.txt >> .gitignore`);
await exec4(`echo node_modules >> .gitignore`);
await exec4(`echo dist >> .gitignore`);
await exec4(`echo .mastra >> .gitignore`);
await exec4(`echo .env.development >> .gitignore`);
await exec4(`echo .env >> .gitignore`);
await exec4(`echo *.db >> .gitignore`);
await exec4(`echo *.db-* >> .gitignore`);
} catch (error) {
throw new Error(`Failed to create .gitignore: ${error instanceof Error ? error.message : "Unknown error"}`);
}
s2.stop(".gitignore added");
p2.outro("Project created successfully");
console.log("");
return { projectName };
} catch (error) {
s2.stop();
const errorMessage = error instanceof Error ? error.message : "An unexpected error occurred";
p2.cancel(`Project creation failed: ${errorMessage}`);
process.exit(1);
}
};
// src/commands/create/create.ts
var create = async (args2) => {
if (args2.template !== void 0) {
await createFromTemplate(args2);
return;
}
const { projectName } = await createMastraProject({
projectName: args2?.projectName,
createVersionTag: args2?.createVersionTag,
timeout: args2?.timeout
});
const directory = args2.directory || "src/";
if (args2.components === void 0 || args2.llmProvider === void 0 || args2.addExample === void 0) {
const result = await interactivePrompt();
const analytics2 = getAnalytics();
if (analytics2 && result?.llmProvider) {
analytics2.trackEvent("cli_model_provider_selected", {
provider: result.llmProvider,
selection_method: "interactive"
});
}
await init({
...result,
llmApiKey: result?.llmApiKey,
components: ["agents", "tools", "workflows"],
addExample: true
});
postCreate({ projectName });
return;
}
const { components = [], llmProvider = "openai", addExample = false, llmApiKey } = args2;
const analytics = getAnalytics();
if (analytics) {
analytics.trackEvent("cli_model_provider_selected", {
provider: llmProvider,
selection_method: "cli_args"
});
}
await init({
directory,
components,
llmProvider,
addExample,
llmApiKey,
configureEditorWithDocsMCP: args2.mcpServer
});
postCreate({ projectName });
};
var postCreate = ({ projectName }) => {
const packageManager = getPackageManager();
p2.outro(`
${color2.green("To start your project:")}
${color2.cyan("cd")} ${projectName}
${color2.cyan(`${packageManager} run dev`)}
`);
};
function isGitHubUrl(url) {
try {
const parsedUrl = new URL(url);
return parsedUrl.hostname === "github.com" && parsedUrl.pathname.split("/").length >= 3;
} catch {
return false;
}
}
async function validateGitHubProject(githubUrl) {
const errors = [];
try {
const urlParts = new URL(githubUrl).pathname.split("/").filter(Boolean);
const owner = urlParts[0];
const repo = urlParts[1]?.replace(".git", "");
if (!owner || !repo) {
throw new Error("Invalid GitHub URL format");
}
const branches = ["main", "master"];
let packageJsonContent = null;
let indexContent = null;
for (const branch of branches) {
try {
const packageJsonUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/package.json`;
const packageJsonResponse = await fetch(packageJsonUrl);
if (packageJsonResponse.ok) {
packageJsonContent = await packageJsonResponse.text();
const indexUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/src/mastra/index.ts`;
const indexResponse = await fetch(indexUrl);
if (indexResponse.ok) {
indexContent = await indexResponse.text();
}
break;
}
} catch {
}
}
if (!packageJsonContent) {
errors.push("Could not fetch package.json from repository");
return { isValid: false, errors };
}
try {
const packageJson = JSON.parse(packageJsonContent);
const hasMastraCore = packageJson.dependencies?.["@mastra/core"] || packageJson.devDependencies?.["@mastra/core"] || packageJson.peerDependencies?.["@mastra/core"];
if (!hasMastraCore) {
errors.push("Missing @mastra/core dependency in package.json");
}
} catch {
errors.push("Invalid package.json format");
}
if (!indexContent) {
errors.push("Missing src/mastra/index.ts file");
} else {
const hasMastraExport = indexContent.includes("export") && (indexContent.includes("new Mastra") || indexContent.includes("Mastra("));
if (!hasMastraExport) {
errors.push("src/mastra/index.ts does not export a Mastra instance");
}
}
return { isValid: errors.length === 0, errors };
} catch (error) {
errors.push(`Failed to validate GitHub repository: ${error instanceof Error ? error.message : "Unknown error"}`);
return { isValid: false, errors };
}
}
async function createFromGitHubUrl(url) {
const urlParts = new URL(url).pathname.split("/").filter(Boolean);
const owner = urlParts[0] || "unknown";
const repo = urlParts[1] || "unknown";
return {
githubUrl: url,
title: `${owner}/${repo}`,
slug: repo,
agents: [],
mcp: [],
tools: [],
networks: [],
workflows: []
};
}
async function createFromTemplate(args2) {
let selectedTemplate;
if (args2.template === true) {
const templates = await loadTemplates();
const selected = await selectTemplate(templates);
if (!selected) {
p2.log.info("No template selected. Exiting.");
return;
}
selectedTemplate = selected;
} else if (args2.template && typeof args2.template === "string") {
if (isGitHubUrl(args2.template)) {
const spinner5 = p2.spinner();
spinner5.start("Validating GitHub repository...");
const validation = await validateGitHubProject(args2.template);
if (!validation.isValid) {
spinner5.stop("Validation failed");
p2.log.error("This does not appear to be a valid Mastra project:");
validation.errors.forEach((error) => p2.log.error(` - ${error}`));
throw new Error("Invalid Mastra project");
}
spinner5.stop("Valid Mastra project \u2713");
selectedTemplate = await createFromGitHubUrl(args2.template);
} else {
const templates = await loadTemplates();
const found = findTemplateByName(templates, args2.template);
if (!found) {
p2.log.error(`Template "${args2.template}" not found. Available templates:`);
templates.forEach((t) => p2.log.info(` - ${t.title} (use: ${t.slug.replace("template-", "")})`));
throw ne