@bkm016/calcite 0.0.0-stage → 0.2.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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +416 -2
  3. package/THIRD_PARTY_NOTICES.md +44 -0
  4. package/dist/accounts.d.ts +46 -0
  5. package/dist/accounts.js +194 -0
  6. package/dist/accounts.js.map +1 -0
  7. package/dist/cli.d.ts +2 -0
  8. package/dist/cli.js +332 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/client-core.d.ts +66 -0
  11. package/dist/client-core.js +530 -0
  12. package/dist/client-core.js.map +1 -0
  13. package/dist/client.d.ts +127 -0
  14. package/dist/client.js +185 -0
  15. package/dist/client.js.map +1 -0
  16. package/dist/display.d.ts +23 -0
  17. package/dist/display.js +164 -0
  18. package/dist/display.js.map +1 -0
  19. package/dist/hmc.d.ts +53 -0
  20. package/dist/hmc.js +168 -0
  21. package/dist/hmc.js.map +1 -0
  22. package/dist/index.d.ts +11 -0
  23. package/dist/index.js +8 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/install.d.ts +48 -0
  26. package/dist/install.js +119 -0
  27. package/dist/install.js.map +1 -0
  28. package/dist/java.d.ts +37 -0
  29. package/dist/java.js +222 -0
  30. package/dist/java.js.map +1 -0
  31. package/dist/loaders.d.ts +41 -0
  32. package/dist/loaders.js +86 -0
  33. package/dist/loaders.js.map +1 -0
  34. package/dist/lock.d.ts +13 -0
  35. package/dist/lock.js +79 -0
  36. package/dist/lock.js.map +1 -0
  37. package/dist/log.d.ts +13 -0
  38. package/dist/log.js +20 -0
  39. package/dist/log.js.map +1 -0
  40. package/dist/manager.d.ts +15 -0
  41. package/dist/manager.js +65 -0
  42. package/dist/manager.js.map +1 -0
  43. package/dist/mcp.d.ts +13 -0
  44. package/dist/mcp.js +377 -0
  45. package/dist/mcp.js.map +1 -0
  46. package/dist/mods.d.ts +21 -0
  47. package/dist/mods.js +135 -0
  48. package/dist/mods.js.map +1 -0
  49. package/dist/mojang.d.ts +54 -0
  50. package/dist/mojang.js +73 -0
  51. package/dist/mojang.js.map +1 -0
  52. package/dist/names.d.ts +53 -0
  53. package/dist/names.js +267 -0
  54. package/dist/names.js.map +1 -0
  55. package/dist/net.d.ts +26 -0
  56. package/dist/net.js +286 -0
  57. package/dist/net.js.map +1 -0
  58. package/dist/options.d.ts +14 -0
  59. package/dist/options.js +50 -0
  60. package/dist/options.js.map +1 -0
  61. package/dist/paths.d.ts +29 -0
  62. package/dist/paths.js +49 -0
  63. package/dist/paths.js.map +1 -0
  64. package/dist/probe-server.d.ts +28 -0
  65. package/dist/probe-server.js +132 -0
  66. package/dist/probe-server.js.map +1 -0
  67. package/dist/types.d.ts +235 -0
  68. package/dist/types.js +25 -0
  69. package/dist/types.js.map +1 -0
  70. package/dist/version.d.ts +2 -0
  71. package/dist/version.js +11 -0
  72. package/dist/version.js.map +1 -0
  73. package/dist/zip.d.ts +5 -0
  74. package/dist/zip.js +65 -0
  75. package/dist/zip.js.map +1 -0
  76. package/package.json +57 -4
  77. package/vendor/calcite-probe.jar +0 -0
