@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
195 lines (194 loc) • 8.67 kB
JavaScript
;
// 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();