freebuff 0.0.94 → 0.0.96

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 (2) hide show
  1. package/index.js +108 -26
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -17,11 +17,9 @@ const packageName = 'freebuff'
17
17
  * Terminal escape sequences to reset terminal state after the child process exits.
18
18
  * When the binary is SIGKILL'd, it can't clean up its own terminal state.
19
19
  * The wrapper (this process) survives and must reset these modes.
20
- *
21
- * Keep in sync with TERMINAL_RESET_SEQUENCES in cli/src/utils/renderer-cleanup.ts
22
20
  */
23
- const TERMINAL_RESET_SEQUENCES =
24
- '\x1b[?1049l' + // Exit alternate screen buffer
21
+ const EXIT_ALTERNATE_SCREEN_SEQUENCE = '\x1b[?1049l'
22
+ const SAFE_TERMINAL_RESET_SEQUENCES =
25
23
  '\x1b[?1000l' + // Disable X10 mouse mode
26
24
  '\x1b[?1002l' + // Disable button event mouse mode
27
25
  '\x1b[?1003l' + // Disable any-event mouse mode (all motion)
@@ -30,7 +28,12 @@ const TERMINAL_RESET_SEQUENCES =
30
28
  '\x1b[?2004l' + // Disable bracketed paste mode
31
29
  '\x1b[?25h' // Show cursor
32
30
 
33
- function resetTerminal() {
31
+ const FULL_TERMINAL_RESET_SEQUENCES =
32
+ EXIT_ALTERNATE_SCREEN_SEQUENCE + SAFE_TERMINAL_RESET_SEQUENCES
33
+
34
+ function resetTerminal(options = {}) {
35
+ const { exitAlternateScreen = false } = options
36
+
34
37
  try {
35
38
  if (process.stdin.isTTY && process.stdin.setRawMode) {
36
39
  process.stdin.setRawMode(false)
@@ -40,13 +43,37 @@ function resetTerminal() {
40
43
  }
41
44
  try {
42
45
  if (process.stdout.isTTY) {
43
- process.stdout.write(TERMINAL_RESET_SEQUENCES)
46
+ // Exiting the alternate screen is only safe after an interactive child.
47
+ // Plain CLI paths like --help never enter it, and ?1049l can erase output.
48
+ process.stdout.write(
49
+ exitAlternateScreen
50
+ ? FULL_TERMINAL_RESET_SEQUENCES
51
+ : SAFE_TERMINAL_RESET_SEQUENCES,
52
+ )
44
53
  }
45
54
  } catch {
46
55
  // stdout may be closed
47
56
  }
48
57
  }
49
58
 
59
+ function getUnsignedExitCode(code) {
60
+ return code != null && code < 0 ? (code >>> 0) : code
61
+ }
62
+
63
+ function isWindowsNativeCrashCode(code) {
64
+ const unsignedCode = getUnsignedExitCode(code)
65
+ return (
66
+ process.platform === 'win32' &&
67
+ (unsignedCode === 0xC000001D ||
68
+ unsignedCode === 0xC0000005 ||
69
+ unsignedCode === 0xC0000409)
70
+ )
71
+ }
72
+
73
+ function shouldExitAlternateScreen(code, signal) {
74
+ return Boolean(signal) || isWindowsNativeCrashCode(code)
75
+ }
76
+
50
77
  function createConfig(packageName) {
51
78
  const homeDir = os.homedir()
52
79
  const configDir = path.join(homeDir, '.config', 'manicode')
@@ -472,27 +499,21 @@ async function checkForUpdates(runningProcess, exitListener) {
472
499
  }, 5000)
473
500
  })
474
501
 
475
- resetTerminal()
502
+ resetTerminal({ exitAlternateScreen: true })
476
503
  console.log(`Update available: ${currentVersion} → ${latestVersion}`)
477
504
 
478
505
  await downloadBinary(latestVersion)
479
506
 
480
- const newChild = spawn(CONFIG.binaryPath, process.argv.slice(2), {
481
- stdio: 'inherit',
482
- detached: false,
483
- })
507
+ const newChild = spawnInstalledBinary({ detached: false })
484
508
 
485
509
  newChild.on('exit', (code, signal) => {
486
- resetTerminal()
510
+ resetTerminal({
511
+ exitAlternateScreen: shouldExitAlternateScreen(code, signal),
512
+ })
487
513
  printCrashDiagnostics(code, signal)
488
514
  process.exit(signal ? 1 : (code || 0))
489
515
  })
490
516
 
491
- newChild.on('error', (err) => {
492
- console.error('Failed to start freebuff:', err.message)
493
- process.exit(1)
494
- })
495
-
496
517
  return new Promise(() => {})
497
518
  }
