UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

195 lines (194 loc) 8.67 kB
"use strict"; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.scriptCommand = exports.ScriptCommand = void 0; const IToolCommand_1 = require("../IToolCommand"); const Utilities_1 = __importDefault(require("../../../core/Utilities")); class ScriptCommand extends IToolCommand_1.ToolCommandBase { metadata = { name: "script", description: "Run a command or send a message on the Bedrock server", aliases: ["js", "eval"], category: "Server", arguments: [ { name: "code", description: "Command or message to send to the server", type: "string", required: true, }, ], flags: [ { name: "session", shortName: "s", description: "Session name to run on (required for MCP/API)", type: "string", required: false, }, { name: "raw", shortName: "r", description: "Send as a raw server command (e.g., /say, /tp, /give)", type: "boolean", required: false, }, { name: "eval", shortName: "e", description: "Evaluate JavaScript code in the server's scripting context (world, system, dimension available)", type: "boolean", required: false, }, { name: "timeout", shortName: "t", description: "Timeout in seconds for --eval response (default: 5)", type: "string", required: false, }, ], scopes: [IToolCommand_1.ToolCommandScope.serveTerminal, IToolCommand_1.ToolCommandScope.ui, IToolCommand_1.ToolCommandScope.serverApi, IToolCommand_1.ToolCommandScope.mcp], examples: [ "/script say Hello from MCT!", "/script --raw tp @s 0 64 0", "/script --eval return world.getAllPlayers().length", "/script --eval --timeout 30 return world.getDimension('overworld').getEntities().length", "/script --session mySession --eval return world.getDimension('overworld').id", ], }; async execute(context, args, flags) { const code = args.join(" "); const sessionName = flags.session; const isRaw = flags.raw === true; const isEval = flags.eval === true; // In MCP or serverApi context, we need session info if ((context.scope === "mcp" || context.scope === "serverApi") && !sessionName && !context.session) { return this.error("NO_SESSION", "Session name required in MCP/API context. Use --session <name>"); } if (!code || code.trim() === "") { return this.error("NO_CODE", "No command or message provided"); } context.output.info(`Executing: ${code}`); try { // Eval pathway: send code to the in-game addon for evaluation via scriptevent if (isEval) { return await this._executeEval(context, code, sessionName, flags); } // Determine the command to send let command; if (isRaw) { // Raw mode: send the command as-is (prefix with / if needed) command = code.startsWith("/") ? code : "/" + code; } else { // Default mode: treat as a server command // If it looks like a slash command already, send as-is // Otherwise, wrap as a /say command for message delivery if (code.startsWith("/")) { command = code; } else { command = "/say " + code; } } // Try to run via IMinecraft (serve mode, Electron with server connection) if (context.minecraft) { const result = await context.minecraft.runCommand(command); context.output.success("Command executed"); return this.success("Command executed", { result, command, sessionName: sessionName || context.session?.sessionName, }); } // Fall back to session's serverManager (MCP/API contexts) if (context.session?.serverManager) { const server = context.session.serverManager.getActiveServer(context.session.slot || 0); if (server) { const result = await server.runCommandImmediate(command); context.output.success("Command executed"); return this.success("Command executed", { result, command, sessionName: sessionName || context.session?.sessionName, }); } } return this.error("NO_SERVER", "No active Minecraft server connection"); } catch (error) { const message = error instanceof Error ? error.message : String(error); return this.error("SCRIPT_ERROR", `Command execution failed: ${message}`); } } /** * Evaluates JavaScript code in the in-game scripting context via the mct:eval * scriptevent protocol with token-based response parsing. * * Protocol: * 1. Encode: replace all `"` with `|` in the code * 2. Send: `/scriptevent mct:eval "token|encodedCode"` * 3. Wait for stdout line containing the token * 4. Parse `evl|token|result` from the response */ async _executeEval(context, code, sessionName, flags) { // Encode the code for transport: replace double quotes with pipes const encodedCode = code.replace(/"/g, "|"); const token = Utilities_1.default.createRandomLowerId(6); // Build the scriptevent command const command = 'scriptevent mct:eval "' + token + "|" + encodedCode + '"'; let result; const timeoutSec = parseInt(flags.timeout, 10) || 5; const maxWaitMs = timeoutSec * 1000; // Eval requires token-based response parsing via runCommandImmediate. // The session.serverManager pathway supports this; the IMinecraft pathway // (serve mode) does not currently support token-based waiting. if (context.session?.serverManager) { const server = context.session.serverManager.getActiveServer(context.session.slot || 0); if (server) { result = await server.runCommandImmediate(command, token + "|", maxWaitMs); } } else if (context.minecraft) { // IMinecraft.runCommand doesn't support token-based waiting, so the result // may not contain the eval response. Best-effort approach. result = await context.minecraft.runCommand(command); } if (result === undefined) { return this.error("NO_SERVER", "No active Minecraft server connection or eval timed out"); } // Parse the evl|token|result response const evlIndex = result.indexOf("evl|"); if (evlIndex >= 0) { // Find the token after "evl|" const afterEvl = result.substring(evlIndex + 4); const pipeIndex = afterEvl.indexOf("|"); if (pipeIndex >= 0) { const evalResult = afterEvl.substring(pipeIndex + 1); if (evalResult.startsWith("Error: ")) { return this.error("EVAL_ERROR", evalResult); } context.output.success(`Eval result: ${evalResult}`); return this.success("Eval completed", { result: evalResult, code, sessionName: sessionName || context.session?.sessionName, }); } } // If we got a result but couldn't parse it as evl| format, return raw context.output.success(`Result: ${result}`); return this.success("Eval completed", { result, code, sessionName: sessionName || context.session?.sessionName, }); } } exports.ScriptCommand = ScriptCommand; exports.scriptCommand = new ScriptCommand();