@@ -0,0 +1,127 @@
1
+ import { ClientCore } from './client-core.js';
2
+ import { type BlockFace, type BlockInfo, type BlockPosition, type ChatLine, type ClickMode, type ContainerInfo, type DigResult, type EntityInfo, type EntityQuery, type GameState, type HitTarget, type InventoryInfo, type ItemInfo, type LogLine, type MoveControls, type Phase, type WalkResult } from './types.js';
3
+ export * from './types.js';
4
+ export { installVersion, type InstallOptions } from './install.js';
5
+ /**
6
+ * One Minecraft client controlled by Calcite.
7
+ *
8
+ * Events: 'phase' (Phase), 'log' (LogLine), 'chat' (ChatLine), 'state' (GameState), 'exit' (code).
9
+ */
10
+ export declare class Client extends ClientCore {
11
+ state(): Promise<GameState>;
12
+ entities(query?: EntityQuery): Promise<EntityInfo[]>;
13
+ chat(message: string): Promise<void>;
14
+ command(command: string): Promise<void>;
15
+ respawn(): Promise<void>;
16
+ /** Turns the player to an absolute rotation (degrees; yaw 0 = south, pitch -90 = up) or towards a point. */
17
+ look(to: {
18
+ yaw?: number;
19
+ pitch?: number;
20
+ } | {
21
+ x: number;
22
+ y: number;
23
+ z: number;
24
+ }): Promise<{
25
+ yaw: number;
26
+ pitch: number;
27
+ }>;
28
+ /**
29
+ * Holds movement controls until changed (or for {@code ticks} game ticks, 20 per second). Pass false to release
30
+ * a control; {@link stop} releases all of them.
31
+ */
32
+ move(controls: MoveControls, opts?: {
33
+ ticks?: number;
34
+ }): Promise<MoveControls & {
35
+ releaseInTicks?: number;
36
+ }>;
37
+ /** Releases every control and cancels a running walkTo/dig/held use. */
38
+ stopActions(): Promise<void>;
39
+ /**
40
+ * Walks in a straight line to (x, z), jumping over single blocks and swimming. Resolves when within
41
+ * {@code range} blocks, or with {@code arrived: false} when stuck (no progress for 3 s) or timed out.
42
+ * There is no path finding: route around obstacles with intermediate points.
43
+ */
44
+ walkTo(x: number, z: number, opts?: {
45
+ range?: number;
46
+ sprint?: boolean;
47
+ timeoutMs?: number;
48
+ }): Promise<WalkResult>;
49
+ /** Left click: attacks the entity (facing it first) or whatever the crosshair points at. */
50
+ attack(entityId?: number): Promise<HitTarget>;
51
+ /**
52
+ * Right click: interacts with an entity, a block (opening chests, pressing buttons, placing the held block
53
+ * against that face) or, with no target, whatever the crosshair points at / the held item in the air.
54
+ * {@code holdTicks} keeps the button pressed afterwards (eating, drinking, drawing a bow, shields).
55
+ */
56
+ use(target?: {
57
+ entityId?: number;
58
+ block?: BlockPosition & {
59
+ face?: BlockFace;
60
+ };
61
+ holdTicks?: number;
62
+ }): Promise<HitTarget>;
63
+ /** Mines a block like a player (holding left click, taking the real break time). */
64
+ dig(block: BlockPosition & {
65
+ face?: BlockFace;
66
+ }, opts?: {
67
+ timeoutMs?: number;
68
+ }): Promise<DigResult>;
69
+ /** The block at a position as the client sees it. */
70
+ block(pos: BlockPosition): Promise<BlockInfo>;
71
+ /** What the crosshair points at. */
72
+ target(): Promise<HitTarget>;
73
+ inventory(): Promise<InventoryInfo>;
74
+ /** Selects hotbar slot 0-8. */
75
+ selectSlot(slot: number): Promise<{
76
+ selected: number;
77
+ item: ItemInfo | null;
78
+ }>;
79
+ /** The open container (chest, furnace, villager trade, ...) or the inventory; {@code waitMs} waits for one to open. */
80
+ container(opts?: {
81
+ waitMs?: number;
82
+ }): Promise<ContainerInfo>;
83
+ /**
84
+ * Clicks a slot of the open container (or the inventory). Modes: pickup (default; button 0 left, 1 right),
85
+ * quick_move (shift click), swap (button = hotbar slot 0-8 or 40 for the offhand), clone, throw (button 1 =
86
+ * whole stack), quick_craft, pickup_all. Slot -999 is outside the window.
87
+ */
88
+ click(slot: number, opts?: {
89
+ button?: number;
90
+ mode?: ClickMode;
91
+ }): Promise<ContainerInfo>;
92
+ closeContainer(): Promise<void>;
93
+ /** Drops one item (or the whole stack) from the selected hotbar slot. */
94
+ drop(opts?: {
95
+ all?: boolean;
96
+ }): Promise<ItemInfo>;
97
+ setRender(enabled: boolean): Promise<void>;
98
+ /** Takes a screenshot and returns the PNG. With {@code keep} the file stays in the game's screenshots folder. */
99
+ screenshot({ keep }?: {
100
+ keep?: boolean | undefined;
101
+ }): Promise<{
102
+ path?: string;
103
+ png: Buffer;
104
+ }>;
105
+ logsSince(opts?: {
106
+ since?: number;
107
+ limit?: number;
108
+ contains?: string;
109
+ source?: LogLine['source'];
110
+ }): LogLine[];
111
+ chatSince(since?: number, limit?: number): ChatLine[];
112
+ /**
113
+ * Waits for a condition: a chat line matching {@code chat} (regex), an entity appearing/disappearing, or a
114
+ * phase. Resolves with a description of what matched.
115
+ */
116
+ waitFor(cond: {
117
+ chat?: string;
118
+ entity?: EntityQuery & {
119
+ present?: boolean;
120
+ };
121
+ phase?: Phase;
122
+ }, timeoutMs?: number): Promise<{
123
+ matched: string;
124
+ chat?: ChatLine;
125
+ entities?: EntityInfo[];
126
+ }>;
127
+ }
package/dist/client.js ADDED
@@ -0,0 +1,185 @@
1
+ import { readFile, rm } from 'node:fs/promises';
2
+ import { ClientCore, sleep } from './client-core.js';
3
+ import { ProbeError } from './probe-server.js';
4
+ import { CalciteError, } from './types.js';
5
+ export * from './types.js';
6
+ export { installVersion } from './install.js';
7
+ /**
8
+ * One Minecraft client controlled by Calcite.
9
+ *
10
+ * Events: 'phase' (Phase), 'log' (LogLine), 'chat' (ChatLine), 'state' (GameState), 'exit' (code).
11
+ */
12
+ export class Client extends ClientCore {
13
+ async state() {
14
+ const state = await this.requireProbe().request('state');
15
+ this.lastState = state;
16
+ return state;
17
+ }
18
+ async entities(query = {}) {
19
+ const list = await this.requireProbe().request('entities', { radius: query.radius ?? 0, limit: query.type || query.uuid || query.name ? 0 : query.limit ?? 0, includeSelf: !!query.includeSelf }, 20_000);
20
+ const type = query.type ? (query.type.includes(':') ? query.type : `minecraft:${query.type}`).toLowerCase() : undefined;
21
+ const name = query.name?.toLowerCase();
22
+ let result = list.filter((e) => (!type || e.type?.toLowerCase() === type) &&
23
+ (!query.uuid || e.uuid === query.uuid) &&
24
+ (!name || (e.name ?? '').toLowerCase().includes(name) || (e.customName ?? '').toLowerCase().includes(name)));
25
+ if (query.limit && query.limit > 0)
26
+ result = result.slice(0, query.limit);
27
+ return result;
28
+ }
29
+ async chat(message) {
30
+ if (!message || message.length > 256)
31
+ throw new CalciteError('bad_message', 'Chat message must be 1-256 characters');
32
+ await this.requireProbe().request('chat', { message });
33
+ }
34
+ async command(command) {
35
+ if (!command.trim())
36
+ throw new CalciteError('bad_command', 'Command is empty');
37
+ await this.requireProbe().request('command', { command: command.trim() });
38
+ }
39
+ async respawn() {
40
+ await this.requireProbe().request('respawn');
41
+ }
42
+ // ------------------------------------------------------------------ actions
43
+ /** Turns the player to an absolute rotation (degrees; yaw 0 = south, pitch -90 = up) or towards a point. */
44
+ async look(to) {
45
+ return this.requireProbe().request('look', to);
46
+ }
47
+ /**
48
+ * Holds movement controls until changed (or for {@code ticks} game ticks, 20 per second). Pass false to release
49
+ * a control; {@link stop} releases all of them.
50
+ */
51
+ async move(controls, opts = {}) {
52
+ return this.requireProbe().request('move', { ...controls, ticks: opts.ticks ?? 0 });
53
+ }
54
+ /** Releases every control and cancels a running walkTo/dig/held use. */
55
+ async stopActions() {
56
+ await this.requireProbe().request('stop');
57
+ }
58
+ /**
59
+ * Walks in a straight line to (x, z), jumping over single blocks and swimming. Resolves when within
60
+ * {@code range} blocks, or with {@code arrived: false} when stuck (no progress for 3 s) or timed out.
61
+ * There is no path finding: route around obstacles with intermediate points.
62
+ */
63
+ async walkTo(x, z, opts = {}) {
64
+ const timeoutMs = opts.timeoutMs ?? 60_000;
65
+ return this.requireProbe().request('walk_to', { x, z, range: opts.range ?? 0.5, sprint: opts.sprint ?? true, timeoutMs }, timeoutMs + 15_000);
66
+ }
67
+ /** Left click: attacks the entity (facing it first) or whatever the crosshair points at. */
68
+ async attack(entityId) {
69
+ return this.requireProbe().request('attack', entityId === undefined ? {} : { entityId });
70
+ }
71
+ /**
72
+ * Right click: interacts with an entity, a block (opening chests, pressing buttons, placing the held block
73
+ * against that face) or, with no target, whatever the crosshair points at / the held item in the air.
74
+ * {@code holdTicks} keeps the button pressed afterwards (eating, drinking, drawing a bow, shields).
75
+ */
76
+ async use(target = {}) {
77
+ const { block, ...rest } = target;
78
+ return this.requireProbe().request('use', { ...rest, ...(block ?? {}) });
79
+ }
80
+ /** Mines a block like a player (holding left click, taking the real break time). */
81
+ async dig(block, opts = {}) {
82
+ const timeoutMs = opts.timeoutMs ?? 30_000;
83
+ return this.requireProbe().request('dig', { ...block, timeoutMs }, timeoutMs + 15_000);
84
+ }
85
+ /** The block at a position as the client sees it. */
86
+ async block(pos) {
87
+ return this.requireProbe().request('block', { ...pos });
88
+ }
89
+ /** What the crosshair points at. */
90
+ async target() {
91
+ return this.requireProbe().request('target');
92
+ }
93
+ async inventory() {
94
+ return this.requireProbe().request('inventory');
95
+ }
96
+ /** Selects hotbar slot 0-8. */
97
+ async selectSlot(slot) {
98
+ return this.requireProbe().request('select_slot', { slot });
99
+ }
100
+ /** The open container (chest, furnace, villager trade, ...) or the inventory; {@code waitMs} waits for one to open. */
101
+ async container(opts = {}) {
102
+ const waitMs = opts.waitMs ?? 0;
103
+ return this.requireProbe().request('container', { waitMs }, waitMs + 15_000);
104
+ }
105
+ /**
106
+ * Clicks a slot of the open container (or the inventory). Modes: pickup (default; button 0 left, 1 right),
107
+ * quick_move (shift click), swap (button = hotbar slot 0-8 or 40 for the offhand), clone, throw (button 1 =
108
+ * whole stack), quick_craft, pickup_all. Slot -999 is outside the window.
109
+ */
110
+ async click(slot, opts = {}) {
111
+ return this.requireProbe().request('click', { slot, button: opts.button ?? 0, mode: opts.mode ?? 'pickup' });
112
+ }
113
+ async closeContainer() {
114
+ await this.requireProbe().request('close_container');
115
+ }
116
+ /** Drops one item (or the whole stack) from the selected hotbar slot. */
117
+ async drop(opts = {}) {
118
+ return this.requireProbe().request('drop', { all: !!opts.all });
119
+ }
120
+ async setRender(enabled) {
121
+ if (this.headless)
122
+ throw new CalciteError('headless', 'This client runs without a renderer');
123
+ await this.requireProbe().request('render', { enabled });
124
+ }
125
+ /** Takes a screenshot and returns the PNG. With {@code keep} the file stays in the game's screenshots folder. */
126
+ async screenshot({ keep = false } = {}) {
127
+ if (this.headless) {
128
+ throw new CalciteError('headless', this.render === 'off'
129
+ ? 'This client was started with render "off"; start it with render "on-demand" to take screenshots'
130
+ : 'Screenshots need a renderer: offline accounts can only render on Linux (Xvfb); use a Microsoft account on Windows/macOS');
131
+ }
132
+ const name = `calcite-${Date.now()}.png`;
133
+ const path = await this.requireProbe().request('screenshot', { name, settleFrames: 3, timeoutMs: 30_000 }, 45_000);
134
+ const png = await readFile(path);
135
+ if (keep)
136
+ return { path, png };
137
+ await rm(path, { force: true });
138
+ return { png };
139
+ }
140
+ logsSince(opts = {}) {
141
+ const contains = opts.contains?.toLowerCase();
142
+ const lines = this.logs.filter((l) => l.seq > (opts.since ?? 0) && (!opts.source || l.source === opts.source) && (!contains || l.line.toLowerCase().includes(contains)));
143
+ return opts.limit ? lines.slice(-opts.limit) : lines;
144
+ }
145
+ chatSince(since = 0, limit) {
146
+ const lines = this.chats.filter((c) => c.seq > since);
147
+ return limit ? lines.slice(-limit) : lines;
148
+ }
149
+ /**
150
+ * Waits for a condition: a chat line matching {@code chat} (regex), an entity appearing/disappearing, or a
151
+ * phase. Resolves with a description of what matched.
152
+ */
153
+ async waitFor(cond, timeoutMs = 30_000) {
154
+ const deadline = Date.now() + timeoutMs;
155
+ const chatRe = cond.chat ? new RegExp(cond.chat, 'i') : undefined;
156
+ const startSeq = this.seq;
157
+ for (;;) {
158
+ if (chatRe) {
159
+ const hit = this.chats.find((c) => c.seq > startSeq && chatRe.test(c.message));
160
+ if (hit)
161
+ return { matched: 'chat', chat: hit };
162
+ }
163
+ if (cond.phase && this.phase === cond.phase)
164
+ return { matched: 'phase' };
165
+ if (cond.entity && this.probe?.connected) {
166
+ const { present = true, ...query } = cond.entity;
167
+ try {
168
+ const found = await this.entities(query);
169
+ if (present ? found.length > 0 : found.length === 0)
170
+ return { matched: present ? 'entity_present' : 'entity_absent', entities: found };
171
+ }
172
+ catch (err) {
173
+ if (!(err instanceof ProbeError) || !['not_ready', 'not_in_game'].includes(err.code))
174
+ throw err;
175
+ }
176
+ }
177
+ if (Date.now() > deadline)
178
+ throw new CalciteError('timeout', `Condition not met within ${timeoutMs}ms`);
179
+ if (['stopped', 'crashed'].includes(this.phase))
180
+ throw new CalciteError('not_running', `Client stopped (phase ${this.phase})`);
181
+ await sleep(300);
182
+ }
183
+ }
184
+ }
185
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface DisplayLease {
2
+ /** Environment variables the game needs (DISPLAY, software GL). */
3
+ env: Record<string, string>;
4
+ /** True when Calcite started its own Xvfb (HeadlessMC must then be told to check for it). */
5
+ virtual: boolean;
6
+ release(): void;
7
+ }
8
+ export declare function which(binary: string): string | null;
9
+ /** True when a GPU device node exists (Linux); otherwise Mesa software rendering is used. */
10
+ export declare function hasGpu(): boolean;
11
+ /** True when a shared library (e.g. "libEGL.so.1") is present in the usual Linux library directories. */
12
+ export declare function hasSharedLib(name: string): boolean;
13
+ /**
14
+ * Provides a display for a rendering client.
15
+ * - Linux with DISPLAY set (desktop, or an existing Xvfb): use it.
16
+ * - Linux without DISPLAY: start a shared Xvfb (needs the `Xvfb` binary) with software GL when there is no GPU.
17
+ * - Windows / macOS: the native display is used.
18
+ */
19
+ export declare function acquireDisplay(opts?: {
20
+ width?: number;
21
+ height?: number;
22
+ forceVirtual?: boolean;
23
+ }): Promise<DisplayLease>;
@@ -0,0 +1,164 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
3
+ import { delimiter, join } from 'node:path';
4
+ import { logger } from './log.js';
5
+ const log = logger('display');
6
+ export function which(binary) {
7
+ for (const dir of (process.env.PATH || '').split(delimiter)) {
8
+ if (!dir)
9
+ continue;
10
+ const p = join(dir, binary);
11
+ if (existsSync(p))
12
+ return p;
13
+ }
14
+ return null;
15
+ }
16
+ /** True when a GPU device node exists (Linux); otherwise Mesa software rendering is used. */
17
+ export function hasGpu() {
18
+ if (process.platform !== 'linux')
19
+ return true;
20
+ try {
21
+ return readdirSync('/dev/dri').some((f) => f.startsWith('renderD') || f.startsWith('card'));
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ const libCache = new Map();
28
+ /** True when a shared library (e.g. "libEGL.so.1") is present in the usual Linux library directories. */
29
+ export function hasSharedLib(name) {
30
+ const cached = libCache.get(name);
31
+ if (cached !== undefined)
32
+ return cached;
33
+ const dirs = ['/usr/lib64', '/usr/lib', '/lib64', '/lib', '/usr/local/lib'];
34
+ for (const base of ['/usr/lib', '/lib']) {
35
+ try {
36
+ for (const d of readdirSync(base))
37
+ if (/-linux-gnu/.test(d))
38
+ dirs.push(join(base, d));
39
+ }
40
+ catch {
41
+ // not there
42
+ }
43
+ }
44
+ const found = dirs.some((d) => existsSync(join(d, name)));
45
+ libCache.set(name, found);
46
+ return found;
47
+ }
48
+ function softwareGlEnv() {
49
+ return hasGpu() ? {} : { LIBGL_ALWAYS_SOFTWARE: '1', GALLIUM_DRIVER: 'llvmpipe' };
50
+ }
51
+ /**
52
+ * Environment for a virtual (Xvfb) display. Minecraft 26.x renders through SDL3 and asks for an sRGB-capable
53
+ * framebuffer, which Xvfb's GLX never offers; SDL's EGL path provides it. Older versions use GLFW and ignore this.
54
+ */
55
+ function virtualDisplayEnv() {
56
+ return { ...softwareGlEnv(), ...(hasSharedLib('libEGL.so.1') ? { SDL_VIDEO_FORCE_EGL: '1' } : {}) };
57
+ }
58
+ let shared = null;
59
+ /** Display numbers in use, including X servers that only listen on an abstract socket (no lock or socket file). */
60
+ function displaysInUse() {
61
+ const used = new Set();
62
+ try {
63
+ for (const m of readFileSync('/proc/net/unix', 'utf8').matchAll(/@?\/tmp\/\.X11-unix\/X(\d+)/g))
64
+ used.add(Number(m[1]));
65
+ }
66
+ catch {
67
+ // no procfs
68
+ }
69
+ return used;
70
+ }
71
+ function freeDisplayNumber(skip) {
72
+ const used = displaysInUse();
73
+ for (let n = 99; n < 199; n++) {
74
+ if (skip.has(n) || used.has(n))
75
+ continue;
76
+ if (!existsSync(`/tmp/.X11-unix/X${n}`) && !existsSync(`/tmp/.X${n}-lock`))
77
+ return n;
78
+ }
79
+ throw new Error('No free X display number between :99 and :198');
80
+ }
81
+ async function waitFor(check, timeoutMs) {
82
+ const deadline = Date.now() + timeoutMs;
83
+ while (Date.now() < deadline) {
84
+ if (check())
85
+ return true;
86
+ await new Promise((r) => setTimeout(r, 100));
87
+ }
88
+ return check();
89
+ }
90
+ /**
91
+ * Provides a display for a rendering client.
92
+ * - Linux with DISPLAY set (desktop, or an existing Xvfb): use it.
93
+ * - Linux without DISPLAY: start a shared Xvfb (needs the `Xvfb` binary) with software GL when there is no GPU.
94
+ * - Windows / macOS: the native display is used.
95
+ */
96
+ export async function acquireDisplay(opts = {}) {
97
+ if (process.platform !== 'linux') {
98
+ return { env: {}, virtual: false, release() { } };
99
+ }
100
+ if (process.env.DISPLAY && !opts.forceVirtual) {
101
+ const virtual = /xvfb/i.test(process.env.CALCITE_DISPLAY_KIND || '');
102
+ return { env: { DISPLAY: process.env.DISPLAY, ...(virtual ? virtualDisplayEnv() : softwareGlEnv()) }, virtual, release() { } };
103
+ }
104
+ if (!shared || shared.proc.exitCode !== null) {
105
+ const xvfb = which('Xvfb');
106
+ if (!xvfb) {
107
+ throw new Error('Rendering on a Linux machine without a display needs Xvfb and Mesa: apt-get install -y xvfb libgl1-mesa-dri libegl1 libegl-mesa0 (or run with --render off)');
108
+ }
109
+ const width = opts.width ?? 1280;
110
+ const height = opts.height ?? 720;
111
+ // a display may still be taken without leaving traces we can see; Xvfb then exits and the next number is tried
112
+ const tried = new Set();
113
+ let n = 0;
114
+ let proc;
115
+ for (let attempt = 0; attempt < 5 && !proc; attempt++) {
116
+ n = freeDisplayNumber(tried);
117
+ tried.add(n);
118
+ const candidate = spawn(xvfb, [`:${n}`, '-screen', '0', `${width}x${height}x24`, '-nolisten', 'tcp'], {
119
+ stdio: 'ignore',
120
+ detached: false,
121
+ });
122
+ candidate.unref();
123
+ await waitFor(() => existsSync(`/tmp/.X11-unix/X${n}`) || candidate.exitCode !== null, 10_000);
124
+ if (candidate.exitCode === null && existsSync(`/tmp/.X11-unix/X${n}`))
125
+ proc = candidate;
126
+ else {
127
+ candidate.kill();
128
+ log.debug(`Xvfb :${n} did not start, trying another display`);
129
+ }
130
+ }
131
+ if (!proc)
132
+ throw new Error(`Xvfb failed to start (tried displays ${[...tried].map((d) => `:${d}`).join(', ')})`);
133
+ log.info(`started Xvfb :${n} (${width}x${height})`);
134
+ const xvfbProc = proc;
135
+ shared = { proc: xvfbProc, display: `:${n}`, refs: 0 };
136
+ const cleanup = () => {
137
+ try {
138
+ xvfbProc.kill();
139
+ }
140
+ catch {
141
+ // already gone
142
+ }
143
+ };
144
+ process.once('exit', cleanup);
145
+ }
146
+ const lease = shared;
147
+ lease.refs++;
148
+ let released = false;
149
+ return {
150
+ env: { DISPLAY: lease.display, ...virtualDisplayEnv() },
151
+ virtual: true,
152
+ release() {
153
+ if (released)
154
+ return;
155
+ released = true;
156
+ lease.refs--;
157
+ if (lease.refs <= 0 && shared === lease) {
158
+ lease.proc.kill();
159
+ shared = null;
160
+ }
161
+ },
162
+ };
163
+ }
164
+ //# sourceMappingURL=display.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"display.js","sourceRoot":"","sources":["../src/display.ts"],"names":[],"mappings":"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"}
package/dist/hmc.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ import type { CalcitePaths } from './paths.js';
3
+ /** Pinned HeadlessMC release (MIT, https://github.com/headlesshq/headlessmc). */
4
+ export declare const HMC_VERSION = "3.0.0-RC3";
5
+ export declare const HMC_URL = "https://github.com/headlesshq/headlessmc/releases/download/3.0.0-RC3/headlessmc.jar";
6
+ export declare const HMC_SHA256 = "dcc061a336ecf7aa638794bd47840fa9970b31d562e630e84c3d587c906153a8";
7
+ /** Path of the HeadlessMC jar (CALCITE_HMC_JAR overrides the pinned download). */
8
+ export declare function ensureHmc(paths: CalcitePaths): Promise<string>;
9
+ /** Escapes one entry of a comma separated HeadlessMC list setting. */
10
+ export declare function hmcListEntry(value: string): string;
11
+ /** Quotes one argument for HeadlessMC's `--jvm`/`--game` options, which are split like a shell command line. */
12
+ export declare function hmcQuote(arg: string): string;
13
+ /**
14
+ * Writes the HeadlessMC profile used when launching `versionId` from `location`. HeadlessMC only runs a version
15
+ * with the exact Java major the version asks for; the profile pins the runtime Calcite chose instead. With a
16
+ * {@code loader} the profile selects that platform (fabric, forge, neoforge) and build for Minecraft {@code versionId}.
17
+ */
18
+ export declare function writeHmcProfile(location: string, versionId: string, gameDir: string, javaMajor: number, loader?: {
19
+ kind: string;
20
+ build?: string;
21
+ }): Promise<void>;
22
+ /**
23
+ * HeadlessMC's version argument: "1.21.11", or "fabric 1.21.11 0.19.5" for a mod loader. HeadlessMC names NeoForge
24
+ * builds without the Minecraft part of their version (NeoForge 21.11.45 for 1.21.11 is build "45").
25
+ */
26
+ export declare function hmcVersionArgs(versionId: string, loader?: {
27
+ kind: string;
28
+ build?: string;
29
+ }): string[];
30
+ export interface HmcRun {
31
+ child: ChildProcess;
32
+ /** Resolves with the exit code when the HeadlessMC process exits. */
33
+ exited: Promise<number | null>;
34
+ }
35
+ export interface HmcRunOptions {
36
+ javaPath: string;
37
+ hmcJar: string;
38
+ /** HeadlessMC files location (config, accounts, caches) of this run. */
39
+ location: string;
40
+ /** hmc.* (and other) system properties for this run. */
41
+ props?: Record<string, string>;
42
+ /** HeadlessMC command line, e.g. ["launch", "1.21.11", "--headless"]. */
43
+ command: string[];
44
+ env?: NodeJS.ProcessEnv;
45
+ onLine?: (line: string) => void;
46
+ }
47
+ /**
48
+ * Runs one HeadlessMC command non-interactively; HeadlessMC exits once the command (and a launched game) finished.
49
+ * Settings are passed as -D system properties, which take precedence over any config file.
50
+ */
51
+ export declare function runHmc(opts: HmcRunOptions): HmcRun;
52
+ /** Stops a process tree started by {@link runHmc}. */
53
+ export declare function killTree(child: ChildProcess, graceMs?: number): Promise<void>;