@kzheart_/mc-pilot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/bin/mct +2 -0
  2. package/data/variants.json +139 -0
  3. package/dist/client/ClientManager.d.ts +82 -0
  4. package/dist/client/ClientManager.js +213 -0
  5. package/dist/client/WebSocketClient.d.ts +15 -0
  6. package/dist/client/WebSocketClient.js +76 -0
  7. package/dist/commands/block.d.ts +2 -0
  8. package/dist/commands/block.js +52 -0
  9. package/dist/commands/book.d.ts +2 -0
  10. package/dist/commands/book.js +21 -0
  11. package/dist/commands/channel.d.ts +2 -0
  12. package/dist/commands/channel.js +24 -0
  13. package/dist/commands/chat.d.ts +2 -0
  14. package/dist/commands/chat.js +31 -0
  15. package/dist/commands/client.d.ts +2 -0
  16. package/dist/commands/client.js +87 -0
  17. package/dist/commands/combat.d.ts +2 -0
  18. package/dist/commands/combat.js +46 -0
  19. package/dist/commands/craft.d.ts +5 -0
  20. package/dist/commands/craft.js +45 -0
  21. package/dist/commands/effects.d.ts +2 -0
  22. package/dist/commands/effects.js +16 -0
  23. package/dist/commands/entity.d.ts +2 -0
  24. package/dist/commands/entity.js +53 -0
  25. package/dist/commands/gui.d.ts +2 -0
  26. package/dist/commands/gui.js +49 -0
  27. package/dist/commands/hud.d.ts +2 -0
  28. package/dist/commands/hud.js +16 -0
  29. package/dist/commands/input.d.ts +2 -0
  30. package/dist/commands/input.js +124 -0
  31. package/dist/commands/inventory.d.ts +2 -0
  32. package/dist/commands/inventory.js +28 -0
  33. package/dist/commands/look.d.ts +2 -0
  34. package/dist/commands/look.js +37 -0
  35. package/dist/commands/move.d.ts +2 -0
  36. package/dist/commands/move.js +50 -0
  37. package/dist/commands/position.d.ts +2 -0
  38. package/dist/commands/position.js +7 -0
  39. package/dist/commands/request-helpers.d.ts +26 -0
  40. package/dist/commands/request-helpers.js +58 -0
  41. package/dist/commands/resourcepack.d.ts +2 -0
  42. package/dist/commands/resourcepack.js +9 -0
  43. package/dist/commands/rotation.d.ts +2 -0
  44. package/dist/commands/rotation.js +7 -0
  45. package/dist/commands/screen.d.ts +2 -0
  46. package/dist/commands/screen.js +7 -0
  47. package/dist/commands/screenshot.d.ts +2 -0
  48. package/dist/commands/screenshot.js +14 -0
  49. package/dist/commands/server.d.ts +2 -0
  50. package/dist/commands/server.js +66 -0
  51. package/dist/commands/sign.d.ts +2 -0
  52. package/dist/commands/sign.js +30 -0
  53. package/dist/commands/status.d.ts +2 -0
  54. package/dist/commands/status.js +17 -0
  55. package/dist/commands/wait.d.ts +2 -0
  56. package/dist/commands/wait.js +23 -0
  57. package/dist/download/CacheManager.d.ts +20 -0
  58. package/dist/download/CacheManager.js +50 -0
  59. package/dist/download/DownloadUtils.d.ts +2 -0
  60. package/dist/download/DownloadUtils.js +23 -0
  61. package/dist/download/JavaDetector.d.ts +7 -0
  62. package/dist/download/JavaDetector.js +31 -0
  63. package/dist/download/ModVariantCatalog.d.ts +10 -0
  64. package/dist/download/ModVariantCatalog.js +52 -0
  65. package/dist/download/SearchCommand.d.ts +29 -0
  66. package/dist/download/SearchCommand.js +37 -0
  67. package/dist/download/VersionMatrix.d.ts +72 -0
  68. package/dist/download/VersionMatrix.js +227 -0
  69. package/dist/download/client/Arm64LwjglPatcher.d.ts +5 -0
  70. package/dist/download/client/Arm64LwjglPatcher.js +153 -0
  71. package/dist/download/client/ClientDownloader.d.ts +42 -0
  72. package/dist/download/client/ClientDownloader.js +233 -0
  73. package/dist/download/client/FabricRuntimeDownloader.d.ts +10 -0
  74. package/dist/download/client/FabricRuntimeDownloader.js +91 -0
  75. package/dist/download/server/ServerDownloader.d.ts +51 -0
  76. package/dist/download/server/ServerDownloader.js +196 -0
  77. package/dist/download/types.d.ts +37 -0
  78. package/dist/download/types.js +1 -0
  79. package/dist/index.d.ts +2 -0
  80. package/dist/index.js +89 -0
  81. package/dist/server/ServerManager.d.ts +63 -0
  82. package/dist/server/ServerManager.js +114 -0
  83. package/dist/util/command.d.ts +10 -0
  84. package/dist/util/command.js +35 -0
  85. package/dist/util/config.d.ts +30 -0
  86. package/dist/util/config.js +59 -0
  87. package/dist/util/context.d.ts +17 -0
  88. package/dist/util/context.js +16 -0
  89. package/dist/util/errors.d.ts +20 -0
  90. package/dist/util/errors.js +37 -0
  91. package/dist/util/net.d.ts +5 -0
  92. package/dist/util/net.js +35 -0
  93. package/dist/util/output.d.ts +4 -0
  94. package/dist/util/output.js +23 -0
  95. package/dist/util/process.d.ts +3 -0
  96. package/dist/util/process.js +32 -0
  97. package/dist/util/state.d.ts +10 -0
  98. package/dist/util/state.js +40 -0
  99. package/package.json +54 -0
