@mindexec/cli 0.2.443 → 0.2.444

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/lzo1x.js ADDED
@@ -0,0 +1,110 @@
1
+ function requireInput(input, index, length) {
2
+ if (length < 0 || index < 0 || index + length > input.length) {
3
+ throw new Error('Invalid LZO1X block: input overrun.');
4
+ }
5
+ }
6
+
7
+ function readExtendedLength(input, state, basis) {
8
+ let zeroBytes = 0;
9
+ while (true) {
10
+ requireInput(input, state.inputIndex, 1);
11
+ if (input[state.inputIndex] !== 0) break;
12
+ zeroBytes += 1;
13
+ state.inputIndex += 1;
14
+ }
15
+ const length = zeroBytes * 255 + basis + input[state.inputIndex];
16
+ state.inputIndex += 1;
17
+ return length;
18
+ }
19
+
20
+ function copyLiteral(input, state, output, length) {
21
+ requireInput(input, state.inputIndex, length);
22
+ if (state.outputIndex + length > output.length) {
23
+ throw new Error('Invalid LZO1X block: output overrun.');
24
+ }
25
+ output.set(input.subarray(state.inputIndex, state.inputIndex + length), state.outputIndex);
26
+ state.inputIndex += length;
27
+ state.outputIndex += length;
28
+ }
29
+
30
+ export function decodeLzo1xBlock(input, outputLength) {
31
+ if (!(input instanceof Uint8Array) || input.length < 3 || outputLength < 0) {
32
+ throw new Error('Invalid LZO1X block.');
33
+ }
34
+ const output = new Uint8Array(outputLength);
35
+ const cursor = { inputIndex: 0, outputIndex: 0 };
36
+ let state = 0;
37
+ let matchLength = 0;
38
+ if (input[cursor.inputIndex] >= 22) {
39
+ const length = input[cursor.inputIndex] - 17;
40
+ cursor.inputIndex += 1;
41
+ copyLiteral(input, cursor, output, length);
42
+ state = 4;
43
+ } else if (input[cursor.inputIndex] >= 18) {
44
+ state = input[cursor.inputIndex] - 17;
45
+ cursor.inputIndex += 1;
46
+ copyLiteral(input, cursor, output, state);
47
+ }
48
+ while (true) {
49
+ requireInput(input, cursor.inputIndex, 1);
50
+ const instruction = input[cursor.inputIndex++];
51
+ let nextState;
52
+ let matchIndex;
53
+ if ((instruction & 0xc0) !== 0) {
54
+ requireInput(input, cursor.inputIndex, 1);
55
+ matchIndex = cursor.outputIndex - ((input[cursor.inputIndex] << 3) + ((instruction >> 2) & 7) + 1);
56
+ cursor.inputIndex += 1;
57
+ matchLength = (instruction >> 5) + 1;
58
+ nextState = instruction & 3;
59
+ } else if ((instruction & 0x20) !== 0) {
60
+ matchLength = (instruction & 0x1f) + 2;
61
+ if (matchLength === 2) matchLength += readExtendedLength(input, cursor, 31);
62
+ requireInput(input, cursor.inputIndex, 2);
63
+ const encoded = input[cursor.inputIndex] | (input[cursor.inputIndex + 1] << 8);
64
+ cursor.inputIndex += 2;
65
+ matchIndex = cursor.outputIndex - ((encoded >> 2) + 1);
66
+ nextState = encoded & 3;
67
+ } else if ((instruction & 0x10) !== 0) {
68
+ matchLength = (instruction & 7) + 2;
69
+ if (matchLength === 2) matchLength += readExtendedLength(input, cursor, 7);
70
+ requireInput(input, cursor.inputIndex, 2);
71
+ const encoded = input[cursor.inputIndex] | (input[cursor.inputIndex + 1] << 8);
72
+ cursor.inputIndex += 2;
73
+ matchIndex = cursor.outputIndex - (((instruction & 8) << 11) + (encoded >> 2));
74
+ nextState = encoded & 3;
75
+ if (matchIndex === cursor.outputIndex) break;
76
+ matchIndex -= 0x4000;
77
+ } else if (state === 0) {
78
+ let length = instruction + 3;
79
+ if (length === 3) length += readExtendedLength(input, cursor, 15);
80
+ copyLiteral(input, cursor, output, length);
81
+ state = 4;
82
+ continue;
83
+ } else if (state !== 4) {
84
+ requireInput(input, cursor.inputIndex, 1);
85
+ nextState = instruction & 3;
86
+ matchIndex = cursor.outputIndex - ((instruction >> 2) + (input[cursor.inputIndex] << 2) + 1);
87
+ cursor.inputIndex += 1;
88
+ matchLength = 2;
89
+ } else {
90
+ requireInput(input, cursor.inputIndex, 1);
91
+ nextState = instruction & 3;
92
+ matchIndex = cursor.outputIndex - ((instruction >> 2) + (input[cursor.inputIndex] << 2) + 2049);
93
+ cursor.inputIndex += 1;
94
+ matchLength = 3;
95
+ }
96
+ if (matchIndex < 0 || cursor.outputIndex + matchLength + nextState > output.length) {
97
+ throw new Error('Invalid LZO1X block: lookbehind overrun.');
98
+ }
99
+ for (let index = 0; index < matchLength; index += 1) {
100
+ output[cursor.outputIndex++] = output[matchIndex++];
101
+ }
102
+ state = nextState;
103
+ copyLiteral(input, cursor, output, nextState);
104
+ }
105
+ if (matchLength !== 3 || cursor.inputIndex !== input.length || cursor.outputIndex !== output.length) {
106
+ throw new Error('Invalid LZO1X block: incomplete output.');
107
+ }
108
+ return output;
109
+ }
110
+
@@ -0,0 +1,440 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'node:child_process';
4
+ import { createRequire } from 'node:module';
5
+ import { existsSync } from 'node:fs';
6
+ import { decodeLzo1xBlock } from './lzo1x.js';
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const DEFAULT_WIDTH = 1920;
10
+ const DEFAULT_HEIGHT = 1080;
11
+ const DEFAULT_FPS = 20;
12
+ const MAX_DEVICES = 100;
13
+ const FRAME_POOL_SIZE = 8;
14
+ const MAX_TILE_WIDTH = 480;
15
+ const MAX_TILE_HEIGHT = 270;
16
+
17
+ const rgb565Red = new Uint8Array(65_536);
18
+ const rgb565Green = new Uint8Array(65_536);
19
+ const rgb565Blue = new Uint8Array(65_536);
20
+ for (let pixel = 0; pixel < 65_536; pixel += 1) {
21
+ rgb565Red[pixel] = (((pixel >> 11) & 0x1f) * 255 / 31) | 0;
22
+ rgb565Green[pixel] = (((pixel >> 5) & 0x3f) * 255 / 63) | 0;
23
+ rgb565Blue[pixel] = ((pixel & 0x1f) * 255 / 31) | 0;
24
+ }
25
+
26
+ let config = normalizeConfig({});
27
+ let layoutVersion = 0;
28
+ let layout = [];
29
+ let latestTiles = new Map();
30
+ let framePool = [];
31
+ let encoder = null;
32
+ let encoderCandidateIndex = 0;
33
+ let encoderCandidates = [];
34
+ let encoderOutputSeen = false;
35
+ let encoderGeneration = 0;
36
+ let encoderRestartTimer = null;
37
+ let encoderStartupTimer = null;
38
+ let inputWaitTimer = null;
39
+ let outputBuffer = Buffer.alloc(0);
40
+ let frameSeq = 0;
41
+ let tickTimer = null;
42
+ let nextTickAt = 0;
43
+ let closed = false;
44
+ let lastComposeMs = 0;
45
+ let poolStarved = 0;
46
+ const ENCODER_STARTUP_OUTPUT_TIMEOUT_MS = 8_000;
47
+
48
+ function clamp(value, min, max, fallback) {
49
+ const number = Number(value);
50
+ return Number.isFinite(number) ? Math.max(min, Math.min(max, Math.round(number))) : fallback;
51
+ }
52
+
53
+ function normalizeConfig(value) {
54
+ const deviceIds = [...new Set((Array.isArray(value?.deviceIds) ? value.deviceIds : [])
55
+ .map(item => String(item || '').trim()).filter(Boolean))].slice(0, MAX_DEVICES);
56
+ const maxWidth = clamp(value?.width, 640, 3840, DEFAULT_WIDTH);
57
+ const maxHeight = clamp(value?.height, 360, 2160, DEFAULT_HEIGHT);
58
+ const sharpTiles = Number(value?.tileWidth) >= 480 || Number(value?.tileHeight) >= 270;
59
+ const sourceTileWidth = sharpTiles ? 480 : 320;
60
+ const sourceTileHeight = sharpTiles ? 270 : 180;
61
+ const count = Math.max(1, deviceIds.length);
62
+ const columns = Math.max(1, Math.ceil(Math.sqrt(count)));
63
+ const rows = Math.max(1, Math.ceil(count / columns));
64
+ const tileWidth = Math.max(2, Math.min(sourceTileWidth, Math.floor(maxWidth / columns))) & ~1;
65
+ const tileHeight = Math.max(2, Math.min(sourceTileHeight, Math.floor(maxHeight / rows))) & ~1;
66
+ return {
67
+ deviceIds,
68
+ maxWidth,
69
+ maxHeight,
70
+ width: tileWidth * columns,
71
+ height: tileHeight * rows,
72
+ columns,
73
+ rows,
74
+ tileWidth,
75
+ tileHeight,
76
+ sourceTileWidth,
77
+ sourceTileHeight,
78
+ fps: clamp(value?.fps, 1, 30, DEFAULT_FPS)
79
+ };
80
+ }
81
+
82
+ function send(message) {
83
+ if (process.connected) process.send?.(message);
84
+ }
85
+
86
+ function resolveFfmpegPaths() {
87
+ const paths = [];
88
+ const add = value => {
89
+ const path = String(value || '').trim();
90
+ if (!path || paths.includes(path)) return;
91
+ if (path === 'ffmpeg' || existsSync(path)) paths.push(path);
92
+ };
93
+ add(process.env.LIVEDESK_FFMPEG);
94
+ try { add(require('@ffmpeg-installer/ffmpeg')?.path); } catch {}
95
+ try { add(require('ffmpeg-static')); } catch {}
96
+ add('ffmpeg');
97
+ return paths;
98
+ }
99
+
100
+ function buildEncoderCandidates() {
101
+ const codecCandidates = process.platform === 'win32'
102
+ ? [
103
+ ['windows-nvenc', ['-c:v', 'h264_nvenc', '-preset', 'llhp', '-profile:v', 'baseline', '-rc:v', 'cbr_ld_hq', '-b:v', '6M', '-maxrate', '6M', '-bufsize', '1M', '-rc-lookahead', '0', '-zerolatency', '1', '-g', String(config.fps), '-bf', '0']],
104
+ ['windows-qsv', ['-c:v', 'h264_qsv', '-preset', 'veryfast', '-global_quality', '28', '-look_ahead', '0', '-g', String(config.fps), '-bf', '0']],
105
+ ['windows-amf', ['-c:v', 'h264_amf', '-quality', 'speed', '-usage', 'lowlatency', '-rc', 'cqp', '-qp_i', '25', '-qp_p', '28', '-g', String(config.fps), '-bf', '0']]
106
+ ]
107
+ : process.platform === 'darwin'
108
+ ? [['macos-videotoolbox', ['-c:v', 'h264_videotoolbox', '-realtime', '1', '-allow_sw', '0', '-b:v', '6M', '-g', String(config.fps), '-bf', '0']]]
109
+ : [['linux-nvenc', ['-c:v', 'h264_nvenc', '-preset', 'llhp', '-profile:v', 'baseline', '-rc-lookahead', '0', '-zerolatency', '1', '-g', String(config.fps), '-bf', '0']]];
110
+ codecCandidates.push(['software-x264', [
111
+ '-c:v', 'libx264', '-preset', 'ultrafast', '-tune', 'zerolatency',
112
+ '-profile:v', 'baseline', '-crf', '28', '-g', String(config.fps),
113
+ '-keyint_min', String(config.fps), '-sc_threshold', '0', '-bf', '0',
114
+ '-x264-params', 'aud=1:repeat-headers=1'
115
+ ]]);
116
+ return resolveFfmpegPaths().flatMap(path => codecCandidates.map(([name, args]) => ({ path, name, args })));
117
+ }
118
+
119
+ function startEncoder() {
120
+ if (closed || encoder || encoderCandidates.length === 0) return;
121
+ if (encoderCandidateIndex >= encoderCandidates.length) {
122
+ send({ type: 'status', state: 'error', error: 'No Mode 4 H.264 encoder is available.' });
123
+ return;
124
+ }
125
+ const candidate = encoderCandidates[encoderCandidateIndex++];
126
+ encoderGeneration += 1;
127
+ const args = [
128
+ '-hide_banner', '-loglevel', 'warning',
129
+ '-f', 'rawvideo', '-pixel_format', 'rgb24',
130
+ '-video_size', `${config.width}x${config.height}`,
131
+ '-framerate', String(config.fps), '-i', 'pipe:0', '-an',
132
+ ...candidate.args,
133
+ '-bsf:v', 'h264_metadata=aud=insert', '-f', 'h264', 'pipe:1'
134
+ ];
135
+ const child = spawn(candidate.path, args, { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
136
+ encoder = { child, candidate, inputSeen: false };
137
+ encoderOutputSeen = false;
138
+ outputBuffer = Buffer.alloc(0);
139
+ child.stdout.on('data', appendEncodedBytes);
140
+ child.stdin.on('error', error => {
141
+ if (encoder?.child === child && !isClosedPipeError(error)) {
142
+ send({ type: 'log', level: 'warn', message: `Mode 4 ${candidate.name} input: ${error.message}` });
143
+ }
144
+ });
145
+ child.stderr.on('data', chunk => {
146
+ const message = String(chunk || '').trim();
147
+ if (message) send({ type: 'log', level: 'warn', message: `Mode 4 ${candidate.name}: ${message.slice(0, 600)}` });
148
+ });
149
+ child.on('error', error => send({ type: 'log', level: 'warn', message: `Mode 4 ${candidate.name}: ${error.message}` }));
150
+ child.on('exit', code => {
151
+ clearTimeout(encoderStartupTimer);
152
+ encoderStartupTimer = null;
153
+ const wasCurrent = encoder?.child === child;
154
+ if (wasCurrent) encoder = null;
155
+ flushEncodedUnit();
156
+ if (closed) return;
157
+ send({ type: 'status', state: 'encoder-restart', encoder: candidate.name, code, emitted: encoderOutputSeen });
158
+ clearTimeout(encoderRestartTimer);
159
+ encoderRestartTimer = setTimeout(startEncoder, encoderOutputSeen ? 800 : 120);
160
+ });
161
+ send({ type: 'status', state: 'encoder-starting', encoder: candidate.name });
162
+ }
163
+
164
+ function isClosedPipeError(error) {
165
+ const code = String(error?.code || '').toUpperCase();
166
+ const message = String(error?.message || '').toLowerCase();
167
+ return code === 'EPIPE' || code === 'EOF' || code === 'ERR_STREAM_DESTROYED'
168
+ || message.includes('write eof') || message.includes('broken pipe');
169
+ }
170
+
171
+ function armEncoderStartupTimer(child, candidate) {
172
+ clearTimeout(encoderStartupTimer);
173
+ encoderStartupTimer = setTimeout(() => {
174
+ if (encoder?.child === child && encoder.inputSeen && !encoderOutputSeen) {
175
+ send({ type: 'log', level: 'warn', message: `Mode 4 ${candidate.name}: no output after receiving input; trying the next encoder.` });
176
+ try { child.kill(); } catch {}
177
+ }
178
+ }, ENCODER_STARTUP_OUTPUT_TIMEOUT_MS);
179
+ }
180
+
181
+ function stopEncoder() {
182
+ clearTimeout(encoderRestartTimer);
183
+ encoderRestartTimer = null;
184
+ clearTimeout(encoderStartupTimer);
185
+ encoderStartupTimer = null;
186
+ const child = encoder?.child;
187
+ encoder = null;
188
+ if (!child) return;
189
+ try { child.stdin.end(); } catch {}
190
+ setTimeout(() => { try { child.kill(); } catch {} }, 250).unref?.();
191
+ }
192
+
193
+ function findStartCodes(buffer) {
194
+ const starts = [];
195
+ for (let index = 0; index + 3 < buffer.length; index += 1) {
196
+ if (buffer[index] !== 0 || buffer[index + 1] !== 0) continue;
197
+ if (buffer[index + 2] === 1) {
198
+ starts.push({ offset: index, header: index + 3 });
199
+ index += 2;
200
+ } else if (buffer[index + 2] === 0 && buffer[index + 3] === 1) {
201
+ starts.push({ offset: index, header: index + 4 });
202
+ index += 3;
203
+ }
204
+ }
205
+ return starts;
206
+ }
207
+
208
+ function appendEncodedBytes(chunk) {
209
+ outputBuffer = outputBuffer.length ? Buffer.concat([outputBuffer, chunk]) : Buffer.from(chunk);
210
+ while (true) {
211
+ const auds = findStartCodes(outputBuffer).filter(start => (outputBuffer[start.header] & 0x1f) === 9);
212
+ if (auds.length < 2) break;
213
+ const boundary = auds[1].offset;
214
+ emitEncodedUnit(outputBuffer.subarray(0, boundary));
215
+ outputBuffer = outputBuffer.subarray(boundary);
216
+ }
217
+ }
218
+
219
+ function flushEncodedUnit() {
220
+ if (outputBuffer.length > 0) emitEncodedUnit(outputBuffer);
221
+ outputBuffer = Buffer.alloc(0);
222
+ }
223
+
224
+ function emitEncodedUnit(payload) {
225
+ if (!payload?.length) return;
226
+ const starts = findStartCodes(payload);
227
+ const isKeyFrame = starts.some(start => (payload[start.header] & 0x1f) === 5);
228
+ encoderOutputSeen = true;
229
+ clearTimeout(encoderStartupTimer);
230
+ encoderStartupTimer = null;
231
+ frameSeq += 1;
232
+ send({
233
+ type: 'frame',
234
+ payload: Buffer.from(payload),
235
+ metadata: {
236
+ frameSeq,
237
+ streamFrameSeq: frameSeq,
238
+ width: config.width,
239
+ height: config.height,
240
+ fps: config.fps,
241
+ durationUs: Math.round(1_000_000 / config.fps),
242
+ timestampUs: Math.round(frameSeq * 1_000_000 / config.fps),
243
+ isKeyFrame,
244
+ chunkType: isKeyFrame ? 'key' : 'delta',
245
+ hardwareEncoder: encoder?.candidate?.name || '',
246
+ encoderGeneration,
247
+ layoutVersion,
248
+ composeMs: lastComposeMs,
249
+ poolStarved,
250
+ readyTileCount: latestTiles.size,
251
+ tileCount: config.deviceIds.length
252
+ }
253
+ });
254
+ }
255
+
256
+ function rebuildLayout() {
257
+ const { columns, rows, tileWidth, tileHeight } = config;
258
+ layoutVersion += 1;
259
+ layout = config.deviceIds.map((deviceId, index) => ({
260
+ deviceId,
261
+ index,
262
+ column: index % columns,
263
+ row: Math.floor(index / columns),
264
+ x: (index % columns) * tileWidth,
265
+ y: Math.floor(index / columns) * tileHeight,
266
+ width: tileWidth,
267
+ height: tileHeight
268
+ }));
269
+ for (const tile of latestTiles.values()) tile.rendered = null;
270
+ framePool = Array.from({ length: FRAME_POOL_SIZE }, () => Buffer.alloc(config.width * config.height * 3));
271
+ send({ type: 'layout', layoutVersion, width: config.width, height: config.height, columns, rows, tiles: layout });
272
+ }
273
+
274
+ function renderTile(tileState, tileLayout) {
275
+ const sourceWidth = tileState.width;
276
+ const sourceHeight = tileState.height;
277
+ const scale = Math.min(tileLayout.width / sourceWidth, tileLayout.height / sourceHeight);
278
+ const width = Math.max(1, Math.floor(sourceWidth * scale));
279
+ const height = Math.max(1, Math.floor(sourceHeight * scale));
280
+ const pixels = Buffer.allocUnsafe(width * height * 3);
281
+ let target = 0;
282
+ if (width === sourceWidth && height === sourceHeight) {
283
+ for (let source = 0; source < tileState.rgb565.length; source += 2) {
284
+ const pixel = tileState.rgb565[source] | (tileState.rgb565[source + 1] << 8);
285
+ pixels[target++] = rgb565Red[pixel];
286
+ pixels[target++] = rgb565Green[pixel];
287
+ pixels[target++] = rgb565Blue[pixel];
288
+ }
289
+ } else {
290
+ for (let y = 0; y < height; y += 1) {
291
+ const sourceY = Math.min(sourceHeight - 1, Math.floor((y + 0.5) * sourceHeight / height));
292
+ for (let x = 0; x < width; x += 1) {
293
+ const sourceX = Math.min(sourceWidth - 1, Math.floor((x + 0.5) * sourceWidth / width));
294
+ const source = (sourceY * sourceWidth + sourceX) * 2;
295
+ const pixel = tileState.rgb565[source] | (tileState.rgb565[source + 1] << 8);
296
+ pixels[target++] = rgb565Red[pixel];
297
+ pixels[target++] = rgb565Green[pixel];
298
+ pixels[target++] = rgb565Blue[pixel];
299
+ }
300
+ }
301
+ }
302
+ tileState.rendered = {
303
+ pixels,
304
+ width,
305
+ height,
306
+ x: tileLayout.x + Math.floor((tileLayout.width - width) / 2),
307
+ y: tileLayout.y + Math.floor((tileLayout.height - height) / 2),
308
+ layoutVersion
309
+ };
310
+ }
311
+
312
+ function ingestFrame(message) {
313
+ const deviceId = String(message.deviceId || '');
314
+ if (!deviceId || !config.deviceIds.includes(deviceId)) return;
315
+ const width = clamp(message.width, 1, config.sourceTileWidth, 0);
316
+ const height = clamp(message.height, 1, config.sourceTileHeight, 0);
317
+ const expectedLength = width * height * 2;
318
+ if (!width || !height || Number(message.uncompressedByteLength) !== expectedLength) return;
319
+ try {
320
+ const compressed = Buffer.isBuffer(message.payload) ? message.payload : Buffer.from(message.payload || []);
321
+ const rgb565 = decodeLzo1xBlock(new Uint8Array(compressed), expectedLength);
322
+ const tileState = { width, height, rgb565, rendered: null, frameSeq: Number(message.frameSeq || 0) };
323
+ latestTiles.set(deviceId, tileState);
324
+ if (!encoder && encoderCandidates.length > 0) startEncoder();
325
+ if (config.deviceIds.length > 0 && config.deviceIds.every(id => latestTiles.has(id))) {
326
+ clearTimeout(inputWaitTimer);
327
+ inputWaitTimer = null;
328
+ }
329
+ const tileLayout = layout.find(item => item.deviceId === deviceId);
330
+ if (tileLayout) renderTile(tileState, tileLayout);
331
+ } catch (error) {
332
+ send({ type: 'log', level: 'warn', message: `Mode 4 frame rejected for ${deviceId}: ${error.message}` });
333
+ }
334
+ }
335
+
336
+ function writeAtlasFrame() {
337
+ if (latestTiles.size === 0) return;
338
+ if (!encoder) {
339
+ startEncoder();
340
+ return;
341
+ }
342
+ const composeStartedAt = performance.now();
343
+ const target = framePool.pop();
344
+ const stdin = encoder?.child?.stdin;
345
+ if (!target || !stdin || stdin.destroyed || !stdin.writable) {
346
+ if (!target) poolStarved += 1;
347
+ return;
348
+ }
349
+ target.fill(0);
350
+ for (const tileLayout of layout) {
351
+ const tileState = latestTiles.get(tileLayout.deviceId);
352
+ if (!tileState) continue;
353
+ if (!tileState.rendered || tileState.rendered.layoutVersion !== layoutVersion) renderTile(tileState, tileLayout);
354
+ const rendered = tileState.rendered;
355
+ for (let row = 0; row < rendered.height; row += 1) {
356
+ const sourceStart = row * rendered.width * 3;
357
+ const targetStart = ((rendered.y + row) * config.width + rendered.x) * 3;
358
+ rendered.pixels.copy(target, targetStart, sourceStart, sourceStart + rendered.width * 3);
359
+ }
360
+ }
361
+ lastComposeMs = Math.max(0, performance.now() - composeStartedAt);
362
+ try {
363
+ const encoderEntry = encoder;
364
+ if (!encoderEntry.inputSeen) {
365
+ encoderEntry.inputSeen = true;
366
+ armEncoderStartupTimer(encoderEntry.child, encoderEntry.candidate);
367
+ }
368
+ stdin.write(target, error => {
369
+ framePool.push(target);
370
+ if (error && encoder?.child === encoderEntry.child && !isClosedPipeError(error)) {
371
+ send({ type: 'log', level: 'warn', message: `Mode 4 encoder write: ${error.message}` });
372
+ }
373
+ });
374
+ } catch (error) {
375
+ framePool.push(target);
376
+ if (!isClosedPipeError(error)) send({ type: 'log', level: 'warn', message: `Mode 4 encoder write: ${error.message}` });
377
+ }
378
+ }
379
+
380
+ function startComposeClock() {
381
+ clearTimeout(tickTimer);
382
+ const intervalMs = 1000 / config.fps;
383
+ nextTickAt = performance.now() + intervalMs;
384
+ const tick = () => {
385
+ if (closed) return;
386
+ writeAtlasFrame();
387
+ const now = performance.now();
388
+ nextTickAt += intervalMs;
389
+ if (nextTickAt < now - intervalMs) nextTickAt = now + intervalMs;
390
+ tickTimer = setTimeout(tick, Math.max(0, nextTickAt - performance.now()));
391
+ };
392
+ tickTimer = setTimeout(tick, intervalMs);
393
+ }
394
+
395
+ function configure(value) {
396
+ const next = normalizeConfig(value);
397
+ const encoderChanged = next.width !== config.width || next.height !== config.height || next.fps !== config.fps;
398
+ config = next;
399
+ latestTiles = new Map([...latestTiles].filter(([deviceId]) => config.deviceIds.includes(deviceId)));
400
+ clearTimeout(inputWaitTimer);
401
+ inputWaitTimer = config.deviceIds.length > 0 ? setTimeout(() => {
402
+ const missing = config.deviceIds.filter(deviceId => !latestTiles.has(deviceId));
403
+ if (missing.length > 0) {
404
+ send({
405
+ type: 'log',
406
+ level: 'warn',
407
+ message: `Mode 4 waiting for Mode 2 input from ${missing.length}/${config.deviceIds.length} devices: ${missing.slice(0, 8).join(', ')}`
408
+ });
409
+ }
410
+ }, 3000) : null;
411
+ rebuildLayout();
412
+ if (encoderChanged || !encoder) {
413
+ stopEncoder();
414
+ encoderCandidates = buildEncoderCandidates();
415
+ encoderCandidateIndex = 0;
416
+ if (latestTiles.size > 0) startEncoder();
417
+ }
418
+ startComposeClock();
419
+ }
420
+
421
+ process.on('message', message => {
422
+ if (message?.type === 'configure') configure(message);
423
+ else if (message?.type === 'frame') ingestFrame(message);
424
+ else if (message?.type === 'close') shutdown();
425
+ });
426
+
427
+ function shutdown() {
428
+ if (closed) return;
429
+ closed = true;
430
+ clearTimeout(tickTimer);
431
+ clearTimeout(inputWaitTimer);
432
+ stopEncoder();
433
+ setTimeout(() => process.exit(0), 50).unref?.();
434
+ }
435
+
436
+ process.on('disconnect', shutdown);
437
+ process.on('SIGTERM', shutdown);
438
+ process.on('SIGINT', shutdown);
439
+ send({ type: 'status', state: 'ready' });
440
+
package/mode4-atlas.js ADDED
@@ -0,0 +1,130 @@
1
+ import { fork } from 'node:child_process';
2
+ import { fileURLToPath } from 'node:url';
3
+
4
+ const workerPath = fileURLToPath(new URL('./mode4-atlas-worker.js', import.meta.url));
5
+
6
+ export class Mode4AtlasSession {
7
+ constructor(options = {}) {
8
+ this.onFrame = typeof options.onFrame === 'function' ? options.onFrame : () => {};
9
+ this.onLayout = typeof options.onLayout === 'function' ? options.onLayout : () => {};
10
+ this.onStatus = typeof options.onStatus === 'function' ? options.onStatus : () => {};
11
+ this.deviceIds = [];
12
+ this.deviceSet = new Set();
13
+ this.config = null;
14
+ this.closed = false;
15
+ this.worker = null;
16
+ this.restartTimer = null;
17
+ this.pendingFrames = new Map();
18
+ this.frameSendsInFlight = new Set();
19
+ this.workerGeneration = 0;
20
+ this.startWorker();
21
+ }
22
+
23
+ startWorker() {
24
+ if (this.closed || this.worker) return;
25
+ const workerGeneration = ++this.workerGeneration;
26
+ const worker = fork(workerPath, [], {
27
+ env: { ...process.env },
28
+ stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
29
+ serialization: 'advanced',
30
+ windowsHide: true
31
+ });
32
+ this.worker = worker;
33
+ worker.on('message', message => {
34
+ if (message?.type === 'frame' && message.metadata && message.payload) {
35
+ this.onFrame({
36
+ metadata: { ...message.metadata, workerGeneration },
37
+ payload: Buffer.from(message.payload)
38
+ });
39
+ } else if (message?.type === 'layout') {
40
+ this.onLayout(message);
41
+ } else if (message?.type === 'status' || message?.type === 'log') {
42
+ this.onStatus(message);
43
+ }
44
+ });
45
+ worker.on('exit', code => {
46
+ if (this.worker === worker) this.worker = null;
47
+ this.frameSendsInFlight.clear();
48
+ if (this.closed) return;
49
+ this.onStatus({ type: 'status', state: 'worker-restart', code });
50
+ clearTimeout(this.restartTimer);
51
+ this.restartTimer = setTimeout(() => {
52
+ this.startWorker();
53
+ if (this.config) this.worker?.send({ type: 'configure', ...this.config });
54
+ }, 750);
55
+ });
56
+ worker.on('error', error => this.onStatus({ type: 'status', state: 'worker-error', error: error.message }));
57
+ if (this.config) {
58
+ worker.send({ type: 'configure', ...this.config }, () => {
59
+ for (const deviceId of this.pendingFrames.keys()) this.pumpDeviceFrame(deviceId);
60
+ });
61
+ }
62
+ }
63
+
64
+ configure(options = {}) {
65
+ this.deviceIds = [...new Set((Array.isArray(options.deviceIds) ? options.deviceIds : [])
66
+ .map(value => String(value || '').trim()).filter(Boolean))].slice(0, 100);
67
+ this.deviceSet = new Set(this.deviceIds);
68
+ this.pendingFrames = new Map([...this.pendingFrames].filter(([deviceId]) => this.deviceSet.has(deviceId)));
69
+ this.config = {
70
+ deviceIds: this.deviceIds,
71
+ width: Number(options.width || 1920),
72
+ height: Number(options.height || 1080),
73
+ tileWidth: Number(options.tileWidth || 320),
74
+ tileHeight: Number(options.tileHeight || 180),
75
+ fps: Number(options.fps || 20)
76
+ };
77
+ this.worker?.send({ type: 'configure', ...this.config });
78
+ }
79
+
80
+ pumpDeviceFrame(deviceId) {
81
+ if (this.closed || !this.worker || this.frameSendsInFlight.has(deviceId)) return;
82
+ const message = this.pendingFrames.get(deviceId);
83
+ if (!message) return;
84
+ this.pendingFrames.delete(deviceId);
85
+ this.frameSendsInFlight.add(deviceId);
86
+ try {
87
+ this.worker.send(message, error => {
88
+ this.frameSendsInFlight.delete(deviceId);
89
+ if (error && !this.closed) {
90
+ this.onStatus({ type: 'log', level: 'warn', message: `Mode 4 input ${deviceId}: ${error.message}` });
91
+ }
92
+ this.pumpDeviceFrame(deviceId);
93
+ });
94
+ } catch (error) {
95
+ this.frameSendsInFlight.delete(deviceId);
96
+ this.onStatus({ type: 'log', level: 'warn', message: `Mode 4 input ${deviceId}: ${error.message}` });
97
+ }
98
+ }
99
+
100
+ ingest(frameEvent) {
101
+ if (this.closed || !this.worker) return;
102
+ const frame = frameEvent?.frame || {};
103
+ const deviceId = String(frameEvent?.deviceId || frame.deviceId || '').trim();
104
+ const mode = String(frame.frameMode || frame.mode || '').toLowerCase();
105
+ if (!this.deviceSet.has(deviceId) || mode !== 'mode2-lzo' || !Buffer.isBuffer(frameEvent?.payload)) return;
106
+ this.pendingFrames.set(deviceId, {
107
+ type: 'frame',
108
+ deviceId,
109
+ frameSeq: Number(frame.frameSeq || 0),
110
+ width: Number(frame.width || 0),
111
+ height: Number(frame.height || 0),
112
+ uncompressedByteLength: Number(frame.uncompressedByteLength || 0),
113
+ payload: frameEvent.payload
114
+ });
115
+ this.pumpDeviceFrame(deviceId);
116
+ }
117
+
118
+ close() {
119
+ if (this.closed) return;
120
+ this.closed = true;
121
+ clearTimeout(this.restartTimer);
122
+ this.pendingFrames.clear();
123
+ this.frameSendsInFlight.clear();
124
+ const worker = this.worker;
125
+ this.worker = null;
126
+ try { worker?.send({ type: 'close' }); } catch {}
127
+ setTimeout(() => { try { worker?.kill(); } catch {} }, 400).unref?.();
128
+ }
129
+ }
130
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexec/cli",
3
- "version": "0.2.443",
3
+ "version": "0.2.444",
4
4
  "description": "MindExec local runtime and bridge CLI",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -10,6 +10,9 @@
10
10
  "launch-bridge.cjs",
11
11
  "server.js",
12
12
  "remote-hub.js",
13
+ "lzo1x.js",
14
+ "mode4-atlas.js",
15
+ "mode4-atlas-worker.js",
13
16
  "codex-runtime.js",
14
17
  "codex-model-catalog.js",
15
18
  "port-guard.cjs",
@@ -59,11 +62,13 @@
59
62
  "node": ">=20"
60
63
  },
61
64
  "dependencies": {
65
+ "@ffmpeg-installer/ffmpeg": "^1.1.0",
62
66
  "@mindexec/remote": "^0.1.17",
63
67
  "@openai/codex-sdk": "^0.144.1",
64
68
  "chokidar": "^3.6.0",
65
69
  "cors": "^2.8.5",
66
70
  "express": "^4.18.2",
71
+ "ffmpeg-static": "^5.3.0",
67
72
  "multer": "^2.2.0",
68
73
  "playwright-core": "^1.53.0",
69
74
  "sharp": "^0.33.2",
package/server.js CHANGED
@@ -24,6 +24,7 @@ import { fileURLToPath } from 'url';
24
24
  import { createCodexRuntime } from './codex-runtime.js';
25
25
  import { createCodexModelCatalog } from './codex-model-catalog.js';
26
26
  import { createRemoteHub } from './remote-hub.js';
27
+ import { Mode4AtlasSession } from './mode4-atlas.js';
27
28
  import portGuard from './port-guard.cjs';
28
29
 
29
30
  const execAsync = promisify(exec);
@@ -2399,12 +2400,15 @@ app.get('/api/local-files/:id', async (req, res) => {
2399
2400
  const httpServer = createServer(app);
2400
2401
  const wss = new WebSocketServer({ noServer: true });
2401
2402
  const remoteFrameWss = new WebSocketServer({ noServer: true });
2403
+ const remoteAtlasWss = new WebSocketServer({ noServer: true });
2402
2404
  const remoteInputWss = new WebSocketServer({ noServer: true });
2403
2405
  const wsClients = new Set();
2404
2406
  const remoteFrameClients = new Set();
2407
+ const remoteAtlasClients = new Map();
2405
2408
  const remoteInputClients = new Set();
2406
2409
  const shellJobs = new Map();
2407
2410
  let remoteFrameClientSeq = 0;
2411
+ let remoteAtlasClientSeq = 0;
2408
2412
  let remoteInputClientSeq = 0;
2409
2413
  const remoteFrameWsDiagnostics = {
2410
2414
  opened: 0,
@@ -2480,6 +2484,14 @@ httpServer.on('upgrade', (req, socket, head) => {
2480
2484
  return;
2481
2485
  }
2482
2486
 
2487
+ if (parsed.pathname === '/api/remote/atlas/ws'
2488
+ && (!bridgeAuthRequired || token === bridgeToken)) {
2489
+ remoteAtlasWss.handleUpgrade(req, socket, head, (ws) => {
2490
+ remoteAtlasWss.emit('connection', ws, req);
2491
+ });
2492
+ return;
2493
+ }
2494
+
2483
2495
  if (parsed.pathname === '/api/remote/input/ws'
2484
2496
  && (!bridgeAuthRequired || token === bridgeToken)) {
2485
2497
  remoteInputWss.handleUpgrade(req, socket, head, (ws) => {
@@ -2740,6 +2752,15 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
2740
2752
  durationUs: Number.isFinite(Number(frame.durationUs)) ? Number(frame.durationUs) : 0,
2741
2753
  streamFrameSeq: Number.isFinite(Number(frame.streamFrameSeq)) ? Number(frame.streamFrameSeq) : 0,
2742
2754
  commandId: frame.commandId || ''
2755
+ ,layoutVersion: Number(frame.layoutVersion || 0) || 0
2756
+ ,atlasComposeMs: Number(frame.atlasComposeMs || 0) || 0
2757
+ ,atlasPoolStarved: Number(frame.atlasPoolStarved || 0) || 0
2758
+ ,atlasReadyTiles: Number(frame.atlasReadyTiles || 0) || 0
2759
+ ,atlasTileCount: Number(frame.atlasTileCount || 0) || 0
2760
+ ,hardwareEncoder: frame.hardwareEncoder || ''
2761
+ ,platformProfile: frame.platformProfile || ''
2762
+ ,workerGeneration: Number(frame.workerGeneration || 0) || 0
2763
+ ,encoderGeneration: Number(frame.encoderGeneration || 0) || 0
2743
2764
  };
2744
2765
  const metaBuffer = Buffer.from(JSON.stringify(metadata), 'utf8');
2745
2766
  const header = Buffer.allocUnsafe(4);
@@ -2747,7 +2768,84 @@ function buildRemoteFrameBinaryPacket(frameEvent) {
2747
2768
  return Buffer.concat([header, metaBuffer, payload], header.length + metaBuffer.length + payload.length);
2748
2769
  }
2749
2770
 
2771
+ function sendMode4AtlasFrame(ws, output) {
2772
+ if (!ws || ws.readyState !== 1 || !output?.payload?.length) return;
2773
+ const metadata = output.metadata || {};
2774
+ const frameSeq = Number(metadata.frameSeq || 0) || 0;
2775
+ const isKeyFrame = metadata.isKeyFrame === true;
2776
+ const workerGeneration = Math.max(1, Number(metadata.workerGeneration || 1) || 1);
2777
+ const encoderGeneration = Math.max(1, Number(metadata.encoderGeneration || 1) || 1);
2778
+ const streamId = `${ws.remoteAtlasStreamId}-w${workerGeneration}-e${encoderGeneration}`;
2779
+ const width = Number(metadata.width || 1920) || 1920;
2780
+ const height = Number(metadata.height || 1080) || 1080;
2781
+ const frameEvent = {
2782
+ kind: 'live',
2783
+ deviceId: '__mindexec_mode4_atlas__',
2784
+ mimeType: 'video/h264',
2785
+ byteLength: output.payload.length,
2786
+ payload: output.payload,
2787
+ frame: {
2788
+ deviceId: '__mindexec_mode4_atlas__', frameSeq,
2789
+ streamFrameSeq: Number(metadata.streamFrameSeq || frameSeq) || frameSeq,
2790
+ streamId, commandId: streamId, width, height,
2791
+ sourceWidth: width, sourceHeight: height,
2792
+ mimeType: 'video/h264', mode: 'mode4-h264-atlas', frameMode: 'mode4-h264-atlas',
2793
+ frameProfile: 'mode4-h264-atlas-v1', encoding: 'video-atlas', codec: 'h264',
2794
+ compression: 'video/h264', h264Format: 'annexb', isKeyFrame,
2795
+ chunkType: isKeyFrame ? 'key' : 'delta',
2796
+ timestampUs: Number(metadata.timestampUs || 0), durationUs: Number(metadata.durationUs || 0),
2797
+ fps: Number(metadata.fps || 20), capturedAt: new Date().toISOString(), receivedAt: new Date().toISOString(),
2798
+ hardwareEncoder: String(metadata.hardwareEncoder || ''), platformProfile: 'mode4-hub-atlas-worker',
2799
+ workerGeneration, encoderGeneration, layoutVersion: Number(metadata.layoutVersion || 0),
2800
+ atlasComposeMs: Number(metadata.composeMs || 0), atlasPoolStarved: Number(metadata.poolStarved || 0),
2801
+ atlasReadyTiles: Number(metadata.readyTileCount || 0), atlasTileCount: Number(metadata.tileCount || 0)
2802
+ }
2803
+ };
2804
+ const packet = buildRemoteFrameBinaryPacket(frameEvent);
2805
+ if (!packet || ws.bufferedAmount > 8 * 1024 * 1024) return;
2806
+ try { ws.send(packet, { binary: true }); } catch { /* client closed */ }
2807
+ }
2808
+
2809
+ function normalizeRemoteAtlasMonitorSelections(value) {
2810
+ if (!value || typeof value !== 'object') return {};
2811
+ const result = {};
2812
+ for (const [key, entry] of Object.entries(value)) result[String(key)] = Math.max(0, Number(entry) || 0);
2813
+ return result;
2814
+ }
2815
+
2816
+ function configureMode4Atlas(ws, payload = {}) {
2817
+ const deviceIds = normalizeRemoteFrameWsDeviceIds(payload.deviceIds ?? payload.devices ?? payload.deviceId).slice(0, 100);
2818
+ const monitorSelections = normalizeRemoteAtlasMonitorSelections(payload.monitorSelections);
2819
+ const sharpTiles = Number(payload.tileWidth) >= 480 || Number(payload.tileHeight) >= 270;
2820
+ const tileWidth = sharpTiles ? 480 : 320;
2821
+ const tileHeight = sharpTiles ? 270 : 180;
2822
+ ws.remoteAtlasDeviceIds = new Set(deviceIds);
2823
+ ws.remoteAtlasInputOptions = { tileWidth, tileHeight, monitorSelections };
2824
+ ws.remoteAtlasSession.configure({
2825
+ deviceIds,
2826
+ width: clampRemoteFrameWsNumber(payload.width, 640, 3840, 1920),
2827
+ height: clampRemoteFrameWsNumber(payload.height, 360, 2160, 1080),
2828
+ tileWidth, tileHeight,
2829
+ fps: clampRemoteFrameWsNumber(payload.fps, 1, 30, 20)
2830
+ });
2831
+ for (const deviceId of deviceIds) startMode4AtlasInput(ws, deviceId, 'atlas-configure');
2832
+ sendRemoteFrameClientJson(ws, { type: 'Mode4AtlasSubscription', deviceIds, inputMode: 'mode2-lzo', outputMode: 'mode4-h264-atlas', tileWidth, tileHeight, timestamp: new Date().toISOString() });
2833
+ }
2834
+
2835
+ function startMode4AtlasInput(ws, deviceId, reason = 'atlas-configure') {
2836
+ const options = ws?.remoteAtlasInputOptions;
2837
+ if (!options || !ws.remoteAtlasDeviceIds?.has(deviceId)) return { ok: false, error: 'atlas-device-not-configured' };
2838
+ return remoteHub.startLiveStream(deviceId, {
2839
+ fps: 8, maxWidth: options.tileWidth, maxHeight: options.tileHeight, quality: 60,
2840
+ mode: 'mode2-lzo', frameMode: 'mode2-lzo', streamPurpose: 'atlas',
2841
+ monitorIndex: Number(options.monitorSelections?.[deviceId] || 0),
2842
+ streamId: `atlas-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`,
2843
+ restartReason: reason
2844
+ });
2845
+ }
2846
+
2750
2847
  function broadcastRemoteBinaryFrame(frameEvent) {
2848
+ for (const session of remoteAtlasClients.values()) session.ingest(frameEvent);
2751
2849
  const frame = frameEvent?.frame || {};
2752
2850
  remoteFrameWsDiagnostics.framesReceived += 1;
2753
2851
  remoteFrameWsDiagnostics.lastFrameAt = new Date().toISOString();
@@ -2908,6 +3006,44 @@ remoteFrameWss.on('connection', (ws, req) => {
2908
3006
  });
2909
3007
  });
2910
3008
 
3009
+ remoteAtlasWss.on('connection', (ws) => {
3010
+ ws.binaryType = 'arraybuffer';
3011
+ ws.remoteAtlasClientId = `ratlas-${++remoteAtlasClientSeq}`;
3012
+ ws.remoteAtlasStreamId = `mode4-atlas-${crypto.randomUUID()}`;
3013
+ try { ws._socket?.setNoDelay?.(true); } catch { /* ignore */ }
3014
+ const session = new Mode4AtlasSession({
3015
+ onFrame: output => sendMode4AtlasFrame(ws, output),
3016
+ onLayout: layout => sendRemoteFrameClientJson(ws, { ...layout, type: 'Mode4AtlasLayout' }),
3017
+ onStatus: status => sendRemoteFrameClientJson(ws, { ...status, type: 'Mode4AtlasStatus' })
3018
+ });
3019
+ ws.remoteAtlasSession = session;
3020
+ remoteAtlasClients.set(ws, session);
3021
+ let cleaned = false;
3022
+ const cleanup = () => {
3023
+ if (cleaned) return;
3024
+ cleaned = true;
3025
+ remoteAtlasClients.delete(ws);
3026
+ session.close();
3027
+ ws.remoteAtlasSession = null;
3028
+ };
3029
+ ws.on('message', data => {
3030
+ try {
3031
+ const payload = JSON.parse(Buffer.isBuffer(data) ? data.toString('utf8') : String(data || ''));
3032
+ if (payload?.type === 'subscribe' || payload?.type === 'configure') configureMode4Atlas(ws, payload);
3033
+ } catch {
3034
+ sendRemoteFrameClientJson(ws, { type: 'Mode4AtlasError', error: 'invalid-message' });
3035
+ }
3036
+ });
3037
+ ws.on('close', cleanup);
3038
+ ws.on('error', cleanup);
3039
+ sendRemoteFrameClientJson(ws, {
3040
+ type: 'Mode4AtlasSocketReady',
3041
+ protocol: 'mindexec.remote.atlas.h264.v1',
3042
+ clientId: ws.remoteAtlasClientId,
3043
+ timestamp: new Date().toISOString()
3044
+ });
3045
+ });
3046
+
2911
3047
  function sendRemoteInputClientJson(ws, payload) {
2912
3048
  if (!ws || ws.readyState !== WebSocket.OPEN) {
2913
3049
  return false;
@@ -12418,6 +12554,7 @@ app.get('/api/status', async (req, res) => {
12418
12554
  wsToken,
12419
12555
  wsPath: '/events',
12420
12556
  remoteFrameWsPath: '/api/remote/frames/ws',
12557
+ remoteAtlasWsPath: '/api/remote/atlas/ws',
12421
12558
  remoteInputWsPath: '/api/remote/input/ws',
12422
12559
  bridgeToken,
12423
12560
  bridgeTokenHeader,
@@ -14342,6 +14479,14 @@ async function shutdownBridge(signal) {
14342
14479
  }
14343
14480
  }
14344
14481
 
14482
+ for (const client of remoteAtlasClients.keys()) {
14483
+ try { client.close(); } catch { /* Ignore close race */ }
14484
+ }
14485
+ for (const session of remoteAtlasClients.values()) {
14486
+ try { session.close(); } catch { /* Ignore atlas worker shutdown errors */ }
14487
+ }
14488
+ remoteAtlasClients.clear();
14489
+
14345
14490
  for (const client of remoteInputClients) {
14346
14491
  try {
14347
14492
  client.close();
@@ -14356,6 +14501,12 @@ async function shutdownBridge(signal) {
14356
14501
  // Ignore if already closed
14357
14502
  }
14358
14503
 
14504
+ try {
14505
+ remoteAtlasWss.close();
14506
+ } catch {
14507
+ // Ignore if already closed
14508
+ }
14509
+
14359
14510
  try {
14360
14511
  remoteInputWss.close();
14361
14512
  } catch {