@opencrater/sdk 0.8.49 → 0.8.51

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.

Files changed (2) hide show
  1. package/dist/paint.js +118 -14
  2. package/package.json +1 -1
package/dist/paint.js CHANGED
@@ -298,11 +298,18 @@ 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
- "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) } } } }",
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",
@@ -313,7 +320,7 @@ var WIN_PUMP_PS = [
313
320
  // free). The painter blocks on this line, then anchors the card to the true
314
321
  // right edge. win.R/L/B/T are the on-screen window rect (not the scrollback
315
322
  // buffer), so this is exactly what `mode con` SHOULD have returned.
316
- "if($fb){ $cols=$fb.win.R-$fb.win.L+1; $rows=$fb.win.B-$fb.win.T+1; [Console]::Out.WriteLine('OCSIZE '+$cols+' '+$rows); [Console]::Out.Flush() }",
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() }",
317
324
  "$m=[uint32]0;[void][OCP.N]::GetConsoleMode($h,[ref]$m);[void][OCP.N]::SetConsoleMode($h,($m -bor 4));[void][OCP.N]::SetConsoleOutputCP(65001)",
318
325
  // Stream frames straight to the console as they arrive. The painter drives
319
326
  // the timing — it repaints the overlay (live countdown, position defence)
@@ -373,10 +380,10 @@ function spawnWindowsConsoleSink() {
373
380
  out.setEncoding("utf8");
374
381
  out.on("data", (chunk) => {
375
382
  acc += chunk;
376
- const m = acc.match(/OCSIZE\s+(\d+)\s+(\d+)/);
383
+ const m = acc.match(/OCWIN\s+(\d+)\s+(\d+)\s+(\d+)/);
377
384
  if (m) {
378
- pdebug("pump reported console size", m[1], "x", m[2]);
379
- done({ cols: Number(m[1]), rows: Number(m[2]) });
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]) });
380
387
  }
381
388
  });
382
389
  out.on("error", () => done(null));
@@ -420,6 +427,50 @@ function spawnWindowsConsoleSink() {
420
427
  return null;
421
428
  }
422
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, _env: process.env }), "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
+ }
423
474
  function windowsVtOk(env) {
424
475
  if (env["WT_SESSION"]) return true;
425
476
  if (env["TERM_PROGRAM"]) return true;
@@ -759,9 +810,10 @@ function releaseLock() {
759
810
  } catch {
760
811
  }
761
812
  }
813
+ var DEBUG_ON = process.env["OPENCRATER_DEBUG"] === "1";
762
814
  function pdebug(...args) {
763
815
  try {
764
- if (process.env["OPENCRATER_DEBUG"] !== "1") return;
816
+ if (!DEBUG_ON) return;
765
817
  const line = `[opencrater-paint] ${args.map((a) => String(a)).join(" ")}`;
766
818
  console.error(line);
767
819
  try {
@@ -776,16 +828,46 @@ function pdebug(...args) {
776
828
  }
777
829
  }
778
830
  function main() {
779
- const raw = process.env["OPENCRATER_PAINT"];
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
+ }
780
854
  if (!raw) {
781
- pdebug("no OPENCRATER_PAINT payload in env \u2014 nothing to draw");
855
+ pdebug("no paint payload (env or --payload-file) \u2014 nothing to draw");
782
856
  return;
783
857
  }
784
858
  let payload;
785
859
  try {
786
- payload = JSON.parse(raw);
860
+ const parsed = JSON.parse(raw);
861
+ if (parsed._env && typeof parsed._env === "object") {
862
+ const keepPid = process.env["OPENCRATER_WIN_CONSOLE_PID"];
863
+ Object.assign(process.env, parsed._env);
864
+ if (keepPid) process.env["OPENCRATER_WIN_CONSOLE_PID"] = keepPid;
865
+ if (process.env["OPENCRATER_DEBUG"] === "1") DEBUG_ON = true;
866
+ delete parsed._env;
867
+ }
868
+ payload = parsed;
787
869
  } catch (err) {
788
- pdebug("OPENCRATER_PAINT payload is not valid JSON:", err.message);
870
+ pdebug("paint payload is not valid JSON:", err.message);
789
871
  return;
790
872
  }
791
873
  const ttyPath = process.env["OPENCRATER_TTY"] ?? "/dev/tty";
@@ -1356,7 +1438,7 @@ function main() {
1356
1438
  width = Math.min(maxWidth, cols - 4);
1357
1439
  inner = width - 4;
1358
1440
  col = Math.max(1, cols - width - 1);
1359
- pdebug("windows geometry from pump:", "cols", cols, "width", width, "col", col);
1441
+ pdebug("windows geometry from pump:", "cols", cols, "width", width, "col", col, "ownerPid", real.pid);
1360
1442
  }
1361
1443
  if (format === "image" || format === "logo" || format === "gif" || format === "video") {
1362
1444
  await upgradeVisual();
@@ -1364,6 +1446,28 @@ function main() {
1364
1446
  await upgradeAudio();
1365
1447
  }
1366
1448
  if (payload.audioUrl && format !== "audio") void upgradeAudio(payload.audioUrl);
1449
+ if (persist) {
1450
+ const duration2 = Math.min(Math.max(payload.durationMs ?? DEFAULT_DURATION_MS, 3e3), 12e4);
1451
+ deadlineAt = Date.now() + duration2;
1452
+ if (payloadFile) {
1453
+ try {
1454
+ fs2.unlinkSync(payloadFile);
1455
+ } catch {
1456
+ }
1457
+ }
1458
+ const framesNow = ansiFrames;
1459
+ const animated = !!(framesNow && framesNow.length > 1);
1460
+ paint();
1461
+ interval = setInterval(paint, animated ? ANIM_MS : REPAINT_MS);
1462
+ setTimeout(() => cleanup(), duration2);
1463
+ return;
1464
+ }
1465
+ const ownerPid = real?.pid ?? 0;
1466
+ if (ownerPid > 0 && escapeToPersistentPainter(payload, ownerPid)) {
1467
+ sink.close();
1468
+ process.exit(0);
1469
+ }
1470
+ pdebug("no job-escape (ownerPid", ownerPid, ") \u2014 bounded toast fallback");
1367
1471
  const envHold = Number(process.env["OPENCRATER_WIN_HOLD_MS"]);
1368
1472
  const holdMs = envHold > 0 ? envHold : Math.min(payload.durationMs ?? DEFAULT_DURATION_MS, 5e3);
1369
1473
  deadlineAt = Date.now() + holdMs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencrater/sdk",
3
- "version": "0.8.49",
3
+ "version": "0.8.51",
4
4
  "description": "OpenCrater SDK — sponsor cards for CLI tools and MCP servers. Fail-silent, zero dependencies.",
5
5
  "keywords": [
6
6
  "opencrater",