@@ -0,0 +1,35 @@
1
+ import process from "node:process";
2
+ import { createCommandContext } from "./context.js";
3
+ import { normalizeError } from "./errors.js";
4
+ import { printError, printSuccess } from "./output.js";
5
+ export function attachGlobalOptions(command) {
6
+ return command
7
+ .option("--human", "Human-readable output (default: JSON)")
8
+ .option("--config <path>", "Config file path (default: ./mct.config.json)")
9
+ .option("--state-dir <path>", "State directory (default: ./.mct-state/)")
10
+ .option("--client <name>", "Target client name (required when multiple clients are running)");
11
+ }
12
+ export function wrapCommand(action) {
13
+ return async function wrappedCommand(...input) {
14
+ const command = input.at(-1);
15
+ const options = input.at(-2);
16
+ const args = input.slice(0, -2).map((value) => String(value));
17
+ const globalOptions = command.optsWithGlobals();
18
+ try {
19
+ const context = await createCommandContext(globalOptions);
20
+ const result = await action(context, {
21
+ args,
22
+ options,
23
+ command,
24
+ globalOptions
25
+ });
26
+ printSuccess(result, context.outputMode);
27
+ }
28
+ catch (error) {
29
+ const normalized = normalizeError(error);
30
+ const mode = globalOptions.human ? "human" : "json";
31
+ printError(normalized, mode);
32
+ process.exitCode = normalized.exitCode;
33
+ }
34
+ };
35
+ }
@@ -0,0 +1,30 @@
1
+ export declare const DEFAULT_WS_PORT_BASE = 25580;
2
+ export interface MctConfig {
3
+ server: {
4
+ jar?: string;
5
+ dir: string;
6
+ port: number;
7
+ jvmArgs: string[];
8
+ };
9
+ clients: Record<string, {
10
+ version?: string;
11
+ account?: string;
12
+ wsPort?: number;
13
+ server?: string;
14
+ headless?: boolean;
15
+ launchCommand?: string[];
16
+ workingDir?: string;
17
+ env?: Record<string, string>;
18
+ }>;
19
+ screenshot: {
20
+ outputDir: string;
21
+ };
22
+ timeout: {
23
+ serverReady: number;
24
+ clientReady: number;
25
+ default: number;
26
+ };
27
+ }
28
+ export declare function resolveConfigPath(configPath: string | undefined, cwd: string): string;
29
+ export declare function loadConfig(configPath: string | undefined, cwd: string): Promise<MctConfig>;
30
+ export declare function writeConfig(configPath: string | undefined, cwd: string, config: MctConfig): Promise<void>;
@@ -0,0 +1,59 @@
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export const DEFAULT_WS_PORT_BASE = 25580;
4
+ function createDefaultConfig() {
5
+ return {
6
+ server: {
7
+ dir: "./server",
8
+ port: 25565,
9
+ jvmArgs: []
10
+ },
11
+ clients: {},
12
+ screenshot: {
13
+ outputDir: "./screenshots"
14
+ },
15
+ timeout: {
16
+ serverReady: 120,
17
+ clientReady: 60,
18
+ default: 10
19
+ }
20
+ };
21
+ }
22
+ export function resolveConfigPath(configPath, cwd) {
23
+ if (!configPath) {
24
+ return path.join(cwd, "mct.config.json");
25
+ }
26
+ return path.isAbsolute(configPath) ? configPath : path.resolve(cwd, configPath);
27
+ }
28
+ export async function loadConfig(configPath, cwd) {
29
+ const resolvedPath = resolveConfigPath(configPath, cwd);
30
+ const defaultConfig = createDefaultConfig();
31
+ try {
32
+ await access(resolvedPath);
33
+ }
34
+ catch {
35
+ return defaultConfig;
36
+ }
37
+ const raw = await readFile(resolvedPath, "utf8");
38
+ const parsed = JSON.parse(raw);
39
+ return {
40
+ server: {
41
+ ...defaultConfig.server,
42
+ ...parsed.server
43
+ },
44
+ clients: parsed.clients ?? defaultConfig.clients,
45
+ screenshot: {
46
+ ...defaultConfig.screenshot,
47
+ ...parsed.screenshot
48
+ },
49
+ timeout: {
50
+ ...defaultConfig.timeout,
51
+ ...parsed.timeout
52
+ }
53
+ };
54
+ }
55
+ export async function writeConfig(configPath, cwd, config) {
56
+ const resolvedPath = resolveConfigPath(configPath, cwd);
57
+ await mkdir(path.dirname(resolvedPath), { recursive: true });
58
+ await writeFile(resolvedPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
59
+ }
@@ -0,0 +1,17 @@
1
+ import { type MctConfig } from "./config.js";
2
+ import { type OutputMode } from "./output.js";
3
+ import { StateStore } from "./state.js";
4
+ export interface GlobalOptions {
5
+ human?: boolean;
6
+ config?: string;
7
+ stateDir?: string;
8
+ client?: string;
9
+ }
10
+ export interface CommandContext {
11
+ cwd: string;
12
+ configPath: string;
13
+ config: MctConfig;
14
+ state: StateStore;
15
+ outputMode: OutputMode;
16
+ }
17
+ export declare function createCommandContext(options: GlobalOptions): Promise<CommandContext>;
@@ -0,0 +1,16 @@
1
+ import process from "node:process";
2
+ import { loadConfig, resolveConfigPath } from "./config.js";
3
+ import { resolveStateDir, StateStore } from "./state.js";
4
+ export async function createCommandContext(options) {
5
+ const cwd = process.cwd();
6
+ const configPath = resolveConfigPath(options.config, cwd);
7
+ const config = await loadConfig(configPath, cwd);
8
+ const state = new StateStore(resolveStateDir(options.stateDir, cwd));
9
+ return {
10
+ cwd,
11
+ configPath,
12
+ config,
13
+ state,
14
+ outputMode: options.human ? "human" : "json"
15
+ };
16
+ }
@@ -0,0 +1,20 @@
1
+ export interface ErrorPayload {
2
+ code: string;
3
+ message: string;
4
+ details?: unknown;
5
+ }
6
+ export declare class MctError extends Error {
7
+ readonly code: string;
8
+ readonly exitCode: number;
9
+ readonly details?: unknown;
10
+ constructor(payload: ErrorPayload, exitCode?: number);
11
+ toJSON(): {
12
+ success: boolean;
13
+ error: {
14
+ code: string;
15
+ message: string;
16
+ details: {};
17
+ };
18
+ };
19
+ }
20
+ export declare function normalizeError(error: unknown): MctError;
@@ -0,0 +1,37 @@
1
+ export class MctError extends Error {
2
+ code;
3
+ exitCode;
4
+ details;
5
+ constructor(payload, exitCode = 1) {
6
+ super(payload.message);
7
+ this.name = "MctError";
8
+ this.code = payload.code;
9
+ this.exitCode = exitCode;
10
+ this.details = payload.details;
11
+ }
12
+ toJSON() {
13
+ return {
14
+ success: false,
15
+ error: {
16
+ code: this.code,
17
+ message: this.message,
18
+ details: this.details ?? {}
19
+ }
20
+ };
21
+ }
22
+ }
23
+ export function normalizeError(error) {
24
+ if (error instanceof MctError) {
25
+ return error;
26
+ }
27
+ if (error instanceof Error) {
28
+ return new MctError({
29
+ code: "INTERNAL_ERROR",
30
+ message: error.message
31
+ }, 1);
32
+ }
33
+ return new MctError({
34
+ code: "INTERNAL_ERROR",
35
+ message: "Unknown error"
36
+ }, 1);
37
+ }
@@ -0,0 +1,5 @@
1
+ export declare function waitForTcpPort(host: string, port: number, timeoutSeconds: number): Promise<{
2
+ reachable: boolean;
3
+ host: string;
4
+ port: number;
5
+ }>;
@@ -0,0 +1,35 @@
1
+ import net from "node:net";
2
+ import { MctError } from "./errors.js";
3
+ function wait(ms) {
4
+ return new Promise((resolve) => {
5
+ setTimeout(resolve, ms);
6
+ });
7
+ }
8
+ export async function waitForTcpPort(host, port, timeoutSeconds) {
9
+ const deadline = Date.now() + timeoutSeconds * 1000;
10
+ while (Date.now() < deadline) {
11
+ const reachable = await new Promise((resolve) => {
12
+ const socket = net.createConnection({ host, port });
13
+ socket.once("connect", () => {
14
+ socket.destroy();
15
+ resolve(true);
16
+ });
17
+ socket.once("error", () => {
18
+ socket.destroy();
19
+ resolve(false);
20
+ });
21
+ });
22
+ if (reachable) {
23
+ return {
24
+ reachable: true,
25
+ host,
26
+ port
27
+ };
28
+ }
29
+ await wait(500);
30
+ }
31
+ throw new MctError({
32
+ code: "TIMEOUT",
33
+ message: `Timed out waiting for ${host}:${port}`
34
+ }, 2);
35
+ }
@@ -0,0 +1,4 @@
1
+ import type { MctError } from "./errors.js";
2
+ export type OutputMode = "json" | "human";
3
+ export declare function printSuccess(data: unknown, mode: OutputMode): void;
4
+ export declare function printError(error: MctError, mode: OutputMode): void;
@@ -0,0 +1,23 @@
1
+ import process from "node:process";
2
+ import { inspect } from "node:util";
3
+ export function printSuccess(data, mode) {
4
+ if (mode === "human") {
5
+ if (typeof data === "string") {
6
+ process.stdout.write(`${data}\n`);
7
+ return;
8
+ }
9
+ process.stdout.write(`${inspect(data, { colors: true, depth: null })}\n`);
10
+ return;
11
+ }
12
+ process.stdout.write(`${JSON.stringify({ success: true, data }, null, 2)}\n`);
13
+ }
14
+ export function printError(error, mode) {
15
+ if (mode === "human") {
16
+ process.stderr.write(`[${error.code}] ${error.message}\n`);
17
+ if (error.details && typeof error.details === "object" && Object.keys(error.details).length > 0) {
18
+ process.stderr.write(`${inspect(error.details, { colors: true, depth: null })}\n`);
19
+ }
20
+ return;
21
+ }
22
+ process.stderr.write(`${JSON.stringify(error.toJSON(), null, 2)}\n`);
23
+ }
@@ -0,0 +1,3 @@
1
+ export declare function isProcessRunning(pid: number): boolean;
2
+ export declare function killProcessTree(pid: number, signal?: NodeJS.Signals): void;
3
+ export declare function getListeningPids(port: number): number[];
@@ -0,0 +1,32 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import process from "node:process";
3
+ export function isProcessRunning(pid) {
4
+ try {
5
+ process.kill(pid, 0);
6
+ return true;
7
+ }
8
+ catch {
9
+ return false;
10
+ }
11
+ }
12
+ export function killProcessTree(pid, signal = "SIGTERM") {
13
+ try {
14
+ process.kill(-pid, signal);
15
+ return;
16
+ }
17
+ catch {
18
+ process.kill(pid, signal);
19
+ }
20
+ }
21
+ export function getListeningPids(port) {
22
+ const result = spawnSync("lsof", ["-nP", "-t", `-iTCP:${port}`, "-sTCP:LISTEN"], {
23
+ encoding: "utf8"
24
+ });
25
+ if (result.status !== 0 || !result.stdout) {
26
+ return [];
27
+ }
28
+ return result.stdout
29
+ .split(/\s+/)
30
+ .map((entry) => Number(entry.trim()))
31
+ .filter((entry) => Number.isInteger(entry) && entry > 0);
32
+ }
@@ -0,0 +1,10 @@
1
+ export declare class StateStore {
2
+ private readonly rootDir;
3
+ constructor(rootDir: string);
4
+ getRootDir(): string;
5
+ ensure(): Promise<void>;
6
+ readJson<T>(name: string, fallback: T): Promise<T>;
7
+ writeJson(name: string, value: unknown): Promise<void>;
8
+ remove(name: string): Promise<void>;
9
+ }
10
+ export declare function resolveStateDir(stateDir: string | undefined, cwd: string): string;
@@ -0,0 +1,40 @@
1
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export class StateStore {
4
+ rootDir;
5
+ constructor(rootDir) {
6
+ this.rootDir = rootDir;
7
+ }
8
+ getRootDir() {
9
+ return this.rootDir;
10
+ }
11
+ async ensure() {
12
+ await mkdir(this.rootDir, { recursive: true });
13
+ }
14
+ async readJson(name, fallback) {
15
+ await this.ensure();
16
+ const target = path.join(this.rootDir, name);
17
+ try {
18
+ const raw = await readFile(target, "utf8");
19
+ return JSON.parse(raw);
20
+ }
21
+ catch {
22
+ return fallback;
23
+ }
24
+ }
25
+ async writeJson(name, value) {
26
+ await this.ensure();
27
+ const target = path.join(this.rootDir, name);
28
+ const content = JSON.stringify(value, null, 2);
29
+ await writeFile(target, `${content}\n`, "utf8");
30
+ }
31
+ async remove(name) {
32
+ await rm(path.join(this.rootDir, name), { force: true });
33
+ }
34
+ }
35
+ export function resolveStateDir(stateDir, cwd) {
36
+ if (!stateDir) {
37
+ return path.join(cwd, ".mct-state");
38
+ }
39
+ return path.isAbsolute(stateDir) ? stateDir : path.resolve(cwd, stateDir);
40
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@kzheart_/mc-pilot",
3
+ "version": "0.1.0",
4
+ "description": "Minecraft plugin/mod automated testing CLI – control a real Minecraft client to simulate player actions",
5
+ "type": "module",
6
+ "bin": {
7
+ "mct": "./bin/mct"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "dist/",
12
+ "data/",
13
+ "!dist/**/*.test.js",
14
+ "!dist/**/*.test.d.ts",
15
+ "!dist/**/*.e2e.test.js",
16
+ "!dist/**/*.e2e.test.d.ts"
17
+ ],
18
+ "keywords": [
19
+ "minecraft",
20
+ "testing",
21
+ "automation",
22
+ "fabric",
23
+ "plugin",
24
+ "mod",
25
+ "cli"
26
+ ],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/kzheart/mc-pilot.git",
30
+ "directory": "cli"
31
+ },
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=20"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc -p tsconfig.json",
38
+ "test": "tsc -p tsconfig.json && node --test dist/*.test.js dist/**/*.test.js",
39
+ "test:e2e": "tsc -p tsconfig.json && node --test dist/*.e2e.test.js dist/**/*.e2e.test.js"
40
+ },
41
+ "dependencies": {
42
+ "@xmcl/core": "^2.15.1",
43
+ "@xmcl/file-transfer": "^2.0.3",
44
+ "@xmcl/installer": "^6.1.2",
45
+ "commander": "^14.0.1",
46
+ "ws": "^8.19.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^24.5.2",
50
+ "@types/ws": "^8.18.1",
51
+ "tsx": "^4.20.5",
52
+ "typescript": "^5.9.2"
53
+ }
54
+ }