@opencrater/sdk 0.8.48 → 0.8.50
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.
Potentially problematic release.
This version of @opencrater/sdk might be problematic. Click here for more details.
- package/dist/paint.js +147 -12
- package/package.json +1 -1
package/dist/paint.js
CHANGED
|
@@ -298,16 +298,29 @@ var WIN_PUMP_PS = [
|
|
|
298
298
|
// parent chain, attach to each ancestor, and keep the first console that is
|
|
299
299
|
// NOT the 9001-row hidden buffer — that is the one with a window on screen.
|
|
300
300
|
"$self=Csbi (OpenConout); if($self){ L ('pump: SELF bufH='+$self.sz.Y+' winRows='+($self.win.B-$self.win.T+1)+' curY='+$self.cur.Y) }",
|
|
301
|
-
"$par=@{}; try{ Get-CimInstance Win32_Process -ErrorAction Stop | ForEach-Object { $par[[int]$_.ProcessId]=[int]$_.ParentProcessId } }catch{ L 'pump: WMI walk failed' }",
|
|
302
|
-
"$chain=New-Object System.Collections.ArrayList; $p=$PID; for($i=0;$i -lt 12;$i++){ if(-not $par.ContainsKey($p)){break}; $pp=$par[$p]; if($pp -le 4){break}; $nm='?'; try{ $nm=(Get-Process -Id $pp -ErrorAction Stop).ProcessName }catch{}; [void]$chain.Add(@{id=$pp;name=$nm}); $p=$pp }",
|
|
303
|
-
"L ('pump: ancestors = ' + (($chain | ForEach-Object { $_.name+'('+$_.id+')' }) -join ' -> '))",
|
|
304
301
|
"$how='self'; $winId=0",
|
|
305
|
-
|
|
302
|
+
// Given an explicit console-owner PID (the escaped painter passes it — its
|
|
303
|
+
// own parent chain is WmiPrvSE, so the walk below would never find Windows
|
|
304
|
+
// Terminal), attach to it directly and skip discovery.
|
|
305
|
+
"$given=[int]($env:OPENCRATER_WIN_CONSOLE_PID)",
|
|
306
|
+
"if($given -gt 0){ [void][OCP.N]::FreeConsole(); if([OCP.N]::AttachConsole([uint32]$given)){ $winId=$given; $how='given' } L ('pump: given console pid '+$given+' attached='+($winId -eq $given)) }",
|
|
307
|
+
"if($winId -eq 0){",
|
|
308
|
+
" $par=@{}; try{ Get-CimInstance Win32_Process -ErrorAction Stop | ForEach-Object { $par[[int]$_.ProcessId]=[int]$_.ParentProcessId } }catch{ L 'pump: WMI walk failed' }",
|
|
309
|
+
" $chain=New-Object System.Collections.ArrayList; $p=$PID; for($i=0;$i -lt 12;$i++){ if(-not $par.ContainsKey($p)){break}; $pp=$par[$p]; if($pp -le 4){break}; $nm='?'; try{ $nm=(Get-Process -Id $pp -ErrorAction Stop).ProcessName }catch{}; [void]$chain.Add(@{id=$pp;name=$nm}); $p=$pp }",
|
|
310
|
+
" L ('pump: ancestors = ' + (($chain | ForEach-Object { $_.name+'('+$_.id+')' }) -join ' -> '))",
|
|
311
|
+
" foreach($c in $chain){ [void][OCP.N]::FreeConsole(); if([OCP.N]::AttachConsole([uint32]$c.id)){ $b=Csbi (OpenConout); if($b){ L ('pump: '+$c.name+'('+$c.id+') bufH='+$b.sz.Y+' winRows='+($b.win.B-$b.win.T+1)+' curY='+$b.cur.Y); if($winId -eq 0 -and $b.sz.Y -lt 1000){ $winId=$c.id; $how=('attach:'+$c.name) } } } }",
|
|
312
|
+
"}",
|
|
306
313
|
"[void][OCP.N]::FreeConsole()",
|
|
307
314
|
"if($winId -ne 0){ [void][OCP.N]::AttachConsole([uint32]$winId) } else { [void][OCP.N]::AttachConsole(0xFFFFFFFF); $how='attach-parent' }",
|
|
308
315
|
"$h=OpenConout",
|
|
309
316
|
"if($h -eq [IntPtr]-1){ L 'pump: NO CONSOLE after attach walk'; exit 1 }",
|
|
310
317
|
"$fb=Csbi $h; if($fb){ L ('pump: FINAL via '+$how+' bufH='+$fb.sz.Y+' winRows='+($fb.win.B-$fb.win.T+1)+' curY='+$fb.cur.Y) } else { L ('pump: FINAL via '+$how) }",
|
|
318
|
+
// Report the REAL visible-console size back to the painter (over stdout, a
|
|
319
|
+
// pipe — the card itself goes to CONOUT$ via WriteFile, so this channel is
|
|
320
|
+
// free). The painter blocks on this line, then anchors the card to the true
|
|
321
|
+
// right edge. win.R/L/B/T are the on-screen window rect (not the scrollback
|
|
322
|
+
// buffer), so this is exactly what `mode con` SHOULD have returned.
|
|
323
|
+
"if($fb){ $cols=$fb.win.R-$fb.win.L+1; $rows=$fb.win.B-$fb.win.T+1; [Console]::Out.WriteLine('OCWIN '+$winId+' '+$cols+' '+$rows); [Console]::Out.Flush() }",
|
|
311
324
|
"$m=[uint32]0;[void][OCP.N]::GetConsoleMode($h,[ref]$m);[void][OCP.N]::SetConsoleMode($h,($m -bor 4));[void][OCP.N]::SetConsoleOutputCP(65001)",
|
|
312
325
|
// Stream frames straight to the console as they arrive. The painter drives
|
|
313
326
|
// the timing — it repaints the overlay (live countdown, position defence)
|
|
@@ -337,7 +350,7 @@ function spawnWindowsConsoleSink() {
|
|
|
337
350
|
}
|
|
338
351
|
pdebug("spawning pump:", exe);
|
|
339
352
|
const child = child_process.spawn(exe, ["-NoProfile", "-NonInteractive", "-EncodedCommand", b64], {
|
|
340
|
-
stdio: ["pipe",
|
|
353
|
+
stdio: ["pipe", "pipe", errStdio],
|
|
341
354
|
windowsHide: false
|
|
342
355
|
});
|
|
343
356
|
child.on("error", (e) => pdebug("pump spawn error:", exe, "-", e.message));
|
|
@@ -353,7 +366,32 @@ function spawnWindowsConsoleSink() {
|
|
|
353
366
|
child.on("exit", () => {
|
|
354
367
|
exited = true;
|
|
355
368
|
});
|
|
369
|
+
const size = new Promise((resolve) => {
|
|
370
|
+
let acc = "";
|
|
371
|
+
let settled = false;
|
|
372
|
+
const done = (v) => {
|
|
373
|
+
if (!settled) {
|
|
374
|
+
settled = true;
|
|
375
|
+
resolve(v);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
const out = child.stdout;
|
|
379
|
+
if (!out) return done(null);
|
|
380
|
+
out.setEncoding("utf8");
|
|
381
|
+
out.on("data", (chunk) => {
|
|
382
|
+
acc += chunk;
|
|
383
|
+
const m = acc.match(/OCWIN\s+(\d+)\s+(\d+)\s+(\d+)/);
|
|
384
|
+
if (m) {
|
|
385
|
+
pdebug("pump reported console pid", m[1], "size", m[2] + "x" + m[3]);
|
|
386
|
+
done({ pid: Number(m[1]), cols: Number(m[2]), rows: Number(m[3]) });
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
out.on("error", () => done(null));
|
|
390
|
+
out.on("end", () => done(null));
|
|
391
|
+
setTimeout(() => done(null), 2500);
|
|
392
|
+
});
|
|
356
393
|
return {
|
|
394
|
+
size,
|
|
357
395
|
write: (d) => {
|
|
358
396
|
try {
|
|
359
397
|
stdin.write(d);
|
|
@@ -389,6 +427,50 @@ function spawnWindowsConsoleSink() {
|
|
|
389
427
|
return null;
|
|
390
428
|
}
|
|
391
429
|
}
|
|
430
|
+
function wmiSpawnDetached(cmdLine) {
|
|
431
|
+
try {
|
|
432
|
+
const esc = cmdLine.replace(/'/g, "''");
|
|
433
|
+
const ps = `$ErrorActionPreference='Stop';try{$r=Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine='${esc}'};if($r.ReturnValue -eq 0){[Console]::Out.Write('OCPID '+$r.ProcessId)}else{[Console]::Out.Write('OCERR rv='+$r.ReturnValue)}}catch{[Console]::Out.Write('OCERR '+$_.Exception.Message)}`;
|
|
434
|
+
const b64 = Buffer.from(ps, "utf16le").toString("base64");
|
|
435
|
+
const res = child_process.spawnSync(
|
|
436
|
+
powershellPath(),
|
|
437
|
+
["-NoProfile", "-NonInteractive", "-EncodedCommand", b64],
|
|
438
|
+
{ timeout: 5e3, encoding: "utf8" }
|
|
439
|
+
);
|
|
440
|
+
const m = (res.stdout ?? "").match(/OCPID\s+(\d+)/);
|
|
441
|
+
if (m) return Number(m[1]);
|
|
442
|
+
pdebug("wmi spawn failed:", (res.stdout ?? "").trim(), "|", (res.stderr ?? "").trim());
|
|
443
|
+
return 0;
|
|
444
|
+
} catch (err) {
|
|
445
|
+
pdebug("wmi spawn threw", err.message);
|
|
446
|
+
return 0;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function escapeToPersistentPainter(payload, consolePid) {
|
|
450
|
+
try {
|
|
451
|
+
const dir = path3__namespace.join(os.homedir(), ".config", "opencrater");
|
|
452
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
453
|
+
const file = path3__namespace.join(dir, `paint-${process.pid}-${Date.now()}.json`);
|
|
454
|
+
fs2.writeFileSync(file, JSON.stringify(payload), "utf8");
|
|
455
|
+
const q = (s) => `"${s}"`;
|
|
456
|
+
const parts = [
|
|
457
|
+
q(process.execPath),
|
|
458
|
+
q(__filename),
|
|
459
|
+
"--persist",
|
|
460
|
+
"--payload-file",
|
|
461
|
+
q(file),
|
|
462
|
+
"--console-pid",
|
|
463
|
+
String(consolePid)
|
|
464
|
+
];
|
|
465
|
+
if (DEBUG_ON) parts.push("--debug");
|
|
466
|
+
const pid = wmiSpawnDetached(parts.join(" "));
|
|
467
|
+
pdebug("escape: wmi persistent painter pid", pid);
|
|
468
|
+
return pid > 0;
|
|
469
|
+
} catch (err) {
|
|
470
|
+
pdebug("escape failed", err.message);
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
392
474
|
function windowsVtOk(env) {
|
|
393
475
|
if (env["WT_SESSION"]) return true;
|
|
394
476
|
if (env["TERM_PROGRAM"]) return true;
|
|
@@ -728,9 +810,10 @@ function releaseLock() {
|
|
|
728
810
|
} catch {
|
|
729
811
|
}
|
|
730
812
|
}
|
|
813
|
+
var DEBUG_ON = process.env["OPENCRATER_DEBUG"] === "1";
|
|
731
814
|
function pdebug(...args) {
|
|
732
815
|
try {
|
|
733
|
-
if (
|
|
816
|
+
if (!DEBUG_ON) return;
|
|
734
817
|
const line = `[opencrater-paint] ${args.map((a) => String(a)).join(" ")}`;
|
|
735
818
|
console.error(line);
|
|
736
819
|
try {
|
|
@@ -745,16 +828,38 @@ function pdebug(...args) {
|
|
|
745
828
|
}
|
|
746
829
|
}
|
|
747
830
|
function main() {
|
|
748
|
-
const
|
|
831
|
+
const argv = process.argv.slice(2);
|
|
832
|
+
const argVal = (flag) => {
|
|
833
|
+
const i = argv.indexOf(flag);
|
|
834
|
+
return i >= 0 ? argv[i + 1] : void 0;
|
|
835
|
+
};
|
|
836
|
+
if (argv.includes("--debug")) {
|
|
837
|
+
DEBUG_ON = true;
|
|
838
|
+
process.env["OPENCRATER_DEBUG"] = "1";
|
|
839
|
+
}
|
|
840
|
+
const persist = argv.includes("--persist") || process.env["OPENCRATER_WIN_PERSIST"] === "1";
|
|
841
|
+
const consolePidArg = Number(argVal("--console-pid") ?? "0");
|
|
842
|
+
if (consolePidArg > 0) {
|
|
843
|
+
process.env["OPENCRATER_WIN_CONSOLE_PID"] = String(consolePidArg);
|
|
844
|
+
}
|
|
845
|
+
const payloadFile = argVal("--payload-file");
|
|
846
|
+
let raw = process.env["OPENCRATER_PAINT"];
|
|
847
|
+
if (payloadFile) {
|
|
848
|
+
try {
|
|
849
|
+
raw = fs2.readFileSync(payloadFile, "utf8");
|
|
850
|
+
} catch (err) {
|
|
851
|
+
pdebug("payload-file unreadable:", err.message);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
749
854
|
if (!raw) {
|
|
750
|
-
pdebug("no
|
|
855
|
+
pdebug("no paint payload (env or --payload-file) \u2014 nothing to draw");
|
|
751
856
|
return;
|
|
752
857
|
}
|
|
753
858
|
let payload;
|
|
754
859
|
try {
|
|
755
860
|
payload = JSON.parse(raw);
|
|
756
861
|
} catch (err) {
|
|
757
|
-
pdebug("
|
|
862
|
+
pdebug("paint payload is not valid JSON:", err.message);
|
|
758
863
|
return;
|
|
759
864
|
}
|
|
760
865
|
const ttyPath = process.env["OPENCRATER_TTY"] ?? "/dev/tty";
|
|
@@ -807,13 +912,13 @@ function main() {
|
|
|
807
912
|
}
|
|
808
913
|
const mediaRowCount = isVisualMedia ? Math.max(8, Math.min(16, Math.floor((termRows - 14) / 2) * 2)) : MEDIA_ROWS;
|
|
809
914
|
const effectiveRows = sideMedia ? 5 : mediaRowCount;
|
|
810
|
-
|
|
915
|
+
let width = Math.min(maxWidth, cols - 4);
|
|
811
916
|
if (width < 24) {
|
|
812
917
|
sink.close();
|
|
813
918
|
return;
|
|
814
919
|
}
|
|
815
|
-
|
|
816
|
-
|
|
920
|
+
let inner = width - 4;
|
|
921
|
+
let col = Math.max(1, cols - width - 1);
|
|
817
922
|
const startRow = 2;
|
|
818
923
|
const painterStartedAt = Date.now();
|
|
819
924
|
const dim = color ? `${ESC}[2m` : "";
|
|
@@ -1319,12 +1424,42 @@ function main() {
|
|
|
1319
1424
|
};
|
|
1320
1425
|
if (IS_WINDOWS) {
|
|
1321
1426
|
void (async () => {
|
|
1427
|
+
const real = sink.size ? await sink.size : null;
|
|
1428
|
+
if (real && real.cols >= 24) {
|
|
1429
|
+
cols = real.cols;
|
|
1430
|
+
width = Math.min(maxWidth, cols - 4);
|
|
1431
|
+
inner = width - 4;
|
|
1432
|
+
col = Math.max(1, cols - width - 1);
|
|
1433
|
+
pdebug("windows geometry from pump:", "cols", cols, "width", width, "col", col, "ownerPid", real.pid);
|
|
1434
|
+
}
|
|
1322
1435
|
if (format === "image" || format === "logo" || format === "gif" || format === "video") {
|
|
1323
1436
|
await upgradeVisual();
|
|
1324
1437
|
} else if (format === "audio") {
|
|
1325
1438
|
await upgradeAudio();
|
|
1326
1439
|
}
|
|
1327
1440
|
if (payload.audioUrl && format !== "audio") void upgradeAudio(payload.audioUrl);
|
|
1441
|
+
if (persist) {
|
|
1442
|
+
const duration2 = Math.min(Math.max(payload.durationMs ?? DEFAULT_DURATION_MS, 3e3), 12e4);
|
|
1443
|
+
deadlineAt = Date.now() + duration2;
|
|
1444
|
+
if (payloadFile) {
|
|
1445
|
+
try {
|
|
1446
|
+
fs2.unlinkSync(payloadFile);
|
|
1447
|
+
} catch {
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
const framesNow = ansiFrames;
|
|
1451
|
+
const animated = !!(framesNow && framesNow.length > 1);
|
|
1452
|
+
paint();
|
|
1453
|
+
interval = setInterval(paint, animated ? ANIM_MS : REPAINT_MS);
|
|
1454
|
+
setTimeout(() => cleanup(), duration2);
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const ownerPid = real?.pid ?? 0;
|
|
1458
|
+
if (ownerPid > 0 && escapeToPersistentPainter(payload, ownerPid)) {
|
|
1459
|
+
sink.close();
|
|
1460
|
+
process.exit(0);
|
|
1461
|
+
}
|
|
1462
|
+
pdebug("no job-escape (ownerPid", ownerPid, ") \u2014 bounded toast fallback");
|
|
1328
1463
|
const envHold = Number(process.env["OPENCRATER_WIN_HOLD_MS"]);
|
|
1329
1464
|
const holdMs = envHold > 0 ? envHold : Math.min(payload.durationMs ?? DEFAULT_DURATION_MS, 5e3);
|
|
1330
1465
|
deadlineAt = Date.now() + holdMs;
|