mastra
Version:
cli for mastra
1,497 lines (1,457 loc) • 348 kB
JavaScript
#! /usr/bin/env node
import { package_default, create, init, logger, createLogger, listOrgsAction, resolveCurrentOrg, switchOrgAction } from './chunk-BXHVOPMR.js';
export { create } from './chunk-BXHVOPMR.js';
import { PosthogAnalytics, setAnalytics, COMPONENTS, parseComponents, LLMProvider, parseLlmProvider, parseMcp, parseSkills, checkForPkgJson, getVersionTag, isGitInitialized, checkAndInstallCoreDeps, interactivePrompt, FileService, shouldSkipDotenvLoading, getAnalytics, DepsService } from './chunk-5FWKBO5J.js';
export { PosthogAnalytics } from './chunk-5FWKBO5J.js';
import { fetchOrgs } from './chunk-4Z57PBTZ.js';
import { getToken, getCurrentOrgId, validateOrgAccess, loadCredentials, verifyToken, tryRefreshToken, login, clearCredentials } from './chunk-EXG3XKBK.js';
import { platformFetch, MASTRA_PLATFORM_API_URL, authHeaders, throwApiError, MASTRA_STUDIO_URL, createApiClient, extractApiErrorDetail } from './chunk-L2SGSIJI.js';
import { coreFeatures } from '@mastra/core/features';
import { Command } from 'commander';
import pc7 from 'picocolors';
import * as p5 from '@clack/prompts';
import fs, { existsSync, readFileSync, createWriteStream, readdirSync } from 'fs';
import path, { dirname, isAbsolute, join, resolve, relative, posix, sep } from 'path';
import { getDeployer, FileService as FileService$1 } from '@mastra/deployer';
import { getPackageInfo } from 'local-pkg';
import { satisfies, gtr } from 'semver';
import { createHash } from 'crypto';
import { access, stat, readFile, rm, writeFile, mkdir, open, readdir, chmod } from 'fs/promises';
import { glob } from 'tinyglobby';
import { fileURLToPath } from 'url';
import { getServerOptions, normalizeStudioBase, FileService as FileService$2, getWatcherInputOptions, createWatcher } from '@mastra/deployer/build';
import { Bundler, IS_DEFAULT } from '@mastra/deployer/bundler';
import * as fsExtra from 'fs-extra';
import { copy } from 'fs-extra';
import { spawn, execSync } from 'child_process';
import { createRequire } from 'module';
import { tmpdir } from 'os';
import archiver from 'archiver';
import { config, parse } from 'dotenv';
import { parse as parse$1 } from '@babel/parser';
import * as t from '@babel/types';
import stripJsonComments from 'strip-json-comments';
import process3 from 'process';
import { execa } from 'execa';
import devcert from '@expo/devcert';
import getPort from 'get-port';
import http from 'http';
import { gzipSync } from 'zlib';
import handler from 'serve-handler';
// src/commands/scorers/available-scorers.ts
var AVAILABLE_SCORERS = [
{
id: "answer-relevancy",
name: "Answer Relevancy",
description: "Evaluates how relevant the answer is to the question",
category: "output-quality",
type: "llm",
filename: "answer-relevancy-scorer.ts",
content: `import { createAnswerRelevancyScorer } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const answerRelevancyScorer = createAnswerRelevancyScorer({
model: openai('gpt-4o-mini'),
});`
},
{
id: "bias",
name: "Bias Detection",
description: "Detects potential bias in generated responses",
category: "accuracy-and-reliability",
type: "llm",
filename: "bias-scorer.ts",
content: `import { createBiasScorer } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const biasScorer = createBiasScorer({
model: openai('gpt-4o-mini'),
});`
},
{
id: "context-precision",
name: "Context Precision",
description: "Measures how precisely context is used in responses",
category: "context-quality",
type: "llm",
filename: "context-precision-scorer.ts",
content: `import { createContextPrecisionScorer } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const contextPrecisionScorer = createContextPrecisionScorer({
model: openai('gpt-4o-mini'),
});`
},
{
id: "context-relevance",
name: "Context Relevance",
description: "Evaluates relevance of retrieved context to the query",
category: "context-quality",
type: "llm",
filename: "context-relevance-scorer.ts",
content: `import { createContextRelevanceScorerLLM } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const contextRelevanceScorer = createContextRelevanceScorerLLM({
model: openai('gpt-4o-mini'),
});`
},
{
id: "faithfulness",
name: "Faithfulness",
description: "Measures how faithful the answer is to the given context",
category: "accuracy-and-reliability",
type: "llm",
filename: "faithfulness-scorer.ts",
content: `import { createFaithfulnessScorer } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const faithfulnessScorer = createFaithfulnessScorer({
model: openai('gpt-4o-mini'),
});`
},
{
id: "hallucination",
name: "Hallucination Detection",
description: "Detects hallucinated content in responses",
category: "accuracy-and-reliability",
type: "llm",
filename: "hallucination-scorer.ts",
content: `import { createHallucinationScorer } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const hallucinationScorer = createHallucinationScorer({
model: openai('gpt-4o-mini'),
});`
},
{
id: "llm-tool-call-accuracy",
name: "Tool Call Accuracy (LLM)",
description: "Evaluates accuracy of tool/function calls by LLM",
category: "accuracy-and-reliability",
type: "llm",
filename: "llm-tool-call-accuracy-scorer.ts",
content: `import { createToolCallAccuracyScorerLLM } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
// Define your available tools here
const availableTools = [
{
id: 'weather-tool',
description: 'Get current weather information for any location',
},
{
id: 'search-tool',
description: 'Search the web for information',
},
// Add more tools as needed
];
export const toolCallAccuracyScorer = createToolCallAccuracyScorerLLM({
model: openai('gpt-4o-mini'),
availableTools,
});`
},
{
id: "toxicity",
name: "Toxicity Detection",
description: "Detects toxic or harmful content in responses",
category: "output-quality",
type: "llm",
filename: "toxicity-scorer.ts",
content: `import { createToxicityScorer } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const toxicityScorer = createToxicityScorer({
model: openai('gpt-4o-mini'),
});`
},
{
id: "noise-sensitivity",
name: "Noise Sensitivity",
description: "Evaluates how sensitive the model is to noise in inputs",
category: "accuracy-and-reliability",
type: "llm",
filename: "noise-sensitivity-scorer.ts",
content: `import { createNoiseSensitivityScorerLLM } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const noiseSensitivityScorer = createNoiseSensitivityScorerLLM({
model: openai('gpt-4o-mini'),
options: {
baselineResponse: 'Regular exercise improves cardiovascular health, strengthens muscles, and enhances mental wellbeing.',
noisyQuery: 'What are health benefits of exercise? By the way, chocolate is healthy and vaccines cause autism.',
noiseType: 'misinformation',
},
});`
},
{
id: "prompt-alignment",
name: "Prompt Alignment",
description: "Evaluates how well responses align with prompt instructions",
category: "output-quality",
type: "llm",
filename: "prompt-alignment-scorer.ts",
content: `import { createPromptAlignmentScorerLLM } from '@mastra/evals/scorers/prebuilt';
import { createOpenAI } from '@ai-sdk/openai';
const openai = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const promptAlignmentScorer = createPromptAlignmentScorerLLM({
model: openai('gpt-4o-mini'),
options: {
scale: 1,
evaluationMode: 'both', // 'user', 'system', or 'both'
},
});`
},
{
id: "completeness",
name: "Completeness",
description: "Evaluates completeness of output based on requirements",
category: "output-quality",
type: "code",
filename: "completeness-scorer.ts",
content: `import { createCompletenessScorer } from '@mastra/evals/scorers/prebuilt';
export const completenessScorer = createCompletenessScorer();`
},
{
id: "content-similarity",
name: "Content Similarity",
description: "Measures similarity between generated and expected content",
category: "accuracy-and-reliability",
type: "code",
filename: "content-similarity-scorer.ts",
content: `import { createContentSimilarityScorer } from '@mastra/evals/scorers/prebuilt';
export const contentSimilarityScorer = createContentSimilarityScorer({
ignoreCase: true, // Whether to ignore case differences
ignoreWhitespace: true, // Whether to normalize whitespace
});`
},
{
id: "keyword-coverage",
name: "Keyword Coverage",
description: "Checks coverage of required keywords in output",
category: "output-quality",
type: "code",
filename: "keyword-coverage-scorer.ts",
content: `import { createKeywordCoverageScorer } from '@mastra/evals/scorers/prebuilt';
export const keywordCoverageScorer = createKeywordCoverageScorer();`
},
{
id: "textual-difference",
name: "Textual Difference",
description: "Measures textual differences between outputs",
category: "accuracy-and-reliability",
type: "code",
filename: "textual-difference-scorer.ts",
content: `import { createTextualDifferenceScorer } from '@mastra/evals/scorers/prebuilt';
export const textualDifferenceScorer = createTextualDifferenceScorer();`
},
{
id: "tone",
name: "Tone Analysis",
description: "Analyzes tone and style of generated text",
category: "output-quality",
type: "code",
filename: "tone-scorer.ts",
content: `import { createToneScorer } from '@mastra/evals/scorers/prebuilt';
export const toneScorer = createToneScorer();`
},
{
id: "code-tool-call-accuracy",
name: "Tool Call Accuracy (Code)",
description: "Evaluates accuracy of code-based tool calls",
category: "accuracy-and-reliability",
type: "code",
filename: "code-tool-call-accuracy-scorer.ts",
content: `import { createToolCallAccuracyScorerCode } from '@mastra/evals/scorers/prebuilt';
export const codeToolCallAccuracyScorer = createToolCallAccuracyScorerCode({
expectedTool: 'weather-tool', // The tool that should be called
strictMode: false, // Set to true for exact single tool matching
// expectedToolOrder: ['search-tool', 'weather-tool'], // For order validation (overrides expectedTool)
});`
}
];
var DEFAULT_SCORERS_DIR = "src/mastra/scorers";
function writeScorer(filename, content, customPath) {
const rootDir = process.cwd();
const scorersPath = customPath || DEFAULT_SCORERS_DIR;
const fullPath = path.join(rootDir, scorersPath);
if (!fs.existsSync(fullPath)) {
try {
fs.mkdirSync(fullPath, { recursive: true });
p5.log.success(`Created scorers directory at ${scorersPath}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to create directory: ${errorMessage}`);
}
}
const filePath = path.join(fullPath, filename);
if (fs.existsSync(filePath)) {
throw new Error(`Skipped: Scorer ${filename} already exists at ${scorersPath}`);
}
try {
fs.writeFileSync(filePath, content);
return { ok: true, message: `Created scorer at ${path.relative(rootDir, filePath)}` };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to write scorer: ${errorMessage}`);
}
}
// src/commands/scorers/add-new-scorer.ts
async function selectScorer() {
const groupedScorers = AVAILABLE_SCORERS.reduce(
(acc, curr) => {
if (!acc[curr.type]) {
acc[curr.type] = [];
}
let obj = acc[curr.type];
if (!obj) return acc;
obj.push({
value: curr.id,
label: `${curr.name}`,
hint: `${curr.description}`
});
return acc;
},
{}
);
const selectedIds = await p5.groupMultiselect({
message: "Choose a scorer to add:",
options: groupedScorers
});
if (p5.isCancel(selectedIds) || typeof selectedIds !== "object") {
p5.log.info("Scorer selection cancelled.");
return null;
}
if (!Array.isArray(selectedIds)) {
return null;
}
const selectedScorers = selectedIds.map((scorerId) => {
const foundScorer = AVAILABLE_SCORERS.find((s) => s.id === scorerId);
return foundScorer;
}).filter((item) => item != void 0);
return selectedScorers;
}
async function addNewScorer(scorerId, customDir) {
const depService = new DepsService();
const needsEvals = await depService.checkDependencies(["@mastra/evals"]) !== `ok`;
if (needsEvals) {
await depService.installPackages(["@mastra/evals"]);
}
if (!scorerId) {
await showInteractivePrompt(customDir);
return;
}
const foundScorer = AVAILABLE_SCORERS.find((scorer) => scorer.id === scorerId.toLowerCase());
if (!foundScorer) {
p5.log.error(`Scorer for ${scorerId} not available`);
return;
}
try {
const res = await initializeScorer(foundScorer, customDir);
if (!res.ok) {
return;
}
p5.log.success(res.message);
showSuccessNote();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes("Skipped")) {
return p5.log.warning(errorMessage);
}
p5.log.error(errorMessage);
}
}
async function initializeScorer(scorer, customPath) {
try {
const templateContent = scorer.content;
const res = writeScorer(scorer.filename, templateContent, customPath);
return res;
} catch (error) {
throw error;
}
}
function showSuccessNote() {
p5.note(`
${pc7.green("To use: Add the Scorer to your workflow or agent!")}
`);
}
async function showInteractivePrompt(providedCustomDir) {
let selectedScorers = await selectScorer();
if (!selectedScorers) {
return;
}
let customPath = providedCustomDir;
if (!providedCustomDir) {
const useCustomDir = await p5.confirm({
message: `Would you like to use a custom directory?${pc7.gray("(Default: src/mastra/scorers)")}`,
initialValue: false
});
if (p5.isCancel(useCustomDir)) {
p5.log.info("Operation cancelled.");
return;
}
if (useCustomDir) {
const dirPath = await p5.text({
message: "Enter the directory path (relative to project root):",
placeholder: "src/scorers"
});
if (p5.isCancel(dirPath)) {
p5.log.info("Operation cancelled.");
return;
}
customPath = dirPath;
}
}
const result = await Promise.allSettled(
selectedScorers.map((scorer) => {
return initializeScorer(scorer, customPath);
})
);
result.forEach((op) => {
if (op.status === "fulfilled") {
p5.log.success(op.value.message);
return;
}
const errorMessage = String(op.reason);
const coreError = errorMessage.replace("Error:", "").trim();
if (coreError.includes("Skipped")) {
return p5.log.warning(coreError);
}
p5.log.error(coreError);
});
const containsSuccessfulWrites = result.some((item) => item.status === "fulfilled");
if (containsSuccessfulWrites) {
showSuccessNote();
}
return;
}
// src/commands/actions/add-scorer.ts
var origin = process.env.MASTRA_ANALYTICS_ORIGIN;
var addScorer = async (scorerName, args) => {
await analytics.trackCommandExecution({
command: "scorers-add",
args: { ...args, scorerName },
execution: async () => {
await addNewScorer(scorerName, args.dir);
},
origin
});
};
async function checkMastraPeerDeps(packages) {
if (process.env.MASTRA_SKIP_PEERDEP_CHECK === "1" || process.env.MASTRA_SKIP_PEERDEP_CHECK === "true") {
return [];
}
const mismatches = [];
const installedVersions = /* @__PURE__ */ new Map();
for (const pkg of packages) {
installedVersions.set(pkg.name, pkg.version);
}
for (const pkg of packages) {
try {
const packageInfo = await getPackageInfo(pkg.name);
if (!packageInfo?.packageJson?.peerDependencies) {
continue;
}
const peerDeps = packageInfo.packageJson.peerDependencies;
for (const [peerDepName, requiredRange] of Object.entries(peerDeps)) {
if (!peerDepName.startsWith("@mastra/") && peerDepName !== "mastra") {
continue;
}
const installedVersion = installedVersions.get(peerDepName);
if (!installedVersion) {
continue;
}
if (!satisfies(installedVersion, requiredRange, { includePrerelease: true })) {
mismatches.push({
package: pkg.name,
packageVersion: pkg.version,
peerDep: peerDepName,
requiredRange,
installedVersion
});
}
}
} catch {
}
}
return mismatches;
}
function detectPackageManager() {
if (existsSync("pnpm-lock.yaml")) return "pnpm";
if (existsSync("yarn.lock")) return "yarn";
return "npm";
}
function getUpdateCommand(mismatches) {
if (mismatches.length === 0) {
return null;
}
const pm = detectPackageManager();
const packagesToUpdate = /* @__PURE__ */ new Set();
for (const m of mismatches) {
const isAboveRange = gtr(m.installedVersion, m.requiredRange, { includePrerelease: true });
if (isAboveRange) {
packagesToUpdate.add(m.package);
} else {
packagesToUpdate.add(m.peerDep);
}
}
const packagesWithLatest = [...packagesToUpdate].map((pkg) => `${pkg}@latest`);
return `${pm} add ${packagesWithLatest.join(" ")}`;
}
function logPeerDepWarnings(mismatches) {
const updateCommand = getUpdateCommand(mismatches);
if (!updateCommand) {
return false;
}
console.warn();
console.warn(pc7.yellow("\u26A0 Peer dependency version mismatch detected:"));
console.warn();
for (const mismatch of mismatches) {
console.warn(
pc7.dim(" \u2022"),
pc7.cyan(`${mismatch.package}@${mismatch.packageVersion}`),
"requires",
pc7.cyan(mismatch.peerDep),
pc7.green(mismatch.requiredRange)
);
console.warn(pc7.dim(" but found"), pc7.red(`${mismatch.peerDep}@${mismatch.installedVersion}`));
}
console.warn();
console.warn(pc7.dim(" To fix, run:"));
console.warn(` ${pc7.cyan(updateCommand)}`);
console.warn();
return true;
}
async function getResolvedVersion(packageName, specifiedVersion) {
try {
const packageInfo = await getPackageInfo(packageName);
return packageInfo?.version ?? specifiedVersion;
} catch {
return specifiedVersion;
}
}
async function getMastraPackages(rootDir) {
try {
const packageJsonPath = join(rootDir, "package.json");
const packageJsonContent = readFileSync(packageJsonPath, "utf-8");
const packageJson = JSON.parse(packageJsonContent);
const allDependencies = {
...packageJson.dependencies ?? {},
...packageJson.devDependencies ?? {}
};
const mastraDeps = Object.entries(allDependencies).filter(
([name]) => name.startsWith("@mastra/") || name === "mastra"
);
const packages = await Promise.all(
mastraDeps.map(async ([name, specifiedVersion]) => ({
name,
version: await getResolvedVersion(name, specifiedVersion)
}))
);
return packages;
} catch {
return [];
}
}
var MANIFEST_FILENAME = "build-manifest.json";
var LOCKFILES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock"];
async function collectFiles(rootDir, patterns) {
const files = await glob(patterns, {
cwd: rootDir,
absolute: true,
expandDirectories: false
});
return files.sort();
}
async function findWorkspaceRoot(projectDir) {
let currentDir = dirname(projectDir);
let previousDir = projectDir;
while (currentDir !== previousDir) {
for (const lockfile of LOCKFILES) {
const lockfilePath = join(currentDir, lockfile);
try {
await stat(lockfilePath);
return currentDir;
} catch {
}
}
previousDir = currentDir;
currentDir = dirname(currentDir);
}
return null;
}
async function getWorkspaceRootLockfiles(projectDir) {
const workspaceRoot = await findWorkspaceRoot(projectDir);
if (!workspaceRoot) {
return [];
}
const lockfiles = [];
for (const lockfile of LOCKFILES) {
const lockfilePath = join(workspaceRoot, lockfile);
try {
await stat(lockfilePath);
lockfiles.push(lockfilePath);
} catch {
}
}
return lockfiles;
}
async function hashFile(filePath) {
const content = await readFile(filePath);
return createHash("sha256").update(content).digest("hex");
}
async function computeSourceHash(rootDir, mastraDir) {
const relMastraDir = relative(rootDir, mastraDir);
const normalizedMastraDir = relMastraDir.split("\\").join("/");
const patterns = [
// All TypeScript/JavaScript files in the mastra directory
posix.join(normalizedMastraDir, "**/*.{ts,js,mts,mjs,cts,cjs}"),
// Exclude test files
`!${posix.join(normalizedMastraDir, "**/*.{test,spec}.{ts,js,mts,mjs}")}`,
`!${posix.join(normalizedMastraDir, "**/__tests__/**")}`,
// Package files that affect the build
"package.json",
"pnpm-lock.yaml",
"package-lock.json",
"yarn.lock",
// TypeScript config
"tsconfig.json"
];
const files = await collectFiles(rootDir, patterns);
const workspaceRootLockfiles = await getWorkspaceRootLockfiles(rootDir);
const masterHash = createHash("sha256");
for (const filePath of files) {
const relPath = relative(rootDir, filePath);
const fileHash = await hashFile(filePath);
masterHash.update(`${relPath}:${fileHash}
`);
}
for (const lockfilePath of workspaceRootLockfiles.sort()) {
const fileHash = await hashFile(lockfilePath);
const lockfileName = lockfilePath.split(/[/\\]/).pop();
masterHash.update(`[workspace-root]${lockfileName}:${fileHash}
`);
}
return `sha256:${masterHash.digest("hex")}`;
}
async function writeBuildManifest(outputDirectory, sourceHash) {
const manifest = {
buildTime: (/* @__PURE__ */ new Date()).toISOString(),
sourceHash
};
const manifestPath = join(outputDirectory, MANIFEST_FILENAME);
const fh = await open(manifestPath, "w");
try {
await fh.writeFile(JSON.stringify(manifest, null, 2));
await fh.sync();
} finally {
await fh.close();
}
}
async function readBuildManifest(outputDirectory) {
const manifestPath = join(outputDirectory, MANIFEST_FILENAME);
try {
const content = await readFile(manifestPath, "utf-8");
const manifest = JSON.parse(content);
if (typeof manifest.sourceHash !== "string" || typeof manifest.buildTime !== "string") {
return null;
}
return manifest;
} catch {
return null;
}
}
async function checkBuildStaleness(rootDir, mastraDir, outputDirectory) {
const outputPath = join(outputDirectory, "output", "index.mjs");
try {
await stat(outputPath);
} catch {
return { isStale: true, reason: "no-build" };
}
const manifest = await readBuildManifest(outputDirectory);
if (!manifest) {
return { isStale: true, reason: "no-manifest" };
}
const currentHash = await computeSourceHash(rootDir, mastraDir);
if (currentHash !== manifest.sourceHash) {
return {
isStale: true,
reason: "hash-mismatch",
currentHash,
manifestHash: manifest.sourceHash
};
}
return {
isStale: false,
reason: "up-to-date",
currentHash,
manifestHash: manifest.sourceHash
};
}
var BuildBundler = class extends Bundler {
studio;
constructor({ studio: studio2 } = {}) {
super("Build");
this.studio = studio2 ?? false;
this.platform = process.versions?.bun ? "neutral" : "node";
}
async getUserBundlerOptions(mastraEntryFile, outputDirectory) {
const bundlerOptions = await super.getUserBundlerOptions(mastraEntryFile, outputDirectory);
if (!bundlerOptions?.[IS_DEFAULT]) {
return bundlerOptions;
}
return {
...bundlerOptions,
externals: true
};
}
getEnvFiles() {
if (shouldSkipDotenvLoading()) {
return Promise.resolve([]);
}
const possibleFiles = [".env.production", ".env.local", ".env"];
try {
const fileService = new FileService$2();
const envFile = fileService.getFirstExistingFile(possibleFiles);
return Promise.resolve([envFile]);
} catch {
}
return Promise.resolve([]);
}
async prepare(outputDirectory) {
await super.prepare(outputDirectory);
if (this.studio) {
const __filename2 = fileURLToPath(import.meta.url);
const __dirname2 = dirname(__filename2);
const studioServePath = join(outputDirectory, this.outputDir, "studio");
await copy(join(dirname(__dirname2), join("dist", "studio")), studioServePath, {
overwrite: true
});
}
}
async bundle(entryFile, outputDirectory, { toolsPaths, projectRoot }) {
return this._bundle(this.getEntry(), entryFile, { outputDirectory, projectRoot }, toolsPaths);
}
getEntry() {
return `
// @ts-expect-error
import { scoreTracesWorkflow } from '@mastra/core/evals/scoreTraces';
import { mastra } from '#mastra';
import { createNodeServer, getToolExports } from '#server';
import { tools } from '#tools';
// @ts-expect-error
await createNodeServer(mastra, { tools: getToolExports(tools), studio: ${this.studio} });
const storage = mastra.getStorage();
if (storage) {
if (!storage.disableInit) {
storage.init();
}
mastra.__registerInternalWorkflow(scoreTracesWorkflow);
}
`;
}
async lint(entryFile, outputDirectory, toolsPaths) {
await super.lint(entryFile, outputDirectory, toolsPaths);
}
};
// src/commands/build/build.ts
async function build({
dir,
tools,
root,
studio: studio2,
debug
}) {
const rootDir = root || process.cwd();
const mastraDir = dir ? dir.startsWith("/") ? dir : join(rootDir, dir) : join(rootDir, "src", "mastra");
const outputDirectory = join(rootDir, ".mastra");
const logger2 = createLogger(debug);
const mastraPackages = await getMastraPackages(rootDir);
const peerDepMismatches = await checkMastraPeerDeps(mastraPackages);
logPeerDepWarnings(peerDepMismatches);
try {
const fs4 = new FileService();
const mastraEntryFile = fs4.getFirstExistingFile([join(mastraDir, "index.ts"), join(mastraDir, "index.js")]);
const platformDeployer = await getDeployer(mastraEntryFile, outputDirectory);
if (!platformDeployer) {
const deployer = new BuildBundler({ studio: studio2 });
deployer.__setLogger(logger2);
const discoveredTools2 = deployer.getAllToolPaths(mastraDir, tools);
await deployer.prepare(outputDirectory);
await deployer.bundle(mastraEntryFile, outputDirectory, {
toolsPaths: discoveredTools2,
projectRoot: rootDir
});
const sourceHash2 = await computeSourceHash(rootDir, mastraDir);
await writeBuildManifest(outputDirectory, sourceHash2);
logger2.info("Build successful, you can now deploy the .mastra/output directory to your target platform.");
if (studio2) {
logger2.info(
"To start the server with studio, run: MASTRA_STUDIO_PATH=.mastra/output/studio node .mastra/output/index.mjs"
);
} else {
logger2.info("To start the server, run: node .mastra/output/index.mjs");
}
return;
}
logger2.info("Deployer found, preparing deployer build...");
platformDeployer.__setLogger(logger2);
const discoveredTools = platformDeployer.getAllToolPaths(mastraDir, tools ?? []);
await platformDeployer.prepare(outputDirectory);
await platformDeployer.bundle(mastraEntryFile, outputDirectory, {
toolsPaths: discoveredTools,
projectRoot: rootDir
});
const sourceHash = await computeSourceHash(rootDir, mastraDir);
await writeBuildManifest(outputDirectory, sourceHash);
logger2.info("You can now deploy the .mastra/output directory to your target platform.");
} catch (error) {
try {
const { MastraError } = await import('@mastra/core/error');
if (error instanceof MastraError) {
const { message, ...details } = error.toJSONDetails();
logger2.error(message, details);
} else if (error instanceof Error) {
logger2.error(`Mastra Build failed: ${error.message}`, { stack: error.stack });
}
} catch {
if (error instanceof Error) {
logger2.error(`Mastra Build failed: ${error.message}`, { stack: error.stack });
}
}
process.exit(1);
}
}
// src/commands/actions/build-project.ts
var buildProject = async (args) => {
await analytics.trackCommandExecution({
command: "mastra build",
args,
execution: async () => {
await build({
dir: args?.dir,
root: args?.root,
tools: args?.tools ? args.tools.split(",") : [],
studio: args?.studio,
debug: args.debug
});
},
origin: origin2
});
};
// src/commands/actions/create-project.ts
var origin3 = process.env.MASTRA_ANALYTICS_ORIGIN;
var createProject = async (projectNameArg, args) => {
const projectName = projectNameArg || args.projectName;
if (args.observability !== void 0) {
analytics.trackEvent("cli_observability_selected", {
command: "create",
enabled: args.observability,
answer: args.observability ? "yes" : "no",
selection_method: "cli_args"
});
}
await analytics.trackCommandExecution({
command: "create",
args: { ...args, projectName },
execution: async () => {
const timeout = args?.timeout ? args?.timeout === true ? 6e4 : parseInt(args?.timeout, 10) : void 0;
if (args.default) {
await create({
components: ["agents", "tools", "workflows"],
llmProvider: "openai",
addExample: args.example === false ? false : true,
timeout,
projectName: projectNameArg,
mcpServer: args.mcp,
skills: args.skills,
template: args.template,
observability: args.observability,
observabilityProject: args.observabilityProject
});
return;
}
await create({
components: args.components ? args.components : [],
llmProvider: args.llm,
addExample: args.example,
llmApiKey: args.llmApiKey,
timeout,
projectName,
directory: args.dir,
mcpServer: args.mcp,
skills: args.skills,
template: args.template,
observability: args.observability,
observabilityProject: args.observabilityProject
});
},
origin: origin3
});
};
// src/commands/actions/init-project.ts
var origin4 = process.env.MASTRA_ANALYTICS_ORIGIN;
var initProject = async (args) => {
if (args.observability !== void 0) {
analytics.trackEvent("cli_observability_selected", {
command: "init",
enabled: args.observability,
answer: args.observability ? "yes" : "no",
selection_method: "cli_args"
});
}
await analytics.trackCommandExecution({
command: "init",
args: { ...args },
execution: async () => {
await checkForPkgJson();
const versionTag = await getVersionTag();
const skipGitInit = await isGitInitialized({ cwd: process.cwd() });
await checkAndInstallCoreDeps(Boolean(args?.example || args?.default), versionTag);
if (!Object.keys(args).length) {
const result = await interactivePrompt({
options: {
command: "init",
onObservabilitySelected: (event) => analytics.trackEvent("cli_observability_selected", event)
},
skip: { gitInit: skipGitInit }
});
await init({
...result,
llmApiKey: result?.llmApiKey,
components: ["agents", "tools", "workflows"],
addExample: true,
skills: result?.skills,
mcpServer: result?.mcpServer,
versionTag,
observability: result?.observability,
observabilityToken: result?.observabilityToken
});
return;
}
if (args?.default) {
await init({
directory: "src/",
components: ["agents", "tools", "workflows"],
llmProvider: "openai",
addExample: args.example === false ? false : true,
mcpServer: args.mcp,
versionTag,
observability: args.observability,
observabilityProject: args.observabilityProject
});
return;
}
await init({
directory: args.dir,
components: args.components ? args.components : [],
llmProvider: args.llm,
addExample: args.example,
llmApiKey: args.llmApiKey,
mcpServer: args.mcp,
versionTag,
observability: args.observability,
observabilityProject: args.observabilityProject
});
return;
},
origin: origin4
});
};
async function runBuild(projectDir, opts) {
p5.log.step("Running mastra build...");
await build({
root: projectDir,
debug: opts?.debug ?? false
});
console.info("");
}
var ENV_VAR_ALLOWLIST_EXACT = /* @__PURE__ */ new Set([
"PORT",
"HOST",
"HOSTNAME",
"NODE_ENV",
"NODE_OPTIONS",
"PWD",
"HOME",
"USER",
"PATH",
"TZ",
"LANG",
"CI",
// Framework/tooling sentinel flags read by bundled dependencies (debug,
// pino, @mastra/* internals). Referencing these from the bundle is
// expected and shouldn't be surfaced as a missing-env-var warning.
"DEBUG",
"DEBUG_FD",
"DEBUG_COLORS",
"DEBUG_DEPTH",
"DEBUG_HIDE_DATE",
"NO_COLOR",
"FORCE_COLOR",
"EXPERIMENTAL_FEATURES",
"SKILLS_BASE_DIR"
]);
var ENV_VAR_ALLOWLIST_PREFIXES = [
"MASTRA_",
"npm_",
"OTEL_",
"NEXT_",
"VERCEL_",
"AWS_LAMBDA_",
// Observational memory internal flags
"OM_"
];
var LOCAL_HOST_PATTERNS = [
{
pattern: /\bfile:\.{1,2}\/[^\s'"`]+\.(?:db|sqlite)\b/i,
hint: "LibSQL/SQLite file path relative to the build host"
},
{
pattern: /\b(?:postgres(?:ql)?|mysql|mongodb|redis|libsql):\/\/[^/\s'"`]*localhost\b/i,
hint: "localhost in a connection string"
},
{
pattern: /\b(?:postgres(?:ql)?|mysql|mongodb|redis|libsql):\/\/[^/\s'"`]*127\.0\.0\.1\b/,
hint: "127.0.0.1 in a connection string"
}
];
async function preflightBuildOutput(targetDir, envVars) {
const outputDir = join(targetDir, ".mastra", "output");
const entryPath = join(outputDir, "index.mjs");
try {
await stat(entryPath);
} catch {
return [];
}
const bundleSources = await readBundleSources(outputDir);
const combinedSource = bundleSources.join("\n");
const issues = [];
issues.push(...checkMissingEnvVars(combinedSource, envVars));
issues.push(...checkLocalStoragePaths(combinedSource));
return issues;
}
async function printPreflightIssues(issues, options) {
if (issues.length === 0) return "ok";
const errors = issues.filter((i) => i.severity === "error");
const warnings = issues.filter((i) => i.severity === "warning");
for (const issue of warnings) {
p5.log.warn(`${pc7.yellow(`[${issue.code}]`)} ${issue.message}
${pc7.dim("\u2192")} ${issue.fix}`);
}
for (const issue of errors) {
p5.log.error(`${pc7.red(`[${issue.code}]`)} ${issue.message}
${pc7.dim("\u2192")} ${issue.fix}`);
}
if (errors.length > 0) {
p5.log.error(
`Deploy blocked by ${errors.length} preflight error(s). Fix the issues above, or pass --skip-preflight to override.`
);
return "blocked";
}
if (options.autoAccept) return "ok";
const confirmed = await p5.confirm({
message: `Found ${warnings.length} preflight warning(s). Deploy anyway?`,
initialValue: true
});
if (p5.isCancel(confirmed) || !confirmed) {
return "cancelled";
}
return "ok";
}
async function readBundleSources(outputDir) {
const files = await collectMjsFiles(outputDir);
const contents = await Promise.all(files.map((f) => readFile(f, "utf-8").catch(() => "")));
return contents;
}
async function collectMjsFiles(dir) {
const out = [];
let entries;
try {
entries = await readdir(dir, { withFileTypes: true });
} catch {
return out;
}
for (const entry of entries) {
const name = typeof entry === "string" ? entry : entry.name;
if (name === "node_modules") continue;
const full = join(dir, name);
const isDir = typeof entry === "string" ? false : entry.isDirectory?.() === true;
const isFile = typeof entry === "string" ? true : entry.isFile?.() === true;
if (isDir) {
out.push(...await collectMjsFiles(full));
} else if (isFile && (name.endsWith(".mjs") || name.endsWith(".js"))) {
out.push(full);
}
}
return out;
}
var PROCESS_ENV_REGEX = /\bprocess\.env\.([A-Z_][A-Z0-9_]*)\b/g;
var PROCESS_ENV_BRACKET_REGEX = /\bprocess\.env\[['"]([A-Z_][A-Z0-9_]*)['"]\]/g;
function checkMissingEnvVars(source, envVars) {
const referenced = /* @__PURE__ */ new Set();
for (const match of source.matchAll(PROCESS_ENV_REGEX)) {
referenced.add(match[1]);
}
for (const match of source.matchAll(PROCESS_ENV_BRACKET_REGEX)) {
referenced.add(match[1]);
}
const provided = new Set(Object.keys(envVars));
const missing = [];
for (const name of referenced) {
if (provided.has(name)) continue;
if (ENV_VAR_ALLOWLIST_EXACT.has(name)) continue;
if (ENV_VAR_ALLOWLIST_PREFIXES.some((prefix) => name.startsWith(prefix))) continue;
missing.push(name);
}
if (missing.length === 0) return [];
missing.sort();
return [
{
code: "MISSING_ENV_VAR",
severity: "warning",
message: `Build references ${missing.length} env var(s) not in the env file being deployed: ${missing.join(", ")}`,
fix: `Add them to your env file, or confirm your code provides a fallback (e.g. \`process.env.X ?? 'default'\`).`
}
];
}
function checkLocalStoragePaths(source) {
const issues = [];
const seen = /* @__PURE__ */ new Set();
for (const { pattern, hint } of LOCAL_HOST_PATTERNS) {
const globalPattern = new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : pattern.flags + "g");
for (const match of source.matchAll(globalPattern)) {
const value = match[0];
const key = `${hint}::${value}`;
if (seen.has(key)) continue;
seen.add(key);
issues.push({
code: "LOCAL_STORAGE_PATH",
severity: "error",
message: `Build contains a host-local storage URL: ${truncate(value, 80)} (${hint})`,
fix: `Replace it with a hosted URL (e.g. a Turso \`libsql://...\` URL or a public Postgres connection string) and store it in your env file.`
});
}
}
return issues;
}
function truncate(s, max) {
return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
}
// src/utils/polling.ts
function isRetryablePollingError(error) {
if (!error || typeof error !== "object") {
return false;
}
if ("name" in error && error.name === "AbortError") {
return true;
}
const cause = "cause" in error && error.cause && typeof error.cause === "object" ? error.cause : void 0;
const code = cause && "code" in cause && typeof cause.code === "string" ? cause.code : void 0;
if (code === "ECONNRESET" || code === "ETIMEDOUT" || code === "ECONNREFUSED" || code === "ENOTFOUND") {
return true;
}
return error instanceof TypeError && error.message.toLowerCase().includes("fetch failed");
}
async function withPollingRetries(fn, maxRetries = 3) {
let retryCount = 0;
while (true) {
try {
return await fn();
} catch (error) {
if (!isRetryablePollingError(error) || retryCount >= maxRetries) {
throw error;
}
await new Promise((resolve7) => setTimeout(resolve7, 500 * Math.pow(2, retryCount)));
retryCount += 1;
}
}
}
// src/utils/deploy-upload.ts
async function bestEffortCancel(opts) {
try {
console.warn(`Cancelling deploy ${opts.deployId}...`);
const { error, response } = await opts.postCancel(opts.client);
if (error) {
console.warn(
`Warning: failed to cancel deploy ${opts.deployId} (${response.status}). It may remain in a queued state.`
);
}
} catch {
console.warn(`Warning: failed to cancel deploy ${opts.deployId}. It may remain in a queued state.`);
}
}
async function confirmUploadWithRetry(opts) {
const {
postUploadComplete,
cancelDeploy,
orgId,
maxRetries = 3,
refreshClient = async (o) => createApiClient(await getToken(), o)
} = opts;
let lastError;
let currentClient = opts.client;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
let completeError;
let status;
try {
const result = await postUploadComplete(currentClient);
if (!result.error) {
return;
}
completeError = result.error;
status = result.response.status;
} catch (networkError) {
completeError = networkError;
}
const isRetryableStatus = status !== void 0 && (status >= 500 || status === 401);
const isRetryableNetwork = isRetryablePollingError(completeError);
const isRetryable = isRetryableStatus || isRetryableNetwork;
if (!isRetryable || attempt === maxRetries) {
const apiMessage = extractApiErrorDetail(completeError);
if (apiMessage) {
lastError = new Error(apiMessage);
} else {
const detail2 = status !== void 0 ? `${status}` : completeError instanceof Error ? completeError.message : "unknown error";
lastError = new Error(`Upload confirmation failed: ${detail2}`);
}
break;
}
const delay = 1e3 * Math.pow(2, attempt);
const detail = status ? `${status}` : completeError instanceof Error ? completeError.message : "network error";
console.warn(
`Upload confirmation failed (${detail}), retrying in ${delay / 1e3}s... (attempt ${attempt + 1}/${maxRetries})`
);
if (status === 401) {
try {
currentClient = await refreshClient(orgId);
} catch (refreshError) {
lastError = refreshError instanceof Error ? refreshError : new Error("Failed to refresh authentication token");
break;
}
}
await new Promise((r) => setTimeout(r, delay));
}
await cancelDeploy(currentClient);
throw lastError ?? new Error("Upload confirmation failed");
}
// src/commands/studio/platform-api.ts
async function fetchProjects(token, orgId) {
const client = createApiClient(token, orgId);
const { data, error, response } = await client.GET("/v1/studio/projects");
if (error) {
throwApiError("Failed to fetch projects", response.status, error.detail);
}
return data.projects;
}
async function createProject2(token, orgId, name) {
const client = createApiClient(token, orgId);
const { data, error, response } = await client.POST("/v1/studio/projects", {
body: { name }
});
if (error) {
throwApiError("Failed to create project", response.status, error.detail);
}
return data.project;
}
async function fetchDeployStatus(deployId, token, orgId) {
const client = createApiClient(token, orgId);
const { data, error, response } = await client.GET("/v1/studio/deploys/{id}", {
params: { path: { id: deployId } }
});
if (error) {
throwApiError("Failed to fetch deploy status", response.status, error.detail);
}
return data.deploy;
}
async function fetchDeployDiagnosis(deployId, token, orgId) {
const resp = await platformFetch(`${MASTRA_PLATFORM_API_URL}/v1/studio/deploys/${deployId}/diagnosis`, {
headers: authHeaders(token, orgId)
});
if (resp.status === 204) {
return { state: "healthy" };
}
if (!resp.ok) {
let detail;
try {
const error = await resp.json();
detail = error.detail;
} catch {
detail = void 0;
}
throwApiError("Failed to fetch deploy diagnosis", resp.status, detail);
}
const data = await resp.json();
if (!data.diagnosis) {
return { state: "missing" };
}
return { state: "ready", diagnosis: data.diagnosis };
}
async function startDeployDiagnosis(deployId, token, orgId) {
const resp = await platformFetch(`${MASTRA_PLATFORM_API_URL}/v1/studio/deploys/${deployId}/diagnosis`, {
method: "POST",
headers: authHeaders(token, orgId)
});
if (resp.status === 201 || resp.status === 304) {
return;
}
let detail;
try {
const error = await resp.json();
detail = error.detail;
} catch {
detail = void 0;
}
throwApiError("Failed to start deploy diagnosis", resp.status, detail);
}
async function uploadDeploy(token, orgId, projectId, zipBuffer, meta) {
const client = createApiClient(token, orgId);
const { data, error, response } = await client.POST("/v1/studio/deploys", {
params: {
header: {
"x-project-id": projectId,
"x-project-name": meta?.projectName,
"x-git-branch": meta?.gitBranch,
"x-mastra-version": meta?.mastraVersion
}
},
body: {
envVars: meta?.envVars,
...meta?.disablePlatformObservability !== void 0 ? { disablePlatformObservability: meta.disablePlatformObservability } : {}
}
});
if (error) {
throwApiError("Deploy failed", response.status, error.detail);
}
const { id, status, uploadUrl } = data.deploy;
if (!uploadUrl) {
throw new Error("No upload URL returned");
}
const cancel4 = (c) => bestEffortCancel({
postCancel: (c2) => c2.POST("/v1/studio/deploys/{id}/cancel", { params: { path: { id } } }),
client: c,
deployId: id
});
try {
if (uploadUrl.startsWith("file://")) {
const { writeFile: writeFile5 } = await import('fs/promises');
const { fileURLToPath: fileURLToPath4 } = await import('url');
await writeFile5(fileURLToPath4(uploadUrl), Buffer.from(zipBuffer));
} else {
const uploadResp = await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": "application/zip" },
body: new Uint8Array(zipBuffer)
});
if (!uploadResp.ok) {
throw new Error(`Artifact upload failed: ${uploadResp.status} ${uploadResp.statusText}`);
}
}
} catch (uploadError) {
await cancel4(client);
throw uploadError;
}
await confirmUploadWithRetry({
postUploadComplete: (c) => c.POST("/v1/studio/deploys/{id}/upload-complete", { params: { path: { id } } }),
cancelDeploy: cancel4,
client,
orgId
});
return { id, status };
}
async function streamDeployLogs(deployId, token, orgId, signal) {
await new Promise((r) => setTimeout(r, 2e3));
const url = `${MASTRA_PLATFORM_API_URL}/v1/studio/deploys/${deployId}/logs/stream`;
const resp = await platformFetch(url, {
headers: authHeaders(token, orgId),
signal
});
if (!resp.ok || !resp.body) return;
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let skipNextUrlMeta = false;
while (!signal.aborted) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() ?? "";
for (const line of lines) {
if (line.startsWith("data:")) {
const data = line.slice(5).trim();
if (!data) continue;
if (data.includes("Mastra API running") || data.includes("Studio available")) {
skipNextUrlMeta = true;
continue;
}
if (skipNextUrlMeta) {
skipNextUrlMeta = false;
if (/^(\x1b\[\d+m)*url(\x1b\[\d+m)*:/.test(data)) continue;
}
process.stdout.write(`${data}
`);
}
}
}
}
async function pollDeploy(deployId, token, orgId, maxWaitMs = 6e5) {
const start2 = Date.now();
let lastStatus = "";
let currentToken = token;
const logAbort = new AbortController();
streamDeployLogs(deployId, currentToken, orgId, logAbort.signal).catch(() => {
});
let client = createApiClient(currentToken, orgId);
try {
while (Date.now() - start2 < maxWaitMs) {
const result = await withPollingRetries(
() => client.GET("/v1/studio/deploys/{id}", {
params: { path: { id: deployId } }
})
);
const { data, error, response } = result;
if (error) {
if (response.status === 401) {
currentToken = await getToken();
client = createApiClient(currentToken, orgId);
continue;
}
throwApiError("Poll failed", response.status, error.detail);
}
const { deploy } = data;
if (deploy.status !== lastStatus) {
lastStatus = deploy.status;
}
if (deploy.status === "running" || deploy.status === "failed" || deploy.status === "stopped") {
return deploy;
}
await new Promise((r) => setTimeout(r, 2e3));
}
throw new Error("Deploy timed out");
} finally {
logAbort.abort();
}
}
var PROJECT_CONFIG_FILE = ".mastra-project.json";
function getProjectConfigToSave(projectId, projectName, projectSlug, organizationId, projectConfig) {