@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.
- package/dist/commands/events.d.ts +2 -0
- package/dist/commands/events.js +129 -0
- package/dist/index.js +2 -0
- package/package.json +1 -1
|
@@ -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();
|