UNPKG

mastra

Version:
1,497 lines (1,457 loc) • 348 kB
#! /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) {