mastra
Version:
cli for mastra
1,067 lines (1,019 loc) • 33 kB
JavaScript
import * as p from '@clack/prompts';
import color2 from 'picocolors';
import child_process from 'node:child_process';
import util from 'node:util';
import * as fs3 from 'fs';
import fs3__default, { existsSync } from 'fs';
import fs4 from 'fs/promises';
import path, { dirname } from 'path';
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';
import yoctoSpinner from 'yocto-spinner';
import { createLogger } from '@mastra/core/logger';
// src/commands/create/create.ts
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 (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":
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 = 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 getPackageVersion() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const pkgJsonPath = path.join(__dirname, "..", "package.json");
const content = await fsExtra3.readJSON(pkgJsonPath);
return content.version;
}
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/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 args = ["-y", "@mastra/mcp-docs-server@latest"];
var mcpConfig = {
mcpServers: {
mastra: process.platform === `win32` ? {
command: "cmd",
args: ["/c", "npx", ...args]
} : {
command: "npx",
args
}
}
};
function makeConfig(original) {
return {
...original,
mcpServers: {
...original?.mcpServers || {},
...mcpConfig.mcpServers
}
};
}
async function writeMergedConfig(configPath) {
const configExists = existsSync(configPath);
const config = makeConfig(configExists ? await readJSON(configPath) : {});
await ensureFile(configPath);
await writeJSON(configPath, config, {
spaces: 2
});
}
var windsurfGlobalMCPConfigPath = path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json");
async function installMastraDocsMCPServer({
editor,
directory
}) {
if (editor === `cursor`) await writeMergedConfig(path.join(directory, ".cursor", "mcp.json"));
const windsurfIsInstalled = await globalWindsurfMCPIsAlreadyInstalled();
if (editor === `windsurf` && !windsurfIsInstalled) await writeMergedConfig(windsurfGlobalMCPConfigPath);
}
async function globalWindsurfMCPIsAlreadyInstalled() {
if (!existsSync(windsurfGlobalMCPConfigPath)) {
return false;
}
try {
const configContents = await readJSON(windsurfGlobalMCPConfigPath);
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 (e) {
console.error(e);
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) => {
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.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 = path.dirname(__filename);
const filePath = path.resolve(__dirname, "starter-files", inputFile);
const fileString = fs3__default.readFileSync(filePath, "utf8");
if (fs3__default.existsSync(outputFilePath) && !replaceIfExists) {
console.log(`${outputFilePath} already exists`);
return false;
}
await fsExtra3.outputFile(outputFilePath, fileString);
return true;
}
async setupEnvFile({ dbUrl }) {
const envPath = path.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 (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);
}
};
var logger = createLogger({
name: "Mastra CLI",
level: "debug"
});
// src/commands/init/utils.ts
var exec = util.promisify(child_process.exec);
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')`;
} 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-1.5-pro-latest')`;
} 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.
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\u2019t 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
${addExampleTool ? "Use the weatherTool to fetch current weather data." : ""}
`;
const content = `
${providerImport}
import { Agent } from '@mastra/core/agent';
${addExampleTool ? `import { weatherTool } from '../tools';` : ""}
export const weatherAgent = new Agent({
name: 'Weather Agent',
instructions: \`${instructions}\`,
model: ${modelItem},
${addExampleTool ? "tools: { weatherTool }," : ""}
});
`;
const formattedContent = await prettier.format(content, {
parser: "typescript",
singleQuote: true
});
await fs4.writeFile(destPath, "");
await fs4.writeFile(destPath, formattedContent);
}
async function writeWorkflowSample(destPath, llmProvider) {
const { providerImport, modelItem } = getProviderImportAndModelItem(llmProvider);
const content = `${providerImport}
import { Agent } from '@mastra/core/agent';
import { Step, Workflow } from '@mastra/core/workflows';
import { z } from 'zod';
const llm = ${modelItem};
const agent = new Agent({
name: 'Weather Agent',
model: llm,
instructions: \`
You are a local activities and travel expert who excels at weather-based planning. Analyze the weather data and provide practical activity recommendations.
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 fetchWeather = new Step({
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'),
}),
execute: async ({ context }) => {
const triggerData = context?.getStepResult<{ city: string }>('trigger');
if (!triggerData) {
throw new Error('Trigger data not found');
}
const geocodingUrl = \`https://geocoding-api.open-meteo.com/v1/search?name=\${encodeURIComponent(triggerData.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 '\${triggerData.city}' not found\`);
}
const { latitude, longitude, name } = geocodingData.results[0];
const weatherUrl = \`https://api.open-meteo.com/v1/forecast?latitude=\${latitude}&longitude=\${longitude}&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_mean,weathercode&timezone=auto\`;
const response = await fetch(weatherUrl);
const data = (await response.json()) as {
daily: {
time: string[];
temperature_2m_max: number[];
temperature_2m_min: number[];
precipitation_probability_mean: number[];
weathercode: number[];
};
};
const forecast = data.daily.time.map((date: string, index: number) => ({
date,
maxTemp: data.daily.temperature_2m_max[index],
minTemp: data.daily.temperature_2m_min[index],
precipitationChance: data.daily.precipitation_probability_mean[index],
condition: getWeatherCondition(data.daily.weathercode[index]!),
location: name,
}));
return forecast;
},
});
const forecastSchema = z.array(
z.object({
date: z.string(),
maxTemp: z.number(),
minTemp: z.number(),
precipitationChance: z.number(),
condition: z.string(),
location: z.string(),
}),
);
const planActivities = new Step({
id: 'plan-activities',
description: 'Suggests activities based on weather conditions',
inputSchema: forecastSchema,
execute: async ({ context, mastra }) => {
const forecast = context?.getStepResult<z.infer<typeof forecastSchema>>('fetch-weather');
if (!forecast || forecast.length === 0) {
throw new Error('Forecast data not found');
}
const prompt = \`Based on the following weather forecast for \${forecast[0]?.location}, suggest appropriate activities:
\${JSON.stringify(forecast, null, 2)}
\`;
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,
};
},
});
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 weatherWorkflow = new Workflow({
name: 'weather-workflow',
triggerSchema: z.object({
city: z.string().describe('The city to get the weather for'),
}),
})
.step(fetchWeather)
.then(planActivities);
weatherWorkflow.commit();
export { weatherWorkflow };`;
const formattedContent = await prettier.format(content, {
parser: "typescript",
semi: true,
singleQuote: true
});
await fs4.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, llmprovider);
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 = path.join(indexPath);
try {
await fs4.writeFile(destPath, "");
const filteredExports = [
addWorkflow ? `workflows: { weatherWorkflow },` : "",
addAgent ? `agents: { weatherAgent },` : ""
].filter(Boolean);
if (!addExample) {
await fs4.writeFile(
destPath,
`
import { Mastra } from '@mastra/core';
export const mastra = new Mastra()
`
);
return;
}
await fs4.writeFile(
destPath,
`
import { Mastra } from '@mastra/core/mastra';
import { createLogger } from '@mastra/core/logger';
${addWorkflow ? `import { weatherWorkflow } from './workflows';` : ""}
${addAgent ? `import { weatherAgent } from './agents';` : ""}
export const mastra = new Mastra({
${filteredExports.join("\n ")}
logger: createLogger({
name: 'Mastra',
level: 'info',
}),
});
`
);
} catch (err) {
throw err;
}
};
var checkAndInstallCoreDeps = async () => {
const depsService = new DepsService();
const depCheck = await depsService.checkDependencies(["@mastra/core"]);
if (depCheck !== "ok") {
await installCoreDeps();
}
};
var spinner = yoctoSpinner({ text: "Installing Mastra core dependencies\n" });
async function installCoreDeps() {
try {
const confirm2 = await p.confirm({
message: "You do not have the @mastra/core package installed. Would you like to install it?",
initialValue: false
});
if (p.isCancel(confirm2)) {
p.cancel("Installation Cancelled");
process.exit(0);
}
if (!confirm2) {
p.cancel("Installation Cancelled");
process.exit(0);
}
spinner.start();
const depsService = new DepsService();
await depsService.installPackages(["@mastra/core@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);
await exec(`echo ${key}=${apiKey} >> .env.development`);
};
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 fsExtra3.ensureDir(dirPath);
return { ok: true, dirPath };
}
};
var writeCodeSample = async (dirPath, component, llmProvider, importComponents) => {
const destPath = dirPath + `/${component}/index.ts`;
try {
await writeCodeSampleForComponents(llmProvider, component, destPath, importComponents);
} catch (err) {
throw err;
}
};
var interactivePrompt = async () => {
p.intro(color2.inverse("Mastra Init"));
const mastraProject = await p.group(
{
directory: () => p.text({
message: "Where should we create the Mastra files? (default: src/)",
placeholder: "src/",
defaultValue: "src/"
}),
components: () => p.multiselect({
message: "Choose components to install:",
options: [
{ value: "agents", label: "Agents", hint: "recommended" },
{
value: "workflows",
label: "Workflows"
}
]
}),
shouldAddTools: () => p.confirm({
message: "Add tools?",
initialValue: false
}),
llmProvider: () => p.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 p.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 p.text({
message: "Enter your API key:",
placeholder: "sk-..."
});
}
return void 0;
},
addExample: () => p.confirm({
message: "Add example",
initialValue: false
}),
configureEditorWithDocsMCP: async () => {
const windsurfIsAlreadyInstalled = await globalWindsurfMCPIsAlreadyInstalled();
const editor = await p.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" },
{
value: "windsurf",
label: "Windsurf",
hint: windsurfIsAlreadyInstalled ? `Already installed` : void 0
}
]
});
if (editor === `skip`) return void 0;
if (editor === `windsurf` && windsurfIsAlreadyInstalled) {
p.log.message(`
Windsurf is already installed, skipping.`);
return 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 === `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: "skip", label: "No, skip for now" }
]
});
if (confirm2 !== `yes`) {
return void 0;
}
}
return editor;
}
},
{
onCancel: () => {
p.cancel("Operation cancelled.");
process.exit(0);
}
}
);
const { shouldAddTools, components, ...rest } = mastraProject;
const mastraComponents = shouldAddTools ? [...components, "tools"] : components;
return { ...rest, components: mastraComponents };
};
var checkPkgJson = async () => {
const cwd = process.cwd();
const pkgJsonPath = path.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 = p.spinner();
var exec2 = 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 key = await getAPIKey(llmProvider || "openai");
const aiSdkPackage = getAISDKPackage(llmProvider);
const depsService = new DepsService();
const pm = depsService.packageManager;
const installCommand = getPackageManagerInstallCommand(pm);
await exec2(`${pm} ${installCommand} ${aiSdkPackage}`);
if (configureEditorWithDocsMCP) {
await installMastraDocsMCPServer({
editor: configureEditorWithDocsMCP,
directory: process.cwd()
});
}
s.stop();
if (!llmApiKey) {
p.note(`
${color2.green("Mastra initialized successfully!")}
Add your ${color2.cyan(key)} as an environment variable
in your ${color2.cyan(".env.development")} file
`);
} else {
p.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 exec3 = util.promisify(child_process.exec);
var execWithTimeout = async (command, timeoutMs) => {
try {
const promise = exec3(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) {
console.error(error);
throw error;
}
};
var createMastraProject = async ({
projectName: name,
createVersionTag,
timeout
}) => {
p.intro(color2.inverse("Mastra Create"));
const projectName = name ?? await p.text({
message: "What do you want to name your project?",
placeholder: "my-mastra-app",
defaultValue: "my-mastra-app"
});
if (p.isCancel(projectName)) {
p.cancel("Operation cancelled");
process.exit(0);
}
const s2 = p.spinner();
s2.start("Creating project");
try {
await fs4.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 or delete the existing directory.`
);
process.exit(1);
}
throw error;
}
process.chdir(projectName);
const pm = getPackageManager();
const installCommand = getPackageManagerInstallCommand(pm);
s2.message("Creating project");
await exec3(`npm init -y`);
await exec3(`npm pkg set type="module"`);
const depsService = new DepsService();
await depsService.addScriptsToPackageJson({
dev: "mastra dev"
});
s2.stop("Project created");
s2.start(`Installing ${pm} dependencies`);
await exec3(`${pm} ${installCommand} zod`);
await exec3(`${pm} ${installCommand} typescript tsx @types/node --save-dev`);
await exec3(`echo '{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"dist",
".mastra"
]
}' > tsconfig.json`);
s2.stop(`${pm} dependencies installed`);
s2.start("Installing mastra");
const versionTag = createVersionTag ? `@${createVersionTag}` : "@latest";
await execWithTimeout(`${pm} ${installCommand} mastra${versionTag}`, timeout);
s2.stop("mastra installed");
s2.start("Installing @mastra/core");
await execWithTimeout(`${pm} ${installCommand} @mastra/core${versionTag}`, timeout);
s2.stop("@mastra/core installed");
s2.start("Adding .gitignore");
await exec3(`echo output.txt >> .gitignore`);
await exec3(`echo node_modules >> .gitignore`);
await exec3(`echo dist >> .gitignore`);
await exec3(`echo .mastra >> .gitignore`);
await exec3(`echo .env.development >> .gitignore`);
await exec3(`echo .env >> .gitignore`);
await exec3(`echo *.db >> .gitignore`);
s2.stop(".gitignore added");
p.outro("Project created successfully");
console.log("");
return { projectName };
};
// src/commands/create/create.ts
var create = async (args2) => {
const { projectName } = await createMastraProject({
projectName: args2?.projectName,
createVersionTag: args2?.createVersionTag,
timeout: args2?.timeout
});
const directory = "/src";
if (!args2.components || !args2.llmProvider || !args2.addExample) {
const result = await interactivePrompt();
await init({
...result,
llmApiKey: result?.llmApiKey
});
postCreate({ projectName });
return;
}
const { components = [], llmProvider = "openai", addExample = false, llmApiKey } = args2;
await init({
directory,
components,
llmProvider,
addExample,
llmApiKey
});
postCreate({ projectName });
};
var postCreate = ({ projectName }) => {
p.outro(`
${color2.green("To start your project:")}
${color2.cyan("cd")} ${projectName}
${color2.cyan("npm run dev")}
`);
};
export { DepsService, FileService, checkAndInstallCoreDeps, checkPkgJson, create, init, interactivePrompt, logger };