UNPKG

mastra

Version:

cli for mastra

472 lines (468 loc) • 17.1 kB
#! /usr/bin/env node import { PosthogAnalytics } from './chunk-SDQ6DRUS.js'; export { PosthogAnalytics } from './chunk-SDQ6DRUS.js'; import { DepsService, create, checkPkgJson, checkAndInstallCoreDeps, interactivePrompt, init, logger, FileService as FileService$1 } from './chunk-4QQ2IFQI.js'; export { create } from './chunk-4QQ2IFQI.js'; import { Command } from 'commander'; import { join as join$1, dirname, basename } from 'node:path'; import { getWatcherInputOptions, writeTelemetryConfig, createWatcher, FileService as FileService$2 } from '@mastra/deployer/build'; import { Bundler } from '@mastra/deployer/bundler'; import * as fsExtra2 from 'fs-extra'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { join } from 'path'; import { FileService, getDeployer } from '@mastra/deployer'; import { execa } from 'execa'; import { stat } from 'node:fs/promises'; import { config } from 'dotenv'; var BuildBundler = class extends Bundler { constructor() { super("Build"); } getEnvFiles() { const possibleFiles = [".env.production", ".env.local", ".env"]; try { const fileService = new FileService$2(); const envFile = fileService.getFirstExistingFile(possibleFiles); return Promise.resolve([envFile]); } catch (err) { } return Promise.resolve([]); } async prepare(outputDirectory) { await super.prepare(outputDirectory); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const playgroundServePath = join$1(outputDirectory, this.outputDir, "playground"); await fsExtra2.copy(join$1(dirname(__dirname), "src/playground/dist"), playgroundServePath, { overwrite: true }); } bundle(entryFile, outputDirectory) { return this._bundle(this.getEntry(), entryFile, outputDirectory); } getEntry() { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); return readFileSync(join$1(__dirname, "templates", "dev.entry.js"), "utf8"); } }; // src/commands/build/build.ts async function build({ dir }) { const mastraDir = dir ?? process.cwd(); const outputDirectory = join$1(mastraDir, ".mastra"); const deployer = new BuildBundler(); const fs = new FileService$1(); const mastraEntryFile = fs.getFirstExistingFile([ join$1(mastraDir, "src", "mastra", "index.ts"), join$1(mastraDir, "src", "mastra", "index.js") ]); console.log(join$1(mastraDir, "index.ts"), join$1(mastraDir, "index.js")); await deployer.prepare(outputDirectory); await deployer.bundle(mastraEntryFile, outputDirectory); } async function deploy({ dir }) { let mastraDir = dir || join(process.cwd(), "src/mastra"); try { const outputDirectory = join(process.cwd(), ".mastra"); const fs = new FileService$1(); const mastraEntryFile = fs.getFirstExistingFile([join(mastraDir, "index.ts"), join(mastraDir, "index.js")]); const deployer = await getDeployer(mastraEntryFile, outputDirectory); if (!deployer) { logger.warn("No deployer found."); return; } try { await deployer.prepare(outputDirectory); await deployer.bundle(mastraEntryFile, outputDirectory); try { await deployer.deploy(outputDirectory); } catch (error) { console.error("[Mastra Deploy] - Error deploying:", error); } } catch (err) { if (err instanceof Error) { logger.debug(`error: ${err.message}`, { error: err }); } } } catch (error) { if (error instanceof Error) { logger.debug(`error: ${error.message}`, { error }); } logger.warn("No deployer found."); } } var DevBundler = class extends Bundler { mastraToolsPaths = []; constructor() { super("Dev"); } getEnvFiles() { const possibleFiles = [".env.development", ".env.local", ".env"]; try { const fileService = new FileService(); const envFile = fileService.getFirstExistingFile(possibleFiles); return Promise.resolve([envFile]); } catch { } return Promise.resolve([]); } async loadEnvVars() { const superEnvVars = await super.loadEnvVars(); superEnvVars.set("MASTRA_TOOLS_PATH", this.mastraToolsPaths.join(",")); return superEnvVars; } async writePackageJson() { } async prepare(outputDirectory) { await super.prepare(outputDirectory); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const playgroundServePath = join$1(outputDirectory, this.outputDir, "playground"); await fsExtra2.copy(join$1(dirname(__dirname), "src/playground/dist"), playgroundServePath, { overwrite: true }); } async watch(entryFile, outputDirectory, toolsPaths) { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const envFiles = await this.getEnvFiles(); const inputOptions = await getWatcherInputOptions(entryFile, "node"); await writeTelemetryConfig(entryFile, join$1(outputDirectory, this.outputDir)); await this.writeInstrumentationFile(join$1(outputDirectory, this.outputDir)); if (toolsPaths?.length) { for (const toolPath of toolsPaths) { if (await fsExtra2.pathExists(toolPath)) { const toolName = basename(toolPath); const toolOutputPath = join$1(outputDirectory, this.outputDir, "tools", toolName); const fileService = new FileService(); const entryFile2 = fileService.getFirstExistingFile([ join$1(toolPath, "index.ts"), join$1(toolPath, "index.js"), toolPath // if toolPath itself is a file ]); if (!entryFile2 || (await stat(entryFile2)).isDirectory()) { this.logger.warn(`No entry file found in ${toolPath}, skipping...`); continue; } const toolInputOptions = await getWatcherInputOptions(entryFile2, "node"); const watcher2 = await createWatcher( { ...toolInputOptions, input: { index: entryFile2 } }, { dir: toolOutputPath } ); await new Promise((resolve, reject) => { const cb = (event) => { if (event.code === "BUNDLE_END") { watcher2.off("event", cb); resolve(void 0); } if (event.code === "ERROR") { watcher2.off("event", cb); reject(event); } }; watcher2.on("event", cb); }); this.mastraToolsPaths.push(join$1(toolOutputPath, "index.mjs")); } else { this.logger.warn(`Tool path ${toolPath} does not exist, skipping...`); } } } const outputDir = join$1(outputDirectory, this.outputDir); const copyPublic = this.copyPublic.bind(this); const watcher = await createWatcher( { ...inputOptions, plugins: [ // @ts-ignore - types are good // eslint-disable-next-line @typescript-eslint/no-misused-promises ...inputOptions.plugins, { name: "env-watcher", buildStart() { for (const envFile of envFiles) { this.addWatchFile(envFile); } } }, { name: "tools-watcher", buildStart() { if (toolsPaths?.length) { for (const toolPath of toolsPaths) { this.addWatchFile(toolPath); } } } }, { name: "public-dir-watcher", buildStart() { this.addWatchFile(join$1(dirname(entryFile), "public")); }, buildEnd() { return copyPublic(dirname(entryFile), outputDirectory); } } ], input: { index: join$1(__dirname, "templates", "dev.entry.js") } }, { dir: outputDir } ); this.logger.info("Starting watcher..."); return new Promise((resolve, reject) => { const cb = (event) => { if (event.code === "BUNDLE_END") { this.logger.info("Bundling finished, starting server..."); watcher.off("event", cb); resolve(watcher); } if (event.code === "ERROR") { console.log(event); this.logger.error("Bundling failed, stopping watcher..."); watcher.off("event", cb); reject(event); } }; watcher.on("event", cb); }); } async bundle() { } }; // src/commands/dev/dev.ts var currentServerProcess; var isRestarting = false; var startServer = async (dotMastraPath, port, env) => { try { logger.info("[Mastra Dev] - Starting server..."); const instrumentation = import.meta.resolve("@opentelemetry/instrumentation/hook.mjs"); currentServerProcess = execa( "node", ["--import=./instrumentation.mjs", `--import=${instrumentation}`, "index.mjs"], { cwd: dotMastraPath, env: { ...Object.fromEntries(env), PORT: port.toString() || process.env.PORT || "4111", MASTRA_DEFAULT_STORAGE_URL: `file:${join(dotMastraPath, "..", "mastra.db")}` }, stdio: "inherit", reject: false } ); if (currentServerProcess?.exitCode && currentServerProcess?.exitCode !== 0) { if (!currentServerProcess) { throw new Error(`Server failed to start`); } throw new Error(`Server failed to start with error: ${currentServerProcess.stderr}`); } await new Promise((resolve) => setTimeout(resolve, 1e3)); try { await fetch(`http://localhost:${port}/__refresh`, { method: "POST", headers: { "Content-Type": "application/json" } }); } catch { await new Promise((resolve) => setTimeout(resolve, 1500)); try { await fetch(`http://localhost:${port}/__refresh`, { method: "POST", headers: { "Content-Type": "application/json" } }); } catch { } } if (currentServerProcess.exitCode !== null) { logger.error("Server failed to start with error:", { message: currentServerProcess.stderr }); return; } } catch (err) { const execaError = err; if (execaError.stderr) logger.error("Server error output:", { stderr: execaError.stderr }); if (execaError.stdout) logger.debug("Server output:", { stdout: execaError.stdout }); } }; async function rebundleAndRestart(dotMastraPath, port, bundler, tools) { if (isRestarting) { return; } isRestarting = true; try { if (currentServerProcess) { logger.debug("Stopping current server..."); currentServerProcess.kill("SIGINT"); } const env = await bundler.loadEnvVars(); await startServer(join(dotMastraPath, "output"), port, env); } finally { isRestarting = false; } } async function dev({ port, dir, root, tools }) { const rootDir = root || process.cwd(); const mastraDir = join(rootDir, dir || "src/mastra"); const dotMastraPath = join(rootDir, ".mastra"); const defaultToolsPath = join(mastraDir, "tools"); const discoveredTools = [defaultToolsPath, ...tools || []]; const fileService = new FileService(); const entryFile = fileService.getFirstExistingFile([join(mastraDir, "index.ts"), join(mastraDir, "index.js")]); const bundler = new DevBundler(); await bundler.prepare(dotMastraPath); const watcher = await bundler.watch(entryFile, dotMastraPath, discoveredTools); const env = await bundler.loadEnvVars(); await startServer(join(dotMastraPath, "output"), port, env); watcher.on("event", (event) => { if (event.code === "BUNDLE_END") { logger.info("[Mastra Dev] - Bundling finished, restarting server..."); rebundleAndRestart(dotMastraPath, port, bundler); } }); process.on("SIGINT", () => { logger.info("[Mastra Dev] - Stopping server..."); if (currentServerProcess) { currentServerProcess.kill(); } watcher.close(); process.exit(0); }); } var depsService = new DepsService(); var version = await depsService.getPackageVersion(); var analytics = new PosthogAnalytics({ apiKey: "phc_SBLpZVAB6jmHOct9CABq3PF0Yn5FU3G2FgT4xUr2XrT", host: "https://us.posthog.com", version }); var program = new Command(); var origin = process.env.MASTRA_ANALYTICS_ORIGIN; program.version(`${version}`, "-v, --version").description(`Mastra CLI ${version}`).action(() => { try { analytics.trackCommand({ command: "version", origin }); console.log(`Mastra CLI: ${version}`); } catch { } }); program.command("create").description("Create a new Mastra project").option("--default", "Quick start with defaults(src, OpenAI, no examples)").option("-c, --components <components>", "Comma-separated list of components (agents, tools, workflows)").option("-l, --llm <model-provider>", "Default model provider (openai, anthropic, groq, google, or cerebras))").option("-k, --llm-api-key <api-key>", "API key for the model provider").option("-e, --example", "Include example code").option("-t, --timeout [timeout]", "Configurable timeout for package installation, defaults to 60000 ms").option( "-p, --project-name <string>", "Project name that will be used in package.json and as the project directory name." ).action(async (args) => { await analytics.trackCommandExecution({ command: "create", args, 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: false, timeout }); return; } await create({ components: args.components ? args.components.split(",") : [], llmProvider: args.llm, addExample: args.example, llmApiKey: args["llm-api-key"], timeout, projectName: args.projectName }); }, origin }); }); program.command("init").description("Initialize Mastra in your project").option("--default", "Quick start with defaults(src, OpenAI, no examples)").option("-d, --dir <directory>", "Directory for Mastra files to (defaults to src/)").option("-c, --components <components>", "Comma-separated list of components (agents, tools, workflows)").option("-l, --llm <model-provider>", "Default model provider (openai, anthropic, or groq))").option("-k, --llm-api-key <api-key>", "API key for the model provider").option("-e, --example", "Include example code").action(async (args) => { await analytics.trackCommandExecution({ command: "init", args, execution: async () => { await checkPkgJson(); await checkAndInstallCoreDeps(); if (!Object.keys(args).length) { const result = await interactivePrompt(); await init({ ...result, llmApiKey: result?.llmApiKey }); return; } if (args?.default) { await init({ directory: "src/", components: ["agents", "tools", "workflows"], llmProvider: "openai", addExample: false }); return; } const componentsArr = args.components ? args.components.split(",") : []; await init({ directory: args.dir, components: componentsArr, llmProvider: args.llm, addExample: args.example, llmApiKey: args["llm-api-key"] }); return; }, origin }); }); program.command("dev").description("Start mastra server").option("-d, --dir <dir>", "Path to your mastra folder").option("-r, --root <root>", "Path to your root folder").option("-t, --tools <toolsDirs>", "Comma-separated list of paths to tool files to include").option("-p, --port <port>", "Port number for the development server (defaults to 4111)").action((args) => { analytics.trackCommand({ command: "dev", origin }); dev({ port: args?.port ? parseInt(args.port) : 4111, dir: args?.dir, root: args?.root, tools: args?.tools ? args.tools.split(",") : [] }).catch((err) => { logger.error(err.message); }); }); program.command("build").description("Build your Mastra project").option("-d, --dir <path>", "Path to directory").action(async (args) => { await analytics.trackCommandExecution({ command: "mastra build", args, execution: async () => { await build({ dir: args.dir }); }, origin }); }); program.command("deploy").description("Deploy your Mastra project").option("-d, --dir <path>", "Path to directory").action(async (args) => { config({ path: [".env", ".env.production"] }); await analytics.trackCommandExecution({ command: "mastra deploy", args, execution: async () => { await deploy({ dir: args.dir }); }, origin }); }); program.parse(process.argv);