cyclops-clientdevbridge-cli 0.1.1
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/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/artifacts.d.ts +56 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +58 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +348 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/compare.d.ts +27 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +209 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/context.d.ts +25 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +36 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/doctor.d.ts +31 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +234 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/hotswap.d.ts +7 -0
- package/dist/commands/hotswap.d.ts.map +1 -0
- package/dist/commands/hotswap.js +76 -0
- package/dist/commands/hotswap.js.map +1 -0
- package/dist/commands/input.d.ts +53 -0
- package/dist/commands/input.d.ts.map +1 -0
- package/dist/commands/input.js +204 -0
- package/dist/commands/input.js.map +1 -0
- package/dist/commands/inspect.d.ts +19 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +90 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/lifecycle.d.ts +29 -0
- package/dist/commands/lifecycle.d.ts.map +1 -0
- package/dist/commands/lifecycle.js +201 -0
- package/dist/commands/lifecycle.js.map +1 -0
- package/dist/commands/snapshot.d.ts +37 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +133 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/commands/world.d.ts +27 -0
- package/dist/commands/world.d.ts.map +1 -0
- package/dist/commands/world.js +203 -0
- package/dist/commands/world.js.map +1 -0
- package/dist/detect.d.ts +58 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +185 -0
- package/dist/detect.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +37 -0
- package/dist/errors.js.map +1 -0
- package/dist/hotswap.d.ts +58 -0
- package/dist/hotswap.d.ts.map +1 -0
- package/dist/hotswap.js +284 -0
- package/dist/hotswap.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/initscript.d.ts +49 -0
- package/dist/initscript.d.ts.map +1 -0
- package/dist/initscript.js +202 -0
- package/dist/initscript.js.map +1 -0
- package/dist/java.d.ts +47 -0
- package/dist/java.d.ts.map +1 -0
- package/dist/java.js +114 -0
- package/dist/java.js.map +1 -0
- package/dist/launcher.d.ts +104 -0
- package/dist/launcher.d.ts.map +1 -0
- package/dist/launcher.js +609 -0
- package/dist/launcher.js.map +1 -0
- package/dist/loopback.d.ts +17 -0
- package/dist/loopback.d.ts.map +1 -0
- package/dist/loopback.js +19 -0
- package/dist/loopback.js.map +1 -0
- package/dist/output.d.ts +21 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +41 -0
- package/dist/output.js.map +1 -0
- package/dist/paths.d.ts +23 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +47 -0
- package/dist/paths.js.map +1 -0
- package/dist/protocol/client.d.ts +35 -0
- package/dist/protocol/client.d.ts.map +1 -0
- package/dist/protocol/client.js +170 -0
- package/dist/protocol/client.js.map +1 -0
- package/dist/protocol/types.d.ts +114 -0
- package/dist/protocol/types.d.ts.map +1 -0
- package/dist/protocol/types.js +38 -0
- package/dist/protocol/types.js.map +1 -0
- package/dist/session.d.ts +77 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +79 -0
- package/dist/session.js.map +1 -0
- package/dist/snapshot/model.d.ts +327 -0
- package/dist/snapshot/model.d.ts.map +1 -0
- package/dist/snapshot/model.js +91 -0
- package/dist/snapshot/model.js.map +1 -0
- package/dist/snapshot/outline.d.ts +35 -0
- package/dist/snapshot/outline.d.ts.map +1 -0
- package/dist/snapshot/outline.js +135 -0
- package/dist/snapshot/outline.js.map +1 -0
- package/dist/xvfb.d.ts +30 -0
- package/dist/xvfb.d.ts.map +1 -0
- package/dist/xvfb.js +66 -0
- package/dist/xvfb.js.map +1 -0
- package/package.json +66 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { CliError, EXIT_OK, EXIT_PROTOCOL, EXIT_SESSION, ProtocolError } from './errors.js';
|
|
5
|
+
import { runRestart, runStart, runStatus, runStop, START_DEFAULTS } from './commands/lifecycle.js';
|
|
6
|
+
import { runLogs, runScreenshot } from './commands/inspect.js';
|
|
7
|
+
import { runDoctor } from './commands/doctor.js';
|
|
8
|
+
import { runBlock, runCommand, runGive, runInventory, runLook, runSetblock, runTeleport, runWorldLeave, runWorldList, runWorldLoad, runWorldReset, } from './commands/world.js';
|
|
9
|
+
import { runClick, runCloseScreen, runDrag, runEval, runHoldKey, runKey, runMouseMove, runOpenGui, runScroll, runType, runWaitFor, } from './commands/input.js';
|
|
10
|
+
import { runFind, runInspectGui, runSnapshot, runTooltip } from './commands/snapshot.js';
|
|
11
|
+
import { runCompare, runResize } from './commands/compare.js';
|
|
12
|
+
import { runHotswap } from './commands/hotswap.js';
|
|
13
|
+
const program = new Command();
|
|
14
|
+
program
|
|
15
|
+
.name('clientdevbridge')
|
|
16
|
+
.description('Launch and drive a Minecraft dev client from the shell, for coding agents.\n' +
|
|
17
|
+
'Screenshots are written to files and their paths printed; open them with your agent\'s file-reading tool.')
|
|
18
|
+
.version('0.1.0')
|
|
19
|
+
.option('-p, --project <dir>', 'the mod project to drive', process.cwd())
|
|
20
|
+
.option('--json', 'print the raw protocol result instead of readable text', false)
|
|
21
|
+
.option('-q, --quiet', 'print only the essential output', false)
|
|
22
|
+
.showHelpAfterError();
|
|
23
|
+
function globals() {
|
|
24
|
+
const options = program.opts();
|
|
25
|
+
const project = String(options['project']);
|
|
26
|
+
// Checked once, here, rather than in each command: a --project that does not exist otherwise
|
|
27
|
+
// reads as "Not running (no session recorded) in /typo", which is true and useless.
|
|
28
|
+
if (!fs.existsSync(project)) {
|
|
29
|
+
throw new CliError(`No such directory: ${project}`, 2, 'Check the path passed to --project.');
|
|
30
|
+
}
|
|
31
|
+
if (!fs.statSync(project).isDirectory()) {
|
|
32
|
+
throw new CliError(`--project must be a directory, but ${project} is a file.`, 2);
|
|
33
|
+
}
|
|
34
|
+
return { project, json: options['json'], quiet: options['quiet'] };
|
|
35
|
+
}
|
|
36
|
+
program
|
|
37
|
+
.command('start')
|
|
38
|
+
.description('launch the dev client and wait until it answers')
|
|
39
|
+
.option('--loader <loader>', 'fabric or neoforge (default: detected from the applied Gradle plugins)')
|
|
40
|
+
.option('--mc-version <version>', "Minecraft version (default: gradle.properties 'minecraft_version')")
|
|
41
|
+
.option('--clientdevbridge-version <version>', 'pin a specific ClientDevBridge mod build')
|
|
42
|
+
.option('--world <name>', 'join this singleplayer world on startup, via --quickPlaySingleplayer')
|
|
43
|
+
.option('--headed', 'use a real window even when $DISPLAY is unset', false)
|
|
44
|
+
.option('--port <port>', 'localhost port for the bridge', START_DEFAULTS.port)
|
|
45
|
+
.option('--timeout <seconds>', 'how long to wait for the client to come up', START_DEFAULTS.timeout)
|
|
46
|
+
.option('--width <px>', 'client window width', START_DEFAULTS.width)
|
|
47
|
+
.option('--height <px>', 'client window height', START_DEFAULTS.height)
|
|
48
|
+
.option('--jdwp-port <port>', 'also open a JDWP debug port, required by hotswap')
|
|
49
|
+
.option('--gradle-args <args>', 'extra arguments to pass to Gradle, space separated')
|
|
50
|
+
.option('--no-eval', 'do not enable the eval escape hatch')
|
|
51
|
+
.option('--no-pin-options', 'do not pin the determinism settings in options.txt')
|
|
52
|
+
.action(async (options) => runStart(globals(), options));
|
|
53
|
+
program
|
|
54
|
+
.command('stop')
|
|
55
|
+
.description('kill the dev client and its whole process group')
|
|
56
|
+
.option('--port <port>', 'the bridge port to check for an orphaned client', START_DEFAULTS.port)
|
|
57
|
+
.action(async (options) => runStop(globals(), options));
|
|
58
|
+
program
|
|
59
|
+
.command('status')
|
|
60
|
+
.description('report whether a client is running, and what it is showing')
|
|
61
|
+
.action(async () => runStatus(globals()));
|
|
62
|
+
program
|
|
63
|
+
.command('restart')
|
|
64
|
+
.description('stop, then start again with the same options')
|
|
65
|
+
.option('--loader <loader>', 'fabric or neoforge')
|
|
66
|
+
.option('--mc-version <version>', 'Minecraft version')
|
|
67
|
+
.option('--clientdevbridge-version <version>', 'pin a specific ClientDevBridge mod build')
|
|
68
|
+
.option('--world <name>', 'join this singleplayer world on startup')
|
|
69
|
+
.option('--headed', 'use a real window even when $DISPLAY is unset', false)
|
|
70
|
+
.option('--port <port>', 'localhost port for the bridge', START_DEFAULTS.port)
|
|
71
|
+
.option('--timeout <seconds>', 'how long to wait for the client to come up', START_DEFAULTS.timeout)
|
|
72
|
+
.option('--width <px>', 'client window width', START_DEFAULTS.width)
|
|
73
|
+
.option('--height <px>', 'client window height', START_DEFAULTS.height)
|
|
74
|
+
.option('--jdwp-port <port>', 'also open a JDWP debug port, required by hotswap')
|
|
75
|
+
.option('--gradle-args <args>', 'extra arguments to pass to Gradle, space separated')
|
|
76
|
+
.option('--no-eval', 'do not enable the eval escape hatch')
|
|
77
|
+
.option('--no-pin-options', 'do not pin the determinism settings in options.txt')
|
|
78
|
+
.action(async (options) => runRestart(globals(), options));
|
|
79
|
+
program
|
|
80
|
+
.command('screenshot')
|
|
81
|
+
.description('capture the framebuffer to a PNG and print its path')
|
|
82
|
+
.option('--name <name>', 'file name to write (default: a timestamp)')
|
|
83
|
+
.option('--region <x,y,w,h>', 'capture only this rectangle')
|
|
84
|
+
.option('--space <space>', 'coordinate space for --region: gui or pixel', 'gui')
|
|
85
|
+
.option('--scale <factor>', 'rescale the captured image')
|
|
86
|
+
.option('--after-ticks <n>', 'wait this many client ticks before capturing')
|
|
87
|
+
.action(async (options) => runScreenshot(globals(), options));
|
|
88
|
+
program
|
|
89
|
+
.command('logs')
|
|
90
|
+
.description("show the game's recent log lines, or Gradle's with --gradle")
|
|
91
|
+
.option('-n, --lines <n>', 'how many lines to show', '200')
|
|
92
|
+
.option('--filter <regex>', 'only lines matching this regular expression')
|
|
93
|
+
.option('--level <level>', 'lowest severity to show: trace, debug, info, warn, error, fatal', 'info')
|
|
94
|
+
.option('--gradle', 'read .clientdevbridge/gradle.log instead of the in-game log', false)
|
|
95
|
+
.action(async (options) => runLogs(globals(), options));
|
|
96
|
+
program
|
|
97
|
+
.command('wait')
|
|
98
|
+
.description('wait for ticks to pass, or for the game to reach a state')
|
|
99
|
+
.option('--ticks <n>', 'wait this many client ticks')
|
|
100
|
+
.option('--screen <name>', 'wait until this screen is open (simple or fully-qualified class name)')
|
|
101
|
+
.option('--expr <groovy>', 'wait until this expression is true')
|
|
102
|
+
.option('--chunk <x,y,z>', 'wait until the chunk containing this block is loaded')
|
|
103
|
+
.option('--in-world', 'wait until a world is joined', false)
|
|
104
|
+
.option('--timeout <ms>', 'give up after this many milliseconds', '10000')
|
|
105
|
+
.action(async (options) => runWaitFor(globals(), options));
|
|
106
|
+
program
|
|
107
|
+
.command('snapshot')
|
|
108
|
+
.description('print the widget tree of the open screen as an outline')
|
|
109
|
+
.option('--include-hidden', 'also list invisible widgets and pure decorations', false)
|
|
110
|
+
.option('--max-depth <n>', 'stop descending past this depth')
|
|
111
|
+
.action(async (options) => runSnapshot(globals(), options));
|
|
112
|
+
program
|
|
113
|
+
.command('find <text>')
|
|
114
|
+
.description('find widgets by label, type or path, and print where to click them')
|
|
115
|
+
.option('--type <type>', 'only widgets whose class name contains this')
|
|
116
|
+
.action(async (text, options) => runFind(globals(), text, options));
|
|
117
|
+
program
|
|
118
|
+
.command('click')
|
|
119
|
+
.description('click a point or a widget')
|
|
120
|
+
.option('--at <x,y>', 'click this point')
|
|
121
|
+
.option('--widget <text-or-path>', 'click the centre of the matching widget')
|
|
122
|
+
.option('--button <n>', '0 left, 1 right, 2 middle', '0')
|
|
123
|
+
.option('--space <space>', 'coordinate space for --at: gui or pixel', 'gui')
|
|
124
|
+
.action(async (options) => runClick(globals(), options));
|
|
125
|
+
program
|
|
126
|
+
.command('type <text>')
|
|
127
|
+
.description('type text into the focused widget')
|
|
128
|
+
.action(async (text) => runType(globals(), text));
|
|
129
|
+
program
|
|
130
|
+
.command('key <key>')
|
|
131
|
+
.description("press a key, e.g. 'E', 'ESCAPE' or 'GLFW_KEY_F3'")
|
|
132
|
+
.option('--action <action>', 'tap, press or release', 'tap')
|
|
133
|
+
.option('--modifiers <bits>', 'GLFW modifier bits: 1 shift, 2 ctrl, 4 alt')
|
|
134
|
+
.action(async (key, options) => runKey(globals(), key, options));
|
|
135
|
+
program
|
|
136
|
+
.command('hold-key <key>')
|
|
137
|
+
.description('hold a bound key down for a number of ticks, for movement')
|
|
138
|
+
.requiredOption('--ticks <n>', 'how many client ticks to hold it')
|
|
139
|
+
.action(async (key, options) => runHoldKey(globals(), key, options));
|
|
140
|
+
program
|
|
141
|
+
.command('mouse-move <at>')
|
|
142
|
+
.description("move the mouse to 'x,y'")
|
|
143
|
+
.option('--space <space>', 'gui or pixel', 'gui')
|
|
144
|
+
.action(async (at, options) => runMouseMove(globals(), at, options));
|
|
145
|
+
program
|
|
146
|
+
.command('scroll')
|
|
147
|
+
.description('scroll at a point')
|
|
148
|
+
.requiredOption('--at <x,y>', 'where to scroll')
|
|
149
|
+
.requiredOption('--dy <amount>', 'vertical scroll amount')
|
|
150
|
+
.option('--dx <amount>', 'horizontal scroll amount', '0')
|
|
151
|
+
.option('--space <space>', 'gui or pixel', 'gui')
|
|
152
|
+
.action(async (options) => runScroll(globals(), options));
|
|
153
|
+
program
|
|
154
|
+
.command('drag')
|
|
155
|
+
.description('press, move and release, for sliders and item dragging')
|
|
156
|
+
.requiredOption('--from <x,y>', 'where the drag starts')
|
|
157
|
+
.requiredOption('--to <x,y>', 'where it ends')
|
|
158
|
+
.option('--button <n>', '0 left, 1 right', '0')
|
|
159
|
+
.option('--steps <n>', 'how many intermediate move events to send', '8')
|
|
160
|
+
.option('--space <space>', 'gui or pixel', 'gui')
|
|
161
|
+
.action(async (options) => runDrag(globals(), options));
|
|
162
|
+
program
|
|
163
|
+
.command('tooltip')
|
|
164
|
+
.description('read the tooltip shown at a point or over a widget')
|
|
165
|
+
.option('--at <x,y>', 'where to hover')
|
|
166
|
+
.option('--widget <text-or-path>', 'hover the matching widget')
|
|
167
|
+
.action(async (options) => runTooltip(globals(), options));
|
|
168
|
+
program
|
|
169
|
+
.command('open-gui <x> <y> <z>')
|
|
170
|
+
.description("right-click a block to open its GUI")
|
|
171
|
+
.option('--no-approach', 'do not teleport the player within reach first')
|
|
172
|
+
.action(async (x, y, z, options) => runOpenGui(globals(), x, y, z, options));
|
|
173
|
+
program
|
|
174
|
+
.command('close-screen')
|
|
175
|
+
.description('close the open screen')
|
|
176
|
+
.action(async () => runCloseScreen(globals()));
|
|
177
|
+
program
|
|
178
|
+
.command('inspect-gui <x> <y> <z>')
|
|
179
|
+
.description('open a block GUI, print its outline, and write a screenshot — the usual starting point')
|
|
180
|
+
.option('--no-approach', 'do not teleport the player within reach first')
|
|
181
|
+
.option('--name <name>', 'screenshot file name')
|
|
182
|
+
.action(async (x, y, z, options) => runInspectGui(globals(), x, y, z, options));
|
|
183
|
+
program
|
|
184
|
+
.command('world-reset')
|
|
185
|
+
.description('delete and recreate the test world: creative superflat, no daylight cycle, no mobs')
|
|
186
|
+
.option('--name <name>', 'world folder name')
|
|
187
|
+
.option('--template <name>', 'copy clientdevbridge/templates/<name> instead of generating one')
|
|
188
|
+
.option('--setup <command>', 'run this command once the world is ready')
|
|
189
|
+
.action(async (options) => runWorldReset(globals(), options));
|
|
190
|
+
program
|
|
191
|
+
.command('world-load <name>')
|
|
192
|
+
.description('load an existing singleplayer world')
|
|
193
|
+
.action(async (name) => runWorldLoad(globals(), name));
|
|
194
|
+
program
|
|
195
|
+
.command('world-leave')
|
|
196
|
+
.description('leave the current world')
|
|
197
|
+
.action(async () => runWorldLeave(globals()));
|
|
198
|
+
program
|
|
199
|
+
.command('world-list')
|
|
200
|
+
.description('list the singleplayer worlds in the run directory')
|
|
201
|
+
.action(async () => runWorldList(globals()));
|
|
202
|
+
program
|
|
203
|
+
.command('command <command>')
|
|
204
|
+
.description('run a Minecraft command on the integrated server and print its output')
|
|
205
|
+
.action(async (command) => runCommand(globals(), command));
|
|
206
|
+
program
|
|
207
|
+
.command('block <x> <y> <z>')
|
|
208
|
+
.description('describe the block at a position')
|
|
209
|
+
.option('--nbt', 'include the block entity NBT the client knows about', false)
|
|
210
|
+
.action(async (x, y, z, options) => runBlock(globals(), x, y, z, options));
|
|
211
|
+
program
|
|
212
|
+
.command('setblock <x> <y> <z> <block>')
|
|
213
|
+
.description('place a block')
|
|
214
|
+
.action(async (x, y, z, block) => runSetblock(globals(), x, y, z, block));
|
|
215
|
+
program
|
|
216
|
+
.command('give <item> [count]')
|
|
217
|
+
.description('give the player an item')
|
|
218
|
+
.action(async (item, count) => runGive(globals(), item, count ?? '1'));
|
|
219
|
+
program
|
|
220
|
+
.command('teleport <x> <y> <z>')
|
|
221
|
+
.description('teleport the player')
|
|
222
|
+
.option('--yaw <degrees>', 'facing yaw')
|
|
223
|
+
.option('--pitch <degrees>', 'facing pitch')
|
|
224
|
+
.action(async (x, y, z, options) => runTeleport(globals(), x, y, z, options));
|
|
225
|
+
program
|
|
226
|
+
.command('look')
|
|
227
|
+
.description('point the camera')
|
|
228
|
+
.option('--at <x,y,z>', 'look at this block position')
|
|
229
|
+
.option('--yaw <degrees>', 'set yaw')
|
|
230
|
+
.option('--pitch <degrees>', 'set pitch')
|
|
231
|
+
.action(async (options) => runLook(globals(), options));
|
|
232
|
+
program
|
|
233
|
+
.command('inventory')
|
|
234
|
+
.description("list the player's inventory")
|
|
235
|
+
.action(async () => runInventory(globals()));
|
|
236
|
+
program
|
|
237
|
+
.command('compare <name>')
|
|
238
|
+
.description('compare a screenshot against a committed golden image')
|
|
239
|
+
.option('--region <x,y,w,h>', 'compare only this rectangle; applied to the golden too, so it needs no re-record')
|
|
240
|
+
.option('--space <space>', 'coordinate space for --region: gui or pixel', 'gui')
|
|
241
|
+
.option('--threshold <pct>', 'percentage of differing pixels still counted as a match', '0.1')
|
|
242
|
+
.option('--pixel-threshold <0-1>', 'per-pixel colour tolerance passed to pixelmatch', '0.1')
|
|
243
|
+
.option('--after-ticks <n>', 'wait this many ticks before capturing')
|
|
244
|
+
.option('--renderer <name>', 'golden set to use, instead of detecting it from GL_RENDERER')
|
|
245
|
+
.option('--update', 'write the current screenshot as the golden image instead of comparing', false)
|
|
246
|
+
.action(async (name, options) => runCompare(globals(), name, options));
|
|
247
|
+
program
|
|
248
|
+
.command('resize')
|
|
249
|
+
.description('set the window size and GUI scale, for reproducible screenshots')
|
|
250
|
+
.requiredOption('--width <px>', 'window width (required)')
|
|
251
|
+
.requiredOption('--height <px>', 'window height (required)')
|
|
252
|
+
.option('--gui-scale <n>', 'fixed GUI scale, or 0 for automatic; --width and --height are still required')
|
|
253
|
+
.action(async (options) => runResize(globals(), options));
|
|
254
|
+
program
|
|
255
|
+
.command('eval <code>')
|
|
256
|
+
.description('evaluate a Groovy expression with mc, player, level, screen and server bound')
|
|
257
|
+
.action(async (code) => runEval(globals(), code));
|
|
258
|
+
program
|
|
259
|
+
.command('hotswap')
|
|
260
|
+
.description('recompile the project and redefine the changed classes in the running client')
|
|
261
|
+
.option('--no-compile', 'skip the Gradle compile and swap whatever is already built')
|
|
262
|
+
.option('--baseline', 'record the current classes as the baseline without swapping', false)
|
|
263
|
+
.action(async (options) => runHotswap(globals(), options));
|
|
264
|
+
program
|
|
265
|
+
.command('doctor')
|
|
266
|
+
.description('check that this machine can build and launch a dev client')
|
|
267
|
+
.option('--loader <loader>', 'check for this loader specifically: fabric or neoforge')
|
|
268
|
+
.option('--no-network', 'skip the network reachability probes')
|
|
269
|
+
.action(async (options) => {
|
|
270
|
+
const code = await runDoctor(globals(), options);
|
|
271
|
+
process.exitCode = code;
|
|
272
|
+
});
|
|
273
|
+
/**
|
|
274
|
+
* Piping into `head` or `grep -q` closes stdout while the CLI is still writing, and Node turns
|
|
275
|
+
* that into a fatal EPIPE. Since piping is exactly how an agent reads this output, a broken pipe
|
|
276
|
+
* has to mean "the reader has what it needs", not a crash.
|
|
277
|
+
*/
|
|
278
|
+
function ignoreBrokenPipe() {
|
|
279
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
280
|
+
stream.on('error', (error) => {
|
|
281
|
+
if (error.code === 'EPIPE') {
|
|
282
|
+
process.exit(process.exitCode ?? EXIT_OK);
|
|
283
|
+
}
|
|
284
|
+
throw error;
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Marker that hides a negative number from commander's option parser.
|
|
290
|
+
*
|
|
291
|
+
* Minecraft coordinates are routinely negative, and `teleport -10 5 -10` was rejected with
|
|
292
|
+
* "unknown option '-10'". `--` is not a usable answer: it has to come before every operand, so it
|
|
293
|
+
* would swallow the command's own flags too, and no reasonable caller thinks to write it. Nothing
|
|
294
|
+
* in this CLI takes a numeric option name, so a token that looks like a negative number is one.
|
|
295
|
+
*/
|
|
296
|
+
const NEGATIVE_NUMBER_MARKER = '\u0000';
|
|
297
|
+
function hideNegativeNumbers(argv) {
|
|
298
|
+
return argv.map((arg) => (/^-\d+(\.\d+)?$/.test(arg) ? `${NEGATIVE_NUMBER_MARKER}${arg}` : arg));
|
|
299
|
+
}
|
|
300
|
+
function reveal(value) {
|
|
301
|
+
if (typeof value === 'string' && value.startsWith(NEGATIVE_NUMBER_MARKER)) {
|
|
302
|
+
return value.slice(NEGATIVE_NUMBER_MARKER.length);
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(value)) {
|
|
305
|
+
return value.map(reveal);
|
|
306
|
+
}
|
|
307
|
+
return value;
|
|
308
|
+
}
|
|
309
|
+
// Undone in one place, after commander has finished parsing and before any command sees its
|
|
310
|
+
// arguments, so no individual command has to know this happened.
|
|
311
|
+
program.hook('preAction', (_parent, command) => {
|
|
312
|
+
const withArgs = command;
|
|
313
|
+
withArgs.processedArgs = withArgs.processedArgs.map(reveal);
|
|
314
|
+
const options = command.opts();
|
|
315
|
+
for (const [key, value] of Object.entries(options)) {
|
|
316
|
+
options[key] = reveal(value);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
async function main() {
|
|
320
|
+
ignoreBrokenPipe();
|
|
321
|
+
try {
|
|
322
|
+
await program.parseAsync(hideNegativeNumbers(process.argv));
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
if (error instanceof ProtocolError) {
|
|
326
|
+
process.stderr.write(`error: ${error.message}\n`);
|
|
327
|
+
if (error.hint !== undefined) {
|
|
328
|
+
process.stderr.write(`${error.hint}\n`);
|
|
329
|
+
}
|
|
330
|
+
process.exitCode = EXIT_PROTOCOL;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (error instanceof CliError) {
|
|
334
|
+
process.stderr.write(`error: ${error.message}\n`);
|
|
335
|
+
if (error.hint !== undefined) {
|
|
336
|
+
process.stderr.write(`${error.hint}\n`);
|
|
337
|
+
}
|
|
338
|
+
process.exitCode = error.exitCode;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
process.stderr.write(`error: ${error.message}\n`);
|
|
342
|
+
process.exitCode = EXIT_SESSION;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
process.exitCode = process.exitCode ?? EXIT_OK;
|
|
346
|
+
}
|
|
347
|
+
void main();
|
|
348
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type GlobalOptions } from './context.js';
|
|
2
|
+
export interface CompareOptions {
|
|
3
|
+
readonly region?: string | undefined;
|
|
4
|
+
readonly space: string;
|
|
5
|
+
readonly threshold: string;
|
|
6
|
+
readonly pixelThreshold: string;
|
|
7
|
+
readonly update: boolean;
|
|
8
|
+
readonly afterTicks?: string | undefined;
|
|
9
|
+
readonly renderer?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Golden images are keyed by renderer.
|
|
13
|
+
*
|
|
14
|
+
* Software rasterisation and a real GPU do not produce identical pixels — different texture
|
|
15
|
+
* filtering, different rounding — and no single tolerance covers both without also hiding real
|
|
16
|
+
* regressions. Keeping a set per renderer means each one can be compared strictly.
|
|
17
|
+
*/
|
|
18
|
+
export declare function goldenPathFor(goldenDir: string, name: string, renderer: string): string;
|
|
19
|
+
/** Normalises a GL_RENDERER string into a short, stable directory name. */
|
|
20
|
+
export declare function rendererKey(raw: string | null | undefined): string;
|
|
21
|
+
export declare function runCompare(global: GlobalOptions, name: string, options: CompareOptions): Promise<void>;
|
|
22
|
+
export declare function runResize(global: GlobalOptions, options: {
|
|
23
|
+
width: string;
|
|
24
|
+
height: string;
|
|
25
|
+
guiScale?: string | undefined;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/commands/compare.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,cAAc,CAAC;AAG9D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEvF;AAED,2EAA2E;AAC3E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CASlE;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA0I5G;AAgBD,wBAAsB,SAAS,CAC7B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACxE,OAAO,CAAC,IAAI,CAAC,CAgBf"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { PNG } from 'pngjs';
|
|
4
|
+
import pixelmatch from 'pixelmatch';
|
|
5
|
+
import { CliError } from '../errors.js';
|
|
6
|
+
import { line, printJson, printPath } from '../output.js';
|
|
7
|
+
import { withClient } from './context.js';
|
|
8
|
+
import { parseRegion } from './inspect.js';
|
|
9
|
+
/**
|
|
10
|
+
* Golden images are keyed by renderer.
|
|
11
|
+
*
|
|
12
|
+
* Software rasterisation and a real GPU do not produce identical pixels — different texture
|
|
13
|
+
* filtering, different rounding — and no single tolerance covers both without also hiding real
|
|
14
|
+
* regressions. Keeping a set per renderer means each one can be compared strictly.
|
|
15
|
+
*/
|
|
16
|
+
export function goldenPathFor(goldenDir, name, renderer) {
|
|
17
|
+
return path.join(goldenDir, renderer, `${name}.png`);
|
|
18
|
+
}
|
|
19
|
+
/** Normalises a GL_RENDERER string into a short, stable directory name. */
|
|
20
|
+
export function rendererKey(raw) {
|
|
21
|
+
if (raw === null || raw === undefined || raw.length === 0) {
|
|
22
|
+
return 'unknown';
|
|
23
|
+
}
|
|
24
|
+
const lowered = raw.toLowerCase();
|
|
25
|
+
if (lowered.includes('llvmpipe') || lowered.includes('softpipe') || lowered.includes('swrast')) {
|
|
26
|
+
return 'llvmpipe';
|
|
27
|
+
}
|
|
28
|
+
return 'gpu';
|
|
29
|
+
}
|
|
30
|
+
export async function runCompare(global, name, options) {
|
|
31
|
+
await withClient(global, async ({ client, paths }) => {
|
|
32
|
+
// --region deliberately does not reach the screenshot call. It is a comparison-time filter --
|
|
33
|
+
// "ignore everything outside this rectangle" -- and cropping the capture but not the golden is
|
|
34
|
+
// what made it unusable: it always failed with a size mismatch that blamed the window size.
|
|
35
|
+
// Both images are cropped, below, by the same rectangle.
|
|
36
|
+
const params = {};
|
|
37
|
+
const rawRegion = options.region === undefined ? null : parseRegion(options.region, options.space);
|
|
38
|
+
if (options.afterTicks !== undefined) {
|
|
39
|
+
params['afterTicks'] = Number(options.afterTicks);
|
|
40
|
+
}
|
|
41
|
+
const shot = await client.call('screenshot', params);
|
|
42
|
+
const actualPng = Buffer.from(String(shot['png']), 'base64');
|
|
43
|
+
const renderer = options.renderer ?? rendererKey(await readRenderer(client));
|
|
44
|
+
const goldenPath = goldenPathFor(paths.golden, name, renderer);
|
|
45
|
+
if (options.update) {
|
|
46
|
+
// The golden is stored whole even when --region is given, so the same golden can later be
|
|
47
|
+
// compared under any region, or none.
|
|
48
|
+
fs.mkdirSync(path.dirname(goldenPath), { recursive: true });
|
|
49
|
+
fs.writeFileSync(goldenPath, actualPng);
|
|
50
|
+
if (global.json) {
|
|
51
|
+
printJson({ updated: true, golden: goldenPath, renderer });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
line(`Wrote the golden image for '${name}' (renderer ${renderer}).`);
|
|
55
|
+
printPath(path.resolve(goldenPath));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!fs.existsSync(goldenPath)) {
|
|
59
|
+
throw new CliError(`There is no golden image for '${name}' at ${goldenPath}.`, 1, `Create it with: clientdevbridge compare ${name} --update`);
|
|
60
|
+
}
|
|
61
|
+
const wholeGolden = PNG.sync.read(fs.readFileSync(goldenPath));
|
|
62
|
+
const wholeActual = PNG.sync.read(actualPng);
|
|
63
|
+
// Both images are in pixels; a region given in GUI space has to be scaled to match, using the
|
|
64
|
+
// scale the client reported with this very screenshot rather than a remembered one.
|
|
65
|
+
const region = rawRegion === null ? null : toPixelRegion(rawRegion, Number(shot['guiScale'] ?? 1));
|
|
66
|
+
if (region !== null) {
|
|
67
|
+
const tooBig = [
|
|
68
|
+
{ label: 'golden image', image: wholeGolden },
|
|
69
|
+
{ label: 'screenshot', image: wholeActual },
|
|
70
|
+
].find(({ image }) => region.x + region.width > image.width || region.y + region.height > image.height);
|
|
71
|
+
if (tooBig !== undefined) {
|
|
72
|
+
throw new CliError(`--region ${region.x},${region.y},${region.width},${region.height} does not fit inside the ` +
|
|
73
|
+
`${tooBig.label}, which is ${tooBig.image.width}x${tooBig.image.height}.`, 1, 'Regions are in pixel space unless --space gui is given.');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const golden = region === null ? wholeGolden : crop(wholeGolden, region);
|
|
77
|
+
const actual = region === null ? wholeActual : crop(wholeActual, region);
|
|
78
|
+
if (golden.width !== actual.width || golden.height !== actual.height) {
|
|
79
|
+
const actualPath = path.join(paths.diffs, `${name}-actual.png`);
|
|
80
|
+
fs.mkdirSync(paths.diffs, { recursive: true });
|
|
81
|
+
fs.writeFileSync(actualPath, actualPng);
|
|
82
|
+
if (global.json) {
|
|
83
|
+
printJson({
|
|
84
|
+
match: false,
|
|
85
|
+
reason: 'size',
|
|
86
|
+
golden: { width: golden.width, height: golden.height },
|
|
87
|
+
actual: { width: actual.width, height: actual.height },
|
|
88
|
+
actualPath,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
line(`Size mismatch: the golden image is ${golden.width}x${golden.height} but the screenshot is ` +
|
|
93
|
+
`${actual.width}x${actual.height}.`);
|
|
94
|
+
line('Pin the window with `clientdevbridge resize` before comparing, or re-record with --update.');
|
|
95
|
+
line('(--region is applied to both images, so it is never the cause of a size mismatch.)');
|
|
96
|
+
line('The screenshot that did not match was written to:');
|
|
97
|
+
printPath(path.resolve(actualPath));
|
|
98
|
+
}
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const diff = new PNG({ width: golden.width, height: golden.height });
|
|
103
|
+
const pixelsDiff = pixelmatch(golden.data, actual.data, diff.data, golden.width, golden.height, {
|
|
104
|
+
threshold: Number(options.pixelThreshold),
|
|
105
|
+
});
|
|
106
|
+
const total = golden.width * golden.height;
|
|
107
|
+
const percentage = (pixelsDiff / total) * 100;
|
|
108
|
+
const allowed = Number(options.threshold);
|
|
109
|
+
const match = percentage <= allowed;
|
|
110
|
+
let diffPath = null;
|
|
111
|
+
if (!match) {
|
|
112
|
+
fs.mkdirSync(paths.diffs, { recursive: true });
|
|
113
|
+
// Timestamped, so comparing twice does not throw away the first failure. Chasing a
|
|
114
|
+
// regression usually means running compare several times, and the run before last is
|
|
115
|
+
// exactly the one worth looking at again.
|
|
116
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').replace('Z', '');
|
|
117
|
+
diffPath = path.join(paths.diffs, `${name}_${stamp}-diff.png`);
|
|
118
|
+
fs.writeFileSync(diffPath, PNG.sync.write(diff));
|
|
119
|
+
fs.writeFileSync(path.join(paths.diffs, `${name}_${stamp}-actual.png`), actualPng);
|
|
120
|
+
pruneDiffs(paths.diffs, name);
|
|
121
|
+
}
|
|
122
|
+
if (global.json) {
|
|
123
|
+
printJson({ match, pixelsDiff, pct: percentage, threshold: allowed, renderer, golden: goldenPath, diffPath });
|
|
124
|
+
}
|
|
125
|
+
else if (match) {
|
|
126
|
+
line(`${name}: matches (${pixelsDiff} of ${total} pixels differ, ${percentage.toFixed(3)}% <= ${allowed}%).`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
line(`${name}: DIFFERS — ${pixelsDiff} of ${total} pixels, ${percentage.toFixed(3)}% > ${allowed}%.` +
|
|
130
|
+
` Golden: ${goldenPath}`);
|
|
131
|
+
printPath(path.resolve(diffPath));
|
|
132
|
+
if (!global.quiet) {
|
|
133
|
+
line(`The screenshot itself is beside it, as ${path.basename(diffPath).replace(/-diff\.png$/, '-actual.png')}`);
|
|
134
|
+
}
|
|
135
|
+
if (!global.quiet) {
|
|
136
|
+
line('If the diff is concentrated on an animated block (lava, fire, water, a portal), on a ' +
|
|
137
|
+
'toast popup, or on the chat lines that commands like give and setblock leave on ' +
|
|
138
|
+
'screen for ten seconds, that is animation rather than a regression: wait it out, ' +
|
|
139
|
+
'raise --threshold, or pass --region to compare only the part that should hold still.');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!match) {
|
|
143
|
+
process.exitCode = 1;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Asks the game which GL renderer it is on, so golden sets can be kept apart.
|
|
149
|
+
* Falls back to 'unknown' rather than failing: a missing renderer name is not worth an error.
|
|
150
|
+
*/
|
|
151
|
+
async function readRenderer(client) {
|
|
152
|
+
try {
|
|
153
|
+
const result = await client.call('status');
|
|
154
|
+
const renderer = result['glRenderer'];
|
|
155
|
+
return typeof renderer === 'string' ? renderer : null;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export async function runResize(global, options) {
|
|
162
|
+
await withClient(global, async ({ client }) => {
|
|
163
|
+
const params = { width: Number(options.width), height: Number(options.height) };
|
|
164
|
+
if (options.guiScale !== undefined) {
|
|
165
|
+
params['guiScale'] = Number(options.guiScale);
|
|
166
|
+
}
|
|
167
|
+
const result = await client.call('window.resize', params);
|
|
168
|
+
if (global.json) {
|
|
169
|
+
printJson(result);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
line(`window ${result['pixelWidth']}x${result['pixelHeight']}px, ` +
|
|
173
|
+
`gui ${result['guiWidth']}x${result['guiHeight']} @ scale ${result['guiScale']}`);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/** Keeps the newest few diffs per golden, so the directory does not grow without limit. */
|
|
177
|
+
function pruneDiffs(directory, name, keep = 10) {
|
|
178
|
+
const suffixes = ['-diff.png', '-actual.png'];
|
|
179
|
+
for (const suffix of suffixes) {
|
|
180
|
+
const files = fs
|
|
181
|
+
.readdirSync(directory)
|
|
182
|
+
.filter((entry) => entry.startsWith(`${name}_`) && entry.endsWith(suffix))
|
|
183
|
+
.sort();
|
|
184
|
+
for (const stale of files.slice(0, Math.max(0, files.length - keep))) {
|
|
185
|
+
try {
|
|
186
|
+
fs.unlinkSync(path.join(directory, stale));
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Someone else removed it, or it is open elsewhere; either way not worth failing a compare.
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function toPixelRegion(raw, guiScale) {
|
|
195
|
+
const factor = raw['space'] === 'pixel' ? 1 : guiScale;
|
|
196
|
+
return {
|
|
197
|
+
x: Math.round(Number(raw['x']) * factor),
|
|
198
|
+
y: Math.round(Number(raw['y']) * factor),
|
|
199
|
+
width: Math.round(Number(raw['w']) * factor),
|
|
200
|
+
height: Math.round(Number(raw['h']) * factor),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/** Cuts a rectangle out of a decoded PNG, so both sides of a comparison can be narrowed alike. */
|
|
204
|
+
function crop(image, region) {
|
|
205
|
+
const out = new PNG({ width: region.width, height: region.height });
|
|
206
|
+
PNG.bitblt(image, out, region.x, region.y, region.width, region.height, 0, 0);
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../src/commands/compare.ts"],"names":[],"mappings":"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"}
|