@kzheart_/mc-pilot 0.4.1 → 0.5.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.
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createEventsCommand(): Command;
@@ -0,0 +1,129 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { Command } from "commander";
4
+ import { MctError } from "../util/errors.js";
5
+ import { resolveMctHome } from "../util/paths.js";
6
+ import { wrapCommand } from "../util/command.js";
7
+ function resolveEventsFile(clientName) {
8
+ if (!clientName) {
9
+ throw new MctError({
10
+ code: "INVALID_PARAMS",
11
+ message: "Client name is required. Use --client <name> or set an active profile."
12
+ }, 4);
13
+ }
14
+ return path.join(resolveMctHome(), "logs", clientName, "events.jsonl");
15
+ }
16
+ function parseSince(raw, nowMs) {
17
+ if (!raw)
18
+ return undefined;
19
+ const trimmed = raw.trim();
20
+ // 支持 "30s" / "5m" / "1h" / "200ms" / epoch 毫秒
21
+ const m = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(trimmed);
22
+ if (!m) {
23
+ throw new MctError({ code: "INVALID_PARAMS", message: `Invalid --since value: ${raw}` }, 4);
24
+ }
25
+ const value = Number(m[1]);
26
+ const unit = m[2];
27
+ if (!unit) {
28
+ // epoch millis
29
+ return value;
30
+ }
31
+ const multipliers = { ms: 1, s: 1000, m: 60_000, h: 3_600_000 };
32
+ return nowMs - value * multipliers[unit];
33
+ }
34
+ function readAllEvents(filePath) {
35
+ if (!existsSync(filePath))
36
+ return [];
37
+ const raw = readFileSync(filePath, "utf8");
38
+ const lines = raw.split("\n").filter((line) => line.length > 0);
39
+ const out = [];
40
+ for (const line of lines) {
41
+ try {
42
+ out.push(JSON.parse(line));
43
+ }
44
+ catch {
45
+ // 跳过损坏行
46
+ }
47
+ }
48
+ return out;
49
+ }
50
+ export function createEventsCommand() {
51
+ const command = new Command("events").description("Inspect the client event log (written by the mod to ~/.mct/logs/<client>/events.jsonl)");
52
+ command
53
+ .command("list")
54
+ .description("Print events. Defaults to the last 20 for the active client.")
55
+ .option("--tail <n>", "Show the last N events (default 20)", (v) => Number(v))
56
+ .option("--since <duration>", "Only show events since duration ago (e.g. 30s, 5m, 1h) or epoch ms")
57
+ .option("--type <types>", "Comma-separated list of event types to include")
58
+ .option("--all", "Show all events (ignore --tail)")
59
+ .option("--file <path>", "Override the log file path")
60
+ .action(wrapCommand(async (context, { options, globalOptions }) => {
61
+ const clientName = globalOptions.client ?? context.activeProfile?.clients[0];
62
+ const filePath = options.file ?? resolveEventsFile(clientName);
63
+ const events = readAllEvents(filePath);
64
+ let filtered = events;
65
+ const sinceMs = parseSince(options.since, Date.now());
66
+ if (sinceMs !== undefined) {
67
+ filtered = filtered.filter((e) => e.t >= sinceMs);
68
+ }
69
+ if (options.type) {
70
+ const wanted = new Set(options.type.split(",").map((s) => s.trim()).filter(Boolean));
71
+ filtered = filtered.filter((e) => wanted.has(e.type));
72
+ }
73
+ if (!options.all) {
74
+ const tail = options.tail ?? 20;
75
+ if (filtered.length > tail) {
76
+ filtered = filtered.slice(filtered.length - tail);
77
+ }
78
+ }
79
+ return {
80
+ file: filePath,
81
+ total: events.length,
82
+ returned: filtered.length,
83
+ events: filtered
84
+ };
85
+ }));
86
+ command
87
+ .command("tail")
88
+ .description("Shortcut for `events list --tail N`")
89
+ .argument("[n]", "Number of events (default 20)")
90
+ .option("--type <types>", "Comma-separated event types to include")
91
+ .option("--file <path>", "Override the log file path")
92
+ .action(wrapCommand(async (context, { args, options, globalOptions }) => {
93
+ const tail = args[0] ? Number(args[0]) : 20;
94
+ const clientName = globalOptions.client ?? context.activeProfile?.clients[0];
95
+ const filePath = options.file ?? resolveEventsFile(clientName);
96
+ let events = readAllEvents(filePath);
97
+ if (options.type) {
98
+ const wanted = new Set(options.type.split(",").map((s) => s.trim()).filter(Boolean));
99
+ events = events.filter((e) => wanted.has(e.type));
100
+ }
101
+ if (events.length > tail)
102
+ events = events.slice(events.length - tail);
103
+ return { file: filePath, returned: events.length, events };
104
+ }));
105
+ command
106
+ .command("clear")
107
+ .description("Truncate the event log for the active client")
108
+ .option("--file <path>", "Override the log file path")
109
+ .action(wrapCommand(async (context, { options, globalOptions }) => {
110
+ const { truncateSync, existsSync: exists, mkdirSync } = await import("node:fs");
111
+ const clientName = globalOptions.client ?? context.activeProfile?.clients[0];
112
+ const filePath = options.file ?? resolveEventsFile(clientName);
113
+ if (exists(filePath)) {
114
+ truncateSync(filePath, 0);
115
+ return { cleared: true, file: filePath };
116
+ }
117
+ // ensure directory exists for future writes
118
+ mkdirSync(path.dirname(filePath), { recursive: true });
119
+ return { cleared: false, reason: "file_not_found", file: filePath };
120
+ }));
121
+ command
122
+ .command("path")
123
+ .description("Print the expected path to the event log for the active client")
124
+ .action(wrapCommand(async (context, { globalOptions }) => {
125
+ const clientName = globalOptions.client ?? context.activeProfile?.clients[0];
126
+ return { file: resolveEventsFile(clientName), exists: existsSync(resolveEventsFile(clientName)) };
127
+ }));
128
+ return command;
129
+ }
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { createChatCommand } from "./commands/chat.js";
9
9
  import { createCombatCommand } from "./commands/combat.js";
10
10
  import { createAnvilCommand, createCraftCommand, createEnchantCommand, createTradeCommand } from "./commands/craft.js";
11
11
  import { createEntityCommand } from "./commands/entity.js";
12
+ import { createEventsCommand } from "./commands/events.js";
12
13
  import { createGuiCommand } from "./commands/gui.js";
13
14
  import { createHudCommand } from "./commands/hud.js";
14
15
  import { createInputCommand } from "./commands/input.js";
@@ -90,6 +91,7 @@ export function buildProgram() {
90
91
  program.addCommand(createEnchantCommand());
91
92
  program.addCommand(createTradeCommand());
92
93
  program.addCommand(createWaitCommand());
94
+ program.addCommand(createEventsCommand());
93
95
  return program;
94
96
  }
95
97
  const program = buildProgram();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kzheart_/mc-pilot",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Minecraft plugin/mod automated testing CLI – control a real Minecraft client to simulate player actions",
5
5
  "type": "module",
6
6
  "bin": {