498
519
  } catch (error) {
@@ -502,7 +523,7 @@ async function checkForUpdates(runningProcess, exitListener) {
502
523
 
503
524
  function printCrashDiagnostics(code, signal) {
504
525
  // Windows NTSTATUS codes (unsigned DWORD)
505
- const unsignedCode = code != null && code < 0 ? (code >>> 0) : code
526
+ const unsignedCode = getUnsignedExitCode(code)
506
527
  const isIllegalInstruction =
507
528
  signal === 'SIGILL' ||
508
529
  (process.platform === 'win32' && unsignedCode === 0xC000001D)
@@ -548,26 +569,87 @@ function printCrashDiagnostics(code, signal) {
548
569
  console.error('')
549
570
  }
550
571
 
551
- async function main() {
552
- await ensureBinaryExists()
572
+ function getInstalledBinaryStatus() {
573
+ try {
574
+ const stats = fs.statSync(CONFIG.binaryPath)
575
+ return stats.isFile() ? `yes (${formatBytes(stats.size)})` : 'no'
576
+ } catch {
577
+ return 'no'
578
+ }
579
+ }
580
+
581
+ function printSpawnFailure(err) {
582
+ resetTerminal()
583
+ const code = err && err.code ? ` (${err.code})` : ''
584
+
585
+ console.error(`Failed to start ${packageName}: ${err.message}${code}`)
586
+ console.error('')
587
+ console.error('System info:')
588
+ console.error(` Platform: ${process.platform} ${process.arch}`)
589
+ console.error(` Node: ${process.version}`)
590
+ console.error(` Binary: ${CONFIG.binaryPath}`)
591
+ console.error(` Exists: ${getInstalledBinaryStatus()}`)
592
+
593
+ if (process.platform === 'win32') {
594
+ console.error('')
595
+ console.error(
596
+ 'On Windows, this can happen when Windows Security or antivirus blocks',
597
+ )
598
+ console.error(
599
+ 'or quarantines the downloaded executable, or when the binary requires',
600
+ )
601
+ console.error('CPU instructions that are not available on this machine.')
602
+ }
603
+
604
+ console.error('')
605
+ console.error('Try deleting the downloaded files and running again:')
606
+ console.error(` ${CONFIG.configDir}`)
607
+ console.error('')
608
+ }
609
+
610
+ function spawnInstalledBinary(options = {}) {
611
+ if (!fs.existsSync(CONFIG.binaryPath)) {
612
+ try {
613
+ if (fs.existsSync(CONFIG.metadataPath)) fs.unlinkSync(CONFIG.metadataPath)
614
+ } catch {
615
+ // best effort
616
+ }
617
+ const error = new Error(
618
+ `downloaded binary is missing at ${CONFIG.binaryPath}`,
619
+ )
620
+ error.code = 'BINARY_MISSING'
621
+ printSpawnFailure(error)
622
+ process.exit(1)
623
+ }
553
624
 
554
625
  const child = spawn(CONFIG.binaryPath, process.argv.slice(2), {
555
626
  stdio: 'inherit',
627
+ ...options,
628
+ })
629
+
630
+ child.on('error', (err) => {
631
+ printSpawnFailure(err)
632
+ process.exit(1)
556
633
  })
557
634
 
635
+ return child
636
+ }
637
+
638
+ async function main() {
639
+ await ensureBinaryExists()
640
+
641
+ const child = spawnInstalledBinary()
642
+
558
643
  const exitListener = (code, signal) => {
559
- resetTerminal()
644
+ resetTerminal({
645
+ exitAlternateScreen: shouldExitAlternateScreen(code, signal),
646
+ })
560
647
  printCrashDiagnostics(code, signal)
561
648
  process.exit(signal ? 1 : (code || 0))
562
649
  }
563
650
 
564
651
  child.on('exit', exitListener)
565
652
 
566
- child.on('error', (err) => {
567
- console.error('Failed to start freebuff:', err.message)
568
- process.exit(1)
569
- })
570
-
571
653
  setTimeout(() => {
572
654
  checkForUpdates(child, exitListener)
573
655
  }, 100)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freebuff",
3
- "version": "0.0.94",
3
+ "version": "0.0.96",
4
4
  "description": "The world's strongest free coding agent",
5
5
  "license": "MIT",
6
6
  "bin": {